testsAndMisc/DOCS-design-audit.md
Krzysztof kuhy Rudnicki ed9fb48c51
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Move TODOs to todo app
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-02 11:29:12 +01:00

31 KiB
Raw Permalink Blame History

Design audit — kcd2_dice_solver + bucket_catch frontend

Generated against safe-design-rules (anthonyhobday.com/sideprojects/saferules). Report only — nothing in either app was changed by the audit itself.

Contrast ratios and L* (perceptual lightness) values below were computed with the WCAG relative-luminance formula against each file's literal hex values — not measured from a rendered page.


kcd2_dice_solver (~/src/testsAndMisc/kcd2_dice_solver, Vite/TS)

Design tokens live in src/index.css (422 lines), a single :root block of CSS custom properties consumed by all 5 component files. This is the correct pattern — findings below are deviations from it, not an absence of it.

Palette: --bg:#17150f --panel:#201d16 --panel-2:#2a261d --line:#3a3428 --text:#e8e0cf --muted:#9d947f --accent:#c8a24a --good:#7fb069 --bad:#c96b5b (src/index.css:2-10), plus two hardcoded tier colors outside the token system (see Rule 4).

All 28 rules

# Rule Verdict Evidence
1 Near-black/near-white Pass --bg:#17150f, --text:#e8e0cf — neither pure black nor pure white. src/index.css:2,6
2 Saturate your neutrals Pass bg/panel/panel-2/line all carry R>G>B (warm brown/gold tint), not desaturated gray. src/index.css:2-5
3 High contrast for important elements Pass --accent (7.58:1 vs --bg, computed) reserved for header title and key values. src/index.css:8,36 (header h1), :219 (.die-tag)
4 Everything deliberate Violation .tier-tin{color:#a8a196} and .tier-silver{color:#cfd4da} are hardcoded hex, bypassing the custom-property system every other color in the file uses. src/index.css:271,274
5 Optical alignment N/A No irregular/asymmetric glyphs; layout is rectangle/text based.
6 Letter-spacing/line-height by size Violation header h1 is the largest text (1.5rem) yet adds tracking (letter-spacing:0.02em) instead of tightening it; smaller labels correctly widen tracking as they shrink (.tier 0.8rem/+0.08em, .stats dt 0.7rem/+0.05em). src/index.css:33-38 vs :263-268,341-346
7 Border contrast with both surfaces Violation --line:#3a3428 borders measure only 1.48:1 against --bg and 1.36:1 against --panel (computed) — well under a legible ~3:1 non-text-contrast floor. src/index.css:5, used e.g. :62,87,107,147,156
8 Everything aligns with something Pass Consistent flex/grid: .columns (:45-50), .toolbar (:77-81), .stepper (:244-248); only .die-face-label uses absolute positioning, intentionally anchored to its own bar (:224-232).
9 Distinct brightness values Violation --accent (L*≈68.5), --good (L*≈67.0) and hardcoded .tier-tin (L*≈66.5) are within 2 L* points of each other despite signaling 3 different things (highlight / positive stat / badge tier) — differentiated only by hue, not lightness. src/index.css:8,9,271
10 Warm OR cool, not both Pass Every neutral and accent leans warm (brown/gold/olive/warm-red); no cool tone mixed in. src/index.css:2-10
11 Mathematically related measurements Violation :root font-size:15px (src/index.css:14), not 16px, so the rem-based spacing scale (0.25/0.5/0.75/1/1.25/1.5rem, e.g. :30,64,85,112,155) resolves to 3.75/7.5/11.25/15/18.75/22.5px — off any clean 4- or 8-px grid. Border-radius values (3,4,6,8px at :238,179,88,108,148,256,337,63) also don't follow a consistent scale.
12 Order by visual weight N/A Two side-by-side panels of comparable weight (inventory vs results); no dominant element to check edge-placement against. src/App.tsx:127-195
13 12-column grid N/A Simple 2-column grid sized for one breakpoint collapse, not a general layout grid. src/index.css:45-50
14 Space between high-contrast points N/A Requires rendered-glyph inspection; unverifiable from CSS source alone.
15 Closer elements lighter Pass --bg(L6.8) → --panel(L10.9) → --panel-2(L15.3) → --line(L22.0) increases monotonically with each more-elevated layer (computed). src/index.css:2-5
16 Shadow blur ≈ 2x distance N/A Zero box-shadow declarations in src/index.css (grep-confirmed).
17 Simple on complex / complex on simple Pass All surfaces are flat single colors; no background images/patterns anywhere.
18 Container brightness limits (~12% dark) Pass Adjacent stack deltas are 4.07 L* (bg→panel) and 4.44 L* (panel→panel-2), computed — well inside the ~12-point dark-mode budget.
19 Outer padding ≥ inner padding Pass section.inventory outer padding 1rem (:64) vs nested .die-row 0.25rem 0.5rem (:155) and .stats div 0.5rem 0.6rem (:338) — inner always smaller.
20 Body text ≥16px Violation :root{font-size:15px} (src/index.css:14); .hint (:126-130) and .subtitle (:40-43) inherit it uncorrected — under the 16px floor.
21 Line length ~70 chars N/A Fluid width (.app{max-width:1400px}, :28), no fixed reading column; needs a rendered viewport to measure.
22 Button padding: horizontal = 2× vertical Violation .clear/.stepper-button padding 0.4rem 0.7rem (:112) → 1.75x ratio; .search padding 0.5rem 0.75rem (:85) → 1.5x. Neither hits ~2x.
23 Two typefaces max Pass Single monospace stack throughout ("Iosevka","Cascadia Code",ui-monospace,...). src/index.css:13
24 Nest corners properly Violation section.inventory (8px radius, 1rem/16px padding, :63-64) contains .search (6px radius, :88) and .die-list (6px radius, :148); with 16px between outer/inner edges, nested radius should trend toward 0, not stay at 6-8px — corners read as mismatched, not concentric.
25 Avoid adjacent hard divides Pass Every bordered region is separated by gap/padding (e.g. .toolbar{gap:0.5rem} :79), never abutting directly.
26 No shadows in dark interfaces Pass color-scheme:dark (:12) and zero box-shadow usage anywhere.
27 Don't mix depth techniques Pass Only borders are used for depth/separation throughout — one consistent technique.
28 Lower icon contrast paired with text N/A No icon usage found anywhere (grep of src/components/ and App.tsx found none) — pure text UI.

Notes

  • The custom-property system in src/index.css is otherwise well-centralized — the two hardcoded tier colors (Rule 4) are the only leak, and fixing them also resolves part of Rule 9.
  • Rule 11 and Rule 20 share a root cause: :root{font-size:15px} (src/index.css:14). Bumping the base to 16px would fix the body-text floor and straighten out the rem-based spacing scale in one change.

bucket_catch frontend (~/src/testsAndMisc/bucket_catch/packages/frontend, Vite/TS)

Confirmed: no shared design tokens exist. src/index.css is a 12-line reset only (box-sizing + html/body/#root margin/font/background/color) — no :root custom properties, no theme module. All actual styling lives in 6 independent per-component files, each with its own hardcoded hex values:

  • src/components/DropZone.module.css (165 lines)
  • src/components/GameCanvas.module.css (24 lines)
  • src/components/ModeSelect.module.css (157 lines)
  • src/components/PuzzleCanvas.module.css (25 lines)
  • src/components/PuzzleResult.module.css (86 lines)
  • src/components/ScoreScreen.module.css (157 lines)

Across these, 19 distinct hex colors appear (grep -roh '#[0-9a-fA-F]\{6\}' | sort | uniq -c), including exact-duplicate literals copy-pasted across files: the page background linear-gradient(135deg, #0f0c29, #302b63, #24243e) appears verbatim in DropZone.module.css:6, ModeSelect.module.css:8, ScoreScreen.module.css:9, PuzzleResult.module.css:9; the heading gradient linear-gradient(90deg, #818cf8, #f472b6) appears in DropZone.module.css:36, ModeSelect.module.css:16, ScoreScreen.module.css:25 — but PuzzleResult.module.css:25 reverses the stop order (#f472b6, #818cf8), so even the copy-paste isn't consistent.

This absence is itself the Rule 4 finding: there is no single place that enforces "every color/spacing/radius decision is deliberate" — each file can (and does) drift independently. It also makes Rules 9 and 18 unverifiable as whole-palette checks (there is no one palette to check); both are marked N/A below with spot-checks noted instead.

All 28 rules

# Rule Verdict Evidence
1 Near-black/near-white Pass bg gradient stops #0f0c29/#302b63/#24243e (not pure black), text #e0e7ff (not pure white). src/index.css:10-11, DropZone.module.css:6
2 Saturate your neutrals Violation Text/bg are saturated navy/lavender, but the muted-gray set is 4 unrelated shades with no shared token: #94a3b8, #4b5563, #6b7280, #64748b — ModeSelect.module.css:24,66,99,138; ScoreScreen.module.css:64,103; DropZone.module.css:44,75,107,145. Not "one coherent palette," 4 independent picks.
3 High contrast for important elements Pass Primary CTAs (.browseBtn DropZone.module.css:58-68, .actionBtn ScoreScreen.module.css:114-124, .puzzleStart ModeSelect.module.css:142-153) use bold gradient fill + white text, visually dominant over ghost/preset buttons (.preset, border-only, ~2.5:1, DropZone.module.css:102-112).
4 Everything deliberate Violation (structural) No shared token file exists at all — see summary above. Duplicated/inconsistent gradient literals across DropZone.module.css:6,36, ModeSelect.module.css:8,16, ScoreScreen.module.css:9,25, PuzzleResult.module.css:9,25.
5 Optical alignment N/A No irregular glyphs; emoji icons centered via flex (DropZone.module.css:27-30, ModeSelect.module.css:55-57), not manually aligned.
6 Letter-spacing/line-height by size Pass .divider (0.8rem, +0.1em, DropZone.module.css:74-79) and .listTitle (1rem, +0.08em, ScoreScreen.module.css:61-67) correctly widen tracking as size shrinks; no large text adds positive tracking.
7 Border contrast with both surfaces Violation .preset border #4b5563 vs page bg #0f0c29 measures 2.51:1 (computed); vs the lighter gradient stop #24243e it drops to 1.99:1 — under a legible border contrast. DropZone.module.css:106, ModeSelect.module.css:99
8 Everything aligns with something Pass Every surface uses flex/grid with centered alignment (e.g. .container ModeSelect.module.css:1-11, .cards :28-33) — consistent system throughout.
9 Distinct brightness values N/A (whole-palette) No shared palette to check (Rule 4). Spot-check: the two accents #818cf8/#f472b6 are used together as an intentional gradient pair, not as competing solo swatches — not itself a violation.
10 Warm OR cool, not both Pass Palette is entirely cool (navy/indigo/violet bg + slate grays), with magenta/pink as the one warmer accent, always paired consistently with violet in gradients — no clash.
11 Mathematically related measurements Violation Most padding follows a 4px-multiple scale (24px/16px DropZone.module.css:9; 32px/40px ModeSelect.module.css:39; 12px/32px ScoreScreen.module.css:115), but small controls break it: 3px vertical padding recurs (DropZone.module.css:103,127; ModeSelect.module.css:96,120) alongside off-grid gaps 5px (ModeSelect.module.css:89), 10px (DropZone.module.css:24), 3px (PuzzleResult.module.css:48) — a second, uncoordinated scale.
12 Order by visual weight N/A Simple centered vertical stacks (heading → content → actions); no competing elements to order.
13 12-column grid N/A No multi-column page layout; PuzzleResult.module.css:46-55 .grid is a dynamic NxM puzzle-piece grid, not a layout grid.
14 Space between high-contrast points N/A Unverifiable from CSS source without rendering.
15 Closer elements lighter Pass (single-level) .card and .fileItem (both rgba(30,27,75,0.7), ModeSelect.module.css:35-48, ScoreScreen.module.css:79-87) sit lighter than the darkest bg stop — directionally correct, though both reuse the identical translucent value regardless of nesting depth, so distinct elevation levels aren't distinguishable from each other.
16 Shadow blur ≈ 2x distance N/A Zero box-shadow declarations found across all *.module.css (grep-confirmed).
17 Simple on complex / complex on simple Pass Foreground text/controls sit on a flat gradient (no photographic noise); text on .card's translucent fill is plain-on-plain.
18 Container brightness limits N/A (whole-palette) No shared token system to audit (Rule 4). The one measurable stack — the page gradient itself (L4.7→L21.1→L*15.4 across its 3 stops, computed) — is decorative, not the container-on-container relationship this rule targets.
19 Outer padding ≥ inner padding Pass .container 32px 16px (ScoreScreen.module.css:8) > nested .fileItem 6px 12px (:85); .zone 24px 16px (DropZone.module.css:9) > any nested element's own padding.
20 Body text ≥16px Violation .cardDesc (ModeSelect.module.css:64-69), the descriptive copy under each mode card, is 0.85rem (13.6px) — under the 16px floor. (Small labels like .divider/.gridLabel are UI chrome, not flagged.)
21 Line length ~70 chars N/A .cardDesc is width-constrained by its 220px .card parent (ModeSelect.module.css:40) to a few words per line — can't exceed ~70 chars; not a meaningful case to score.
22 Button padding: horizontal = 2× vertical Violation .browseBtn/.puzzleBtn 8px 24px (DropZone.module.css:59,150) → 3x ratio; .actionBtn 12px 32px (ScoreScreen.module.css:115) → 2.67x; .restartBtn 10px 28px (:131) → 2.8x; .puzzleStart 8px 20px (ModeSelect.module.css:144) → 2.5x. None land near the ~2x target.
23 Two typefaces max Pass Base sans-serif system stack (src/index.css:9) plus one intentional monospace override for filenames (.fname, ScoreScreen.module.css:95) — exactly 2, functionally justified.
24 Nest corners properly Violation .grid (8px radius, 3px border, overflow:hidden, PuzzleResult.module.css:46-55) clips .piece/.hole children which have 0px radius (:57-69) — works only because overflow:hidden clips edge pieces; there's no deliberate inner-radius value (outer 8px − 3px border ≈ 5px expected, actual 0px).
25 Avoid adjacent hard divides Pass No abutting borders/backgrounds found; bordered elements (.card, .preset, .gridInput) are separated by flex/grid gaps.
26 No shadows in dark interfaces Pass Entire UI is dark-themed with zero box-shadow usage — compliant.
27 Don't mix depth techniques Pass Depth conveyed only via translucent fills + borders — one consistent technique, no shadows/glows mixed in.
28 Lower icon contrast paired with text Violation .icon/.cardIcon emoji render at full native contrast (DropZone.module.css:27-30, 52px; ModeSelect.module.css:55-57, 48px) directly above body text styled at reduced opacity/muted color (.subtitle, .cardDesc) — the icon is visually heavier than the text it's paired with; no opacity/contrast reduction applied to the icon itself.

Notes

  • Primary structural finding: introduce a shared token source (CSS custom properties in a src/theme.css imported by index.css, or a TS theme.ts) before doing any rule-by-rule fix — otherwise each violation below has to be independently re-fixed per *.module.css file, and new files will keep reintroducing the same drift (as the reversed-gradient-stop-order bug on PuzzleResult.module.css:25 already shows).
  • Once tokens exist, Rules 9 and 18 become checkable as whole-palette rules and should be re-audited.
  • The 4-gray inconsistency (Rule 2) and the duplicated-gradient literals (Rule 4) are the same underlying fix — collapsing to a theme.css resolves both simultaneously.