refactor: drop classic page, modern is sole view with original image inline

- remove tablist + panel-classic; lift modern panel out of tabpanel wrapper
- inline 2018 source image as visible figure (was collapsible <details>)
- update title/meta description, add keywords + Open Graph/Twitter tags
- delete unused js/mode-toggle.js; prune mode-toggle and tabpanel CSS
This commit is contained in:
2026-04-27 19:31:31 +07:00
parent 651b123f05
commit e868002d50
4 changed files with 71 additions and 200 deletions
+12 -5
View File
@@ -1,10 +1,10 @@
# programming-fengshui
Trang giới thiệu ảnh "Lựa chọn ngôn ngữ lập trình theo phong thuỷ".
Trang phân loại ngôn ngữ lập trình theo Ngũ Hành (Kim, Mộc, Thuỷ, Hoả, Thổ) dựa trên màu sắc của GitHub Linguist / GitLab Linguist, tập trung TIOBE Top 20.
## Chạy thử
Mở `index.html` trực tiếp trong trình duyệt, hoặc:
Chạy qua HTTP server (cần thiết để `fetch` dữ liệu JSON):
```bash
python3 -m http.server 8080
@@ -18,9 +18,16 @@ rồi truy cập http://localhost:8080.
.
├── index.html
├── style.css
├── assets/
── ngon-ngu-lap-trinh-phong-thuy.png
└── README.md
├── js/
── main.js
│ ├── classify-element.js
│ ├── render-elements.js
│ └── tiobe-top.js
├── data/
│ ├── github-colors.json
│ └── gitlab-colors.json
└── assets/
└── ngon-ngu-lap-trinh-phong-thuy.png
```
## Credit
+41 -74
View File
@@ -3,15 +3,31 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lựa chọn ngôn ngữ lập trình theo phong thuỷ</title>
<meta name="description" content="Chọn ngôn ngữ lập trình theo Ngũ Hành: Kim, Mộc, Thuỷ, Hoả, Thổ — vui là chính." />
<title>Phong thuỷ ngôn ngữ lập trình — phân loại theo Ngũ Hành</title>
<meta
name="description"
content="Phân loại ngôn ngữ lập trình theo Ngũ Hành (Kim, Mộc, Thuỷ, Hoả, Thổ) dựa trên màu sắc của GitHub Linguist và GitLab Linguist, tập trung TIOBE Top 20."
/>
<meta
name="keywords"
content="phong thuỷ, ngôn ngữ lập trình, ngũ hành, kim mộc thuỷ hoả thổ, GitHub Linguist, GitLab Linguist, TIOBE"
/>
<meta name="author" content="tiennm99" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Phong thuỷ ngôn ngữ lập trình — phân loại theo Ngũ Hành" />
<meta
property="og:description"
content="Phân loại ngôn ngữ lập trình theo Ngũ Hành dựa trên màu Linguist (GitHub/GitLab), tập trung TIOBE Top 20."
/>
<meta property="og:image" content="assets/ngon-ngu-lap-trinh-phong-thuy.png" />
<meta name="twitter:card" content="summary_large_image" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#main-content">Bỏ qua tới nội dung chính</a>
<main class="page" id="main-content">
<header class="hero">
<h1>Lựa chọn ngôn ngữ lập trình theo phong thuỷ</h1>
<h1>Phong thuỷ ngôn ngữ lập trình</h1>
<p class="subtitle">Ngũ Hành tương sinh — code cho hợp tuổi, hợp mệnh.</p>
<div class="hero-ribbon" aria-hidden="true">
<span class="r-kim"></span>
@@ -22,78 +38,30 @@
</div>
</header>
<div class="mode-toggle" role="tablist" aria-label="Chế độ hiển thị">
<button type="button" role="tab" id="tab-classic" data-mode="classic"
aria-controls="panel-classic" aria-selected="true" tabindex="0">
Cổ điển (bài gốc)
</button>
<button type="button" role="tab" id="tab-modern" data-mode="modern"
aria-controls="panel-modern" aria-selected="false" tabindex="-1">
Hiện đại (tự phân loại)
</button>
</div>
<section id="panel-classic" role="tabpanel" aria-labelledby="tab-classic">
<section class="figure">
<figure>
<img
src="assets/ngon-ngu-lap-trinh-phong-thuy.png"
alt="Sơ đồ phong thuỷ ngôn ngữ lập trình theo Ngũ Hành"
/>
<figcaption>Sơ đồ ngôn ngữ lập trình xếp theo Ngũ Hành.</figcaption>
</figure>
</section>
<section class="elements">
<h2>Ngũ Hành &amp; ngôn ngữ <small class="mode-tag">(theo bài gốc, 2018)</small></h2>
<div class="grid">
<article class="card kim">
<h3>KIM</h3>
<p>JavaScript, Objective-C, Python</p>
</article>
<article class="card thuy">
<h3>THUỶ</h3>
<p>C++, CSS, PHP</p>
</article>
<article class="card moc">
<h3>MỘC</h3>
<p>Android, C#, Node.js</p>
</article>
<article class="card hoa">
<h3>HOẢ</h3>
<p>Java, HTML, Scala, Ruby</p>
</article>
<article class="card tho">
<h3>THỔ</h3>
<p>JavaScript, Go</p>
</article>
</div>
<p class="disclaimer">* Bảng phân loại mang tính giải trí, lấy từ ảnh gốc.</p>
</section>
<section class="elements">
<h2>Ngũ Hành &amp; ngôn ngữ <small class="mode-tag" id="source-tag">Nguồn: GitHub Linguist</small></h2>
<div class="modern-controls">
<div id="source-toggle"></div>
<div id="view-toggle"></div>
</div>
<div class="grid" id="element-grid"></div>
<p class="legend"></p>
<details id="debug-panel" hidden></details>
<p class="disclaimer">* Mặc định hiển thị TIOBE Top 20 (April 2026). Bấm "Xem tất cả ngôn ngữ" để mở rộng. Có thể đổi nguồn màu giữa GitHub Linguist và GitLab Linguist (palette khác hẳn).</p>
</section>
<section id="panel-modern" role="tabpanel" aria-labelledby="tab-modern" hidden>
<section class="elements">
<h2>Ngũ Hành &amp; ngôn ngữ <small class="mode-tag" id="source-tag">Nguồn: GitHub Linguist</small></h2>
<div class="modern-controls">
<div id="source-toggle"></div>
<div id="view-toggle"></div>
</div>
<div class="grid" id="element-grid"></div>
<p class="legend"></p>
<details id="debug-panel" hidden></details>
<p class="disclaimer">* Mặc định hiển thị TIOBE Top 20 (April 2026). Bấm "Xem tất cả ngôn ngữ" để mở rộng. Có thể đổi nguồn màu giữa GitHub Linguist và GitLab Linguist (palette khác hẳn).</p>
</section>
<details class="original-image">
<summary>Ảnh gốc</summary>
<figure>
<img
src="assets/ngon-ngu-lap-trinh-phong-thuy.png"
alt="Sơ đồ phong thuỷ ngôn ngữ lập trình theo Ngũ Hành"
/>
<figcaption>Sơ đồ ngôn ngữ lập trình xếp theo Ngũ Hành.</figcaption>
</figure>
</details>
<section class="figure" aria-labelledby="classic-image-heading">
<h2 id="classic-image-heading" class="visually-hidden">Ảnh gốc 2018</h2>
<figure>
<img
src="assets/ngon-ngu-lap-trinh-phong-thuy.png"
alt="Sơ đồ phong thuỷ ngôn ngữ lập trình theo Ngũ Hành (bài gốc 2018)"
/>
<figcaption>
Sơ đồ gốc (2018) — Phạm Huy Hoàng,
<a href="https://toidicodedao.com/2018/01/02/top-5-ngon-ngu-lap-trinh-dang-hoc-cho-nam-2018/" target="_blank" rel="noopener noreferrer">Tôi đi code dạo</a>.
</figcaption>
</figure>
</section>
<footer class="credit">
@@ -118,7 +86,6 @@
<p class="note">Trang này chỉ dùng cho mục đích trình bày lại nội dung, mọi quyền thuộc về tác giả gốc.</p>
</footer>
</main>
<script type="module" src="./js/mode-toggle.js"></script>
<script type="module" src="./js/main.js"></script>
</body>
</html>
-70
View File
@@ -1,70 +0,0 @@
// Reserved hashes: #classic, #modern. Do not reuse for in-page anchors.
const MODES = ['classic', 'modern'];
const DEFAULT_MODE = 'classic';
function readHash() {
const h = (location.hash || '').replace('#', '');
return MODES.includes(h) ? h : DEFAULT_MODE;
}
function setMode(mode, { writeHash = true, push = false } = {}) {
if (!MODES.includes(mode)) mode = DEFAULT_MODE;
for (const m of MODES) {
const panel = document.getElementById(`panel-${m}`);
const tab = document.getElementById(`tab-${m}`);
if (panel) panel.hidden = m !== mode;
if (tab) {
tab.setAttribute('aria-selected', String(m === mode));
tab.tabIndex = m === mode ? 0 : -1;
}
}
if (writeHash) {
const target = '#' + mode;
if (push) {
location.hash = target;
} else if (location.hash !== target) {
history.replaceState(null, '', target);
}
}
}
function onTabClick(e) {
setMode(e.currentTarget.dataset.mode, { push: true });
}
function onTabListKeydown(e) {
const keys = ['ArrowLeft', 'ArrowRight', 'Home', 'End'];
if (!keys.includes(e.key)) return;
e.preventDefault();
const tabs = MODES.map((m) => document.getElementById(`tab-${m}`));
const current = tabs.indexOf(document.activeElement);
let next;
if (e.key === 'Home') next = 0;
else if (e.key === 'End') next = tabs.length - 1;
else if (e.key === 'ArrowLeft') next = (current <= 0 ? tabs.length : current) - 1;
else next = (current + 1) % tabs.length;
const target = tabs[next];
if (target) {
target.focus();
setMode(target.dataset.mode, { push: true });
}
}
function onHashChange() {
setMode(readHash(), { writeHash: false });
}
document.addEventListener('DOMContentLoaded', () => {
for (const m of MODES) {
const tab = document.getElementById(`tab-${m}`);
if (tab) tab.addEventListener('click', onTabClick);
}
const list = document.querySelector('[role="tablist"]');
if (list) list.addEventListener('keydown', onTabListKeydown);
setMode(readHash(), { writeHash: false });
});
window.addEventListener('hashchange', onHashChange);
+18 -51
View File
@@ -90,19 +90,24 @@ body {
.hero-ribbon .r-hoa { background: var(--hoa); }
.hero-ribbon .r-tho { background: var(--tho); }
/* ---------- Classic figure ---------- */
.figure { display: flex; justify-content: center; margin: 1.25rem 0 2.25rem; }
/* ---------- Original figure ---------- */
.figure { display: flex; justify-content: center; margin: 2rem 0 0; }
.figure figure {
margin: 0; background: var(--card-bg); padding: 0.85rem;
border: 1px solid var(--border); border-radius: var(--radius);
box-shadow: var(--shadow); max-width: 100%;
}
.figure img, .original-image img {
.figure img {
display: block; max-width: 100%; height: auto; border-radius: var(--radius-sm);
}
.figure figcaption, .original-image figcaption {
.figure figcaption {
text-align: center; margin-top: 0.6rem; color: var(--muted); font-size: 0.9rem;
}
.figure figcaption a {
color: var(--accent-ink); text-decoration: none;
border-bottom: 1px dotted var(--accent);
}
.figure figcaption a:hover { color: var(--gold); border-bottom-color: var(--gold); }
/* ---------- Section headings ---------- */
.elements h2, .credit h2 {
@@ -163,62 +168,30 @@ body {
.credit a:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 3px; }
.credit .note { font-size: 0.85rem; color: var(--muted); font-style: italic; }
/* ---------- Mode toggle (tablist) ---------- */
.mode-toggle {
display: flex;
flex-wrap: wrap; /* avoid 320px overflow */
justify-content: center;
gap: 0.5rem;
margin: 1rem 0 1.5rem;
}
.mode-toggle [role="tab"] {
font: inherit; font-weight: 500;
padding: 0.5rem 1.05rem; border-radius: 999px;
border: 1px solid var(--border-strong);
background: var(--card-bg); color: var(--fg); cursor: pointer;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.mode-toggle [role="tab"]:hover { border-color: var(--accent); }
.mode-toggle [role="tab"][aria-selected="true"] {
background: var(--accent); color: #fff; border-color: var(--accent);
box-shadow: 0 2px 6px rgba(184, 49, 47, 0.25);
}
.mode-toggle [role="tab"]:focus-visible { outline: none; box-shadow: var(--focus-ring); }
[role="tabpanel"][hidden] { display: none; }
@media (prefers-reduced-motion: no-preference) {
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
[role="tabpanel"]:not([hidden]) { animation: fadeIn 180ms ease; }
/* ---------- Visually hidden (a11y) ---------- */
.visually-hidden {
position: absolute;
width: 1px; height: 1px;
margin: -1px; padding: 0;
overflow: hidden; clip: rect(0 0 0 0);
white-space: nowrap; border: 0;
}
/* ---------- Original-image disclosure ---------- */
.original-image {
margin: 2rem 0 0; background: var(--card-bg);
border: 1px solid var(--border); padding: 0.85rem 1rem;
border-radius: var(--radius); box-shadow: var(--shadow-sm);
}
.original-image > summary,
/* ---------- Debug panel disclosure ---------- */
#debug-panel > summary {
cursor: pointer; list-style: none; font-weight: 500;
display: inline-flex; align-items: center; gap: 0.4rem;
color: var(--muted);
}
.original-image > summary { color: var(--accent-ink); }
#debug-panel > summary { color: var(--muted); }
.original-image > summary::-webkit-details-marker,
#debug-panel > summary::-webkit-details-marker { display: none; }
.original-image > summary::before,
#debug-panel > summary::before {
content: "▸"; display: inline-block;
transition: transform 0.15s ease; color: var(--gold);
}
.original-image[open] > summary::before,
#debug-panel[open] > summary::before { transform: rotate(90deg); }
.original-image > summary:focus-visible,
#debug-panel > summary:focus-visible {
outline: none; box-shadow: var(--focus-ring); border-radius: 4px;
}
.original-image figure { margin: 0.75rem 0 0; }
/* ---------- Chip grid (modern panel) ---------- */
.card-count {
@@ -311,14 +284,8 @@ body {
@media (max-width: 420px) {
.page { padding: 1.75rem 0.85rem 2.5rem; }
.chip { font-size: 0.74rem; padding: 0.18rem 0.5rem; }
.mode-toggle [role="tab"] {
padding: 0.4rem 0.85rem; font-size: 0.92rem;
flex: 1 1 auto; text-align: center; min-width: 0;
}
}
@media (max-width: 320px) {
.hero h1 { font-size: 1.4rem; }
.mode-toggle { gap: 0.4rem; }
.mode-toggle [role="tab"] { padding: 0.4rem 0.6rem; font-size: 0.88rem; }
}