diff --git a/index.html b/index.html index a3c885b..7a015b4 100644 --- a/index.html +++ b/index.html @@ -67,10 +67,11 @@ Ngũ Hành & ngôn ngữ (tự phân loại theo màu GitHub) + - * Phân loại tự động theo màu chính thức GitHub Linguist + quy tắc HSL. + * Mặc định hiển thị TIOBE Top 20 (April 2026). Bấm "Xem tất cả ngôn ngữ" để mở rộng. Ảnh gốc diff --git a/js/main.js b/js/main.js index 898ffa1..38ad124 100644 --- a/js/main.js +++ b/js/main.js @@ -1,5 +1,6 @@ import { classify } from './classify-element.js'; -import { renderGrid, renderError, renderDebugPanel, isBorderline } from './render-elements.js'; +import { renderGrid, renderError, renderDebugPanel, isBorderline, mountViewToggle } from './render-elements.js'; +import { TIOBE_TOP } from './tiobe-top.js'; const HEX_RE = /^#[0-9a-fA-F]{6}$/; const LEGEND_TEXT = @@ -27,15 +28,22 @@ async function init() { continue; } const element = classify(color); - buckets[element].push({ name, color }); + const rank = TIOBE_TOP[name] || null; + buckets[element].push({ name, color, rank }); if (isBorderline(color)) borderline.push({ name, color, element }); } for (const key of Object.keys(buckets)) { - buckets[key].sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); + buckets[key].sort((a, b) => { + if (a.rank && b.rank) return a.rank - b.rank; + if (a.rank) return -1; + if (b.rank) return 1; + return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }); + }); } renderGrid(buckets, gridEl); + mountViewToggle(document.getElementById('view-toggle'), document.querySelector('#panel-modern .elements')); if (legendEl) legendEl.textContent = LEGEND_TEXT; renderDebugPanel({ skipped, borderline }, debugEl); } catch (err) { diff --git a/js/render-elements.js b/js/render-elements.js index 8cf526d..de3d6fe 100644 --- a/js/render-elements.js +++ b/js/render-elements.js @@ -7,15 +7,16 @@ function pickTextColor(hex) { return (r * 299 + g * 587 + b * 114) / 1000 >= 128 ? 'black' : 'white'; } -function buildChip(name, color) { +function buildChip(name, color, { rank = null } = {}) { const span = document.createElement('span'); - span.className = 'chip'; + span.className = 'chip' + (rank ? ' chip-tiobe' : ' chip-other'); span.textContent = name; if (color) { span.style.background = color; span.style.color = pickTextColor(color); - span.title = color; + span.title = rank ? `${color} · TIOBE #${rank}` : color; } + if (rank) span.dataset.rank = String(rank); return span; } @@ -24,22 +25,45 @@ export function renderGrid(buckets, mountEl) { const fragment = document.createDocumentFragment(); for (const { key, label } of ELEMENTS) { const langs = buckets[key] || []; + const tiobeCount = langs.filter((l) => l.rank).length; const card = document.createElement('article'); card.className = `card ${key}`; const h3 = document.createElement('h3'); h3.textContent = label; const count = document.createElement('small'); count.className = 'card-count'; - count.textContent = `${langs.length} ngôn ngữ`; + count.textContent = tiobeCount + ? `${tiobeCount} TIOBE · ${langs.length - tiobeCount} khác` + : `${langs.length} ngôn ngữ`; const chips = document.createElement('div'); chips.className = 'chips'; - for (const { name, color } of langs) chips.appendChild(buildChip(name, color)); + for (const { name, color, rank } of langs) chips.appendChild(buildChip(name, color, { rank })); card.append(h3, count, chips); fragment.appendChild(card); } mountEl.replaceChildren(fragment); } +export function mountViewToggle(mountEl, scopeEl) { + if (!mountEl || !scopeEl) return; + const btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'view-toggle-btn'; + const labels = { + tiobe: 'Xem tất cả ngôn ngữ', + all: 'Chỉ TIOBE Top 20', + }; + function apply(view) { + scopeEl.classList.toggle('show-all', view === 'all'); + btn.textContent = labels[view]; + btn.dataset.view = view; + btn.setAttribute('aria-pressed', String(view === 'all')); + } + btn.addEventListener('click', () => apply(btn.dataset.view === 'all' ? 'tiobe' : 'all')); + apply('tiobe'); + mountEl.replaceChildren(btn); +} + export function renderError(message, mountEl) { if (!mountEl) return; const p = document.createElement('p'); diff --git a/js/tiobe-top.js b/js/tiobe-top.js new file mode 100644 index 0000000..aa7887f --- /dev/null +++ b/js/tiobe-top.js @@ -0,0 +1,25 @@ +// TIOBE Index Top 20, April 2026 snapshot. +// Keys are exact GitHub Linguist names. Scratch is omitted (not tracked by Linguist). +// Source: https://www.tiobe.com/tiobe-index/ + +export const TIOBE_TOP = { + 'Python': 1, + 'C': 2, + 'C++': 3, + 'Java': 4, + 'C#': 5, + 'JavaScript': 6, + 'Visual Basic .NET': 7, + 'SQL': 8, + 'R': 9, + 'Pascal': 10, + 'Perl': 12, + 'Fortran': 13, + 'PHP': 14, + 'Go': 15, + 'Rust': 16, + 'MATLAB': 17, + 'Assembly': 18, + 'Swift': 19, + 'Ada': 20, +}; diff --git a/style.css b/style.css index 619f1ab..d99e458 100644 --- a/style.css +++ b/style.css @@ -278,6 +278,47 @@ body { color: var(--fg); } +.chip-tiobe { + border-color: rgba(0, 0, 0, 0.35); + font-weight: 600; +} + +.elements .chip-other { display: none; } +.elements.show-all .chip-other { display: inline-block; } + +#view-toggle { + display: flex; + justify-content: center; + margin: 0 0 1rem; +} + +.view-toggle-btn { + font: inherit; + font-size: 0.85rem; + padding: 0.35rem 0.9rem; + border-radius: 999px; + border: 1px solid var(--muted); + background: var(--card-bg); + color: var(--fg); + cursor: pointer; + transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; +} + +.view-toggle-btn:hover { + border-color: var(--accent); +} + +.view-toggle-btn[aria-pressed="true"] { + background: var(--accent); + color: #fff; + border-color: var(--accent); +} + +.view-toggle-btn:focus-visible { + outline: 2px solid var(--gold); + outline-offset: 2px; +} + .legend { text-align: center; font-style: italic;
* Phân loại tự động theo màu chính thức GitHub Linguist + quy tắc HSL.
* Mặc định hiển thị TIOBE Top 20 (April 2026). Bấm "Xem tất cả ngôn ngữ" để mở rộng.