diff --git a/plans/260427-0854-color-element-mapping/phase-01-data-and-classifier.md b/plans/260427-0854-color-element-mapping/phase-01-data-and-classifier.md deleted file mode 100644 index 7fb0be4..0000000 --- a/plans/260427-0854-color-element-mapping/phase-01-data-and-classifier.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -title: "Phase 01 — Vendor color data + HSL classifier module" -status: pending -priority: P2 -effort: 60m ---- - -## Context Links - -- Plan overview: [plan.md](plan.md) -- Color data report: [../reports/researcher-260427-0855-github-language-colors.md](../reports/researcher-260427-0855-github-language-colors.md) -- Classifier algorithm report: [../reports/researcher-260427-0854-nguhanh-color-classifier.md](../reports/researcher-260427-0854-nguhanh-color-classifier.md) -- Existing files: `index.html`, `style.css` - -## Overview - -- **Priority:** P2 (foundation for phases 02–04) -- **Status:** pending -- **Description:** Vendor `ozh/github-colors` JSON locally and implement a pure-function HSL classifier (`hex → element`) with an in-browser sanity-check harness. - -## Key Insights - -- Report #1 confirms `ozh/github-colors` JSON is CORS-friendly + 78KB, but vendoring locally avoids any runtime third-party dependency and keeps the page working offline / on `file://`. -- Report #2 provides a deterministic HSL algorithm with explicit Step-2 grayscale handling and Step-4 edge-case refinements. Translate Python pseudocode to JS literally — do not invent variations. -- Report #2 §3 lists 12 sample languages with expected outputs; report §4 lists 10 canonical edge colors. These are the test fixtures. -- Element keys must be lowercase (`kim`, `moc`, `thuy`, `hoa`, `tho`) so they match the existing CSS class names in `style.css` (lines 124–133). Reports use uppercase Vietnamese — translate at module boundary. - -## Requirements - -### Functional -- Vendor `data/github-colors.json` (~78KB) at the documented schema: `{ "": { "color": "#hex"|null, "url": "..." } }`. -- Provide ES module `js/classify-element.js` exporting: - - `hexToHsl(hex: string): { h: number, s: number, l: number }` — H ∈ [0, 360), S/L ∈ [0, 100] - - `classify(hex: string): 'kim' | 'moc' | 'thuy' | 'hoa' | 'tho'` — implements report §2 Steps 2–4 verbatim - - `ELEMENTS` constant: ordered list `['kim', 'moc', 'thuy', 'hoa', 'tho']` with display labels (`KIM`, `MỘC`, `THUỶ`, `HOẢ`, `THỔ`) -- Provide `js/classify-element.test.html` — opens in any browser, runs assertions, prints green PASS / red FAIL list. No test framework. No build. - -### Non-functional -- File size budget: `classify-element.js` ≤ 120 lines, `classify-element.test.html` ≤ 100 lines (project rule: <200 lines/file). -- Pure functions: no I/O, no DOM, no globals. Importable from Node for future tooling without changes. -- Hex input tolerated: `#RRGGBB` and `#rrggbb`. Throw on malformed input (don't silently mis-classify). - -## Architecture - -### Data flow -``` -data/github-colors.json (static) - │ - ▼ (fetched in Phase 02) -{ "JavaScript": { color: "#f1e05a", url: "..." }, ... } - │ - ▼ (Phase 02 calls classify(entry.color)) -classify-element.js ──▶ 'thy' | 'kim' | ... -``` - -In Phase 01, the classifier is exercised only by the test harness — no integration yet. - -### Module contract (`js/classify-element.js`) - -```js -// Input: '#RRGGBB' or '#rrggbb' -// Output: 'kim' | 'moc' | 'thuy' | 'hoa' | 'tho' -// Throws: TypeError if input is null/undefined/not a 7-char hex string. -export function classify(hex) { ... } - -export function hexToHsl(hex) { ... } // exported for test visibility - -export const ELEMENTS = [ - { key: 'kim', label: 'KIM' }, - { key: 'moc', label: 'MỘC' }, - { key: 'thuy', label: 'THUỶ' }, - { key: 'hoa', label: 'HOẢ' }, - { key: 'tho', label: 'THỔ' }, -]; -``` - -### Classifier rules (mirror report #2 §2) - -1. Parse `#RRGGBB` → R, G, B ∈ [0, 255] → normalize → HSL. -2. Grayscale (`S < 5`): - - `L < 20` → `thuy` - - `20 ≤ L < 70` → `tho` - - `L ≥ 70` → `kim` -3. Hue ranges: - - `[0, 20)` → `hoa` - - `[20, 40)` → `hoa` if `S ≥ 60 && L ≥ 50` else `tho` - - `[40, 70)` → `tho` - - `[70, 200)` → `moc` (covers green + jade/cyan) - - `[200, 260)` → `thuy` - - `[260, 360)` → `hoa` - -## Related Code Files - -### Create -- `data/github-colors.json` (vendored, ~78KB) -- `js/classify-element.js` -- `js/classify-element.test.html` - -### Modify -- None. - -### Delete -- None. - -## Implementation Steps - -1. Fetch the JSON once: `curl -fsSL https://raw.githubusercontent.com/ozh/github-colors/master/colors.json -o data/github-colors.json`. Verify file is well-formed JSON (`python3 -m json.tool data/github-colors.json | head` is fine for a smoke check). -2. Create `js/classify-element.js`: - - Implement `hexToHsl(hex)` — port report #2 pseudocode line by line. Handle `max == min` achromatic case (S=0, H=0). - - Implement `classify(hex)` — call `hexToHsl`, then run Step 2 then Step 3, return lowercase string. - - Export `ELEMENTS` array. - - Add a leading file comment citing the algorithm source: `// Algorithm: plans/reports/researcher-260427-0854-nguhanh-color-classifier.md §2`. -3. Create `js/classify-element.test.html`: - - Plain HTML with `` before ``. -- `js/mode-toggle.js` responsibilities: - - On `DOMContentLoaded`: read `location.hash`, normalize (`#modern` → modern, anything else → classic), call `setMode(mode)`. - - `setMode(mode)` sets `hidden` on the inactive panel, removes from active, updates `aria-selected` + `tabindex` on the two ` - - - -
- -
-
- ... -
Sơ đồ ngôn ngữ lập trình xếp theo Ngũ Hành.
-
-
- -
-

