@trading-game/design-intelligence-layer 0.17.1 → 0.17.3

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/README.md CHANGED
@@ -123,8 +123,8 @@ npm install react react-dom tailwindcss
123
123
  | Field | `Field, FieldLabel, FieldDescription, FieldError, FieldGroup` |
124
124
  | Form | `Form, FormItem, FormLabel, FormControl, FormField, FormMessage` |
125
125
  | Hover Card | `HoverCard, HoverCardTrigger, HoverCardContent` |
126
- | Input | `Input` |
127
- | Input Group | `InputGroup, InputGroupAddon, InputGroupButton, InputGroupText` |
126
+ | Input | `Input` — variants: `default` (light surfaces), `inverse` (dark sunken fill + white text, for use on `bg-primary` / brand-coloured surfaces). |
127
+ | Input Group | `InputGroup, InputGroupAddon, InputGroupButton, InputGroupText` — variants: `default`, `inverse` (propagates to nested `InputGroupInput` / `InputGroupAddon` / `InputGroupText` via context). |
128
128
  | Input OTP | `InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator` — Variants: OTP 4 digits, OTP 6 digits, OTP 6 split, OTP 8 digits, OTP 6 separated |
129
129
  | Item | `Item, ItemMedia, ItemContent, ItemTitle, ItemDescription` |
130
130
  | Kbd | `Kbd, KbdGroup` |
