From e868002d503eb766934f75c44de8eb9d1e0fbf86 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Mon, 27 Apr 2026 19:31:31 +0700 Subject: [PATCH] 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
) - update title/meta description, add keywords + Open Graph/Twitter tags - delete unused js/mode-toggle.js; prune mode-toggle and tabpanel CSS --- README.md | 17 +++++-- index.html | 115 +++++++++++++++++----------------------------- js/mode-toggle.js | 70 ---------------------------- style.css | 69 ++++++++-------------------- 4 files changed, 71 insertions(+), 200 deletions(-) delete mode 100644 js/mode-toggle.js diff --git a/README.md b/README.md index b64b361..31819e7 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/index.html b/index.html index 632bd5a..c9c764d 100644 --- a/index.html +++ b/index.html @@ -3,15 +3,31 @@ - Lựa chọn ngôn ngữ lập trình theo phong thuỷ - + Phong thuỷ ngôn ngữ lập trình — phân loại theo Ngũ Hành + + + + + + + +
-

Lựa chọn ngôn ngữ lập trình theo phong thuỷ

+

Phong thuỷ ngôn ngữ lập trình

Ngũ Hành tương sinh — code cho hợp tuổi, hợp mệnh.

-
- - -
- -
-
-
- Sơ đồ phong thuỷ ngôn ngữ lập trình theo Ngũ Hành -
Sơ đồ ngôn ngữ lập trình xếp theo Ngũ Hành.
-
-
- -
-

Ngũ Hành & ngôn ngữ (theo bài gốc, 2018)

-
-
-

KIM

-

JavaScript, Objective-C, Python

-
-
-

THUỶ

-

C++, CSS, PHP

-
-
-

MỘC

-

Android, C#, Node.js

-
-
-

HOẢ

-

Java, HTML, Scala, Ruby

-
-
-

THỔ

-

JavaScript, Go

-
-
-

* Bảng phân loại mang tính giải trí, lấy từ ảnh gốc.

-
+
+

Ngũ Hành & ngôn ngữ Nguồn: GitHub Linguist

+
+
+
+
+
+

+ +

* 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).

-
- diff --git a/js/mode-toggle.js b/js/mode-toggle.js deleted file mode 100644 index 1e12f39..0000000 --- a/js/mode-toggle.js +++ /dev/null @@ -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); diff --git a/style.css b/style.css index 587e800..6684e57 100644 --- a/style.css +++ b/style.css @@ -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; } }