mirror of
https://github.com/tiennm99/programming-fengshui.git
synced 2026-09-03 12:22:03 +00:00
style: ui/ux audit pass — a11y, contrast, brand cohesion
- Design tokens: add --bg-tint, --accent-ink, --border, --shadow-sm, --radius, --focus-ring; Vietnamese-friendly font stack (system-first, no network) - Contrast: darken --muted; nudge --kim/--moc/--tho for AA on cream bg - Brand: warm radial wash + hero Ngũ Hành ribbon (5 element-color bars) - A11y: skip link, focus-ring on tabs/links/details, custom ▸ chevron - Chip text picker: YIQ → WCAG relative luminance (fixes mid-tone flips) - Chip aria-label exposes name · #HEX · TIOBE rank for screen readers - Mobile: 1-col collapse moved 500px → 420px; mode-toggle wraps at 320px - Reduced-motion: respected on card hover transforms and panel fade
This commit is contained in:
+9
-1
@@ -8,10 +8,18 @@
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<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>
|
||||
<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>
|
||||
<span class="r-moc"></span>
|
||||
<span class="r-thuy"></span>
|
||||
<span class="r-hoa"></span>
|
||||
<span class="r-tho"></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="mode-toggle" role="tablist" aria-label="Chế độ hiển thị">
|
||||
|
||||
+16
-5
@@ -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;
|
||||
|
||||
@@ -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 | `<details>` 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 + `<details>` summaries have no `:focus-visible`. |
|
||||
| 9 | Low | `<small class="mode-tag">` inline inside `<h2>` | 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. **`<details>` 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 `<details>` 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 `<body>`.
|
||||
2. Added `id="main-content"` to `<main>` for skip link target.
|
||||
3. Added `<div class="hero-ribbon" aria-hidden="true">` with 5 element spans inside `<header class="hero">`.
|
||||
4. Did NOT change `Credit` heading (constraint: footer content byte-identical). Did NOT change classic 5 cards, hero h1, original image, or any link text.
|
||||
|
||||
### `js/render-elements.js`
|
||||
|
||||
1. Replaced YIQ `pickTextColor` with WCAG-style relative luminance. Threshold 0.5 produces correct white-on-mid-tone results that the old YIQ flubbed. Tested mentally against `#d44950` (Apex), `#5d4037` (Brown), `#427819` (XSLT) — all now get white correctly.
|
||||
2. Chip `aria-label` + `title` now read `"<name> · <#HEX> · TIOBE #<rank>"` — hex is now SR-announceable and visible on hover. Previously the hex only lived in a `title` (invisible on touch).
|
||||
3. File 120 → 131 lines, still well under cap.
|
||||
|
||||
### Files NOT touched (intentional)
|
||||
|
||||
- `js/classify-element.js` — algorithm correct, well-documented, out of UX scope.
|
||||
- `js/main.js` — orchestration, not UX.
|
||||
- `js/mode-toggle.js` — tablist works, keyboard nav correct, hash sync correct.
|
||||
- `js/tiobe-top.js` — pure data.
|
||||
- `data/github-colors.json` — vendored data.
|
||||
- The original 5 classic cards, image, hero h1, footer text — per constraint.
|
||||
|
||||
---
|
||||
|
||||
## (c) Things intentionally NOT changed
|
||||
|
||||
- **"Credit" heading stays English** — constraint said footer content byte-identical. Would prefer "Nguồn" but won't violate spec; logged as open question.
|
||||
- **No webfont fetch** — `Be Vietnam Pro` is added to the stack only as a hint; if not installed, system stack handles Vietnamese diacritics correctly. Constraint said try system-stack first.
|
||||
- **No dark mode** — out of scope for a one-page joke; the warm cream IS the brand. Adding a dark variant would dilute the lunar-new-year palette without user benefit.
|
||||
- **Classic cards, hero text, image, links** — per hard constraints.
|
||||
- **Element keys remain lowercase `kim/moc/thuy/hoa/tho`** — per constraint.
|
||||
- **No build step / framework** — vanilla HTML/CSS/ES modules only.
|
||||
- **No new top-level files** — only edited existing ones.
|
||||
- **`hexToHsl` import in render-elements.js** — kept (used by `isBorderline`); did not refactor classifier to share.
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
- All assets serve 200 over local HTTP server (`index.html`, `style.css`, `js/main.js`, `js/render-elements.js`, `data/github-colors.json`).
|
||||
- File sizes: `style.css` 302 lines, `index.html` 121, all JS files ≤ 131. Cap = 400.
|
||||
- No new files outside allowed roots. No npm, no fonts pulled over network, no markdown outside `plans/`.
|
||||
|
||||
---
|
||||
|
||||
## Open questions
|
||||
|
||||
1. "Credit" heading is English in a Vietnamese page — kept due to byte-identical constraint. Confirm if "Nguồn" is acceptable in a follow-up; trivial 1-line change.
|
||||
2. `Be Vietnam Pro` is in the font stack as a hint — if you'd prefer truly system-only, drop the first two stack entries. Current behavior: zero network calls regardless, just better rendering on systems that have it.
|
||||
3. Element accent palette was nudged for AA contrast (`--kim/--moc/--tho`). If the original hexes are part of a documented brand spec, revert and accept the contrast hit on h3-only.
|
||||
4. Hero ribbon order is Kim → Mộc → Thuỷ → Hoả → Thổ (Ngũ Hành tương sinh — generative cycle: Metal→Wood→Water→Fire→Earth). Confirm if you'd prefer the destructive cycle order or a different element ordering.
|
||||
5. Mobile 1-column collapse breakpoint moved 500→420px. At 375px users now see 2 cols; some may prefer the larger-tap 1-col layout — easy to tune.
|
||||
|
||||
**Status:** DONE
|
||||
**Summary:** Shipped 12 targeted UI/UX fixes across `style.css`, `index.html`, `js/render-elements.js` — contrast, mobile overflow, focus rings, brand ribbon, accessible chip labels, distinct disclosure styling. All hard constraints respected. CSS file 302 lines (<400 cap), no new files, no build step.
|
||||
@@ -1,96 +1,120 @@
|
||||
/* Phong-thuỷ ngôn ngữ lập trình — design tokens & layout.
|
||||
Palette: warm cream + accent red + gold (lunar-new-year).
|
||||
Element accents (kim/moc/thuy/hoa/tho) kept as brand. */
|
||||
|
||||
:root {
|
||||
--bg: #fdf6ec;
|
||||
--bg-tint: #f7ecd8;
|
||||
--fg: #2b2118;
|
||||
--muted: #6b5a48;
|
||||
--muted: #5b4a38; /* darkened for AA on cream */
|
||||
--accent: #b8312f;
|
||||
--accent-ink: #8d1f1d;
|
||||
--gold: #c79b3c;
|
||||
--card-bg: #ffffff;
|
||||
--shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
|
||||
--border: rgba(43, 33, 24, 0.12);
|
||||
--border-strong: rgba(43, 33, 24, 0.22);
|
||||
|
||||
--kim: #d4af37;
|
||||
--kim: #c8932a;
|
||||
--thuy: #2e6fb5;
|
||||
--moc: #3a8f4a;
|
||||
--moc: #2f7d3f;
|
||||
--hoa: #c0392b;
|
||||
--tho: #8a6d3b;
|
||||
--tho: #7a5f30;
|
||||
|
||||
--shadow: 0 6px 18px rgba(58, 36, 12, 0.10);
|
||||
--shadow-sm: 0 2px 6px rgba(58, 36, 12, 0.08);
|
||||
--radius-sm: 8px;
|
||||
--radius: 12px;
|
||||
--focus-ring: 0 0 0 3px rgba(199, 155, 60, 0.45);
|
||||
|
||||
--font-sans: "Be Vietnam Pro", "Inter", "Segoe UI", system-ui, -apple-system,
|
||||
"Helvetica Neue", Arial, "Noto Sans", "Noto Sans Vietnamese", sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; }
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Segoe UI", "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
||||
background: var(--bg);
|
||||
font-family: var(--font-sans);
|
||||
background: radial-gradient(1200px 600px at 50% -120px, #fff5dd 0%, transparent 60%), var(--bg);
|
||||
color: var(--fg);
|
||||
line-height: 1.55;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
top: 0;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
padding: 0.5rem 0.9rem;
|
||||
border-radius: 0 0 8px 0;
|
||||
font-weight: 600;
|
||||
z-index: 100;
|
||||
}
|
||||
.skip-link:focus { left: 0; }
|
||||
|
||||
.page {
|
||||
max-width: 880px;
|
||||
margin: 0 auto;
|
||||
padding: 2.5rem 1.25rem 3rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
padding: 2.25rem 1rem 3rem;
|
||||
}
|
||||
|
||||
/* ---------- Hero ---------- */
|
||||
.hero { text-align: center; margin-bottom: 1.75rem; padding: 0 0.25rem; }
|
||||
.hero h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(1.6rem, 4vw, 2.4rem);
|
||||
color: var(--accent);
|
||||
letter-spacing: 0.5px;
|
||||
font-size: clamp(1.55rem, 4.4vw, 2.5rem);
|
||||
line-height: 1.2; color: var(--accent); letter-spacing: 0.2px; font-weight: 700;
|
||||
}
|
||||
|
||||
.hero .subtitle {
|
||||
margin: 0.5rem 0 0;
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.figure {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1.5rem 0 2.5rem;
|
||||
.hero .subtitle { margin: 0.5rem auto 0; max-width: 38ch; color: var(--muted); font-style: italic; }
|
||||
.hero-ribbon {
|
||||
display: flex; justify-content: center; gap: 6px;
|
||||
margin: 0.9rem auto 0; width: max-content; max-width: 100%;
|
||||
}
|
||||
.hero-ribbon span { display: block; width: 28px; height: 4px; border-radius: 2px; }
|
||||
.hero-ribbon .r-kim { background: var(--kim); }
|
||||
.hero-ribbon .r-moc { background: var(--moc); }
|
||||
.hero-ribbon .r-thuy { background: var(--thuy); }
|
||||
.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; }
|
||||
.figure figure {
|
||||
margin: 0;
|
||||
background: var(--card-bg);
|
||||
padding: 1rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow);
|
||||
max-width: 100%;
|
||||
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 {
|
||||
display: block; max-width: 100%; height: auto; border-radius: var(--radius-sm);
|
||||
}
|
||||
.figure figcaption, .original-image figcaption {
|
||||
text-align: center; margin-top: 0.6rem; color: var(--muted); font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.figure img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
/* ---------- Section headings ---------- */
|
||||
.elements h2, .credit h2 {
|
||||
text-align: center; color: var(--accent);
|
||||
margin: 1.75rem 0 1rem; font-weight: 700; letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.figure figcaption {
|
||||
text-align: center;
|
||||
margin-top: 0.75rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.elements h2,
|
||||
.credit h2 {
|
||||
text-align: center;
|
||||
color: var(--accent);
|
||||
margin: 2rem 0 1rem;
|
||||
.mode-tag {
|
||||
display: block; font-size: 0.85rem; font-style: italic;
|
||||
color: var(--muted); font-weight: 400; margin-top: 0.15rem;
|
||||
}
|
||||
|
||||
/* ---------- Card grid ---------- */
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 0.9rem;
|
||||
@@ -98,265 +122,181 @@ body {
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card-bg);
|
||||
border-radius: 10px;
|
||||
padding: 1rem 1rem 1.1rem;
|
||||
box-shadow: var(--shadow);
|
||||
border-top: 4px solid var(--gold);
|
||||
transition: transform 0.15s ease;
|
||||
position: relative; background: var(--card-bg);
|
||||
border: 1px solid var(--border); border-top: 4px solid var(--gold);
|
||||
border-radius: var(--radius); padding: 0.95rem 1rem 1.05rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
.card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.card { transition: none; }
|
||||
.card:hover { transform: none; }
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.card h3 { margin: 0 0 0.45rem; font-size: 1.05rem; letter-spacing: 1.2px; font-weight: 700; }
|
||||
.card p { margin: 0; color: var(--fg); }
|
||||
|
||||
.card h3 {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 1.1rem;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.card.kim { border-top-color: var(--kim); } .card.kim h3 { color: var(--kim); }
|
||||
.card.thuy { border-top-color: var(--thuy); } .card.thuy h3 { color: var(--thuy); }
|
||||
.card.moc { border-top-color: var(--moc); } .card.moc h3 { color: var(--moc); }
|
||||
.card.hoa { border-top-color: var(--hoa); } .card.hoa h3 { color: var(--hoa); }
|
||||
.card.tho { border-top-color: var(--tho); } .card.tho h3 { color: var(--tho); }
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.card.kim { border-top-color: var(--kim); }
|
||||
.card.kim h3 { color: var(--kim); }
|
||||
.card.thuy { border-top-color: var(--thuy); }
|
||||
.card.thuy h3 { color: var(--thuy); }
|
||||
.card.moc { border-top-color: var(--moc); }
|
||||
.card.moc h3 { color: var(--moc); }
|
||||
.card.hoa { border-top-color: var(--hoa); }
|
||||
.card.hoa h3 { color: var(--hoa); }
|
||||
.card.tho { border-top-color: var(--tho); }
|
||||
.card.tho h3 { color: var(--tho); }
|
||||
|
||||
.disclaimer {
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.disclaimer { text-align: center; color: var(--muted); font-size: 0.85rem; margin-top: 1rem; }
|
||||
|
||||
/* ---------- Credit ---------- */
|
||||
.credit {
|
||||
margin-top: 2.5rem;
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 1.25rem 1.5rem;
|
||||
box-shadow: var(--shadow);
|
||||
margin-top: 2.5rem; background: var(--card-bg);
|
||||
border: 1px solid var(--border); border-radius: var(--radius);
|
||||
padding: 1.25rem 1.4rem 1.4rem; box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.credit p {
|
||||
margin: 0.4rem 0;
|
||||
}
|
||||
|
||||
.credit ul {
|
||||
margin: 0.5rem 0 0.75rem 1.25rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.credit p { margin: 0.4rem 0; }
|
||||
.credit ul { margin: 0.5rem 0 0.75rem 1.25rem; padding: 0; }
|
||||
.credit a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
color: var(--accent-ink); text-decoration: none;
|
||||
border-bottom: 1px dotted var(--accent);
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
.credit a:hover { color: var(--gold); border-bottom-color: var(--gold); }
|
||||
.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; }
|
||||
|
||||
.credit a:hover {
|
||||
color: var(--gold);
|
||||
border-bottom-color: var(--gold);
|
||||
}
|
||||
|
||||
.credit .note {
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ===== Dual-mode toggle + panels ===== */
|
||||
/* ---------- 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;
|
||||
padding: 0.45rem 1rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--muted);
|
||||
background: var(--card-bg);
|
||||
color: var(--fg);
|
||||
cursor: pointer;
|
||||
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);
|
||||
}
|
||||
|
||||
.mode-toggle [role="tab"]:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.mode-toggle [role="tab"]:focus-visible {
|
||||
outline: 2px solid var(--gold);
|
||||
outline-offset: 2px;
|
||||
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; }
|
||||
|
||||
.mode-tag {
|
||||
font-size: 0.85rem;
|
||||
font-style: italic;
|
||||
color: var(--muted);
|
||||
font-weight: normal;
|
||||
margin-left: 0.4rem;
|
||||
@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; }
|
||||
}
|
||||
|
||||
/* ---------- Original-image disclosure ---------- */
|
||||
.original-image {
|
||||
margin: 2rem 0 0;
|
||||
background: var(--card-bg);
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow);
|
||||
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 {
|
||||
cursor: pointer;
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
.original-image > summary,
|
||||
#debug-panel > summary {
|
||||
cursor: pointer; list-style: none; font-weight: 500;
|
||||
display: inline-flex; align-items: center; gap: 0.4rem;
|
||||
}
|
||||
.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; }
|
||||
|
||||
.original-image img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.original-image figcaption {
|
||||
text-align: center;
|
||||
margin-top: 0.5rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
[role="tabpanel"]:not([hidden]) { animation: fadeIn 150ms ease; }
|
||||
}
|
||||
|
||||
/* ===== Chip grid (modern panel) ===== */
|
||||
/* ---------- Chip grid (modern panel) ---------- */
|
||||
.card-count {
|
||||
display: block;
|
||||
margin: 0 0 0.6rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.8rem;
|
||||
display: block; margin: 0 0 0.6rem;
|
||||
color: var(--muted); font-size: 0.78rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.chips { display: flex; flex-wrap: wrap; gap: 0.35rem; }
|
||||
|
||||
.chip {
|
||||
display: inline-block;
|
||||
padding: 0.18rem 0.55rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.4;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
white-space: nowrap;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
display: inline-flex; align-items: center;
|
||||
padding: 0.22rem 0.6rem; border-radius: 999px;
|
||||
font-size: 0.78rem; line-height: 1.4;
|
||||
border: 1px solid rgba(0, 0, 0, 0.18); white-space: nowrap;
|
||||
background: var(--bg); color: var(--fg);
|
||||
max-width: 100%; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chip-tiobe {
|
||||
border-color: rgba(0, 0, 0, 0.35);
|
||||
font-weight: 600;
|
||||
border-color: rgba(0, 0, 0, 0.45); font-weight: 600;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.elements .chip-other { display: none; }
|
||||
.elements.show-all .chip-other { display: inline-block; }
|
||||
.elements.show-all .chip-other { display: inline-flex; }
|
||||
|
||||
#view-toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
#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;
|
||||
font: inherit; font-size: 0.85rem;
|
||||
padding: 0.4rem 0.95rem; 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;
|
||||
}
|
||||
|
||||
.view-toggle-btn:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.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;
|
||||
background: var(--accent); color: #fff; border-color: var(--accent);
|
||||
}
|
||||
.view-toggle-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
|
||||
|
||||
.legend {
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
margin: 1rem 0 0;
|
||||
text-align: center; font-style: italic;
|
||||
color: var(--muted); font-size: 0.85rem;
|
||||
margin: 1rem auto 0; max-width: 60ch;
|
||||
}
|
||||
|
||||
.render-error {
|
||||
text-align: center;
|
||||
color: var(--accent);
|
||||
padding: 1rem;
|
||||
border: 1px dashed var(--accent);
|
||||
border-radius: 8px;
|
||||
text-align: center; color: var(--accent-ink);
|
||||
padding: 0.9rem 1rem; border: 1px dashed var(--accent);
|
||||
border-radius: var(--radius-sm); background: #fff7f6;
|
||||
}
|
||||
|
||||
/* ---------- Debug panel ---------- */
|
||||
#debug-panel {
|
||||
margin-top: 1rem;
|
||||
background: var(--card-bg);
|
||||
background: var(--bg-tint);
|
||||
border: 1px dashed var(--border-strong);
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
#debug-panel[open] { background: var(--card-bg); }
|
||||
#debug-panel h4 { margin: 0.85rem 0 0.4rem; font-size: 0.9rem; color: var(--fg); }
|
||||
|
||||
#debug-panel summary {
|
||||
cursor: pointer;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
#debug-panel h4 {
|
||||
margin: 0.75rem 0 0.4rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.chip { font-size: 0.72rem; padding: 0.15rem 0.45rem; }
|
||||
/* ---------- Mobile ---------- */
|
||||
@media (max-width: 420px) {
|
||||
.page { padding: 1.75rem 0.85rem 2.5rem; }
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
.mode-toggle [role="tab"] { padding: 0.35rem 0.75rem; font-size: 0.9rem; }
|
||||
.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