Files
Lumotia/src/design-system/v0.3-quietware-tokens.css
Jake 2f11f493c5 v0.3 Phase 4g: four-tier semantic tokens + notice usage layer
Round-5 feedback flagged the muddy result of forcing one --color-caution
to be both bright yellow (signal) and accessible text (ochre). The
single-token-per-role model collapsed the visible identity. Caution
notices ended up reading as brown/khaki surfaces because color-mix(
in oklab, var(--color-caution) 8%, transparent) over cream produced
muddy results whatever the source hex.

New architecture: four-tier per-role tokens, hand-tuned -bg surfaces,
plus usage-specific notice tokens on top.

Tier model — each semantic role publishes four tokens:

  signal  Brand-true colour. Left bars, large icons, dots, focus rings.
          Looks like the role at a glance.
  ink     Accessible text variant. Used ONLY when text itself must
          carry the role colour. WCAG AA 4.5:1 on its surface.
  border  Middle value. Visible chip/notice outline; not text-safe.
  bg      Hand-tuned pale tint surface. NOT a color-mix derivation.

Example, light-mode caution:
  --color-caution-signal #FFCD00  (bright yellow)
  --color-caution-ink    #7A5D00  (ochre, for small text only)
  --color-caution-border #D9A900  (mid gold)
  --color-caution-bg     #FFF7D6  (clean pale yellow tint)

--color-{role} retained as a backward-compat alias pointing at -signal.
v0.2 callers keep working; new code reaches for the specific tier.

Usage layer — components subscribe to usage-specific tokens, not
role-tier tokens directly:

  --notice-{role}-bar     left bar, signal-tier
  --notice-{role}-icon    signal in dark, ink in light, signal in HC
  --notice-{role}-border  outer border colour
  --notice-{role}-bg      hand-tuned pale surface

LumotiaNotice reads these via Tailwind arbitrary-value classes
(bg-[var(--notice-info-bg)] etc). v0.2 fallback values defined at
:root (outside quietware blocks) using color-mix from v0.2 --color-
{role} tokens so the Notice keeps rendering sensibly when quietware
is off.

HC contract — every -bg token forces to transparent. HC notices
render text + icon + 2-px border + left bar, no body tint. Brand
atmosphere steps fully aside.

Per Jake's round-5 spec: yellow stays yellow as the visible signal,
ochre only appears as small-text ink, and brown/khaki never
represents the caution role's primary visual identity again. Same
discipline applies to red/blue/green — each role has a clean
hand-tuned -bg pale tint per theme.

Verified — visible result.

  Light caution notice: bright #FFCD00 bar + signal icon + ink-darkened
  small icon + pale #FFF7D6 surface + #D9A900 border. Clean,
  semantically clear, no mud.

  Dark caution notice: bright #FFCD00 bar + signal icon + dark warm
  #29210A surface + bright yellow border. Reads as yellow at a glance.

  HC modes: no body tint, strong bordered notices, semantic colours
  preserved as primary identifier alongside icon + label.

  npm run check: 0 errors, 0 warnings across 5707 files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:27:46 +01:00

470 lines
17 KiB
CSS

