mirror of
https://github.com/tiennm99/programming-fengshui.git
synced 2026-09-02 06:20:57 +00:00
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:
@@ -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
@@ -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 & 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 & 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 & 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>
|
||||
|
||||
@@ -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);
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user