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