v0.3 Phase 4j: colour grammar correction — record red, brand orange separate, wirelines bumped

Major correction per round-8 redirect.

Problem.

  - Brown/copper --color-accent #C97845/#9D5F32 was dragging the UI
    back into the warm-brutalist palette and competing with semantic
    yellow.
  - Record button about to become blue (Lumotia primary). Wrong —
    record is universally red across products and OSes.
  - Brand orange conflated with primary action. Orange should be
    brand mark only.

Solution.

  Source tokens — identity-only, never used directly in components:
    --source-red    #FF0700
    --source-yellow #FFCD00
    --source-green  #00FF56
    --source-blue   #000AFF
    --source-orange-brand #F0620A
    --{role}-complement-source for the wireline layer

  Component tokens — what components subscribe to. Hand-tuned hex
  values verified at WCAG AA on the foreground each button pairs with.
  Tonal scales (red-50..red-950 etc.) reserved for a focused future
  commit; component names stay stable through that upgrade:

    --button-record-bg     #DC2626  (red)
    --button-record-fg     #FFFFFF
    --button-record-border #B91C1C
    --button-record-wire   #00D8E0  (cyan, red's complement)

    --button-primary-bg    #2563EB  (blue)
    --button-primary-fg    #FFFFFF
    --button-primary-wire  #F6E600  (yellow, blue's complement)

    --button-danger-bg     same as record (red, fg white, cyan wire)

    --button-success-bg    #15803D dark / #166534 light  (green)
    --button-success-wire  #E600A0  (magenta, green's complement)

    --button-caution-bg    #FACC15  (yellow, fg #1A1500 near-black)
    --button-caution-wire  #1850D8  (blue, yellow's complement)

    --brand-accent          #F0620A (orange, LOGO ONLY)
    --brand-wire            #0A98F0 (cyan-blue, orange's complement)

  --color-accent repointed:
    quietware dark:   #4A7BFF  (was #C97845 copper)
    quietware light:  #1D4ED8  (was #9D5F32 copper)
    HC:               #005FCC  (existing, unchanged)
    v0.2 fallback:    var(--color-accent) preserved → v0.2 stays amber.

  New LumotiaButton variant: "record". Used by DictationPage's
  record control. Existing v0.2 record button class swapped from
  bg-accent (amber) to bg-[var(--button-record-bg)] (red). v0.2
  fallback at :root maps --button-record-bg to var(--color-danger)
  so non-quietware surface keeps its red-when-recording / amber-when-
  idle behaviour.

Wirelines (renamed from counterlines).

    --wire-width 1px / --wire-width-active 2px / --wire-width-focus 2px
    --wire-opacity-dark 0.75 (was 0.32; bumped per round-8 spec)
    --wire-opacity-light 0.6 (was 0.22)
    --wire-opacity-active 0.9
    --wire-opacity-focus 1
    --wire-opacity is a mode-aware resolver (-dark dark / -light light)
    --wire-style solid (dotted reserved for design-preview / drag-drop)

  HC contract: --wire-width 0, --wire-width-focus 2px kept.
  --focus-ring-color #005FCC (strong blue), not a decorative complement.

  Legacy --counterline-* tokens kept as aliases pointing at the new
  --wire-* set so Phase 4h/4i opt-in code still works.

Design-system-v2 preview now shows the new record variant alongside
primary so both red and blue button identities are visible.

Verified.

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

Phase 5c (Dictation layout migration) still queued for the next
focused sitting. This commit gives Phase 5c the correct grammar to
build on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 15:39:21 +01:00
parent 001d8fe83d
commit 2c6f5ca230
5 changed files with 294 additions and 86 deletions

View File