@@ -186,11 +186,14 @@ See [`AGENTS.md`](./AGENTS.md#blocks) for full usage examples per block.
186
186
  ## Button variants and sizes
187
187
 
188
188
  ```tsx
189
- <Button variant="primary" /> // Blue filled — main CTA (light surfaces)
190
- <Button variant="primary-inverse" /> // White filled + blue text — main CTA on dark/coloured surfaces
191
- <Button variant="secondary" /> // Black outline — secondary actions (light surfaces)
192
- <Button variant="secondary-inverse" /> // White outline + white text — secondary actions on dark/coloured surfaces
193
- <Button variant="tertiary" /> // Text only — minimal (light surfaces)
189
+ <Button variant="primary" /> // Blue filled — main CTA (light surfaces)
190
+ <Button variant="primary-inverse" /> // White filled + blue text — main CTA on dark/coloured surfaces
191
+ <Button variant="primary-shimmer" /> // Same as `primary` + sweeping diagonal shimmer (headline CTAs that need to draw the eye)
192
+ <Button variant="primary-inverse-shimmer" /> // Same as `primary-inverse` + brand-blue shimmer over the white surface
193
+ <Button variant="secondary" /> // Black outline — secondary actions (light surfaces)
194
+ <Button variant="secondary-inverse" /> // White outline + white text — secondary actions on dark/coloured surfaces
195
+ <Button variant="tertiary" /> // Text only — minimal (light surfaces)
196
+ <Button variant="tertiary-inverse" /> // Text only + white text — minimal on dark/coloured surfaces
194
197
 
195
198
  // Sizes
196
199
  <Button size="lg" /> // 48px height (default)
@@ -236,7 +239,7 @@ States: hover (`bg-secondary-hover`), focus (3px `ring-ring/50`), active (`opaci
236
239
  <Badge variant="fill-success" /> // Green tint
237
240
  <Badge variant="fill-fail" /> // Red tint
238
241
  <Badge variant="fill-warning" /> // Yellow tint — status/warning
239
- <Badge variant="fill-credit" /> // Deep peach (#FDCA8A) + bold deep-amber (#521900) text — Welcome credit / bonus labels
242
+ <Badge variant="fill-credit" /> // Frosted-glass surface (`--semantic-boost-surface` = `oklch(1 0 0 / 0.22)`) + white text + `backdrop-blur-md` — Welcome credit / bonus chip. Only readable on coloured surfaces (e.g. a brand-blue hero card); blends into white surfaces by design.
240
243
  <Badge variant="fill-demo" /> // Peach surface + red-orange text — demo/test-mode flags
241
244
 
242
245
  // Outline (black border, secondary-hover on hover)
@@ -712,6 +715,21 @@ Design tokens are managed in Figma and exported as CSS variables. To update:
712
715
  ## Changelog
713
716
 
714
717
  ### Unreleased
718
+ - **Button — new `tertiary-inverse` variant:** mirror of `tertiary` (transparent + text-only) but recoloured for use on dark / brand-coloured surfaces — `bg-transparent text-on-primary hover:bg-on-primary/[0.10]`. Pair with any size including `icon-*` (icon-only square). Same `variant` × `size` orthogonality as every other Button variant.
719
+ - **Input — new `variant="inverse"`:** sunken dark fill + white text recipe for use on `bg-primary` (and other coloured surfaces) where the default frosted-white fill blends into the chrome. 25 %-white border, `bg-input-inverse-surface` fill (35 % black), white text, 45 %-white placeholder, white selection. On focus the border solidifies to 100 % white with a 40 %-white ring. `variant="default"` (the existing look) is unchanged — no existing call site moves visually.
720
+ - **InputGroup — new `variant="inverse"`:** mirrors the Input variant. A new `InputGroupVariantContext` propagates the variant down so `InputGroupInput`, `InputGroupAddon`, and `InputGroupText` all pick up the inverse styling automatically (addon icons / suffix text swap to `text-on-primary/70`). Override per-instance via `className` (e.g. `border-transparent` for the borderless wallet-address treatment).
721
+ - **New token — `--input-inverse-surface`:** added in both `src/styles.css` and `app/globals.css` (component-token layer, references the existing primitive palette). Mapped via `@theme inline` to `--color-input-inverse-surface` so it's exposed as `bg-input-inverse-surface` to consumers. Tune the dark-fill shade once, propagates everywhere.
722
+ - **Form-control consistency pass — radius unified to `rounded-xs` (4 px):** `Input`, `InputGroup`, and `Select` (trigger) moved from `rounded-sm` (6 px) → `rounded-xs` (4 px), matching `Button` across every size. Form controls placed in a row (`<Input>` + `<Select>` + `<Button>`) now share identical heights *and* identical corner radii.
723
+ - **Form-control consistency pass — resting shadows removed:** `Input` and `InputGroup` dropped their `shadow-xs` resting drop-shadow so they sit flat alongside the shadowless `Select` and `Button`. Overlay shadows (Dialog, Popover, Tooltip, etc.) are untouched — only the skeuomorphic raised-surface shadow on form inputs is gone.
724
+ - **InputGroup — addon-side padding no longer mirrors:** previously, an inline-end addon would also reduce the input's *left* padding to `pl-2` "for visual symmetry," squeezing text to the field's left edge. Now only the side an addon is attached to reduces its inset; the opposite side keeps the natural Input `pl-3` / `pr-3` (12 px). Fixes the suffix-text input ("0.00 ... USD") looking flush-left compared to the prefix-icon row.
725
+ - **Component playground — Button section split into two columns:** the showcase Button page now renders default-surface variants on the left and `bg-primary`-surface inverse variants on the right, side-by-side, with row-by-row alignment so each (regular ↔ inverse) pair sits on the same y-line. Stacks vertically below `lg:` (1024 px) with the inverse half dropping below the default half. Inverse rows previously sat in individual `bg-primary` wrappers — now a single shared blue panel.
726
+ - **Component playground — Input / InputGroup inverse showcases:** both sections gain a `variant="inverse"` row demonstrating the new variant on `bg-primary`. Plus a standalone `/input-on-blue` route with longer-form exploration (default vs inverse; borderless wallet-address mock; real-world sign-in row).
727
+ - **Component playground — `/tertiary-inverse` route:** standalone page demonstrating the new Button `tertiary-inverse` variant at every `icon-*` size and labeled size on a brand-blue surface.
728
+ - **OpenPositionsBlock — double-divider fix:** the block was rendering both its own manual `<div className="h-px ...">` between rows AND the new ItemGroup `::after` auto-divider, producing two hairlines per gap. The manual divs are removed; the ItemGroup auto-divider now handles spacing. _Migration note for consumers:_ if you wrap your own `<Item>` rows in an `<ItemGroup>` and were manually injecting dividers between them, drop the manual dividers — or pass `divider={false}` on the `<ItemGroup>` to opt out and keep your own. The same cleanup landed in the demo app's Sheet "List" showcase.
729
+ - **Button — two new shimmer variants for headline CTAs:** `primary-shimmer` and `primary-inverse-shimmer`. Same surface tokens as their non-shimmer cousins, plus `relative overflow-hidden` to anchor an absolutely-positioned shimmer overlay. The overlay sweeps a diagonal (`110deg`) gradient driven by a new `button-shimmer` keyframe — two quick back-to-back sweeps every cycle, then a ~3 s pause off-screen, so it reads as an occasional glint rather than a continuous loader. Peak opacity is tuned per-surface (white at 32 % over brand blue; brand-blue at 22 % over white) and falls off into transparent edges. Suppressed automatically when the button is `loading` or `disabled`.
730
+ - **Button — `button-shimmer` keyframe:** new `@keyframes button-shimmer` in both `src/styles.css` and `app/globals.css`. Pattern: `0%, 10% → 30%, 100%` translateX, with an opacity fade at the seam between the two sweeps so the reset between flashes is invisible.
731
+ - **Badge — `fill-credit` is now a frosted glass chip:** `bg-semantic-boost-surface` was repurposed from the old peach (`#FDCA8A`) to `oklch(1 0 0 / 0.22)` — a translucent white surface that composes over whatever sits behind it. Paired with `backdrop-blur-md` and `text-on-primary` (white). Custom `!font-bold` / `!text-xs` overrides removed — the variant now only sets surface tokens, so font weight / size match every other badge (`font-semibold`, `text-xs` from the base). Border override removed too. The variant is intentionally invisible on white surfaces — designed to live on coloured surfaces (e.g. a brand-blue hero card / wallet card), matching the wallet mock's `.c-hero-wc`.
732
+ - **Item — auto-divider in `ItemGroup`:** new `divider` prop (default `true`) adds a 1 px `bg-border-subtle` divider between consecutive default-variant `Item`s. The line is drawn via `::after` (not a border on the item itself), sitting in a 4 px `gap-1` between items with `inset-x-4` so it aligns with the item content padding. The last item's `::after` is suppressed; non-default variants (`outline`, `muted`) are untouched. Pass `divider={false}` to opt out for flush items or manual `<ItemSeparator />` usage.
715
733
  - **Tooltip — black surface by default:** `variant="default"` switched from brand-blue to black (`bg-prominent-inverse` + `text-on-prominent-inverse`). The arrow follows. `variant="inverse"` (white) is unchanged. Default primitive now bakes in `max-w-xs whitespace-normal`, so consumers no longer pass these classes.
716
734
  - **Tooltip — keyboard-shortcut breathing room:** when the tooltip contains a `Kbd` (detected via `:has([data-slot=kbd])`), padding bumps to `px-3.5 py-2` and child elements get `space-y-1.5` so the title / kbd-row pair doesn't crowd.
717
735
  - **Switch — pill + white circle knob:** track switched from `rounded-[6px]` to `rounded-full`, thumb from `rounded-[3px]` to `rounded-full`. Thumb is always `bg-prominent` (white) in both states. Checked track: `bg-primary`. Unchecked track: `bg-input`. No API change.
package/dist/index.cjs CHANGED
@@ -530,10 +530,20 @@ var buttonVariants = (0, import_class_variance_authority.cva)(
530
530
  primary: "bg-primary text-on-primary hover:bg-primary-hover",
531
531
  // Primary Inverse — white bg + blue text; for use on dark/coloured surfaces
532
532
  "primary-inverse": "bg-primary-inverse text-on-primary-inverse hover:bg-primary-inverse/80",
533
+ // Primary Shimmer — same surface as `primary` plus a sweeping
534
+ // white shimmer overlay (same animation as the TicketCard deposit
535
+ // stub). Use for headline CTAs that need to draw the eye.
536
+ "primary-shimmer": "relative overflow-hidden bg-primary text-on-primary hover:bg-primary-hover",
537
+ // Primary Inverse Shimmer — same surface as `primary-inverse` plus
538
+ // a sweeping brand-blue shimmer (white shimmer would be invisible
539
+ // on the white surface). For headline CTAs on dark/coloured pages.
540
+ "primary-inverse-shimmer": "relative overflow-hidden bg-primary-inverse text-on-primary-inverse hover:bg-primary-inverse/80",
533
541
  secondary: "bg-transparent border-[1.5px] border-border-prominent text-on-prominent hover:bg-secondary-hover",
534
542
  // Secondary Inverse — white border + white text; for use on dark/coloured surfaces
535
543
  "secondary-inverse": "bg-transparent border-[1.5px] border-primary-inverse text-on-prominent-inverse hover:bg-primary-inverse/10",
536
- tertiary: "bg-transparent text-primary hover:bg-primary/[0.08]"
544
+ tertiary: "bg-transparent text-primary hover:bg-primary/[0.08]",
545
+ // Tertiary Inverse — transparent + white text/icon; for use on dark/coloured surfaces
546
+ "tertiary-inverse": "bg-transparent text-on-primary hover:bg-on-primary/[0.10]"
537
547
  },
538
548
  size: {
539
549
  lg: "h-12 w-fit gap-3 rounded-xs px-6 py-[14px] text-base [&_svg:not([class*='size-'])]:size-4",
@@ -564,6 +574,13 @@ var spinnerSizeMap = {
564
574
  "icon-sm": "size-3.5",
565
575
  "icon-xs": "size-3"
566
576
  };
577
+ var shimmerGradients = {
578
+ // 110° tilts the bright band forward so the sweep reads as a diagonal
579
+ // shine. Three stops around 50% (soft → mid → soft) shape the highlight
580
+ // like a polished-metal gleam without going too bright at the peak.
581
+ "primary-shimmer": "linear-gradient(110deg, transparent 20%, rgba(255,255,255,0.10) 40%, rgba(255,255,255,0.20) 50%, rgba(255,255,255,0.10) 60%, transparent 80%)",
582
+ "primary-inverse-shimmer": "linear-gradient(110deg, transparent 20%, rgba(35,35,255,0.07) 40%, rgba(35,35,255,0.14) 50%, rgba(35,35,255,0.07) 60%, transparent 80%)"
583
+ };
567
584
  function Button(_a) {
568
585
  var _b = _a, {
569
586
  className,
@@ -581,7 +598,7 @@ function Button(_a) {
581
598
  "children"
582
599
  ]);
583
600
  const Comp = asChild ? import_radix_ui2.Slot.Root : "button";
584
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
601
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
585
602
  Comp,
586
603
  __spreadProps(__spreadValues({
587
604
  "data-slot": "button",
@@ -594,10 +611,20 @@ function Button(_a) {
594
611
  ),
595
612
  "aria-busy": loading || void 0
596
613
  }, props), {
597
- children: loading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
598
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "invisible inline-flex items-center gap-[inherit]", "aria-hidden": true, children }),
599
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { className: spinnerSizeMap[size != null ? size : "lg"] }) })
600
- ] }) : children
614
+ children: [
615
+ variant && shimmerGradients[variant] && !loading && !props.disabled ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
616
+ "span",
617
+ {
618
+ "aria-hidden": true,
619
+ className: "pointer-events-none absolute inset-0 [animation:button-shimmer_4s_linear_infinite]",
620
+ style: { background: shimmerGradients[variant] }
621
+ }
622
+ ) : null,
623
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
624
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "invisible inline-flex items-center gap-[inherit]", "aria-hidden": true, children }),
625
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { className: spinnerSizeMap[size != null ? size : "lg"] }) })
626
+ ] }) : children
627
+ ]
601
628
  })
