@uniweb/theming 0.1.9 → 0.1.11

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": "@uniweb/theming",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Theming utilities for the Uniweb platform — shade generation, CSS generation, theme processing",
5
5
  "type": "module",
6
6
  "exports": {
@@ -26,7 +26,12 @@ const DEFAULT_CONTEXT_TOKENS = {
26
26
  'ring': 'var(--primary-500)',
27
27
  'link': 'var(--primary-600)',
28
28
  'link-hover': 'var(--primary-700)',
29
- 'accent': 'var(--accent-600)',
29
+ // Shade 500 is the site's colour exactly as authored (the palette anchors
30
+ // the input there). `link` and `primary` sit at 600 because they carry the
31
+ // strict contrast burden — body-size links, and white text on a fill. An
32
+ // accent is decorative emphasis, usually at display size, so it keeps the
33
+ // brand colour unmuddied instead of paying for contrast it doesn't need.
34
+ 'accent': 'var(--accent-500)',
30
35
  'primary': 'var(--primary-600)',
31
36
  'primary-foreground': 'white',
32
37
  'primary-hover': 'var(--primary-700)',
@@ -55,7 +60,7 @@ const DEFAULT_CONTEXT_TOKENS = {
55
60
  'ring': 'var(--primary-500)',
56
61
  'link': 'var(--primary-600)',
57
62
  'link-hover': 'var(--primary-700)',
58
- 'accent': 'var(--accent-600)',
63
+ 'accent': 'var(--accent-500)', // exact brand — see the light context
59
64
  'primary': 'var(--primary-600)',
60
65
  'primary-foreground': 'white',
61
66
  'primary-hover': 'var(--primary-700)',
@@ -84,6 +89,11 @@ const DEFAULT_CONTEXT_TOKENS = {
84
89
  'ring': 'var(--primary-500)',
85
90
  'link': 'var(--primary-400)',
86
91
  'link-hover': 'var(--primary-300)',
92
+ // Stays lightened, unlike the light/medium contexts which use the exact
93
+ // brand at 500. Every text token here is lightened (body, subtle, link)
94
+ // because a mid-brand hue on a near-black surface often isn't legible —
95
+ // a navy or forest-green accent at 500 would effectively disappear. A site
96
+ // whose brand does read on dark can set `contexts.dark.accent` to it.
87
97
  'accent': 'var(--accent-400)',
88
98
  'primary': 'var(--primary-500)',
89
99
  'primary-foreground': 'white',
@@ -108,6 +118,8 @@ const DEFAULT_CONTEXT_TOKENS = {
108
118
  const DEFAULT_COLORS = {
109
119
  primary: '#3b82f6', // Blue
110
120
  secondary: '#64748b', // Slate
121
+ // Last resort only. A site that names no accent inherits its primary (see
122
+ // processTheme) — this literal is reached solely when the primary is unusable.
111
123
  accent: '#8b5cf6', // Purple
112
124
  neutral: '#78716c', // Stone
113
125
  }
@@ -573,6 +585,14 @@ export function generateThemeCSS(config = {}, options = {}) {
573
585
  }
574
586
 
575
587
  // 6. Dark scheme support (if enabled, default is dark, or follows system preference)
588
+ //
589
+ // This guard decides whether the `.scheme-dark` rules physically exist. It is
590
+ // the CANONICAL "site reaches dark" predicate; the model-side copy that the
591
+ // runtime boot resolver and kit's useAppearance consume is hasDarkScheme() in
592
+ // @uniweb/core (theme.js). These MUST agree — a scheme applied with no
593
+ // matching CSS renders as a light page claiming to be dark. @uniweb/theming
594
+ // has no @uniweb/core dependency (it also runs server-side in the platform),
595
+ // so the predicate is duplicated here by necessity; keep the two in lockstep.
576
596
  if (appearance.allowToggle || appearance.default === 'dark' || appearance.default === 'system' || appearance.schemes?.includes('dark')) {
577
597
  sections.push(generateDarkSchemeCSS(appearance, contexts.dark, colorVars))
578
598
  }
package/src/processor.js CHANGED
@@ -26,10 +26,19 @@ const NEUTRAL_PRESETS = {
26
26
  /**
27
27
  * Default inline text styles (content-author markdown: [text]{accent})
28
28
  * These reference semantic tokens so they adapt to context automatically
29
+ *
30
+ * `accent` resolves to --accent, not --link. It used to be --link, which
31
+ * conflated two roles that have genuinely different constraints: --link means
32
+ * "navigable text" and must stay readable at body size (4.5:1), while an accent
33
+ * is decorative emphasis, usually at display size (3:1 suffices). Welding them
34
+ * together meant a site that responsibly darkened its link colour also drained
35
+ * the brand colour out of its headlines, where the darker value buys nothing.
36
+ * It also left the `accent` colour family nearly unreachable from markdown, and
37
+ * contradicted `callout` right below, which already used --accent.
29
38
  */
30
39
  const DEFAULT_INLINE = {
31
40
  accent: {
32
- color: 'var(--link)',
41
+ color: 'var(--accent)',
33
42
  'font-weight': '600',
34
43
  },
35
44
  callout: {
@@ -486,6 +495,19 @@ export function processTheme(rawConfig = {}, options = {}) {
486
495
  rawColors.neutral = NEUTRAL_PRESETS[rawColors.neutral]
487
496
  }
488
497
 
498
+ // A site that declares no accent colour almost always means "my brand
499
+ // colour" — an accent is an optional second voice, not a required one. The
500
+ // alternative is a fixed hue nobody chose showing up wherever markdown says
501
+ // [text]{accent} or {callout}. Falling back to primary also makes the
502
+ // --accent token safe to use as a default elsewhere: it is always *a* brand
503
+ // colour rather than a stock purple. The literal in DEFAULT_COLORS survives
504
+ // only as a last resort for a site whose primary is unusable.
505
+ const brandColor = rawColors.primary ?? defaultColors.primary
506
+ const isUsableColor = (v) => (typeof v === 'object' && v !== null) || isValidColor(v)
507
+ if (rawColors.accent === undefined && isUsableColor(brandColor)) {
508
+ rawColors.accent = brandColor
509
+ }
510
+
489
511
  // Filter to only valid colors (skip invalid ones in non-strict mode)
490
512
  const validColors = {}
491
513
  for (const [name, value] of Object.entries({ ...defaultColors, ...rawColors })) {