/* ============================================================
Lumotia v0.3 — Tactile Quietware tokens.
Phase 1 of the v0.3 release. Tokens only. No component refactors,
no layout changes, no behavioural shifts. The new palette and
typography activate only when html[data-design="quietware"] is set.
When the attribute is absent the runtime app behaves exactly as it
did in v0.2.
Activation paths:
1. Manual: add data-design="quietware" to <html> via dev tools.
2. Build flag (Phase 2): VITE_LUMOTIA_QUIETWARE=1 wires the
attribute through +layout.svelte automatically.
--------------------------------------------------------------
Phase 4g — four-tier semantic role tokens (2026-05-15 round 5).
One semantic role colour cannot do every UI job. Forcing
--color-caution to be both bright yellow (signal) AND accessible
text on cream (ochre) collapses the visible identity. Instead,
each role has four tier-specific tokens:
signal the brand-true colour for the role. Used for left
bars, large icons, dots, status pills, focus rings.
Looks like the role at a glance.
ink accessible text variant. Used ONLY when text itself
must carry the role colour (small chips, badges).
WCAG AA 4.5:1 on the surface it sits on.
border middle value. Visible as a notice or chip outline
but does not need to clear AA-text contrast.
bg hand-tuned pale tint surface. NOT a generic
color-mix derivation, because that produces muddy
results in mixed-luminance themes. Each value is
picked per theme so the surface stays clean.
Usage layer on top: --notice-{role}-{bar,icon,border,bg,title,body}.
Components reference the usage tokens so future per-usage
re-tuning does not require touching every site.
The source hue tokens stay (encoding role identity from Jake's
color.adobe.com palette). They are NEVER used in the runtime UI
directly. Use the tiered tokens.
Brand accent (--color-accent, copper-clay) remains distinct from
semantic yellow. Brand is action / Lumotia identity. Yellow is
caution / review.
Typography stack (V4 pairing, locked 2026-05-15):
Work Sans -> body, UI, controls, headers
Young Serif -> brand moments only (wordmark, empty-state hook)
JetBrains Mono -> logs, paths, model IDs
Source brief: docs/release/v0.3-tactile-quietware.md.
============================================================ */
@font-face {
font-family: 'Work Sans';
src: url('/fonts/work-sans-variable.woff2') format('woff2-variations'),
url('/fonts/work-sans-variable.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Work Sans';
src: url('/fonts/work-sans-italic-variable.woff2') format('woff2-variations'),
url('/fonts/work-sans-italic-variable.woff2') format('woff2');
font-weight: 100 900;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Young Serif';
src: url('/fonts/young-serif.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* ============================================================
Semantic source hues. Identity-only, never used directly.
Each --color-{role}-{tier} is a hand-tuned derivation.
============================================================ */
:root[data-design="quietware"] {
--semantic-red-source: #FF0700;
--semantic-yellow-source: #FFCD00;
--semantic-green-source: #00FF56;
--semantic-blue-source: #000AFF;
}
/* ============================================================
Root-level fallback notice tokens. When quietware is OFF the
v0.2 surface still gets sensible notice-* values derived from
the v0.2 --color-{role} tokens via color-mix. Quietware blocks
below override these with hand-tuned per-mode values.
============================================================ */
:root {
--notice-info-bar: var(--color-info);
--notice-info-icon: var(--color-info);
--notice-info-border: color-mix(in oklab, var(--color-info) 40%, transparent);
--notice-info-bg: color-mix(in oklab, var(--color-info) 8%, transparent);
--notice-danger-bar: var(--color-danger);
--notice-danger-icon: var(--color-danger);
--notice-danger-border: color-mix(in oklab, var(--color-danger) 40%, transparent);
--notice-danger-bg: color-mix(in oklab, var(--color-danger) 8%, transparent);
--notice-success-bar: var(--color-success);
--notice-success-icon: var(--color-success);
--notice-success-border: color-mix(in oklab, var(--color-success) 40%, transparent);
--notice-success-bg: color-mix(in oklab, var(--color-success) 8%, transparent);
--notice-caution-bar: var(--color-caution);
--notice-caution-icon: var(--color-caution);
--notice-caution-border: color-mix(in oklab, var(--color-caution) 40%, transparent);
--notice-caution-bg: color-mix(in oklab, var(--color-caution) 8%, transparent);
}
/* ============================================================
Quietware — dark mode default. Warm brown-charcoal atmosphere.
============================================================ */
:root[data-design="quietware"] {
--color-bg: #12100E;
--color-bg-elevated: #1A1713;
--color-bg-card: #211D18;
--color-bg-input: #181510;
--color-border: #4A4035;
--color-border-subtle: #2C261F;
--color-text: #F3EFE7;
--color-text-secondary: #BDB6A9;
--color-text-tertiary: #908779;
/* Brand accent. Copper-clay, distinct from semantic yellow. */
--color-accent: #C97845;
--color-accent-hover: #B96638;
--color-accent-subtle: #C9784514;
--color-accent-glow: #C9784525;
/* Four-tier role tokens. Signal stays bright (the role's identity);
ink lightens for text legibility on dark; border is a middle value;
bg is a hand-tuned dark tint, not a color-mix derivation. */
--color-info-signal: #6EA8FF;
--color-info-ink: #9CC6FF;
--color-info-border: #4D8DFF;
--color-info-bg: #101A2A;
--color-danger-signal: #FF5A52;
--color-danger-ink: #FF9B96;
--color-danger-border: #FF5A52;
--color-danger-bg: #2A1412;
--color-success-signal: #00FF56;
--color-success-ink: #7DFFA8;
--color-success-border: #00D94A;
--color-success-bg: #0D2415;
--color-caution-signal: #FFCD00;
--color-caution-ink: #FFE066;
--color-caution-border: #FFCD00;
--color-caution-bg: #29210A;
/* Backward-compat aliases. v0.2 callers of --color-danger etc.
get the signal variant by default. New code should reach for the
specific tier. */
--color-info: var(--color-info-signal);
--color-danger: var(--color-danger-signal);
--color-success: var(--color-success-signal);
--color-caution: var(--color-caution-signal);
--color-warning: var(--color-caution-signal);
/* Notice usage tokens. Components subscribe to these so future
per-usage retuning does not require touching every site. In dark
mode the notice icon uses signal (bright on dark surface);
title/body always neutral text. */
--notice-info-bar: var(--color-info-signal);
--notice-info-icon: var(--color-info-signal);
--notice-info-border: var(--color-info-border);
--notice-info-bg: var(--color-info-bg);
--notice-danger-bar: var(--color-danger-signal);
--notice-danger-icon: var(--color-danger-signal);
--notice-danger-border: var(--color-danger-border);
--notice-danger-bg: var(--color-danger-bg);
--notice-success-bar: var(--color-success-signal);
--notice-success-icon: var(--color-success-signal);
--notice-success-border: var(--color-success-border);
--notice-success-bg: var(--color-success-bg);
--notice-caution-bar: var(--color-caution-signal);
--notice-caution-icon: var(--color-caution-signal);
--notice-caution-border: var(--color-caution-border);
--notice-caution-bg: var(--color-caution-bg);
--color-sidebar: #0F0E0C;
--color-nav-active: #211D18;
--color-hover: #1A1713;
--color-overlay-dim: rgba(15, 14, 12, 0.7);
--font-family-body: 'Work Sans', system-ui, sans-serif;
--font-family-display: 'Young Serif', serif;
--font-family-mono: 'JetBrains Mono', monospace;
/* Phase 4e behavioural tokens for the HC rendering contract. */
--grain-opacity: 0.06;
--shadow-strength: 1;
--border-width-control: 1px;
--focus-ring-width: 2px;
--panel-radius: 8px;
--quietware-texture-opacity: var(--grain-opacity);
--quietware-texture-color: rgba(243, 239, 231, 1);
}
/* ============================================================
Quietware — light mode. Warm paper.
Caution signal stays bright #FFCD00; ink #7A5D00 only appears
when caution text itself must be coloured.
============================================================ */
:root[data-design="quietware"][data-theme="light"] {
--color-bg: #FBF8F2;
--color-bg-elevated: #F6F1EA;
--color-bg-card: #FFFDF8;
--color-bg-input: #F4EFE7;
--color-border: #897D70;
--color-border-subtle: #D8D0C4;
--color-text: #1D1B18;
--color-text-secondary: #5E574C;
--color-text-tertiary: #74685B;
--color-accent: #9D5F32;
--color-accent-hover: #854E29;
--color-accent-subtle: #9D5F3214;
--color-accent-glow: #9D5F3225;
/* Four-tier role tokens, light mode. Bg values hand-picked as
clean pale tints, not muddy color-mix products. */
--color-info-signal: #000AFF;
--color-info-ink: #0033A0;
--color-info-border: #3A6BFF;
--color-info-bg: #EEF3FF;
--color-danger-signal: #FF0700;
--color-danger-ink: #B3261E;
--color-danger-border: #E13A32;
--color-danger-bg: #FFF0EF;
--color-success-signal: #00D94A;
--color-success-ink: #006B2D;
--color-success-border: #159947;
--color-success-bg: #EAF8EE;
--color-caution-signal: #FFCD00;
--color-caution-ink: #7A5D00;
--color-caution-border: #D9A900;
--color-caution-bg: #FFF7D6;
--color-info: var(--color-info-signal);
--color-danger: var(--color-danger-signal);
--color-success: var(--color-success-signal);
--color-caution: var(--color-caution-signal);
--color-warning: var(--color-caution-signal);
/* In light mode the notice icon uses ink (the dark variant) so it
reads clearly against the pale -bg tint. Signal would wash out. */
--notice-info-bar: var(--color-info-signal);
--notice-info-icon: var(--color-info-ink);
--notice-info-border: var(--color-info-border);
--notice-info-bg: var(--color-info-bg);
--notice-danger-bar: var(--color-danger-signal);
--notice-danger-icon: var(--color-danger-ink);
--notice-danger-border: var(--color-danger-border);
--notice-danger-bg: var(--color-danger-bg);
--notice-success-bar: var(--color-success-signal);
--notice-success-icon: var(--color-success-ink);
--notice-success-border: var(--color-success-border);
--notice-success-bg: var(--color-success-bg);
--notice-caution-bar: var(--color-caution-signal);
--notice-caution-icon: var(--color-caution-ink);
--notice-caution-border: var(--color-caution-border);
--notice-caution-bg: var(--color-caution-bg);
--color-sidebar: #F3EEE6;
--color-nav-active: #E8E2D6;
--color-hover: #EAE4D8;
--color-overlay-dim: rgba(29, 27, 24, 0.55);
--grain-opacity: 0.07;
--quietware-texture-opacity: var(--grain-opacity);
--quietware-texture-color: rgba(29, 27, 24, 1);
}
/* ============================================================
Quietware — high-contrast. Simple and brutal.
All notice -bg goes transparent; -bar/-icon/-border collapse
to a single source colour per role. Semantic recognition by
colour identity + label + icon shape, not by ambient tinting.
============================================================ */
:root[data-design="quietware"][data-contrast="high"],
:root[data-design="quietware"][data-theme="light"][data-contrast="high"] {
--color-bg: #FFFFFF;
--color-bg-elevated: #FFFFFF;
--color-bg-card: #FFFFFF;
--color-bg-input: #FFFFFF;
--color-border: #000000;
--color-border-subtle: #000000;
--color-text: #000000;
--color-text-secondary: #000000;
--color-text-tertiary: #000000;
--color-accent: #005FCC;
--color-accent-hover: #004BA0;
--color-accent-subtle: transparent;
--color-accent-glow: transparent;
--color-info-signal: #000AFF;
--color-info-ink: #000AFF;
--color-info-border: #000AFF;
--color-info-bg: transparent;
--color-danger-signal: #FF0700;
--color-danger-ink: #FF0700;
--color-danger-border: #FF0700;
--color-danger-bg: transparent;
--color-success-signal: #00B83E;
--color-success-ink: #007A2B;
--color-success-border: #00B83E;
--color-success-bg: transparent;
--color-caution-signal: #FFCD00;
--color-caution-ink: #7A5D00;
--color-caution-border: #FFCD00;
--color-caution-bg: transparent;
--color-info: var(--color-info-signal);
--color-danger: var(--color-danger-signal);
--color-success: var(--color-success-signal);
--color-caution: var(--color-caution-signal);
--color-warning: var(--color-caution-signal);
--notice-info-bar: var(--color-info-signal);
--notice-info-icon: var(--color-info-signal);
--notice-info-border: var(--color-info-border);
--notice-info-bg: transparent;
--notice-danger-bar: var(--color-danger-signal);
--notice-danger-icon: var(--color-danger-signal);
--notice-danger-border: var(--color-danger-border);
--notice-danger-bg: transparent;
--notice-success-bar: var(--color-success-signal);
--notice-success-icon: var(--color-success-ink);
--notice-success-border: var(--color-success-border);
--notice-success-bg: transparent;
--notice-caution-bar: var(--color-caution-signal);
--notice-caution-icon: var(--color-caution-ink);
--notice-caution-border: var(--color-caution-border);
--notice-caution-bg: transparent;
--color-sidebar: #FFFFFF;
--color-nav-active: #000000;
--color-hover: #F0F0F0;
--color-overlay-dim: rgba(0, 0, 0, 0.85);
--grain-opacity: 0;
--shadow-strength: 0;
--border-width-control: 2px;
--focus-ring-width: 3px;
--panel-radius: 8px;
--quietware-texture-opacity: 0;
--quietware-texture-color: rgba(0, 0, 0, 0);
}
/* Dark-base high-contrast — black background, white borders. Used
when the user explicitly wants HC inside the dark theme. */
:root[data-design="quietware"][data-contrast="high"]:not([data-theme="light"]) {
--color-bg: #000000;
--color-bg-elevated: #000000;
--color-bg-card: #000000;
--color-bg-input: #000000;
--color-border: #FFFFFF;
--color-border-subtle: #FFFFFF;
--color-text: #FFFFFF;
--color-text-secondary: #FFFFFF;
--color-text-tertiary: #FFFFFF;
--color-accent: #75A8F0;
--color-accent-hover: #5C92E0;
/* HC dark inverts the contrast direction: signals are the lighter
end of each hue, not the saturated source. -bg stays transparent
so the black surface shows through directly. */
--color-info-signal: #8AB4F8;
--color-info-ink: #8AB4F8;
--color-info-border: #8AB4F8;
--color-info-bg: transparent;
--color-danger-signal: #FF8A8A;
--color-danger-ink: #FF8A8A;
--color-danger-border: #FF8A8A;
--color-danger-bg: transparent;
--color-success-signal: #79D59B;
--color-success-ink: #79D59B;
--color-success-border: #79D59B;
--color-success-bg: transparent;
--color-caution-signal: #F2C94C;
--color-caution-ink: #FFE066;
--color-caution-border: #F2C94C;
--color-caution-bg: transparent;
--color-info: var(--color-info-signal);
--color-danger: var(--color-danger-signal);
--color-success: var(--color-success-signal);
--color-caution: var(--color-caution-signal);
--color-warning: var(--color-caution-signal);
--notice-info-icon: var(--color-info-signal);
--notice-danger-icon: var(--color-danger-signal);
--notice-success-icon: var(--color-success-signal);
--notice-caution-icon: var(--color-caution-signal);
--color-sidebar: #000000;
--color-nav-active: #FFFFFF;
--color-hover: #1A1A1A;
}
/* ============================================================
Static grain overlay. Reads --grain-opacity. Zero in HC mode by
contract. Fixed-position so it does not scroll.
============================================================ */
:root[data-design="quietware"] body::after {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9999;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
opacity: var(--grain-opacity);
mix-blend-mode: overlay;
}
/* ============================================================
Reduced motion. Honour OS preference; quietware rules require it.
============================================================ */
@media (prefers-reduced-motion: reduce) {
:root[data-design="quietware"] * {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}