Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .impeccable/design.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down Expand Up @@ -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": "<button class=\"ds-btn-primary\">Continue the story</button>",
"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",
Expand Down
19 changes: 13 additions & 6 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
21 changes: 20 additions & 1 deletion lib/shared/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,27 @@ class AppTheme {
return base.copyWith(
textTheme: tt,
extensions: <ThemeExtension<dynamic>>[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
Expand Down
Loading