@olympusoss/canvas 2.18.0 → 2.20.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olympusoss/canvas",
3
- "version": "2.18.0",
3
+ "version": "2.20.2",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,61 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ export interface DotPulseProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ /** Number of active (pulsing) dots. Clamped to `[0, total]`. */
7
+ count: number;
8
+ /** Total number of dots rendered. Default `5`. */
9
+ total?: number;
10
+ /**
11
+ * CSS variable name (without leading `--`) used for active dot fill.
12
+ * Default `chart-1`. The variable should resolve to an HSL triplet.
13
+ */
14
+ colorVar?: string;
15
+ }
16
+
17
+ /**
18
+ * Pure-CSS severity indicator: a row of small circles where the first `count`
19
+ * dots pulse with color and the rest are muted. Designed for `<StatCard>`
20
+ * slots where no time-series data exists (e.g. lockout counts).
21
+ */
22
+ export const DotPulse = React.forwardRef<HTMLDivElement, DotPulseProps>(
23
+ ({ count, total = 5, colorVar = "chart-1", className, ...props }, ref) => {
24
+ const active = Math.max(0, Math.min(total, count));
25
+ const color = `hsl(var(--${colorVar}))`;
26
+
27
+ return (
28
+ <div
29
+ ref={ref}
30
+ className={cn("flex items-center gap-2", className)}
31
+ role="presentation"
32
+ {...props}
33
+ >
34
+ {Array.from({ length: total }, (_, i) => {
35
+ const isActive = i < active;
36
+ return (
37
+ <span
38
+ key={i}
39
+ className={cn("inline-block size-2 rounded-full", !isActive && "bg-muted")}
40
+ style={
41
+ isActive
42
+ ? {
43
+ background: color,
44
+ animation: `canvas-dot-pulse 1.5s ease-in-out infinite`,
45
+ animationDelay: `${i * 0.15}s`,
46
+ }
47
+ : undefined
48
+ }
49
+ aria-hidden
50
+ />
51
+ );
52
+ })}
53
+ <style>{`@keyframes canvas-dot-pulse {
54
+ 0%, 100% { transform: scale(1); opacity: 1; }
55
+ 50% { transform: scale(1.4); opacity: 0.5; }
56
+ }`}</style>
57
+ </div>
58
+ );
59
+ },
60
+ );
61
+ DotPulse.displayName = "DotPulse";
@@ -0,0 +1,80 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ export interface SparklineAreaProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ /** Values to plot. Each entry maps to a point on the area line. Needs at least 2 points. */
7
+ data: number[];
8
+ /** Pixel height of the chart area. */
9
+ height?: number;
10
+ /**
11
+ * CSS variable name (without leading `--`) used for the stroke and fill.
12
+ * Default `chart-1`. The variable should resolve to an HSL triplet.
13
+ */
14
+ colorVar?: string;
15
+ /** Caption rendered below the chart. */
16
+ caption?: React.ReactNode;
17
+ }
18
+
19
+ /**
20
+ * Pure-SVG area sparkline for inline embedding in `<StatCard>` /
21
+ * `<SectionCard>`. Renders a filled area with a gradient fade, a stroke line,
22
+ * and a dot on the last data point. Decorative only (no tooltips, no axes).
23
+ */
24
+ export const SparklineArea = React.forwardRef<HTMLDivElement, SparklineAreaProps>(
25
+ ({ data, height = 48, colorVar = "chart-1", caption, className, ...props }, ref) => {
26
+ const fillId = React.useId();
27
+
28
+ if (data.length < 2) return null;
29
+
30
+ const max = Math.max(...data);
31
+ const min = Math.min(...data);
32
+ const range = max - min || 1;
33
+ const w = (data.length - 1) * 10;
34
+ const h = height;
35
+ const padding = 3; // vertical padding for the end-dot
36
+
37
+ const pts = data
38
+ .map((v, i) => {
39
+ const x = i * 10;
40
+ const y = h - padding - ((v - min) / range) * (h - padding * 2);
41
+ return `${x},${y}`;
42
+ })
43
+ .join(" ");
44
+
45
+ const area = `0,${h} ${pts} ${w},${h}`;
46
+ const last = data[data.length - 1];
47
+ const lastY = h - padding - ((last - min) / range) * (h - padding * 2);
48
+ const color = `hsl(var(--${colorVar}))`;
49
+
50
+ return (
51
+ <div ref={ref} className={cn("w-full", className)} {...props}>
52
+ <svg
53
+ viewBox={`0 0 ${w} ${h}`}
54
+ preserveAspectRatio="none"
55
+ className="h-full w-full overflow-visible"
56
+ style={{ height }}
57
+ aria-hidden
58
+ >
59
+ <defs>
60
+ <linearGradient id={fillId} x1="0" x2="0" y1="0" y2="1">
61
+ <stop offset="0%" stopColor={color} stopOpacity="0.25" />
62
+ <stop offset="100%" stopColor={color} stopOpacity="0" />
63
+ </linearGradient>
64
+ </defs>
65
+ <polygon points={area} fill={`url(#${fillId})`} />
66
+ <polyline
67
+ points={pts}
68
+ fill="none"
69
+ stroke={color}
70
+ strokeWidth="1.5"
71
+ vectorEffect="non-scaling-stroke"
72
+ />
73
+ <circle cx={w} cy={lastY} r="2.5" fill={color} />
74
+ </svg>
75
+ {caption && <p className="mt-2 text-xs text-muted-foreground">{caption}</p>}
76
+ </div>
77
+ );
78
+ },
79
+ );
80
+ SparklineArea.displayName = "SparklineArea";
@@ -21,6 +21,8 @@ export interface StatCardProps {
21
21
  * `delta` as ReactNode to fully control rendering yourself.
22
22
  */
23
23
  deltaArrow?: boolean;
24
+ /** Optional content rendered below the delta row (e.g. a sparkline or mini chart). */
25
+ children?: React.ReactNode;
24
26
  className?: string;
25
27
  }
