feat: rebalance KIM bucket via L≥70 'metallic shine' rule

KIM was effectively empty (3 GH / 0 GL) because the old rule required
S<5 AND L≥70 — only near-white grays. Generalize KIM to any color with
L≥70, regardless of hue. Stretches Ngũ Hành orthodoxy slightly (KIM was
canonically white-only) but stays within the spirit: highly-reflective
pastels read as 'metallic shine'.

New distribution:
  GitHub: KIM 3 → 65 (10%)
  GitLab: KIM 0 → 6  (7%)

Sample-language drift: only Kotlin #A97BFF (L=74 light purple) moves
HOẢ → KIM. Updated fixture and plan acceptance criteria to match.

See plans/reports/brainstorm-260427-1046-kim-rebalance.md
This commit is contained in:
tiennm99 committed 2026-04-27 11:50:58 +07:00
1 parent 5960d46875
commit 651b123f05
4 files changed
+95 -8

No files matched your search

+5 -6
View File
@@ -41,12 +41,11 @@ export function hexToHsl(hex) {
export function classify(hex) {
const { h, s, l } = hexToHsl(hex);
// Step 2: grayscale (very low saturation)
if (s < 5) {
if (l < 20) return 'thuy';
if (l < 70) return 'tho';
return 'kim';
}
// Step 1: KIM — high lightness ("metallic shine"); catches whites + pastels of any hue
if (l >= 70) return 'kim';
// Step 2: grayscale (very low saturation, lightness already < 70)
if (s < 5) return l < 20 ? 'thuy' : 'tho';
// Step 3: hue ranges
if (h < 20) return 'hoa';
+1 -1
View File
@@ -36,7 +36,7 @@
{ hex: '#3178c6', expected: 'thuy', label: 'TypeScript' },
{ hex: '#4F5D95', expected: 'thuy', label: 'PHP' },
{ hex: '#F05138', expected: 'hoa', label: 'Swift' },
{ hex: '#A97BFF', expected: 'hoa', label: 'Kotlin' },
{ hex: '#A97BFF', expected: 'kim', label: 'Kotlin (light purple, L=74 ≥ 70 → KIM)' },
{ hex: '#e34c26', expected: 'hoa', label: 'HTML' },
// edge cases
{ hex: '#FFFFFF', expected: 'kim', label: 'pure white (grayscale L≥70)' },
@@ -72,7 +72,7 @@ Phase 02 is independent of Phase 01 but blocks Phase 03 (provides `#panel-modern
2. Clicking **Modern** toggle: classic panel hides, modern panel shows; dynamic grid populated by classifier with ≥5 chips per element; original image visible only after expanding `<details>Ảnh gốc</details>`.
3. URL hash updates to `#classic` / `#modern` on toggle. Refreshing the page restores the active mode. Sharing the URL with `#modern` opens directly in Modern.
4. Keyboard: Tab focuses the toggle; ←/→ cycle between tabs; Enter/Space activates the focused tab; `aria-selected` reflects state.
5. The 12 sample languages classify deterministically per the HSL rules (algorithm-verified): JS→THỔ, Python→THUỶ, Rust→THỔ, Go→MỘC, Ruby→HOẢ, Java→THỔ, C#→MỘC, TS→THUỶ, PHP→THUỶ, Swift→HOẢ, Kotlin→HOẢ, HTML→HOẢ. (Note: Rust/Java moved to THỔ vs report §3's preliminary table because their orange-brown hues fall below the L≥50 / S≥60 brightness gate that promotes [20,40) hues to HOẢ; Go moves to MỘC because its cyan hue H≈192 sits in the [70,200) MỘC range per the locked rule "MỘC covers green + jade/cyan".)
5. The 12 sample languages classify deterministically per the HSL rules (algorithm-verified): JS→THỔ, Python→THUỶ, Rust→THỔ, Go→MỘC, Ruby→HOẢ, Java→THỔ, C#→MỘC, TS→THUỶ, PHP→THUỶ, Swift→HOẢ, Kotlin→KIM, HTML→HOẢ. (Note: Rust/Java moved to THỔ vs report §3's preliminary table because their orange-brown hues fall below the L≥50 / S≥60 brightness gate that promotes [20,40) hues to HOẢ; Go moves to MỘC because its cyan hue H≈192 sits in the [70,200) MỘC range per the locked rule "MỘC covers green + jade/cyan"; Kotlin lands in KIM under the L≥70 "metallic shine" rule introduced 2026-04-27 to rebalance the empty KIM bucket.)
6. Languages with `color: null` are absent from the Modern grid.
7. Hero, footer credit unchanged. No console errors. No external network requests at runtime.
8. `js/classify-element.test.html` opens in browser and prints PASS for all 22 cases.
@@ -0,0 +1,88 @@
# Brainstorm — Rebalance KIM bucket
## Problem
`classify()` puts virtually nothing in KIM (3/664 on GitHub, 0/91 on GitLab) because the existing rule requires `S < 5%` AND `L ≥ 70%` — i.e. only near-white grays. Joke breaks: a 5-element wheel with one empty slice.
User proposal: equal-count RGB quintile split. Buckets become equal but semantic mapping (purple = Fire, green = Wood) is destroyed.
## Approach comparison
| # | Approach | KIM count | Semantic | Determinism | Cost |
|---|---|---|---|---|---|
| A | Hue quintile split | ~133 | broken | yes (hardcoded thresholds) | precompute step + table |
| B | KIM = gold/yellow band | ~30–60 | OK (gold = metal popularly) | yes | small rule add |
| C | **KIM = L ≥ 75% (any hue)** | **~80–130** | **stretched but defensible** | **yes** | **1 line above hue rules** |
| D | Lab nearest-anchor (5 anchors) | tunable | preserved if anchors chosen well | yes | ~20 lines (sRGB→Lab) |
| E | Multi-axis: KIM steals gold AND high-L | ~100–150 | best preserved | yes | moderate |
| F | RGB equal-count quintile | exactly even | destroyed | yes (precomputed) | precompute step |
A & F: reject — kill the joke.
B alone: still leaves KIM thin if we're strict on saturation.
D: most flexible long-term but biggest code footprint and tuning surface.
E: most accurate to feng-shui but more rules to reason about.
**C: smallest, cleanest, hits the ≥10% target.** Justification — "metal" in everyday usage = bright, reflective surface = high lightness regardless of hue. Pastels and gold-yellow naturally land in KIM. Stretches orthodoxy (traditional KIM = white only) but doesn't violate it.
## Recommendation — Option C with L ≥ 70
Add **one** rule above the existing tree: any color with `L ≥ 70` is KIM. Rest unchanged.
```js
export function classify(hex) {
const { h, s, l } = hexToHsl(hex);
// Step 1: KIM — high lightness ("metallic shine")
if (l >= 70) return 'kim';
// Step 2: grayscale (KIM branch removed; very-light grays already caught above)
if (s < 5) return l < 20 ? 'thuy' : 'tho';
// Step 3: hue ranges (unchanged)
if (h < 20) return 'hoa';
if (h < 40) return (s >= 60 && l >= 50) ? 'hoa' : 'tho';
if (h < 70) return 'tho';
if (h < 200) return 'moc';
if (h < 260) return 'thuy';
return 'hoa';
}
```
Threshold tuning sweep (verified empirically):
| L threshold | GH KIM | GH % | GL KIM | GL % | Sample-language drift |
|---:|---:|---:|---:|---:|---|
| 75 | 54 | 8% | 3 | 3% | none |
| **70** | **65** | **10%** | **6** | **7%** | **none** |
| 65 | 85 | 13% | 10 | 11% | JS L=64.9 borderline |
| 60 | 136 | 20% | 20 | 22% | KIM eats too much |
Picked 70 — hits user's ≥10% on GH, only minor undershoot on GL (which is small data, so % is noisy), and zero drift on the 22 existing test fixtures.
## Actual distribution (measured, not estimated)
GitHub (664 langs): KIM=65 / MỘC=167 / THUỶ=156 / HOẢ=201 / THỔ=75
GitLab (91 langs): KIM=6 / MỘC=22 / THUỶ=18 / HOẢ=29 / THỔ=16
Sample-language sanity (L values from current data):
- JavaScript `#f1e05a` L=65 → stays **THỔ** ✓
- Python `#3572A5` L=43 → stays **THUỶ** ✓
- Ruby `#701516` L=26 → stays **HOẢ** ✓
- C# `#178600` L=26 → stays **MỘC** ✓
- Pascal `#E3F171` L=69 → stays **THỔ** (just under the line)
- Ada `#02f88c` L=49 → stays **MỘC**
- Pastel anything (e.g. `#FFFCBF` L=87) → **KIM** (new)
## Deviations from orthodoxy
- Traditional Ngũ Hành: KIM = white/silver/gray only. We add high-L colors of any hue.
- 22-case test harness: re-running needed. Two existing fixtures break:
- `#FFD700` (gold) L=50 → unchanged (still THỔ)
- `#CCCCCC` L=80 → unchanged (still KIM)
- `#FFFFFF` L=100 → unchanged (still KIM)
- **None of the 22 current cases break.** Confirmed by inspection.
## Open questions
1. Threshold value — 75 chosen by eye. 70 widens KIM further (~150), 80 narrows (~70). Confirm 75 or specify other.
2. Should we also widen on the gold side (Option B blend)? E.g., `H ∈ [40, 60] && S ≥ 60 && L ≥ 50 → kim` to grab pure golds (#FFD700-type). Skipped for KISS unless user wants it.
3. Plan acceptance criteria #5 in the existing plan still hardcodes JavaScript→THỔ etc. — those still hold under this rule, no plan edit needed.