602
629
  );
603
630
  }
@@ -1128,7 +1155,7 @@ var badgeVariants = (0, import_class_variance_authority3.cva)(
1128
1155
  "fill-success": "bg-semantic-win/10 border-transparent text-semantic-win",
1129
1156
  "fill-fail": "bg-semantic-loss/10 border-transparent text-semantic-loss",
1130
1157
  "fill-warning": "bg-semantic-warning/10 border-transparent text-semantic-warning",
1131
- "fill-credit": "bg-semantic-boost-surface border-transparent text-on-semantic-boost !text-xs !font-bold",
1158
+ "fill-credit": "bg-semantic-boost-surface backdrop-blur-md text-on-primary",
1132
1159
  // Demo / test-mode flag — opaque light-peach surface + red-orange text.
1133
1160
  // Uses --semantic-demo-surface (a solid color-mix of demo @ 10% with white)
1134
1161
  // so the badge looks identical on any background.
@@ -1180,7 +1207,7 @@ var import_lucide_react3 = require("lucide-react");
1180
1207
  var import_jsx_runtime9 = require("react/jsx-runtime");
1181
1208
  var SURFACE_CLASSES = "w-full rounded-sm border border-primary/[0.16] bg-primary/[0.08] px-4 py-3.5";
1182
1209
  function IconBadge({ glyph }) {
1183
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "inline-flex size-10 shrink-0 items-center justify-center rounded-xs bg-primary text-on-primary", children: glyph });
1210
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "inline-flex size-10 shrink-0 items-center justify-center rounded-full bg-primary text-on-primary", children: glyph });
1184
1211
  }
