@trading-game/design-intelligence-layer 1.0.7 → 1.2.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
 
@@ -6,7 +6,7 @@ Date and date-range picker built on react-day-picker, restyled with circular day
6
6
 
7
7
  - Picking expiry dates, statement ranges, or tournament windows — single dates or ranges.
8
8
 
9
- **When not:** For choosing from a short list of preset periods use [Chip](./chip.md) rows or a Select. Embed the calendar in a Popover for input-triggered pickers.
9
+ **When not:** For choosing from a short list of preset periods use [Chip](./chip.md) rows or a Select. Embed the calendar in a Popover for input-triggered pickers. On phones, use [Date Wheel Picker](./date-wheel-picker.md) in a Drawer instead. The month grid is cramped at phone width and slow for dates years away.
10
10
 
11
11
  ## Anatomy
12
12
 
@@ -0,0 +1,106 @@
1
+ # Date Wheel Picker
2
+
3
+ The phone's date picker: three scroll-snap wheels (day / month / year) behind one selection band. The band is the only place the date shows.
4
+
5
+ ## When to use
6
+
7
+ - Picking a date on a phone, especially far from today: date of birth, document expiry, an order's end date.
8
+ - Inside a [Drawer](./drawer.md): the phone pattern. The picker owns no surface, header, or button, so the Drawer supplies the title and the footer supplies Save.
9
+
10
+ **When not:** On desktop, or for picking near dates where seeing the week matters, use [Calendar](./calendar.md) (in a [Popover](./popover.md)). For a date range use Calendar `mode="range"`. Don't pair the wheels with a text field showing the same date. The band already shows it, and two readouts of one value is the redundancy this component exists to remove.
11
+
12
+ ## Anatomy
13
+
14
+ - `DateWheelPicker`: `role="group"`, `aria-label="Date"`, a 200px row of three wheels with a 4px gap. `data-slot="date-wheel-picker"`.
15
+ - Band: a 40px `rounded-md` row in `bg-background-brand-selected`, fixed behind the middle row. `data-slot="date-wheel-picker-band"`.
16
+ - Wheels: one per part, `role="spinbutton"` with `aria-label` Day / Month / Year and `aria-valuetext` the visible label. `data-slot="date-wheel-picker-wheel"`. Five 40px rows show; edges fade with a mask.
17
+ - Rows: `type-date-wheel-picker-item`, `data-selected` on the row under the band, `data-disabled` on rows outside the range.
18
+
19
+ ## API
20
+
21
+ ### DateWheelPicker
22
+
23
+ | Prop | Type | Default | Notes |
24
+ | --- | --- | --- | --- |
25
+ | `value` | `Date` | — | Controlled date. The wheels always show a date. |
26
+ | `defaultValue` | `Date` | today | Uncontrolled start. Clamped to the range. |
27
+ | `onValueChange` | `(date: Date) => void` | — | Fires once a wheel settles (~120ms after scrolling stops). It never fires mid-spin. |
28
+ | `min` | `Date` | 1 Jan 1900 | Earliest selectable day (time ignored). |
29
+ | `max` | `Date` | 31 Dec, 10 years ahead | Latest selectable day (time ignored). |
30
+ | `order` | `"dmy" \| "mdy" \| "ymd"` | `"dmy"` | Wheel order, left to right. |
31
+ | `monthFormat` | `"long" \| "short" \| "numeric"` | `"long"` | `January` / `Jan` / `01`. Long months get a wider wheel. |
32
+ | `locale` | `string` | runtime locale | BCP 47 tag for month names. |
33
+
34
+ `defaultValue` and `onChange` are omitted from the div props; everything else passes through.
35
+
36
+ ## Variants & sizes
37
+
38
+ No variants and no sizes. The picker always uses the 40px row rail, five rows tall (200px), and fills its container's width.
39
+
40
+ ```tsx
41
+ import {
42
+ Button,
43
+ DateWheelPicker,
44
+ Drawer,
45
+ DrawerContent,
46
+ DrawerFooter,
47
+ DrawerHeader,
48
+ DrawerTitle,
49
+ } from "@trading-game/design-intelligence-layer"
50
+
51
+ // The profile pattern: "Add" opens a Drawer holding only the wheels and Save.
52
+ const today = new Date()
53
+ const adultCutoff = new Date(today.getFullYear() - 18, today.getMonth(), today.getDate())
54
+
55
+ const [draft, setDraft] = React.useState<Date>(dob ?? adultCutoff)
56
+
57
+ <Drawer open={open} onOpenChange={setOpen}>
58
+ <DrawerContent dismiss="close">
59
+ <DrawerHeader>
60
+ <DrawerTitle>{dob ? "Edit" : "Add"} date of birth</DrawerTitle>
61
+ </DrawerHeader>
62
+ <div className="px-4">
63
+ <DateWheelPicker value={draft} onValueChange={setDraft} max={adultCutoff} />
64
+ </div>
65
+ <DrawerFooter>
66
+ <Button onClick={() => save(draft)}>Save</Button>
67
+ </DrawerFooter>
68
+ </DrawerContent>
69
+ </Drawer>
70
+
71
+ // A future range, year first, numeric months:
72
+ <DateWheelPicker value={expiry} onValueChange={setExpiry} min={new Date()} order="ymd" monthFormat="numeric" />
73
+ ```
74
+
75
+ **Responsive:** render the wheels in a Drawer on phones and [Calendar](./calendar.md) in a Popover on desktop, switching on `useIsMobile()`. The demo app's Date Wheel Picker page shows both behind the screen-mode toggle.
76
+
77
+ ## Tokens
78
+
79
+ **Colour (semantic):**
80
+ - band: `bg-background-brand-selected`
81
+ - selected row: `text-text-brand-selected`. Other rows: `text-text-subtle-default`. Out of range: `text-text-disabled-default`
82
+ - focus: 3px `ring-ring-focus-strong` around the focused wheel
83
+
84
+ **Type (private):** `--date-wheel-picker-item-*`: 16 / 24, regular, with the selected row stepping to semibold (`--date-wheel-picker-item-selected-font-weight`) through a CSS data-attribute rule on `.type-date-wheel-picker-item[data-selected]`.
85
+
86
+ ## Behaviour
87
+
88
+ - **Settling.** Rows snap to the band. The highlight follows the finger live, and `onValueChange` fires only after the wheel has rested for ~120ms.
89
+ - **Range.** Dates outside `min`/`max` stay visible but greyed. A wheel that settles on one springs back to the nearest allowed row, and the highlight moves straight away rather than waiting for the animation. A month is disabled only when none of its days are in range. The year wheel lists only years in range.
90
+ - **Clamping.** Changing month or year clamps the day to the month's length (31 → 30, Feb 28/29 in leap years) and then to the range.
91
+ - **Always a value.** The wheels always show a date. To let people save without spinning, start the controlled value at the date the picker opens on, as the profile does with the 18-years-ago cutoff.
92
+ - **Keyboard.** Each wheel is a spinbutton: Arrow Up/Down step one allowed row, Page Up/Down step five, Home/End jump to the first or last allowed row.
93
+ - **Motion.** Smooth scrolling and the selected row's 4% scale both turn off under `prefers-reduced-motion`. On Android, each row passed gives a light vibration tick. iOS ignores it.
94
+
95
+ ## Do / Don't
96
+
97
+ **Do**
98
+ - Put it in a Drawer with a clear title ("Add date of birth") and a Save button in the footer.
99
+ - Set `max` for age rules and `min` for future-only dates, so impossible dates never reach validation.
100
+ - Open on a sensible date (the age cutoff, today, or the saved value) so most people spin a few rows, not decades.
101
+
102
+ **Don't**
103
+ - Don't add a text field or a readout that repeats the selected date.
104
+ - Don't use it on desktop. The calendar grid is faster with a mouse.
105
+ - Don't wrap it in its own card or border inside a Drawer. The Drawer is the surface.
106
+ - Don't restyle the band with brand washes (`bg-brand-default/10`). The selected family is what keeps it legible in dark.
@@ -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.
@@ -21,7 +21,7 @@ Transient notification system — a themed wrapper around `sonner` on the invers
21
21
  | --- | --- | --- | --- |
