@trading-game/design-intelligence-layer 1.0.7 → 1.1.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.
@@ -21,7 +21,7 @@ Non-interactive status pill — emphasis (solid or tint) crossed with meaning (b
21
21
  | Prop | Type | Default | Notes |
22
22
  | --- | --- | --- | --- |
23
23
  | `variant` | `"solid" \| "solid-demo" \| "solid-neutral" \| "solid-success" \| "solid-fail" \| "solid-warning" \| "tint" \| "tint-demo" \| "tint-neutral" \| "tint-success" \| "tint-fail" \| "tint-warning"` | `"solid"` | Emphasis + meaning grid; brand is the unsuffixed default. |
24
- | `size` | `"xs" \| "sm" \| "md" \| "lg"` | `"md"` | Heights 20 / 24 / 32 / 40. |
24
+ | `size` | `"xs" \| "sm" \| "md" \| "lg" \| "count"` | `"md"` | Heights 20 / 24 / 32 / 40. |
25
25
  | `asChild` | `boolean` | `false` | Render onto a caller element. Badges are read-only markers — no hover states exist (interactive pills are Chips). |
26
26
 
27
27
  The `glass` variant was removed (breaking change); there is no replacement — use `tint` on imagery-free surfaces.
@@ -46,11 +46,29 @@ import { Badge } from "@trading-game/design-intelligence-layer"
46
46
  | `md` | 32px | `px-3 py-1` | 14/20 |
47
47
  | `lg` | 40px | `px-4 py-1.5` | 16/24 |
48
48
 
49
+ ### `count`
50
+
51
+ `size="count"` is a shape, not a step on the xs/sm/md/lg rail: an 18px disc whose minimum
52
+ width equals its height, so one digit is a circle and more digits grow it into a pill at the
53
+ same height. `tabular-nums` fixes the digit width, so a ticking count only resizes when the
54
+ number of digits changes, not on every update.
55
+
56
+ Conventions the caller owns, not the component: hide the badge at zero, and cap above 99 as
57
+ `99+`. Pinning it over other chrome needs a knockout ring in the colour of whatever is
58
+ behind it — Badge cannot know that, so the caller adds it:
59
+
60
+ ```tsx
61
+ <Badge aria-hidden size="count"
62
+ className="absolute -top-0.5 -right-0.5 ring-2 ring-background-primary-canvas">
63
+ {count > 99 ? "99+" : count}
64
+ </Badge>
65
+ ```
66
+
49
67
  ## Tokens
50
68
 
51
69
  **Colour (semantic):**
52
70
  - Solid: `background-brand-default` + `text-on-brand-static`; `background-demo-solid`; `background-inverse-surface` + `text-prominent-inverse`; `background-success-solid` / `background-error-solid` / `background-warning-solid`, all with `text-on-status-static`.
53
- - Tint: `--background-brand-container` (layered static recipe, painted via `[background:…]`) + `text-brand-container-static`; `background-demo-default` + `text-demo-default`; `background-secondary-surface` + `text-prominent-default`; `background-success/error/warning-default` + matching `text-*` status tokens. All tints are borderless — the tinted fill alone carries the meaning.
71
+ - Tint: `background-brand-selected` + `text-brand-selected` (the theme-aware selection family, same recipe as Calendar's range band); `background-demo-default` + `text-demo-default`; `background-secondary-surface` + `text-prominent-default`; `background-success/error/warning-default` + matching `text-*` status tokens. All tints are borderless — the tinted fill alone carries the meaning.
54
72
  - The `tint-demo` / `tint-success` / `tint-fail` / `tint-warning` fills are one family by construction: a light `-50` step in light, and the hue's base step at **24% over the canvas** in dark. `tint-demo` gained its dark override so it stops reading as a light chip beside Won/Lost/Pending; note ember's base step is 500, not 600, because it is a game accent rather than a status colour.
55
73
  - Focus: `ring-focus-strong`.
56
74
 
@@ -59,8 +77,7 @@ import { Badge } from "@trading-game/design-intelligence-layer"
59
77
  ## Behaviour
60
78
 
61
79
  - Status solids use the bright 600 fills with shared static white ink (`text-on-status-static`) — deliberately below AA on green and amber; a style call, not an oversight.
62
- - `tint` (brand) paints the layered `--background-brand-container` value via arbitrary `background`, so a `bg-*` utility cannot override it — replace the whole `[background:…]` if you must.
63
- - `tint` (brand) is the one tint that stays light in dark, because `--background-brand-container` is a deliberately static icy panel shared with Calendar's selection. It sits outside the status/demo tint family on purpose.
80
+ - `tint` (brand) flips with the theme like every other tint: `blue-600 @ 10%` over white in light, `blue-400 @ 24%` over the canvas in dark, with `blue-200` ink. It was the last variant on the static `--background-brand-container`; that token now has no consumers.
64
81
  - No hover states — badges are read-only markers; if it needs to be clickable, it's a [Chip](./chip.md).
65
82
  - `rounded-full`, `border-transparent` on every variant — no outlines, no shadow.
66
83
 
@@ -13,7 +13,7 @@ Generic list row — media, title/description, and actions in one flex shell, wi
13
13
 
14
14
  - `ItemGroup` — `role="list"` column; auto-divider mode on by default. `data-slot="item-group"`, `data-divider`.
15
15
  - `ItemSeparator` — manual hairline (`Separator`). `data-slot="item-separator"`.
16
- - `Item` — the row (`rounded-md`, transparent hairline border for state changes). `data-slot="item"`, `data-variant`, `data-size`.
16
+ - `Item` — the row (`rounded-2xl`, transparent hairline border for state changes). `data-slot="item"`, `data-variant`, `data-size`.
17
17
  - `ItemMedia` — leading icon/image slot; top-aligns itself when a description is present. `data-slot="item-media"`, `data-variant`.
18
18
  - `ItemContent` — flex-1 column for title + description. `data-slot="item-content"`.
19
19
  - `ItemTitle` — 14/medium/20 prominent ink. `data-slot="item-title"`.
@@ -27,13 +27,13 @@ Generic list row — media, title/description, and actions in one flex shell, wi
27
27
 
28
28
  | Prop | Type | Default | Notes |
29
29
  | --- | --- | --- | --- |
30
- | `divider` | `boolean` | `true` | Draws a 1px `border-default-default` line (inset `inset-x-4`) after each non-last **default-variant** Item, in a 4px gap. `outline`/`muted` items are skipped so borders don't double. Pass `false` for flush stacks or manual `ItemSeparator`. |
30
+ | `divider` | `boolean` | `true` | Draws a 1px `border-default-default` line (inset `inset-x-4`) after each non-last **default-variant** Item, in a 4px gap. `outline`/`fill`/`muted` items are skipped so borders don't double. Pass `false` for flush stacks or manual `ItemSeparator`. |
31
31
 
32
32
  ### Item
33
33
 
34
34
  | Prop | Type | Default | Notes |
35
35
  | --- | --- | --- | --- |
36
- | `variant` | `"default" \| "outline" \| "muted"` | `"default"` | default transparent; outline = hairline `border-border-default-default` + hover fill; muted = `bg-background-secondary-surface`. |
36
+ | `variant` | `"default" \| "outline" \| "fill" \| "muted"` | `"default"` | default transparent; outline = hairline `border-border-default-default` + hover glaze, no fill; **fill** = solid `bg-background-primary-surface` + the same hairline, hover steps to `background-secondary-surface`; muted = `bg-background-secondary-surface`, no border. |
37
37
  | `size` | `"default" \| "sm"` | `"default"` | default `gap-4 p-4`; sm `gap-2.5 px-4 py-3`. |
38
38
  | `asChild` | `boolean` | `false` | Render as link/button via Radix Slot; anchors get `hover:bg-background-hover-default`. |
39
39
 
@@ -73,11 +73,29 @@ import { BellIcon } from "lucide-react"
73
73
  </ItemGroup>
74
74
  ```
75
75
 
76
+ ### Picking between `outline` and `fill`
77
+
78
+ Both draw the same hairline. They differ only on a **non-white** background:
79
+
80
+ - On a Card, Sheet or Popover they are identical at rest — pick either.
81
+ - On `background-primary-canvas` (a game screen, a ledger page), `outline` lets the canvas
82
+ show through and the row reads as part of the page. `fill` gives the row its own solid
83
+ plane — the wallet-ledger pattern, and the right default for trade lists.
84
+
85
+ Every variant sits on `rounded-2xl` (18px) — the card radius Card, Dialog and Popover use.
86
+ An Item is a content shell, not a menu row, so it takes the card corner rather than the
87
+ tighter `rounded-md` it used previously.
88
+
89
+ `fill` hover steps `primary-surface` -> `secondary-surface` rather than using the shared
90
+ `background-hover-default` glaze, which is translucent and would let the canvas through a
91
+ filled row instead of darkening it. The same override is repeated for `[a]:` because the
92
+ base link hover uses that glaze and outranks a plain `hover:` on an anchor.
93
+
76
94
  ## Tokens
77
95
 
78
96
  **Colour (semantic):**
79
97
  - title `text-text-prominent-default`; description `text-text-subtle-default` (links underline, hover `text-text-brand-selected`)
80
- - outline: `border-border-default-default`, hover `bg-background-hover-default`; muted: `bg-background-secondary-surface`
98
+ - outline: `border-border-default-default`, hover `bg-background-hover-default`; fill: `bg-background-primary-surface` + `border-border-default-default`, hover `bg-background-secondary-surface`; muted: `bg-background-secondary-surface`
81
99
  - group divider: `bg-border-default-default`
82
100
  - focus: `border-ring-focus-default` + 3px `ring-ring-focus-strong` (control ring)
83
101
  - icon media: `bg-background-secondary-surface` + `text-icon-prominent-default`
@@ -96,10 +114,10 @@ import { BellIcon } from "lucide-react"
96
114
 
97
115
  **Do**
98
116
  - Let `ItemGroup` draw dividers; only reach for `ItemSeparator` with `divider={false}`.
99
- - Use `rounded-md` rows as-is — 8px is the row radius tier.
117
+ - Leave the radius alone — Item is `rounded-2xl` (18px), the card tier, not the 8px row tier.
100
118
  - Use `asChild` with a real `<a>`/`<button>` when the whole row navigates.
101
119
 
102
120
  **Don't**
103
- - Don't mix `variant="outline"` items into a dividered group expecting dividers — they are deliberately skipped.
121
+ - Don't mix `variant="outline"` or `variant="fill"` items into a dividered group expecting dividers — they are deliberately skipped.
104
122
  - Don't push the title past medium 500; row titles never reach semibold, and bold does not exist in components.
105
- - Don't add shadows to muted/outline rows; state is shown by fill and hairline only.
123
+ - Don't add shadows to muted/outline/fill rows; state is shown by fill and hairline only.
@@ -53,7 +53,7 @@ All scales run 50→1000 (mono 50→1200; blue adds 25 and 1000 as the two canva
53
53
  | `background-brand-hover` | black 16% over brand | (same recipe, re-derives) | Primary button hover — a state layer, not a scale step |
54
54
  | `background-brand-pressed` | black 32% over brand | (same recipe) | Primary button pressed |
55
55
  | `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 |
56
- | `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-*`** |
56
+ | `background-brand-container` | white 92% glaze over blue-600 (layered) | static — same | The icy brand panel. **No current consumers** — Calendar's range band and Badge `tint` both moved to `background-brand-selected` because a static value cannot carry dark. Retained for the v1.0 sweep. **Layered value — paint with `background:`, never `bg-*`** |
57
57
  | `background-inverse-surface` | mono-1100 `#1C1C1C` | mono-50 `#FFFFFF` | The flipped surface (tooltips, toasts) |
58
58
  | `background-overlay-default` | black-alpha-50 | black-alpha-64 | Modal scrims |
59
59
  | `background-success-default` | green-50 | green-600 24% over canvas | Status tint surface (Section Message) |
@@ -80,7 +80,7 @@ All scales run 50→1000 (mono 50→1200; blue adds 25 and 1000 as the two canva
80
80
  | `text-on-brand-static` | mono-50 | static | White text on brand fills — never flips |
81
81
  | `text-on-brand-subtle-static` | white-alpha-64 | static | Secondary copy on a brand fill. On brand the ladder **inverts** — it descends from white through the alpha rungs instead of climbing the mono ramp (3.8:1 on blue-600, 2.7:1 on blue-400) |
82
82
  | `text-on-brand-disabled-default` | white-alpha-32 | white-alpha-24 | Disabled ink on a brand fill — 1.8:1 over blue-600, 1.5:1 over blue-400. **Not static**, unlike the rest of the on-brand family: the fill itself flips blue-600 → blue-400, and blue-400 caps white at 4.3:1, so the rung comes down with it. Replaces the `opacity-24` wash on on-brand variants |
83
- | `text-brand-container-static` | blue-600 | static | Ink on `background-brand-container` |
83
+ | `text-brand-container-static` | blue-600 | static | Brand ink on a **static white** surface — Button `on-brand`, Toast's action button, Input selection. Still in use; do not retire with `background-brand-container` |
84
84
  | `text-static-black` | mono-1100 | static | Ink on `background-static-white` |
85
85
  | `text-success-default` / `-error-` / `-warning-` / `-information-` | status 600 | **static — same in both themes** (deliberate call) | Status text |
86
86
  | `text-on-status-static` | mono-50 | static | White on solid status fills (below AA on green/amber — deliberate style call) |
@@ -109,7 +109,7 @@ Several values are **recipes, not scale steps** — a solid base with a fixed la
109
109
  - **State layers**: hover = black 16% / pressed = black 32% mixed over the brand fill (`color-mix`), so any brand colour derives its own states.
110
110
  - **Dark surfaces**: white 6% / 12% mixed over the dark canvas — solid results, no translucency.
111
111
  - **Dark status tints**: status-600 at 24% mixed over the canvas — solid.
112
- - **Brand container**: a real two-layer paint (white 92% glaze over blue-600) — consume via `background: var(--background-brand-container)`.
112
+ - **Brand container**: a real two-layer paint (white 92% glaze over blue-600) — consume via `background: var(--background-brand-container)`. Nothing consumes it today: it is static by construction, so anything needing a brand tint that survives dark takes `background-brand-selected` instead.
113
113
  - **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.
114
114
  - **Glass** (Button/NavigationButton `glass`, Card `glass`): `white-alpha-24` fill + border + `backdrop-blur-md`; hover 32, press 16. Theme-independent.
115
115
 
@@ -24,9 +24,9 @@ Radius encodes the element's *class*, not taste:
24
24
  |---|---|---|
25
25
  | Actions — buttons, chips, pills, toggle pills | `rounded-full` | Button, Chip, Pagination cells, Bottom Navigation, badges, glass circles |
26
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`) |
27
+ | Cards & panels | `rounded-2xl` = **18px** | Card, Dialog, Alert Dialog, Popover, Hover Card, Empty shell, **Item** (every variant — a content shell, not a menu row), Drawer's attached edge (`rounded-t-2xl`) |
28
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 |
29
+ | Rows inside menus/lists | `rounded-md` = **8px** | Menu items, Tabs triggers, Sidebar rows, Numpad keys |
30
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
31
  | Checkboxes | `rounded-2xs` | Checkbox |
32
32
 
@@ -1,6 +1,6 @@
1
1
  # trading.game — Brand voice
2
2
 
3
- **Version:** 2.0 · April 2026
3
+ **Version:** 2.1 · September 2026
4
4
  **Adds:** Channel-specific voice application per persona mode; anti-AI writing rules for all copy
5
5
 
6
6
  ---
@@ -106,7 +106,7 @@ The tone is not the same for all three modes. Edge Seekers respond to validation
106
106
  Give them something to be early on. Frame the product as a read, not a game.
107
107
 
108
108
  > "Vol 100 has been oscillating inside a 12-point range for three sessions.
109
- > Rise & Fall is pricing that in. If you've been watching it, now's the time."
109
+ > Rise-Fall is pricing that in. If you've been watching it, now's the time."
110
110
 
111
111
  > "The fastest feedback loop on a market read.
112
112
  > Five ticks. Real price data. No waiting for a daily close."
@@ -163,7 +163,7 @@ Subject lines that work are different by mode:
163
163
 
164
164
  **Edge Seeker:**
165
165
  - "Your read on V_100 paid out."
166
- - "Rise & Fall is running on Vol 200. Your call."
166
+ - "Rise-Fall is running on Vol 200. Your call."
167
167
  - "The chart moved where you said it would."
168
168
 
169
169
  **System Runner:**
@@ -228,7 +228,7 @@ State the thing. Then the implication for the player. Don't lead with your feeli
228
228
 
229
229
  | ✅ | ❌ |
230
230
  |---|---|
231
- | "Rise & Fall now runs on V_200. Higher vol, wider payout range. Check the multiplier before you trade." | "We're excited to announce an exciting new instrument! Amazing opportunity for all traders!" |
231
+ | "Rise-Fall now runs on V_200. Higher vol, wider payout range. Check the multiplier before you trade." | "We're excited to announce an exciting new instrument! Amazing opportunity for all traders!" |
232
232
  | "Digits is live. Tick-based prediction on the last digit of a price feed. 1–10 ticks, six contract types." | "Introducing Digits, our innovative new game that revolutionises the prediction experience!" |
233
233
  | "Withdrawal processing is now instant. Not 'faster.' Instant." | "We've made some improvements to our withdrawal process for a more seamless experience." |
234
234
 
@@ -266,9 +266,9 @@ The other difference: HyperSwap assumes DeFi fluency. We can't. Some players hav
266
266
 
267
267
  | HyperSwap register | trading.game register |
268
268
  |---|---|
269
- | "SWAP Genesis is live. Early believers get rewarded 🔥" | "Rise & Fall is live on V_100. Edge is 2%. The rest is skill." |
269
+ | "SWAP Genesis is live. Early believers get rewarded 🔥" | "Rise-Fall is live on V_100. Edge is 2%. The rest is skill." |
270
270
  | "Swappies, the airdrop is coming 🚀" | "Your win rate this week: 54%. Baseline is 50%. You're ahead." |
271
- | "Earn boosted rewards for LPs 💰" | "Transferred 20 USDT to GridRush. Your capital, your call." |
271
+ | "Earn boosted rewards for LPs 💰" | "Transferred 20 USDT to Box-O. Your capital, your call." |
272
272
 
273
273
  ---
274
274
 
@@ -292,11 +292,10 @@ The other difference: HyperSwap assumes DeFi fluency. We can't. Some players hav
292
292
 
293
293
  | Correct | Not |
294
294
  |---|---|
295
- | Rise & Fall | Rise and Fall / Rise/Fall |
296
- | GridRush | Grid Rush / Grid rush |
297
- | Swipe Cards | Swipe-Cards / SwipeCards |
295
+ | Rise-Fall | Rise & Fall / Rise and Fall / Rise/Fall |
296
+ | Box-O | BoxO / Box O / GridRush |
297
+ | Swipe | Swipe Cards / Swipe-Cards / SwipeCards |
298
298
  | Digits | The Digits game |
299
- | Dice | The Dice game |
300
299
 
301
300
  ### Formatting
302
301
 
@@ -322,4 +321,4 @@ Before publishing anything:
322
321
 
323
322
  ---
324
323
 
325
- *trading.game Brand Voice — v2.0*
324
+ *trading.game Brand Voice — v2.1*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trading-game/design-intelligence-layer",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "description": "Trading Game Design System — shadcn/ui components with Tailwind CSS v4",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1,366 +0,0 @@
1
- ---
2
- description: Enforce correct usage of the @trading-game/design-intelligence-layer npm package. Apply whenever building UI in a project that has the package installed — before writing any component, layout, or styled element.
3
- globs: ["**/*.tsx", "**/*.jsx", "**/*.ts", "**/*.css"]
4
- alwaysApply: true
5
- ---
6
-
7
- # @trading-game/design-intelligence-layer — AI Agent Usage Rules
8
-
9
- ## Rule 1 — Check the package before writing any custom UI
10
-
11
- **BEFORE** writing any `<div>`, `<button>`, `<span>`, or other element styled with Tailwind classes, you MUST check if a component already exists in `@trading-game/design-intelligence-layer`.
12
-
13
- ### Available components (check this list first)
14
-
15
- Accordion, Alert, AlertDialog, AspectRatio, Avatar, AvatarGroup, Badge, **Banner**, Breadcrumb, Button, Calendar, Card, Carousel, Chart, Checkbox, **Chip**, Collapsible, Combobox, Command, ContextMenu, Dialog, Direction, Drawer, DropdownMenu, Empty, Field, Form, HoverCard, Input, InputGroup, InputOTP, Item, Kbd, Label, **Link**, Menubar, NativeSelect, NavigationButton, NavigationMenu, Pagination, Popover, Progress, RadioGroup, Resizable, ScrollArea, Select, Separator, Sheet, Sidebar, Skeleton, Slider, Spinner, **Stepper**, Switch, Table, Tabs, Textarea, **TicketCard / CreditTicketCard**, Toast/Toaster, **ResultSnackbar**, Toggle, ToggleGroup, Tooltip
16
-
17
- ### Decision flow
18
-
19
- ```
20
- Does a component exist in the list above?
21
- YES → Import it from @trading-game/design-intelligence-layer. Do NOT re-implement it.
22
- NO → STOP. Tell the user:
23
- "The [ComponentName] component does not exist in @trading-game/design-intelligence-layer.
24
- Options:
25
- (a) Build a custom one using design system tokens only
26
- (b) Use a different existing component
27
- (c) Skip this component"
28
- Wait for user to choose. Do NOT proceed without confirmation.
29
- ```
30
-
31
- ### Correct import pattern
32
-
33
- ```tsx
34
- import { Button, Card, Badge } from "@trading-game/design-intelligence-layer"
35
- ```
36
-
37
- ---
38
-
39
- ## Rule 2 — Token-only rule (applies to ALL code, including approved custom builds)
40
-
41
- ### Never use
42
-
43
- ```
44
- ❌ Hardcoded hex: #2323FF, #000000, rgba(0,0,0,0.5)
45
- ❌ Raw Tailwind palette: bg-gray-100, text-zinc-500, text-slate-400, bg-black, bg-white
46
- ❌ Arbitrary color: bg-[#EEEEEE], text-[rgba(35,35,255,0.1)]
47
- ❌ Raw CSS var: bg-[var(--border-subtle)] ← NEVER do this
48
- ❌ hsl(var()) syntax: hsl(var(--primary)) ← this is Tailwind v3, not v4
49
- ❌ Raw opacity on non-tokens: bg-black/50
50
- ```
51
-
52
- ### Always use semantic token classes
53
-
54
- #### Background tokens
55
- ```
56
- ✅ bg-prominent — page background (white #FFFFFF)
57
- ✅ bg-card — card/panel surface (white #FFFFFF)
58
- ✅ bg-popover — popover/dropdown surface (white #FFFFFF)
59
- ✅ bg-subtle — subtle tinted surface (#F5F5F5)
60
- ✅ bg-overlay — modal/dialog backdrop (black 50%) — ONLY for overlays
61
- ✅ bg-primary — brand blue #2323FF — CTAs and primary actions
62
- ✅ bg-primary-hover — darker blue #0B0BD2 — primary button hover
63
- ✅ bg-primary-inverse — white surface for use on dark/coloured areas
64
- ✅ bg-prominent-inverse — dark surface for use on light pages (toasts, etc.)
65
- ✅ bg-secondary-hover — light grey #EEEEEE — outline/secondary button hover
66
- ✅ bg-semantic-win — green — profit/positive
67
- ✅ bg-semantic-loss — red — loss/negative
68
- ✅ bg-semantic-warning — orange — warning/caution
69
- ✅ bg-semantic-boost — amber-orange #F9840F — boost/bonus badge (tinted: bg-semantic-boost/16)
70
- ```
71
-
72
- #### Text tokens — paired with a surface
73
-
74
- > Convention: `bg-<surface>` always has a paired `text-on-<surface>` foreground. Use them together for guaranteed legibility.
75
-
76
- ```
77
- ✅ text-on-prominent — paired with bg-prominent (black #000000) — default body text
78
- ✅ text-on-prominent-inverse — paired with bg-prominent-inverse (white) — toast text, etc.
79
- ✅ text-on-primary — paired with bg-primary (white) — primary button label, tooltip text
80
- ✅ text-on-primary-inverse — paired with bg-primary-inverse (blue) — primary-inverse button label
81
- ✅ text-on-semantic-win — paired with bg-semantic-win (white)
82
- ✅ text-on-semantic-loss — paired with bg-semantic-loss (white)
83
- ✅ text-on-semantic-warning — paired with bg-semantic-warning (white)
84
- ✅ text-on-semantic-boost — paired with bg-semantic-boost (dark amber #713813)
85
- ✅ text-on-subtle — secondary text (aliases text-subtle-default: mono-800 #717171 light / mono-600 #AAAAAA dark)
86
- ✅ text-on-disabled — disabled / inactive text (aliases text-disabled-default: mono-400 #C6C6C6 light / mono-900 #555555 dark)
87
-
88
- ✅ text-primary — brand blue #2323FF — inline brand text over neutral surfaces
89
- ✅ text-semantic-win — green — profit/positive inline text
90
- ✅ text-semantic-loss — red — loss/negative inline text
91
- ```
92
-
93
- > ⚠️ **Deprecated:** `text-on-prominent-static-inverse` still works (aliased to `--on-prominent-inverse`) but new code should pick the explicit `text-on-<surface>` for whatever background sits behind the text.
94
-
95
- #### Border tokens
96
- ```
97
- ✅ border-border-subtle — light grey #EEEEEE — default UI borders, dividers, cards
98
- ✅ border-border-prominent — pure black #000000 — outline variant components
99
- ✅ border-border — @deprecated alias for border-subtle (still works, prefer border-border-subtle)
100
- ✅ border-input — input field borders (same value as border-subtle)
101
- ✅ ring-ring — focus ring (blue #2323FF)
102
- ```
103
-
104
- #### Opacity on a token is fine
105
- ```
106
- ✅ bg-primary/20, border-border-subtle/50, ring-ring/10
107
- ```
108
-
109
- ---
110
-
111
- ## Rule 3 — Layout utilities are exempt
112
-
113
- Structural Tailwind utilities are freely usable without token rules:
114
-
115
- ```
116
- ✅ flex, grid, gap-4, p-6, m-2, w-full, h-screen, max-w-lg
117
- ✅ z-50, overflow-hidden, opacity-50, transition-all
118
- ✅ col-span-2, items-center, justify-between
119
- ```
120
-
121
- Token rules apply **only** to: color (bg, text, border, ring, shadow color), border radius, and font family.
122
-
123
- ---
124
-
125
- ## Rule 3.5 — Typography: use the type scale, never hand-written sizes
126
-
127
- **MANDATORY decision procedure for ANY text you render.** Do not choose font sizes. Every piece of text gets its size, line-height, and weight from one of the 13 scale classes below — this is what keeps every screen's hierarchy identical.
128
-
129
- ### Step 1 — Is there a component for it?
130
-
131
- If the text lives in a component slot, USE THE COMPONENT — every component carries its own typography internally (via private component tokens or a built-in scale class):
132
- `DialogTitle` / `AlertDialogTitle` / `SheetTitle` / `DrawerTitle` / `EmptyTitle`, `CardTitle`, every `*Description` slot, `Label` / `FieldLabel` / `FieldTitle` / `ItemTitle`, `AccordionTrigger` / `AccordionContent`, `Button` — never hand-style any component's text.
133
-
134
- ### Step 2 — Freeform text: pick from the scale
135
-
136
- | Class | Size / line | Weight | Use for |
137
- | ------------ | ----------- | ------------- | ------------------------------------------------------ |
138
- | `display` | 36→56 fluid | ExtraBold 800 | Hero statement on a landing/marketing page |
139
- | `h1` | 30→40 fluid | Bold 700 | The single main title of a page/screen (ONE per page) |
140
- | `h2` | 26→32 fluid | Bold 700 | A major section heading |
141
- | `h3` | 22→24 fluid | SemiBold 600 | A sub-group heading inside a section |
142
- | `h4` | 20 / 28 | SemiBold 600 | A dashboard panel / large feature-card header |
143
- | `h5` | 18 / 24 | SemiBold 600 | Dialog, sheet, and drawer titles (freeform) |
144
- | `h6` | 16 / 24 | SemiBold 600 | A card / compact panel title (freeform) |
145
- | `body-lg` | 18 / 28 | Regular 400 | Intro / lead paragraph |
146
- | `body-md` | 16 / 24 | Regular 400 | Default body text |
147
- | `body-sm` | 14 / 20 | Regular 400 | Description under a title, helper copy |
148
- | `label-text` | 14 / 20 | Medium 500 | Form label, list-item title, table header (freeform) |
149
- | `caption` | 12 / 16 | Medium 500 | Metadata, timestamps, fine print |
150
- | `overline` | 12 / 16 | SemiBold 600 | Eyebrow above a heading, tag (uppercases itself) |
151
-
152
- **`h1`–`h6` are STYLE names, not HTML tags.** Put the class on whatever element the document outline needs — `<h2 className="h4">` is correct and normal when heading level and visual size legitimately differ. Never let the class name pick the tag for you.
153
-
154
- ```tsx
155
- ✅ <h1 className="h1 text-on-prominent">Account settings</h1>
156
- ✅ <h2 className="h2 text-on-prominent">Security</h2>
157
- ✅ <p className="body-sm text-on-subtle">Manage your sign-in methods.</p>
158
- ✅ <span className="overline text-primary">New</span> {/* uppercases itself */}
159
- ```
160
-
161
- ### Hard rules
162
- ```
163
- ❌ NEVER hand-build titles/body from utilities (text-2xl font-bold tracking-tight, text-[13px]…)
164
- ❌ NEVER uppercase a heading — the system is sentence-case; only `overline` uppercases (automatically)
165
- ❌ NEVER add responsive size overrides (md:text-*, sm:text-*) to a scale class — display and
166
- h1–h3 already scale desktop→mobile via their tokens. A class NEVER changes between
167
- breakpoints: h2 on desktop is h2 on mobile.
168
- ❌ NEVER use more than one h1 per page; never skip more than one hierarchy level
169
- ❌ NEVER tweak a scale class with utilities (h5 font-medium does nothing — scale classes are
170
- unlayered CSS and beat utilities). Need something different? Use a different class.
171
- ✅ Emphasis inside body text: <b>/<span> + font-semibold on that span — never a bigger size
172
- ✅ Numeric data displays (P&L, balances) are the accepted exception: bespoke size + tabular-nums
173
- ✅ A SMALLER class passed via className overrides a component's built-in one (stylesheet is
174
- ordered largest→smallest), e.g. <EmptyTitle className="h6"> for compact contexts
175
- ```
176
-
177
- > Naming notes: `overline` is also a Tailwind text-decoration utility — the package's class wins and clears the line. The label class is `label-text` (not `label`) to avoid confusion with the `Label` component.
178
-
179
- ---
180
-
181
- ## Rule 3.6 — Result Snackbar is for a settled game result, and nothing else
182
-
183
- `ResultSnackbar` exists for exactly one thing: reporting a **settled contract the player was not watching** — a win or a loss, with its figure.
184
-
185
- ```
186
- Did a contract just settle, win or loss, off-screen?
187
- YES → ResultSnackbar
188
- NO → toast(...) // everything else
189
- ```
190
-
191
- Use `toast(...)` — never `ResultSnackbar` — for:
192
-
193
- - **Refunds.** Nothing was won or lost, so there is no figure to report. A refund is a non-event.
194
- - **Trade rejections, live payout warnings, connection problems**, and every other notification.
195
- - Anything that needs an **action** from the player. `ResultSnackbar` is pointer-transparent by design: it is a report, not a control.
196
-
197
- And do not use it for the contract the player **was** watching as it ended — that gets the full-screen result moment. `ResultSnackbar` deliberately does not take the board.
198
-
199
- It is never the surface of record either. Every result also lands in positions and history, which is where a player goes to study terms.
200
-
201
- **Why the two look nothing alike:** that difference is intentional, not drift. `toast` is transient system messaging on the inverse surface. `ResultSnackbar` reports money: floating-chrome glass, an illustration, a signed figure in outcome ink, and a visible dwell timer. Do not "unify" them.
202
-
203
- ```tsx
204
- import { ResultSnackbar } from "@trading-game/design-intelligence-layer"
205
-
206
- <ResultSnackbar
207
- outcome="win" // "win" | "loss" — the only two states
208
- amount="+8.40" // pre-signed; use a true Unicode minus for losses
209
- amountValue={8.4} // optional count-up target
210
- currency="USDT"
211
- contractLabel="Rise"
212
- duration="15 seconds"
213
- icon={<img src="/thumbs-up.webp" alt="" aria-hidden className="size-8" />}
214
- onDismiss={advanceQueue} // fires once, after the exit finishes
215
- className="absolute top-3 right-3" // placement is yours; anchor it to something meaningful
216
- />
217
- ```
218
-
219
- Placement and queueing are the consumer's: the meaningful anchor differs per game, and deciding what waits, what is stale, and what replays after an interruption is product logic. The component shows one settlement and reports when it is done.
220
-
221
- ---
222
-
223
- ## Rule 4 — Do NOT install or configure these separately
224
-
225
- ```
226
- ❌ Do NOT install lucide-react — it is already bundled in the package
227
- ❌ Do NOT install tailwindcss separately — the package ships its own Tailwind v4 setup
228
- ❌ Do NOT add a tailwind.config.js — configuration is handled by the package
229
- ❌ Do NOT use @apply with hsl(var(--token)) — Tailwind v4 uses CSS variables directly
230
- ❌ Do NOT override font-family manually in CSS — use font-display and font-body utility classes
231
- ```
232
-
233
- ### Correct CSS setup in the consuming project
234
-
235
- ```css
236
- @import "@trading-game/design-intelligence-layer/styles";
237
- @import "tailwindcss";
238
- @source "../node_modules/@trading-game/design-intelligence-layer/dist";
239
- ```
240
-
241
- ### Vite projects — required plugin
242
-
243
- If the project uses Vite, `@tailwindcss/vite` MUST be in `vite.config.js`:
244
-
245
- ```js
246
- import tailwindcss from '@tailwindcss/vite'
247
- // plugins: [react(), tailwindcss()]
248
- ```
249
-
250
- Without this, Tailwind CSS will not process any styles. Next.js projects do NOT need this.
251
-
252
- ---
253
-
254
- ## Rule 5 — Button and Badge variants
255
-
256
- ### Button variants
257
- ```tsx
258
- // Light surfaces
259
- <Button variant="primary" /> // Blue filled — main CTA
260
- <Button variant="secondary" /> // Black outline — secondary actions
261
- <Button variant="tertiary" /> // Text only — minimal
262
-
263
- // Dark / coloured surfaces
264
- <Button variant="primary-inverse" /> // White filled + blue text — main CTA on dark bg
265
- <Button variant="secondary-inverse" /> // White outline + white text — secondary on dark bg
266
- ```
267
-
268
- All variants support icon sizes — use `size="icon-lg|icon-md|icon-sm|icon-xs"` for icon-only buttons on any variant:
269
- ```tsx
270
- <Button variant="primary-inverse" size="icon-md"><Bell /></Button>
271
- <Button variant="secondary-inverse" size="icon-sm"><X /></Button>
272
- ```
273
-
274
- ### Badge variants
275
- ```tsx
276
- // Solid
277
- <Badge variant="default" /> // Blue solid
278
- <Badge variant="standard" /> // Subtle grey solid
279
- <Badge variant="default-success" /> // Green solid
280
- <Badge variant="default-fail" /> // Red solid
281
- <Badge variant="default-warning" /> // Orange solid
282
-
283
- // Tint
284
- <Badge variant="fill" /> // Blue tint bg
285
- <Badge variant="fill-success" /> // Green tint bg
286
- <Badge variant="fill-fail" /> // Red tint bg
287
- <Badge variant="fill-warning" /> // Orange tint bg
288
- <Badge variant="fill-credit" /> // Deep peach (#FDCA8A) + bold amber text — credit/boost (Welcome credit, bonus)
289
- <Badge variant="fill-demo" /> // Peach + red-orange text — demo / test-mode indicator
290
-
291
- // Outline (black border, hover grey)
292
- <Badge variant="outline" />
293
-
294
- // Ghost (transparent bg)
295
- <Badge variant="ghost" />
296
- <Badge variant="ghost-success" />
297
- <Badge variant="ghost-fail" />
298
- <Badge variant="ghost-warning" />
299
- ```
300
-
301
- ---
302
-
303
- ## Rule 6 — If in doubt, stop and ask
304
-
305
- If you cannot find a token for a value you need, do NOT fall back to a hardcoded value.
306
-
307
- Stop and tell the user:
308
- ```
309
- "I need [value] for [element]. No design token exists for this.
310
- Should I: (a) use a hardcoded value, or (b) skip this styling?"
311
- ```
312
-
313
- Wait for confirmation before proceeding.
314
-
315
- ---
316
-
317
- ## Rule 8 — Blocks are first-class exports (import them by name)
318
-
319
- **Blocks** are opinionated, composed UI sections — exported from `@trading-game/design-intelligence-layer` the same as primitives. Import by name and pass data via props. Variants live as a `layout` / `mode` / `status` prop on a single block, not as separate components.
320
-
321
- ```
322
- ✅ Import blocks directly from the package — same pattern as primitives
323
- ✅ Pass data via props; variants are configured via a single variant prop
324
- ✅ Block names ARE valid component names (treat them like Rule 1 components)
325
- ❌ Do NOT re-implement a block by hand if a block export already covers it
326
- ❌ Do NOT split variants into separate blocks — use the variant prop
327
- ```
328
-
329
- | Block | Variants | Importable? |
330
- |-------|----------|-------------|
331
- | `HeroBlock` | `layout: "centered" \| "split"` | Yes — `import { HeroBlock }` |
332
- | `AuthBlock` | `mode: "sign-in" \| "sign-up"` | Yes — `import { AuthBlock }` |
333
- | `FAQBlock` | `layout: "desktop" \| "mobile"` | Yes — `import { FAQBlock }` |
334
- | `NavBarBlock` | — (internal mobile-menu state) | Yes — `import { NavBarBlock }` |
335
- | `HeaderNavigationBlock` | — (optional `history.count` badge) | Yes — `import { HeaderNavigationBlock }` |
336
- | `OpenPositionsBlock` | `Position` discriminated union | Yes — `import { OpenPositionsBlock, type Position }` |
337
- | `ResultBlock` | `status` + `ctaMode` | Yes — `import { ResultBlock }` |
338
- | `ResultDialog` | — | Yes — `import { ResultDialog }` |
339
-
340
- See `guides/design-system-guide/trading-game-ds-guide.md` § 8.5 for full per-block API, used components, and used tokens.
341
-
342
- ---
343
-
344
- ## Rule 7 — Package version upgrades (stay aligned with the published design system)
345
-
346
- When the user asks to **update**, **upgrade**, or **install the latest** `@trading-game/design-intelligence-layer`, or after the dependency version changes in `package.json`:
347
-
348
- ### How updates actually apply
349
-
350
- - If the project **imports components only** from `@trading-game/design-intelligence-layer`, new styles and behavior come from **`node_modules/.../dist`** after **`npm install` / `npm ci` and a dev or production build**. The package is the source of truth — no AI step is required for those imports to change.
351
- - If the repo **contains copied or forked** files that mirror package components (e.g. a local `components/ui/button.tsx` with full implementation), **`npm install` does not update those files.** They stay stale until someone reconciles them.
352
-
353
- ### What you MUST do after a design-system version bump
354
-
355
- 1. **Search for local duplication** — Look for UI files that re-implement package exports (`components/ui/`, `@/components/ui`, etc.). Flag any file that is not a **thin re-export** or **documented wrapper** around the package.
356
- 2. **Reconcile** — Prefer **removing** duplicate implementations and **importing from the package**. If a fork must stay, align it with the **exact** current implementation in the installed package (or tag on GitHub) and document why it diverges (file name + reason).
357
- 3. **Notify on replace** — If you **delete, replace, or substantially overwrite** local component code to match the package, **stop and tell the user clearly**, e.g.
358
- `Aligned [ComponentName] with @trading-game/design-intelligence-layer@[version]. Previous local behavior or classes: [short summary]. Re-apply needs via package variants/props, tokens, or a thin wrapper only if product requires it.`
359
- 4. **Review `className` overrides** — Parent apps often pass `className` on DS components. Old overrides (radius, colors, shadows) can **hide** new defaults (e.g. pill buttons). After upgrade, scan for overrides on DS components and trim or adjust them when they conflict with the new design.
360
- 5. **Refresh Cursor rules** — The package ships `guides/rules/design-system-consuming-project.mdc`. Cursor does **not** read it from `node_modules` automatically. Tell the user to re-copy it (see README **AI Agent Setup → Cursor**) so agent instructions match the release.
361
- 6. **Tailwind** — Confirm `@source` still points at `node_modules/@trading-game/design-intelligence-layer/dist` so Tailwind v4 emits classes from the updated bundle.
362
-
363
- ### Do not assume
364
-
365
- - Do **not** assume `npm update` alone fixed forked files in the app repo.
366
- - Do **not** silently delete user customizations — always report what was removed or replaced and offer a token-safe way to re-apply if needed.