26
28
 
@@ -49,6 +51,7 @@ export function StatCard({
49
51
  deltaTone = "up",
50
52
  deltaCaption,
51
53
  deltaArrow = true,
54
+ children,
52
55
  className,
53
56
  }: StatCardProps) {
54
57
  const showArrow = deltaArrow && typeof delta === "string" && deltaTone !== "neutral";
@@ -88,6 +91,7 @@ export function StatCard({
88
91
  {deltaCaption != null && <span className="text-muted-foreground">{deltaCaption}</span>}
89
92
  </div>
90
93
  )}
94
+ {children != null && <div className="mt-3">{children}</div>}
91
95
  </CardContent>
92
96
  </Card>
93
97
  );
@@ -391,6 +391,7 @@ const SidebarInset = React.forwardRef<HTMLDivElement, SidebarInsetProps>(
391
391
  return (
392
392
  <main
393
393
  ref={ref}
394
+ data-slot="sidebar-inset"
394
395
  className={cn(
395
396
  "relative flex w-full flex-1 flex-col bg-background",
396
397
  "md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
package/src/index.ts CHANGED
@@ -94,6 +94,10 @@ export {
94
94
  Sector,
95
95
  Trapezoid,
96
96
  } from "./components/charts/details";
97
+ export {
98
+ DotPulse,
99
+ type DotPulseProps,
100
+ } from "./components/charts/dot-pulse";
97
101
  export { Gauge, type GaugeProps } from "./components/charts/gauge";
98
102
  export { CartesianGrid, PolarGrid } from "./components/charts/grids";
99
103
  export {
@@ -123,6 +127,10 @@ export {
123
127
  Sparkline,
124
128
  type SparklineProps,
125
129
  } from "./components/charts/sparkline";
130
+ export {
131
+ SparklineArea,
132
+ type SparklineAreaProps,
133
+ } from "./components/charts/sparkline-area";
126
134
  export {
127
135
  StackedBar,
128
136
  type StackedBarProps,
package/styles/glass.css CHANGED
@@ -20,158 +20,156 @@
20
20
  *
21
21
  * Mirrors the Athena design handoff's `app.css` glass block; do not
22
22
  * tighten these values without checking against the handoff bundle.
23
+ *
24
+ * Deliberately unlayered so glass backgrounds override Tailwind
25
+ * utilities like `bg-card` (which live in @layer utilities).
23
26
  */
24
27
 
25
- @layer base {
26
- html[data-surface="glass"] {
27
- --glass-tint: 0 0% 100%; /* card tint base */
28
- --glass-tint-alpha: 0.55;
29
- --glass-border: 0 0% 100%;
30
- --glass-border-alpha: 0.45;
31
- --glass-highlight: 0 0% 100%;
32
- --glass-highlight-alpha: 0.55;
33
- --glass-shadow: 220 30% 20%;
34
- --glass-blur: 18px;
35
- --glass-saturate: 140%;
36
- }
28
+ html[data-surface="glass"] {
29
+ --glass-tint: 0 0% 100%;
30
+ --glass-tint-alpha: 0.55;
31
+ --glass-border: 0 0% 100%;
32
+ --glass-border-alpha: 0.45;
33
+ --glass-highlight: 0 0% 100%;
34
+ --glass-highlight-alpha: 0.55;
35
+ --glass-shadow: 220 30% 20%;
36
+ --glass-backdrop: blur(18px) saturate(140%);
37
+ --glass-backdrop-subtle: blur(8px);
38
+ }
37
39
 
38
- html[data-surface="glass"].dark {
39
- --glass-tint: 222 22% 18%;
40
- --glass-tint-alpha: 0.55;
41
- --glass-border: 0 0% 100%;
42
- --glass-border-alpha: 0.1;
43
- --glass-highlight: 0 0% 100%;
44
- --glass-highlight-alpha: 0.1;
45
- --glass-shadow: 222 60% 4%;
46
- }
40
+ html[data-surface="glass"].dark {
41
+ --glass-tint: 222 22% 18%;
42
+ --glass-tint-alpha: 0.55;
43
+ --glass-border: 0 0% 100%;
44
+ --glass-border-alpha: 0.1;
45
+ --glass-highlight: 0 0% 100%;
46
+ --glass-highlight-alpha: 0.1;
47
+ --glass-shadow: 222 60% 4%;
48
+ }
47
49
 
48
- /* ──────────────────────────────────────────────────────────────
49
- * Aurora backdrop — three soft radial washes anchored to body so
50
- * they remain stable across route changes and ignore the inner
51
- * shell's flat fill. The surfaces above are translucent so this
52
- * is what users actually see "through" the glass.
53
- * ──────────────────────────────────────────────────────────── */
54
- html[data-surface="glass"] body {
55
- background:
56
- radial-gradient(60% 50% at 12% 10%, hsl(28 100% 80% / 0.55), transparent 60%),
57
- radial-gradient(55% 55% at 88% 8%, hsl(210 100% 78% / 0.55), transparent 60%),
58
- radial-gradient(70% 60% at 50% 100%, hsl(270 90% 82% / 0.45), transparent 65%),
59
- hsl(220 30% 97%);
60
- background-attachment: fixed;
61
- }
50
+ /* ──────────────────────────────────────────────────────────────
51
+ * Aurora backdrop
52
+ * ──────────────────────────────────────────────────────────── */
53
+ html[data-surface="glass"] body {
54
+ background:
55
+ radial-gradient(60% 50% at 12% 10%, hsl(28 100% 80% / 0.55), transparent 60%),
56
+ radial-gradient(55% 55% at 88% 8%, hsl(210 100% 78% / 0.55), transparent 60%),
57
+ radial-gradient(70% 60% at 50% 100%, hsl(270 90% 82% / 0.45), transparent 65%),
58
+ hsl(220 30% 97%);
59
+ background-attachment: fixed;
60
+ }
62
61
 
63
- html[data-surface="glass"].dark body {
64
- background:
65
- radial-gradient(55% 50% at 10% 8%, hsl(245 90% 60% / 0.4), transparent 60%),
66
- radial-gradient(50% 55% at 92% 12%, hsl(280 85% 55% / 0.35), transparent 60%),
67
- radial-gradient(70% 60% at 50% 100%, hsl(190 90% 45% / 0.28), transparent 70%),
68
- hsl(222 30% 6%);
69
- background-attachment: fixed;
70
- }
62
+ html[data-surface="glass"].dark body {
63
+ background:
64
+ radial-gradient(55% 50% at 10% 8%, hsl(245 90% 60% / 0.4), transparent 60%),
65
+ radial-gradient(50% 55% at 92% 12%, hsl(280 85% 55% / 0.35), transparent 60%),
66
+ radial-gradient(70% 60% at 50% 100%, hsl(190 90% 45% / 0.28), transparent 70%),
67
+ hsl(222 30% 6%);
68
+ background-attachment: fixed;
69
+ }
71
70
 
72
- /* ──────────────────────────────────────────────────────────────
73
- * Frosted-pane mixin. Same translucent fill, blur, and refractive
74
- * highlight on every load-bearing surface so the whole layout
75
- * reads as one stack of glass panes. Includes cards, chrome,
76
- * dialogs, dropdown panels, popovers, tooltips, alerts, calendar,
77
- * terminal, menubar, navigation-menu, tabs list.
78
- * ──────────────────────────────────────────────────────────── */
79
- html[data-surface="glass"] [data-slot="card"],
80
- html[data-surface="glass"] [data-slot="sidebar"],
81
- html[data-surface="glass"] [data-slot="topbar"],
82
- html[data-surface="glass"] [data-slot="data-table"],
83
- html[data-surface="glass"] [data-slot="empty-state"],
84
- html[data-surface="glass"] [data-slot="popover-content"],
85
- html[data-surface="glass"] [data-slot="sheet-content"],
86
- html[data-surface="glass"] [data-slot="drawer-content"],
87
- html[data-surface="glass"] [data-slot="alert"],
88
- html[data-surface="glass"] [data-slot="alert-dialog-content"],
89
- html[data-surface="glass"] [data-slot="dialog-content"],
90
- html[data-surface="glass"] [data-slot="dropdown-menu-content"],
91
- html[data-surface="glass"] [data-slot="dropdown-menu-sub-content"],
92
- html[data-surface="glass"] [data-slot="context-menu-content"],
93
- html[data-surface="glass"] [data-slot="context-menu-sub-content"],
94
- html[data-surface="glass"] [data-slot="hover-card-content"],
95
- html[data-surface="glass"] [data-slot="select-content"],
96
- html[data-surface="glass"] [data-slot="menubar"],
97
- html[data-surface="glass"] [data-slot="menubar-content"],
98
- html[data-surface="glass"] [data-slot="menubar-sub-content"],
99
- html[data-surface="glass"] [data-slot="navigation-menu-viewport"],
100
- html[data-surface="glass"] [data-slot="tooltip-content"],
101
- html[data-surface="glass"] [data-slot="command"],
102
- html[data-surface="glass"] [data-slot="calendar"],
103
- html[data-surface="glass"] [data-slot="terminal"] {
104
- background: hsl(var(--glass-tint) / var(--glass-tint-alpha));
105
- backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
106
- -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
107
- border-color: hsl(var(--glass-border) / var(--glass-border-alpha));
108
- box-shadow:
109
- inset 0 1px 0 hsl(var(--glass-highlight) / var(--glass-highlight-alpha)),
110
- 0 1px 2px hsl(var(--glass-shadow) / 0.06),
111
- 0 8px 24px -12px hsl(var(--glass-shadow) / 0.18);
112
- }
71
+ /* ──────────────────────────────────────────────────────────────
72
+ * Frosted-pane surfaces
73
+ * ──────────────────────────────────────────────────────────── */
74
+ html[data-surface="glass"] [data-slot="card"],
75
+ html[data-surface="glass"] [data-slot="sidebar"],
76
+ html[data-surface="glass"] [data-slot="topbar"],
77
+ html[data-surface="glass"] [data-slot="data-table"],
78
+ html[data-surface="glass"] [data-slot="empty-state"],
79
+ html[data-surface="glass"] [data-slot="popover-content"],
80
+ html[data-surface="glass"] [data-slot="sheet-content"],
81
+ html[data-surface="glass"] [data-slot="drawer-content"],
82
+ html[data-surface="glass"] [data-slot="alert"],
83
+ html[data-surface="glass"] [data-slot="alert-dialog-content"],
84
+ html[data-surface="glass"] [data-slot="dialog-content"],
85
+ html[data-surface="glass"] [data-slot="dropdown-menu-content"],
86
+ html[data-surface="glass"] [data-slot="dropdown-menu-sub-content"],
87
+ html[data-surface="glass"] [data-slot="context-menu-content"],
88
+ html[data-surface="glass"] [data-slot="context-menu-sub-content"],
89
+ html[data-surface="glass"] [data-slot="hover-card-content"],
90
+ html[data-surface="glass"] [data-slot="select-content"],
91
+ html[data-surface="glass"] [data-slot="menubar"],
92
+ html[data-surface="glass"] [data-slot="menubar-content"],
93
+ html[data-surface="glass"] [data-slot="menubar-sub-content"],
94
+ html[data-surface="glass"] [data-slot="navigation-menu-viewport"],
95
+ html[data-surface="glass"] [data-slot="tooltip-content"],
96
+ html[data-surface="glass"] [data-slot="command"],
97
+ html[data-surface="glass"] [data-slot="calendar"],
98
+ html[data-surface="glass"] [data-slot="terminal"] {
99
+ background: hsl(var(--glass-tint) / var(--glass-tint-alpha));
100
+ /* Whole-value var(): Lightning CSS strips backdrop-filter when var()
101
+ appears *inside* a filter function (e.g. blur(var(--x))). A single
102
+ top-level var() is preserved as an opaque custom-property substitution
103
+ the browser resolves at runtime. */
104
+ backdrop-filter: var(--glass-backdrop);
105
+ -webkit-backdrop-filter: var(--glass-backdrop);
106
+ border-color: hsl(var(--glass-border) / var(--glass-border-alpha));
107
+ box-shadow:
108
+ inset 0 1px 0 hsl(var(--glass-highlight) / var(--glass-highlight-alpha)),
109
+ 0 1px 2px hsl(var(--glass-shadow) / 0.06),
110
+ 0 8px 24px -12px hsl(var(--glass-shadow) / 0.18);
111
+ }
113
112
 
114
- /* Sidebar and topbar take a slightly less-opaque tint so they
115
- * read as chrome rather than content cards. */
116
- html[data-surface="glass"] [data-slot="sidebar"],
117
- html[data-surface="glass"] [data-slot="topbar"] {
118
- background: hsl(var(--glass-tint) / calc(var(--glass-tint-alpha) - 0.1));
119
- border-color: hsl(var(--glass-border) / var(--glass-border-alpha));
120
- }
113
+ html[data-surface="glass"] [data-slot="sidebar"],
114
+ html[data-surface="glass"] [data-slot="topbar"] {
115
+ background: hsl(var(--glass-tint) / calc(var(--glass-tint-alpha) - 0.1));
116
+ border-color: hsl(var(--glass-border) / var(--glass-border-alpha));
117
+ }
121
118
 
122
- /* DataTable internal rules header tint, row separators, hover. */
123
- html[data-surface="glass"] [data-slot="data-table"] thead tr {
124
- background: hsl(var(--glass-highlight) / 0.1);
125
- border-bottom: 1px solid hsl(var(--glass-border) / var(--glass-border-alpha));
126
- }
127
- html[data-surface="glass"] [data-slot="data-table"] tbody tr {
128
- border-bottom: 1px solid hsl(var(--glass-border) / calc(var(--glass-border-alpha) - 0.2));
129
- }
130
- html[data-surface="glass"] [data-slot="data-table"] tbody tr:hover {
131
- background: hsl(var(--glass-highlight) / 0.1);
132
- }
119
+ /* SidebarInset (the main content column) is fully transparent in glass
120
+ * mode so the aurora gradient on <body> reads through unimpeded. No
121
+ * backdrop-filter is applied: the content area is a window onto the
122
+ * aurora, not a frosted pane. The Tailwind `bg-background` utility on
123
+ * the component is overridden here by the higher-specificity selector
124
+ * and the unlayered cascade of glass.css. */
125
+ html[data-surface="glass"] [data-slot="sidebar-inset"] {
126
+ background: transparent;
127
+ }
133
128
 
134
- /* SectionCard divider matches the border tone. */
135
- html[data-surface="glass"] [data-slot="card"] [data-slot="card-divider"] {
136
- background: hsl(var(--glass-border) / var(--glass-border-alpha));
137
- }
129
+ /* DataTable internal rules */
130
+ html[data-surface="glass"] [data-slot="data-table"] thead tr {
131
+ background: hsl(var(--glass-highlight) / 0.1);
132
+ border-bottom: 1px solid hsl(var(--glass-border) / var(--glass-border-alpha));
133
+ }
134
+ html[data-surface="glass"] [data-slot="data-table"] tbody tr {
135
+ border-bottom: 1px solid hsl(var(--glass-border) / calc(var(--glass-border-alpha) - 0.2));
136
+ }
137
+ html[data-surface="glass"] [data-slot="data-table"] tbody tr:hover {
138
+ background: hsl(var(--glass-highlight) / 0.1);
139
+ }
138
140
 
139
- /* Inputs, kbd, codeblock, tabs list — frosted to match. Less blur
140
- * than full panes so dense form rows stay readable. */
141
- html[data-surface="glass"] [data-slot="input"],
142
- html[data-surface="glass"] [data-slot="code-block"],
143
- html[data-surface="glass"] [data-slot="tabs-list"] {
144
- background: hsl(var(--glass-tint) / 0.35);
145
- border-color: hsl(var(--glass-border) / var(--glass-border-alpha));
146
- backdrop-filter: blur(8px);
147
- -webkit-backdrop-filter: blur(8px);
148
- }
141
+ html[data-surface="glass"] [data-slot="card"] [data-slot="card-divider"] {
142
+ background: hsl(var(--glass-border) / var(--glass-border-alpha));
143
+ }
149
144
 
150
- /* Accordion items use only a bottom border — re-tint that border so it
151
- * tracks the glass palette instead of the solid border token. */
152
- html[data-surface="glass"] [data-slot="accordion-item"] {
153
- border-bottom-color: hsl(var(--glass-border) / var(--glass-border-alpha));
154
- }
145
+ html[data-surface="glass"] [data-slot="input"],
146
+ html[data-surface="glass"] [data-slot="code-block"],
147
+ html[data-surface="glass"] [data-slot="tabs-list"] {
148
+ background: hsl(var(--glass-tint) / 0.35);
149
+ border-color: hsl(var(--glass-border) / var(--glass-border-alpha));
150
+ backdrop-filter: var(--glass-backdrop-subtle);
151
+ -webkit-backdrop-filter: var(--glass-backdrop-subtle);
152
+ }
153
+
154
+ html[data-surface="glass"] [data-slot="accordion-item"] {
155
+ border-bottom-color: hsl(var(--glass-border) / var(--glass-border-alpha));
156
+ }
155
157
 
156
- /* Popovers / sheets / drawers / dialog-like panels — slightly more
157
- * opaque so the content stays legible against the body aurora.
158
- * Overrides the base translucent fill from the frosted-pane block. */
159
- html[data-surface="glass"] [data-slot="popover-content"],
160
- html[data-surface="glass"] [data-slot="sheet-content"],
161
- html[data-surface="glass"] [data-slot="drawer-content"],
162
- html[data-surface="glass"] [data-slot="alert-dialog-content"],
163
- html[data-surface="glass"] [data-slot="dialog-content"],
164
- html[data-surface="glass"] [data-slot="dropdown-menu-content"],
165
- html[data-surface="glass"] [data-slot="dropdown-menu-sub-content"],
166
- html[data-surface="glass"] [data-slot="context-menu-content"],
167
- html[data-surface="glass"] [data-slot="context-menu-sub-content"],
168
- html[data-surface="glass"] [data-slot="hover-card-content"],
169
- html[data-surface="glass"] [data-slot="select-content"],
170
- html[data-surface="glass"] [data-slot="menubar-content"],
171
- html[data-surface="glass"] [data-slot="menubar-sub-content"],
172
- html[data-surface="glass"] [data-slot="navigation-menu-viewport"],
173
- html[data-surface="glass"] [data-slot="tooltip-content"],
174
- html[data-surface="glass"] [data-slot="command"] {
175
- background: hsl(var(--glass-tint) / 0.85);
176
- }
158
+ html[data-surface="glass"] [data-slot="popover-content"],
159
+ html[data-surface="glass"] [data-slot="sheet-content"],
160
+ html[data-surface="glass"] [data-slot="drawer-content"],
161
+ html[data-surface="glass"] [data-slot="alert-dialog-content"],
162
+ html[data-surface="glass"] [data-slot="dialog-content"],
163
+ html[data-surface="glass"] [data-slot="dropdown-menu-content"],
164
+ html[data-surface="glass"] [data-slot="dropdown-menu-sub-content"],
165
+ html[data-surface="glass"] [data-slot="context-menu-content"],
166
+ html[data-surface="glass"] [data-slot="context-menu-sub-content"],
167
+ html[data-surface="glass"] [data-slot="hover-card-content"],
168
+ html[data-surface="glass"] [data-slot="select-content"],
169
+ html[data-surface="glass"] [data-slot="menubar-content"],
170
+ html[data-surface="glass"] [data-slot="menubar-sub-content"],
171
+ html[data-surface="glass"] [data-slot="navigation-menu-viewport"],
172
+ html[data-surface="glass"] [data-slot="tooltip-content"],
173
+ html[data-surface="glass"] [data-slot="command"] {
174
+ background: hsl(var(--glass-tint) / 0.85);
177
175
  }
package/styles/tokens.css CHANGED
@@ -42,8 +42,13 @@
42
42
  --card-foreground: 240 10% 3.9%;
43
43
  --popover: 0 0% 100%;
44
44
  --popover-foreground: 240 10% 3.9%;
45
- --primary: 240 5.9% 10%;
46
- --primary-foreground: 0 0% 98%;
45
+ /* Vivid blue primary (per handoff's rendered theme, not the canonical
46
+ * dark-zinc default in colors_and_type.css). The handoff prototype
47
+ * runs a runtime theme picker that overrides --primary + --ring inline
48
+ * on <html>; this bakes that override into Canvas so every consumer
49
+ * gets the same blue accent without the picker. */
50
+ --primary: 240 79% 60%;
51
+ --primary-foreground: 0 0% 100%;
47
52
  --secondary: 240 4.8% 95.9%;
48
53
  --secondary-foreground: 240 5.9% 10%;
49
54
  --muted: 240 4.8% 95.9%;
@@ -56,7 +61,7 @@
56
61
  --brand-foreground: 0 0% 100%;
57
62
  --border: 240 5.9% 90%;
58
63
  --input: 240 5.9% 90%;
59
- --ring: 240 5.9% 10%;
64
+ --ring: 240 79% 60%;
60
65
  --radius: 0.5rem;
61
66
 
62
67
  /* ── Chart palette ─────────────────────────────────────────── */
@@ -119,8 +124,11 @@
119
124
  --popover: 240 10% 3.9%;
120
125
  --popover-foreground: 0 0% 98%;
121
126
 
122
- --primary: 0 0% 98%;
123
- --primary-foreground: 240 5.9% 10%;
127
+ /* Same vivid blue as light mode so the accent stays consistent
128
+ * across themes (per handoff runtime override). The brighter blue
129
+ * still contrasts white text in dark mode. */
130
+ --primary: 240 79% 60%;
131
+ --primary-foreground: 0 0% 100%;
124
132
 
125
133
  --secondary: 240 3.7% 15.9%;
126
134
  --secondary-foreground: 0 0% 98%;
@@ -136,7 +144,7 @@
136
144
 
137
145
  --border: 240 3.7% 15.9%;
138
146
  --input: 240 3.7% 15.9%;
139
- --ring: 240 4.9% 83.9%;
147
+ --ring: 240 79% 60%;
140
148
 
141
149
  /* Chart palette (Canvas-specific; handoff dashboard uses --primary
142
150
  * for bar fills rather than --chart-1, so these stay as-is). */