@bsuite/theme 0.10.0 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/theme",
3
- "version": "0.10.0",
3
+ "version": "0.10.3",
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 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).",
14
+ "description": "BSuite Universal theme \u2014 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",
@@ -193,11 +193,23 @@
193
193
  --text-on-warning: var(--text-on-dark-fill);
194
194
  --text-on-success: var(--text-on-dark-fill);
195
195
  --text-on-info: var(--text-on-light-fill);
196
+ /* Was MISSING and preset-v4.css references it, so --color-overlay-foreground
197
+ resolved to nothing and any text on a scrim fell back to whatever it
198
+ inherited. Measured undefined in the browser on 2026-08-03. The scrim is
199
+ dark in both modes, so this is light in both. */
200
+ --text-on-overlay: var(--text-on-light-fill);
196
201
 
197
202
  /* ============================================================
198
203
  DECORATION — shadow, glow, dot and grid
199
204
  ============================================================ */
200
205
  --gradient-accent: linear-gradient(135deg, var(--braden-red), var(--braden-gold));
206
+ /* Same gradient, re-stopped for a DARK backdrop. --gradient-accent is built
207
+ for fills on a light surface: against the hero scrim (L~0.36) its red stop
208
+ measures ~1.7:1 and the text half-disappears. This keeps the red hue and
209
+ chroma exactly (17.6 / 0.17) and lifts only lightness to a value the
210
+ contract already ships, so both stops clear the scrim and G2 still passes. */
211
+ --braden-red-on-dark: oklch(0.704 0.170 17.6);
212
+ --gradient-accent-on-dark: linear-gradient(135deg, var(--braden-red-on-dark), var(--braden-gold));
201
213
  --shadow-xs: 0 1px 2px oklch(0.30 0.03 250 / 0.06);
202
214
  --shadow-sm: 0 1px 3px oklch(0.30 0.03 250 / 0.08), 0 1px 2px oklch(0.30 0.03 250 / 0.04);
203
215
  --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);
@@ -283,15 +295,93 @@
283
295
  }
284
296
 
