@trading-game/design-intelligence-layer 0.17.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +104 -231
- package/README.md +46 -742
- package/dist/index.cjs +2753 -2985
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +247 -261
- package/dist/index.d.ts +247 -261
- package/dist/index.js +2648 -2855
- package/dist/index.js.map +1 -1
- package/docs/components/accordion.md +85 -0
- package/docs/components/alert-dialog.md +98 -0
- package/docs/components/aspect-ratio.md +55 -0
- package/docs/components/avatar.md +88 -0
- package/docs/components/badge.md +75 -0
- package/docs/components/banner.md +84 -0
- package/docs/components/bottom-navigation.md +90 -0
- package/docs/components/breadcrumb.md +85 -0
- package/docs/components/button.md +94 -0
- package/docs/components/calendar.md +74 -0
- package/docs/components/card.md +79 -0
- package/docs/components/carousel.md +82 -0
- package/docs/components/checkbox.md +66 -0
- package/docs/components/chip.md +72 -0
- package/docs/components/command.md +86 -0
- package/docs/components/context-menu.md +90 -0
- package/docs/components/dialog.md +95 -0
- package/docs/components/drawer.md +97 -0
- package/docs/components/dropdown-menu.md +92 -0
- package/docs/components/empty.md +87 -0
- package/docs/components/field.md +117 -0
- package/docs/components/hover-card.md +77 -0
- package/docs/components/input-group.md +105 -0
- package/docs/components/input-otp.md +87 -0
- package/docs/components/input.md +71 -0
- package/docs/components/item.md +105 -0
- package/docs/components/label.md +56 -0
- package/docs/components/link.md +66 -0
- package/docs/components/menubar.md +102 -0
- package/docs/components/native-select.md +71 -0
- package/docs/components/navigation-button.md +68 -0
- package/docs/components/navigation-menu.md +99 -0
- package/docs/components/numpad.md +78 -0
- package/docs/components/pagination.md +84 -0
- package/docs/components/popover.md +89 -0
- package/docs/components/profile-photo.md +81 -0
- package/docs/components/progress.md +60 -0
- package/docs/components/radio-group.md +82 -0
- package/docs/components/resizable.md +79 -0
- package/docs/components/scroll-area.md +66 -0
- package/docs/components/section-message.md +99 -0
- package/docs/components/select.md +105 -0
- package/docs/components/separator.md +55 -0
- package/docs/components/sheet.md +91 -0
- package/docs/components/sidebar.md +125 -0
- package/docs/components/skeleton.md +51 -0
- package/docs/components/slider.md +61 -0
- package/docs/components/spinner.md +52 -0
- package/docs/components/stepper.md +68 -0
- package/docs/components/switch.md +57 -0
- package/docs/components/table.md +86 -0
- package/docs/components/tabs.md +95 -0
- package/docs/components/textarea.md +58 -0
- package/docs/components/toast.md +66 -0
- package/docs/components/toggle-group.md +77 -0
- package/docs/components/toggle.md +60 -0
- package/docs/components/tooltip.md +83 -0
- package/docs/foundations/colors.md +109 -0
- package/docs/foundations/motion.md +63 -0
- package/docs/foundations/shape-layout.md +56 -0
- package/docs/foundations/typography.md +83 -0
- package/docs/patterns/forms.md +70 -0
- package/docs/patterns/menus.md +50 -0
- package/docs/patterns/on-brand.md +43 -0
- package/guides/audits/design-system-audit-2026-07.md +135 -0
- package/guides/rules/design-system-consuming-project.mdc +56 -0
- package/package.json +6 -7
- package/src/styles.css +1616 -251
- package/guides/design-system-guide/trading-game-ds-guide.md +0 -917
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
Hover/focus hint on desktop that automatically becomes a bottom Drawer with a title and close button on mobile.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- Short clarifications for icons, abbreviations, and truncated values — fee breakdowns, badge meanings, collapsed sidebar labels.
|
|
8
|
+
|
|
9
|
+
**When not:** For rich interactive content on hover use Hover Card or Popover. For persistent inline help use helper text under the field.
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- `TooltipProvider` — Radix provider, `delayDuration` preset to `0`; `data-slot="tooltip-provider"`.
|
|
14
|
+
- `Tooltip` — root that picks the surface per viewport via the internal `TooltipModeContext`: Radix Tooltip on desktop, vaul `Drawer` on mobile; `data-slot="tooltip"` (desktop).
|
|
15
|
+
- `TooltipTrigger` — renders a Radix `Trigger` or a `DrawerTrigger` depending on mode; `data-slot="tooltip-trigger"`.
|
|
16
|
+
- `TooltipContent` — desktop: portalled bubble with arrow; mobile: `DrawerContent` with `DrawerHeader`/`DrawerTitle`, body, and a primary close `Button` in the `DrawerFooter`; `data-slot="tooltip-content"`, `data-variant`.
|
|
17
|
+
|
|
18
|
+
## API
|
|
19
|
+
|
|
20
|
+
### TooltipContent
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Notes |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| `variant` | `"default" \| "inverse"` | `"default"` | default: inverse surface (dark-on-light / light-on-dark). inverse: bordered white/primary-surface card for use over saturated surfaces. Desktop only — mobile always uses the neutral drawer. |
|
|
25
|
+
| `sideOffset` | `number` | `6` | Desktop offset from the trigger. |
|
|
26
|
+
| `title` | `string` | `"Title"` | Mobile drawer heading. The default is a placeholder — always pass real text. Ignored on desktop. |
|
|
27
|
+
| `closeLabel` | `string` | `"Label"` | Mobile drawer dismiss-button text. Placeholder default — always pass real text. Ignored on desktop. |
|
|
28
|
+
|
|
29
|
+
`Tooltip` takes Radix root props (`open`, `onOpenChange`, `delayDuration`); `TooltipProvider` presets `delayDuration={0}`.
|
|
30
|
+
|
|
31
|
+
## Variants & sizes
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import {
|
|
35
|
+
TooltipProvider, Tooltip, TooltipTrigger, TooltipContent,
|
|
36
|
+
} from "@trading-game/design-intelligence-layer"
|
|
37
|
+
|
|
38
|
+
<TooltipProvider>
|
|
39
|
+
<Tooltip>
|
|
40
|
+
<TooltipTrigger asChild><button>Fees</button></TooltipTrigger>
|
|
41
|
+
<TooltipContent title="Fees" closeLabel="Got it">
|
|
42
|
+
0.1% maker / 0.2% taker.
|
|
43
|
+
</TooltipContent>
|
|
44
|
+
</Tooltip>
|
|
45
|
+
|
|
46
|
+
{/* over a saturated surface */}
|
|
47
|
+
<Tooltip>
|
|
48
|
+
<TooltipTrigger asChild><span>2x boost</span></TooltipTrigger>
|
|
49
|
+
<TooltipContent variant="inverse" title="Boost" closeLabel="Close">
|
|
50
|
+
Doubles points on winning trades.
|
|
51
|
+
</TooltipContent>
|
|
52
|
+
</Tooltip>
|
|
53
|
+
</TooltipProvider>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Tokens
|
|
57
|
+
|
|
58
|
+
**Colour (semantic):** `bg-background-inverse-surface` + `text-text-prominent-inverse` (default desktop bubble, arrow fill matches), `bg-background-primary-surface` + `text-text-prominent-default` + `border-border-default-default` (inverse variant); mobile drawer body uses `text-text-subtle-default` on the Drawer's own surface.
|
|
59
|
+
**Type (private):**
|
|
60
|
+
|
|
61
|
+
| Token | Value |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `--tooltip-*` (via `.type-tooltip`, desktop) | 14 / medium (500) / 20 |
|
|
64
|
+
| Mobile body (via `.type-drawer-description`) | 14 / regular (400) / 20; drawer title 18 / semibold (600) / 24 |
|
|
65
|
+
|
|
66
|
+
## Behaviour
|
|
67
|
+
|
|
68
|
+
- The mobile/desktop decision is centralized in `Tooltip` through `TooltipModeContext`, so trigger and content always agree within a render — avoiding SSR-hydration mismatches ("TooltipTrigger must be used within Tooltip").
|
|
69
|
+
- Mobile (`useIsMobile`): tap opens a bottom Drawer that persists until scrim tap, swipe-down, or the footer's primary md close Button — hover tooltips do not exist on touch.
|
|
70
|
+
- Desktop: portalled bubble, `rounded-md`, max width `max-w-xs`, `sideOffset` 6, zoom/fade/slide animations, no shadow; `variant` only affects desktop.
|
|
71
|
+
- `SidebarProvider` wraps its tree in a `TooltipProvider` already; elsewhere you must provide one.
|
|
72
|
+
|
|
73
|
+
## Do / Don't
|
|
74
|
+
|
|
75
|
+
**Do**
|
|
76
|
+
- Always pass real `title` and `closeLabel` — the source defaults are the literal placeholder strings "Title" and "Label", and they render on mobile.
|
|
77
|
+
- Use `variant="inverse"` over saturated coloured surfaces where the inverse bubble would feel heavy.
|
|
78
|
+
- Keep content to a sentence or two; the desktop bubble caps at `max-w-xs`.
|
|
79
|
+
|
|
80
|
+
**Don't**
|
|
81
|
+
- Don't put links or buttons inside tooltip content — on desktop it is hover-only; use Popover instead.
|
|
82
|
+
- Don't wrap tooltip text past medium 500 weight on desktop; `.type-tooltip` is fixed at 14/medium/20.
|
|
83
|
+
- Don't build a separate mobile fallback — the drawer behaviour is automatic.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Colour
|
|
2
|
+
|
|
3
|
+
Champion Trade's colour system is three layers. Components consume **semantic tokens only** — never primitives, never hex values. The semantic layer is what makes the dark theme work: every semantic token re-resolves in dark, and dark values are chosen for dark, not mirrored from light.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Layer 1 Primitives --primitive-blue-600: #2323FF raw scales, never used in components
|
|
7
|
+
Layer 2 Semantics --background-brand-default the only layer components reference
|
|
8
|
+
Layer 3 Components (typography tokens only — colour has NO component layer)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Token grammar (Quill style): `{element}-{role}-{variant}` — e.g. `background-brand-selected`, `text-error-default`, `border-default-default`. In Tailwind classes the element prefix repeats: `bg-background-brand-default`, `text-text-error-default`, `border-border-default-default`.
|
|
12
|
+
|
|
13
|
+
## Hard rules
|
|
14
|
+
|
|
15
|
+
1. **Semantic tokens only in components.** A raw primitive is allowed only for alpha glazes (hovers, glass, scrims) — e.g. `bg-(--primitive-white-alpha-24)` for glass.
|
|
16
|
+
2. **Surfaces are always solid.** Alpha values are for *state layers on top of* a solid base (hover glazes, pressed glazes, glass, scrims, washes) — never for a resting surface. Dark surfaces are built as `color-mix()` of white over the canvas, producing a solid colour.
|
|
17
|
+
3. **Dark is not a mirror.** When a light construction fails on near-black (a 10% wash disappears, mid-blue ink loses contrast), the dark value diverges — that's what the `.dark` block is for. Never hardcode a light construction and assume it flips.
|
|
18
|
+
4. **Selection states use the `*-brand-selected` family** — never `brand-default/10` washes or `text-brand-default` ink for a selected/active element.
|
|
19
|
+
5. **No shadows anywhere.** Elevation is hairline borders and surface steps.
|
|
20
|
+
|
|
21
|
+
## Primitive scales
|
|
22
|
+
|
|
23
|
+
All scales run 50→1000 (mono 50→1200; blue adds 25 and 1000 as the two canvases). Bases: brand blue at **600**, status colours (green/red/amber/azure) at **600**, game accents (ember/violet/jade) at **500**.
|
|
24
|
+
|
|
25
|
+
| Scale | Base | Notes |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `blue` | 600 `#2323FF` | Brand. 25 `#F7F8FC` = light canvas · 1000 `#00001F` = dark canvas · 400 `#4D6BFF` = dark-theme brand |
|
|
28
|
+
| `mono` | — | 50 `#FFFFFF` → 1200 `#000000`; 1100 `#1C1C1C` is prominent ink |
|
|
29
|
+
| `green` | 600 `#00BD46` | Status: success / win |
|
|
30
|
+
| `red` | 600 `#FE2925` | Status: error / loss |
|
|
31
|
+
| `amber` | 600 `#F49E00` | Status: warning |
|
|
32
|
+
| `azure` | 600 `#2A90E0` | Status: information + game accent (sky ≈ 500) |
|
|
33
|
+
| `ember` | 500 `#E85C1A` | Game accent + demo-account marker |
|
|
34
|
+
| `violet` | 500 `#9A3CF0` | Game accent (lilac ≈ 200) |
|
|
35
|
+
| `jade` | 500 `#0AA060` | Game accent |
|
|
36
|
+
| `white-alpha` | — | 4/6/8/12/16/20/24/32/40/50/64/80 — lifts and glazes on dark or coloured surfaces |
|
|
37
|
+
| `black-alpha` | — | same steps — glazes and scrims on light surfaces |
|
|
38
|
+
| `blue-alpha` | — | 4→80 — frosted brand washes |
|
|
39
|
+
|
|
40
|
+
## Semantic tokens — Background
|
|
41
|
+
|
|
42
|
+
| Token | Light | Dark | Job |
|
|
43
|
+
|---|---|---|---|
|
|
44
|
+
| `background-primary-canvas` | blue-25 `#F7F8FC` | blue-1000 `#00001F` | The app page behind everything |
|
|
45
|
+
| `background-primary-surface` | mono-50 `#FFFFFF` | white 6% over canvas ≈ `#0F0F2C` | Cards, panels, menus, inputs, sheets — the default solid surface |
|
|
46
|
+
| `background-secondary-surface` | mono-100 `#F1F1F1` | white 12% over canvas ≈ `#1F1F3A` | One step up: muted fills, tracks, icon tiles, read-only fields |
|
|
47
|
+
| `background-hover-default` | black-alpha-4 | white-alpha-12 | The hover glaze — laid over any solid surface |
|
|
48
|
+
| `background-pressed-default` | black-alpha-8 | white-alpha-20 | The pressed glaze |
|
|
49
|
+
| `background-brand-default` | blue-600 `#2323FF` | blue-400 `#4D6BFF` | Primary CTAs, checked controls, solid selection chips |
|
|
50
|
+
| `background-brand-hover` | black 16% over brand | (same recipe, re-derives) | Primary button hover — a state layer, not a scale step |
|
|
51
|
+
| `background-brand-pressed` | black 32% over brand | (same recipe) | Primary button pressed |
|
|
52
|
+
| `background-brand-selected` | blue-600 10% over white ≈ `#E9E9FF` | blue-400 24% over canvas ≈ `#121A55` | Selected/active fills: active nav row, current page, checked choice card |
|
|
53
|
+
| `background-brand-container` | white 92% glaze over blue-600 (layered) | static — same | The icy brand panel (calendar selection). **Layered value — paint with `background:`, never `bg-*`** |
|
|
54
|
+
| `background-inverse-surface` | mono-1100 `#1C1C1C` | mono-50 `#FFFFFF` | The flipped surface (tooltips, toasts) |
|
|
55
|
+
| `background-overlay-default` | black-alpha-50 | black-alpha-64 | Modal scrims |
|
|
56
|
+
| `background-success-default` | green-50 | green-600 24% over canvas | Status tint surface (Section Message) |
|
|
57
|
+
| `background-error-default` | red-50 | red-600 24% over canvas | ditto |
|
|
58
|
+
| `background-warning-default` | amber-50 | amber-600 24% over canvas | ditto |
|
|
59
|
+
| `background-information-default` | azure-50 | azure-600 24% over canvas | ditto |
|
|
60
|
+
| `background-success-solid` / `-error-solid` / `-warning-solid` | status 600 | static — same | Solid status fills (badges) |
|
|
61
|
+
| `background-demo-solid` / `background-demo-default` | ember-500 / ember-50 | static | Demo-account badge fills |
|
|
62
|
+
| `background-static-white` | mono-50 | static — never flips | White elements on fixed-colour art panels (on-brand buttons) |
|
|
63
|
+
|
|
64
|
+
## Semantic tokens — Text & Icon
|
|
65
|
+
|
|
66
|
+
| Token | Light | Dark | Job |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| `text-prominent-default` | mono-1100 `#1C1C1C` | mono-50 `#FFFFFF` | Titles, values, primary content |
|
|
69
|
+
| `text-subtle-default` | mono-900 `#555555` | mono-600 `#AAAAAA` | Descriptions, labels, placeholders, secondary content |
|
|
70
|
+
| `text-disabled-default` | mono-500 | mono-800 | Disabled labels |
|
|
71
|
+
| `text-prominent-inverse` | mono-50 | mono-1100 | Ink on `background-inverse-surface` |
|
|
72
|
+
| `text-brand-default` | blue-600 | blue-400 `#4D6BFF` | Links, brand-coloured text on the canvas |
|
|
73
|
+
| `text-brand-hover` | blue-700 `#0606C7` | blue-300 `#7392FF` | Hover step for brand text (links, menu hover ink) |
|
|
74
|
+
| `text-brand-selected` | blue-600 | blue-200 `#99B7FF` | Ink on selected/active elements — two stops lighter in dark for contrast |
|
|
75
|
+
| `text-on-brand-static` | mono-50 | static | White text on brand fills — never flips |
|
|
76
|
+
| `text-brand-container-static` | blue-600 | static | Ink on `background-brand-container` |
|
|
77
|
+
| `text-static-black` | mono-1100 | static | Ink on `background-static-white` |
|
|
78
|
+
| `text-success-default` / `-error-` / `-warning-` / `-information-` | status 600 | **static — same in both themes** (deliberate call) | Status text |
|
|
79
|
+
| `text-on-status-static` | mono-50 | static | White on solid status fills (below AA on green/amber — deliberate style call) |
|
|
80
|
+
| `text-demo-default` | ember-500 | static | Demo badge ink |
|
|
81
|
+
| `icon-prominent-default` … `icon-information-default` | aliases of the matching text tokens | — | Separate namespace so icons (3:1 requirement) can diverge later |
|
|
82
|
+
|
|
83
|
+
## Semantic tokens — Border & Ring
|
|
84
|
+
|
|
85
|
+
| Token | Light | Dark | Job |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| `border-default-default` | mono-200 `#E3E3E3` | white-alpha-12 | The hairline — every card, input, menu, divider |
|
|
88
|
+
| `border-prominent-default` | mono-1100 | mono-50 | Heavy outline (outline toggles) |
|
|
89
|
+
| `border-brand-selected` | blue-600 | blue-300 `#7392FF` | Border on selected elements (outline chip, active page) |
|
|
90
|
+
| `border-neutral-default` | mono-500 | white-alpha-12 | Soft hairline for neutral tint chips |
|
|
91
|
+
| `border-success-default` / `-error-` / `-warning-` / `-information-` | status 600 | static — same | Status outlines |
|
|
92
|
+
| `ring-focus-default` | blue-600 | blue-400 | Base focus ink; `ring-focus-strong` (50% mix) is the 3px control ring, `ring-focus-soft` (8%) the text-field ring |
|
|
93
|
+
| `ring-error-default` / `ring-error-soft` | red-600 / 20% mix | same | Invalid-field ring |
|
|
94
|
+
| `ring-brand-soft` | brand 40% mix | flips with brand | Sidebar icon-rail hover/active ring |
|
|
95
|
+
|
|
96
|
+
## Construction recipes (the Figma fill-stack model)
|
|
97
|
+
|
|
98
|
+
Several values are **recipes, not scale steps** — a solid base with a fixed layer on top:
|
|
99
|
+
|
|
100
|
+
- **State layers**: hover = black 16% / pressed = black 32% mixed over the brand fill (`color-mix`), so any brand colour derives its own states.
|
|
101
|
+
- **Dark surfaces**: white 6% / 12% mixed over the dark canvas — solid results, no translucency.
|
|
102
|
+
- **Dark status tints**: status-600 at 24% mixed over the canvas — solid.
|
|
103
|
+
- **Brand container**: a real two-layer paint (white 92% glaze over blue-600) — consume via `background: var(--background-brand-container)`.
|
|
104
|
+
- **On-brand sunken fields** (Input/InputGroup `variant="on-brand"`): `black-alpha-32` glaze fill + `white-alpha-24` border over whatever brand colour the consumer painted.
|
|
105
|
+
- **Glass** (Button/NavigationButton `glass`, Card `glass`): `white-alpha-24` fill + border + `backdrop-blur-md`; hover 32, press 16. Theme-independent.
|
|
106
|
+
|
|
107
|
+
## Deprecated aliases
|
|
108
|
+
|
|
109
|
+
A block of old names (`--prominent`, `--on-subtle`, `--primary`, `--border-subtle`, `--ring`, `--tabs`, mono-alpha primitives, …) still resolves to the new tokens for external consumers. **Never use them in new code.** The entire block is deleted at v1.0.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Motion
|
|
2
|
+
|
|
3
|
+
Motion mirrors the colour architecture: **primitives → semantic roles → components**. Components write `duration-fast ease-standard` etc.; the `duration-*` utilities resolve through the semantic roles, so retuning a role retunes every consumer. Components never name a motion primitive directly. Roles never flip in dark.
|
|
4
|
+
|
|
5
|
+
## Layer 1 — Primitives
|
|
6
|
+
|
|
7
|
+
### Durations
|
|
8
|
+
|
|
9
|
+
| Primitive | Value | Notes |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `instant` | 50ms | Focus rings; press-release fades |
|
|
12
|
+
| `fast` | 100ms | Backs `--motion-hover` |
|
|
13
|
+
| `base` | 200ms | Backs `--motion-surface` / `--motion-structural` |
|
|
14
|
+
| `slow` | 300ms | Backs `--motion-progress` |
|
|
15
|
+
| `open` | 500ms | Ceremony only (ProfilePhoto ring) — overlays no longer use it |
|
|
16
|
+
| `blink` | 1000ms | OTP caret blink only |
|
|
17
|
+
|
|
18
|
+
### Easing
|
|
19
|
+
|
|
20
|
+
| Primitive | Curve | Character |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `standard` | `cubic-bezier(0.2, 0, 0, 1)` | The house move — quick start, soft landing |
|
|
23
|
+
| `enter` | `cubic-bezier(0, 0, 0.2, 1)` | Decelerates — things arriving |
|
|
24
|
+
| `exit` | `cubic-bezier(0.4, 0, 1, 1)` | Accelerates — things leaving |
|
|
25
|
+
| `linear` | `linear` | Constant speed — progress, sidebar width |
|
|
26
|
+
| `spring` | `cubic-bezier(0.3, 1.25, 0.4, 1)` | Liquid travel — slight overshoot, settles |
|
|
27
|
+
| `overshoot` | `cubic-bezier(0.2, 1.8, 0.4, 1)` | Pops & ticks — springs past, snaps back |
|
|
28
|
+
|
|
29
|
+
## Layer 2 — Semantic roles
|
|
30
|
+
|
|
31
|
+
| Role | Resolves to | Consumers |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `--motion-hover` | fast · standard | Every hover glaze & fill (backs the `duration-fast` utility) |
|
|
34
|
+
| `--motion-press` | **0ms in, fast out** | Pressed glazes — a pattern, not a utility: `active:duration-0` lands press the frame it happens; it fades on release |
|
|
35
|
+
| `--motion-surface` | base · enter/exit | Menus, popovers, tooltips (backs `duration-base`) |
|
|
36
|
+
| `--motion-overlay-open` | 320ms · enter | Sheet, Drawer, Dialog entering |
|
|
37
|
+
| `--motion-overlay-close` | 250ms · exit | …and leaving — exits are quicker |
|
|
38
|
+
| `--motion-structural` | base · linear | Sidebar collapse, accordion, layout changes |
|
|
39
|
+
| `--motion-progress` | slow · linear | Progress fill, timers (backs `duration-slow`) |
|
|
40
|
+
|
|
41
|
+
Sheet consumes the overlay role via arbitrary properties (`[animation-duration:var(--motion-overlay-open)]`) because tw-animate's `duration-*` coupling doesn't reach custom utility names.
|
|
42
|
+
|
|
43
|
+
## Layer 3 — Signature moves
|
|
44
|
+
|
|
45
|
+
The liquid selection language, named:
|
|
46
|
+
|
|
47
|
+
| Token | Value | Consumers |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `--motion-travel-lead` | 200ms · spring | Liquid indicator leading edge (Bottom Navigation; Tabs planned) |
|
|
50
|
+
| `--motion-travel-trail` | 380ms · spring | Trailing edge, dragged behind — the water-drop absorb |
|
|
51
|
+
| `--motion-pop` | 450ms · overshoot | Arriving icon pop; chip tick / success check (planned) |
|
|
52
|
+
| `--animate-button-shimmer` | 4s · linear · infinite | Button `shimmer` modifier |
|
|
53
|
+
| `--animate-skeleton-sweep` | 1.6s · standard · infinite | Skeleton directional loading sweep |
|
|
54
|
+
|
|
55
|
+
Bottom Navigation reads `--motion-travel-lead/trail` and `--primitive-ease-spring` at runtime via `getComputedStyle` (TS fallbacks mirror the token values).
|
|
56
|
+
|
|
57
|
+
## Rules
|
|
58
|
+
|
|
59
|
+
- Exits are faster than entries (250ms close vs 320ms open) — getting out of the way beats ceremony.
|
|
60
|
+
- Press feedback lands at 0ms (`active:duration-0`) and fades on release; a press that fades *in* reads as lag.
|
|
61
|
+
- Never write a raw duration (`duration-200`) or generic easing (`ease-in-out`) — always a named utility.
|
|
62
|
+
- Decorative loops (shimmer, skeleton sweep) are the only infinite animations; the Spinner is exempt from any future reduced-motion guard because it conveys state.
|
|
63
|
+
- To retune the system: edit the role in `src/styles.css` **and** `app/globals.css` (both files, always).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Shape & Layout
|
|
2
|
+
|
|
3
|
+
## The control size rail — 32 / 40 / 48
|
|
4
|
+
|
|
5
|
+
Every interactive control sits on one three-step height rail. A field and its action button always line up flush when they share a size.
|
|
6
|
+
|
|
7
|
+
| Size | Height | Text step | Used by |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `sm` | 32px (`h-8`) | 12 or 14 | Button sm, Input sm, Select sm, Native Select sm, Chip sm, Toggle sm, Switch sm (20px track), Input Group sm |
|
|
10
|
+
| `md` | 40px (`h-10`) — **default everywhere** | 14 or 16 | Button md, Input md, Select md, Chip md, Stepper md … |
|
|
11
|
+
| `lg` | 48px (`h-12`) | 16 or 18 | Button lg, Input lg, Select lg, Numpad keys, Bottom Navigation (60px pill with 48px touch rows) |
|
|
12
|
+
|
|
13
|
+
Icon buttons are squares of the same rail: `icon-sm` 32 · `icon-md` 40 · `icon-lg`/`icon` 48 (+ `icon-xs` 24 where density demands it). Icons track the height: 14px→sm, 16px→md, 20px→lg.
|
|
14
|
+
|
|
15
|
+
### Button padding rule
|
|
16
|
+
|
|
17
|
+
Horizontal padding = **height/2 − 4** (lg 20px · md 16px · sm 12px); content gap fixed at **8px**. Padding never changes when icons are hidden — the label column stays put.
|
|
18
|
+
|
|
19
|
+
## Radius system
|
|
20
|
+
|
|
21
|
+
Radius encodes the element's *class*, not taste:
|
|
22
|
+
|
|
23
|
+
| Element class | Radius | Examples |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Actions — buttons, chips, pills, toggle pills | `rounded-full` | Button, Chip, Pagination cells, Bottom Navigation, badges, glass circles |
|
|
26
|
+
| Icon buttons | circles (`rounded-full` on a square) | Navigation Button, Button icon sizes, Input Group icon buttons |
|
|
27
|
+
| Cards & panels | `rounded-2xl` = **18px** | Card, Dialog, Alert Dialog, Popover, Hover Card, Empty shell, Drawer's attached edge (`rounded-t-2xl`) |
|
|
28
|
+
| Menus & floating lists | `rounded-lg` = **10px** | Dropdown/Context/Menubar/Select panels, Menubar bar, Navigation Menu viewport |
|
|
29
|
+
| Rows inside menus/lists | `rounded-md` = **8px** | Menu items, Item rows, Tabs triggers, Sidebar rows, Numpad keys |
|
|
30
|
+
| Form fields | `rounded-xs` = **4px** — deliberate, unchanged | Input, Textarea, Native Select, Input Group frame, OTP slot end caps — the whole text-entry family |
|
|
31
|
+
| Checkboxes | `rounded-2xs` | Checkbox |
|
|
32
|
+
|
|
33
|
+
Side drawers and sheets stay square on their full-height edges; top/bottom drawers round only the detached edge.
|
|
34
|
+
|
|
35
|
+
## Spacing
|
|
36
|
+
|
|
37
|
+
- Base unit 4px (Tailwind scale). Component-internal gaps: 8px (button content), 12px (Field md/lg), 8px (Field sm), 4px (menu padding `p-1`, Numpad grid `gap-1`).
|
|
38
|
+
- Card/panel slots: 16px padding (`p-4`, was deliberately reduced from 24).
|
|
39
|
+
- Overlay panels: 24px padding (`p-6` on Dialog/Alert Dialog).
|
|
40
|
+
|
|
41
|
+
## Responsive layout grid
|
|
42
|
+
|
|
43
|
+
Semantic grid tokens flip at breakpoints (gutter/margin primitives: 16px sm, 36px md):
|
|
44
|
+
|
|
45
|
+
| Viewport | Columns | Gutter | Margin |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| < 320px | 1 | 0 | 16px |
|
|
48
|
+
| ≥ 320px | 4 | 16px | 16px |
|
|
49
|
+
| ≥ 600px | 8 | 36px | 16px |
|
|
50
|
+
| ≥ 1136px | 12 | 36px | 16px |
|
|
51
|
+
|
|
52
|
+
Tokens: `--semantic-layout-grid-columns`, `--semantic-layout-gutter`, `--semantic-layout-margin-inline`.
|
|
53
|
+
|
|
54
|
+
## Elevation
|
|
55
|
+
|
|
56
|
+
Inline components carry none — **no shadows** — every component's `shadow-*` has been removed. The one sanctioned exception is **floating mobile chrome** (Bottom Navigation): a soft ambient lift (`shadow-chrome-default`, `0 12px 30px`) separates chrome from the content scrolling beneath it, per the Refined Mobile file. Elevation reads through the hairline (`border-default-default`) and surface steps (canvas → primary surface → secondary surface). On dark this is why surfaces are solid white-mixes: the step between `#00001F`, `#0F0F2C`, `#1F1F3A` is the elevation.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Typography
|
|
2
|
+
|
|
3
|
+
One family — **Plus Jakarta Sans** (`--font-plus-jakarta-sans`, falls back to system sans). Utilities: `font-display` (headings/buttons), `font-body` (content), `font-sans`.
|
|
4
|
+
|
|
5
|
+
Typography is the **only** place the component token layer exists: every component's text is sized by **private `--<component>-*` tokens** that point at the primitive scales. Colour has no component tokens; type has nothing else.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Layer 1 Primitives --primitive-font-size-14, --primitive-line-height-20, --primitive-font-weight-medium
|
|
9
|
+
Layer 2 Foundation --type-h1-*, --type-body-md-* (the page-content scale, used via .h1/.body-md classes)
|
|
10
|
+
Layer 3 Component --button-font-size-md, --dialog-title-font-size (private per component, applied via .type-* classes)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Hard rules
|
|
14
|
+
|
|
15
|
+
1. **No bold (700) in components.** Semibold **600 is the maximum component weight** — Button, Chip, Badge, active states, everything. The 700/800 primitives exist only for the foundation heading scale (H1/H2, display).
|
|
16
|
+
2. **Every size traces to a primitive step.** No raw `text-[13px]`; no Tailwind `text-sm` in component code — components use their `.type-<component>-*` class.
|
|
17
|
+
3. **`.type-*` classes are plain CSS, not Tailwind utilities.** Two consequences:
|
|
18
|
+
- They **cannot be gated by variants** — `data-[state=active]:type-foo` silently does nothing. Pick the class in JSX, or write a CSS rule keyed on the data attribute.
|
|
19
|
+
- They **beat layered utilities** — `font-medium` next to a `.type-*` class loses. Weight changes need their own token + CSS rule (see Tabs/Sidebar active-semibold rules in `styles.css`).
|
|
20
|
+
|
|
21
|
+
## Primitive scales
|
|
22
|
+
|
|
23
|
+
- **Sizes (px)**: 10 · 12 · 14 · 16 · 18 · 20 · 22 · 24 · 26 · 30 · 32 · 36 · 40 · 56
|
|
24
|
+
- **Line heights (px)**: 16 · 20 · 24 · 28 · 32 · 36 · 40 · 48 · 64 — standard pairings: 12→16, 14→20, 16→24, 18→24, 20→28
|
|
25
|
+
- **Weights**: 400 regular · 500 medium · 600 semibold · 700 bold (foundation headings only) · 800 extrabold (display only)
|
|
26
|
+
- **Tracking**: tightest −0.03em (display) → normal 0 (h5/body) → widest +0.12em (overline)
|
|
27
|
+
|
|
28
|
+
## Foundation scale (page content — classes `.display`, `.h1` … `.h6`, `.body-lg/md/sm`, `.label-text`, `.caption`, `.overline`)
|
|
29
|
+
|
|
30
|
+
| Style | Desktop | Mobile | Weight | Tracking |
|
|
31
|
+
|---|---|---|---|---|
|
|
32
|
+
| display | 56/64 | 36/40 | 800 | −0.03em |
|
|
33
|
+
| h1 | 40/48 | 30/36 | 700 | −0.025em |
|
|
34
|
+
| h2 | 32/40 | 26/32 | 700 | −0.02em |
|
|
35
|
+
| h3 | 24/32 | 22/28 | 600 | −0.015em |
|
|
36
|
+
| h4 | 20/28 | — | 600 | −0.01em |
|
|
37
|
+
| h5 | 18/24 | — | 600 | 0 |
|
|
38
|
+
| h6 | 16/24 | — | 600 | 0 |
|
|
39
|
+
| body-lg | 18/28 | — | 400 | — |
|
|
40
|
+
| body-md | 16/24 | — | 400 | — |
|
|
41
|
+
| body-sm | 14/20 | — | 400 | — |
|
|
42
|
+
| label | 14/20 | — | 500 | 0 |
|
|
43
|
+
| caption | 12/16 | — | 500 | +0.005em |
|
|
44
|
+
| overline | 12/16 | — | 600 | +0.12em |
|
|
45
|
+
|
|
46
|
+
Display/h1/h2/h3 are fluid — desktop and mobile values clamp responsively.
|
|
47
|
+
|
|
48
|
+
## The component token pattern
|
|
49
|
+
|
|
50
|
+
Each component declares its own type tokens in `src/styles.css` (mirrored in `app/globals.css`) and a `.type-<component>[-part][-size]` class per text role. Example (Dialog):
|
|
51
|
+
|
|
52
|
+
```css
|
|
53
|
+
--dialog-title-font-size: var(--primitive-font-size-18);
|
|
54
|
+
--dialog-title-font-weight: var(--primitive-font-weight-semibold);
|
|
55
|
+
--dialog-title-line-height: var(--primitive-line-height-24);
|
|
56
|
+
|
|
57
|
+
.type-dialog-title {
|
|
58
|
+
font-size: var(--dialog-title-font-size);
|
|
59
|
+
line-height: var(--dialog-title-line-height);
|
|
60
|
+
font-weight: var(--dialog-title-font-weight);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Recurring ladders:
|
|
65
|
+
|
|
66
|
+
- **Control sizes** ride one primitive step per size: sm 12/16 · md 14/20 · lg 16/24 (Input, Select trigger, Native Select, Field labels, Tabs triggers, Link).
|
|
67
|
+
- **Buttons**: lg 18/24 · md 16/24 · sm 14/20, all **semibold**. Chips: md 14/20 · sm 12/16, semibold.
|
|
68
|
+
- **Overlay titles** (Dialog, Alert Dialog, Drawer, Sheet, Empty): 18/semibold/24; their descriptions 14/regular/20.
|
|
69
|
+
- **Small labels** (Badge sm, Kbd-like chips, Sidebar labels, Bottom Navigation, menu headings): 12/16, medium.
|
|
70
|
+
- **Helper text** (Field description/error): fixed **12/16 at every field size** — help text never scales with the control.
|
|
71
|
+
|
|
72
|
+
## The one shared family: `--menu-*`
|
|
73
|
+
|
|
74
|
+
Menu-like components deliberately share one type family instead of private ones (user-approved exception), so all menus stay in lockstep:
|
|
75
|
+
|
|
76
|
+
| Token | Value | Used for |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `--menu-item-*` | 14/regular/20 | Rows in Command, Context Menu, Dropdown Menu, Menubar, Select, Navigation Menu links |
|
|
79
|
+
| `--menu-heading-*` | 12/medium/16 | Group labels in all of the above |
|
|
80
|
+
| `--menu-trigger-*` | 14/medium/20 | Menubar and Navigation Menu triggers |
|
|
81
|
+
| `--menu-shortcut-*` | 12/regular/16 | Shortcut hints in menu rows |
|
|
82
|
+
|
|
83
|
+
Classes: `.type-menu-item`, `.type-menu-heading`, `.type-menu-trigger`, `.type-menu-shortcut`.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Pattern — Forms
|
|
2
|
+
|
|
3
|
+
How form controls compose in Champion Trade: one wrapper (Field) drives sizing, labelling, and validation for every control inside it.
|
|
4
|
+
|
|
5
|
+
## The Field cascade
|
|
6
|
+
|
|
7
|
+
`Field` carries a `size` (`sm | md | lg`) and provides it through context. **Input, Input Group, and Native Select default their own size to the surrounding Field's** — one prop sizes the whole frame. An explicit size on the control always wins.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Field, FieldLabel, FieldContent, FieldDescription, Input } from "@trading-game/design-intelligence-layer"
|
|
11
|
+
|
|
12
|
+
<Field size="sm">
|
|
13
|
+
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
14
|
+
<FieldContent>
|
|
15
|
+
<Input id="email" placeholder="you@example.com" /> {/* renders 32px automatically */}
|
|
16
|
+
<FieldDescription>Work email preferred.</FieldDescription>
|
|
17
|
+
</FieldContent>
|
|
18
|
+
</Field>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The full ladder:
|
|
22
|
+
|
|
23
|
+
| Field size | Label | Control height | Helper text | Gap |
|
|
24
|
+
|---|---|---|---|---|
|
|
25
|
+
| `sm` | 12/16 medium | 32px | **12/16** | 8px |
|
|
26
|
+
| `md` (default) | 14/20 medium | 40px | **12/16** | 12px |
|
|
27
|
+
| `lg` | 16/24 medium | 48px | **12/16** | 12px |
|
|
28
|
+
|
|
29
|
+
Helper text (description, error, separator chip) is fixed at 12/16 at every size — help text never scales with the control.
|
|
30
|
+
|
|
31
|
+
## Validation & error ink
|
|
32
|
+
|
|
33
|
+
Mark the Field invalid and set `aria-invalid` on the control:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<Field data-invalid="true">
|
|
37
|
+
<FieldLabel htmlFor="e">Email</FieldLabel>
|
|
38
|
+
<FieldContent>
|
|
39
|
+
<Input id="e" aria-invalid />
|
|
40
|
+
<FieldError>Please enter a valid email address.</FieldError>
|
|
41
|
+
</FieldContent>
|
|
42
|
+
</Field>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- `data-invalid="true"` on Field sets `text-text-error-default` on the root; **the label turns red through inheritance** (Label deliberately sets no colour of its own).
|
|
46
|
+
- `aria-invalid` on the control swaps its border/ring to `border-error-default` / `ring-error-soft`.
|
|
47
|
+
- `FieldError` renders in error ink at the helper ladder (12/16). Error red is red-600 `#FE2925`, static in both themes.
|
|
48
|
+
|
|
49
|
+
## The input family conventions
|
|
50
|
+
|
|
51
|
+
All text-entry controls share one construction: **hairline border (`border-default-default`) + solid `background-primary-surface` + prominent ink + subtle placeholder + brand selection + soft focus ring** (`focus-visible:border-ring-focus-default ring-[3px] ring-ring-focus-soft`), no shadow, radius `rounded-xs` (4px — deliberate).
|
|
52
|
+
|
|
53
|
+
| Control | Notes |
|
|
54
|
+
|---|---|
|
|
55
|
+
| [Input](../components/input.md) | sm/md/lg; `variant="on-brand"` for brand surfaces |
|
|
56
|
+
| [Textarea](../components/textarea.md) | md type (14/20), auto-sizing content |
|
|
57
|
+
| [Input Group](../components/input-group.md) | buttons/prefixes *inside* the field; addons inherit the group's type ladder |
|
|
58
|
+
| [Native Select](../components/native-select.md) | OS-native option list, same rail |
|
|
59
|
+
| [Select](../components/select.md) | custom popup (opens below the trigger) |
|
|
60
|
+
| [Input OTP](../components/input-otp.md) | segmented code entry |
|
|
61
|
+
| [Stepper](../components/stepper.md) | numeric +/− built on Input Group |
|
|
62
|
+
| [Numpad](../components/numpad.md) | mobile amount entry — pairs with a display figure, not an input |
|
|
63
|
+
|
|
64
|
+
## Choice cards
|
|
65
|
+
|
|
66
|
+
A `FieldLabel` wrapping a nested `Field` becomes a selectable card. Checked state uses the selection tokens: `border-border-brand-selected` + `bg-background-brand-selected` (10% tint light / solid `#121A55` dark).
|
|
67
|
+
|
|
68
|
+
## Buttons in forms
|
|
69
|
+
|
|
70
|
+
A Button and a field of the same size align flush (32/40/48 rail). Button text is one primitive step larger than the field's text at every size (14/16/18 vs 12/14/16) — the action stays the loudest element in the row.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Pattern — Menus
|
|
2
|
+
|
|
3
|
+
Five components render menu rows, and they share **one deliberate type family** (`--menu-*`) plus one visual recipe, so every menu in the product looks identical: [Command](../components/command.md), [Context Menu](../components/context-menu.md), [Dropdown Menu](../components/dropdown-menu.md), [Menubar](../components/menubar.md), and [Select](../components/select.md)'s rows ([Navigation Menu](../components/navigation-menu.md) links ride the same family).
|
|
4
|
+
|
|
5
|
+
## The panel shell
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
rounded-lg (10px) · border-border-default-default · bg-background-primary-surface
|
|
9
|
+
p-1 · text-text-prominent-default · NO shadow
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Panels are one radius step below cards (10px vs 18px) — that difference is what visually separates "menu" from "card".
|
|
13
|
+
|
|
14
|
+
## The row recipe
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
type-menu-item (14/regular/20) · rounded-md (8px) · px-2 py-1.5
|
|
18
|
+
focus/hover → bg-background-hover-default + text-text-prominent-default
|
|
19
|
+
icons → text-icon-subtle-default, size-4
|
|
20
|
+
disabled → opacity-24
|
|
21
|
+
destructive rows → text-text-error-default + focus:bg-background-error-default
|
|
22
|
+
checked/selected ink → text-text-brand-selected
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## The shared type family
|
|
26
|
+
|
|
27
|
+
| Token | Value | Role |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `--menu-item-*` | 14/regular/20 | Rows (items, checkbox/radio items, sub-triggers, nav links) |
|
|
30
|
+
| `--menu-heading-*` | 12/medium/16 | Group labels (`SelectLabel`, `DropdownMenuLabel`, …) |
|
|
31
|
+
| `--menu-trigger-*` | 14/medium/20 | Bar triggers (Menubar, Navigation Menu) |
|
|
32
|
+
| `--menu-shortcut-*` | 12/regular/16 | Shortcut hints, right-aligned, subtle ink |
|
|
33
|
+
|
|
34
|
+
Classes: `.type-menu-item`, `.type-menu-heading`, `.type-menu-trigger`, `.type-menu-shortcut`. This is the **only** shared component type family in the system — everywhere else, type tokens are private per component. Extending the family (a new slot) is allowed; pointing non-menu components at it is not.
|
|
35
|
+
|
|
36
|
+
## Positioning conventions
|
|
37
|
+
|
|
38
|
+
- Everything opens **below/beside its trigger** and stays put. Select's Radix "item-aligned" mode (list slides to overlap the trigger) was deliberately replaced with `position="popper"` as the default.
|
|
39
|
+
- Open-state triggers: hover glaze background; Menubar's open trigger ink goes `text-text-brand-selected`.
|
|
40
|
+
|
|
41
|
+
## Which menu component when
|
|
42
|
+
|
|
43
|
+
| Need | Use |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Pick one value into a form | Select (or Native Select for OS-native) |
|
|
46
|
+
| Actions on click of a button | Dropdown Menu |
|
|
47
|
+
| Actions on right-click | Context Menu |
|
|
48
|
+
| Searchable command palette | Command |
|
|
49
|
+
| App-window menu bar | Menubar |
|
|
50
|
+
| Site navigation with panels | Navigation Menu |
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Pattern — On-brand surfaces & glass
|
|
2
|
+
|
|
3
|
+
Components standing on a **brand-coloured or art surface** (hero gradients, banners, game panels) can't use theme tokens — the surface is the same in both themes, so they use the **on-brand family** (static whites and glazes) or the **glass recipe**. "On-brand" replaced the misleading old name "inverse": these are not theme inverses, they are styles for fixed-colour panels.
|
|
4
|
+
|
|
5
|
+
## The on-brand variant family
|
|
6
|
+
|
|
7
|
+
| Component | Variant | Recipe |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| [Button](../components/button.md) | `primary-on-brand` | `background-static-white` pill + `text-brand-default` ink |
|
|
10
|
+
| Button | `secondary-on-brand` | transparent + `border-background-static-white` + `text-on-brand-static`; hover `white-alpha-8` |
|
|
11
|
+
| Button | `tertiary-on-brand` | transparent + `text-on-brand-static`; hover `white-alpha-8` |
|
|
12
|
+
| [Input](../components/input.md) / [Input Group](../components/input-group.md) | `variant="on-brand"` | sunken field: `black-alpha-32` glaze fill + `white-alpha-24` border + static white ink; placeholder `white-alpha-50`; focus = white border + `white-alpha-40` ring |
|
|
13
|
+
| [Link](../components/link.md) | `tone="on-brand"` | `text-on-brand-static`; hover `white-alpha-80` |
|
|
14
|
+
|
|
15
|
+
All values are **static** — the brand panel doesn't flip with the theme, so neither do the elements on it. The old `*-inverse` names remain as deprecated aliases until v1.0.
|
|
16
|
+
|
|
17
|
+
## The glass recipe
|
|
18
|
+
|
|
19
|
+
Frosted elements for art/hero surfaces — theme-independent by nature:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
bg-(--primitive-white-alpha-24) + border-(--primitive-white-alpha-24) + backdrop-blur-md
|
|
23
|
+
text-text-on-brand-static
|
|
24
|
+
hover → white-alpha-32 (frost thickens) · press → white-alpha-16 (frost thins)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Component | Usage |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Button `variant="frosted-on-brand"` | Frosted pill CTA on art; composes with icon sizes for frosted icon circles. Its page-surface counterpart is `variant="frosted"` (brand-selected tint) |
|
|
30
|
+
| [Navigation Button](../components/navigation-button.md) `variant="frosted-on-brand"` | The floating back/close circles over hero gradients |
|
|
31
|
+
| [Card](../components/card.md) `variant="frosted-on-brand"` | Frosted panel: `white-alpha-80` fill + `white-alpha-64` border + `backdrop-blur-xl` + static black ink |
|
|
32
|
+
|
|
33
|
+
Glass lives **only on coloured/art surfaces** — never on the plain page canvas.
|
|
34
|
+
|
|
35
|
+
## The Banner fade (the fill-stack construction)
|
|
36
|
+
|
|
37
|
+
[Banner](../components/banner.md) takes any CSS colour and darkens it with a **fixed** black fade layered on top — `linear-gradient(120deg, rgb(0 0 0 / 0%), rgb(0 0 0 / 50%))` over the solid base. The fade never depends on the colour, so any custom panel colour works with zero tuning. This "solid base + fixed alpha layer" stack is the system's standard construction (same idea as dark surfaces, state layers, and the on-brand sunken field).
|
|
38
|
+
|
|
39
|
+
## Rules
|
|
40
|
+
|
|
41
|
+
- Never put theme tokens (`text-text-prominent-default`, `bg-background-primary-surface`, …) on a fixed-colour panel — they flip in dark and break.
|
|
42
|
+
- Never use glass on the page canvas or inside plain cards.
|
|
43
|
+
- The white CTA on a brand panel is `background-static-white` + `text-static-black`/`text-brand-default` — not `bg-white`.
|