@@ -351,6 +351,64 @@ LumotiaNotice now reads these via Tailwind arbitrary-value classes (`bg-[var(--n
Per Jake's round-5 spec: yellow stays yellow (visible signal), ochre only appears as small-text ink, and brown/khaki never represents the caution role's primary identity again. Per Jake's round-5 spec: yellow stays yellow (visible signal), ochre only appears as small-text ink, and brown/khaki never represents the caution role's primary identity again.
### Phase 4j — Colour grammar correction (round-8 redirect). Landed 2026-05-15.
Major correction. Brown/copper accent was dragging the UI back into the warm-brutalist palette and competing with semantic yellow. Record button was about to become blue (wrong — record is universally red). Brand orange got conflated with primary action.
**New colour grammar:**
| Colour | Role |
|---|---|
| Red `#FF0700` | record / error / destructive / blocked |
| Blue `#000AFF` | primary app action / info / processing |
| Green `#00FF56` | ready / saved / success |
| Yellow `#FFCD00` | caution / needs review / pending |
| Orange `#F0620A` | Lumotia brand mark only, NOT default action |
| Neutral | atmosphere, panels, text, disabled |
**Token architecture:**
```css
/* Source tokens — identity-only, never used directly in components */
--source-red, --source-yellow, --source-green, --source-blue, --source-orange-brand
--red-complement-source, --yellow-complement-source, --green-complement-source,
--blue-complement-source, --orange-complement-source
/* Component tokens — what components actually subscribe to */
--button-record-bg/fg/border/wire (red + cyan)
--button-primary-bg/fg/border/wire (blue + yellow)
--button-danger-bg/fg/border/wire (red + cyan)
--button-success-bg/fg/border/wire (green + magenta)
--button-caution-bg/fg/border/wire (yellow + blue)
--brand-accent + --brand-wire (orange + blue)
```
Tonal scales (red-50..red-950 etc.) reserved for a focused future commit. Component tokens currently pick hand-tuned hex values verified at WCAG AA on their target foreground; the rename to scale-derived values stays invisible to callers.
**Visible changes:**
- `--color-accent` (primary action default) repointed to BLUE — was copper-clay, now `#4A7BFF` dark / `#1D4ED8` light. All buttons that read `bg-accent` (including LumotiaButton primary, LumotiaProgress default tone) inherit blue under quietware.
- New `--brand-accent` token at `#F0620A`, separate from `--color-accent`. Used only on Lumotia logo / brand surfaces.
- New LumotiaButton variant `record` (red, with cyan wireline complement). Dictation page's record button repointed to `--button-record-bg`. The existing recording-state still uses `--color-danger` which now resolves to red.
- v0.2 fallback tokens defined at `:root` so non-quietware app keeps rendering with v0.2 amber.
**Wirelines (renamed from counterlines):**
```
--wire-width: 1px (Level 1 quiet)
--wire-width-active: 2px (Level 2 active)
--wire-width-focus: 2px (Level 2 focus)
--wire-opacity: var(--wire-opacity-dark) / -light per mode
--wire-opacity-dark: 0.75 (was 0.32 — bumped for visibility)
--wire-opacity-light: 0.6 (was 0.22)
--wire-opacity-focus: 1
--wire-style: solid (dotted reserved for design-preview / drag-drop)
```
Legacy `--counterline-*` tokens kept as aliases pointing at the new `--wire-*` set so Phase 4h/4i opt-in code still works.
HC contract zeroes `--wire-width` and `--wire-opacity-*` but keeps `--wire-width-focus: 2px`. HC focus ring is `--focus-ring-color: #005FCC` (strong blue), not a complement.
### Phase 4i — Counterlines as interaction-affordance identity. Landed 2026-05-15. ### Phase 4i — Counterlines as interaction-affordance identity. Landed 2026-05-15.
Round-7 redirect (Option D): move counterlines from notices (where they're invisible) to interactive controls (where they add identity). Three-level model: Round-7 redirect (Option D): move counterlines from notices (where they're invisible) to interactive controls (where they add identity). Three-level model:

View File

@@ -84,14 +84,40 @@
} }
/* ============================================================ /* ============================================================
Semantic source hues. Identity-only, never used directly. Phase 4j — colour-grammar correction (2026-05-15 round 8).
Each --color-{role}-{tier} is a hand-tuned derivation.
Five source hues + five complement sources. Never used directly
in component CSS; consumed only by role and component tokens.
Red record / error / destructive / blocked
Blue primary app action / info / processing
Green ready / saved / success
Yellow caution / needs review / pending
Orange Lumotia brand mark only, NOT default action
Component tokens are defined below in each theme block. Tonal
scales (red-50 .. red-950 etc.) will land in a focused future
commit; for now the component layer picks hand-tuned hex values
verified at WCAG AA on their target surface.
============================================================ */ ============================================================ */
:root[data-design="quietware"] { :root[data-design="quietware"] {
--semantic-red-source: #FF0700; --source-red: #FF0700;
--semantic-yellow-source: #FFCD00; --source-yellow: #FFCD00;
--semantic-green-source: #00FF56; --source-green: #00FF56;
--semantic-blue-source: #000AFF; --source-blue: #000AFF;
--source-orange-brand: #F0620A;
--red-complement-source: #00F8FF;
--yellow-complement-source: #0032FF;
--green-complement-source: #FF00A9;
--blue-complement-source: #FFF500;
--orange-complement-source: #0A98F0;
/* Legacy alias retained for the v0.2 path. */
--semantic-red-source: var(--source-red);
--semantic-yellow-source: var(--source-yellow);
--semantic-green-source: var(--source-green);
--semantic-blue-source: var(--source-blue);
} }
/* ============================================================ /* ============================================================
@@ -120,6 +146,40 @@
--notice-caution-icon: var(--color-caution); --notice-caution-icon: var(--color-caution);
--notice-caution-border: color-mix(in oklab, var(--color-caution) 40%, transparent); --notice-caution-border: color-mix(in oklab, var(--color-caution) 40%, transparent);
--notice-caution-bg: color-mix(in oklab, var(--color-caution) 8%, transparent); --notice-caution-bg: color-mix(in oklab, var(--color-caution) 8%, transparent);
/* v0.2 fallback button-component tokens. When quietware is off these
keep components rendering with their existing v0.2 colours via the
v0.2 --color-accent / --color-danger semantic tokens. Quietware
blocks below override with the Phase 4j specific values. */
--button-record-bg: var(--color-danger);
--button-record-fg: #FFFFFF;
--button-record-border: var(--color-danger);
--button-record-wire: transparent;
--button-primary-bg: var(--color-accent);
--button-primary-fg: #FFFFFF;
--button-primary-border: var(--color-accent);
--button-primary-wire: transparent;
--button-danger-bg: var(--color-danger);
--button-danger-fg: #FFFFFF;
--button-danger-border: var(--color-danger);
--button-danger-wire: transparent;
--button-success-bg: var(--color-success);
--button-success-fg: #FFFFFF;
--button-success-border: var(--color-success);
--button-success-wire: transparent;
--button-caution-bg: var(--color-caution);
--button-caution-fg: #000000;
--button-caution-border: var(--color-caution);
--button-caution-wire: transparent;
--brand-accent: var(--color-accent);
--wire-width: 0px;
--wire-width-focus: 2px;
--wire-opacity-dark: 0;
--wire-opacity-light: 0;
--wire-opacity-focus: 0;
--wire-style: solid;
--focus-ring-color: var(--color-accent);
} }
/* ============================================================ /* ============================================================
@@ -137,11 +197,19 @@
--color-text-secondary: #BDB6A9; --color-text-secondary: #BDB6A9;
--color-text-tertiary: #908779; --color-text-tertiary: #908779;
/* Brand accent. Copper-clay, distinct from semantic yellow. */ /* Phase 4j — brand-accent is bright orange #F0620A, restricted to
--color-accent: #C97845; logo / brand surfaces. Primary action colour is BLUE, not orange.
--color-accent-hover: #B96638; --color-accent stays as the convenience alias for primary action
--color-accent-subtle: #C9784514; so v0.2 component callers (bg-accent etc.) get blue under
--color-accent-glow: #C9784525; quietware. Brand-only surfaces use --brand-accent. */
--brand-accent: var(--source-orange-brand);
--brand-accent-strong: #C24E07;
--brand-wire: var(--orange-complement-source);
--color-accent: #4A7BFF; /* primary action — blue, AA on dark + white */
--color-accent-hover: #2F62F0;
--color-accent-subtle: #4A7BFF14;
--color-accent-glow: #4A7BFF25;
/* Four-tier role tokens. Signal stays bright (the role's identity); /* Four-tier role tokens. Signal stays bright (the role's identity);
ink lightens for text legibility on dark; border is a middle value; ink lightens for text legibility on dark; border is a middle value;
@@ -219,53 +287,79 @@
--quietware-texture-color: rgba(243, 239, 231, 1); --quietware-texture-color: rgba(243, 239, 231, 1);
/* ---------------------------------------------------------- /* ----------------------------------------------------------
Phase 4icounterlines as interactive-affordance identity Phase 4jwirelines (renamed from counterlines) and component
(per round-7 redirect). Three-level model: tokens for buttons (round-8 spec).
Level 0 no counterline (panels, notices, content, disabled) Wireline levels (unchanged):
Level 1 quiet (controls, chips, active nav) Level 0 none panels, notices (default), content, disabled
Level 2 strong / focus (keyboard focus, recording state) Level 1 quiet controls, chips, active nav
Level 2 focus keyboard focus, recording state
Notices retire from default counterline rendering (they have Opacity bumped per round-8 (was 0.32/0.22/0.85; now 0.75/0.6/1.0)
enough semantic language already: bar + icon + border + bg + so Level 1 reads at standard viewing without becoming noisy.
label). Notice can still opt in via the `counterline` prop.
HC contract zeroes the normal width but keeps the focus width
because the HC focus ring IS the tactile detail in that mode.
---------------------------------------------------------- */ ---------------------------------------------------------- */
--counterline-width: 1px; --wire-width: 1px;
--counterline-width-focus: 2px; --wire-width-active: 2px;
--counterline-opacity-dark: 0.48; --wire-width-focus: 2px;
--counterline-opacity-light: 0.34; --wire-opacity-dark: 0.75;
--counterline-opacity-focus: 0.85; --wire-opacity-light: 0.6;
--wire-opacity-active: 0.9;
--wire-opacity-focus: 1;
--wire-opacity: var(--wire-opacity-dark); /* mode-aware resolver */
--wire-style: solid;
/* Raw complementary hues. Brand orange gets its own counterline /* Legacy counterline aliases retained for Phase 4h/4i opt-in path. */
(soft blue) so primary buttons land in the same identity as the --counterline-width: var(--wire-width);
other roles. */ --counterline-width-focus: var(--wire-width-focus);
--role-info-counterline: var(--color-accent); --counterline-opacity-dark: var(--wire-opacity-dark);
--role-danger-counterline: #6EA8FF; --counterline-opacity-light: var(--wire-opacity-light);
--role-success-counterline: #B891FF; --counterline-opacity-focus: var(--wire-opacity-focus);
--role-caution-counterline: #3A6BFF;
--role-brand-counterline: #3A6BFF;
/* Component usage tokens. Buttons + chips + nav subscribe to these. /* Component tokens for buttons. Hand-tuned hex values cleared at
Default opacity is dark; light-mode override below replaces. */ WCAG AA on the foreground each button pairs with. Tonal scales
--button-brand-counterline: color-mix(in srgb, var(--role-brand-counterline) calc(var(--counterline-opacity-dark) * 100%), transparent); (red-50..red-950 etc.) will replace these in a focused future
--button-danger-counterline: color-mix(in srgb, var(--role-danger-counterline) calc(var(--counterline-opacity-dark) * 100%), transparent); commit; component names stay stable. */
--button-info-counterline: color-mix(in srgb, var(--role-info-counterline) calc(var(--counterline-opacity-dark) * 100%), transparent); --button-record-bg: #DC2626;
--button-success-counterline: color-mix(in srgb, var(--role-success-counterline) calc(var(--counterline-opacity-dark) * 100%), transparent); --button-record-fg: #FFFFFF;
--button-caution-counterline: color-mix(in srgb, var(--role-caution-counterline) calc(var(--counterline-opacity-dark) * 100%), transparent); --button-record-border: #B91C1C;
--button-record-wire: #00D8E0; /* cyan — complement of red */
/* Notice-level counterline tokens kept available for opt-in only. --button-primary-bg: #2563EB;
LumotiaNotice no longer applies them by default per round-7. */ --button-primary-fg: #FFFFFF;
--notice-info-counterline: color-mix(in srgb, var(--role-info-counterline) calc(var(--counterline-opacity-dark) * 100%), transparent); --button-primary-border: #1D4ED8;
--notice-danger-counterline: color-mix(in srgb, var(--role-danger-counterline) calc(var(--counterline-opacity-dark) * 100%), transparent); --button-primary-wire: #F6E600; /* yellow — complement of blue */
--notice-success-counterline: color-mix(in srgb, var(--role-success-counterline) calc(var(--counterline-opacity-dark) * 100%), transparent);
--notice-caution-counterline: color-mix(in srgb, var(--role-caution-counterline) calc(var(--counterline-opacity-dark) * 100%), transparent);
/* Focus ring colour — defaults to brand accent. Components can --button-danger-bg: var(--button-record-bg);
override per-context (eg. info focus = info-counterline). */ --button-danger-fg: var(--button-record-fg);
--focus-ring-color: var(--color-accent); --button-danger-border: var(--button-record-border);
--button-danger-wire: var(--button-record-wire);
--button-success-bg: #15803D;
--button-success-fg: #FFFFFF;
--button-success-border: #14532D;
--button-success-wire: #E600A0; /* magenta — complement of green */
--button-caution-bg: #FACC15;
--button-caution-fg: #1A1500;
--button-caution-border: #CA8A04;
--button-caution-wire: #1850D8; /* blue — complement of yellow */
/* Notice counterline tokens retired from defaults (LumotiaNotice
stays opt-in via the `counterline` prop). Tokens kept available
for the opt-in path; same value pattern as Phase 4i. */
--notice-info-counterline: color-mix(in srgb, var(--source-blue) calc(var(--wire-opacity-dark) * 100%), transparent);
--notice-danger-counterline: color-mix(in srgb, var(--red-complement-source) calc(var(--wire-opacity-dark) * 100%), transparent);
--notice-success-counterline: color-mix(in srgb, var(--green-complement-source) calc(var(--wire-opacity-dark) * 100%), transparent);
--notice-caution-counterline: color-mix(in srgb, var(--yellow-complement-source) calc(var(--wire-opacity-dark) * 100%), transparent);
--button-brand-counterline: var(--brand-wire);
/* Focus ring colour — defaults to primary blue. Components can
override per-context (eg. record-button focus could use red). */
--focus-ring-color: var(--button-primary-bg);
}
[data-wire-style="dotted"] {
--wire-style: dotted;
} }
/* ============================================================ /* ============================================================
@@ -285,10 +379,16 @@
--color-text-secondary: #5E574C; --color-text-secondary: #5E574C;
--color-text-tertiary: #74685B; --color-text-tertiary: #74685B;
--color-accent: #9D5F32; /* Phase 4j — brand-accent stays as #F0620A (logo only); --color-accent
--color-accent-hover: #854E29; (primary action) is BLUE under quietware. */
--color-accent-subtle: #9D5F3214; --brand-accent: var(--source-orange-brand);
--color-accent-glow: #9D5F3225; --brand-accent-strong: #C24E07;
--brand-wire: var(--orange-complement-source);
--color-accent: #1D4ED8; /* primary blue, AA on cream */
--color-accent-hover: #1E40AF;
--color-accent-subtle: #1D4ED814;
--color-accent-glow: #1D4ED825;
/* Four-tier role tokens, light mode. Bg values hand-picked as /* Four-tier role tokens, light mode. Bg values hand-picked as
clean pale tints, not muddy color-mix products. */ clean pale tints, not muddy color-mix products. */
@@ -349,18 +449,44 @@
--quietware-texture-opacity: var(--grain-opacity); --quietware-texture-opacity: var(--grain-opacity);
--quietware-texture-color: rgba(29, 27, 24, 1); --quietware-texture-color: rgba(29, 27, 24, 1);
/* Phase 4i light-mode counterlines. Lower opacity per Jake's spec /* Phase 4j light-mode component tokens for buttons. Same component
so the detail stays restrained against the cream surface. */ names as dark mode; values tuned for paper-mode contrast. The
--button-brand-counterline: color-mix(in srgb, var(--role-brand-counterline) calc(var(--counterline-opacity-light) * 100%), transparent); filled buttons keep their dark-mode bg colours (they pair with
--button-danger-counterline: color-mix(in srgb, var(--role-danger-counterline) calc(var(--counterline-opacity-light) * 100%), transparent); white foreground regardless of theme); the wireline tokens drop
--button-info-counterline: color-mix(in srgb, var(--role-info-counterline) calc(var(--counterline-opacity-light) * 100%), transparent); opacity via --wire-opacity-light when applied. */
--button-success-counterline: color-mix(in srgb, var(--role-success-counterline) calc(var(--counterline-opacity-light) * 100%), transparent); --button-record-bg: #B91C1C; /* slightly darker red so 4.5:1 holds */
--button-caution-counterline: color-mix(in srgb, var(--role-caution-counterline) calc(var(--counterline-opacity-light) * 100%), transparent); --button-record-fg: #FFFFFF;
--button-record-border: #7F1D1D;
--button-record-wire: #00D8E0;
--notice-info-counterline: color-mix(in srgb, var(--role-info-counterline) calc(var(--counterline-opacity-light) * 100%), transparent); --button-primary-bg: #1D4ED8;
--notice-danger-counterline: color-mix(in srgb, var(--role-danger-counterline) calc(var(--counterline-opacity-light) * 100%), transparent); --button-primary-fg: #FFFFFF;
--notice-success-counterline: color-mix(in srgb, var(--role-success-counterline) calc(var(--counterline-opacity-light) * 100%), transparent); --button-primary-border: #1E3A8A;
--notice-caution-counterline: color-mix(in srgb, var(--role-caution-counterline) calc(var(--counterline-opacity-light) * 100%), transparent); --button-primary-wire: #F6E600;
--button-danger-bg: var(--button-record-bg);
--button-danger-fg: var(--button-record-fg);
--button-danger-border: var(--button-record-border);
--button-danger-wire: var(--button-record-wire);
--button-success-bg: #166534;
--button-success-fg: #FFFFFF;
--button-success-border: #14532D;
--button-success-wire: #E600A0;
--button-caution-bg: #FACC15;
--button-caution-fg: #1A1500;
--button-caution-border: #A16207;
--button-caution-wire: #1850D8;
--wire-opacity: var(--wire-opacity-light); /* light-mode resolver */
/* Legacy notice counterline tokens — opt-in only. */
--notice-info-counterline: color-mix(in srgb, var(--source-blue) calc(var(--wire-opacity-light) * 100%), transparent);
--notice-danger-counterline: color-mix(in srgb, var(--red-complement-source) calc(var(--wire-opacity-light) * 100%), transparent);
--notice-success-counterline: color-mix(in srgb, var(--green-complement-source) calc(var(--wire-opacity-light) * 100%), transparent);
--notice-caution-counterline: color-mix(in srgb, var(--yellow-complement-source) calc(var(--wire-opacity-light) * 100%), transparent);
--button-brand-counterline: var(--brand-wire);
} }
/* ============================================================ /* ============================================================

View File

@@ -917,17 +917,24 @@
{:else} {:else}
<!-- Control strip --> <!-- Control strip -->
<div class="flex items-center gap-3 px-5 h-[56px] border-b border-border-subtle flex-shrink-0"> <div class="flex items-center gap-3 px-5 h-[56px] border-b border-border-subtle flex-shrink-0">
<!-- Record button — Sub-task B: enlarged to 80px (≥ 80px, primary CTA, impossible to miss) --> <!-- Record button — Sub-task B: enlarged to 80px (≥ 80px, primary CTA, impossible to miss).
v0.3 Phase 4j: idle state uses --button-record-bg (red, globally recognised
recording colour) and the wireline subscribes to --button-record-wire
(cyan complement). v0.2 fallback at :root maps both to --color-danger
and transparent so v0.2 surface is unaffected. -->
<button <button
class="relative flex items-center justify-center w-[80px] h-[80px] min-w-[80px] flex-shrink-0 rounded-full btn-filled-text class="relative flex items-center justify-center w-[80px] h-[80px] min-w-[80px] flex-shrink-0 rounded-full
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 text-[var(--button-record-fg)]
focus-visible:outline-none focus-visible:ring-[var(--wire-width-focus,2px)]
focus-visible:ring-[color-mix(in_srgb,var(--button-record-bg)_calc(var(--wire-opacity-focus,1)*100%),transparent)]
focus-visible:ring-offset-2
{page.recording {page.recording
? 'bg-danger animate-pulse-warm' ? 'bg-danger animate-pulse-warm shadow-[inset_0_0_0_var(--wire-width-active,0)_color-mix(in_srgb,var(--button-record-wire)_calc(var(--wire-opacity-active,0)*100%),transparent)]'
: modelLoading : modelLoading
? 'bg-warning opacity-60 cursor-wait' ? 'bg-warning opacity-60 cursor-wait'
: !tauriRuntimeAvailable : !tauriRuntimeAvailable
? 'bg-bg-elevated text-text-tertiary cursor-not-allowed' ? 'bg-bg-elevated text-text-tertiary cursor-not-allowed'
: 'bg-accent hover:bg-accent-hover shadow-[var(--shadow-accent-lg)]'}" : 'bg-[var(--button-record-bg)] hover:bg-[var(--button-record-border)] shadow-[inset_0_0_0_var(--wire-width,0)_color-mix(in_srgb,var(--button-record-wire)_calc(var(--wire-opacity,0)*100%),transparent)]'}"
onclick={toggleRecording} onclick={toggleRecording}
disabled={modelLoading || !tauriRuntimeAvailable} disabled={modelLoading || !tauriRuntimeAvailable}
aria-label={page.recording ? "Stop recording" : "Start recording"} aria-label={page.recording ? "Stop recording" : "Start recording"}

View File

@@ -6,7 +6,7 @@
// the headless library would only add weight here. // the headless library would only add weight here.
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
type Variant = "primary" | "secondary" | "tertiary" | "destructive"; type Variant = "primary" | "secondary" | "tertiary" | "destructive" | "record";
type Size = "sm" | "md" | "lg"; type Size = "sm" | "md" | "lg";
interface Props { interface Props {
@@ -47,25 +47,41 @@
// (amber/copper) and pairs with .btn-filled-text for AA. Destructive // (amber/copper) and pairs with .btn-filled-text for AA. Destructive
// mirrors primary on --color-danger. // mirrors primary on --color-danger.
// //
// v0.3 Phase 4iinteractive counterlines. primary + destructive // v0.3 Phase 4jcolour-grammar correction.
// gain a Level-1 inset counterline (softened complement) under
// quietware. Secondary + tertiary stay clean. HC contract zeroes
// --counterline-width so the effect vanishes in HC.
// //
// Focus state uses Level-2 width (2 px) + --focus-ring-color via a // primary blue (was amber, now correct primary action)
// focus-visible ring. Tailwind's ring utility writes to a separate // record red (globally recognised recording colour)
// box-shadow slot so it composes cleanly with the inset counterline. // destructive red (same fill as record, distinct semantic)
// secondary neutral
// tertiary transparent
//
// Brand orange (#F0620A) is reserved for the Lumotia logo / brand
// mark only and does NOT appear here. The legacy v0.2 path (bg-accent
// amber) still works for callers that have not migrated; --color-accent
// points at primary blue under quietware via the tokens CSS.
//
// Each filled variant gains a Level-1 wireline (complement of its bg).
// HC contract zeroes --wire-width so the effect vanishes in HC.
//
// Focus state uses Level-2 width via Tailwind's ring utility. Separate
// box-shadow slot, composes cleanly with the inset wireline.
const variantClass: Record<Variant, string> = { const variantClass: Record<Variant, string> = {
primary: primary:
"bg-accent btn-filled-text font-medium hover:bg-accent-hover " + "bg-[var(--button-primary-bg)] text-[var(--button-primary-fg)] font-medium " +
"shadow-[var(--shadow-accent-pill),inset_0_0_0_var(--counterline-width,0)_var(--button-brand-counterline,transparent)]", "hover:bg-[var(--button-primary-border)] " +
"shadow-[inset_0_0_0_var(--wire-width,0)_color-mix(in_srgb,var(--button-primary-wire)_calc(var(--wire-opacity,0)*100%),transparent)]",
record:
"bg-[var(--button-record-bg)] text-[var(--button-record-fg)] font-medium " +
"hover:bg-[var(--button-record-border)] " +
"shadow-[inset_0_0_0_var(--wire-width,0)_color-mix(in_srgb,var(--button-record-wire)_calc(var(--wire-opacity,0)*100%),transparent)]",
secondary: secondary:
"bg-bg-elevated text-text border border-border-subtle hover:bg-hover", "bg-bg-elevated text-text border border-border-subtle hover:bg-hover",
tertiary: tertiary:
"bg-transparent text-text-secondary hover:bg-hover hover:text-text", "bg-transparent text-text-secondary hover:bg-hover hover:text-text",
destructive: destructive:
"bg-danger btn-filled-text font-medium hover:bg-danger/90 " + "bg-[var(--button-danger-bg)] text-[var(--button-danger-fg)] font-medium " +
"shadow-[inset_0_0_0_var(--counterline-width,0)_var(--button-danger-counterline,transparent)]", "hover:bg-[var(--button-danger-border)] " +
"shadow-[inset_0_0_0_var(--wire-width,0)_color-mix(in_srgb,var(--button-danger-wire)_calc(var(--wire-opacity,0)*100%),transparent)]",
}; };
const busy = $derived(loading || disabled); const busy = $derived(loading || disabled);
@@ -75,8 +91,8 @@
{type} {type}
class="rounded-lg font-medium select-none disabled:opacity-50 disabled:cursor-not-allowed class="rounded-lg font-medium select-none disabled:opacity-50 disabled:cursor-not-allowed
focus-visible:outline-none focus-visible:outline-none
focus-visible:ring-[var(--counterline-width-focus,2px)] focus-visible:ring-[var(--wire-width-focus,2px)]
focus-visible:ring-[color-mix(in_srgb,var(--focus-ring-color,var(--color-accent))_calc(var(--counterline-opacity-focus,0.85)*100%),transparent)] focus-visible:ring-[color-mix(in_srgb,var(--focus-ring-color,var(--color-accent))_calc(var(--wire-opacity-focus,1)*100%),transparent)]
focus-visible:ring-offset-2 focus-visible:ring-offset-bg focus-visible:ring-offset-2 focus-visible:ring-offset-bg
{sizeClass[size]} {sizeClass[size]}
{variantClass[variant]} {variantClass[variant]}

View File

@@ -59,7 +59,8 @@
<section class="space-y-3"> <section class="space-y-3">
<h2 class="text-[14px] text-text-secondary uppercase tracking-wider">LumotiaButton</h2> <h2 class="text-[14px] text-text-secondary uppercase tracking-wider">LumotiaButton</h2>
<div class="flex flex-wrap gap-3 items-center"> <div class="flex flex-wrap gap-3 items-center">
<LumotiaButton variant="primary">Record</LumotiaButton> <LumotiaButton variant="record">Record</LumotiaButton>
<LumotiaButton variant="primary">Save</LumotiaButton>
<LumotiaButton variant="secondary">Cancel</LumotiaButton> <LumotiaButton variant="secondary">Cancel</LumotiaButton>
<LumotiaButton variant="tertiary">Skip</LumotiaButton> <LumotiaButton variant="tertiary">Skip</LumotiaButton>
<LumotiaButton variant="destructive">Delete</LumotiaButton> <LumotiaButton variant="destructive">Delete</LumotiaButton>