alpha to alpha
165 added, 168 removed. Audit A to A.
---
version: alpha
name: "Lullwater"
description: "Design system generated by OpenDesigner"
colors:
primary: "#e8a48a"
- on-primary: "#242428"
- surface: "#fcfdff"
- on-surface: "#242428"
- error: "#c0534a"
+ on-primary: "#222428"
+ surface: "#f1f4f9"
+ on-surface: "#222428"
+ error: "#b95850"
typography:
- body-md: { fontFamily: "Instrument Sans", fontSize: 17px, lineHeight: 24px }
+ body-md: { fontFamily: "Albert Sans", fontSize: 17px, lineHeight: 24px }
rounded:
control: 9999px
container: 20px
spacing:
unit: 4px
---
# Lullwater
**Lullwater** is a design system; what it is for is not recorded yet (Q-scope-01).
- **For:** people who use it often, as an everyday app
- **Screens:** not asked yet
- **Runs on:** ios
- **Feel:** calm, gentle (deferential, minimal, friendly; picked for you)
- - **Brand color:** Picked for you, not chosen by you: an orange (#E8A48A) on buttons, links and focus, with dark text on it.
+ - **Brand color:** Your brand color (#E8A48A) is on the buttons exactly, with dark text on it, because white text would be too faint.
- **Decided:** 15 picked for you; everything else uses sourced defaults
- **Standards:** 339 house standards followed (locked, house standards v2); see the Standards section
- **Next:** zoom into Colors, Layout, Shapes, or stop here: each level works
- **Licence risks:** none found
> Generated by the OpenDesigner engine 1.0.0 from `opendesigner/state.json` and `opendesigner/decisions.md`; `opendesigner/tokens/` is the source of truth. Change the system with `engine.py set ...` and `engine.py build`, not by editing this file (notes inside `<!-- od:keep -->` blocks survive). Each section opens with one plain sentence; open **More** for the designer and engineer detail.
## Overview
> Zoom: sketch (0 of 3), 5 decisions in this area. To zoom in, answer Q-dir-01, Q-dir-02.
<!-- od:zoom area=overview level=0 -->
**Lullwater feels deferential, minimal and friendly. Screens are roomy, in a flat style.**
Designers: visual style: flat, tonal, glass · Code: `preset in state.json`
<details><summary>More</summary>
**Intent.** A design system generated from eight dials and a few raw inputs.
- Audience: people who use it often, as an everyday app
- The memorable thing: not recorded yet (Q-brand-02)
- Surfaces: not recorded yet (Q-scope-06)
- Platforms: ios; inputs: pointer, touch
- Direction: preset flat2 (the default until Q-dir-01 is answered); feel deferential, minimal, friendly (words picked for you: calm, gentle)
**Principles (ranked).**
No principles recorded yet (Q-brand-07). Until they are, the dials act as the tie-breakers below.
**Dial positions.** Three posture dials set the overall stance. Five character dials fine-tune it.
- - **Expression 75/100** (productive to expressive: expressive): type ratio 1.333, hero moments: 1-2-per-product, expressive motion: throughout, brand color role: brand-chrome-allowed, containment: visible-containers.
- - **Brand presence 65/100** (native to brand-led: leaning brand-led): typeface suggestion: open-custom-face, brand color placement: accent+signature-surface, native component share about 56%, voice guidance: short.
- - **Density 15/100** (spacious to compact: spacious): body 16px on the web, default control 48px, density mode spacious, icons 24px; hit areas never shrink.
+ - **Expression 45/100** (productive to expressive: balanced): type ratio 1.25, hero moments: significant-moments, expressive motion: significant-moments, brand color role: accent+signature-surface, containment: mixed.
+ - **Brand presence 40/100** (native to brand-led: leaning native): typeface suggestion: open-neutral-face, brand color placement: accent+signature-surface, native component share about 84%, voice guidance: short.
+ - **Density 30/100** (spacious to compact: spacious): body 16px on the web, default control 48px, density mode spacious, icons 24px; hit areas never shrink.
- **Energy 5/100** (calm to energetic: calm): standard easing [0.77, 0, 0.175, 1], duration multiplier 0.82, spring damping 1.0, heading weight 600, navigation: recessive.
- **Roundness 95/100** (sharp to soft: soft): control radius full, icon corners rounded, caps round.
- - **Depth 90/100** (flat to deep: deep): depth model: materials.
- - **Colorfulness 60/100** (monochrome to vivid: leaning vivid): scheme tonalSpot, accent chroma 39.6 (HCT), 3 accent(s), surfaces: tinted-containers.
+ - **Depth 55/100** (flat to deep: balanced): depth model: tonal.
+ - **Colorfulness 30/100** (monochrome to vivid: monochrome): scheme neutral, accent chroma 28.6 (HCT), 1 accent(s), surfaces: neutral+one-accent.
- **Warmth 75/100** (cool/formal to warm/friendly: warm/friendly): neutral tint chroma 0.013 at hue 58, borders: soft, sentence-case, contractions encouraged.
**Zoom by area** (sketch, broad, defined, detailed). You can stop at any level; each one works.
| Area | Zoom | Next questions |
|---|---|---|
| Colors | sketch | Q-color-02 |
| Typography | broad | Q-type-03, Q-type-04, Q-type-17 |
| Layout | sketch | Q-space-01, Q-space-02, Q-space-03 |
| Elevation & Depth | defined | Q-depth-02, Q-depth-03, Q-depth-05 |
| Shapes | sketch | Q-shape-01 |
| Components | sketch | Q-comp-01, Q-comp-02, Q-state-01 |
| Motion | broad | Q-motion-07, Q-motion-08 |
| Modes and Themes | sketch | Q-theme-03 |
| Iconography and Imagery | sketch | Q-icon-01, Q-icon-02, Q-icon-06 |
| Content and Voice | sketch | Q-voice-01, Q-voice-02, Q-voice-03 |
| Accessibility | sketch | Q-aud-02, Q-aud-03, Q-aud-04 |
| Platforms and Devices | sketch | Q-plat-05, Q-plat-10, Q-plat-08 |
The tokens in `opendesigner/tokens/` (DTCG 2025.10 with `opendesigner.resolver.json`) are the source of truth. This file is a generated view.
- Decisions: D-0002 (answers.Q-aud-01), D-0007 (macros), D-0023 (dials.expression), D-0024 (dials.brandPresence), D-0025 (dials.density).
+ Decisions: D-0002 (answers.Q-aud-01), D-0007 (macros), D-0063 (dials.expression), D-0064 (dials.brandPresence), D-0068 (dials.density).
</details>
+ **Taste card.** How it looks and feels, in five lines for you, your team and any AI that builds with it. Nothing on it is invented: each line says where it comes from.
+ Designers: style brief, mood in words · Code: `taste.brief, taste.never`
+
+ | Line | What it says | From |
+ |---|---|---|
+ | In your words | Not given yet. Describe the look as a picture, not adjectives (for example, "a quiet library at night"). | not asked yet |
+ | Color | One accent color: your brand color `#E8A48A` (exactly) marks buttons, links and focus. Everything else stays neutral. Keep the accent to small areas, about 15% of a screen at most (a rule of thumb, DC-L15-03). | brand color; colorfulness 30/100 |
+ | Shape | Buttons and fields have fully round corners; cards have 20px corners. Surfaces step in lightness; only menus and dialogs cast shadows. | roundness 95/100, depth 55/100 |
+ | Motion | Motion is off: things change in place without moving. | energy 5/100 |
+ | Never | Nothing yet. Say what it should never look like, or rate real screens in the taste poll. | not asked yet |
+
## Colors
> Zoom: sketch (0 of 3), 9 decisions in this area. To zoom in, answer Q-color-02.
<!-- od:zoom area=color level=0 -->
- **Three accent colors: an orange picked for you (`#E8A48A`) marks buttons, links and focus; `#c67ea8`, `#7a67c4` add highlights.** Everything else stays neutral.
+ **One accent color: your brand color `#E8A48A` (exactly) marks buttons, links and focus.** Everything else stays neutral.
Designers: primary or brand accent · Code: `color.bg.accent`
<details><summary>More</summary>
**Your answers here** (each one either sets tokens or is a rule for this section):
- Do you have fixed brand colors, or should I build the palette from one color? **Keep your exact brand colors and tune the shades by hand (Carbon, Primer, Atlassian, GOV.UK)** (delegated; tokens; Q-color-01)
- How many accent colors does the product need? **One accent with nearby hues for backgrounds and drawings** (delegated; tokens; Q-color-04)
- How dark should dark mode be: pure black, near-black, or charcoal? **Near-black #0D1117 to #161616** (delegated; tokens; Q-color-21)
- **Intent.** Brand color `#E8A48A` drives the accent ramp's hue; the Colorfulness dial (60) sets its chroma (scheme tonalSpot, peak HCT chroma 39.6). The brand's role is **brand-chrome-allowed** and it sits closest to accent step 9. Picked for you, not chosen by you: an orange (#E8A48A) on buttons, links and focus, with dark text on it.
+ **Intent.** Brand color `#E8A48A` drives the accent ramp's hue; the Colorfulness dial (30) sets its chroma (scheme neutral, peak HCT chroma 28.62). The brand's role is **accent+signature-surface** and it sits closest to accent step 9. Your brand color #E8A48A is on the buttons exactly (dark text on it, because white text would be too faint).
**How ramps are built.** Each hue gets twelve steps in OKLCH, placed by contrast. Step 8 reaches at least 3:1 (edges and focus). Steps 10 and 11 reach the text minimum on backgrounds 1-4. Step 12 reaches at least 7:1. Steps 2-6 are spaced evenly in lightness between step 1 and step 7. Light and dark ramps are solved separately. Dark solids are lighter and carry dark text (dark mode is a separate mapping, never an inversion). Text minimum: 4.5:1 (WCAG 2.2 AA).
| Ramp | Light steps 1-12 | Solid carries |
|---|---|---|
- | `accent` | `#fffcfb` `#faf3f0` `#f8e8e3` `#f6ded5` `#f4d4c7` `#f2c9ba` `#f0bfac` `#b37861` `#e8a48a` `#e09274` `#93533b` `#4a2c21` | dark |
- | `accent2` | `#fffcfd` `#f9f3f6` `#f6e8ef` `#f3dde9` `#f0d3e2` `#edc8dc` `#e9bdd5` `#ab7694` `#c67ea8` `#d38db5` `#8b5273` `#472b3b` | dark |
- | `accent3` | `#fdfcff` `#f4f3fc` `#eceafb` `#e3e0fc` `#dbd6fc` `#d2cdfc` `#cac3fc` `#887dc3` `#7a67c4` `#6e5cb3` `#5b4c97` `#352f53` | light |
- | `danger` | `#fffcfc` `#fcf2f1` `#fde7e4` `#fedbd7` `#fed0ca` `#fec4bd` `#feb9b0` `#c26f66` `#c0534a` `#b04941` `#933a34` `#522723` | light |
- | `info` | `#fbfdff` `#edf5fc` `#dceefc` `#cae7fc` `#b8e0fd` `#a5d8fd` `#92d1fd` `#348dc4` `#007cb7` `#0070a6` `#005c8a` `#043753` | light |
- | `neutral` | `#fcfdff` `#f3f3fe` `#eaeaf9` `#e1e1f4` `#d8d9ef` `#cfd0e9` `#c7c7e4` `#8383a2` `#737293` `#616181` `#555470` `#242428` | light |
- | `success` | `#fafefb` `#eef6ef` `#e0efe2` `#d0e9d4` `#c0e3c6` `#b1dcb8` `#a1d6ab` `#4e945f` `#158740` `#0b7a38` `#01652c` `#163b20` | light |
- | `warning` | `#fffcf9` `#f8f4ec` `#f4eadb` `#f0e1c9` `#ecd8b7` `#e8cfa5` `#e4c592` `#a6803a` `#eab34f` `#d8a446` `#845d00` `#44300a` | dark |
+ | `accent` | `#fffcfb` `#f9f3f1` `#f5e9e5` `#f1dfd8` `#eed5cc` `#eacbc0` `#e7c2b4` `#a97c6b` `#e8a48a` `#d29881` `#885846` `#452e26` | dark |
+ | `danger` | `#fffcfc` `#fbf2f1` `#fbe7e5` `#fbdcd8` `#fad1cc` `#f9c6bf` `#f8bbb3` `#bc726a` `#b95850` `#a84e46` `#8d4039` `#4f2925` | light |
+ | `info` | `#fbfdff` `#eef5fa` `#dfedf8` `#cfe6f7` `#bfdff6` `#aed7f5` `#9ed0f3` `#4a8cb8` `#057cb6` `#0070a5` `#005c8a` `#13374d` | light |
+ | `neutral` | `#fbfdff` `#f1f4f9` `#e7ecf2` `#dee3ec` `#d4dbe5` `#cbd2de` `#c2cad7` `#7e8794` `#6c7685` `#5b6573` `#505864` `#222428` | light |
+ | `success` | `#fafefb` `#eff5f0` `#e1efe3` `#d3e8d6` `#c4e2c9` `#b6dbbc` `#a7d4af` `#599365` `#318549` `#297840` `#1d6333` `#1c3a23` | light |
+ | `warning` | `#fffcf9` `#f8f4ed` `#f3eadd` `#efe1cb` `#ebd8ba` `#e6cfa8` `#e2c697` `#a48042` `#e7b55b` `#d5a652` `#845d03` `#43310f` | dark |
**Step-distance guarantee** (neutral, light; WCAG contrast of a foreground step on background steps 1-5):
| Foreground | on 1 | on 2 | on 3 | on 4 | on 5 |
|---|---|---|---|---|---|
- | neutral 8 | 3.60 | 3.32 | 3.08 | 2.84 | 2.63 |
- | neutral 9 | 4.53 | 4.18 | 3.87 | 3.57 | 3.31 |
- | neutral 10 | 5.84 | 5.39 | 4.99 | 4.60 | 4.27 |
- | neutral 11 | 7.14 | 6.60 | 6.11 | 5.64 | 5.22 |
- | neutral 12 | 15.19 | 14.04 | 12.99 | 11.99 | 11.11 |
+ | neutral 8 | 3.56 | 3.30 | 3.06 | 2.82 | 2.61 |
+ | neutral 9 | 4.51 | 4.17 | 3.87 | 3.57 | 3.30 |
+ | neutral 10 | 5.80 | 5.36 | 4.97 | 4.59 | 4.24 |
+ | neutral 11 | 7.05 | 6.52 | 6.05 | 5.58 | 5.16 |
+ | neutral 12 | 15.24 | 14.10 | 13.09 | 12.07 | 11.15 |
**States.** Hover is at least 1.15:1 from its fill and pressed at least 1.3:1, as shade steps (DC-L01-17). Where a color is unknown at design time, use the state opacities in `opacity.state.*`.
| Fill | Base | Hover | Pressed |
|---|---|---|---|
- | `color.bg.action.primary` | `#e8a48a` | `#e09274` (1.18:1) | `#d5886a` (1.34:1) |
- | `color.bg.accent.bold` | `#e8a48a` | `#e09274` (1.18:1) | `#d5886a` (1.34:1) |
+ | `color.bg.action.primary` | `#e8a48a` | `#d29881` (1.18:1) | `#c78e77` (1.34:1) |
+ | `color.bg.accent.bold` | `#e8a48a` | `#d29881` (1.18:1) | `#c78e77` (1.34:1) |
**Semantic roles** (use these, never the ramps):
| Token | Light | Dark | Use |
|---|---|---|---|
| `color.bg.accent.bold` | `#e8a48a` | `#e8a48a` | Solid accent fill. |
- | `color.bg.accent.bold-hover` | `#e09274` | `#f8b499` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
- | `color.bg.accent.bold-pressed` | `#d5886a` | `#ffc2ab` | Pressed: at least 1.3:1 from the solid. |
- | `color.bg.accent.subtle` | `#f8e8e3` | `#312520` | Selected rows, accent badges. |
- | `color.bg.accent.subtle-hover` | `#f6ded5` | `#3d2c25` | Hover on accent subtle fills. |
- | `color.bg.accent.subtle-pressed` | `#f4d4c7` | `#4a3229` | Pressed accent subtle fills. |
- | `color.bg.accent2.bold` | `#c67ea8` | `#be83a5` | accent2 solid fill. |
- | `color.bg.accent2.bold-hover` | `#d38db5` | `#cc91b3` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
- | `color.bg.accent2.bold-pressed` | `#de97c0` | `#d89bbd` | Pressed: at least 1.3:1 from the solid. |
- | `color.bg.accent2.subtle` | `#f6e8ef` | `#30252b` | accent2 subtle fill. |
- | `color.bg.accent3.bold` | `#7a67c4` | `#867ac6` | accent3 solid fill. |
- | `color.bg.accent3.bold-hover` | `#6e5cb3` | `#9387d4` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
- | `color.bg.accent3.bold-pressed` | `#6654aa` | `#9d91df` | Pressed: at least 1.3:1 from the solid. |
- | `color.bg.accent3.subtle` | `#eceafb` | `#282634` | accent3 subtle fill. |
+ | `color.bg.accent.bold-hover` | `#d29881` | `#f5b59d` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
+ | `color.bg.accent.bold-pressed` | `#c78e77` | `#ffc2ab` | Pressed: at least 1.3:1 from the solid. |
+ | `color.bg.accent.subtle` | `#f5e9e5` | `#302521` | Selected rows, accent badges. |
+ | `color.bg.accent.subtle-hover` | `#f1dfd8` | `#3c2c25` | Hover on accent subtle fills. |
+ | `color.bg.accent.subtle-pressed` | `#eed5cc` | `#48322a` | Pressed accent subtle fills. |
| `color.bg.action.primary` | `#e8a48a` | `#e8a48a` | Primary action. One per view (DC-L15-03, L15 P08). |
- | `color.bg.action.primary-hover` | `#e09274` | `#f8b499` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
- | `color.bg.action.primary-pressed` | `#d5886a` | `#ffc2ab` | Pressed: at least 1.3:1 from the solid. |
+ | `color.bg.action.primary-hover` | `#d29881` | `#f5b59d` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
+ | `color.bg.action.primary-pressed` | `#c78e77` | `#ffc2ab` | Pressed: at least 1.3:1 from the solid. |
| `color.bg.brand` | `#e8a48a` | `#e8a48a` | Brand fill for signature surfaces; carries text.on-brand (brandAnchor rule, DC-L01-09). |
- | `color.bg.danger.bold` | `#c0534a` | `#c56a61` | Danger solid fill. |
- | `color.bg.danger.bold-hover` | `#b04941` | `#d3776e` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
- | `color.bg.danger.bold-pressed` | `#a64039` | `#de8177` | Pressed: at least 1.3:1 from the solid. |
- | `color.bg.danger.subtle` | `#fde7e4` | `#352421` | Danger banner and badge background. |
- | `color.bg.disabled` | `#eaeaf9` | `#2c2c3a` | Disabled control fill. |
- | `color.bg.field` | `#ffffff` | `#24242f` | Text field fill: outlined fields sit on the raised surface (Q-form-01). |
- | `color.bg.info.bold` | `#007cb7` | `#248cc7` | Info solid fill. |
- | `color.bg.info.bold-hover` | `#0070a6` | `#3699d6` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
- | `color.bg.info.bold-pressed` | `#006799` | `#42a3e0` | Pressed: at least 1.3:1 from the solid. |
- | `color.bg.info.subtle` | `#dceefc` | `#1a2934` | Info banner and badge background. |
- | `color.bg.inverse` | `#242428` | `#e8e9ef` | Tooltips and toasts: the inverted surface. |
- | `color.bg.neutral.subtle` | `#eaeaf9` | `#353546` | Secondary buttons, chips, hovered rows. |
- | `color.bg.neutral.subtle-hover` | `#e1e1f4` | `#3e3d52` | Hover: +1 step (DC-L01-17). |
- | `color.bg.neutral.subtle-pressed` | `#d8d9ef` | `#46465e` | Pressed or selected: +2 steps (DC-L01-17). |
- | `color.bg.success.bold` | `#158740` | `#46935a` | Success solid fill. |
- | `color.bg.success.bold-hover` | `#0b7a38` | `#53a066` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
- | `color.bg.success.bold-pressed` | `#007131` | `#5daa6f` | Pressed: at least 1.3:1 from the solid. |
- | `color.bg.success.subtle` | `#e0efe2` | `#1d2b20` | Success banner and badge background. |
- | `color.bg.warning.bold` | `#eab34f` | `#e2b66a` | Warning solid fill. |
- | `color.bg.warning.bold-hover` | `#d8a446` | `#f3c67a` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
- | `color.bg.warning.bold-pressed` | `#cd9a3a` | `#ffd388` | Pressed: at least 1.3:1 from the solid. |
- | `color.bg.warning.subtle` | `#f4eadb` | `#2e271a` | Warning banner and badge background. |
- | `color.border.accent` | `#b37861` | `#ac755f` | Selected card or accent outline. |
- | `color.border.danger` | `#c26f66` | `#bb6c64` | Danger boundary, for example an invalid field (3:1). |
- | `color.border.default` | `#cfd0e9` | `#3e3d52` | Outlines (soft borders). |
- | `color.border.focus` | `#93533b` | `#ffbea6` | Focus ring (3:1 against every surface, DC-L04-09). |
- | `color.border.info` | `#348dc4` | `#3789bc` | Info boundary, for example an invalid field (3:1). |
- | `color.border.input` | `#8383a2` | `#7e7e9c` | Text field and select borders (3:1, DC-L01-16). |
- | `color.border.strong` | `#8383a2` | `#7e7e9c` | Boundaries that are the only cue: inputs, checkboxes, switches (3:1, WCAG 1.4.11). |
- | `color.border.subtle` | `#d8d9ef` | `#353546` | Dividers (soft borders: Warmth 67+, Linear 2026 S-L06-067). |
- | `color.border.success` | `#4e945f` | `#4e8f5d` | Success boundary, for example an invalid field (3:1). |
- | `color.border.warning` | `#a6803a` | `#a07c3c` | Warning boundary, for example an invalid field (3:1). |
- | `color.icon.accent` | `#93533b` | `#ffbea6` | Selected and interactive icons. |
- | `color.icon.default` | `#555470` | `#a8a9c8` | Icons beside text; same color as secondary text (DC-L05-08). |
- | `color.icon.on-accent` | `#242428` | `#151519` | Icons on solid accent fills. |
- | `color.icon.subtle` | `#616181` | `#9292b4` | Decorative icons. |
- | `color.overlay.scrim` | `#242428 @0.45` | `#000000` | Modal backdrop (DC-L04-18; Q-depth-06). |
- | `color.surface.base` | `#fcfdff` | `#1c1c24` | The page background. |
- | `color.surface.glass` | `#ffffff @0.72` | `#24242f` | Glass for controls and navigation only, never content (DC-L04-15). |
+ | `color.bg.danger.bold` | `#b95850` | `#bd6e65` | Danger solid fill. |
+ | `color.bg.danger.bold-hover` | `#a84e46` | `#cc7b72` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
+ | `color.bg.danger.bold-pressed` | `#9f463e` | `#d6847b` | Pressed: at least 1.3:1 from the solid. |
+ | `color.bg.danger.subtle` | `#fbe7e5` | `#332422` | Danger banner and badge background. |
+ | `color.bg.disabled` | `#e7ecf2` | `#2a2e34` | Disabled control fill. |
+ | `color.bg.field` | `#fbfdff` | `#22252a` | Text field fill: outlined fields sit on the raised surface (Q-form-01). |
+ | `color.bg.info.bold` | `#057cb6` | `#418aba` | Info solid fill. |
+ | `color.bg.info.bold-hover` | `#0070a5` | `#4e97c8` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
+ | `color.bg.info.bold-pressed` | `#006799` | `#58a1d2` | Pressed: at least 1.3:1 from the solid. |
+ | `color.bg.info.subtle` | `#dfedf8` | `#1c2932` | Info banner and badge background. |
+ | `color.bg.inverse` | `#222428` | `#e6e9ee` | Tooltips and toasts: the inverted surface. |
+ | `color.bg.neutral.subtle` | `#e7ecf2` | `#31373e` | Secondary buttons, chips, hovered rows. |
+ | `color.bg.neutral.subtle-hover` | `#dee3ec` | `#393f49` | Hover: +1 step (DC-L01-17). |
+ | `color.bg.neutral.subtle-pressed` | `#d4dbe5` | `#424953` | Pressed or selected: +2 steps (DC-L01-17). |
+ | `color.bg.success.bold` | `#318549` | `#519160` | Success solid fill. |
+ | `color.bg.success.bold-hover` | `#297840` | `#5e9e6c` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
+ | `color.bg.success.bold-pressed` | `#1e7039` | `#68a876` | Pressed: at least 1.3:1 from the solid. |
+ | `color.bg.success.subtle` | `#e1efe3` | `#1f2a21` | Success banner and badge background. |
+ | `color.bg.warning.bold` | `#e7b55b` | `#dfb772` | Warning solid fill. |
+ | `color.bg.warning.bold-hover` | `#d5a652` | `#f0c781` | Hover: one step, at least 1.15:1 from the solid (DC-L01-17). |
+ | `color.bg.warning.bold-pressed` | `#ca9c47` | `#fdd48e` | Pressed: at least 1.3:1 from the solid. |
+ | `color.bg.warning.subtle` | `#f3eadd` | `#2d261b` | Warning banner and badge background. |
+ | `color.border.accent` | `#a97c6b` | `#a87562` | Selected card or accent outline. |
+ | `color.border.danger` | `#bc726a` | `#b46f67` | Danger boundary, for example an invalid field (3:1). |
+ | `color.border.default` | `#cbd2de` | `#393f49` | Outlines (soft borders). |
+ | `color.border.focus` | `#885846` | `#fbbfa9` | Focus ring (3:1 against every surface, DC-L04-09). |
+ | `color.border.info` | `#4a8cb8` | `#4a87b1` | Info boundary, for example an invalid field (3:1). |
+ | `color.border.input` | `#7e8794` | `#78818f` | Text field and select borders (3:1, DC-L01-16). |
+ | `color.border.separator` | `#e7ecf2` | `#2a2e34` | Hairlines between table rows and list items, lighter than border.subtle; decorative, so never the only cue (DC-L23-05). |
+ | `color.border.strong` | `#7e8794` | `#78818f` | Boundaries that are the only cue: inputs, checkboxes, switches (3:1, WCAG 1.4.11). |
+ | `color.border.subtle` | `#d4dbe5` | `#31373e` | Dividers (soft borders: Warmth 67+, Linear 2026 S-L06-067). |
+ | `color.border.success` | `#599365` | `#578d63` | Success boundary, for example an invalid field (3:1). |
+ | `color.border.warning` | `#a48042` | `#9d7c42` | Warning boundary, for example an invalid field (3:1). |
+ | `color.icon.accent` | `#885846` | `#fbbfa9` | Selected and interactive icons. |
+ | `color.icon.default` | `#505864` | `#a2abb9` | Icons beside text; same color as secondary text (DC-L05-08). |
+ | `color.icon.on-accent` | `#222428` | `#141517` | Icons on solid accent fills. |
+ | `color.icon.subtle` | `#5b6573` | `#8c96a6` | Decorative icons. |
+ | `color.overlay.scrim` | `#222428 @0.45` | `#000000` | Modal backdrop (DC-L04-18; Q-depth-06). |
+ | `color.surface.base` | `#f1f4f9` | `#1b1d21` | The page background. |
+ | `color.surface.glass` | `#ffffff @0.72` | `#22252a` | Glass for controls and navigation only, never content (DC-L04-15). |
| `color.surface.glass-dimming` | `#000000 @0.35` | `#000000` | 35% dimming under clear glass over bright content (S-L15-073). |
- | `color.surface.glass-fallback` | `#ffffff` | `#2c2c3a` | Solid twin under Reduce Transparency and Increase Contrast (DC-L04-16). |
- | `color.surface.nav` | `#f3f3fe` | `#151519` | Recessive navigation, dimmer than content (Linear 2026, S-L06-067). |
- | `color.surface.overlay` | `#ffffff` | `#2c2c3a` | Menus, popovers, dialogs; always with elevation.floating or elevation.overlay. |
- | `color.surface.raised` | `#ffffff` | `#24242f` | Cards and panels on the page. |
- | `color.surface.sunken` | `#f3f3fe` | `#151519` | Wells, code blocks, page gutters. |
- | `color.surface.tinted` | `#faf3f0` | `#251f1c` | Tinted container (Colorfulness 50+, L09 X rubric; Q-color-05). |
- | `color.text.accent` | `#93533b` | `#ffbea6` | Accent text and selected labels. |
- | `color.text.accent2` | `#8b5273` | `#d49cbc` | accent2 text. |
- | `color.text.accent3` | `#5b4c97` | `#aea3ed` | accent3 text. |
- | `color.text.danger` | `#933a34` | `#ed958b` | Danger text and icons; pair with an icon or label (DC-L01-15). |
- | `color.text.disabled` | `#8383a2` | `#7e7e9c` | Disabled labels; exempt from contrast minimums (WCAG 1.4.3 exception). |
- | `color.text.info` | `#005c8a` | `#5eb5ee` | Info text and icons; pair with an icon or label (DC-L01-15). |
- | `color.text.inverse` | `#ffffff` | `#151519` | Text on color.bg.inverse (tooltips, toasts). |
- | `color.text.link` | `#93533b` | `#ffbea6` | Links. Underline them in running text so color is not the only cue. |
- | `color.text.on-accent` | `#242428` | `#151519` | Text and icons on color.bg.accent.bold* (auto-picked light or dark, S-L06-094). |
- | `color.text.on-accent2` | `#242428` | `#151519` | Text on accent2 solid. |
- | `color.text.on-accent3` | `#ffffff` | `#151519` | Text on accent3 solid. |
- | `color.text.on-action` | `#242428` | `#151519` | Text on the primary action fill. |
- | `color.text.on-brand` | `#242428` | `#151519` | Text on color.bg.brand. |
- | `color.text.on-danger` | `#ffffff` | `#151519` | Text on bg.danger.bold. |
- | `color.text.on-info` | `#ffffff` | `#151519` | Text on bg.info.bold. |
- | `color.text.on-success` | `#ffffff` | `#151519` | Text on bg.success.bold. |
- | `color.text.on-warning` | `#242428` | `#151519` | Text on bg.warning.bold. |
- | `color.text.primary` | `#242428` | `#e8e9ef` | Body copy, headings, labels (guaranteed >= 7:1 on steps 1-5). |
- | `color.text.secondary` | `#555470` | `#a8a9c8` | Supporting text (guaranteed >= the text minimum on surfaces and neutral fills 1-4). |
- | `color.text.success` | `#01652c` | `#74bc84` | Success text and icons; pair with an icon or label (DC-L01-15). |
- | `color.text.tertiary` | `#616181` | `#9292b4` | Captions and placeholders (still meets the text minimum on surfaces 1-4). |
- | `color.text.warning` | `#845d00` | `#fad08a` | Warning text and icons; pair with an icon or label (DC-L01-15). |
+ | `color.surface.glass-fallback` | `#ffffff` | `#2a2e34` | Solid twin under Reduce Transparency and Increase Contrast (DC-L04-16). |
+ | `color.surface.nav` | `#f1f4f9` | `#141517` | Recessive navigation, dimmer than content (Linear 2026, S-L06-067). |
+ | `color.surface.overlay` | `#ffffff` | `#2a2e34` | Menus, popovers, dialogs; always with elevation.floating or elevation.overlay. |
+ | `color.surface.raised` | `#fbfdff` | `#22252a` | Cards and panels on the page. |
+ | `color.surface.sunken` | `#e7ecf2` | `#141517` | Wells, code blocks, page gutters. |
+ | `color.text.accent` | `#885846` | `#fbbfa9` | Accent text and selected labels. |
+ | `color.text.danger` | `#8d4039` | `#e6988f` | Danger text and icons; pair with an icon or label (DC-L01-15). |
+ | `color.text.disabled` | `#7e8794` | `#78818f` | Disabled labels; exempt from contrast minimums (WCAG 1.4.3 exception). |
+ | `color.text.info` | `#005c8a` | `#70b3e1` | Info text and icons; pair with an icon or label (DC-L01-15). |
+ | `color.text.inverse` | `#ffffff` | `#141517` | Text on color.bg.inverse (tooltips, toasts). |
+ | `color.text.link` | `#885846` | `#fbbfa9` | Links. Underline them in running text so color is not the only cue. |
+ | `color.text.on-accent` | `#222428` | `#141517` | Text and icons on color.bg.accent.bold* (auto-picked light or dark, S-L06-094). |
+ | `color.text.on-action` | `#222428` | `#141517` | Text on the primary action fill. |
+ | `color.text.on-brand` | `#222428` | `#141517` | Text on color.bg.brand. |
+ | `color.text.on-danger` | `#ffffff` | `#141517` | Text on bg.danger.bold. |
+ | `color.text.on-info` | `#ffffff` | `#141517` | Text on bg.info.bold. |
+ | `color.text.on-success` | `#ffffff` | `#141517` | Text on bg.success.bold. |
+ | `color.text.on-warning` | `#222428` | `#141517` | Text on bg.warning.bold. |
+ | `color.text.primary` | `#222428` | `#e6e9ee` | Body copy, headings, labels (guaranteed >= 7:1 on steps 1-5). |
+ | `color.text.secondary` | `#505864` | `#a2abb9` | Supporting text (guaranteed >= the text minimum on surfaces and neutral fills 1-4). |
+ | `color.text.success` | `#1d6333` | `#7eba8a` | Success text and icons; pair with an icon or label (DC-L01-15). |
+ | `color.text.tertiary` | `#5b6573` | `#8c96a6` | Captions and placeholders (still meets the text minimum on surfaces 1-4). |
+ | `color.text.warning` | `#845d03` | `#f8d191` | Warning text and icons; pair with an icon or label (DC-L01-15). |
**Use / avoid.**
- Use one solid accent fill per view for the primary action; spend chroma on small, meaningful elements.
- Avoid gray text on colored fills; use the matching `text.on-*` role.
- Avoid color as the only signal: pair status colors with an icon or label.
- Charts: no chart palette yet (Q-color-19). Take chart colors from the status and accent hues, and check them separately.
**Tailwind classes:** `bg-action-primary`, `hover:bg-action-primary-hover`, `active:bg-action-primary-pressed`, `text-on-action`, `bg-surface-base`, `bg-surface-raised`, `text-fg-primary`, `text-fg-secondary`, `text-fg-link`, `border-line-subtle`, `border-line-input`, `outline-line-focus`, `bg-danger-bold`.
- Decisions: D-0009 (answers.Q-theme-01), D-0011 (answers.Q-color-01), D-0012 (raw.brandColor), D-0013 (raw.neutralBase), D-0014 (answers.Q-color-21), D-0029 (dials.colorfulness), D-0030 (dials.warmth), D-0033 (answers.Q-color-04), D-0034 (raw.secondaryColors).
+ Decisions: D-0009 (answers.Q-theme-01), D-0011 (answers.Q-color-01), D-0014 (answers.Q-color-21), D-0030 (dials.warmth), D-0033 (answers.Q-color-04), D-0059 (raw.neutralBase), D-0061 (raw.secondaryColors), D-0062 (raw.brandColor), D-0065 (dials.colorfulness).
</details>
## Typography
> Zoom: broad (1 of 3), 8 decisions in this area. To zoom in, answer Q-type-03, Q-type-04, Q-type-17.
<!-- od:zoom area=typography level=1 -->
- **Body text is 17px, and each larger size is about 1.333 times the one below.**
+ **Body text is 17px, and each larger size is about 1.25 times the one below.**
Designers: type scale: base size and ratio · Code: `font.size.14`
<details><summary>More</summary>
**Your answers here** (each one either sets tokens or is a rule for this section):
- Should it use the device's built-in font, a free plain font like Inter, or your brand font? **Brand font for headlines, system font for body (Apple's advice)** (delegated; tokens; Q-type-01)
- Do you have the brand font files? **Yes** (delegated; asset status; Q-type-02)
- What size should body text be? **17pt (iOS Body)** (delegated; tokens; Q-type-08)
- How much bigger should each text size be than the one below it? **Hand-tuned list (11-15 named styles in 12 systems; only Carbon and Ant use a formula)** (delegated; a rule here; Q-type-09)
- How many text styles, and how should they be named? **Named semantic styles (Apple's 11** (delegated; a rule here; Q-type-10)
- **Intent.** Body text is 17px; sizes follow `round(17 x 1.333^n)` for n from -2 to 6, reaching about 6.0x body (product surfaces only). Sizes: 13, 17, 23, 30, 40, 54, 72, 95px.
+ **Intent.** Body text is 17px; sizes follow `round(17 x 1.25^n)` for n from -2 to 8, reaching about 6.0x body (product surfaces only). Sizes: 14, 17, 21, 27, 42, 52, 81, 101px.
- - Text face: Instrument Sans, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif. Licence status (hook H-type): **have**.
- - Licence by platform: text: Instrument Sans (open licence or system font: web, apps and self-hosting); display: Instrument Serif: licensed for web, apps, self-hosting.
+ - Text face: Albert Sans, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif. Licence status (hook H-type): **have**.
+ - Licence by platform: text: Albert Sans: licensed for web, apps, self-hosting; display: Albert Sans: licensed for web, apps, self-hosting.
- On iOS the Swift file uses Dynamic Type text styles (body 17 pt), so text follows the person's text size setting; Compose uses sp, which follows the Android font scale.
- - Weights: body 400, label 500, heading 600, display 700 (two or three distinct weights per view).
- - Emphasized variants (Expression 75, on from 67): 11 styles under `text.emphasized.*`, the same size one weight heavier, for the selected item, key actions and headlines (DC-L02-11).
+ - Weights: body 400, label 500, heading 600 (two or three distinct weights per view).
+ - Emphasized variants: off (they turn on at Expression 67 and up, DC-L02-11).
- Line heights snap to a 4px grid: 1.5 up to 17px, 1.4 to 26px, 1.25 to 44px, 1.12 above.
- Letter spacing: +0.02em at 11-12px, -0.01em from 32px, -0.02em from 48px.
- Text scaling: CSS sizes are rem, line heights unitless, so text scales to 200% (WCAG 1.4.4); containers must grow with it.
- Scripts: Latin only; add scripts in raw.scripts to get per-script line heights (DC-L02-25).
| Style | Size/line (px) | Weight | Tracking | Use |
|---|---|---|---|---|
- | `text.body.lg` | 20/32 | 400 | 0em | Long-form reading, onboarding copy. |
+ | `text.body.lg` | 20/28 | 400 | 0em | Long-form reading, onboarding copy. |
| `text.body.md` | 17/24 | 400 | 0em | Default UI text: forms, tables, descriptions. |
| `text.body.sm` | 15/20 | 400 | 0em | Helper text, timestamps, captions. |
| `text.code.md` | 17/24 | 400 | 0em | Code blocks, token names. |
- | `text.code.sm` | 13/20 | 400 | 0em | Inline values, dense code rows. |
- | `text.display.sm` | 95/108 | 700 | -0.02em | One hero line per page: marketing, empty states. |
- | `text.headline.lg` | 72/80 | 600 | -0.02em | Page and section titles. |
- | `text.headline.md` | 34/60 | 600 | -0.02em | Page and section titles. |
- | `text.headline.sm` | 28/48 | 600 | -0.01em | Page and section titles. |
+ | `text.code.sm` | 14/20 | 400 | 0em | Inline values, dense code rows. |
+ | `text.display.lg` | 101/112 | 600 | -0.02em | One hero line per page: marketing, empty states. |
+ | `text.display.md` | 81/92 | 600 | -0.02em | One hero line per page: marketing, empty states. |
+ | `text.headline.lg` | 52/60 | 600 | -0.02em | Page and section titles. |
+ | `text.headline.sm` | 28/52 | 600 | -0.01em | Page and section titles. |
| `text.label.lg` | 17/24 | 500 | 0em | Button and control labels. |
| `text.label.md` | 13/20 | 500 | 0em | Small control labels, badges, dense tabs. |
| `text.label.sm` | 11/20 | 500 | 0.05em caps | Overlines in capitals with wide tracking (DC-L02-18). |
- | `text.title.lg` | 22/36 | 600 | 0em | Card, panel and group titles. |
- | `text.title.md` | 20/32 | 600 | 0em | Card, panel and group titles. |
+ | `text.title.lg` | 22/32 | 600 | 0em | Card, panel and group titles. |
+ | `text.title.md` | 20/28 | 600 | 0em | Card, panel and group titles. |
| `text.title.sm` | 17/24 | 600 | 0em | Table headers, field-group titles. |
**Use / avoid.**
- Use at most three sizes and two weights in one view; let color and weight carry hierarchy before size.
- Avoid all caps for sentences and in scripts without case; use weight for emphasis there.
- **Tailwind classes:** `text-body-md`, `text-label-lg`, `text-title-md`, `text-headline-md`.
+ **Tailwind classes:** `text-body-md`, `text-label-lg`, `text-title-md`.
- Decisions: D-0017 (answers.Q-type-01), D-0018 (raw.displayFace), D-0019 (raw.textFace), D-0020 (answers.Q-type-02), D-0021 (answers.Q-type-08), D-0022 (overrides.type.displayReach), D-0035 (answers.Q-type-10), D-0036 (answers.Q-type-09).
+ Decisions: D-0017 (answers.Q-type-01), D-0020 (answers.Q-type-02), D-0021 (answers.Q-type-08), D-0022 (overrides.type.displayReach), D-0035 (answers.Q-type-10), D-0036 (answers.Q-type-09), D-0075 (raw.textFace), D-0077 (raw.displayFace).
</details>
## Layout
> Zoom: sketch (0 of 3), 1 decision in this area. To zoom in, answer Q-space-01, Q-space-02, Q-space-03.
<!-- od:zoom area=layout level=0 -->
**Spacing comes in steps of 4px, and screens use spacious spacing by default.**
Designers: spacing scale, 0 to 96 · Code: `space.0 to space.96`
<details><summary>More</summary>
**Intent.** Everything sits on a 4px unit: ladder 0, 2, 4, 6, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96. Density is a mode on the semantic layer (default **spacious**). Primitives and minimum tap areas never change.
- | Density | inset xs/sm/md/lg/xl | section sm/md/lg | control sm/md/lg | outer:inner |
- |---|---|---|---|---|
- | spacious (default) | 6 / 12 / 16 / 20 / 32 | 64 / 80 / 96 | 40 / 48 / 56 | 3.5 |
- | comfortable | 4 / 8 / 12 / 16 / 24 | 32 / 40 / 64 | 32 / 40 / 48 | 2.5 |
- | compact | 2 / 6 / 8 / 12 / 20 | 16 / 24 / 40 | 24 / 32 / 40 | 2.0 |
+ | Density | inset xs/sm/md/lg/xl | section sm/md/lg | control sm/md/lg | row sm/md/lg/two-line | outer:inner |
+ |---|---|---|---|---|---|
+ | spacious (default) | 6 / 12 / 16 / 20 / 32 | 64 / 80 / 96 | 40 / 48 / 56 | 52 / 56 / 64 / 80 | 3.5 |
+ | comfortable | 4 / 8 / 12 / 16 / 24 | 32 / 40 / 64 | 32 / 40 / 48 | 44 / 48 / 56 / 72 | 2.5 |
+ | compact | 2 / 6 / 8 / 12 / 20 | 16 / 24 / 40 | 24 / 32 / 40 | 36 / 40 / 48 / 64 | 2.0 |
- Target sizes: pointer 24px, touch 44px; `size.target.min` is the floor for the primary input, and `space.target.gap` separates adjacent targets.
- Inner gaps stay below outer gaps (at least half), so grouping reads without borders (L15 P16).
- Breakpoints and grid: not generated yet; use the platform defaults and keep layout spacing independent of density (DC-L03-17).
**Use / avoid.**
- Parents own spacing (padding and gap); children never set outer margins.
- Avoid values off the ladder; if one is needed, add it as a decision.
- Decisions: D-0025 (dials.density).
+ Decisions: D-0068 (dials.density).
</details>
## Elevation & Depth
> Zoom: defined (2 of 3), 3 decisions in this area. To zoom in, answer Q-depth-02, Q-depth-03, Q-depth-05.
<!-- od:zoom area=elevation level=2 -->
- **Controls and bars can be see-through glass, with solid fallbacks.**
+ **Surfaces step in lightness; only menus and dialogs cast shadows.**
Designers: flat, shadowed, tonal or glass · Code: `$extensions.opendesigner.model`
<details><summary>More</summary>
**Your answers here** (each one either sets tokens or is a rule for this section):
- How should cards and panels stand out from the page: shadows, color steps, lines, or glass? **Materials and glass** (delegated; tokens; Q-depth-01)
- Should menus, bars and pop-ups be see-through glass, or stay solid? **Glass on navigation and controls only, never on content (Apple Liquid Glass** (delegated; tokens; Q-depth-04)
- **Intent.** Depth 90/100 selects the **materials** model. Shadow alpha in light mode is 0.221; dark mode doubles it and lifts surfaces by lightness instead (sunken, base, raised, overlay).
+ **Intent.** Depth 55/100 selects the **tonal** model. Shadow alpha in light mode is 0.154; dark mode doubles it and lifts surfaces by lightness instead (sunken, base, raised, overlay).
| Token | Layers (x y blur spread) | Use |
|---|---|---|
- | `elevation.raised` | 0 0 2 0, 0 1 2 0 | Cards that lift on hover, sticky headers, dragged rows. |
- | `elevation.floating` | 0 0 2 0, 0 4 8 0 | Menus, popovers, dropdowns. |
- | `elevation.overlay` | 0 0 2 0, 0 14 28 0 | Dialogs and sheets (with color.overlay.scrim). |
+ | `elevation.raised` | none | Cards that lift on hover, sticky headers, dragged rows. |
+ | `elevation.floating` | 0 1 2 0, 0 4 12 0 | Menus, popovers, dropdowns. |
+ | `elevation.overlay` | 0 1 2 0, 0 12 32 0 | Dialogs and sheets (with color.overlay.scrim). |
- Borders: 1px default, 2px selected and focus, 4px emphasis; width changes use an inset shadow so layout does not jump.
- Stacking: page < raised < floating (menus) < overlay (dialogs with `color.overlay.scrim`).
- - Glass is for controls and navigation only, with `color.surface.glass-fallback` under Reduce Transparency.
+ - No materials or glass in this system.
- Decisions: D-0015 (answers.Q-depth-01), D-0031 (dials.depth), D-0032 (answers.Q-depth-04).
+ Decisions: D-0015 (answers.Q-depth-01), D-0032 (answers.Q-depth-04), D-0066 (dials.depth).
</details>
## Shapes
> Zoom: sketch (0 of 3), 1 decision in this area. To zoom in, answer Q-shape-01.
<!-- od:zoom area=shape level=0 -->
**Buttons and fields have fully round corners; cards have 20px corners.**
Designers: corner radius, sharp to pill · Code: `radius.control, CSS border-radius`
<details><summary>More</summary>
**Intent.** Roundness 95/100 gives controls a **full** radius; details 4px, containers 20px, overlays 28px, people full. Controls under 32px tall use `radius.control-sm` (full).
- Nested radius: inner = max(outer - padding, smallest step). Here it is 2px inside a container with `space.inset.lg` padding. Equal radii on nested shapes look uneven (DC-L04-05).
- Focus ring: 2px, 2px offset, radius = control radius + offset (full).
- Icons: rounded corners, round caps. On iOS, use continuous corners (DC-L04-04).
- Signature shapes: none generated; a brand shape library is a designer asset (hook H-motif).
Decisions: D-0027 (dials.roundness).
</details>
## Components
> Zoom: sketch (0 of 3), 0 decisions in this area. To zoom in, answer Q-comp-01, Q-comp-02, Q-state-01.
<!-- od:zoom area=components level=0 -->
**Version 1 plans 25 components, all built from these tokens.**
Designers: core set of about 25 components · Code: `button.* (proposed), APG pattern`
<details><summary>More</summary>
**Intent.** v1 components use the semantic tokens only. Base: not chosen yet (Q-comp-01). Buttons are 48px tall (spacious), full radius, `text.label.lg`; one primary action per view.
| Component | Status |
|---|---|
| button | planned (tokens ready) |
| text-field | planned (tokens ready) |
| textarea | planned (tokens ready) |
| select | planned (tokens ready) |
| checkbox | planned (tokens ready) |
| radio | planned (tokens ready) |
| switch | planned (tokens ready) |
| slider | planned (tokens ready) |
| tabs | planned (tokens ready) |
| tooltip | planned (tokens ready) |
| popover | planned (tokens ready) |
| dialog | planned (tokens ready) |
| menu | planned (tokens ready) |
| progress-bar | planned (tokens ready) |
| spinner | planned (tokens ready) |
| banner | planned (tokens ready) |
| badge | planned (tokens ready) |
| avatar | planned (tokens ready) |
| card | planned (tokens ready) |
| list | planned (tokens ready) |
| table | planned (tokens ready) |
| link | planned (tokens ready) |
| breadcrumbs | planned (tokens ready) |
| side-navigation | planned (tokens ready) |
| accordion | planned (tokens ready) |
**Contested policies:** Validation timing and disabled submit: pending; Field style: pending; Toasts: pending; Undo versus confirm: pending; Danger actions: pending; Button emphasis levels: pending.
**Use / avoid.**
- Every interactive component shows hover, pressed, focus-visible and disabled states from the tokens.
- Inputs keep a 3:1 boundary (`color.border.input`); signifier strength: minimal-allowed.
- Component docs belong in `opendesigner/components/<name>.md` (spec 7.8).
Decisions: none recorded yet; values are defaults from references/levers.json.
</details>
## Do's and Don'ts
**These rules keep screens clear and easy to read.** The engine checks the ones it can.
Designers: guardrails and design linting · Code: `engine.py validate, engine.py review`
<details><summary>More</summary>
**Enforced by construction.** The generator cannot break these:
- text contrast 4.5:1, and 3:1 for edges, in every mode
- text on colored fills picked automatically
- tap areas pointer 24px, touch 44px
- a 2px focus ring with a 2px offset
- a reduced-motion mode
- inner spacing smaller than outer spacing, three text colors, and nested radii
**Do**
- Use semantic tokens; never raw hex, px or ms values in components.
- Keep one primary action per view and at most three type sizes per view.
- Pair every status color with an icon or label; underline links in running text.
**Don't**
- Don't shrink hit areas in dense layouts; shrink visuals and extend the hit area.
- Don't use motion as the only signal, or flash more than three times a second.
- Don't copy another brand's identity from a reference (colors, typeface, logo, imagery, copy).
- **Current validation findings:** warn: light: color.bg.action.primary (#e8a48a) on color.surface.base (#fcfdff) is 2.04:1, needs 3.0:1, warn: light: color.bg.action.primary (#e8a48a) on color.surface.raised (#ffffff) is 2.08:1, needs 3.0:1, warn: 14 text sizes; 8 to 10 is the usual range
+ **Current validation findings:** warn: light: color.bg.action.primary (#e8a48a) on color.surface.base (#f1f4f9) is 1.88:1, needs 3.0:1, warn: light: color.bg.action.primary (#e8a48a) on color.surface.raised (#fbfdff) is 2.04:1, needs 3.0:1, warn: 17 text sizes; 8 to 10 is the usual range
**Waivers:** none.
</details>
## Standards
**Follow these rules when you build.** They come from sources marked non-negotiable, their values are locked, and `engine.py review` checks the ones it can.
House standards v2: 339 rules followed in this project.
**Don't break these:**
- No motion on 100+ times-a-day actions (STD-when-to-animate-05).
- Never use ease-in on UI (STD-easing-duration-03).
- Never enter from scale(0) (STD-enter-exit-origin-01).
- Never block input during transitions (STD-springs-gestures-13).
- Never rely on haptics alone (STD-accessibility-motion-20).
<details><summary>Every theme (185 rules)</summary>
- **When to animate** (15 rules, 13 must): Decide whether to animate first; Name a valid purpose for every animation; Scale motion down as frequency rises; No motion on 100+ times-a-day actions; Never animate keyboard-initiated actions; Remove or shrink motion seen tens-of-times daily; Spend delight only on rare moments; Explanatory motion only in marketing, onboarding; and 5 more must rules. Locks `motion.duration.instant` 0ms.
- **Easing and duration** (12 rules, 10 must): Pick easing by the motion's job; Strong custom curves, not built-in keywords; Never use ease-in on UI; UI motion under 300ms unless justified; Duration budget for each element; Nothing over 1s unless illustrative; Duration grows with size and distance; Slow deliberate phase, snappy response; and 2 more must rules. Locks `motion.easing.linear` [0, 0, 1, 1], `motion.easing.hover` [0.25, 0.1, 0.25, 1], `motion.easing.enter` [0.23, 1, 0.32, 1], `motion.easing.exit` [0.23, 1, 0.32, 1], `motion.easing.standard` [0.77, 0, 0.175, 1], `motion.duration.micro` 100ms, `motion.duration.short` 150ms, `motion.duration.medium` 200ms, `motion.duration.long` 250ms.
- **Enter, exit and origin** (10 rules, 7 must): Never enter from scale(0); No opacity-only fade entrances; Exit the way it entered; Stagger group entrances 30–80ms; Measure heights, never animate to auto; React Native: never animate header height; No entering on virtualized rows. Locks `motion.scale.enter` 0.95, `motion.duration.stagger` 50ms.
- **Springs and gestures** (28 rules, 19 must): Springs for anything a finger drives; Default springs are critically damped; Bounce only after a momentum gesture; Keep bounce subtle, 0.1 to 0.3; Sheet and drawer spring values; Every animation can be grabbed mid-flight; Never block input during transitions; Start from the on-screen value; and 11 more must rules. Locks `motion.spring.spatial.dampingRatio` 1.0, `motion.spring.spatial.stiffness` 246.7, `motion.spring.momentum` a composite value (see standards.md), `motion.spring.sheet` a composite value (see standards.md), `motion.spring.rotation` a composite value (see standards.md), `motion.gesture.deceleration-rate` 0.998, `motion.gesture.dismiss-velocity` 0.11, `motion.gesture.rubberband-constant` 0.55, `motion.gesture.direction-threshold` 10px.
- **Performance and properties** (9 rules, 7 must): Animate only transform and opacity; Only clip-path, accordion height, childless width; Keep per-frame values out of React state; Pick the cheapest animation tool; Treat dropped frames as a regression; Keep React Native motion on the UI thread; Crossfade static layers, not elevation or blur.
- **Accessibility in motion** (9 rules, 5 must): Ship reduced motion with the animation; Reduced motion is gentler, never zero; Scale layout with the user's text size; Never rely on haptics alone; Never trap the user in an overlay. Locks `raw.reducedMotion` "replace", `motion.duration.reduced-fade` 200ms, `reduced:motion.transition.enter` a composite value (see standards.md), `reduced:motion.transition.exit` a composite value (see standards.md), `reduced:motion.transition.expand` a composite value (see standards.md).
- **Toasts, drawers and overlays** (10 rules, 4 must): React Native toasts: 300ms in, 250ms out; Drawer curve: cubic-bezier(0.32, 0.72, 0, 1); React Native: form sheets on both platforms; Feedback on press, commit on release. Locks `component.toast.duration` 4000ms, `motion.easing.drawer` [0.32, 0.72, 0, 1], `motion.gesture.drag-lock-after-scroll` 100ms.
- **Mobile and touch** (22 rules, 18 must): Press feedback scales to 0.97, briefly; Touch targets 44pt on iOS, 48dp on Android; Paint edge to edge, pad the safe areas; Ignore extra fingers during a drag; Redesign hover affordances for touch; Use Gesture Handler, never PanResponder; Use scheduleOnRN, not runOnJS; Cross to the JS thread only at thresholds; and 10 more must rules. Locks `motion.scale.press` 0.97, `motion.duration.micro` 100ms (STD-easing-duration-07 wins), `size.target.touch` 44px, `size.target.min` 44px, `motion.spring.spatial.dampingRatio` 1.0 (STD-springs-gestures-04 wins).
- **Visual details** (41 rules, 13 must): Tabular digits for changing numbers; Use the ellipsis character; Loosen tracking on uppercase labels; Make letter-spacing specific to size; Reserve underlines for links; Never stack light translucent surfaces; Restrained look with no project; Use and extend existing tokens; and 5 more must rules.
- **Process, review and taste** (14 rules, 8 must): Motion that merely runs is not done; Self-check motion before finishing; Report review findings as one table; Close every motion review with Block or Approve; Make the motion call yourself; Keep the motion note to a few lines; Say when feel can't be judged from code; Judge feel on real hardware.
- **Swift** (15 rules, 11 must): Check the Swift toolchain first; Simplest, most static thing first; Value types by default; Make invalid states unrepresentable; Main actor first, then step down; Enable Approachable Concurrency everywhere; MainActor default isolation for app modules; Libraries ship nonisolated APIs; and 3 more must rules.
</details>
Every rule, with its values, reasons and sources: `opendesigner/standards.md` (it also has the 154 rules this file leaves out). Read the themes you touch before you build.
## Motion
> Zoom: broad (1 of 3), 2 decisions in this area. To zoom in, answer Q-motion-07, Q-motion-08.
<!-- od:zoom area=motion level=1 -->
**Motion is off: things change in place without moving.**
Designers: motion system: duration, easing, springs · Code: `motion.duration.*, motion.easing.*`
<details><summary>More</summary>
**Your answers here** (each one either sets tokens or is a rule for this section):
- Should motion be quick and barely seen, calm with a few bold moments, or physical and bouncy? **Minimal motion (GOV.UK)** (delegated; tokens; Q-motion-01)
**Intent.** Energy 5/100: the duration multiplier x0.82 scales the extra step (standards fix medium, long). Standard easing `cubic-bezier(0.77, 0, 0.175, 1)`, enter `cubic-bezier(0.23, 1, 0.32, 1)`, exit `cubic-bezier(0.23, 1, 0.32, 1)`. Spatial spring: damping 1.0, stiffness 246.7 (Apple duration 0.4s, bounce 0.0; web `linear()` sample over 590ms). Motion is off (flags.motionOff): standard equals reduced.
| Token | Value | Note |
|---|---|---|
| `motion.duration.instant` | 0ms | set by house standard STD-when-to-animate-05 |
| `motion.duration.micro` | 100ms | set by house standard STD-easing-duration-07 |
| `motion.duration.short` | 150ms | set by house standard STD-easing-duration-07 |
| `motion.duration.medium` | 200ms | set by house standard STD-easing-duration-07 |
| `motion.duration.long` | 250ms | set by house standard STD-easing-duration-07 |
| `motion.duration.extra` | 570ms | illustrative and marketing motion only, never UI (STD-easing-duration-06) |
| `motion.duration.medium-exit` | 160ms | derived: about 0.8 x medium |
| `motion.duration.long-exit` | 200ms | derived: about 0.8 x long |
| `motion.duration.reduced-fade` | 200ms | set by house standard STD-accessibility-motion-02 |
| `motion.duration.stagger` | 50ms | set by house standard STD-enter-exit-origin-13 |
- Exits are derived, not set on their own: each is about 20% shorter than the entrance in force (medium-exit 160ms from medium 200ms, long-exit 200ms from long 250ms; STD-easing-duration-11). Change an entrance and its exit follows. Linear easing only for spinners and progress.
- Reduced motion (`prefers-reduced-motion` or `data-motion="reduced"`): travel becomes opacity, feedback stays, `motion.transition.move` is 0ms.
- Springs are stored as damping and stiffness in `$extensions.opendesigner.spring` because DTCG 2025.10 has no spring type.
- Haptics: light intensity where the platform has them; sound: hook H-sound is pending.
- Decisions: D-0010 (25 values, standards), D-0016 (answers.Q-motion-01), D-0026 (dials.energy).
+ Decisions: D-0010 (25 values, standards), D-0016 (answers.Q-motion-01), D-0067 (dials.energy).
</details>
## Modes and Themes
> Zoom: sketch (0 of 3), 1 decision in this area. To zoom in, answer Q-theme-03.
<!-- od:zoom area=modes level=0 -->
**The system has light and dark color modes, three spacing densities and a reduced-motion mode.**
Designers: modes: light, dark, density, motion · Code: `resolver modifiers, Figma collection modes`
<details><summary>More</summary>
**Your answers here** (each one either sets tokens or is a rule for this section):
- Should the app have light and dark modes that follow the device, or just one mode? **Light and dark, matching the device setting** (delegated; tokens; Q-theme-01)
**Intent.** Modes live in the DTCG resolver (`opendesigner/tokens/opendesigner.resolver.json`); modifiers are orthogonal, so no two set the same token.
| Modifier | Contexts | Default |
|---|---|---|
| theme | light, dark | light |
| density | spacious, comfortable, compact | spacious |
| motion | standard, reduced | reduced |
- Web: `prefers-color-scheme` with a `[data-theme]` override; `[data-density]`; `prefers-reduced-motion` with `[data-motion]` (see `opendesigner/build/css/tokens.css`).
- High-contrast themes are not generated; set `raw.contrastTarget` to AAA for 7:1 text everywhere.
- A second brand or a client re-skin may override the color primitives and `color.bg.brand`; semantic names never change.
Decisions: D-0009 (answers.Q-theme-01).
</details>
## Iconography and Imagery
> Zoom: sketch (0 of 3), 0 decisions in this area. To zoom in, answer Q-icon-01, Q-icon-02, Q-icon-06.
<!-- od:zoom area=iconography level=0 -->
**Icons are 24px by default.** The logo, photos and custom icons come from a person, not from the engine.
Designers: icon system: style, sizes, color · Code: `size.icon.*, icon.stroke.*, color.icon.*`
<details><summary>More</summary>
**Intent.** Default icon size 24px; sizes 16/20/24 pair with 14/16/20px text. Stroke follows the label weight: 1.5px at 16, 2.0px at 20, 2.5px at 24. Rest style: outline.
| Asset | Hook | Status |
|---|---|---|
| Logo and marks | H-logo | pending |
| Favicon set | H-favicon | pending |
| App icon | H-appicon | pending |
| Custom icons | H-icons | pending |
| Illustration | H-illus | pending |
| Photography | H-photo | pending |
| Motifs and brand shapes | H-motif | pending |
- Placeholders are labelled as placeholders. A generated stand-in is never shown as a finished brand asset.
- Icon-only buttons need an accessible name and a hit area of `size.target.min`.
Decisions: none recorded yet; values are defaults from references/levers.json.
</details>
## Content and Voice
> Zoom: sketch (0 of 3), 0 decisions in this area. To zoom in, answer Q-voice-01, Q-voice-02, Q-voice-03.
<!-- od:zoom area=content level=0 -->
**Words stay plain and short, and every button says what it does.**
Designers: content: voice, tone, microcopy · Code: `DESIGN.md Content and Voice section`
<details><summary>More</summary>
- **Intent.** Draft: voice guidance level **short** (Brand presence 65); **sentence-case**; contractions **encouraged** (Warmth 75). Voice guide hook H-voice: pending.
+ **Intent.** Draft: voice guidance level **short** (Brand presence 40); **sentence-case**; contractions **encouraged** (Warmth 75). Voice guide hook H-voice: pending.
- Buttons say what they do in two to four words; errors say what happened and how to fix it.
- Reading level: plain language; avoid jargon in UI copy.
- Word list: not recorded yet (Q-voice-03).
Decisions: none recorded yet; values are defaults from references/levers.json.
</details>
## Accessibility
> Zoom: sketch (0 of 3), 0 decisions in this area. To zoom in, answer Q-aud-02, Q-aud-03, Q-aud-04.
<!-- od:zoom area=accessibility level=0 -->
**Text passes WCAG 2.2 AA contrast in every mode, and focus is always visible.**
Designers: WCAG 2.2 AA target · Code: `raw.contrastTarget`
<details><summary>More</summary>
**Your answers here** (each one either sets tokens or is a rule for this section):
- Who uses it, and how often: all day for work, often, or now and then on the go? **Often, in an everyday app** (delegated; tokens; Q-aud-01)
**Intent.** Standard: WCAG 2.2 AA. APCA is reported as advice only.
**The system guarantees:**
- text and edge contrast in every mode
- visible focus (2px ring, 2px offset, 3:1)
- minimum target sizes for each kind of input
- a reduced-motion mode
- rem-based type, so text scales to 200%
- text on colored fills chosen automatically
**Product teams own:** accessible names and labels, alt text, reading and focus order, and never using color alone. Also: clear error messages, dialogs that can be closed, no pre-checked consent boxes, and captions for media.
**Test on each platform:**
- keyboard only
- a screen reader (VoiceOver, NVDA or TalkBack)
- 200% zoom
- forced colors (Windows High Contrast)
- reduced motion
- touch on a phone, for every surface that allows touch
Decisions: none recorded yet; values are defaults from references/levers.json.
</details>
## Platforms and Devices
> Zoom: sketch (0 of 3), 1 decision in this area. To zoom in, answer Q-plat-05, Q-plat-10, Q-plat-08.
<!-- od:zoom area=platforms level=0 -->
**It runs on ios, and each platform keeps its own navigation.**
Designers: target platforms · Code: `raw.platforms; size.target.min`
<details><summary>More</summary>
**Your answers here** (each one either sets tokens or is a rule for this section):
- Where will it run first: web, iOS, Android or desktop? **iOS/iPadOS** (delegated; tokens; Q-plat-01)
- **Intent.** Platforms: ios. Inputs: pointer, touch. Brand presence 65: each platform keeps its own shapes and materials: no; each platform keeps its own body text size: no.
+ **Intent.** Platforms: ios. Inputs: pointer, touch. Brand presence 40: each platform keeps its own shapes and materials: yes; each platform keeps its own body text size: yes.
- Navigation, back, sheets and pickers stay native on every platform (DC-L10-02).
- Swift and Compose files in `opendesigner/build/` use the same names as the CSS variables (Figma code syntax matches them).
- - Fonts by platform: text: Instrument Sans (open licence or system font: web, apps and self-hosting); display: Instrument Serif: licensed for web, apps, self-hosting.
+ - Fonts by platform: text: Albert Sans: licensed for web, apps, self-hosting; display: Albert Sans: licensed for web, apps, self-hosting.
Decisions: D-0006 (answers.Q-plat-01).
</details>
## Decisions
- **55 decisions so far.** Each one says who set it and why.
+ **77 decisions so far.** Each one says who set it and why.
Designers: decision history with reasons · Code: `opendesigner/decisions.md`
<details><summary>More</summary>
Your own decisions first (the newest value per path), then your changes to standards, then one row per standards batch. Full log: [opendesigner/decisions.md](opendesigner/decisions.md).
| Id | Path | Value | Set by | Reason |
|---|---|---|---|---|
| D-0002 | `answers.Q-aud-01` | "regular" | delegated | sketch: picked for them |
| D-0003 | `context.audience` | "people who use it often, as an everyday app" | delegated | sketch: plain words for Q-aud-01, picked for them |
| D-0006 | `answers.Q-plat-01` | ["ios"] | delegated | sketch: picked for them |
| D-0007 | `macros` | ["deferential", "minimal", "friendly"] | delegated | sketch: picked for them (feel words: calm, gentle) |
| D-0008 | `taste.feelWords` | ["calm", "gentle"] | delegated | sketch: feel words picked for them |
| D-0009 | `answers.Q-theme-01` | "system-light-dark" | delegated | sketch: picked for them |
| D-0011 | `answers.Q-color-01` | "keep-hex" | delegated | dusk peach is the brand; keep it exact |
- | D-0012 | `raw.brandColor` | "#E8A48A" | delegated | restore the dusk peach the sketch turned into a brown; dark text passes on it |
- | D-0013 | `raw.neutralBase` | "#231E4A" | delegated | night-indigo grays: the app lives after dark |
| D-0014 | `answers.Q-color-21` | "near-black" | delegated | deep night, not pure black (OLED smear on the slow orb) |
| D-0015 | `answers.Q-depth-01` | "glass" | delegated | frosted controls float over the living gradient |
| D-0016 | `answers.Q-motion-01` | "none" | delegated | a sleep screen should barely move; only the orb breathes |
| D-0017 | `answers.Q-type-01` | "brand-display" | delegated | a calm serif for the timer and title, a soft sans for controls |
- | D-0018 | `raw.displayFace` | "Instrument Serif" | delegated | light, calm, condensed serif with a lovely italic (OFL) |
- | D-0019 | `raw.textFace` | "Instrument Sans" | delegated | the serif's companion sans (OFL) |
| D-0020 | `answers.Q-type-02` | "yes" | delegated | both are SIL OFL from Google Fonts |
| D-0021 | `answers.Q-type-08` | 17 | delegated | iOS body 17pt |
| D-0022 | `type.displayReach` | 6 | delegated | the timer is the hero; it needs a step near 100px |
- | D-0023 | `dials.expression` | 75 | delegated | an immersive player: the orb and the timer perform |
- | D-0024 | `dials.brandPresence` | 65 | delegated | the dusk palette and serif are the brand |
- | D-0025 | `dials.density` | 15 | delegated | very spacious; one thing at a time |
- | D-0026 | `dials.energy` | 5 | delegated | the calmest possible; slow, soft |
| D-0027 | `dials.roundness` | 95 | delegated | circles and pills only; nothing sharp at bedtime |
- | D-0029 | `dials.colorfulness` | 60 | delegated | dusk gradients carry color |
| D-0030 | `dials.warmth` | 75 | delegated | warm peach light against indigo |
- | D-0031 | `dials.depth` | 90 | delegated | glass, as answered in Q-depth-01 (my earlier depth 45 had silently undone it) |
| D-0032 | `answers.Q-depth-04` | "control-layer" | delegated | frosted controls over the gradient |
| D-0033 | `answers.Q-color-04` | "analogous" | delegated | dusk needs neighbouring hues for the sky: peach, rose, lavender |
- | D-0034 | `raw.secondaryColors` | ["#C77DA8", "#7E6CC9"] | delegated | dusk rose and evening lavender, the two hues either side of the peach horizon |
| D-0035 | `answers.Q-type-10` | "named" | delegated | owner rule: Apple HIG Dynamic Type roles |
| D-0036 | `answers.Q-type-09` | "hand-tuned" | delegated | HIG sizes are hand-tuned, not a ratio |
- | D-0037 | `font.size.11` | {"value": 11, "unit": "px"} | delegated | HIG Dynamic Type step |
- | D-0038 | `font.size.13` | {"value": 13, "unit": "px"} | delegated | HIG Dynamic Type step |
- | D-0039 | `font.size.15` | {"value": 15, "unit": "px"} | delegated | HIG Dynamic Type step |
- | D-0040 | `font.size.17` | {"value": 17, "unit": "px"} | delegated | HIG Dynamic Type step |
| D-0041 | `font.size.20` | {"value": 20, "unit": "px"} | delegated | HIG Dynamic Type step |
| D-0042 | `font.size.22` | {"value": 22, "unit": "px"} | delegated | HIG Dynamic Type step |
- | D-0043 | `font.size.28` | {"value": 28, "unit": "px"} | delegated | HIG Dynamic Type step |
| D-0044 | `font.size.34` | {"value": 34, "unit": "px"} | delegated | HIG Dynamic Type step |
| D-0045 | `text.label.sm` | {"fontFamily": "{font.family.text}", "fontSize": "{font.size.11}", "fontWeight": "{font.weight.label}", "letterSpacing": {"value": 0.06, "unit": "em"}, "lineHeight": 1.1818} | delegated | HIG Caption 2 (11/13), used as the overline |
+ | D-0046 | `text.label.md` | {"fontFamily": "{font.family.text}", "fontSize": "{font.size.13}", "fontWeight": "{font.weight.label}", "letterSpacing": {"value": 0.0, "unit": "em"}, "lineHeight": 1.3846} | delegated | HIG Footnote (13/18) |
+ | D-0047 | `text.label.lg` | {"fontFamily": "{font.family.text}", "fontSize": "{font.size.17}", "fontWeight": "{font.weight.label}", "letterSpacing": {"value": 0.0, "unit": "em"}, "lineHeight": 1.2941} | delegated | HIG Body (17/22) for control labels |
+ | D-0048 | `text.body.sm` | {"fontFamily": "{font.family.text}", "fontSize": "{font.size.15}", "fontWeight": "{font.weight.body}", "letterSpacing": {"value": 0.0, "unit": "em"}, "lineHeight": 1.3333} | delegated | HIG Subheadline (15/20) |
+ | D-0049 | `text.body.md` | {"fontFamily": "{font.family.text}", "fontSize": "{font.size.17}", "fontWeight": "{font.weight.body}", "letterSpacing": {"value": 0.0, "unit": "em"}, "lineHeight": 1.2941} | delegated | HIG Body (17/22) |
+ | D-0050 | `text.body.lg` | {"fontFamily": "{font.family.text}", "fontSize": "{font.size.20}", "fontWeight": "{font.weight.body}", "letterSpacing": {"value": 0.0, "unit": "em"}, "lineHeight": 1.25} | delegated | HIG Title 3 size for lead text (20/25) |
+ | D-0051 | `text.title.sm` | {"fontFamily": "{font.family.text}", "fontSize": "{font.size.17}", "fontWeight": "{font.weight.heading}", "letterSpacing": {"value": 0.0, "unit": "em"}, "lineHeight": 1.2941} | delegated | HIG Headline (17/22, semibold) |
+ | D-0052 | `text.title.md` | {"fontFamily": "{font.family.text}", "fontSize": "{font.size.20}", "fontWeight": "{font.weight.heading}", "letterSpacing": {"value": 0.0, "unit": "em"}, "lineHeight": 1.25} | delegated | HIG Title 3 (20/25) |
+ | D-0053 | `text.title.lg` | {"fontFamily": "{font.family.text}", "fontSize": "{font.size.22}", "fontWeight": "{font.weight.heading}", "letterSpacing": {"value": 0.0, "unit": "em"}, "lineHeight": 1.2727} | delegated | HIG Title 2 (22/28) |
+ | D-0054 | `text.headline.sm` | {"fontFamily": "{font.family.display}", "fontSize": "{font.size.28}", "fontWeight": "{font.weight.heading}", "letterSpacing": {"value": 0.0, "unit": "em"}, "lineHeight": 1.2143} | delegated | HIG Title 1 (28/34) |
+ | D-0055 | `text.headline.md` | {"fontFamily": "{font.family.display}", "fontSize": "{font.size.34}", "fontWeight": "{font.weight.heading}", "letterSpacing": {"value": 0.0, "unit": "em"}, "lineHeight": 1.2059} | delegated | HIG Large Title (34/41) |
+ | D-0058 | `raw.fontLicence` | {"web": true, "app": true, "selfHost": true} | chosen | Inter and Source Serif 4 are SIL OFL |
+ | D-0059 | `raw.neutralBase` | "#1C2430" | chosen | round 4: owner said the app screens look cheap; rebuilt as a photo-led player; slate night instead of violet-indigo (no violet AI glow) |
+ | D-0060 | `raw.accentCount` | 1 | chosen | one accent, one job: the sleep timer |
+ | D-0061 | `raw.secondaryColors` | [] | chosen | rose and lavender sky ramps no longer used: the sky is a photograph |
+ | D-0062 | `raw.brandColor` | "#E8A48A" | chosen | brand peach kept as typed |
+ | D-0063 | `dials.expression` | 45 | delegated | round 4: owner said the app screens look cheap; rebuilt as a photo-led player |
- 11 more in the full log.
+ 15 more in the full log.
</details>
## Open Items
**These parts are still defaults, guesses, or waiting for a person.**
- Still defaults (zoom 0, sketch): Colors, Layout, Shapes, Components, Modes and Themes, Iconography and Imagery, Content and Voice, Accessibility, Platforms and Devices
- Assets not final: Logo, wordmark, symbol, lockups (H-logo): pending; App icon (H-appicon): pending; Favicon set (H-favicon): pending; Custom icons, pictograms, spot icons (H-icons): pending; Illustration, characters, empty-state art (H-illus): pending; Photography and art direction (H-photo): pending; Fixed brand colors (H-color): pending; Motion signature and rich media (H-motion): pending; Graphic devices, patterns, textures, brand gradients, signature shape (H-motif): pending; UI sounds and sonic logo (H-sound): pending; Custom haptic patterns (H-haptic): pending; Voice and tone guide (H-voice): pending; Brand guidelines PDF (H-brandbook): pending
- - Answers taken on assumption or delegated: answers.Q-aud-01 (delegated), context.audience (delegated), raw.brandColor (delegated), answers.Q-plat-01 (delegated), macros (delegated), taste.feelWords (delegated), answers.Q-theme-01 (delegated), answers.Q-color-01 (delegated), raw.neutralBase (delegated), answers.Q-color-21 (delegated), answers.Q-depth-01 (delegated), answers.Q-motion-01 (delegated), answers.Q-type-01 (delegated), raw.displayFace (delegated), raw.textFace (delegated), answers.Q-type-02 (delegated), answers.Q-type-08 (delegated), overrides.type.displayReach (delegated), dials.expression (delegated), dials.brandPresence (delegated), dials.density (delegated), dials.energy (delegated), dials.roundness (delegated), dials.depth (delegated), dials.colorfulness (delegated), dials.warmth (delegated), answers.Q-depth-04 (delegated), answers.Q-color-04 (delegated), raw.secondaryColors (delegated), answers.Q-type-10 (delegated), answers.Q-type-09 (delegated), overrides.font.size.11 (delegated), overrides.font.size.13 (delegated), overrides.font.size.15 (delegated), overrides.font.size.17 (delegated), overrides.font.size.20 (delegated), overrides.font.size.22 (delegated), overrides.font.size.28 (delegated), overrides.font.size.34 (delegated), overrides.text.label.sm (delegated), overrides.text.label.md (delegated), overrides.text.label.lg (delegated), overrides.text.body.sm (delegated), overrides.text.body.md (delegated), overrides.text.body.lg (delegated), overrides.text.title.sm (delegated), overrides.text.title.md (delegated), overrides.text.title.lg (delegated), overrides.text.headline.sm (delegated), overrides.text.headline.md (delegated)
+ - Answers taken on assumption or delegated: answers.Q-aud-01 (delegated), context.audience (delegated), answers.Q-plat-01 (delegated), macros (delegated), taste.feelWords (delegated), answers.Q-theme-01 (delegated), answers.Q-color-01 (delegated), answers.Q-color-21 (delegated), answers.Q-depth-01 (delegated), answers.Q-motion-01 (delegated), answers.Q-type-01 (delegated), answers.Q-type-02 (delegated), answers.Q-type-08 (delegated), overrides.type.displayReach (delegated), dials.expression (delegated), dials.brandPresence (delegated), dials.density (delegated), dials.energy (delegated), dials.roundness (delegated), dials.depth (delegated), dials.colorfulness (delegated), dials.warmth (delegated), answers.Q-depth-04 (delegated), answers.Q-color-04 (delegated), answers.Q-type-10 (delegated), answers.Q-type-09 (delegated), overrides.font.size.20 (delegated), overrides.font.size.22 (delegated), overrides.font.size.34 (delegated), overrides.text.label.sm (delegated), overrides.text.label.md (delegated), overrides.text.label.lg (delegated), overrides.text.body.sm (delegated), overrides.text.body.md (delegated), overrides.text.body.lg (delegated), overrides.text.title.sm (delegated), overrides.text.title.md (delegated), overrides.text.title.lg (delegated), overrides.text.headline.sm (delegated), overrides.text.headline.md (delegated)
- Waivers: none.
- Licence risks: none found.
- Validation: 0 errors, 3 warnings.
## For Agents
**AI agents: read this file and the tokens before you change any screen.**
1. Before any visual work, read this file, PRODUCT.md and `opendesigner/tokens/` (start at `opendesigner/tokens/opendesigner.resolver.json`).
- 2. Use tokens, never raw values: CSS `var(--ds-...)`, Tailwind classes from `opendesigner/build/tailwind/theme.css`, `DS.*` in Swift, `DsTheme` in Compose.
- 3. Do not change a locked decision without asking the owner.
+ 2. Use tokens, never raw values: CSS `var(--ds-...)`, Tailwind classes from `opendesigner/build/tailwind/theme.css`, `DS.*` in Swift. In Compose: colors from `LocalDsColors.current` (provide `DsLightColors` or `DsDarkColors`), then `DsSpace`, `DsType` and `DsMotion`.
+ 3. Do not change a locked decision without asking the owner. Keep new screens inside the taste card at the top of the Overview, and never add anything on its Never list without asking.
4. To change the system: `engine.py set <path> <value> --why "..."`, then `engine.py generate`, `engine.py validate`, `engine.py design-md`.
5. To extend: add a decision (never hand-edit tokens or this file). Keep semantic names the same across modes. Deprecate instead of deleting.
6. After any implementation, run `engine.py review` and re-read this file.
7. Follow the Standards section. Change a standard only when the person explicitly asks: `engine.py standard override <id> --why "<their words>"`.
**Tailwind v4 classes** (one import: `@import "tailwindcss"; @import "<path>/opendesigner/build/tailwind/theme.css";`; Tailwind's default colors, text sizes, radii and shadows are switched off, so only these exist):
| Area | Classes |
|---|---|
| Color | `bg-action-primary`, `hover:bg-action-primary-hover`, `active:bg-action-primary-pressed`, `text-on-action`, `bg-surface-base`, `bg-surface-raised`, `text-fg-primary`, `text-fg-secondary`, `text-fg-link`, `border-line-subtle`, `border-line-input`, `outline-line-focus`, `bg-danger-bold` |
| Layout | `p-inset-md`, `gap-stack-md`, `gap-inline-sm`, `h-control-md`, `size-icon-md`, `min-h-target-min` |
| Shape | `rounded-control`, `rounded-container`, `rounded-overlay`, `rounded-full` |
| Elevation | `shadow-raised`, `shadow-floating`, `shadow-overlay` |
- | Type | `text-body-md`, `text-label-lg`, `text-title-md`, `text-headline-md` |
+ | Type | `text-body-md`, `text-label-lg`, `text-title-md` |
| Motion | `transition-feedback`, `ease-enter`, `ease-exit`, `ease-hover` |