# Tinted UI Design Principles

Distilled from the essay 《你的UI廉价，错在颜色》. These are the rules the
generator enforces. Read this when you need to explain *why* a token looks the
way it does, or when hand-tuning a system the script produced.

## The one decision

Cheap-looking UIs use **pure neutral colors**: `#FFFFFF`, `#000000`, `#808080`.
Expensive-looking UIs never do. Every neutral surface carries a few percent of
the brand hue. Brand color does not sit *on top* of the design — it *flows
through* it.

## Where the temperature lives (3 core places)

1. **Backgrounds & surfaces** — mix the brand color into the gray ramp. The
   background leans toward the brand; cards, borders and muted text lean too.
   The shift is tiny (2–3%) but it is what separates "designed" from "assembled".
2. **Shadows** — never `rgba(0,0,0,0.1)`. A shadow is light *absence*; it takes
   the temperature of the surrounding light. Blue brand → blue-violet shadow;
   warm brand → amber shadow. Keep alpha very low. The `0 0 0 1px` hairline
   border is part of the same shadow system and inherits its temperature.
3. **Text hierarchy** — primary text is near-black with a hue lean, not `#000`.
   Muted text follows the brand's warm/cool direction, not flat gray. Values
   still pass WCAG AA at body sizes — temperature is added inside the existing
   contrast budget, never at the cost of readability. **The generator now
   enforces this automatically**: every text-on-surface pair is measured with
   the WCAG 2.1 relative-luminance formula and, if it falls below AA
   (4.5:1 normal / 3:1 muted), the text is nudged along its HSL lightness axis
   until it passes — so readability always wins, and the brand hue-lean is kept
   wherever lightness does not hit an extreme.

## Three extensions (where systems usually break)

4. **Semantic colors** — error/warning/success must also be nudged toward the
   brand temperature (about 6–10° on the hue wheel). A red error in a blue
   product is "correct signal, wrong temperature" — like a fire alarm in a
   library. Keep it red, just lean it a few degrees. Their *subtle* backgrounds
   must lean toward the brand field, not default pink/amber/green.
5. **Gradients** — both stops must be tinted; never let one end drift to pure
   white. Direction should match natural light on a surface of that temperature
   (cool: deep-blue corner → light blue-white center; warm: deep amber bottom →
   cream top). Mesh gradients: every radial point carries brand hue, base is the
   tinted `--color-bg`, never `#FFFFFF`.
6. **Icons & illustration** — icon stroke color IS the text token (same stack,
   not an approximation). Icon container background IS `--color-brand-subtle`.
   Illustration shadows are derived from the same token family as UI shadows, so
   illustration and chrome look lit by one source.

## The test

Desaturate the whole interface to grayscale. If hierarchy still holds, the
temperature work is correct and invisible. If it collapses, the hue was doing
too much structural work — fix the gray ramp first, then re-add temperature.

## Three rules, no exceptions

- Never use pure neutral colors. Every neutral gets a hue, even 2%.
- Shadow color matches brand temperature. Pure black shadows belong to no system.
- Every interactive state (hover/focus/active) is tinted with `--color-brand-subtle`
  or a slightly more saturated surface — never a flat gray overlay.

## Pre-delivery checklist · 交付前验收清单

Run this before you ship a system the generator produced. The generator enforces
most of it automatically, but the checklist catches the *human* steps (wiring,
review, edge brands).

- [ ] **No pure neutrals** — grep the output for `#FFFFFF`, `#000000`, `#808080`.
      None should appear (degenerate black/white/gray brands are the only exception,
      and even then the generator keeps a brand-lean where a hue exists).
- [ ] **Contrast** — body text vs its surface is ≥ 4.5:1; muted text vs surface is
      ≥ 3:1; `--color-on-brand` (text on the brand button) is ≥ 4.5:1 on the
      brand hue. The generator guarantees this, but re-check after any hand-tune.
- [ ] **Dark mode** — open `preview.html`, toggle dark, and confirm the topbar
      title + body text stay legible. Tinting must not wash out in dark.
- [ ] **Grayscale test** — desaturate the rendered UI; hierarchy (background →
      surface → text → muted) must still read. If it collapses, fix the gray ramp
      first, then re-add temperature.
- [ ] **Semantic signal preserved** — error stays red, warning amber, success
      green; the generator only leans them a few degrees toward the brand, never
      swaps the hue.
- [ ] **Interactive states tinted** — hover/focus/active use `--color-brand-subtle`
      or a more saturated surface, not a gray overlay.
- [ ] **Drop-in sanity** — `tokens.css` `:root` variables override the framework
      defaults without a specificity war; no `!important` needed.
- [ ] **Format parity** — if you emitted `--format all`, `tokens.css`,
      `tokens.json` (DTCG), `tailwind.config.js`, and `_tokens.scss` all exist and
      carry the same values.

## Common mistakes / anti-patterns · 常见错误与反模式

- **Pure neutrals "for safety."** Designers sometimes reset a value to `#FFF` to
  "be sure it's clean." That is exactly the cheap look. Keep the 2–3% hue-lean.
- **Over-tinting.** `tint-strength: strong` on an already-saturated brand reads as
  "themed," not "designed." Default `normal` is right for most products; reserve
  `strong` for brand-forward marketing surfaces.
- **Trusting the hue for structure.** Temperature is a *finish*, not a hierarchy
  system. Never let the brand hue carry the contrast budget — lightness does.
- **Forgetting dark mode in review.** Tinted light themes often look great and
  then wash out in dark. Always toggle before sign-off.
- **Swapping semantic hues "to match brand."** A blue error in a blue product is a
  wrong signal. Lean, don't swap.
- **Hand-editing a token then skipping the contrast re-check.** One nudge can drop
  a pair below AA. Re-run the generator or re-measure after any manual change.
- **Mixing token sources.** Don't patch half the set from one brand color and half
  from another — pick one brand and regenerate the whole set.

## Why this skill exists

The cheap-vs-premium gap is almost never the layout or the font. It is the
neutrals. Flat `#FFF`/`#000`/`#808080` read as "template"; a 2–3% brand hue-lean
reads as "someone designed this." This skill automates that single, high-leverage
move — and keeps it accessible while it does.
