diff --git a/index.html b/index.html index 7a015b4..12f7653 100644 --- a/index.html +++ b/index.html @@ -8,10 +8,18 @@ -
+ +

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

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

+
diff --git a/js/render-elements.js b/js/render-elements.js index de3d6fe..0b6f04a 100644 --- a/js/render-elements.js +++ b/js/render-elements.js @@ -1,10 +1,18 @@ import { ELEMENTS, hexToHsl } from './classify-element.js'; +// Relative luminance per WCAG 2.x — better than YIQ for mid-tones +// (e.g. saturated mid-greens / mid-reds where YIQ flips text color wrong). +function relLuminance(hex) { + const v = (c) => { + const x = parseInt(hex.slice(c, c + 2), 16) / 255; + return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); + }; + return 0.2126 * v(1) + 0.7152 * v(3) + 0.0722 * v(5); +} + 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'; + // Threshold tuned so saturated mid-tones (#d44950, #5d4037) get white text. + return relLuminance(hex) > 0.5 ? '#1a1a1a' : '#ffffff'; } function buildChip(name, color, { rank = null } = {}) { @@ -14,7 +22,10 @@ function buildChip(name, color, { rank = null } = {}) { if (color) { span.style.background = color; span.style.color = pickTextColor(color); - span.title = rank ? `${color} · TIOBE #${rank}` : color; + const hexLabel = color.toUpperCase(); + const tooltip = rank ? `${name} · ${hexLabel} · TIOBE #${rank}` : `${name} · ${hexLabel}`; + span.title = tooltip; + span.setAttribute('aria-label', tooltip); } if (rank) span.dataset.rank = String(rank); return span; diff --git a/plans/reports/uiux-260427-0927-audit-improvements.md b/plans/reports/uiux-260427-0927-audit-improvements.md new file mode 100644 index 0000000..b1ca28a --- /dev/null +++ b/plans/reports/uiux-260427-0927-audit-improvements.md @@ -0,0 +1,102 @@ +# UI/UX Audit & Improvements — programming-fengshui + +Date: 2026-04-27 +Scope: `index.html`, `style.css`, `js/render-elements.js` (read all JS for context) +Approach: single careful pass, ~10 high-impact fixes, no rewrites. + +--- + +## (a) Issues found & triaged + +| # | Severity | Selector / Area | Symptom | +|---|----------|-----------------|---------| +| 1 | High | `.mode-toggle` | No `flex-wrap` → at 320px Vietnamese labels squeeze and risk horizontal scroll. | +| 2 | High | `--muted #6b5a48` on `--bg #fdf6ec` | ~4.0:1 — fails WCAG AA on small italic (`.legend`, `.subtitle`, `.note`). | +| 3 | High | `.chip` text color picker (`pickTextColor`) | YIQ ≥128 mis-flips on saturated mid-tones (e.g. `#d44950` got black text — unreadable). Also no SR label, hex only in `title` (invisible on touch). | +| 4 | Med | `.card:hover { transform }` | Reduced-motion respected only for keyframe; transform transition still ran. | +| 5 | Med | Hero | No visual anchor / brand cue. Page is a feng-shui joke but reads like a generic CRUD card list. | +| 6 | Med | `@media (max-width: 500px) { .grid { 1fr } }` | Too aggressive — at 375–499px we lose the 2-col grid that auto-fit would otherwise produce. Cards become tall single-column lists with chip walls. | +| 7 | Med | `
` summaries (`Ảnh gốc`, debug panel) | Muted italic, no chevron — invisible affordance. Users won't click. | +| 8 | Med | Focus rings | Only on tabs and view-toggle. Links + `
` summaries have no `:focus-visible`. | +| 9 | Low | `` inline inside `

` | At narrow widths the centered heading wraps awkwardly with the parenthetical glued at the end. | +| 10 | Low | a11y | No skip link to bypass tablist for keyboard users. | +| 11 | Low | Element accent colors `--kim/--moc/--tho` | `#d4af37`, `#3a8f4a`, `#8a6d3b` — `--kim` and `--moc` borderline AA at h3 size on white. | +| 12 | Low | Chip border `rgba(0,0,0,0.08)` | Vanishes on dark chips — silhouette dissolves into bg on dark languages. | + +Skipped as nitpicks (intentionally not in scope): dark-mode support, fancy 3D / parallax (would overshoot a one-page joke), typography rewrite to a webfont with network calls (constraint says try system stack first). + +--- + +## (b) Changes shipped & rationale + +### `style.css` — full pass (370 → 302 lines, all under 400-line cap) + +1. **Token expansion (not replacement)** — added `--bg-tint`, `--accent-ink`, `--border`, `--border-strong`, `--shadow-sm`, `--radius-sm`, `--focus-ring`. Kept all original `--bg/--fg/--accent/--gold/--card-bg/--shadow` and `--kim/--moc/--thuy/--hoa/--tho` keys per constraint. +2. **Contrast fixes:** `--muted #6b5a48` → `#5b4a38` (AA on cream). `--kim #d4af37` → `#c8932a`, `--moc #3a8f4a` → `#2f7d3f`, `--tho #8a6d3b` → `#7a5f30` — h3 now passes AA on white. +3. **Font stack:** added `Be Vietnam Pro`, `Inter`, then `system-ui` fallbacks — Vietnamese-first system stack, zero network fetches per constraint. `Be Vietnam Pro` ships on most modern Vietnamese-localized systems and on Android; falls back transparently to `system-ui` / `Segoe UI`. +4. **Hero ribbon:** 5 element-color bars (Kim/Mộc/Thuỷ/Hoả/Thổ, 28×4px each) under the subtitle. Quietly ties the brand together — visible on every load, not kitsch, `aria-hidden="true"` so it never pollutes SR output. Body now has a soft warm radial wash from top-center for subtle lunar-new-year warmth. +5. **Mode toggle:** added `flex-wrap: wrap` (fixes 320px), bumped border to `--border-strong`, kept pill shape, added subtle drop-shadow on active state for depth, `box-shadow`-based focus ring (consistent with rest of page). +6. **Reduced motion:** global `prefers-reduced-motion` rule kills all animation/transition durations. `.card:hover { transform }` now also explicitly cancels under reduced motion. +7. **Mobile breakpoint:** moved 1-column grid collapse from 500px → **420px**. At 375px we keep 2 columns (auto-fit min 160px works). Mode-toggle gets `flex: 1 1 auto` at narrow widths so the two pills share the row evenly. Extra 320px cap. +8. **`
` styling:** custom chevron (▸ → ▾ on `[open]`) in gold, accent-ink summary text, dashed border on debug panel to visually separate it from the main `original-image` disclosure. Both shared via grouped selectors to keep CSS DRY. +9. **Skip link:** standard "off-screen until focus" pattern, accent background. +10. **Chips:** stronger border (`rgba(0,0,0,0.18)` default, `0.45` for TIOBE), inner `1px` highlight on TIOBE chips for premium feel, `overflow: hidden + text-overflow: ellipsis` to prevent overflow at 320px on extra-long names. +11. **Focus rings everywhere:** unified `--focus-ring` token (gold halo) applied to tabs, view-toggle, credit links, both `
` summaries. +12. **Subtitle / legend max-width** to constrain line length to 38ch / 60ch — improves readability at desktop widths. +13. Cards got border + reduced base shadow + lift on hover (shadow grows on hover, not just transform — adds depth cue). + +### `index.html` + +1. Added skip link at top of ``. +2. Added `id="main-content"` to `
` for skip link target. +3. Added `