@bsuite/theme 0.10.5 → 0.11.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/README.md +24 -0
- package/package.json +2 -2
- package/src/css/braden.css +89 -9
- package/src/css/vars.css +91 -2
- package/src/preset-v4.css +94 -0
package/README.md
CHANGED
|
@@ -126,6 +126,30 @@ Braden keeps separate corporate identity tokens in `@bsuite/theme/braden-css`: B
|
|
|
126
126
|
|
|
127
127
|
WCAG AA compliance: use semantic text tokens (`text-foreground`, `text-muted-foreground`, `text-text-on-primary`, `text-text-on-accent`) rather than raw `text-white`/`text-black`. Dark-surface text is capped at L=0.94 for extended-session comfort.
|
|
128
128
|
|
|
129
|
+
## Elevation (v0.11.0)
|
|
130
|
+
|
|
131
|
+
`shadow-elev-0` … `shadow-elev-4` — the suite's card-elevation ramp, and the
|
|
132
|
+
first release in which those classes exist at all. 25 call sites across crm7
|
|
133
|
+
and business-suite-unified, four `CLAUDE.md` files and the canonical
|
|
134
|
+
`bsuite-brand-system` skill had instructed `shadow-elev-*` while no rule
|
|
135
|
+
matched it anywhere; every one computed `box-shadow: none`.
|
|
136
|
+
|
|
137
|
+
- `shadow-elev-0` is a real reset (`0 0 0 0 transparent`), not the absence of
|
|
138
|
+
a rule. It is deliberately not `none`, which invalidates Tailwind v4's
|
|
139
|
+
composite `box-shadow` declaration and takes the focus ring with it.
|
|
140
|
+
- Geometry follows Tailwind's `xs`/`md`/`lg`/`xl`; colour comes from
|
|
141
|
+
`--shadow-ink-*`, an OKLCH ladder derived from `--shadow-color` and scaled
|
|
142
|
+
by `--shadow-strength`, rebinding under `.dark`.
|
|
143
|
+
- The same release rebinds Tailwind's own `--shadow-*`, `--inset-shadow-*`,
|
|
144
|
+
`--drop-shadow-*` and `--text-shadow-*` onto that ink. They ship from
|
|
145
|
+
Tailwind as `rgb(0 0 0 / a)` — pure black, banned in every role, and
|
|
146
|
+
invisible to the colour audit because the literal lives in `node_modules`.
|
|
147
|
+
292 `shadow-sm|md|lg|xl|2xl` call sites across the estate were painting it,
|
|
148
|
+
and in dark mode painting it invisibly.
|
|
149
|
+
|
|
150
|
+
Geometry is unchanged, so nothing moves; light mode shifts by a hue-tint only.
|
|
151
|
+
Dark mode changes materially, because dark shadows previously did not render.
|
|
152
|
+
|
|
129
153
|
## Text-role contract (v0.6.0)
|
|
130
154
|
|
|
131
155
|
Six measured tiers — `--role-text-heading`, `--role-text-body`, `--role-text-secondary`, `--role-text-muted`, `--role-text-subtle`, `--role-text-disabled` — each with documented WCAG contrast in both modes. Full numbers + methodology: `docs/TOKEN-MAPPING.md` §8.1.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsuite/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"url": "https://github.com/GaryOcean428/bsuite.git",
|
|
12
12
|
"directory": "packages/theme"
|
|
13
13
|
},
|
|
14
|
-
"description": "BSuite Universal theme
|
|
14
|
+
"description": "BSuite Universal theme — D2C Neon Electric + Braden Corporate baselines. Tailwind v4 preset, CSS vars (5-layer), role aliases, colourblind-safe tokens, eye-strain-safe text scale, BrandingProvider (runtime white-label). Used by BSU, CRM7, conduit, R80.3, throughput (D2C) and braden.com.au (Corporate).",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
17
|
"src/css",
|
package/src/css/braden.css
CHANGED
|
@@ -24,10 +24,32 @@
|
|
|
24
24
|
* already false twice over: the 0.7.0 separation-first contract retired purple
|
|
25
25
|
* for semantics, and the Corporate contract binds error to --braden-indigo.
|
|
26
26
|
*
|
|
27
|
-
* ERROR IS
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
27
|
+
* ERROR IS RED HERE — OPERATOR RULING 2026-08-10, superseding the indigo rule.
|
|
28
|
+
* Braden: "use standard error red not braden corporate red."
|
|
29
|
+
*
|
|
30
|
+
* What this overturns. 0.4.0 bound error to --braden-indigo on a
|
|
31
|
+
* separation-first argument: D2C's primary is blue so error must be red;
|
|
32
|
+
* Corporate's primary IS red so error must not be. That reasoning was sound
|
|
33
|
+
* and it is no longer the rule. Error now uses --error-red, the SAME standard
|
|
34
|
+
* error red the D2C brand uses (oklch(0.580 0.230 25)) — one error colour
|
|
35
|
+
* across the estate. Recognition beats palette separation: a person reads red
|
|
36
|
+
* as "danger" before they read it as "Braden".
|
|
37
|
+
*
|
|
38
|
+
* --error-red is NOT a Braden brand colour and must never be used as one. It
|
|
39
|
+
* is permitted here for the error/destructive roles only. It stays inside the
|
|
40
|
+
* palette whitelist because it is a value the D2C source-of-truth document
|
|
41
|
+
* already ships; the whitelist pools both contracts.
|
|
42
|
+
*
|
|
43
|
+
* THE COST, MEASURED, SO NOBODY REDISCOVERS IT AS A BUG:
|
|
44
|
+
* ΔE_oklch(error red, --braden-red) = 0.113 ← now the palette's tightest pair
|
|
45
|
+
* ΔE_oklch(old indigo, --braden-red) = 0.312
|
|
46
|
+
* The Corporate contract's previously-worst pair was 0.143. A destructive
|
|
47
|
+
* button and a primary button are now both red, separated by lightness (0.092)
|
|
48
|
+
* and chroma rather than hue, and under deuteranopia lightness is nearly all
|
|
49
|
+
* that survives. THEREFORE: destructive actions on Corporate must carry an
|
|
50
|
+
* icon or an explicit verb ("Delete"), never colour alone. The contract
|
|
51
|
+
* already said shape and label do more work on this brand than on D2C; after
|
|
52
|
+
* this ruling that is load-bearing rather than advisory.
|
|
31
53
|
*/
|
|
32
54
|
|
|
33
55
|
|
|
@@ -52,6 +74,12 @@
|
|
|
52
74
|
--braden-teal-deep: oklch(0.500 0.140 220);
|
|
53
75
|
--braden-aqua: oklch(0.800 0.130 198);
|
|
54
76
|
|
|
77
|
+
/* NOT A BRAND COLOUR. The estate's single standard error red, identical to
|
|
78
|
+
D2C's, bound to error/destructive only — operator ruling 2026-08-10.
|
|
79
|
+
Never bind this to primary, accent, a gradient stop or any decorative
|
|
80
|
+
surface: doing so puts a non-Braden red on a Braden page. */
|
|
81
|
+
--error-red: oklch(0.580 0.230 25);
|
|
82
|
+
|
|
55
83
|
/* ============================================================
|
|
56
84
|
LAYER 2 — SURFACES AND BORDERS
|
|
57
85
|
============================================================ */
|
|
@@ -109,13 +137,16 @@
|
|
|
109
137
|
|
|
110
138
|
/* ============================================================
|
|
111
139
|
LAYER 3 — SEMANTIC ROLES
|
|
112
|
-
error = --
|
|
140
|
+
error = --error-red, the estate's standard error red — operator ruling
|
|
141
|
+
2026-08-10. It is deliberately NOT --braden-red: the brand primary and the
|
|
142
|
+
destructive colour must still be two different values, even now that both
|
|
143
|
+
are red. See the header for the measured separation and what it obliges.
|
|
113
144
|
============================================================ */
|
|
114
145
|
--role-primary: var(--braden-red);
|
|
115
146
|
--role-secondary: var(--braden-navy);
|
|
116
147
|
--role-accent: var(--braden-gold);
|
|
117
|
-
--role-error: var(--
|
|
118
|
-
--role-destructive: var(--
|
|
148
|
+
--role-error: var(--error-red);
|
|
149
|
+
--role-destructive: var(--error-red);
|
|
119
150
|
--role-warning: var(--braden-gold);
|
|
120
151
|
--role-success: var(--braden-aqua);
|
|
121
152
|
--role-info: var(--braden-teal-deep);
|
|
@@ -185,7 +216,10 @@
|
|
|
185
216
|
--role-primary-text: oklch(0.566 0.170 17.6);
|
|
186
217
|
--role-accent-text-base: oklch(0.544 0.092 89.9);
|
|
187
218
|
--role-accent-text: var(--role-accent-text-base);
|
|
188
|
-
|
|
219
|
+
/* Red, per the 2026-08-10 ruling. Measured on Corporate's own light
|
|
220
|
+
surfaces, not inherited from D2C's: 6.18:1 on --role-bg-body,
|
|
221
|
+
6.44:1 on --role-bg-panel. Both ✓ AA. */
|
|
222
|
+
--role-error-text: oklch(0.482 0.230 25);
|
|
189
223
|
--role-warning-text: oklch(0.544 0.092 89.9);
|
|
190
224
|
--role-success-text: oklch(0.515 0.130 198);
|
|
191
225
|
--role-info-text: oklch(0.519 0.140 220);
|
|
@@ -196,6 +230,10 @@
|
|
|
196
230
|
--text-on-light-fill: oklch(0.980 0.006 50);
|
|
197
231
|
--text-on-dark-fill: oklch(0.190 0.020 250);
|
|
198
232
|
--text-on-primary: var(--text-on-light-fill);
|
|
233
|
+
/* On the new error red (L 0.58) this measures 4.54:1 — ✓ AA, but with no
|
|
234
|
+
headroom. It was 9.35:1 on the old indigo fill (L 0.40). If the error
|
|
235
|
+
fill is ever lightened, re-measure before shipping: a tenth of a point of
|
|
236
|
+
lightness drops it below AA. */
|
|
199
237
|
--text-on-error: var(--text-on-light-fill);
|
|
200
238
|
--text-on-accent: var(--text-on-dark-fill);
|
|
201
239
|
--text-on-warning: var(--text-on-dark-fill);
|
|
@@ -222,6 +260,33 @@
|
|
|
222
260
|
--shadow-sm: 0 1px 3px oklch(0.30 0.03 250 / 0.08), 0 1px 2px oklch(0.30 0.03 250 / 0.04);
|
|
223
261
|
--shadow-md: 0 4px 10px -3px oklch(0.30 0.03 250 / 0.10), 0 2px 4px -2px oklch(0.30 0.03 250 / 0.06);
|
|
224
262
|
--shadow-lg: 0 14px 26px -8px oklch(0.28 0.04 250 / 0.14), 0 5px 10px -5px oklch(0.28 0.04 250 / 0.07);
|
|
263
|
+
|
|
264
|
+
/* ============================================================
|
|
265
|
+
SHADOW INK — Corporate. Added 2026-08-12 alongside the elevation ramp.
|
|
266
|
+
============================================================
|
|
267
|
+
braden imports BOTH braden-css and preset-v4.css, so it receives the
|
|
268
|
+
`shadow-elev-0..4` utilities and the rebound `--shadow-*` namespace.
|
|
269
|
+
Those resolve through --shadow-ink-*, which lived only in the D2C
|
|
270
|
+
vars.css — without these four lines every one of them would resolve to
|
|
271
|
+
an undefined colour, the browser would drop the layer, and the utility
|
|
272
|
+
would render nothing. That is the exact phantom-utility bug the ramp
|
|
273
|
+
was added to fix, so shipping it for D2C only would have re-created it
|
|
274
|
+
here. braden/CLAUDE.md already instructs `shadow-elev-*`.
|
|
275
|
+
|
|
276
|
+
The four --shadow-xs/sm/md/lg above are UNTOUCHED. They are corporate's
|
|
277
|
+
own bespoke soft geometry and remain correct for direct
|
|
278
|
+
`box-shadow: var(--shadow-md)` call sites; they never fed a Tailwind
|
|
279
|
+
utility, because a `:root` custom property registers no v4 utility.
|
|
280
|
+
|
|
281
|
+
Tint is corporate's own shadow colour, not the D2C navy. Base opacity
|
|
282
|
+
0.10 keeps the light ramp inside the 0.06-0.14 band the values above
|
|
283
|
+
already use — "subtle professional shadows, no neon glow". */
|
|
284
|
+
--shadow-strength: 1;
|
|
285
|
+
--shadow-color: oklch(0.30 0.03 250 / 0.10);
|
|
286
|
+
--shadow-ink-faint: oklch(from var(--shadow-color) l c h / calc(alpha * 0.4 * var(--shadow-strength)));
|
|
287
|
+
--shadow-ink-soft: oklch(from var(--shadow-color) l c h / calc(alpha * 0.6 * var(--shadow-strength)));
|
|
288
|
+
--shadow-ink: oklch(from var(--shadow-color) l c h / calc(alpha * 1.0 * var(--shadow-strength)));
|
|
289
|
+
--shadow-ink-deep: oklch(from var(--shadow-color) l c h / calc(alpha * 1.6 * var(--shadow-strength)));
|
|
225
290
|
--glow-card: none;
|
|
226
291
|
--glow-card-hover: none;
|
|
227
292
|
--panel-hairline: transparent;
|
|
@@ -289,7 +354,9 @@
|
|
|
289
354
|
--role-primary-text: oklch(0.704 0.170 17.6);
|
|
290
355
|
--role-accent-text-base: oklch(0.683 0.092 89.9);
|
|
291
356
|
--role-accent-text: var(--role-accent-text-base);
|
|
292
|
-
|
|
357
|
+
/* Red, per the 2026-08-10 ruling. 7.24:1 on --role-bg-body,
|
|
358
|
+
6.51:1 on --role-bg-panel. Both ✓ AA. */
|
|
359
|
+
--role-error-text: oklch(0.786 0.230 25);
|
|
293
360
|
--role-warning-text: oklch(0.683 0.092 89.9);
|
|
294
361
|
--role-success-text: oklch(0.662 0.130 198);
|
|
295
362
|
--role-info-text: oklch(0.667 0.140 220);
|
|
@@ -297,6 +364,19 @@
|
|
|
297
364
|
--shadow-sm: 0 1px 3px oklch(0.07 0.01 252 / 0.55);
|
|
298
365
|
--shadow-md: 0 6px 16px -6px oklch(0.07 0.01 252 / 0.6);
|
|
299
366
|
--shadow-lg: 0 18px 34px -12px oklch(0.07 0.01 252 / 0.68);
|
|
367
|
+
|
|
368
|
+
/* Corporate dark ink. Re-declared here, not inherited: a var() inside a
|
|
369
|
+
custom property resolves at the element where it is DECLARED, so the
|
|
370
|
+
:root ladder would freeze to its light value and every dark shadow
|
|
371
|
+
would silently be a light-mode shadow. Measured, both a plain alias and
|
|
372
|
+
a computed form freeze this way. Base 0.70 lands the main layer at 0.56
|
|
373
|
+
after --shadow-strength, inside the 0.5-0.68 band above. */
|
|
374
|
+
--shadow-strength: 0.8;
|
|
375
|
+
--shadow-color: oklch(0.07 0.01 252 / 0.70);
|
|
376
|
+
--shadow-ink-faint: oklch(from var(--shadow-color) l c h / calc(alpha * 0.4 * var(--shadow-strength)));
|
|
377
|
+
--shadow-ink-soft: oklch(from var(--shadow-color) l c h / calc(alpha * 0.6 * var(--shadow-strength)));
|
|
378
|
+
--shadow-ink: oklch(from var(--shadow-color) l c h / calc(alpha * 1.0 * var(--shadow-strength)));
|
|
379
|
+
--shadow-ink-deep: oklch(from var(--shadow-color) l c h / calc(alpha * 1.6 * var(--shadow-strength)));
|
|
300
380
|
--glow-card: 0 0 0 1px color-mix(in oklch, var(--braden-gold) 18%, transparent), 0 0 10px -4px color-mix(in oklch, var(--braden-gold) 20%, transparent);
|
|
301
381
|
--glow-card-hover: 0 0 0 1px color-mix(in oklch, var(--braden-gold) 30%, transparent), 0 0 16px -4px color-mix(in oklch, var(--braden-gold) 32%, transparent);
|
|
302
382
|
--panel-hairline: color-mix(in oklch, var(--braden-gold) 26%, transparent);
|
package/src/css/vars.css
CHANGED
|
@@ -77,8 +77,28 @@
|
|
|
77
77
|
--light-bg-tertiary: oklch(0.963 0.003 228.9);
|
|
78
78
|
/* bg-panel. theme-audit-ok: prose. Was oklch(1 0 0.5) — pure white, and since --card and
|
|
79
79
|
--popover bridge to it, every shadcn card and popover in all five D2C
|
|
80
|
-
apps rendered pure white in light mode from this one line.
|
|
81
|
-
|
|
80
|
+
apps rendered pure white in light mode from this one line.
|
|
81
|
+
|
|
82
|
+
2026-08-10: that first fix moved it to oklch(0.994) — 0.6 lightness points
|
|
83
|
+
off pure white. It passed the audit, which matches on literal white, and it
|
|
84
|
+
did not pass the operator, who looked at a rendered page and said "pure
|
|
85
|
+
white displayed, which is forbidden". He was right: 0.6 points is below any
|
|
86
|
+
perceptual threshold, so the panel still READ as white even though it no
|
|
87
|
+
longer WAS white. A colour rule enforced by string match will keep accepting
|
|
88
|
+
values like that.
|
|
89
|
+
|
|
90
|
+
Now oklch(0.98 0.006 260) — 2.0 points below white and 1.9 above
|
|
91
|
+
--light-bg-primary (0.961), so the panel reads as a raised surface from both
|
|
92
|
+
directions. Three times the chroma of the old value, which is what actually
|
|
93
|
+
stops it reading as white: at this lightness the tint does more work than
|
|
94
|
+
the lightness step.
|
|
95
|
+
|
|
96
|
+
CHOSEN FROM THE CONTRACT, NOT INVENTED. The first attempt here used
|
|
97
|
+
oklch(0.976 0.003 260) and the palette gate rejected it — correctly. Only
|
|
98
|
+
colours in packages/theme/docs/d2c-theme-source-of-truth.html are permitted
|
|
99
|
+
(operator ruling 2026-08-03): the document is the source of truth, not the
|
|
100
|
+
code. This value is already in it. */
|
|
101
|
+
--light-bg-accent: oklch(0.98 0.006 260);
|
|
82
102
|
|
|
83
103
|
/* D2C light text scale — hue 260 (cool blue-grey), capped L=0.22.
|
|
84
104
|
Measured WCAG contrast vs --light-bg-primary oklch(0.961 0 0.5), OKLCH ->
|
|
@@ -352,6 +372,57 @@
|
|
|
352
372
|
--glow-strength: 0.4;
|
|
353
373
|
--shadow-strength: 1;
|
|
354
374
|
|
|
375
|
+
/* ============================================================
|
|
376
|
+
SHADOW INK — the paint every elevation is made of.
|
|
377
|
+
============================================================
|
|
378
|
+
Added 2026-08-12. Until now --shadow-strength had ZERO consumers
|
|
379
|
+
anywhere in the monorepo or the six apps: a dial wired to nothing.
|
|
380
|
+
The elevation ramp in preset-v4.css is now the thing it turns.
|
|
381
|
+
|
|
382
|
+
WHY A TOKEN AND NOT A LITERAL PER SHADOW
|
|
383
|
+
theme-audit-ok: prose. Quoting the upstream default this ink replaces.
|
|
384
|
+
Tailwind's own shadow scale is built on `rgb(0 0 0 / a)` — pure black,
|
|
385
|
+
which the contract bans in EVERY role, alpha forms included. That ban
|
|
386
|
+
was silently unenforced for shadows because the value lives inside
|
|
387
|
+
Tailwind's dist, not in our source, so no scanner here could ever see
|
|
388
|
+
it. preset-v4.css now rebinds the whole --shadow-* namespace onto this
|
|
389
|
+
ink, which is how the ban starts applying to the 292 `shadow-sm/md/lg`
|
|
390
|
+
call sites that were painting pure black.
|
|
391
|
+
|
|
392
|
+
--shadow-color carries BOTH the hue and the mode's BASE opacity. The
|
|
393
|
+
ladder below multiplies that base; `alpha` in relative-colour syntax
|
|
394
|
+
reads it back, so one token sets a mode's whole shadow density and the
|
|
395
|
+
four steps keep their relationship to each other.
|
|
396
|
+
|
|
397
|
+
WHY THE BASE DIFFERS SO MUCH BETWEEN MODES (0.10 vs 0.70)
|
|
398
|
+
A shadow is only visible as a difference from the surface under it. In
|
|
399
|
+
light mode the surface is L 0.96 and the ink is L 0.166, so 10% alpha
|
|
400
|
+
already reads. In dark mode the surface IS L 0.166 — ink at 10% would
|
|
401
|
+
be invisible, which is exactly what shipped. The dark ink therefore
|
|
402
|
+
goes BELOW the page (L 0.08) and much denser. These are not invented
|
|
403
|
+
numbers: they are the values crm7 and business-suite-unified had each
|
|
404
|
+
hand-tuned in their own local copies of this ramp, reconciled.
|
|
405
|
+
|
|
406
|
+
NOT pure black at either end — L 0.166 is the estate's near-black
|
|
407
|
+
(--dark-bg-primary), L 0.08 the contract's deepest surface tone. Both
|
|
408
|
+
are hue-matched to the surface family (~268), so a shadow reads as the
|
|
409
|
+
page's own shade rather than a grey hole punched in it.
|
|
410
|
+
|
|
411
|
+
⚠ THE FOUR STEPS ARE RE-DECLARED VERBATIM UNDER .dark, AND MUST BE.
|
|
412
|
+
This looks like duplication begging to be removed. It is not. A var()
|
|
413
|
+
inside a custom property is substituted at the element where THAT
|
|
414
|
+
PROPERTY is declared, and the resolved value is what inherits. Declared
|
|
415
|
+
only here, --shadow-ink resolves against :root's --shadow-color — the
|
|
416
|
+
LIGHT one — and every .dark descendant inherits that already-resolved
|
|
417
|
+
light value. Measured: with the ladder at :root alone, all five
|
|
418
|
+
elevations rendered identical oklch(0.166 … / 0.1) in dark mode.
|
|
419
|
+
Deleting the .dark copy silently restores that bug. */
|
|
420
|
+
--shadow-color: oklch(0.166 0.026 269.4 / 0.10);
|
|
421
|
+
--shadow-ink-faint: oklch(from var(--shadow-color) l c h / calc(alpha * 0.4 * var(--shadow-strength)));
|
|
422
|
+
--shadow-ink-soft: oklch(from var(--shadow-color) l c h / calc(alpha * 0.6 * var(--shadow-strength)));
|
|
423
|
+
--shadow-ink: oklch(from var(--shadow-color) l c h / calc(alpha * 1.0 * var(--shadow-strength)));
|
|
424
|
+
--shadow-ink-deep: oklch(from var(--shadow-color) l c h / calc(alpha * 1.6 * var(--shadow-strength)));
|
|
425
|
+
|
|
355
426
|
/* ============================================================
|
|
356
427
|
CARD SURFACE GLOW — subtle neon separation, dark mode only.
|
|
357
428
|
Light mode keeps the existing border-only treatment (--role-border);
|
|
@@ -496,6 +567,24 @@
|
|
|
496
567
|
--glow-strength: 0.6;
|
|
497
568
|
--shadow-strength: 0.8;
|
|
498
569
|
|
|
570
|
+
/* Shadow ink for the dark surface. Only the SOURCE rebinds — the four
|
|
571
|
+
--shadow-ink-* steps are declared once at :root and resolve per element
|
|
572
|
+
through this token and --shadow-strength, so they follow .dark for free.
|
|
573
|
+
L 0.08 sits BELOW --dark-bg-primary (L 0.166); a shadow at or above the
|
|
574
|
+
page's own lightness is not a shadow. Base opacity 0.70, which after
|
|
575
|
+
--shadow-strength 0.8 puts the main layer at 0.56 — inside the 0.5-0.8
|
|
576
|
+
band crm7 and BSU had each arrived at independently.
|
|
577
|
+
|
|
578
|
+
The four steps below are textually identical to their :root twins and
|
|
579
|
+
are NOT redundant — see the substitution-timing note at the :root
|
|
580
|
+
declaration. Only --shadow-color above differs; the ladder is repeated
|
|
581
|
+
so it re-resolves against it. */
|
|
582
|
+
--shadow-color: oklch(0.08 0.02 268 / 0.70);
|
|
583
|
+
--shadow-ink-faint: oklch(from var(--shadow-color) l c h / calc(alpha * 0.4 * var(--shadow-strength)));
|
|
584
|
+
--shadow-ink-soft: oklch(from var(--shadow-color) l c h / calc(alpha * 0.6 * var(--shadow-strength)));
|
|
585
|
+
--shadow-ink: oklch(from var(--shadow-color) l c h / calc(alpha * 1.0 * var(--shadow-strength)));
|
|
586
|
+
--shadow-ink-deep: oklch(from var(--shadow-color) l c h / calc(alpha * 1.6 * var(--shadow-strength)));
|
|
587
|
+
|
|
499
588
|
/*
|
|
500
589
|
* Card glow — subtle neon separation for card surfaces, dark mode only.
|
|
501
590
|
* Derived from the app's accent colour (--app-accent, falling back to
|
package/src/preset-v4.css
CHANGED
|
@@ -205,6 +205,100 @@
|
|
|
205
205
|
--font-display: var(--font-display);
|
|
206
206
|
--font-mono: var(--font-mono);
|
|
207
207
|
|
|
208
|
+
/* ============================================================
|
|
209
|
+
ELEVATION — `shadow-elev-0` .. `shadow-elev-4`
|
|
210
|
+
============================================================
|
|
211
|
+
Added 2026-08-12. Before this, `shadow-elev-*` was a PHANTOM: 25 call
|
|
212
|
+
sites across crm7 and business-suite-unified, four CLAUDE.md files and
|
|
213
|
+
the canonical bsuite-brand-system skill all instructed it, and no rule
|
|
214
|
+
existed anywhere to match. Every one of them computed
|
|
215
|
+
`box-shadow: none`. Cards drawn as elevated had no elevation.
|
|
216
|
+
|
|
217
|
+
crm7 and BSU had each noticed and each written `--shadow-elev-0..4` into
|
|
218
|
+
their own `:root` — but a `:root` custom property registers no Tailwind
|
|
219
|
+
v4 utility, so the class still did not exist. Two divergent copies of the
|
|
220
|
+
ramp, both inert. They are now redundant and should be deleted from both
|
|
221
|
+
apps (see the rollout note in the PR); this block is the single source.
|
|
222
|
+
|
|
223
|
+
WHY DEFINE IT RATHER THAN MIGRATE THE CALL SITES
|
|
224
|
+
The alternative was rewriting 25 sites onto `shadow-sm/md/lg`. That scale
|
|
225
|
+
block exists to override; the value is Tailwind's, not ours.
|
|
226
|
+
theme-audit-ok: prose — the next line quotes the upstream default.
|
|
227
|
+
is Tailwind's default, which is `rgb(0 0 0 / a)` — the pure black the
|
|
228
|
+
contract bans in every role. Migrating would have moved working-as-
|
|
229
|
+
intended markup onto a banned value, and left the docs to be rewritten
|
|
230
|
+
too. A five-step ramp with an explicit 0 is also the right primitive for
|
|
231
|
+
a card system whose cards are draggable and resizable: "resting" needs a
|
|
232
|
+
name, not the absence of a class.
|
|
233
|
+
|
|
234
|
+
GEOMETRY is Tailwind's xs/md/lg/xl, which is what crm7 and BSU had both
|
|
235
|
+
chosen. COLOUR is --shadow-ink-* (vars.css): OKLCH, hue-matched to the
|
|
236
|
+
surface, never pure black, and scaled by --shadow-strength in both modes.
|
|
237
|
+
|
|
238
|
+
`@theme inline`, per this file's doctrine, for a second reason here: the
|
|
239
|
+
apps still carry their own `:root --shadow-elev-*`. Plain `@theme` would
|
|
240
|
+
emit ours at :root too, where the app's later copy would silently win and
|
|
241
|
+
the package would appear to have no effect. `inline` puts the resolved
|
|
242
|
+
value inside the utility, out of reach of that collision.
|
|
243
|
+
|
|
244
|
+
elev-0 is `0 0 0 0 transparent` and NOT `none`. `none` inside the v4
|
|
245
|
+
composite (`box-shadow: var(--tw-inset-shadow), …, var(--tw-shadow)`)
|
|
246
|
+
makes the whole declaration invalid, so the browser drops it — taking
|
|
247
|
+
--tw-ring-shadow with it. Measured: `shadow-elev-0 ring-2` rendered NO
|
|
248
|
+
FOCUS RING. Tailwind's own `shadow-none` sidesteps this with `0 0 #0000`;
|
|
249
|
+
`transparent` is the same trick without a pure-black alpha form. */
|
|
250
|
+
--shadow-elev-0: 0 0 0 0 transparent;
|
|
251
|
+
--shadow-elev-1: 0 1px 2px 0 var(--shadow-ink-soft);
|
|
252
|
+
--shadow-elev-2: 0 4px 6px -1px var(--shadow-ink), 0 2px 4px -2px var(--shadow-ink-soft);
|
|
253
|
+
--shadow-elev-3: 0 10px 15px -3px var(--shadow-ink), 0 4px 6px -4px var(--shadow-ink-soft);
|
|
254
|
+
--shadow-elev-4: 0 20px 25px -5px var(--shadow-ink), 0 8px 10px -6px var(--shadow-ink-faint);
|
|
255
|
+
|
|
256
|
+
/* ------------------------------------------------------------
|
|
257
|
+
Tailwind's OWN shadow scale, rebound onto the same ink.
|
|
258
|
+
------------------------------------------------------------
|
|
259
|
+
Not cosmetic. `--shadow-*`, `--inset-shadow-*`, `--drop-shadow-*` and
|
|
260
|
+
theme-audit-ok: prose — the next line quotes the upstream default.
|
|
261
|
+
`--text-shadow-*` ship from Tailwind as `rgb(0 0 0 / a)`, and 292
|
|
262
|
+
`shadow-sm|md|lg|xl|2xl` call sites across the five D2C apps and
|
|
263
|
+
packages/ were painting exactly that. Pure black is banned in every
|
|
264
|
+
role, alpha forms included — and the theme-conformance scanner could
|
|
265
|
+
never catch these, because the literal is in node_modules, not in any
|
|
266
|
+
file it reads. Same class of blind spot as the HSL sidebar tokens.
|
|
267
|
+
|
|
268
|
+
In dark mode this is also a correctness fix, not a tint: black at 10%
|
|
269
|
+
alpha over an L 0.166 navy page is invisible, so every one of those 292
|
|
270
|
+
sites had no shadow in dark mode at all.
|
|
271
|
+
|
|
272
|
+
GEOMETRY IS UNCHANGED from Tailwind's defaults, so nothing shifts
|
|
273
|
+
position; only the paint changes. The one deliberate departure is 2xl,
|
|
274
|
+
which drops from 0.25 to 0.16 base alpha (--shadow-ink-deep): at a 50px
|
|
275
|
+
blur, contract ink at Tailwind's density reads as a smudge rather than
|
|
276
|
+
a lift. */
|
|
277
|
+
--shadow-2xs: 0 1px var(--shadow-ink-soft);
|
|
278
|
+
--shadow-xs: 0 1px 2px 0 var(--shadow-ink-soft);
|
|
279
|
+
--shadow-sm: 0 1px 3px 0 var(--shadow-ink), 0 1px 2px -1px var(--shadow-ink);
|
|
280
|
+
--shadow-md: 0 4px 6px -1px var(--shadow-ink), 0 2px 4px -2px var(--shadow-ink);
|
|
281
|
+
--shadow-lg: 0 10px 15px -3px var(--shadow-ink), 0 4px 6px -4px var(--shadow-ink);
|
|
282
|
+
--shadow-xl: 0 20px 25px -5px var(--shadow-ink), 0 8px 10px -6px var(--shadow-ink);
|
|
283
|
+
--shadow-2xl: 0 25px 50px -12px var(--shadow-ink-deep);
|
|
284
|
+
|
|
285
|
+
--inset-shadow-2xs: inset 0 1px var(--shadow-ink-soft);
|
|
286
|
+
--inset-shadow-xs: inset 0 1px 1px var(--shadow-ink-soft);
|
|
287
|
+
--inset-shadow-sm: inset 0 2px 4px var(--shadow-ink-soft);
|
|
288
|
+
|
|
289
|
+
--drop-shadow-xs: 0 1px 1px var(--shadow-ink-soft);
|
|
290
|
+
--drop-shadow-sm: 0 1px 2px var(--shadow-ink);
|
|
291
|
+
--drop-shadow-md: 0 3px 3px var(--shadow-ink);
|
|
292
|
+
--drop-shadow-lg: 0 4px 4px var(--shadow-ink);
|
|
293
|
+
--drop-shadow-xl: 0 9px 7px var(--shadow-ink);
|
|
294
|
+
--drop-shadow-2xl: 0 25px 25px var(--shadow-ink-deep);
|
|
295
|
+
|
|
296
|
+
--text-shadow-2xs: 0px 1px 0px var(--shadow-ink);
|
|
297
|
+
--text-shadow-xs: 0px 1px 1px var(--shadow-ink-deep);
|
|
298
|
+
--text-shadow-sm: 0px 1px 0px var(--shadow-ink-soft), 0px 1px 1px var(--shadow-ink-soft), 0px 2px 2px var(--shadow-ink-soft);
|
|
299
|
+
--text-shadow-md: 0px 1px 1px var(--shadow-ink), 0px 1px 2px var(--shadow-ink), 0px 2px 4px var(--shadow-ink);
|
|
300
|
+
--text-shadow-lg: 0px 1px 2px var(--shadow-ink), 0px 3px 2px var(--shadow-ink), 0px 4px 8px var(--shadow-ink);
|
|
301
|
+
|
|
208
302
|
/* ============================================================
|
|
209
303
|
ANIMATIONS
|
|
210
304
|
============================================================ */
|