diff --git a/plans/260428-1003-implement-all-remaining/phase-01-style-and-markup-polish.md b/plans/260428-1003-implement-all-remaining/phase-01-style-and-markup-polish.md new file mode 100644 index 0000000..ed109cf --- /dev/null +++ b/plans/260428-1003-implement-all-remaining/phase-01-style-and-markup-polish.md @@ -0,0 +1,150 @@ +# Phase 01 — Style & markup polish + +## Context + +Items 5–10 (CSS one-liners), item 13 (chip `role="list"`/`"listitem"`), items 14/15 (decision documentation as code comments). Lowest risk, fastest win. Single pass through `style.css`, `index.html`, `js/render-elements.js`. + +## Overview + +- **Priority:** P2 (polish) + cleanup of decision docs +- **Status:** pending +- **Effort:** ~30 min hand-edit + 10 min manual verify +- **Risk:** very low — pure cosmetic / declarative + +## Files modified + +- `style.css` — items 5, 6, 7, 8, 9, 10, 15 +- `index.html` — item 8 (gold rule under h3 — markup-side none, just CSS), item 14 reference comment +- `js/render-elements.js` — item 13 (chip role attributes) + +## Implementation steps + +### 1. Item 5 — drop subtitle italic + +`style.css:81`: + +```css +/* Was: .hero .subtitle { ... font-style: italic; } + Italic Be Vietnam Pro renders rough on Linux. Use letter-spacing for emphasis. */ +.hero .subtitle { margin: 0.5rem auto 0; max-width: 38ch; color: var(--muted); letter-spacing: 0.15px; } +``` + +### 2. Item 6 — anchor underline migration (figcaption + credit) + +`style.css:123-125` and `style.css:185-188`: + +```css +/* Replace border-bottom: 1px dotted with native dotted underline. + Sub-pixel border at small font sizes can disappear. */ +.original-image figcaption a { + color: var(--accent-ink); + text-decoration: underline; + text-decoration-style: dotted; + text-decoration-color: var(--accent); + text-underline-offset: 2px; +} +.original-image figcaption a:hover { + color: var(--gold); + text-decoration-color: var(--gold); +} +.credit a { + color: var(--accent-ink); + text-decoration: underline; + text-decoration-style: dotted; + text-decoration-color: var(--accent); + text-underline-offset: 2px; + transition: color 0.15s ease, text-decoration-color 0.15s ease; +} +.credit a:hover { color: var(--gold); text-decoration-color: var(--gold); } +``` + +### 3. Item 7 — h3 letter-spacing reduction + +`style.css:161` — change `letter-spacing: 1.2px` → `0.4px` to stop diacritic drift on Vietnamese KIM/MỘC/THUỶ/HOẢ/THỔ. + +### 4. Item 8 — h3 size bump + thin gold rule + +`style.css:161` and a new rule: + +```css +.card h3 { + margin: 0 0 0.5rem; + font-size: 1.2rem; + letter-spacing: 0.4px; + font-weight: 700; + padding-bottom: 0.4rem; + border-bottom: 1px solid color-mix(in oklch, var(--gold) 60%, transparent); +} +``` + +### 5. Item 9 — Credit `
Nguồn: GitHub Linguist
+``` + +**2. Style the note in `style.css` (append to `.mode-tag` block):** + +```css +.source-note { + display: inline-block; + margin-left: 0.4rem; + padding: 0.05rem 0.45rem; + border-radius: 999px; + background: var(--bg-tint); + color: var(--accent-ink); + font-size: 0.7rem; + font-style: normal; + font-weight: 600; + letter-spacing: 0.2px; +} +``` + +**3. Update `loadAndRender()` in `js/main.js` to swap the note text:** + +```js +if (refs.sourceTag) refs.sourceTag.firstChild.nodeValue = `Nguồn: ${source.label} Linguist `; +const note = document.getElementById('source-note'); +if (note) { + if (sourceKey === 'gitlab') { + note.hidden = false; + note.textContent = '91 ngôn ngữ — palette riêng so với GitHub'; + } else { + note.hidden = true; + note.textContent = ''; + } +} +``` + +**4. Add `title` attr on the source-toggle group** — set in `mountSegmentedControl` via the existing `ariaLabel` plumbing or set directly in `init()`: + +```js +document.getElementById('source-toggle').setAttribute('title', 'GitHub có 664 ngôn ngữ; GitLab có 91 và dùng palette riêng'); +``` + +### Item 4 — Hide empty KIM card in TIOBE view + +`js/render-elements.js` `renderGrid()`: + +```js +export function renderGrid(buckets, mountEl) { + if (!mountEl) return; + const showAll = mountEl.closest('.elements')?.classList.contains('show-all') ?? false; + const fragment = document.createDocumentFragment(); + for (const { key, label } of ELEMENTS) { + const langs = buckets[key] || []; + /* In TIOBE view, hide cards with no languages — empty "0 ngôn ngữ" + headings look broken. All-langs view keeps every card so the + Ngũ Hành wheel stays visible. */ + if (!showAll && langs.length === 0) continue; + /* … rest unchanged … */ + } + mountEl.replaceChildren(fragment); +} +``` + +Caveat: if URL-init applies `show-all` to the section AFTER `renderGrid` is first called, the card list will be wrong for one frame. Phase 02 step 1 init order already adds `show-all` BEFORE `loadAndRender`, so this is safe. + +## Todo list + +- [ ] Item 2: URL helpers (`readQueryState`, `writeQueryParam`) +- [ ] Item 2: `init()` reads URL state, seeds toggles, writes on each change +- [ ] Item 2: validation — clamp unknown URL values to defaults +- [ ] Item 3: `#source-note` slot in `index.html` +- [ ] Item 3: `.source-note` CSS pill style +- [ ] Item 3: `loadAndRender` updates note text on source change +- [ ] Item 3: `title` attr on `#source-toggle` +- [ ] Item 4: hide empty cards in TIOBE view via `showAll` check in `renderGrid` +- [ ] Manual verify: load `?s=gitlab&v=all&o=hue` directly, share back to a peer, confirm state restores +- [ ] Manual verify: switch GitHub ↔ GitLab, confirm note shows/hides +- [ ] Manual verify: TIOBE view shows MỘC/THUỶ/HOẢ/THỔ only; all-langs shows all 5 + +## Success criteria + +- Reload preserves all 3 toggle states. +- Sharing the URL recreates the same view in a fresh browser. +- Empty KIM card disappears in TIOBE view (since KIM has 0 TIOBE langs); reappears in all-langs. +- GitLab selection shows a small "91 ngôn ngữ — palette riêng so với GitHub" pill next to the source tag. +- Default URL stays clean (no params) when all toggles are at default. + +## Risks & mitigations + +- **Risk:** URL-init order: if `show-all` class is added after first render, cards flicker. **Mitigation:** apply class before `loadAndRender(validSource)` (already in step 1). +- **Risk:** `firstChild.nodeValue` overwrite assumes the `#source-tag` text node is first child. **Mitigation:** if the markup ever moves the `` first, use `textContent` and rebuild the inner span — or restructure to two spans (tag + note) and set tag's `textContent` directly. +- **Risk:** GitLab pill may wrap on mobile. **Mitigation:** the parent `.mode-tag` already centres + breaks; `.source-note` is `inline-block` with small padding. +- **Risk:** Hiding empty cards changes the grid item count, which `auto-fit minmax(220px, 1fr)` re-flows — at desktop 880 px, 4 visible cards span the full width nicely (no awkward gap). + +## Next steps + +→ Phase 03: additive features (Top-5 non-TIOBE peek, lunar SVG body texture). diff --git a/plans/260428-1003-implement-all-remaining/phase-03-additive-features.md b/plans/260428-1003-implement-all-remaining/phase-03-additive-features.md new file mode 100644 index 0000000..6819fa1 --- /dev/null +++ b/plans/260428-1003-implement-all-remaining/phase-03-additive-features.md @@ -0,0 +1,147 @@ +# Phase 03 — Additive features + +## Context + +Items 11 (Top-5 non-TIOBE peek in TIOBE view) and 12 (subtle Lunar-New-Year SVG body texture). Both additive — they enhance the page without changing existing behaviour. Lower priority than Phase 02; defer if time-constrained. + +## Overview + +- **Priority:** P3 (additive) +- **Status:** pending — depends on Phase 02 +- **Effort:** ~45 min for Top-5 peek (incl. data math), ~20 min for SVG texture (incl. opacity tuning) +- **Risk:** low — all changes are additive; can revert without breaking core flow + +## Files modified + +- `js/main.js` — compute "popularity rank" proxy (alphabetical for now; no real popularity signal in data) +- `js/render-elements.js` — add peek slot to chip rendering +- `style.css` — peek styling (dimmed chips), body bg SVG +- `assets/lunar-pattern.svg` — new file (small, ~1 KB) + +## Implementation steps + +### Item 11 — Top-5 non-TIOBE peek + +In TIOBE view, each card currently shows just its TIOBE-ranked chips. Peek = show 5 dimmed non-TIOBE chips beside the pinned ones, alphabetical (no popularity data in our JSON). User can read them but the visual hierarchy stays TIOBE-first. + +**1. `js/main.js` — pre-compute peek list per bucket** (after `classifyAll`, before `applySortAndRender`): + +```js +const PEEK_COUNT = 5; + +function annotatePeeks(buckets) { + for (const langs of Object.values(buckets)) { + const nonTiobe = langs.filter((l) => !l.rank); + nonTiobe.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); + const peekSet = new Set(nonTiobe.slice(0, PEEK_COUNT).map((l) => l.name)); + for (const l of langs) { + if (!l.rank && peekSet.has(l.name)) l.peek = true; + } + } +} +``` + +Call `annotatePeeks(buckets)` right after `classifyAll(data)` resolves. + +**2. `js/render-elements.js` — emit `chip-peek` class:** + +```js +function buildChip(name, color, { rank = null, peek = false } = {}) { + const span = document.createElement('span'); + let cls = 'chip'; + if (rank) cls += ' chip-tiobe'; + else if (peek) cls += ' chip-peek'; + else cls += ' chip-other'; + span.className = cls; + /* … rest unchanged … */ +} +``` + +And in `renderGrid()` (around line 56): + +```js +for (const { name, color, rank, peek } of langs) + chips.appendChild(buildChip(name, color, { rank, peek })); +``` + +**3. `style.css` — peek visibility rule** (after the existing `.elements .chip-other` line): + +```css +/* Peek: in TIOBE view, surface 5 alphabetical non-TIOBE chips per card, + dimmed so they don't compete with the pinned TIOBE entries. */ +.elements .chip-peek { display: inline-flex; opacity: 0.55; } +.elements.show-all .chip-peek { opacity: 1; } +``` + +### Item 12 — Lunar SVG body texture + +A faint cloud / 雲紋-style repeating pattern at low opacity. Hand-craft a small inline-able SVG so we don't add a binary asset. + +**1. New file `assets/lunar-pattern.svg`** — a 120×120 pattern of stylised cloud/wave curves: + +```xml + +``` + +(Tune curves and dot positions to taste.) + +**2. `style.css:46-53` — body bg layering** (extend existing radial gradient): + +```css +body { + font-family: var(--font-sans); + background: + url('./assets/lunar-pattern.svg') repeat, + radial-gradient(1200px 600px at 50% -120px, #fff5dd 0%, transparent 60%), + var(--bg); + background-size: 120px 120px, auto, auto; + /* SVG already has 0.6 internal opacity on stroke; this layering puts + it at ~3% effective contrast against cream — barely visible, sets + the lunar-new-year mood without distracting. */ + color: var(--fg); + line-height: 1.6; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} +``` + +Tune the SVG `opacity` attribute or stroke colour if too prominent. + +## Todo list + +- [ ] Item 11: `annotatePeeks()` helper + integration into `loadAndRender` +- [ ] Item 11: `buildChip` accepts `peek` flag; emits `chip-peek` class +- [ ] Item 11: CSS rule for `.chip-peek` (visible-dimmed in TIOBE, full in all-langs) +- [ ] Item 12: hand-craft `assets/lunar-pattern.svg` (~1 KB) +- [ ] Item 12: layer SVG into `body` background per CSS above +- [ ] Manual verify: TIOBE view shows TIOBE chips at full strength + 5 peek chips dimmed +- [ ] Manual verify: all-langs view restores all chips at full opacity (no regression) +- [ ] Manual verify: SVG bg renders at desktop / mobile, doesn't fight text contrast (still WCAG AA) +- [ ] Manual verify: print stylesheet — page still readable (SVG should drop or stay subtle) + +## Success criteria + +- TIOBE cards show ~9–12 chips each (4–7 TIOBE pinned + 5 peek), no overflow. +- Peek chips are clearly secondary (lower opacity) but readable. +- Body bg has a faint lunar-new-year pattern; cream + accent red read unchanged. +- No measurable contrast regression (page bg still ≥7:1 against `--fg`). + +## Risks & mitigations + +- **Risk:** No popularity signal in `data/*.json` — alphabetical peek may surface obscure langs (e.g. "Apex" before "Ada"). **Mitigation:** alphabetical is honest; users get a deterministic preview. Future: rank by GitHub language usage data if added. +- **Risk:** SVG bg slows page paint. **Mitigation:** SVG is <1 KB, repeats natively in CSS; no JS cost. +- **Risk:** SVG visible at low-vision-mode / high contrast. **Mitigation:** wrap in `@media (prefers-contrast: more) { body { background-image: none; } }` if needed. +- **Risk:** Peek chips visually lift cards taller — KIM card with 0 TIOBE + 5 peek = 5 small chips. **Mitigation:** with Phase 02 item 4 hide-empty logic, KIM TIOBE bucket count goes from 0 to 5 (peek), so it's no longer empty — card now shows. Reconcile: the hide rule should check `langs.length === 0` AFTER peek annotation, not before. Adjust Phase 02 step 4 if peek shipped: filter on `langs.filter(l => l.rank || l.peek).length === 0` in TIOBE view, OR change Item 4 to "hide if no TIOBE rank langs AND not show-all" — explicit. Recommend: in TIOBE view, hide a card only if it has zero TIOBE chips, regardless of peek (so KIM stays hidden in TIOBE view; peek alone doesn't justify a card). + +## Next steps + +→ Phase 04: OG social card image generation (independent — can run before or in parallel). diff --git a/plans/260428-1003-implement-all-remaining/phase-04-og-image-generation.md b/plans/260428-1003-implement-all-remaining/phase-04-og-image-generation.md new file mode 100644 index 0000000..05d22b3 --- /dev/null +++ b/plans/260428-1003-implement-all-remaining/phase-04-og-image-generation.md @@ -0,0 +1,94 @@ +# Phase 04 — OG social card image generation + +## Context + +Item 1 — `index.html:22` `og:image` still points at `assets/ngon-ngu-lap-trinh-phong-thuy.png` (the 2018 Vietnamese-meme original). When the page is shared on Facebook / Twitter / Zalo, the preview shows the 2018 design, not the current Ngũ Hành cards. Need a 1200×630 image that reflects the current site. + +Independent of Phases 01–03 — can run in parallel or last. + +## Overview + +- **Priority:** P1 (functional / brand) +- **Status:** pending — known blocker on tooling +- **Effort:** 30 min if tooling works; +60 min for SVG fallback +- **Risk:** medium — host has no working browser binary for screenshot capture; previous attempts failed (cached puppeteer chrome on aarch64 host is x86_64 ELF) + +## Files modified + +- `assets/og-card.png` — new file (target ~50–150 KB, 1200×630 PNG) +- `index.html:22` — update `og:image` content path; add `og:image:width` / `og:image:height` meta tags +- `index.html:23` — add `og:image:alt` + +## Implementation steps + +### Step 1 — Try the `design` skill (preferred) + +The `design` skill ships an HTML→PNG pipeline. Compose a single-page HTML mock at 1200×630 that includes: + +- Title "Phong thuỷ ngôn ngữ lập trình" +- Subtitle "Ngũ Hành tương sinh — code cho hợp tuổi, hợp mệnh" +- 5 element-tinted cards (KIM/MỘC/THUỶ/HOẢ/THỔ) with 4–6 representative TIOBE chips each +- Lunar-new-year cream + gold accent palette (matches `style.css` tokens) +- Site URL or repo handle in a corner + +Invoke via `Skill` with `design` and ask for "social card 1200x630, programming-fengshui Ngũ Hành theme, see plan 260428-1003-implement-all-remaining/phase-04-og-image-generation.md for spec". Output to `assets/og-card.png`. + +### Step 2 — Fallback A: hand-craft an SVG, rasterise locally + +If the `design` skill is unavailable or produces something off-brand: + +1. Hand-write `assets/og-card.svg` at viewBox 1200×630 using the same colour tokens as `style.css` (cream bg, accent red title, 5 element bands). +2. Rasterise via ImageMagick CLI (already on the host — already used by `media-processing` skill): + ```bash + convert -density 144 -background "#fdf6ec" assets/og-card.svg -resize 1200x630 assets/og-card.png + ``` +3. If `convert` is unavailable or the rendered output is poor: ship the SVG as `og:image` directly. Most platforms accept SVG; some (Facebook) require raster — accept the trade-off. + +### Step 3 — Fallback B: use existing 2018 image with a new banner + +If neither tool path works, reuse the 2018 image but composite a small "Bản web 2026" badge top-right via ImageMagick. Output to `assets/og-card.png`. Documents that the page is a 2026 reinterpretation, not just a re-host of the original. + +### Step 4 — Update `index.html` meta tags + +```html + + + + + +``` + +(Add `twitter:image` in addition to the existing `twitter:card="summary_large_image"`.) + +### Step 5 — Verify + +- Use Twitter Card Validator: https://cards-dev.twitter.com/validator (need a public URL — paste to user once deployed) +- Use Facebook Sharing Debugger: https://developers.facebook.com/tools/debug/ +- Locally: open `index.html` and inspect ``, confirm `og:image` resolves to a 1200×630 PNG. + +## Todo list + +- [ ] Step 1: try `design` skill HTML→PNG with brand spec +- [ ] If fails — Step 2: hand-craft `assets/og-card.svg` + rasterise via ImageMagick +- [ ] If fails — Step 3: 2018 image + "Bản web 2026" badge +- [ ] Step 4: update `index.html` `og:image` + add width/height/alt + twitter:image +- [ ] Step 5: verify via Twitter / Facebook validators after deploy +- [ ] Manual: confirm `assets/og-card.png` <200 KB (Facebook recommended) + +## Success criteria + +- `og:image` URL resolves to a 1200×630 PNG <200 KB. +- Image visually reflects the current Ngũ Hành cards (not the 2018 source). +- Alt text in `og:image:alt` describes the image in Vietnamese. +- Twitter card / Facebook preview shows the new image after refreshing the URL on each platform. + +## Risks & mitigations + +- **Risk:** `design` skill output may not match the on-page typography (different fonts available in the renderer). **Mitigation:** spec the renderer to use a system-stack font (Be Vietnam Pro fallback to system-ui), accept slight visual drift. +- **Risk:** Hand-crafted SVG looks bland vs the on-page design. **Mitigation:** budget extra time; iterate on element-card visual weight. +- **Risk:** ImageMagick `convert` may not be installed. **Mitigation:** check `which convert` first; if absent, ask user to install or fall back to ship SVG directly as og:image. +- **Risk:** New og:image cached by social platforms — won't update for shared links. **Mitigation:** documented in Step 5 — use platform debuggers to force re-scrape after deploy. + +## Next steps + +→ All 4 phases complete. Final session: update `plans/todo.md` to mark items closed, and either archive this plan dir or move it to `plans/done/`. diff --git a/plans/260428-1003-implement-all-remaining/plan.md b/plans/260428-1003-implement-all-remaining/plan.md new file mode 100644 index 0000000..49c75f5 --- /dev/null +++ b/plans/260428-1003-implement-all-remaining/plan.md @@ -0,0 +1,51 @@ +--- +title: Implement All Remaining UX/UI Items +slug: 260428-1003-implement-all-remaining +status: in-progress +created: 2026-04-28 +blockedBy: [] +blocks: [] +--- + +# Implement All Remaining UX/UI Items + +Single pass through every remaining item in `plans/todo.md` and the post-cleanup `plans/reports/ui-ux-260427-2043-fengshui-page-review.md`. 15 items, 4 phases. + +## Phases + +| # | Title | Status | Items | +|---|-------|--------|-------| +| 01 | Style & markup polish | pending | items 5–10, 13, 14, 15 (CSS one-liners + chip role + decision docs) | +| 02 | Functional toggles & tooltip | pending | items 2, 3, 4 (URL persistence, GitLab tooltip, hide empty KIM in TIOBE view) | +| 03 | Additive features | pending | items 11, 12 (Top-5 non-TIOBE peek, lunar SVG body texture) | +| 04 | OG social card image | pending | item 1 (generate `assets/og-card.png`, may block on tooling) | + +## Default decisions (baked into the plan; flag now if any need to change) + +1. **Empty KIM in TIOBE view** → hide the card entirely. The 5-element wheel is preserved in all-langs view. Cleaner than "0 ngôn ngữ". +2. **5-card-row symbolism** → keep `auto-fit minmax(220px, 1fr)`. Page max-width stays 880 px. +3. **URL persistence scope** → 3 toggles only via query params (`?source=…&view=…&sort=…`). No anchor-scroll state. +4. **GitLab tooltip mechanism** → small visible inline note next to source toggle that swaps text when GitLab is selected ("91 ngôn ngữ — palette riêng so với GitHub"). Plus `title` attr on the source-toggle group. +5. **AA contrast policy** → keep current best-effort (post-fix worst case Swift/MATLAB ~3.5–4.0:1). Document in a one-line CSS comment near `.chip`. +6. **OG image** → try `design` skill HTML→PNG first. Fallback: hand-crafted static SVG of the 5-element ribbon + title text. + +## Dependencies + +- Phase 02 follows Phase 01 (HTML markup stable before JS wiring). +- Phase 03 follows Phase 02 (uses URL state pattern for shareable peek state). +- Phase 04 independent; runs in parallel or last. + +## Constraints + +- No build step. Vanilla HTML/CSS/ES modules only. +- No webfont fetches; system stack only. +- All edits target existing files. New file allowed only for `assets/og-card.png`. +- Manual verification via `python3 -m http.server 8765` after each phase. +- Re-run `js/classify-element.test.html` if any classifier change (none planned). + +## References + +- Live review: `plans/reports/ui-ux-260427-2043-fengshui-page-review.md` +- Live todo: `plans/todo.md` +- Algorithm spec: `plans/reports/researcher-260427-0854-nguhanh-color-classifier.md` +- KIM rule rationale: `plans/reports/brainstorm-260427-1046-kim-rebalance.md`