22
22
  | ...props | sonner `ToasterProps` | — | All pass through; the wrapper presets the values below (overridable). |
23
23
 
24
- Preset defaults: `theme` from `next-themes` (`useTheme`, fallback `"system"`); `position` `"top-center"` on mobile / `"bottom-right"` on desktop (`useIsMobile`); `visibleToasts` 1 mobile / 3 desktop; `duration` 4000ms; status icons CircleCheck / Info / TriangleAlert / OctagonX / spinning Loader2, all `size-4`.
24
+ Preset defaults: `theme` `"system"` (no theme-provider dependency); `position` `"top-center"` on mobile / `"bottom-right"` on desktop (`useIsMobile`); `visibleToasts` 1 mobile / 3 desktop; `duration` 4000ms; status icons CircleCheck / Info / TriangleAlert / OctagonX / spinning Loader2, all `size-4`.
25
25
 
26
26
  ## Variants & sizes
27
27
 
@@ -49,7 +49,7 @@ toast.error("Deposit failed", {
49
49
 
50
50
  - Renders on the inverse surface: dark card on the light theme, light card on dark — with no border and no shadow class of its own.
51
51
  - Position and stack depth adapt to viewport: mobile shows a single toast top-center; desktop stacks up to three bottom-right.
52
- - Theme sync comes from `next-themes`, so the app must be inside a `ThemeProvider` for correct light/dark toasts.
52
+ - No theme provider needed: the toast paints from inverse tokens, which already follow the app's `dark` class. Sonner's `theme` prop only reaches its cancel and close buttons, so apps that use those can pass `theme` explicitly.
53
53
  - Every toast auto-dismisses after 4 seconds unless overridden per call.
54
54
  - All sonner options (promise toasts, custom duration, `richColors` off by default) remain available through the `toast` API and `Toaster` props.
55
55
 
@@ -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.2.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",
@@ -25,12 +25,13 @@
25
25
  "scripts": {
26
26
  "dev": "next dev -H 127.0.0.1 -p 4000",
27
27
  "generate:registry": "node scripts/generate-component-registry.mjs",
28
- "build": "tsup",
28
+ "build": "tsup && npm run check:dist",
29
29
  "build:next": "next build",
30
30
  "start": "next start",
31
31
  "lint": "eslint",
32
32
  "prepublishOnly": "npm run build",
33
33
  "check:tokens": "node scripts/check-token-docs.mjs",
34
+ "check:dist": "node scripts/check-dist-imports.mjs",
34
35
  "predev": "rm -rf .next"
35
36
  },
36
37
  "files": [
package/src/styles.css CHANGED
@@ -1245,6 +1245,10 @@
1245
1245
  --numpad-font-size: var(--primitive-font-size-20);
1246
1246
  --numpad-font-weight: var(--primitive-font-weight-medium);
1247
1247
  --numpad-line-height: var(--primitive-line-height-28);
1248
+ --date-wheel-picker-item-font-size: var(--primitive-font-size-16);
1249
+ --date-wheel-picker-item-line-height: var(--primitive-line-height-24);
1250
+ --date-wheel-picker-item-font-weight: var(--primitive-font-weight-regular);
1251
+ --date-wheel-picker-item-selected-font-weight: var(--primitive-font-weight-semibold);
1248
1252
  /* ── Pagination — component type tokens (private) ── */
1249
1253
  --pagination-font-size: var(--primitive-font-size-14);
1250
1254
  --pagination-font-weight: var(--primitive-font-weight-semibold);
@@ -1820,6 +1824,17 @@
1820
1824
  font-weight: var(--numpad-font-weight);
1821
1825
  }
1822
1826
 
1827
+ /* Date Wheel Picker rows — the selected row steps to semibold. A data-attribute
1828
+ rule, not a variant: .type-* classes can't be gated by utilities. */
1829
+ .type-date-wheel-picker-item {
1830
+ font-size: var(--date-wheel-picker-item-font-size);
1831
+ line-height: var(--date-wheel-picker-item-line-height);
1832
+ font-weight: var(--date-wheel-picker-item-font-weight);
1833
+ }
1834
+ .type-date-wheel-picker-item[data-selected] {
1835
+ font-weight: var(--date-wheel-picker-item-selected-font-weight);
1836
+ }
1837
+
1823
1838
  .type-pagination {
1824
1839
  font-size: var(--pagination-font-size);
1825
1840
  line-height: var(--pagination-line-height);