285
297
  /* ============================================================
286
- BASE — the heading ramp, APPLIED. The tokens existed and nothing bound
287
- them to an actual h1..h6, so every level rendered as flat body colour.
288
- Contract values, verbatim.
289
- ============================================================ */
298
+ BASE — Corporate sets the heading FACE only.
299
+ ============================================================
300
+ Not colour, not size, not weight, not transform. Reverted to this after
301
+ operator review ("well that looks worse") and a measurement that showed
302
+ why: on the live site the hero H1 picked up the ramp colour while the 25
303
+ headings below it kept their explicit `text-braden-navy`. One heading
304
+ stopped matching the rest. That is not a hierarchy.
305
+
306
+ A base rule only reaches elements that do not style themselves. In a
307
+ dense app UI that is most headings, which is the case the ramp was
308
+ measured against. On a marketing site it is almost none — 59 headings
309
+ here carry an explicit brand colour and their own size utility — so a
310
+ base ramp cannot create hierarchy there, only a mismatch on whichever
311
+ few it happens to reach.
312
+
313
+ font-family is the exception: nothing on the page overrides it, so it
314
+ lands uniformly and is exactly what a base rule is for.
315
+
316
+ D2C keeps the full ramp (see vars.css) — those apps ARE the dense-UI
317
+ case, and the ramp is verified there in a real browser by G5. */
290
318
  @layer base {
291
- h1 { font-family: var(--font-display); color: var(--role-h1); font-size: 25px; font-weight: 600; letter-spacing: var(--track-display); }
292
- h2 { font-family: var(--font-heading); color: var(--role-h2); font-size: 19px; font-weight: 700; }
293
- h3 { font-family: var(--font-heading); color: var(--role-h3); font-size: 16px; font-weight: 700; }
294
- h4 { font-family: var(--font-heading); color: var(--role-h4); font-size: 14px; font-weight: 700; }
295
- h5 { font-family: var(--font-heading); color: var(--role-h5); font-size: 13px; font-weight: 700; }
296
- h6 { font-family: var(--font-heading); color: var(--role-h6); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: var(--track-eyebrow); }
319
+ h1 { font-family: var(--font-display); }
320
+ h2, h3, h4, h5, h6 { font-family: var(--font-heading); }
321
+
322
+ /* The root element's colour was the UA default measured pure black (L=0)
323
+ in BOTH modes on 2026-08-03, i.e. the one banned value, sitting at the top
324
+ of the inheritance chain. Nothing renders text at <html> so it was
325
+ invisible to review, but every element that does not set a colour and is
326
+ not under a styled ancestor lands on it. */
327
+ html { color: var(--role-text-body); background-color: var(--role-bg-base); }
328
+
329
+ /* The contract names a family and the package shipped the token, but NOTHING
330
+ bound it to an element — so every app fell back to whatever it happened
331
+ to set itself, which is how crm7 ran Inter against a Geist contract and
332
+ the estate ran four families at once. Same defect as the heading ramp:
333
+ a token nobody applies is not a font, it is a variable. */
334
+ body { font-family: var(--font-body); }
335
+ code, kbd, samp, pre { font-family: var(--font-mono); }
336
+ }
337
+
338
+ /* ============================================================
339
+ DARK SURFACES INSIDE LIGHT MODE
340
+ ============================================================
341
+ A navy or red panel on a light page is a dark surface, and the mode-sensitive
342
+ role tokens are still resolving their LIGHT values inside it. That is not a
343
+ theoretical problem — measured on the contact section 2026-08-03:
344
+
345
+ --role-accent-text (light) on bg-braden-navy ....... 2.20:1 FAIL
346
+ --braden-gold on bg-braden-navy ..... 5.07:1 AA
347
+
348
+ and the mirror holds on a light background, where the brand gold is 2.05:1
349
+ and the light accent is 4.71:1. No single value passes on both, so this
350
+ cannot be fixed by picking a better gold — the token has to follow the
351
+ SURFACE, not just the mode. Mark a dark panel and its accent text flips. */
352
+ [data-surface='dark'] {
353
+ --role-accent-text: var(--braden-gold);
354
+ --role-text-body: var(--text-on-light-fill);
355
+ --role-text-secondary: var(--text-on-light-fill);
356
+ /* D2C's heading ramp reads this; Corporate has no colour ramp, but a shared
357
+ surface contract should behave the same in both brands. */
358
+ --heading-color: currentColor;
359
+ }
360
+
361
+ /* ============================================================
362
+ GRADIENT TEXT — the one place the accent gradient paints glyphs.
363
+ ============================================================
364
+ background-clip:text needs `color: transparent` to show through, which means
365
+ a failed gradient produces INVISIBLE TEXT rather than an ugly one. So both
366
+ stops resolve from tokens defined above in this same file, never from a
367
+ token an app might or might not have. */
368
+ @layer components {
369
+ .text-gradient-accent {
370
+ background-image: var(--gradient-accent);
371
+ -webkit-background-clip: text;
372
+ background-clip: text;
373
+ color: transparent;
374
+ }
375
+ .text-gradient-accent-on-dark {
376
+ background-image: var(--gradient-accent-on-dark);
377
+ -webkit-background-clip: text;
378
+ background-clip: text;
379
+ color: transparent;
380
+ }
381
+ /* Forced-colours mode drops background-image entirely, which would leave
382
+ transparent glyphs on screen. Hand the text back to the system palette. */
383
+ @media (forced-colors: active) {
384
+ .text-gradient-accent,
385
+ .text-gradient-accent-on-dark { background-image: none; color: CanvasText; }
386
+ }
297
387
  }
package/src/css/vars.css CHANGED
@@ -206,6 +206,9 @@
206
206
  --role-error-text: oklch(0.556 0.230 25); /* 4.69:1 ✓ AA */
207
207
  --role-warning-text: oklch(0.535 0.150 75); /* 4.70:1 ✓ AA */
208
208
  --role-success-text: oklch(0.501 0.130 195); /* 4.70:1 ✓ AA */
209
+ /* Pair for --role-info. Every other semantic role had a -text token; info
210
+ was the only gap, which forced apps to invent a local copy (R80.4). */
211
+ --role-info-text: oklch(0.480 0.120 192); /* 4.70:1 ✓ AA on panel */
209
212
 
210
213
  /* Text roles — six-tier scale (W3 §3.1 contract). Every tier below is
211
214
  measured against --role-bg-body (the conservative floor); all tiers
@@ -458,6 +461,7 @@
458
461
  --role-error-text: oklch(0.786 0.230 25); /* 7.00:1 ✓ AAA */
