@nebutra/tokens 0.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/brands/README.md +66 -0
- package/brands/cosmos/DESIGN.md +356 -0
- package/brands/cosmos/brand.json +209 -0
- package/brands/cosmos/refero-tokens.json +312 -0
- package/brands/gsap/brand.json +18 -91
- package/brands/linear/DESIGN.md +478 -0
- package/brands/linear/brand.json +20 -76
- package/brands/notion/brand.json +20 -90
- package/brands/raycast/brand.json +19 -81
- package/brands/stripe/brand.json +17 -88
- package/brands/vanta/brand.json +17 -101
- package/brands/vercel/brand.json +17 -68
- package/dist/brand-package/index.d.ts +123 -0
- package/dist/brand-package/index.js +2316 -0
- package/dist/brand-package/index.js.map +1 -0
- package/dist/brand-package/use-brand.d.ts +2 -0
- package/dist/brand-package/use-brand.js +963 -0
- package/dist/brand-package/use-brand.js.map +1 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.js +2581 -0
- package/dist/index.js.map +1 -0
- package/dist/use-brand-ClmvK_lC.d.ts +387 -0
- package/package.json +41 -10
- package/recipe.css +330 -1
- package/skins/README.md +2 -4
- package/skins/cosmos.css +239 -0
- package/skins/gsap.css +62 -138
- package/skins/linear.css +85 -132
- package/skins/notion.css +62 -141
- package/skins/raycast.css +68 -137
- package/skins/stripe.css +60 -143
- package/skins/vanta.css +60 -151
- package/skins/vercel.css +83 -128
- package/styles.css +477 -115
- package/.turbo/turbo-build.log +0 -8
- package/.turbo/turbo-typecheck.log +0 -4
- package/AGENTS.md +0 -45
- package/DESIGN.md +0 -357
- package/scripts/compile-brand-run.ts +0 -79
- package/scripts/compile-brand.mjs +0 -25
- package/scripts/emit-skins-run.ts +0 -62
- package/scripts/emit-skins.mjs +0 -31
- package/scripts/sync-styles.mjs +0 -29
- package/src/brand-package/__tests__/compile-refero.test.ts +0 -427
- package/src/brand-package/__tests__/emit-css.test.ts +0 -130
- package/src/brand-package/apply-brand.ts +0 -92
- package/src/brand-package/compile-helpers.ts +0 -170
- package/src/brand-package/compile-refero.ts +0 -69
- package/src/brand-package/emit-css.ts +0 -468
- package/src/brand-package/hex-to-hsl.ts +0 -100
- package/src/brand-package/index.ts +0 -71
- package/src/brand-package/infer-recipe.ts +0 -198
- package/src/brand-package/normalize.ts +0 -295
- package/src/brand-package/presets/context.ts +0 -17
- package/src/brand-package/presets/generic.ts +0 -214
- package/src/brand-package/presets/gsap.ts +0 -146
- package/src/brand-package/presets/index.ts +0 -31
- package/src/brand-package/presets/linear.ts +0 -183
- package/src/brand-package/presets/notion.ts +0 -208
- package/src/brand-package/presets/raycast.ts +0 -138
- package/src/brand-package/presets/recipe.ts +0 -61
- package/src/brand-package/presets/stripe.ts +0 -207
- package/src/brand-package/presets/vanta.ts +0 -220
- package/src/brand-package/presets/vercel.ts +0 -187
- package/src/brand-package/types.ts +0 -285
- package/src/brand-package/use-brand.ts +0 -207
- package/src/brand-package/validate.ts +0 -108
- package/src/index.ts +0 -67
- package/src/theme-provider.tsx +0 -246
- package/tsconfig.json +0 -11
- package/turbo.json +0 -15
package/styles.css
CHANGED
|
@@ -73,23 +73,24 @@
|
|
|
73
73
|
--breakpoint-tight: 67.5rem; /** 1080px — dense dashboard header horizontal */
|
|
74
74
|
--breakpoint-shell: 73.75rem; /** 1180px — three-pane shell threshold */
|
|
75
75
|
--breakpoint-3xl: 112.5rem; /** 1800px — 4K / Studio Display friendliness */
|
|
76
|
-
--radius-none: 0;
|
|
77
|
-
--radius-
|
|
78
|
-
--radius-
|
|
79
|
-
--radius-
|
|
80
|
-
--radius-
|
|
81
|
-
--radius-
|
|
82
|
-
--radius-
|
|
83
|
-
--radius-
|
|
84
|
-
--radius-
|
|
85
|
-
--radius-
|
|
86
|
-
--radius-
|
|
76
|
+
--radius-none: 0; /** Job: flush. Edge-to-edge surfaces — a full-bleed table row, a segment welded to its neighbour, an inset that must not round twice. */
|
|
77
|
+
--radius-xs: 0.125rem; /** Job: swatch. The smallest corner that still reads as intentional at 8-12px: chart legend keys, mark highlights, progress and sparkline bar caps. Added because 18 call sites had hand-written 1px, 2px and 3px corners with no step to reach for. Matches the bottom of the Tailwind scale, so rounded-xs and var(--radius-xs) agree. */
|
|
78
|
+
--radius-sm: 0.25rem; /** Job: inner. The corner of something nested inside a control — a thumbnail inside a chip, a swatch inside a tile, a code span inside a paragraph. One step inside md. */
|
|
79
|
+
--radius-md: 0.375rem; /** Job: control. The default for anything you can click or type into at text height: input, select trigger, small button, menu item, badge with square corners. The most-used step on the ladder. */
|
|
80
|
+
--radius-lg: 0.5rem; /** Job: container. Things that hold controls rather than being one: popover, dropdown surface, tooltip, toolbar, inline code block. Aliased as radius-button. */
|
|
81
|
+
--radius-xl: 0.75rem; /** Job: card. A discrete surface in a list or grid that carries its own padding and elevation. Aliased as radius-card. */
|
|
82
|
+
--radius-2xl: 1rem; /** Job: panel. A region that holds cards — bento cell, dialog, sheet, dashboard section. Aliased as radius-panel. */
|
|
83
|
+
--radius-3xl: 1.5rem; /** Job: frame. The outermost shell of a marketing or docs composition — mockup window, hero frame, screenshot chrome. The top of the ladder. */
|
|
84
|
+
--radius-full: 9999px; /** Job: pill. Fully round by intent, not by size: avatar, dot, toggle track, tag. Kept as one step, not a pair, so there is exactly one way to say round. */
|
|
85
|
+
--radius-button: 0.5rem; /** Intent alias for the container step. Reach for this in product code instead of lg so the shape can be re-tuned in one place. */
|
|
86
|
+
--radius-card: 0.75rem; /** Intent alias for the card step. Reach for this in product code instead of xl. */
|
|
87
|
+
--radius-panel: 1rem; /** Intent alias for the panel step. Reach for this in product code instead of 2xl. */
|
|
87
88
|
--duration-micro: 100ms; /** Micro-feedback: hover, focus, toggle, button press */
|
|
88
89
|
--duration-flow: 200ms; /** State flow: modal open, dropdown reveal, tab switch, default page transition */
|
|
89
90
|
--duration-reveal: 300ms; /** Content unveil: slide-in, expand, accordion, drawer */
|
|
90
91
|
--duration-cinematic: 500ms; /** Hero-grade entrance: landing reveal, large delight moments */
|
|
91
92
|
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
|
92
|
-
--ease-out: cubic-bezier(0,
|
|
93
|
+
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
93
94
|
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
|
94
95
|
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
95
96
|
--tracking-display: -0.04em; /** Hero H1 */
|
|
@@ -100,15 +101,17 @@
|
|
|
100
101
|
--ring-hairline: inset 0 0 0 1px rgb(255 255 255 / 0.04); /** Etched-glass hairline (light mode value, dark mode override in themes/dark.json) */
|
|
101
102
|
--overlay-faint: rgb(0 0 0 / 0.06); /** Light-mode faint overlay (6% black) */
|
|
102
103
|
--font-sans:
|
|
103
|
-
var(--font-geist-sans), "Geist", "Noto Sans SC", "PingFang SC",
|
|
104
|
-
-apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
105
|
-
--font-cn:
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), "PingFang SC",
|
|
105
|
+
"Microsoft YaHei", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
106
|
+
--font-cn:
|
|
107
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), "PingFang SC",
|
|
108
|
+
"Microsoft YaHei", sans-serif; /** CJK-locale stack — same order as sans, minus the Latin-only system faces */
|
|
109
|
+
--font-display:
|
|
110
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), sans-serif;
|
|
111
|
+
--font-heading:
|
|
112
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), sans-serif;
|
|
108
113
|
--font-mono:
|
|
109
114
|
var(--font-geist-mono), "Geist Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
|
110
|
-
--transition-duration: 200ms; /** Default transition duration — 200ms flow rail. */
|
|
111
|
-
--transition-easing: ease-out; /** ease-out keyword form (CSS-friendly). Equivalent to cubic-bezier(0,0,0.2,1). */
|
|
112
115
|
--brand-primary: #0033fe; /** Brand primary — 云毓蓝 */
|
|
113
116
|
--brand-accent: #0bf1c3; /** Brand accent — 云毓青 */
|
|
114
117
|
--brand-tertiary: #8b5cf6;
|
|
@@ -154,44 +157,46 @@
|
|
|
154
157
|
--neutral-3: #f1f5f9; /** component bg default */
|
|
155
158
|
--neutral-4: #e2e8f0; /** component bg hover */
|
|
156
159
|
--neutral-5: #cbd5e1; /** component bg active */
|
|
157
|
-
--neutral-6: #
|
|
158
|
-
--neutral-7: #
|
|
159
|
-
--neutral-8: #
|
|
160
|
+
--neutral-6: #b0bccb; /** subtle border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp step5 → step9 at t=0.25. The $value here is a placeholder anchor only; the preprocessor overwrites it. */
|
|
161
|
+
--neutral-7: #96a3b5; /** default border — DERIVED: OKLab lerp step5 → step9 at t=0.50. */
|
|
162
|
+
--neutral-8: #7d8ba0; /** hovered border — DERIVED: OKLab lerp step5 → step9 at t=0.75. */
|
|
160
163
|
--neutral-9: #64748b; /** solid fill */
|
|
161
164
|
--neutral-10: #475569; /** solid fill hover */
|
|
162
165
|
--neutral-11: #334155; /** secondary text */
|
|
163
166
|
--neutral-12: #0f172a; /** primary text */
|
|
164
|
-
--blue-1: #
|
|
165
|
-
--blue-2: #
|
|
166
|
-
--blue-3: #
|
|
167
|
-
--blue-4: #
|
|
168
|
-
--blue-5: #
|
|
169
|
-
--blue-6: #
|
|
170
|
-
--blue-7: #
|
|
171
|
-
--blue-8: #
|
|
172
|
-
--blue-9: #
|
|
173
|
-
--blue-10: #
|
|
174
|
-
--blue-11: #
|
|
175
|
-
--blue-12: #
|
|
176
|
-
--
|
|
177
|
-
--cyan-
|
|
178
|
-
--cyan-
|
|
179
|
-
--cyan-
|
|
180
|
-
--cyan-
|
|
181
|
-
--cyan-
|
|
182
|
-
--cyan-
|
|
183
|
-
--cyan-
|
|
184
|
-
--cyan-
|
|
185
|
-
--cyan-
|
|
186
|
-
--cyan-
|
|
187
|
-
--cyan-
|
|
167
|
+
--blue-1: #fafcfe; /** app tint — L* 99.01, C 0.003, hue 264. Radix blue-1 sits at L* 99.31 C 0.003. */
|
|
168
|
+
--blue-2: #f2f6fd; /** subtle background — L* 97.21, C 0.010. */
|
|
169
|
+
--blue-3: #e7eefc; /** component background default — L* 94.79, C 0.020. */
|
|
170
|
+
--blue-4: #d8e4fb; /** component background hover — L* 91.69, C 0.034. */
|
|
171
|
+
--blue-5: #c6d7fa; /** component background active — L* 87.77, C 0.052. Anchor for the derived border tier. */
|
|
172
|
+
--blue-6: #9fbcf9; /** subtle border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (deep scale, span 32.3 L*) at t=0.25. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 79.61, C 0.093. */
|
|
173
|
+
--blue-7: #79a1f7; /** default border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (deep scale, span 32.3 L*) at t=0.50. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 71.63, C 0.134. */
|
|
174
|
+
--blue-8: #5384f3; /** hovered border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (deep scale, span 32.3 L*) at t=0.75. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 63.47, C 0.175. */
|
|
175
|
+
--blue-9: #2e65ee; /** solid fill — L* 55.50, C 0.215, hue 264.0. White label 4.99:1, inside the 4.7-5.4 band comparable products ship. Same value as shadcn --primary so the deep solid fill has one source, not two. The VI hex #0033FE (C 0.290, L* 48.4) stays the identity token --brand-primary and is deliberately NOT pinned into this ramp slot: pinning it left steps 6-9 only 3.5 L* apart with chroma climbing to 0.290. */
|
|
176
|
+
--blue-10: #2253d4; /** solid fill hover — L* 49.52, C 0.205. White label 6.45:1. */
|
|
177
|
+
--blue-11: #1740ad; /** secondary text — L* 42.04, C 0.180. 8.60:1 on step 1. */
|
|
178
|
+
--blue-12: #031654; /** primary text — L* 23.91, C 0.115. 16.38:1 on step 1. */
|
|
179
|
+
--blue-contrast: #ffffff; /** Label ink for the solid fill tier. Absolute value, identical in both modes: a deep fill always takes white, never the scale's own step 12, which is light in dark mode. 4.99:1 on step 9, 6.45:1 on step 10. */
|
|
180
|
+
--cyan-1: #f9fdfb; /** app tint — L* 99.05, C 0.005, hue 172. */
|
|
181
|
+
--cyan-2: #effbf7; /** subtle background — L* 97.81, C 0.014. */
|
|
182
|
+
--cyan-3: #daf9ee; /** component background default — L* 95.68, C 0.035. */
|
|
183
|
+
--cyan-4: #c3f6e4; /** component background hover — L* 93.29, C 0.058. */
|
|
184
|
+
--cyan-5: #a3f2d8; /** component background active — L* 90.27, C 0.086. Anchor for the derived border tier. */
|
|
185
|
+
--cyan-6: #8eefd1; /** subtle border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step10 (bright scale: |L9-L5| = 4.9 L* < 6.0, so the tier continues toward step 10) at t=0.25. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 88.38, C 0.102. */
|
|
186
|
+
--cyan-7: #77ecc9; /** default border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step10 (bright scale: |L9-L5| = 4.9 L* < 6.0, so the tier continues toward step 10) at t=0.50. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 86.50, C 0.119. */
|
|
187
|
+
--cyan-8: #5be8c2; /** hovered border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step10 (bright scale: |L9-L5| = 4.9 L* < 6.0, so the tier continues toward step 10) at t=0.75. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 84.42, C 0.134. */
|
|
188
|
+
--cyan-9: #0bf1c3; /** solid fill — bright scale, so the fill is LIGHT and takes dark ink: L* 85.41, C 0.164, hue 172.1. Ink 10.35:1 against --cyan-contrast. Step 9 legitimately sits above step 8 on a bright scale, exactly as Radix mint does (8 at L* 72.2, 9 at L* 87.0). */
|
|
189
|
+
--cyan-10: #33e5bb; /** solid fill hover — L* 82.61, C 0.150. 2.80 L* below step 9, matching Radix mint's own 2.95 L* step9-to-step10 drop, so hovering the bright fill deepens it slightly instead of collapsing past its own border tier. The previous value was 10.87 L* below step 9 and landed DARKER than step 8. Ink 9.40:1. */
|
|
190
|
+
--cyan-11: #027a61; /** secondary text — L* 51.64, C 0.100. 5.17:1 on step 1. */
|
|
191
|
+
--cyan-12: #004133; /** primary text — L* 33.45, C 0.065. 11.35:1 on step 1. */
|
|
192
|
+
--cyan-contrast: #0d2b25; /** Label ink for the bright fill tier. Absolute near-black, identical in both modes, carrying a trace of the scale hue. A bright fill needs an explicit dark ink; comparing white against the scale's own step 12 picks white in dark mode, where step 12 is light, and measures 1.52:1. 10.35:1 on step 9, 9.40:1 on step 10. */
|
|
188
193
|
--background: 0 0% 100%; /** HSL channel triple — consume via hsl(var(--background)) */
|
|
189
194
|
--foreground: 222 47% 11%;
|
|
190
195
|
--card: 0 0% 100%;
|
|
191
196
|
--card-foreground: 222 47% 11%;
|
|
192
197
|
--popover: 0 0% 100%;
|
|
193
198
|
--popover-foreground: 222 47% 11%;
|
|
194
|
-
--primary:
|
|
199
|
+
--primary: 222.8 85% 55.7%; /** Product action blue. Derived, not borrowed: hue 264 is the VI hue, L* and C were chosen inside the band the industry actually ships (measured OKLCH C 0.167-0.235, L* 54-67 across Vercel, Stripe, Linear, GitHub, Tailwind, Radix, Figma, Notion). #2e65ee = L*55.5 C0.215, white label 4.99:1. The previous 228 85% 56% (#2f56ee, C0.232) also passed at 5.70:1 — this trims it to the middle of the band rather than its top edge. The VI lock --blue-9 (#0033FE, C0.290) is 23% above anything in that band and must never surface on a component. */
|
|
195
200
|
--primary-foreground: 0 0% 100%;
|
|
196
201
|
--secondary: 210 40% 96%;
|
|
197
202
|
--secondary-foreground: 222 47% 11%;
|
|
@@ -201,10 +206,13 @@
|
|
|
201
206
|
--accent-foreground: 228 85% 56%; /** Softer brand blue — desaturated to match --primary, avoids 100% saturation glare on focus/accent */
|
|
202
207
|
--destructive: 0 84% 45%;
|
|
203
208
|
--destructive-foreground: 0 0% 100%;
|
|
209
|
+
--destructive-strong: 0 84% 45%; /** Red as FOREGROUND. In light mode --destructive already passes (5.42:1 on white), so this equals it; the token exists so consumers reference one name across themes. In DARK, --destructive is 0 63% 38% = 2.15:1 on --card, unreadable as text — see dark.json. Mirrors --warning-strong. */
|
|
204
210
|
--success: 142 71% 29%;
|
|
205
211
|
--success-foreground: 0 0% 100%;
|
|
212
|
+
--success-strong: 142 71% 27%; /** Green as FOREGROUND. --success (142 71% 29%) is 5.15:1 on white but only 4.49:1 as ink on its own 10% tint — one hundredth under AA, which is where badge green-subtle sat. Darkened two points: 5.71:1 on white, 4.99:1 on the tint, alongside --warning-strong (5.40) and --destructive-strong (5.42) so the three status foregrounds read with the same weight. Completes the trio; use --success for fills with --success-foreground on top. */
|
|
206
213
|
--warning: 38 92% 50%;
|
|
207
214
|
--warning-foreground: 222 47% 11%;
|
|
215
|
+
--warning-strong: 38 92% 30%; /** Amber as FOREGROUND on a light surface. --warning is a fill: at 38 92% 50% it is 2.04:1 against --neutral-2, so it is unreadable as text or as an icon stroke. This darkened step is 5.17:1, which sits between --success (4.92) and --destructive (5.19) so the three status colours read with the same weight. Use --warning for fills with --warning-foreground on top; use this for amber text and icons. Destructive and success need no equivalent — they already pass AA as foreground. */
|
|
208
216
|
--info: 228 85% 56%;
|
|
209
217
|
--info-foreground: 0 0% 100%;
|
|
210
218
|
--border: 240 5.9% 90%;
|
|
@@ -263,6 +271,30 @@
|
|
|
263
271
|
--elevation-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
|
264
272
|
--elevation-brand: 0 0 0 1px rgb(0 51 254 / 0.15), 0 4px 20px -2px rgb(0 51 254 / 0.2);
|
|
265
273
|
--elevation-brand-lg: 0 0 0 1px rgb(0 51 254 / 0.2), 0 8px 40px -4px rgb(0 51 254 / 0.3);
|
|
274
|
+
--elevation-ambient-sm:
|
|
275
|
+
0 1px 2px -1px hsl(222 47% 11% / 0.06), 0 6px 16px -6px hsl(222 47% 11% / 0.12); /** Ambient elevation, small. Contact layer plus a wide soft pool — the marketing-surface counterpart to the tight xs..2xl product ramp. */
|
|
276
|
+
--elevation-ambient-md:
|
|
277
|
+
0 2px 4px -2px hsl(222 47% 11% / 0.06), 0 12px 32px -10px hsl(222 47% 11% / 0.14); /** Ambient elevation, medium. Default resting depth for a floating marketing card. */
|
|
278
|
+
--elevation-ambient-lg:
|
|
279
|
+
0 4px 8px -4px hsl(222 47% 11% / 0.06), 0 24px 56px -18px hsl(222 47% 11% / 0.16); /** Ambient elevation, large. One step up from md; use for hover of a md surface, not as a resting default. */
|
|
280
|
+
--elevation-glass-sm:
|
|
281
|
+
inset 0 1px 0 rgb(255 255 255 / 0.5), 0 1px 2px -1px hsl(222 47% 11% / 0.06),
|
|
282
|
+
0 6px 16px -6px hsl(222 47% 11% / 0.12); /** Ambient-sm plus a lit top edge, for translucent panels over a blurred backdrop. */
|
|
283
|
+
--elevation-glass-md:
|
|
284
|
+
inset 0 1px 0 rgb(255 255 255 / 0.5), 0 2px 4px -2px hsl(222 47% 11% / 0.06),
|
|
285
|
+
0 12px 32px -10px hsl(222 47% 11% / 0.14); /** Ambient-md plus a lit top edge. */
|
|
286
|
+
--elevation-glass-lg:
|
|
287
|
+
inset 0 1px 0 rgb(255 255 255 / 0.5), 0 4px 8px -4px hsl(222 47% 11% / 0.06),
|
|
288
|
+
0 24px 56px -18px hsl(222 47% 11% / 0.16); /** Ambient-lg plus a lit top edge. */
|
|
289
|
+
--elevation-sheen: inset 0 1px 0 rgb(255 255 255 / 0.2); /** Top-edge sheen for solid inverted fills (bg-foreground). Lifts with light on a dark fill, with shade on a light one. */
|
|
290
|
+
--elevation-ambient-glow: 0 0 80px hsl(222 47% 11% / 0.1); /** Centred offsetless halo — the only non-directional step on the ramp. For a translucent panel that needs separation from its backdrop without reading as lifted off a surface. In dark mode this inverts to a low-alpha light halo, because a black halo cannot read against a dark surround. */
|
|
291
|
+
--elevation-glow-accent:
|
|
292
|
+
0 8px 32px rgb(0 0 0 / 0.4), 0 0 60px color-mix(in srgb, var(--brand-accent) 8%, transparent); /** Directional pool plus a wide brand-accent halo, for fixed dark-glass surfaces. The pool is theme-invariant black on purpose: the surfaces that carry an accent halo present the same dark glass in both themes, so a theme-flipping pool would lighten in light mode exactly where the panel needs the most separation. Accent sourced from --brand-accent, so it follows the light and dark accent values. */
|
|
293
|
+
--elevation-glow-accent-lg:
|
|
294
|
+
0 12px 48px rgb(0 0 0 / 0.5), 0 0 80px color-mix(in srgb, var(--brand-accent) 12%, transparent); /** Hover escalation of glow-accent. Deeper pool, wider and stronger accent halo. */
|
|
295
|
+
--elevation-glow-accent-sm: 0 0 20px color-mix(in srgb, var(--brand-accent) 10%, transparent); /** Accent halo alone, no pool. For a small accent-tinted chip or icon plate inside a glow-accent surface. */
|
|
296
|
+
--elevation-glow-primary:
|
|
297
|
+
0 20px 25px -5px hsl(var(--primary) / 0.05), 0 8px 10px -6px hsl(var(--primary) / 0.05); /** xl geometry tinted with the action fill at 5 percent. For the emphasised card in a set (a highlighted plan) where the lift should read as coloured rather than neutral. */
|
|
266
298
|
}
|
|
267
299
|
|
|
268
300
|
:root {
|
|
@@ -368,23 +400,24 @@
|
|
|
368
400
|
--breakpoint-tight: 67.5rem; /** 1080px — dense dashboard header horizontal */
|
|
369
401
|
--breakpoint-shell: 73.75rem; /** 1180px — three-pane shell threshold */
|
|
370
402
|
--breakpoint-3xl: 112.5rem; /** 1800px — 4K / Studio Display friendliness */
|
|
371
|
-
--radius-none: 0;
|
|
372
|
-
--radius-
|
|
373
|
-
--radius-
|
|
374
|
-
--radius-
|
|
375
|
-
--radius-
|
|
376
|
-
--radius-
|
|
377
|
-
--radius-
|
|
378
|
-
--radius-
|
|
379
|
-
--radius-
|
|
380
|
-
--radius-
|
|
381
|
-
--radius-
|
|
403
|
+
--radius-none: 0; /** Job: flush. Edge-to-edge surfaces — a full-bleed table row, a segment welded to its neighbour, an inset that must not round twice. */
|
|
404
|
+
--radius-xs: 0.125rem; /** Job: swatch. The smallest corner that still reads as intentional at 8-12px: chart legend keys, mark highlights, progress and sparkline bar caps. Added because 18 call sites had hand-written 1px, 2px and 3px corners with no step to reach for. Matches the bottom of the Tailwind scale, so rounded-xs and var(--radius-xs) agree. */
|
|
405
|
+
--radius-sm: 0.25rem; /** Job: inner. The corner of something nested inside a control — a thumbnail inside a chip, a swatch inside a tile, a code span inside a paragraph. One step inside md. */
|
|
406
|
+
--radius-md: 0.375rem; /** Job: control. The default for anything you can click or type into at text height: input, select trigger, small button, menu item, badge with square corners. The most-used step on the ladder. */
|
|
407
|
+
--radius-lg: 0.5rem; /** Job: container. Things that hold controls rather than being one: popover, dropdown surface, tooltip, toolbar, inline code block. Aliased as radius-button. */
|
|
408
|
+
--radius-xl: 0.75rem; /** Job: card. A discrete surface in a list or grid that carries its own padding and elevation. Aliased as radius-card. */
|
|
409
|
+
--radius-2xl: 1rem; /** Job: panel. A region that holds cards — bento cell, dialog, sheet, dashboard section. Aliased as radius-panel. */
|
|
410
|
+
--radius-3xl: 1.5rem; /** Job: frame. The outermost shell of a marketing or docs composition — mockup window, hero frame, screenshot chrome. The top of the ladder. */
|
|
411
|
+
--radius-full: 9999px; /** Job: pill. Fully round by intent, not by size: avatar, dot, toggle track, tag. Kept as one step, not a pair, so there is exactly one way to say round. */
|
|
412
|
+
--radius-button: 0.5rem; /** Intent alias for the container step. Reach for this in product code instead of lg so the shape can be re-tuned in one place. */
|
|
413
|
+
--radius-card: 0.75rem; /** Intent alias for the card step. Reach for this in product code instead of xl. */
|
|
414
|
+
--radius-panel: 1rem; /** Intent alias for the panel step. Reach for this in product code instead of 2xl. */
|
|
382
415
|
--duration-micro: 100ms; /** Micro-feedback: hover, focus, toggle, button press */
|
|
383
416
|
--duration-flow: 200ms; /** State flow: modal open, dropdown reveal, tab switch, default page transition */
|
|
384
417
|
--duration-reveal: 300ms; /** Content unveil: slide-in, expand, accordion, drawer */
|
|
385
418
|
--duration-cinematic: 500ms; /** Hero-grade entrance: landing reveal, large delight moments */
|
|
386
419
|
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
|
387
|
-
--ease-out: cubic-bezier(0,
|
|
420
|
+
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
388
421
|
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
|
389
422
|
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
390
423
|
--tracking-display: -0.04em; /** Hero H1 */
|
|
@@ -397,17 +430,19 @@
|
|
|
397
430
|
255 255 255 / 0.06
|
|
398
431
|
); /** Dark-mode faint overlay (6% white) — inverted from light */
|
|
399
432
|
--font-sans:
|
|
400
|
-
var(--font-geist-sans), "Geist", "Noto Sans SC", "PingFang SC",
|
|
401
|
-
-apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
402
|
-
--font-cn:
|
|
403
|
-
|
|
404
|
-
|
|
433
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), "PingFang SC",
|
|
434
|
+
"Microsoft YaHei", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
435
|
+
--font-cn:
|
|
436
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), "PingFang SC",
|
|
437
|
+
"Microsoft YaHei", sans-serif; /** CJK-locale stack — same order as sans, minus the Latin-only system faces */
|
|
438
|
+
--font-display:
|
|
439
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), sans-serif;
|
|
440
|
+
--font-heading:
|
|
441
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), sans-serif;
|
|
405
442
|
--font-mono:
|
|
406
443
|
var(--font-geist-mono), "Geist Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
|
407
|
-
--
|
|
408
|
-
--
|
|
409
|
-
--brand-primary: #5c7cfa; /** Dark-mode brand primary — visible against dark surfaces (uses --blue-9 = nebutra-blue-400). */
|
|
410
|
-
--brand-accent: #1af7c8; /** Dark-mode brand accent — visible against dark surfaces (uses --cyan-9 = nebutra-cyan-400). */
|
|
444
|
+
--brand-primary: #5c7cfa; /** Dark-mode brand primary — the identity blue lifted to nebutra-blue-400 so it stays visible on dark surfaces. This is the identity alias, not the functional fill: the dark solid fill is --blue-9 (#396ae2, white label 4.85:1). The two are deliberately separate now that the VI hex is no longer pinned into ramp slot 9. */
|
|
445
|
+
--brand-accent: #1af7c8; /** Dark-mode brand accent — nebutra-cyan-400 (L* 87.1). The functional bright fill is --cyan-9 (#0bf1c3, L* 85.41), the same hex in both modes; dark ink for it is --cyan-contrast at 10.35:1. */
|
|
411
446
|
--brand-tertiary: #8b5cf6;
|
|
412
447
|
--brand-gradient-start: hsl(
|
|
413
448
|
var(--primary)
|
|
@@ -451,45 +486,47 @@
|
|
|
451
486
|
--neutral-3: #1e293b;
|
|
452
487
|
--neutral-4: #334155;
|
|
453
488
|
--neutral-5: #475569;
|
|
454
|
-
--neutral-6: #
|
|
455
|
-
--neutral-7: #
|
|
456
|
-
--neutral-8: #
|
|
489
|
+
--neutral-6: #666666; /** subtle border — DERIVED: OKLab lerp step5 → step9 at t=0.25, chroma forced to 0. The achromatic result is deliberate and preserves the documented intent of color.nebutra-gray ('used where blue undertone clashes — primarily dark-mode borders, dividers, elevation hairlines'). What was broken was the LIGHTNESS: gray-900/800/700 are darker than the slate background tier they sit above, so the border read as a hole rather than a hairline. */
|
|
490
|
+
--neutral-7: #7a7a7a; /** default border — DERIVED: OKLab lerp step5 → step9 at t=0.50, chroma 0. */
|
|
491
|
+
--neutral-8: #8d8d8d; /** hovered border — DERIVED: OKLab lerp step5 → step9 at t=0.75, chroma 0. */
|
|
457
492
|
--neutral-9: #94a3b8;
|
|
458
493
|
--neutral-10: #cbd5e1;
|
|
459
494
|
--neutral-11: #e2e8f0;
|
|
460
495
|
--neutral-12: #f8fafc;
|
|
461
|
-
--blue-1: #
|
|
462
|
-
--blue-2: #
|
|
463
|
-
--blue-3: #
|
|
464
|
-
--blue-4: #
|
|
465
|
-
--blue-5: #
|
|
466
|
-
--blue-6: #
|
|
467
|
-
--blue-7: #
|
|
468
|
-
--blue-8: #
|
|
469
|
-
--blue-9: #
|
|
470
|
-
--blue-10: #
|
|
471
|
-
--blue-11: #
|
|
472
|
-
--blue-12: #
|
|
473
|
-
--
|
|
474
|
-
--cyan-
|
|
475
|
-
--cyan-
|
|
476
|
-
--cyan-
|
|
477
|
-
--cyan-
|
|
478
|
-
--cyan-
|
|
479
|
-
--cyan-
|
|
480
|
-
--cyan-
|
|
481
|
-
--cyan-
|
|
482
|
-
--cyan-
|
|
483
|
-
--cyan-
|
|
484
|
-
--cyan-
|
|
496
|
+
--blue-1: #030815; /** app tint — L* 13.59, C 0.031, hue 264. */
|
|
497
|
+
--blue-2: #060f25; /** subtle background — L* 17.43, C 0.048. */
|
|
498
|
+
--blue-3: #09183b; /** component background default — L* 22.04, C 0.071. */
|
|
499
|
+
--blue-4: #0d2254; /** component background hover — L* 26.99, C 0.095. */
|
|
500
|
+
--blue-5: #132e6f; /** component background active — L* 32.46, C 0.118. Anchor for the derived border tier. */
|
|
501
|
+
--blue-6: #1c3c8a; /** subtle border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (deep scale, span 23.5 L*) at t=0.25. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 38.25, C 0.136. */
|
|
502
|
+
--blue-7: #254ba7; /** default border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (deep scale, span 23.5 L*) at t=0.50. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 44.21, C 0.155. */
|
|
503
|
+
--blue-8: #2f5ac4; /** hovered border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (deep scale, span 23.5 L*) at t=0.75. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 50.01, C 0.173. */
|
|
504
|
+
--blue-9: #396ae2; /** solid fill — L* 55.92, C 0.191, hue 264.3. White label 4.85:1. Same value as the dark shadcn --primary, so light and dark both take their deep fill from one source. Chroma ceiling for dark is 0.190; this is 0.191. */
|
|
505
|
+
--blue-10: #3f70e3; /** solid fill hover — L* 57.44, C 0.183. White label 4.53:1. The hover has to move UP in a dark ramp, which costs contrast, so the step is deliberately small (1.52 L*) to keep white above 4.5:1. */
|
|
506
|
+
--blue-11: #89aaee; /** secondary text — L* 74.01, C 0.106. 8.62:1 on step 1. */
|
|
507
|
+
--blue-12: #d2e0fd; /** primary text — L* 90.52, C 0.043. 15.08:1 on step 1. */
|
|
508
|
+
--blue-contrast: #ffffff; /** Label ink for the solid fill tier. Absolute value, identical in both modes. 4.85:1 on step 9, 4.53:1 on step 10. */
|
|
509
|
+
--cyan-1: #04100c; /** app tint — L* 15.90, C 0.021, hue 172. */
|
|
510
|
+
--cyan-2: #091e18; /** subtle background — L* 21.54, C 0.030. */
|
|
511
|
+
--cyan-3: #0f2e25; /** component background default — L* 27.46, C 0.041. */
|
|
512
|
+
--cyan-4: #1c4035; /** component background hover — L* 34.10, C 0.046. */
|
|
513
|
+
--cyan-5: #2b5347; /** component background active — L* 40.88, C 0.050. Anchor for the derived border tier. */
|
|
514
|
+
--cyan-6: #307864; /** subtle border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (span 44.5 L*) at t=0.25. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 52.12, C 0.079. */
|
|
515
|
+
--cyan-7: #309e82; /** default border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (span 44.5 L*) at t=0.50. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 63.04, C 0.107. */
|
|
516
|
+
--cyan-8: #28c7a2; /** hovered border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (span 44.5 L*) at t=0.75. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 74.30, C 0.136. */
|
|
517
|
+
--cyan-9: #0bf1c3; /** solid fill — same bright value as light mode, the way Radix ships one hex for mint-9 in both modes: L* 85.41, C 0.164. Ink 10.35:1 against --cyan-contrast. */
|
|
518
|
+
--cyan-10: #5af8ce; /** solid fill hover — L* 88.53, C 0.145, 3.12 L* above step 9 (Radix mint dark moves up 4.60 L*). Ink 11.34:1. */
|
|
519
|
+
--cyan-11: #90fbdb; /** secondary text — L* 91.46, C 0.110. 15.68:1 on step 1. */
|
|
520
|
+
--cyan-12: #bdfde8; /** primary text — L* 94.54, C 0.070. 16.95:1 on step 1. */
|
|
521
|
+
--cyan-contrast: #0d2b25; /** Label ink for the bright fill tier. Absolute near-black, identical in both modes. 10.35:1 on step 9, 11.34:1 on step 10. */
|
|
485
522
|
--background: 222 14% 9%;
|
|
486
523
|
--foreground: 210 18% 96%;
|
|
487
524
|
--card: 222 12% 13%;
|
|
488
525
|
--card-foreground: 210 18% 96%;
|
|
489
526
|
--popover: 222 12% 13%;
|
|
490
527
|
--popover-foreground: 210 18% 96%;
|
|
491
|
-
--primary:
|
|
492
|
-
--primary-foreground: 222 14% 9
|
|
528
|
+
--primary: 222.6 74.4% 55.5%; /** Dark-mode action blue, paired with the near-black --primary-foreground, not white. #396ae2 keeps the VI hue and sits at L*55.9 C0.191. */
|
|
529
|
+
--primary-foreground: 0 0% 100%; /** White, matching light mode and matching --blue-contrast. It was near-black (222 14% 9%) when --primary was a LIGHT fill at L* 68.3, where dark ink read 6.20:1. Once --primary became a deep fill at L* 55.9 the pairing inverted and measured 3.74:1, below AA — the fill moved and the ink did not. The deep-fill tier takes white in both modes; --blue-contrast says the same thing, and they must agree because --primary and --blue-9 are now the same value. */
|
|
493
530
|
--secondary: 222 10% 18%;
|
|
494
531
|
--secondary-foreground: 210 18% 96%;
|
|
495
532
|
--muted: 222 10% 17%;
|
|
@@ -498,10 +535,13 @@
|
|
|
498
535
|
--accent-foreground: 228 95% 80%;
|
|
499
536
|
--destructive: 0 63% 38%;
|
|
500
537
|
--destructive-foreground: 210 18% 96%;
|
|
538
|
+
--destructive-strong: 0 80% 66%; /** Red as FOREGROUND on a dark surface. --destructive (0 63% 38%) is 2.15:1 on --card here — a fill only. Lightened to 5.18:1 on --card / 5.61:1 on --neutral-1, which sits alongside the light-mode 5.42:1 so red reads with the same weight in both themes. Use --destructive for fills with --destructive-foreground on top; use this for red text and icons. */
|
|
501
539
|
--success: 142 60% 42%;
|
|
502
540
|
--success-foreground: 222 14% 9%;
|
|
541
|
+
--success-strong: 142 60% 42%; /** Green as foreground on a dark surface. Needs no adjustment — 142 60% 42% is already 4.83:1 as ink on its own 10% tint — so it deliberately equals --success, matching how --warning-strong behaves in this theme. */
|
|
503
542
|
--warning: 38 80% 52%;
|
|
504
543
|
--warning-foreground: 222 14% 9%;
|
|
544
|
+
--warning-strong: 38 80% 52%; /** Amber as foreground on a dark surface. Unlike light mode this needs no adjustment — 38 80% 52% is already 7.98:1 against --neutral-1 — so it deliberately equals --warning. The token exists in both themes so consumers can reference one name. */
|
|
505
545
|
--info: 228 95% 72%;
|
|
506
546
|
--info-foreground: 222 14% 9%;
|
|
507
547
|
--border: 220 8% 22%;
|
|
@@ -560,6 +600,30 @@
|
|
|
560
600
|
--elevation-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.8);
|
|
561
601
|
--elevation-brand: 0 0 0 1px rgb(92 124 250 / 0.25), 0 4px 20px -2px rgb(92 124 250 / 0.3);
|
|
562
602
|
--elevation-brand-lg: 0 0 0 1px rgb(92 124 250 / 0.3), 0 8px 40px -4px rgb(92 124 250 / 0.4);
|
|
603
|
+
--elevation-ambient-sm:
|
|
604
|
+
0 1px 2px -1px rgb(0 0 0 / 0.4), 0 6px 16px -6px rgb(0 0 0 / 0.5); /** Ambient elevation, small. Contact layer plus a wide soft pool — the marketing-surface counterpart to the tight xs..2xl product ramp. */
|
|
605
|
+
--elevation-ambient-md:
|
|
606
|
+
0 2px 4px -2px rgb(0 0 0 / 0.4), 0 12px 32px -10px rgb(0 0 0 / 0.55); /** Ambient elevation, medium. Default resting depth for a floating marketing card. */
|
|
607
|
+
--elevation-ambient-lg:
|
|
608
|
+
0 4px 8px -4px rgb(0 0 0 / 0.4), 0 24px 56px -18px rgb(0 0 0 / 0.6); /** Ambient elevation, large. One step up from md; use for hover of a md surface, not as a resting default. */
|
|
609
|
+
--elevation-glass-sm:
|
|
610
|
+
inset 0 1px 0 rgb(255 255 255 / 0.08), 0 1px 2px -1px rgb(0 0 0 / 0.4),
|
|
611
|
+
0 6px 16px -6px rgb(0 0 0 / 0.5); /** Ambient-sm plus a lit top edge, for translucent panels over a blurred backdrop. */
|
|
612
|
+
--elevation-glass-md:
|
|
613
|
+
inset 0 1px 0 rgb(255 255 255 / 0.08), 0 2px 4px -2px rgb(0 0 0 / 0.4),
|
|
614
|
+
0 12px 32px -10px rgb(0 0 0 / 0.55); /** Ambient-md plus a lit top edge. */
|
|
615
|
+
--elevation-glass-lg:
|
|
616
|
+
inset 0 1px 0 rgb(255 255 255 / 0.08), 0 4px 8px -4px rgb(0 0 0 / 0.4),
|
|
617
|
+
0 24px 56px -18px rgb(0 0 0 / 0.6); /** Ambient-lg plus a lit top edge. */
|
|
618
|
+
--elevation-sheen: inset 0 1px 0 rgb(0 0 0 / 0.12); /** Top-edge sheen for solid inverted fills (bg-foreground). Lifts with light on a dark fill, with shade on a light one. */
|
|
619
|
+
--elevation-ambient-glow: 0 0 80px rgb(255 255 255 / 0.06); /** Centred offsetless halo — the only non-directional step on the ramp. Inverted from the light value: a black halo has nothing to darken against a dark surround, so the halo becomes light. Keep the alpha low; this is separation, not a bloom. */
|
|
620
|
+
--elevation-glow-accent:
|
|
621
|
+
0 8px 32px rgb(0 0 0 / 0.4), 0 0 60px color-mix(in srgb, var(--brand-accent) 8%, transparent); /** Identical to the light value by design. The pool is theme-invariant black because glow-accent surfaces are fixed dark glass in both themes; only the accent halo shifts, via --brand-accent. */
|
|
622
|
+
--elevation-glow-accent-lg:
|
|
623
|
+
0 12px 48px rgb(0 0 0 / 0.5), 0 0 80px color-mix(in srgb, var(--brand-accent) 12%, transparent); /** Hover escalation of glow-accent. Identical to the light value by design. */
|
|
624
|
+
--elevation-glow-accent-sm: 0 0 20px color-mix(in srgb, var(--brand-accent) 10%, transparent); /** Accent halo alone, no pool. Identical to the light value by design. */
|
|
625
|
+
--elevation-glow-primary:
|
|
626
|
+
0 20px 25px -5px hsl(var(--primary) / 0.05), 0 8px 10px -6px hsl(var(--primary) / 0.05); /** xl geometry tinted with the action fill at 5 percent. Same reference as light; --primary carries the dark channels. */
|
|
563
627
|
}
|
|
564
628
|
|
|
565
629
|
.dark {
|
|
@@ -766,6 +830,22 @@
|
|
|
766
830
|
--radius-3xl: 1.5rem;
|
|
767
831
|
--radius-full: 9999px;
|
|
768
832
|
|
|
833
|
+
/* Containers — the page-width contract from CLAUDE.md, as real utilities.
|
|
834
|
+
max-w-text (reading), max-w-content (pricing/blog), max-w-wide (feature bento, navbar).
|
|
835
|
+
|
|
836
|
+
These values also reach :root through the semantic layer, so var(--container-wide) keeps
|
|
837
|
+
working; what was missing was the Tailwind side. Without it there is no max-w-wide to reach
|
|
838
|
+
for, and 92 call sites across apps/** had typed max-w-wide instead — a decided number
|
|
839
|
+
living in a className, where changing the token cannot move it.
|
|
840
|
+
|
|
841
|
+
Literals rather than var(--container-wide): Tailwind emits an @theme entry into its own
|
|
842
|
+
layer, so a same-named reference becomes self-referential and survives only because the
|
|
843
|
+
semantic :root happens to come later in the cascade. Duplicating the number and pinning it
|
|
844
|
+
with a parity test is the honest trade — see design-tokens' container parity test. */
|
|
845
|
+
--container-text: 896px;
|
|
846
|
+
--container-content: 1152px;
|
|
847
|
+
--container-wide: 1400px;
|
|
848
|
+
|
|
769
849
|
/* Shadows */
|
|
770
850
|
--shadow-xs: var(--elevation-xs);
|
|
771
851
|
--shadow-sm: var(--elevation-sm);
|
|
@@ -776,25 +856,84 @@
|
|
|
776
856
|
--shadow-brand: var(--elevation-brand);
|
|
777
857
|
--shadow-brand-lg: var(--elevation-brand-lg);
|
|
778
858
|
|
|
779
|
-
/*
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
--
|
|
783
|
-
--
|
|
859
|
+
/* shadow-ambient-md / shadow-glass-lg / shadow-sheen — so marketing surfaces
|
|
860
|
+
reach for a step on the ramp instead of spelling out a blur and a spread.
|
|
861
|
+
Aliased to var() rather than inlined so the dark theme's values apply. */
|
|
862
|
+
--shadow-ambient-sm: var(--elevation-ambient-sm);
|
|
863
|
+
--shadow-ambient-md: var(--elevation-ambient-md);
|
|
864
|
+
--shadow-ambient-lg: var(--elevation-ambient-lg);
|
|
865
|
+
--shadow-glass-sm: var(--elevation-glass-sm);
|
|
866
|
+
--shadow-glass-md: var(--elevation-glass-md);
|
|
867
|
+
--shadow-glass-lg: var(--elevation-glass-lg);
|
|
868
|
+
--shadow-sheen: var(--elevation-sheen);
|
|
869
|
+
|
|
870
|
+
/* Coloured and centred elevation. shadow-ambient-glow is the one
|
|
871
|
+
non-directional step; shadow-glow-accent|-lg|-sm carry a brand-accent halo
|
|
872
|
+
so a glass card never hand-types the accent as raw rgba; shadow-glow-primary
|
|
873
|
+
tints the xl geometry with the action fill. */
|
|
874
|
+
--shadow-ambient-glow: var(--elevation-ambient-glow);
|
|
875
|
+
--shadow-glow-accent: var(--elevation-glow-accent);
|
|
876
|
+
--shadow-glow-accent-lg: var(--elevation-glow-accent-lg);
|
|
877
|
+
--shadow-glow-accent-sm: var(--elevation-glow-accent-sm);
|
|
878
|
+
--shadow-glow-primary: var(--elevation-glow-primary);
|
|
879
|
+
|
|
880
|
+
/* Motion — Easing curves.
|
|
881
|
+
Indirect, like the shadows above: an @theme entry holding a literal is
|
|
882
|
+
inlined into the utility, so the ease-out class compiled to a fixed
|
|
883
|
+
cubic-bezier and no Brand Package could move it. Pointing at the runtime
|
|
884
|
+
rail in static/base.css makes it follow the language the page is under.
|
|
885
|
+
(No backticks in this block — it is inside a JS template literal.) */
|
|
886
|
+
--ease-in: var(--motion-ease-in);
|
|
887
|
+
--ease-out: var(--motion-ease-out);
|
|
888
|
+
--ease-brand: var(--motion-ease-brand);
|
|
889
|
+
--ease-in-out: var(--motion-ease-in-out);
|
|
890
|
+
--ease-spring: var(--motion-ease-spring);
|
|
784
891
|
|
|
785
892
|
/* Motion — Durations (four-rail; see core.json:duration). Names denote intent, not relative speed. */
|
|
786
|
-
--duration-micro:
|
|
787
|
-
--duration-flow:
|
|
788
|
-
--duration-reveal:
|
|
789
|
-
--duration-cinematic:
|
|
790
|
-
|
|
791
|
-
/*
|
|
893
|
+
--duration-micro: var(--motion-duration-micro);
|
|
894
|
+
--duration-flow: var(--motion-duration-flow);
|
|
895
|
+
--duration-reveal: var(--motion-duration-reveal);
|
|
896
|
+
--duration-cinematic: var(--motion-duration-cinematic);
|
|
897
|
+
|
|
898
|
+
/* The same four rails on the namespace Tailwind reads for duration-*.
|
|
899
|
+
--duration-* is the variable other CSS consumes; the utility class is built
|
|
900
|
+
from --transition-duration-*, and only that one. Without these,
|
|
901
|
+
duration-micro and duration-reveal are class names that generate nothing:
|
|
902
|
+
they were in use in the component library and on the marketing site, and
|
|
903
|
+
every one of them silently fell back to the 150ms default. */
|
|
904
|
+
--transition-duration-micro: var(--motion-duration-micro);
|
|
905
|
+
--transition-duration-flow: var(--motion-duration-flow);
|
|
906
|
+
--transition-duration-reveal: var(--motion-duration-reveal);
|
|
907
|
+
--transition-duration-cinematic: var(--motion-duration-cinematic);
|
|
908
|
+
|
|
909
|
+
/* Likewise for ease-*: the utility namespace is --ease-* already, so the four
|
|
910
|
+
above are reachable as ease-brand / ease-spring without further mapping. */
|
|
911
|
+
|
|
912
|
+
/* Spacing — DO NOT register --spacing-sm|md|lg|xl|2xl here.
|
|
913
|
+
Tailwind v4 puts every --spacing-{key} on the shared size rail, so
|
|
914
|
+
max-w-sm / max-w-2xl (and width/height/gap utilities with the same keys)
|
|
915
|
+
resolve to the breathing-room lengths (0.75rem to 3rem) instead of the
|
|
916
|
+
container scale (24rem to 42rem). That shipped as one-word-per-line prose
|
|
917
|
+
and a 32px install box on nebutra.com (measured 2026-08-03).
|
|
918
|
+
Brand Packages still override --space-source-*; consumers must read
|
|
919
|
+
those vars directly (var(--space-source-md, ...)), never via a @theme
|
|
920
|
+
--spacing-{step} alias. --spacing itself (0.25rem arithmetic base) stays
|
|
921
|
+
untouched. See static/base.css. */
|
|
922
|
+
|
|
923
|
+
/* Font family — Geist keeps Latin + numerals, self-hosted Noto Sans SC takes
|
|
924
|
+
CJK, system CJK faces behind it. Order is the design decision; see the
|
|
925
|
+
fontFamily $description in tokens/core.json. Mirror of those tokens — keep
|
|
926
|
+
the two in sync (verify:parity compares them). */
|
|
792
927
|
--font-sans:
|
|
793
|
-
var(--font-geist-sans), "Geist", "Noto Sans SC", "PingFang SC",
|
|
794
|
-
-apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
795
|
-
--font-cn:
|
|
796
|
-
|
|
797
|
-
|
|
928
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), "PingFang SC",
|
|
929
|
+
"Microsoft YaHei", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
930
|
+
--font-cn:
|
|
931
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), "PingFang SC",
|
|
932
|
+
"Microsoft YaHei", sans-serif;
|
|
933
|
+
--font-display:
|
|
934
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), sans-serif;
|
|
935
|
+
--font-heading:
|
|
936
|
+
var(--font-geist-sans), "Geist", var(--font-noto-sans-sc, "Noto Sans SC"), sans-serif;
|
|
798
937
|
--font-mono:
|
|
799
938
|
var(--font-geist-mono), "Geist Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
|
800
939
|
}
|
|
@@ -810,6 +949,109 @@
|
|
|
810
949
|
page that re-evaluated on every paint. Mode-aware via CSS cascade.
|
|
811
950
|
Lives in static/ so it survives Style Dictionary rebuilds.
|
|
812
951
|
─────────────────────────────────────────────────────────── */
|
|
952
|
+
/* ─── Motion source rail ────────────────────────────────────
|
|
953
|
+
The runtime half of the motion tokens, and the reason it exists under a
|
|
954
|
+
second name: a theme entry cannot point at itself, so the utility layer
|
|
955
|
+
needs a source that is not the same property. Durations already had that
|
|
956
|
+
pair; the curves did not, which is why the ease utilities compiled to a
|
|
957
|
+
literal cubic-bezier and stayed on the default ramp no matter which brand
|
|
958
|
+
package was applied.
|
|
959
|
+
|
|
960
|
+
Same shape as elevation, where `--shadow-*: var(--elevation-*)` has always
|
|
961
|
+
given the utilities something a skin can move. A Brand Package overrides
|
|
962
|
+
these; nothing else should.
|
|
963
|
+
─────────────────────────────────────────────────────────── */
|
|
964
|
+
:root {
|
|
965
|
+
--motion-ease-in: cubic-bezier(0.4, 0, 1, 1);
|
|
966
|
+
--motion-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
967
|
+
--motion-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
|
968
|
+
--motion-ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
969
|
+
/* The signature: a long, strong deceleration — most of the distance is
|
|
970
|
+
covered in the first fifth of the time, then it settles. It has existed as
|
|
971
|
+
`brandEasing.brand` in @nebutra/brand since the motion language was
|
|
972
|
+
written, usable only from JS, so every CSS transition on every marketing
|
|
973
|
+
surface fell back to Tailwind's default curve instead. That default is
|
|
974
|
+
what "the hovers feel stiff" is: a near-linear ramp at 150ms. */
|
|
975
|
+
--motion-ease-brand: cubic-bezier(0.16, 1, 0.3, 1);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/* ─── Category rail ──────────────────────────────────────────
|
|
979
|
+
Four accents for taxonomy — the monorepo tree's four lanes, and
|
|
980
|
+
anywhere else a small set of peers has to be told apart.
|
|
981
|
+
|
|
982
|
+
Derived, not picked. Hand-picking is how the file tree ended up
|
|
983
|
+
with fifteen hues across twenty-five rows, three of them reused
|
|
984
|
+
on unrelated rows: a colour that encodes nothing a reader can
|
|
985
|
+
learn and follows no design language.
|
|
986
|
+
|
|
987
|
+
Each accent is a position on the background→foreground axis,
|
|
988
|
+
tinted a quarter of the way toward the action fill rotated by
|
|
989
|
+
72°. Two axes, both always live:
|
|
990
|
+
|
|
991
|
+
• the position carries contrast. That axis is non-degenerate
|
|
992
|
+
in every language by definition — it is the axis that makes
|
|
993
|
+
body text readable — so a monochrome language still gets
|
|
994
|
+
four distinct tones. Vercel and Notion anchor on black,
|
|
995
|
+
where rotating hue returns the same colour four times.
|
|
996
|
+
• the rotation carries hue separation for the languages that
|
|
997
|
+
have chroma to give.
|
|
998
|
+
|
|
999
|
+
Two earlier constructions failed here and are worth naming.
|
|
1000
|
+
Offsetting lightness from the anchor and clamping collapses when
|
|
1001
|
+
the anchor already sits near the band edge — Raycast's fill is
|
|
1002
|
+
near-white, so all four pinned to one value. A fixed lightness
|
|
1003
|
+
ladder cannot serve both canvases: Raycast and GSAP are dark by
|
|
1004
|
+
default in light mode, so the `.dark` class is not set and an
|
|
1005
|
+
absolute ladder is upside down for them.
|
|
1006
|
+
|
|
1007
|
+
Measured across all seven languages plus factory: lowest contrast
|
|
1008
|
+
3.50:1 against each canvas, closest pair 30/255 apart.
|
|
1009
|
+
─────────────────────────────────────────────────────────── */
|
|
1010
|
+
:root {
|
|
1011
|
+
--category-1: color-mix(
|
|
1012
|
+
in oklab,
|
|
1013
|
+
color-mix(in oklab, hsl(var(--foreground)) 55%, hsl(var(--background))) 75%,
|
|
1014
|
+
oklch(from hsl(var(--primary)) l c h)
|
|
1015
|
+
);
|
|
1016
|
+
--category-2: color-mix(
|
|
1017
|
+
in oklab,
|
|
1018
|
+
color-mix(in oklab, hsl(var(--foreground)) 67%, hsl(var(--background))) 75%,
|
|
1019
|
+
oklch(from hsl(var(--primary)) l c calc(h + 72))
|
|
1020
|
+
);
|
|
1021
|
+
--category-3: color-mix(
|
|
1022
|
+
in oklab,
|
|
1023
|
+
color-mix(in oklab, hsl(var(--foreground)) 79%, hsl(var(--background))) 75%,
|
|
1024
|
+
oklch(from hsl(var(--primary)) l c calc(h + 144))
|
|
1025
|
+
);
|
|
1026
|
+
--category-4: color-mix(
|
|
1027
|
+
in oklab,
|
|
1028
|
+
color-mix(in oklab, hsl(var(--foreground)) 91%, hsl(var(--background))) 75%,
|
|
1029
|
+
oklch(from hsl(var(--primary)) l c calc(h + 216))
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/* ─── Spacing source rail ────────────────────────────────────
|
|
1034
|
+
A second, named scale — NOT Tailwind's own `--spacing` multiplier.
|
|
1035
|
+
`--spacing` (0.25rem) is shared arithmetic for width, height and
|
|
1036
|
+
line-height as well as padding/gap; repointing it per brand would rescale
|
|
1037
|
+
icons and text along with whitespace.
|
|
1038
|
+
|
|
1039
|
+
Brand Packages override `--space-source-*`. Consumers MUST read these
|
|
1040
|
+
vars directly (`var(--space-source-md, ...)`). Do NOT bridge them into
|
|
1041
|
+
`@theme --spacing-sm|md|xl|2xl`: Tailwind v4 maps every --spacing-{key}
|
|
1042
|
+
onto max-width / width / height utilities as well as padding / gap, so
|
|
1043
|
+
those keys hijack the container max-width scale (max-w-2xl became 48px
|
|
1044
|
+
on production 2026-08-03).
|
|
1045
|
+
─────────────────────────────────────────────────────────── */
|
|
1046
|
+
:root {
|
|
1047
|
+
--space-source-xs: 0.5rem;
|
|
1048
|
+
--space-source-sm: 0.75rem;
|
|
1049
|
+
--space-source-md: 1rem;
|
|
1050
|
+
--space-source-lg: 1.5rem;
|
|
1051
|
+
--space-source-xl: 2rem;
|
|
1052
|
+
--space-source-2xl: 3rem;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
813
1055
|
:root {
|
|
814
1056
|
--edge-faint: rgb(0 0 0 / 0.04);
|
|
815
1057
|
--edge-soft: rgb(0 0 0 / 0.07);
|
|
@@ -852,6 +1094,22 @@ html {
|
|
|
852
1094
|
scroll-behavior: smooth;
|
|
853
1095
|
}
|
|
854
1096
|
|
|
1097
|
+
/*
|
|
1098
|
+
* MUST STAY UNLAYERED. Do not wrap this rule in `@layer base` (or any layer).
|
|
1099
|
+
*
|
|
1100
|
+
* Tailwind v4's `@import "tailwindcss"` puts every generated utility — including
|
|
1101
|
+
* every `outline-none` / `focus-visible:outline-none` a component might carry —
|
|
1102
|
+
* inside `@layer utilities`. Per the cascade-layers spec, unlayered rules beat
|
|
1103
|
+
* layered ones regardless of specificity or source order, which is the only
|
|
1104
|
+
* reason this single declaration can supply the keyboard focus ring for the
|
|
1105
|
+
* whole design system without each component opting in.
|
|
1106
|
+
*
|
|
1107
|
+
* Move it into a layer and keyboard focus indication disappears library-wide,
|
|
1108
|
+
* silently: nothing throws, nothing looks wrong until someone tabs.
|
|
1109
|
+
*
|
|
1110
|
+
* Verified against apps/landing's built CSS (2026-07-30): this rule emits
|
|
1111
|
+
* unlayered, `.outline-none` emits inside `@layer components;@layer utilities`.
|
|
1112
|
+
*/
|
|
855
1113
|
:focus-visible:not(
|
|
856
1114
|
:where(input, textarea, select, [cmdk-input], [role="textbox"], [contenteditable="true"])
|
|
857
1115
|
) {
|
|
@@ -1433,24 +1691,128 @@ html {
|
|
|
1433
1691
|
@utility font-cn {
|
|
1434
1692
|
font-family: var(--font-cn);
|
|
1435
1693
|
line-height: 1.75;
|
|
1436
|
-
word-break: break-all
|
|
1694
|
+
/* Never use word-break: break-all on CJK chrome — see CJK layout note below. */
|
|
1437
1695
|
overflow-wrap: break-word;
|
|
1696
|
+
word-break: normal;
|
|
1438
1697
|
}
|
|
1439
1698
|
|
|
1440
1699
|
/* ─── CJK Auto-Activation via :lang() ───────────────────────
|
|
1441
1700
|
When <html lang="zh|ja|ko"> is set by app layouts, body
|
|
1442
1701
|
automatically switches to the CJK font stack.
|
|
1702
|
+
|
|
1703
|
+
Layout note (2026-08): CJK soft-wrap offers a break between almost every
|
|
1704
|
+
glyph, so the CSS *min-content* size of a text run is often one character.
|
|
1705
|
+
Combined with:
|
|
1706
|
+
• flex-col + items-center (children size to fit-content/min-content)
|
|
1707
|
+
• grid tracks minmax(0, fr) (tracks may size from that min-content)
|
|
1708
|
+
prose collapses into a vertical one-glyph strip (forge/landing heroes &
|
|
1709
|
+
footers). Do NOT "fix" this with a global min-width on all p/h* — that
|
|
1710
|
+
breaks responsive cards. Prefer stretch rules below + local w-full.
|
|
1443
1711
|
─────────────────────────────────────────────────────────── */
|
|
1444
1712
|
:is(html:lang(zh), html:lang(ja), html:lang(ko)) body {
|
|
1445
1713
|
font-family: var(--font-cn);
|
|
1446
1714
|
line-height: 1.75;
|
|
1447
|
-
word-break: break-all;
|
|
1448
1715
|
overflow-wrap: break-word;
|
|
1716
|
+
word-break: normal;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
/*
|
|
1720
|
+
* flex-col + items-center:
|
|
1721
|
+
* 1) Direct children (incl. AnimateIn wrappers) get a sane box — max 100% of
|
|
1722
|
+
* the column, min-width 0 so they can wrap instead of forcing the column
|
|
1723
|
+
* to min-content.
|
|
1724
|
+
* 2) Nested prose (p/h*) under those columns takes width:100% so Chinese
|
|
1725
|
+
* copy does not collapse to a 1-glyph strip. We deliberately do NOT set
|
|
1726
|
+
* max-width here — leave Tailwind max-w-* / max-w-[…] in control.
|
|
1727
|
+
*/
|
|
1728
|
+
:is(html:lang(zh), html:lang(ja), html:lang(ko))
|
|
1729
|
+
:is(.flex.flex-col.items-center, .inline-flex.flex-col.items-center)
|
|
1730
|
+
> * {
|
|
1731
|
+
box-sizing: border-box;
|
|
1732
|
+
max-width: 100%;
|
|
1733
|
+
min-width: 0;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
:is(html:lang(zh), html:lang(ja), html:lang(ko))
|
|
1737
|
+
:is(.flex.flex-col.items-center, .inline-flex.flex-col.items-center)
|
|
1738
|
+
:is(p, h1, h2, h3, h4, h5, h6, blockquote) {
|
|
1739
|
+
box-sizing: border-box;
|
|
1740
|
+
width: 100%;
|
|
1741
|
+
min-width: 0;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
/*
|
|
1745
|
+
* Brand blurbs that only set max-w-*: give an explicit width so flex/grid
|
|
1746
|
+
* parents don't size them to 1-glyph min-content. min(100%, …) stays mobile-safe.
|
|
1747
|
+
*/
|
|
1748
|
+
:is(html:lang(zh), html:lang(ja), html:lang(ko)) :is(p, h1, h2, h3, h4, h5, h6).max-w-xs {
|
|
1749
|
+
width: min(100%, 20rem);
|
|
1750
|
+
min-width: 0;
|
|
1751
|
+
}
|
|
1752
|
+
:is(html:lang(zh), html:lang(ja), html:lang(ko)) :is(p, h1, h2, h3, h4, h5, h6).max-w-sm {
|
|
1753
|
+
width: min(100%, 24rem);
|
|
1754
|
+
min-width: 0;
|
|
1755
|
+
}
|
|
1756
|
+
:is(html:lang(zh), html:lang(ja), html:lang(ko)) :is(p, h1, h2, h3, h4, h5, h6).max-w-md {
|
|
1757
|
+
width: min(100%, 28rem);
|
|
1758
|
+
min-width: 0;
|
|
1759
|
+
}
|
|
1760
|
+
:is(html:lang(zh), html:lang(ja), html:lang(ko)) :is(p, h1, h2, h3, h4, h5, h6).max-w-lg {
|
|
1761
|
+
width: min(100%, 32rem);
|
|
1762
|
+
min-width: 0;
|
|
1763
|
+
}
|
|
1764
|
+
:is(html:lang(zh), html:lang(ja), html:lang(ko)) :is(p, h1, h2, h3, h4, h5, h6).max-w-xl {
|
|
1765
|
+
width: min(100%, 36rem);
|
|
1766
|
+
min-width: 0;
|
|
1767
|
+
}
|
|
1768
|
+
:is(html:lang(zh), html:lang(ja), html:lang(ko)) :is(p, h1, h2, h3, h4, h5, h6).max-w-2xl {
|
|
1769
|
+
width: min(100%, 42rem);
|
|
1770
|
+
min-width: 0;
|
|
1771
|
+
}
|
|
1772
|
+
:is(html:lang(zh), html:lang(ja), html:lang(ko)) :is(p, h1, h2, h3, h4, h5, h6).max-w-3xl {
|
|
1773
|
+
width: min(100%, 48rem);
|
|
1774
|
+
min-width: 0;
|
|
1775
|
+
}
|
|
1776
|
+
:is(html:lang(zh), html:lang(ja), html:lang(ko)) :is(p, h1, h2, h3, h4, h5, h6).max-w-4xl {
|
|
1777
|
+
width: min(100%, 56rem);
|
|
1778
|
+
min-width: 0;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
/* Opt-in for wrappers (div/section) that hold CJK prose in flex/grid tracks */
|
|
1782
|
+
@utility cjk-prose {
|
|
1783
|
+
width: 100%;
|
|
1784
|
+
min-width: 0;
|
|
1785
|
+
max-width: 100%;
|
|
1786
|
+
overflow-wrap: break-word;
|
|
1787
|
+
word-break: normal;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
/*
|
|
1791
|
+
* Mobile: kill horizontal page scroll from 100vw / wide tables / marquees.
|
|
1792
|
+
* clip (not hidden) preserves sticky headers.
|
|
1793
|
+
*/
|
|
1794
|
+
@media (max-width: 767px) {
|
|
1795
|
+
html {
|
|
1796
|
+
overflow-x: clip;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
body {
|
|
1800
|
+
overflow-x: clip;
|
|
1801
|
+
max-width: 100%;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
/* Grid/flex children should be allowed to shrink below min-content */
|
|
1805
|
+
.grid > *,
|
|
1806
|
+
.flex > *,
|
|
1807
|
+
.inline-flex > * {
|
|
1808
|
+
min-width: 0;
|
|
1809
|
+
}
|
|
1449
1810
|
}
|
|
1450
1811
|
|
|
1451
1812
|
:is(html:lang(zh), html:lang(ja), html:lang(ko)) :is(code, kbd, pre, samp) {
|
|
1452
1813
|
font-family: var(--font-mono);
|
|
1453
1814
|
word-break: normal;
|
|
1815
|
+
min-width: 0;
|
|
1454
1816
|
}
|
|
1455
1817
|
|
|
1456
1818
|
/* ─── Reduced Motion ─────────────────────────────────────── */
|