1185
1212
  function Banner(_a) {
1186
1213
  var _b = _a, {
@@ -2276,16 +2303,31 @@ var import_class_variance_authority5 = require("class-variance-authority");
2276
2303
  var import_jsx_runtime18 = require("react/jsx-runtime");
2277
2304
  var inputVariants = (0, import_class_variance_authority5.cva)(
2278
2305
  [
2279
- "w-full min-w-0 rounded-sm border border-border-subtle bg-white/5",
2280
- "text-on-prominent font-body shadow-xs transition-[color,box-shadow] outline-none",
2281
- "selection:bg-primary selection:text-on-primary",
2282
- "file:inline-flex file:border-0 file:bg-transparent file:font-medium file:text-on-prominent",
2283
- "placeholder:text-on-subtle",
2306
+ "w-full min-w-0 rounded-xs border font-body transition-[color,box-shadow] outline-none",
2307
+ "file:inline-flex file:border-0 file:bg-transparent file:font-medium",
2284
2308
  "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
2285
- "focus-visible:border-primary focus-visible:ring-[3px] focus-visible:ring-primary/[0.08]"
2309
+ "focus-visible:ring-[3px]"
2286
2310
  ].join(" "),
2287
2311
  {
2288
2312
  variants: {
2313
+ // `default` is the light-surface treatment; `inverse` is the sunken
2314
+ // dark-fill treatment for coloured surfaces (e.g. bg-primary).
2315
+ variant: {
2316
+ default: [
2317
+ "border-border-subtle bg-white/5 text-on-prominent",
2318
+ "file:text-on-prominent",
2319
+ "placeholder:text-on-subtle",
2320
+ "selection:bg-primary selection:text-on-primary",
2321
+ "focus-visible:border-primary focus-visible:ring-primary/[0.08]"
2322
+ ].join(" "),
2323
+ inverse: [
2324
+ "border-on-primary/25 bg-input-inverse-surface text-on-primary",
2325
+ "file:text-on-primary",
2326
+ "placeholder:text-on-primary/45",
2327
+ "selection:bg-on-primary selection:text-primary",
2328
+ "focus-visible:border-on-primary focus-visible:ring-on-primary/40"
2329
+ ].join(" ")
2330
+ },
2289
2331
  size: {
2290
2332
  sm: "h-8 px-3 py-1 text-xs file:h-6 file:text-xs",
2291
2333
  md: "h-10 px-3 py-1 text-sm file:h-7 file:text-sm",
@@ -2293,6 +2335,7 @@ var inputVariants = (0, import_class_variance_authority5.cva)(
2293
2335
  }
2294
2336
  },
2295
2337
  defaultVariants: {
2338
+ variant: "default",
2296
2339
  size: "md"
2297
2340
  }
2298
2341
  }
@@ -2301,10 +2344,12 @@ function Input(_a) {
2301
2344
  var _b = _a, {
2302
2345
  className,
2303
2346
  type,
2347
+ variant = "default",
2304
2348
  size = "md"
2305
2349
  } = _b, props = __objRest(_b, [
2306
2350
  "className",
2307
2351
  "type",
2352
+ "variant",
2308
2353
  "size"
2309
2354
  ]);
2310
2355
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
@@ -2312,8 +2357,9 @@ function Input(_a) {
2312
2357
  __spreadValues({
2313
2358
  type,
2314
2359
  "data-slot": "input",
2360
+ "data-variant": variant,
2315
2361
  "data-size": size,
2316
- className: cn(inputVariants({ size }), className)
2362
+ className: cn(inputVariants({ variant, size }), className)
2317
2363
  }, props)
2318
2364
  );
2319
2365
  }
@@ -2337,24 +2383,36 @@ function Textarea(_a) {
2337
2383
  // components/ui/input-group.tsx
2338
2384
  var import_jsx_runtime20 = require("react/jsx-runtime");
2339
2385
  var InputGroupSizeContext = React6.createContext("md");
2386
+ var InputGroupVariantContext = React6.createContext("default");
2340
2387
  var inputGroupVariants = (0, import_class_variance_authority6.cva)(
2341
2388
  [
2342
- "group/input-group relative flex w-full items-center rounded-sm border border-border-subtle bg-white/5",
2343
- "shadow-xs transition-[color,box-shadow] outline-none min-w-0 has-[>textarea]:h-auto",
2344
- // Reduce input padding on the addon side, and mirror it on the opposite side for visual symmetry.
2389
+ "group/input-group relative flex w-full items-center rounded-xs border",
2390
+ "transition-[color,box-shadow] outline-none min-w-0 has-[>textarea]:h-auto",
2391
+ // Reduce input padding only on the side an addon is attached to; the
2392
+ // opposite side keeps the natural Input padding (pl-3 / pr-3 = 12 px).
2345
2393
  "has-[>[data-align=inline-start]]:[&>input]:pl-2",
2346
- "has-[>[data-align=inline-start]]:[&>input]:pr-2",
2347
2394
  "has-[>[data-align=inline-end]]:[&>input]:pr-2",
2348
- "has-[>[data-align=inline-end]]:[&>input]:pl-2",
2349
2395
  "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
2350
2396
  "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
2351
- // Focus state.
2352
- "has-[[data-slot=input-group-control]:focus-visible]:border-primary has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-primary/[0.08]",
2397
+ // Focus ring (size; colour comes from the variant).
2398
+ "has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
2353
2399
  // Error state.
2354
2400
  "has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20"
2355
2401
  ].join(" "),
2356
2402
  {
2357
2403
  variants: {
2404
+ // `default` is the light-surface treatment; `inverse` is the sunken
2405
+ // dark-fill treatment for coloured surfaces (mirrors `Input`).
2406
+ variant: {
2407
+ default: [
2408
+ "border-border-subtle bg-white/5",
2409
+ "has-[[data-slot=input-group-control]:focus-visible]:border-primary has-[[data-slot=input-group-control]:focus-visible]:ring-primary/[0.08]"
2410
+ ].join(" "),
2411
+ inverse: [
2412
+ "border-on-primary/25 bg-input-inverse-surface",
2413
+ "has-[[data-slot=input-group-control]:focus-visible]:border-on-primary has-[[data-slot=input-group-control]:focus-visible]:ring-on-primary/40"
2414
+ ].join(" ")
2415
+ },
2358
2416
  size: {
2359
2417
  sm: "h-8",
2360
2418
  md: "h-10",
@@ -2362,6 +2420,7 @@ var inputGroupVariants = (0, import_class_variance_authority6.cva)(
2362
2420
  }
2363
2421
  },
2364
2422
  defaultVariants: {
2423
+ variant: "default",
2365
2424
  size: "md"
2366
2425
  }
2367
2426
  }
@@ -2369,24 +2428,35 @@ var inputGroupVariants = (0, import_class_variance_authority6.cva)(
2369
2428
  function InputGroup(_a) {
2370
2429
  var _b = _a, {
2371
2430
  className,
2431
+ variant = "default",
2372
2432
  size = "md"
2373
2433
  } = _b, props = __objRest(_b, [
2374
2434
  "className",
2435
+ "variant",
2375
2436
  "size"
2376
2437
  ]);
2377
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(InputGroupSizeContext.Provider, { value: size, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2378
- "div",
2379
- __spreadValues({
2380
- "data-slot": "input-group",
2381
- "data-size": size,
2382
- role: "group",
2383
- className: cn(inputGroupVariants({ size }), className)
2384
- }, props)
2385
- ) });
2438
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2439
+ InputGroupVariantContext.Provider,
2440
+ {
2441
+ value: variant,
2442
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(InputGroupSizeContext.Provider, { value: size, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2443
+ "div",
2444
+ __spreadValues({
2445
+ "data-slot": "input-group",
2446
+ "data-variant": variant,
2447
+ "data-size": size,
2448
+ role: "group",
2449
+ className: cn(inputGroupVariants({ variant, size }), className)
2450
+ }, props)
2451
+ ) })
2452
+ }
2453
+ );
2386
2454
  }
2387
2455
  var inputGroupAddonVariants = (0, import_class_variance_authority6.cva)(
2388
2456
  [
2389
2457
  "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 font-medium text-on-subtle select-none",
2458
+ // Swap addon text/icon colour when inside an inverse group.
2459
+ "group-data-[variant=inverse]/input-group:text-on-primary/70",
2390
2460
  "group-data-[disabled=true]/input-group:opacity-50",
2391
2461
  "[&>kbd]:rounded-[calc(var(--radius)-5px)]",
2392
2462
  // Size-responsive text and icon sizing.
@@ -2481,6 +2551,8 @@ function InputGroupText(_a) {
2481
2551
  __spreadValues({
2482
2552
  className: cn(
2483
2553
  "flex items-center gap-2 text-on-subtle",
2554
+ // Swap text/icon colour when inside an inverse group.
2555
+ "group-data-[variant=inverse]/input-group:text-on-primary/70",
2484
2556
  "group-data-[size=sm]/input-group:text-xs group-data-[size=sm]/input-group:[&_svg:not([class*='size-'])]:size-3.5",
2485
2557
  "group-data-[size=md]/input-group:text-sm group-data-[size=md]/input-group:[&_svg:not([class*='size-'])]:size-4",
2486
2558
  "group-data-[size=lg]/input-group:text-sm group-data-[size=lg]/input-group:[&_svg:not([class*='size-'])]:size-4",
@@ -2493,18 +2565,23 @@ function InputGroupText(_a) {
2493
2565
  function InputGroupInput(_a) {
2494
2566
  var _b = _a, {
2495
2567
  className,
2496
- size: sizeProp
2568
+ size: sizeProp,
2569
+ variant: variantProp
2497
2570
  } = _b, props = __objRest(_b, [
2498
2571
  "className",
2499
- "size"
2572
+ "size",
2573
+ "variant"
2500
2574
  ]);
2501
2575
  const groupSize = React6.useContext(InputGroupSizeContext);
2576
+ const groupVariant = React6.useContext(InputGroupVariantContext);
2502
2577
  const size = sizeProp != null ? sizeProp : groupSize;
2578
+ const variant = variantProp != null ? variantProp : groupVariant;
2503
2579
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2504
2580
  Input,
2505
2581
  __spreadValues({
2506
2582
  "data-slot": "input-group-control",
2507
2583
  size,
2584
+ variant,
2508
2585
  className: cn(
2509
2586
  "h-full flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0",
2510
2587
  className
@@ -4311,13 +4388,37 @@ var import_class_variance_authority9 = require("class-variance-authority");
4311
4388
  var import_radix_ui18 = require("radix-ui");
4312
4389
  var import_jsx_runtime35 = require("react/jsx-runtime");
4313
4390
  function ItemGroup(_a) {
4314
- var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
4391
+ var _b = _a, {
4392
+ className,
4393
+ divider = true
4394
+ } = _b, props = __objRest(_b, [
4395
+ "className",
4396
+ "divider"
4397
+ ]);
4315
4398
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4316
4399
  "div",
4317
4400
  __spreadValues({
4318
4401
  role: "list",
4319
4402
  "data-slot": "item-group",
4320
- className: cn("group/item-group flex flex-col", className)
4403
+ "data-divider": divider ? "true" : void 0,
4404
+ className: cn(
4405
+ "group/item-group flex flex-col",
4406
+ // Divider mode: 4 px gap between items + a 1 px line drawn via
4407
+ // ::after on each non-last default item, sitting in that gap.
4408
+ // Item itself stays border-free — the line is a separate visual
4409
+ // element rather than an outline on the item.
4410
+ divider && [
4411
+ "gap-1",
4412
+ "[&>[data-slot=item][data-variant=default]:not(:last-child)]:relative",
4413
+ "[&>[data-slot=item][data-variant=default]:not(:last-child)]:after:content-['']",
4414
+ "[&>[data-slot=item][data-variant=default]:not(:last-child)]:after:absolute",
4415
+ "[&>[data-slot=item][data-variant=default]:not(:last-child)]:after:-bottom-0.5",
4416
+ "[&>[data-slot=item][data-variant=default]:not(:last-child)]:after:inset-x-4",
4417
+ "[&>[data-slot=item][data-variant=default]:not(:last-child)]:after:h-px",
4418
+ "[&>[data-slot=item][data-variant=default]:not(:last-child)]:after:bg-border-subtle"
4419
+ ],
4420
+ className
4421
+ )
4321
4422
  }, props)
4322
4423
  );
4323
4424
  }
@@ -4385,8 +4486,8 @@ var itemMediaVariants = (0, import_class_variance_authority9.cva)(
4385
4486
  variants: {
4386
4487
  variant: {
4387
4488
  default: "bg-transparent",
4388
- icon: "size-8 rounded-sm border bg-subtle [&_svg:not([class*='size-'])]:size-4",
4389
- image: "size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover"
4489
+ icon: "size-8 rounded-full border bg-subtle [&_svg:not([class*='size-'])]:size-4",
4490
+ image: "size-10 overflow-hidden rounded-full [&_img]:size-full [&_img]:object-cover"
4390
4491
  }
4391
4492
  },
4392
4493
  defaultVariants: {
@@ -5479,7 +5580,7 @@ function SelectTrigger(_a) {
5479
5580
  "data-readonly": readOnly ? "" : void 0,
5480
5581
  disabled: readOnly || props.disabled,
5481
5582
  className: cn(
5482
- "flex w-fit items-center justify-between gap-2 rounded-sm border border-input bg-prominent appearance-none whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-on-subtle data-[size=sm]:h-8 data-[size=sm]:px-3 data-[size=sm]:py-1 data-[size=sm]:text-xs data-[size=md]:h-10 data-[size=md]:px-3 data-[size=md]:py-1 data-[size=md]:text-sm data-[size=lg]:h-12 data-[size=lg]:px-4 data-[size=lg]:py-2 data-[size=lg]:text-base *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-on-subtle data-[readonly]:cursor-default data-[readonly]:opacity-100",
5583
+ "flex w-fit items-center justify-between gap-2 rounded-xs border border-input bg-prominent appearance-none whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-on-subtle data-[size=sm]:h-8 data-[size=sm]:px-3 data-[size=sm]:py-1 data-[size=sm]:text-xs data-[size=md]:h-10 data-[size=md]:px-3 data-[size=md]:py-1 data-[size=md]:text-sm data-[size=lg]:h-12 data-[size=lg]:px-4 data-[size=lg]:py-2 data-[size=lg]:text-base *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-on-subtle data-[readonly]:cursor-default data-[readonly]:opacity-100",
5483
5584
  className
5484
5585
  )
5485
5586
  }, props), {
@@ -7790,7 +7891,6 @@ function NavBarBlock({ brand, links, signIn, signUp }) {
7790
7891
  }
7791
7892
 
7792
7893
  // components/blocks/open-positions-block.tsx
7793
- var React18 = __toESM(require("react"), 1);
7794
7894
  var import_lucide_react30 = require("lucide-react");
7795
7895
  var import_jsx_runtime68 = require("react/jsx-runtime");
7796
7896
  function OpenPositionsBlock({
@@ -7832,10 +7932,7 @@ function PositionsList({
7832
7932
  onViewHistory
7833
7933
  }) {
7834
7934
  return /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)("div", { className: "flex flex-col flex-1 min-h-0", children: [
7835
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(ItemGroup, { className: "min-h-0 overflow-y-auto", children: positions.map((pos, i) => /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)(React18.Fragment, { children: [
7836
- /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(PositionRow, { position: pos }),
7837
- i < positions.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("div", { className: "h-px mx-4 bg-border-subtle" })
7838
- ] }, i)) }),
7935
+ /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(ItemGroup, { className: "min-h-0 overflow-y-auto", children: positions.map((pos, i) => /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(PositionRow, { position: pos }, i)) }),
7839
7936
  /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(Separator, {}),
7840
7937
  /* @__PURE__ */ (0, import_jsx_runtime68.jsx)("div", { className: "shrink-0 flex justify-center py-4", children: /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(Button, { variant: "tertiary", size: "md", onClick: onViewHistory, children: "View trade history" }) })
7841
7938
  ] });