mirror of
https://github.com/tiennm99/programming-fengshui.git
synced 2026-05-20 22:24:22 +00:00
e1ac51c31f
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
121 lines
4.0 KiB
JavaScript
121 lines
4.0 KiB
JavaScript
import { ELEMENTS, hexToHsl } from './classify-element.js';
|
|
|
|
function pickTextColor(hex) {
|
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
const g = parseInt(hex.slice(3, 5), 16);
|
|
const b = parseInt(hex.slice(5, 7), 16);
|
|
return (r * 299 + g * 587 + b * 114) / 1000 >= 128 ? 'black' : 'white';
|
|
}
|
|
|
|
function buildChip(name, color, { rank = null } = {}) {
|
|
const span = document.createElement('span');
|
|
span.className = 'chip' + (rank ? ' chip-tiobe' : ' chip-other');
|
|
span.textContent = name;
|
|
if (color) {
|
|
span.style.background = color;
|
|
span.style.color = pickTextColor(color);
|
|
span.title = rank ? `${color} · TIOBE #${rank}` : color;
|
|
}
|
|
if (rank) span.dataset.rank = String(rank);
|
|
return span;
|
|
}
|
|
|
|
export function renderGrid(buckets, mountEl) {
|
|
if (!mountEl) return;
|
|
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 = 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, 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');
|
|
p.className = 'render-error';
|
|
p.textContent = message;
|
|
mountEl.prepend(p);
|
|
}
|
|
|
|
const HUE_BOUNDARIES = [20, 40, 70, 200, 260];
|
|
|
|
export function isBorderline(hex) {
|
|
const { h, s, l } = hexToHsl(hex);
|
|
if (s >= 4 && s < 6) return true;
|
|
if (s < 5) return (l >= 18 && l <= 22) || (l >= 68 && l <= 72);
|
|
return HUE_BOUNDARIES.some((b) => Math.abs(h - b) <= 2);
|
|
}
|
|
|
|
export function renderDebugPanel({ skipped, borderline }, mountEl) {
|
|
if (!mountEl) return;
|
|
if (!skipped.length && !borderline.length) {
|
|
mountEl.hidden = true;
|
|
return;
|
|
}
|
|
|
|
const fragment = document.createDocumentFragment();
|
|
const summary = document.createElement('summary');
|
|
summary.textContent = `Kiểm tra tự động (${skipped.length} skipped · ${borderline.length} borderline)`;
|
|
fragment.appendChild(summary);
|
|
|
|
if (skipped.length) {
|
|
const h4 = document.createElement('h4');
|
|
h4.textContent = 'Bỏ qua (không có màu)';
|
|
const list = document.createElement('div');
|
|
list.className = 'chips';
|
|
for (const { name } of skipped) list.appendChild(buildChip(name, null));
|
|
fragment.append(h4, list);
|
|
}
|
|
|
|
if (borderline.length) {
|
|
const h4 = document.createElement('h4');
|
|
h4.textContent = 'Trường hợp ranh giới';
|
|
const list = document.createElement('div');
|
|
list.className = 'chips';
|
|
for (const { name, color, element } of borderline) {
|
|
const chip = buildChip(`${name} → ${element}`, color);
|
|
chip.title = `${color} → ${element}`;
|
|
list.appendChild(chip);
|
|
}
|
|
fragment.append(h4, list);
|
|
}
|
|
|
|
mountEl.replaceChildren(fragment);
|
|
mountEl.hidden = false;
|
|
}
|