diff --git a/.impeccable/design.json b/.impeccable/design.json
index f1c9cd8..fe6a91c 100644
--- a/.impeccable/design.json
+++ b/.impeccable/design.json
@@ -407,8 +407,8 @@
},
{
"name": "primary-lift",
- "value": "0 6px 16px rgba(154, 74, 34, 0.32)",
- "purpose": "Sanctioned for the primary button by the original handoff spec; specified but not yet implemented. The only permitted addition to the shadow vocabulary."
+ "value": "FilledButton elevation: 3, shadowColor: terracotta @ 32%",
+ "purpose": "The primary button, applied at the theme so no call site hand-rolls it. Uses Material elevation rather than a literal BoxShadow, so the One Shadow Rule's count is untouched."
}
],
"motion": [
@@ -466,9 +466,9 @@
"name": "Primary Button",
"kind": "button",
"refersTo": "button-primary",
- "description": "The commitment. Terracotta fill, 48px minimum height, full-width by default — override minimumSize when it shares a Row.",
+ "description": "The commitment. Terracotta fill in both brightnesses (set at the theme, not derived from the M3 scheme), 48px minimum height, a warm-tinted lift.",
"html": "",
- "css": ".ds-btn-primary { background: #9A4A22; color: #FFFBF9; font-family: 'HankenGrotesk', Inter, system-ui, sans-serif; font-size: 14px; font-weight: 500; letter-spacing: 0.1px; border: none; border-radius: 12px; padding: 12px 24px; min-height: 48px; cursor: pointer; transition: background 180ms ease, box-shadow 180ms ease; } .ds-btn-primary:hover { background: #8A4020; } .ds-btn-primary:focus-visible { outline: 2px solid #9A4A22; outline-offset: 2px; } .ds-btn-primary:active { background: #7C3A1A; }"
+ "css": ".ds-btn-primary { background: #9A4A22; color: #FFFBF9; font-family: 'HankenGrotesk', Inter, system-ui, sans-serif; font-size: 14px; font-weight: 500; letter-spacing: 0.1px; border: none; border-radius: 12px; padding: 12px 24px; min-height: 48px; cursor: pointer; box-shadow: 0 6px 16px rgba(154,74,34,0.32); transition: background 180ms ease, box-shadow 180ms ease; } .ds-btn-primary:hover { background: #8A4020; } .ds-btn-primary:focus-visible { outline: 2px solid #9A4A22; outline-offset: 2px; } .ds-btn-primary:active { background: #7C3A1A; }"
},
{
"name": "Secondary Button",
diff --git a/DESIGN.md b/DESIGN.md
index c6492c2..27cd133 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -446,16 +446,18 @@ one `BoxShadow` in the entire codebase, and it belongs to the expanded oracle
result card. That scarcity is doctrine, not an oversight: the shadow *is* the
punctuation on the moment the story turns.
-The one sanctioned expansion is the primary button lift, specified in the
-original handoff and not yet implemented. It may be added; nothing else may.
+The one sanctioned expansion is the primary button lift, now shipped as a
+themed `elevation: 3` with a terracotta `shadowColor` rather than a literal
+`BoxShadow` — so the button lifts without spending the system's one-shadow
+budget. Nothing else may cast.
### Shadow Vocabulary
- **Answer lift** (`BoxShadow(color: terracotta @ 16%, blurRadius: 22, offset: (0, 8))`):
the expanded result card only. Tinted with the accent rather than black, so it
reads as lamplight rather than a drop shadow.
-- **Primary lift** (`BoxShadow(color: rgba(154,74,34,.32), blurRadius: 16, offset: (0, 6))`):
- sanctioned for the primary button. Specified, not yet shipped.
+- **Primary lift** (`FilledButton` `elevation: 3`, `shadowColor: terracotta @ 32%`):
+ the primary button only, applied at the theme so no call site hand-rolls it.
### Named Rules
@@ -500,8 +502,13 @@ page, not something floating above it.
### Buttons
- **Shape:** softly rounded (12px primary, 14px secondary).
-- **Primary** (`FilledButton`): terracotta fill, near-white label, themed to a
- 48px minimum height so it reads as a full-width commitment by default.
+- **Primary** (`FilledButton`): terracotta fill in **both** brightnesses, set at
+ the theme rather than left to the seeded `ColorScheme` — M3 derives a *light*
+ `primary` for dark schemes, which made the app's most important control a pale
+ peach in dark mode, the one thing on screen not wearing the accent. The label
+ flips per brightness (near-white ink on the deep light clay, page-dark ink on
+ the lifted dark clay). Themed to a 48px minimum height so it reads as a
+ full-width commitment by default, and carries the primary lift.
**Gotcha:** the theme sets `minimumSize: Size.fromHeight(48)`, which forces
infinite width under loose constraints — inside a `Wrap`, or beside a flex
sibling in a `Row`, override with `minimumSize: Size(0, 44)` or wrap in
diff --git a/lib/shared/theme.dart b/lib/shared/theme.dart
index c8c4a7c..fe1100f 100644
--- a/lib/shared/theme.dart
+++ b/lib/shared/theme.dart
@@ -27,8 +27,27 @@ class AppTheme {
return base.copyWith(
textTheme: tt,
extensions: >[tokens],
+ // The primary lift — the second and last shadow the system sanctions
+ // (DESIGN.md -> Elevation & Depth). Warm-tinted per The Warm Shadow Rule:
+ // a neutral shadow on cream paper reads as plastic. Elevation, not a
+ // BoxShadow, so The One Shadow Rule's count is untouched.
filledButtonTheme: FilledButtonThemeData(
- style: FilledButton.styleFrom(minimumSize: const Size.fromHeight(48)),
+ style: FilledButton.styleFrom(
+ minimumSize: const Size.fromHeight(48),
+ // The primary action wears the accent in BOTH brightnesses. Left to
+ // the seeded ColorScheme this was a pale peach in dark mode (M3
+ // derives a light `primary` for dark schemes), so the app's most
+ // important control was the one thing not wearing Lamplight
+ // Terracotta — a Rubrication Rule break that light mode hid, because
+ // the amber seed's light `primary` happens to land near the token.
+ backgroundColor: tokens.terracotta,
+ // Label reads against the accent per brightness: near-white ink on
+ // the deep light clay, page-dark ink on the lifted dark clay.
+ foregroundColor:
+ brightness == Brightness.light ? tokens.raised : tokens.cream,
+ elevation: 3,
+ shadowColor: tokens.terracotta.withValues(alpha: 0.32),
+ ),
),
// Modal containers. Sheets and dialogs are this app's second-most-common
// container after the card (60+ call sites) and were rendering at raw M3