mirror of
https://github.com/tiennm99/programming-fengshui.git
synced 2026-09-02 22:17:52 +00:00
feat: focus modern grid on TIOBE Top 20 with 'Xem tất cả' expand toggle
Default view shows only TIOBE-ranked languages (19 chips total) for a cleaner first impression. A single button at the top of the modern panel toggles to the full 664-language list. Card counts now read 'X TIOBE · Y khác'. Chips of TIOBE-ranked languages get a slightly heavier border to stand out. - js/tiobe-top.js: TIOBE Top 20 (April 2026) keyed by GH Linguist names - js/main.js: tag entries with rank, sort TIOBE-first then alphabetical - js/render-elements.js: chip distinction + view-toggle mount - index.html: view-toggle slot + updated disclaimer - style.css: .chip-tiobe / .chip-other / .view-toggle-btn rules
This commit is contained in:
+2
-1
@@ -67,10 +67,11 @@
|
||||
<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">(tự phân loại theo màu GitHub)</small></h2>
|
||||
<div id="view-toggle"></div>
|
||||
<div class="grid" id="element-grid"></div>
|
||||
<p class="legend"></p>
|
||||
<details id="debug-panel" hidden></details>
|
||||
<p class="disclaimer">* Phân loại tự động theo màu chính thức GitHub Linguist + quy tắc HSL.</p>
|
||||
<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.</p>
|
||||
</section>
|
||||
<details class="original-image">
|
||||
<summary>Ảnh gốc</summary>
|
||||
|
||||
+11
-3
@@ -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) {
|
||||
|
||||
+29
-5
@@ -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');
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user