459
462
  --role-warning-text: var(--neon-electric-amber);/* 10.10:1 ✓ AAA */
460
463
  --role-success-text: oklch(0.671 0.130 195); /* 6.99:1 ✓ AAA */
464
+ --role-info-text: oklch(0.780 0.120 192); /* 7.0:1 ✓ AAA on dark panel */
461
465
 
462
466
  /* Glow/shadow scale up in dark mode */
463
467
  --glow-strength: 0.6;
@@ -500,16 +504,44 @@
500
504
  hierarchy survives greyscale and both dichromacies rather than relying on
501
505
  size alone.
502
506
 
503
- Scoped to @layer base so any component class still wins this sets the
504
- floor, it does not fight Tailwind utilities. */
507
+ Scoped to @layer base so any component class ON THE HEADING ITSELF wins.
508
+ That is a narrower guarantee than it looks, and the earlier version of this
509
+ comment ("it does not fight Tailwind utilities") was simply wrong.
510
+
511
+ WHAT IT DOES OVERRIDE: an INHERITED colour. Inheritance is the last resort in
512
+ the cascade, so a rule targeting `h1` beats `text-primary-foreground` sitting
513
+ on an ancestor — specificity never enters into it. Measured on braden
514
+ 2026-08-03: a hero `<h1>` inheriting a near-white foreground from its wrapper
515
+ rendered dark navy against a dark scrim, i.e. all but invisible, and it was
516
+ the ONLY heading on the page that moved. That is the signature of this bug —
517
+ one heading changing is not a small problem, it is the coloured-container
518
+ case, and coloured containers are where text goes invisible.
519
+
520
+ --heading-color is the escape hatch. Any container that colours its own text
521
+ sets it once and the whole heading subtree defers:
522
+ <div class="text-primary-foreground [--heading-color:currentColor]">
523
+ currentColor there resolves to the container's colour, so headings inherit
524
+ again instead of being repainted. */
505
525
  @layer base {
506
- h1 { color: var(--role-h1); font-size: 25px; font-weight: 700; letter-spacing: var(--track-display); }
507
- h2 { color: var(--role-h2); font-size: 19px; }
508
- h3 { color: var(--role-h3); font-size: 16px; }
509
- h4 { color: var(--role-h4); font-size: 14px; }
510
- h5 { color: var(--role-h5); font-size: 13px; }
526
+ /* See braden.css: the root element inherits the UA default pure black when
527
+ nothing sets it, which is the one banned value at the top of the chain. */
528
+ html { color: var(--role-text-body); background-color: var(--role-bg-base); }
529
+
530
+ /* The contract names a family and the package shipped the token, but NOTHING
531
+ bound it to an element — so every app fell back to whatever it happened
532
+ to set itself, which is how crm7 ran Inter against a Geist contract and
533
+ the estate ran four families at once. Same defect as the heading ramp:
534
+ a token nobody applies is not a font, it is a variable. */
535
+ body { font-family: var(--font-body); }
536
+ code, kbd, samp, pre { font-family: var(--font-mono); }
537
+
538
+ h1 { color: var(--heading-color, var(--role-h1)); font-size: 25px; font-weight: 700; letter-spacing: var(--track-display); }
539
+ h2 { color: var(--heading-color, var(--role-h2)); font-size: 19px; }
540
+ h3 { color: var(--heading-color, var(--role-h3)); font-size: 16px; }
541
+ h4 { color: var(--heading-color, var(--role-h4)); font-size: 14px; }
542
+ h5 { color: var(--heading-color, var(--role-h5)); font-size: 13px; }
511
543
  h6 {
512
- color: var(--role-h6);
544
+ color: var(--heading-color, var(--role-h6));
513
545
  font-size: 11px;
514
546
  text-transform: uppercase;
515
547
  letter-spacing: var(--track-eyebrow);
package/src/preset-v4.css CHANGED
@@ -101,6 +101,7 @@
101
101
  --color-error-text: var(--role-error-text);
102
102
  --color-warning-text: var(--role-warning-text);
103
103
  --color-success-text: var(--role-success-text);
104
+ --color-info-text: var(--role-info-text);
104
105
 
105
106
  /* Informational scale — CATEGORICAL, never state. `bg-info-3`, `text-info-5`.
106
107
  Numbered because they are interchangeable slots with no meaning of their