Ngũ Hành & ngôn ngữ (theo bài gốc, 2018)

-
-

KIM

JavaScript, Objective-C, Python

-

THUỶ

C#, PHP

-

MỘC

Android, C#

-

HOẢ

Scala, HTML5, Java, Node.js

-

THỔ

JavaScript, Go, Ruby

-
-

* Bảng phân loại mang tính giải trí, lấy từ ảnh gốc.

-
-
- - - - - - - -``` - -### Module contract (`js/mode-toggle.js`) - -```js -// Public surface: none (self-initialising entry point). -// Side effects: mutates DOM (hidden, aria-selected, tabindex), listens to clicks, keys, hashchange. - -const MODES = ['classic', 'modern']; -const DEFAULT_MODE = 'classic'; - -function readHash() { - const h = (location.hash || '').replace('#', ''); - return MODES.includes(h) ? h : DEFAULT_MODE; -} - -function setMode(mode, { writeHash = true, push = false } = {}) { - // toggle [hidden] on panels, aria-selected + tabindex on tabs, - // update hash via history.replaceState (push=false) or location.hash (push=true) -} - -function onTabClick(e) { setMode(e.currentTarget.dataset.mode, { push: true }); } -function onTabListKeydown(e) { /* ←/→ Home End cycle + setMode */ } -function onHashChange() { setMode(readHash(), { writeHash: false }); } - -document.addEventListener('DOMContentLoaded', () => { - // wire listeners; initial setMode(readHash(), { writeHash: false }) -}); -window.addEventListener('hashchange', onHashChange); -``` - -### Data flow - -``` -[page load] - │ - ▼ -mode-toggle.js DOMContentLoaded - ├── readHash() → 'classic' | 'modern' - └── setMode(mode, { writeHash:false }) - ├── #panel-classic [hidden=mode!=='classic'] - ├── #panel-modern [hidden=mode!=='modern'] - ├── #tab-classic aria-selected, tabindex=0|-1 - └── #tab-modern aria-selected, tabindex=0|-1 - -[tab click] → setMode(mode, { push:true }) → location.hash = '#mode' -[hashchange] → setMode(readHash(), { writeHash:false }) -[arrow key on tablist] → focus next tab + setMode(...) -``` - -## Related Code Files - -### Create -- `js/mode-toggle.js` - -### Modify -- `index.html` — restructure: insert toggle, wrap existing image+cards into `#panel-classic`, add scaffolded `#panel-modern`, add module script tag. **The 5 cards inside `#panel-classic` must be byte-identical copies of current `index.html` lines 30–49.** - -### Delete -- None. - -## Implementation Steps - -1. Open `index.html`. Capture the exact text of lines 17–52 (figure section + elements section) — these blocks move into `#panel-classic` unchanged except for the heading `` insertion. -2. Replace the body `
` content with the structure shown in the DOM contract above: - - Hero unchanged. - - Insert `
` with two buttons. - - Wrap the captured figure + elements blocks inside `
`. Add `(theo bài gốc, 2018)` inside the existing `

`. - - Add `