@koehler8/cms 1.0.0-beta.10

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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -0
  3. package/bin/cms-generate-public-assets.js +27 -0
  4. package/bin/cms-validate-extensions.js +18 -0
  5. package/bin/cms-validate-themes.js +7 -0
  6. package/extensions/manifest.schema.json +125 -0
  7. package/package.json +88 -0
  8. package/public/img/preloaders/preloader-black.svg +1 -0
  9. package/public/img/preloaders/preloader-white.svg +1 -0
  10. package/public/robots.txt +5 -0
  11. package/scripts/check-overflow.mjs +33 -0
  12. package/scripts/generate-public-assets.js +412 -0
  13. package/scripts/migrate-to-content-dirs.mjs +223 -0
  14. package/scripts/patch-lru-cache-tla.js +164 -0
  15. package/scripts/validate-extensions.mjs +392 -0
  16. package/scripts/validate-themes.mjs +64 -0
  17. package/src/App.vue +3 -0
  18. package/src/components/About.vue +481 -0
  19. package/src/components/AboutValue.vue +361 -0
  20. package/src/components/BackToTop.vue +42 -0
  21. package/src/components/ComingSoon.vue +411 -0
  22. package/src/components/ComingSoonModal.vue +230 -0
  23. package/src/components/Contact.vue +518 -0
  24. package/src/components/Footer.vue +65 -0
  25. package/src/components/FooterMinimal.vue +153 -0
  26. package/src/components/Header.vue +583 -0
  27. package/src/components/Hero.vue +327 -0
  28. package/src/components/Home.vue +161 -0
  29. package/src/components/Intro.vue +130 -0
  30. package/src/components/IntroGate.vue +444 -0
  31. package/src/components/Plan.vue +116 -0
  32. package/src/components/Portfolio.vue +459 -0
  33. package/src/components/Preloader.vue +20 -0
  34. package/src/components/Principles.vue +67 -0
  35. package/src/components/Spacer15.vue +9 -0
  36. package/src/components/Spacer30.vue +9 -0
  37. package/src/components/Spacer40.vue +9 -0
  38. package/src/components/Spacer60.vue +9 -0
  39. package/src/components/StickyCTA.vue +263 -0
  40. package/src/components/Team.vue +432 -0
  41. package/src/components/icons/IconLinkedIn.vue +22 -0
  42. package/src/components/icons/IconX.vue +22 -0
  43. package/src/components/ui/SbCard.vue +52 -0
  44. package/src/components/ui/SkeletonPulse.vue +117 -0
  45. package/src/components/ui/UnitChip.vue +69 -0
  46. package/src/composables/useComingSoonConfig.js +120 -0
  47. package/src/composables/useComingSoonInterstitial.js +27 -0
  48. package/src/composables/useComponentResolver.js +235 -0
  49. package/src/composables/useEngagementTracking.js +187 -0
  50. package/src/composables/useIntroGate.js +46 -0
  51. package/src/composables/useLazyImage.js +77 -0
  52. package/src/composables/usePageConfig.js +184 -0
  53. package/src/composables/usePageMeta.js +76 -0
  54. package/src/composables/usePromoBackgroundStyles.js +67 -0
  55. package/src/constants/locales.js +20 -0
  56. package/src/extensions/extensionLoader.js +512 -0
  57. package/src/main.js +187 -0
  58. package/src/router/index.js +112 -0
  59. package/src/styles/base.css +896 -0
  60. package/src/styles/layout.css +342 -0
  61. package/src/styles/theme-base.css +84 -0
  62. package/src/themes/buildCssVarMap.js +246 -0
  63. package/src/themes/themeLoader.js +156 -0
  64. package/src/themes/themeManager.js +34 -0
  65. package/src/themes/themeValidator.js +380 -0
  66. package/src/utils/analytics.js +100 -0
  67. package/src/utils/appInfo.js +9 -0
  68. package/src/utils/assetResolver.js +162 -0
  69. package/src/utils/componentRegistry.js +146 -0
  70. package/src/utils/contentRequirements.js +67 -0
  71. package/src/utils/cookieConsent.js +281 -0
  72. package/src/utils/ctaCopy.js +58 -0
  73. package/src/utils/formatNumber.js +115 -0
  74. package/src/utils/imageSources.js +179 -0
  75. package/src/utils/inflateFlatConfig.js +30 -0
  76. package/src/utils/loadConfig.js +319 -0
  77. package/src/utils/semver.js +49 -0
  78. package/src/utils/siteStyles.js +40 -0
  79. package/src/utils/themeColors.js +65 -0
  80. package/src/utils/trackingContext.js +142 -0
  81. package/src/utils/unwrapDefault.js +14 -0
  82. package/src/utils/useScrollReveal.js +48 -0
  83. package/templates/index.html +36 -0
  84. package/themes/base/README.md +23 -0
  85. package/themes/base/theme.config.js +214 -0
  86. package/vite-plugin.js +726 -0
@@ -0,0 +1,342 @@
1
+ :root {
2
+ --layout-container-max-width: min(1180px, 92vw);
3
+ --layout-container-padding: clamp(16px, 4vw, 48px);
4
+ --layout-section-padding: clamp(48px, 12vw, 120px);
5
+ --layout-section-padding-tight: clamp(32px, 10vw, 80px);
6
+ --layout-grid-gutter: clamp(16px, 4vw, 32px);
7
+ --layout-stack-gap-tight: var(--ui-space-12, 12px);
8
+ --layout-stack-gap: var(--ui-space-16, 16px);
9
+ --layout-stack-gap-lg: var(--ui-space-24, 24px);
10
+ --layout-stack-gap-xl: var(--ui-space-40, 40px);
11
+ --layout-breakpoint-sm: 600px;
12
+ --layout-breakpoint-md: 768px;
13
+ --layout-breakpoint-lg: 1024px;
14
+ --layout-breakpoint-xl: 1280px;
15
+ }
16
+
17
+ /* Containers */
18
+ .container {
19
+ width: min(var(--layout-container-max-width), 100%);
20
+ margin-left: auto;
21
+ margin-right: auto;
22
+ padding-left: var(--layout-container-padding);
23
+ padding-right: var(--layout-container-padding);
24
+ }
25
+
26
+ .container-fluid {
27
+ width: 100%;
28
+ margin: 0;
29
+ padding-left: var(--layout-container-padding);
30
+ padding-right: var(--layout-container-padding);
31
+ }
32
+
33
+ .section-shell {
34
+ padding-block: var(--layout-section-padding);
35
+ }
36
+
37
+ .section-shell--tight {
38
+ padding-block: var(--layout-section-padding-tight);
39
+ }
40
+
41
+ /* Spacing helpers */
42
+ .mt-0 {
43
+ margin-top: 0 !important;
44
+ }
45
+
46
+ .mb-0 {
47
+ margin-bottom: 0 !important;
48
+ }
49
+
50
+ .mb-1 {
51
+ margin-bottom: var(--ui-space-8, 8px) !important;
52
+ }
53
+
54
+ .mb-2 {
55
+ margin-bottom: var(--ui-space-12, 12px) !important;
56
+ }
57
+
58
+ .mb-3 {
59
+ margin-bottom: var(--ui-space-16, 16px) !important;
60
+ }
61
+
62
+ .mb-4 {
63
+ margin-bottom: var(--ui-space-24, 24px) !important;
64
+ }
65
+
66
+ .mt-1 {
67
+ margin-top: var(--ui-space-8, 8px) !important;
68
+ }
69
+
70
+ .mt-2 {
71
+ margin-top: var(--ui-space-12, 12px) !important;
72
+ }
73
+
74
+ .mt-3 {
75
+ margin-top: var(--ui-space-16, 16px) !important;
76
+ }
77
+
78
+ .mt-4 {
79
+ margin-top: var(--ui-space-24, 24px) !important;
80
+ }
81
+
82
+ /* Grid */
83
+ .row {
84
+ display: flex;
85
+ flex-wrap: wrap;
86
+ margin-left: calc(var(--layout-grid-gutter) * -0.5);
87
+ margin-right: calc(var(--layout-grid-gutter) * -0.5);
88
+ row-gap: var(--layout-grid-gutter);
89
+ }
90
+
91
+ .row--tight {
92
+ column-gap: var(--ui-gap-md, 16px);
93
+ row-gap: var(--ui-gap-md, 16px);
94
+ }
95
+
96
+ .row > [class*='col-'] {
97
+ padding-left: calc(var(--layout-grid-gutter) * 0.5);
98
+ padding-right: calc(var(--layout-grid-gutter) * 0.5);
99
+ flex: 0 0 auto;
100
+ width: 100%;
101
+ max-width: 100%;
102
+ --col-span: 12;
103
+ }
104
+
105
+ .row .col-auto {
106
+ flex: 0 0 auto;
107
+ width: auto;
108
+ max-width: none;
109
+ }
110
+
111
+ .row .col {
112
+ flex: 1 1 0;
113
+ width: auto;
114
+ max-width: none;
115
+ }
116
+
117
+ .row .col-1 { --col-span: 1; }
118
+ .row .col-2 { --col-span: 2; }
119
+ .row .col-3 { --col-span: 3; }
120
+ .row .col-4 { --col-span: 4; }
121
+ .row .col-5 { --col-span: 5; }
122
+ .row .col-6 { --col-span: 6; }
123
+ .row .col-7 { --col-span: 7; }
124
+ .row .col-8 { --col-span: 8; }
125
+ .row .col-9 { --col-span: 9; }
126
+ .row .col-10 { --col-span: 10; }
127
+ .row .col-11 { --col-span: 11; }
128
+ .row .col-12 { --col-span: 12; }
129
+
130
+ .row > [class*='col-']:not(.col-auto):not(.col) {
131
+ flex-basis: calc((var(--col-span) / 12) * 100%);
132
+ max-width: calc((var(--col-span) / 12) * 100%);
133
+ }
134
+
135
+ @media (min-width: 600px) {
136
+ .row .col-sm-1 { --col-span: 1; }
137
+ .row .col-sm-2 { --col-span: 2; }
138
+ .row .col-sm-3 { --col-span: 3; }
139
+ .row .col-sm-4 { --col-span: 4; }
140
+ .row .col-sm-5 { --col-span: 5; }
141
+ .row .col-sm-6 { --col-span: 6; }
142
+ .row .col-sm-7 { --col-span: 7; }
143
+ .row .col-sm-8 { --col-span: 8; }
144
+ .row .col-sm-9 { --col-span: 9; }
145
+ .row .col-sm-10 { --col-span: 10; }
146
+ .row .col-sm-11 { --col-span: 11; }
147
+ .row .col-sm-12 { --col-span: 12; }
148
+ }
149
+
150
+ @media (min-width: 768px) {
151
+ .row .col-md-1 { --col-span: 1; }
152
+ .row .col-md-2 { --col-span: 2; }
153
+ .row .col-md-3 { --col-span: 3; }
154
+ .row .col-md-4 { --col-span: 4; }
155
+ .row .col-md-5 { --col-span: 5; }
156
+ .row .col-md-6 { --col-span: 6; }
157
+ .row .col-md-7 { --col-span: 7; }
158
+ .row .col-md-8 { --col-span: 8; }
159
+ .row .col-md-9 { --col-span: 9; }
160
+ .row .col-md-10 { --col-span: 10; }
161
+ .row .col-md-11 { --col-span: 11; }
162
+ .row .col-md-12 { --col-span: 12; }
163
+ }
164
+
165
+ @media (min-width: 1024px) {
166
+ .row .col-lg-1 { --col-span: 1; }
167
+ .row .col-lg-2 { --col-span: 2; }
168
+ .row .col-lg-3 { --col-span: 3; }
169
+ .row .col-lg-4 { --col-span: 4; }
170
+ .row .col-lg-5 { --col-span: 5; }
171
+ .row .col-lg-6 { --col-span: 6; }
172
+ .row .col-lg-7 { --col-span: 7; }
173
+ .row .col-lg-8 { --col-span: 8; }
174
+ .row .col-lg-9 { --col-span: 9; }
175
+ .row .col-lg-10 { --col-span: 10; }
176
+ .row .col-lg-11 { --col-span: 11; }
177
+ .row .col-lg-12 { --col-span: 12; }
178
+ }
179
+
180
+ @media (min-width: 1280px) {
181
+ .row .col-xl-1 { --col-span: 1; }
182
+ .row .col-xl-2 { --col-span: 2; }
183
+ .row .col-xl-3 { --col-span: 3; }
184
+ .row .col-xl-4 { --col-span: 4; }
185
+ .row .col-xl-5 { --col-span: 5; }
186
+ .row .col-xl-6 { --col-span: 6; }
187
+ .row .col-xl-7 { --col-span: 7; }
188
+ .row .col-xl-8 { --col-span: 8; }
189
+ .row .col-xl-9 { --col-span: 9; }
190
+ .row .col-xl-10 { --col-span: 10; }
191
+ .row .col-xl-11 { --col-span: 11; }
192
+ .row .col-xl-12 { --col-span: 12; }
193
+ }
194
+
195
+ /* Stack helpers */
196
+ .stack-tight {
197
+ display: flex;
198
+ flex-direction: column;
199
+ gap: var(--layout-stack-gap-tight);
200
+ }
201
+
202
+ .stack {
203
+ display: flex;
204
+ flex-direction: column;
205
+ gap: var(--layout-stack-gap);
206
+ }
207
+
208
+ .stack-lg {
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: var(--layout-stack-gap-lg);
212
+ }
213
+
214
+ .stack-xl {
215
+ display: flex;
216
+ flex-direction: column;
217
+ gap: var(--layout-stack-gap-xl);
218
+ }
219
+
220
+ .gap-sm {
221
+ gap: var(--layout-stack-gap-tight);
222
+ }
223
+
224
+ .gap-md {
225
+ gap: var(--layout-stack-gap);
226
+ }
227
+
228
+ .gap-lg {
229
+ gap: var(--layout-stack-gap-lg);
230
+ }
231
+
232
+ .gap-xl {
233
+ gap: var(--layout-stack-gap-xl);
234
+ }
235
+
236
+ /* Typography helpers */
237
+ .text-center {
238
+ text-align: center !important;
239
+ }
240
+
241
+ .text-left {
242
+ text-align: left !important;
243
+ }
244
+
245
+ .text-right {
246
+ text-align: right !important;
247
+ }
248
+
249
+ .text-muted {
250
+ color: var(--ui-text-muted, var(--brand-fg-300, #54627b));
251
+ }
252
+
253
+ .text-primary {
254
+ color: var(--ui-text-primary, var(--brand-fg-100, #1f2a44));
255
+ }
256
+
257
+ .text-uppercase {
258
+ text-transform: uppercase;
259
+ letter-spacing: 0.12em;
260
+ }
261
+
262
+ .eyebrow-label {
263
+ font-size: 0.85rem;
264
+ font-weight: 600;
265
+ text-transform: uppercase;
266
+ letter-spacing: 0.16em;
267
+ color: var(--ui-text-muted, var(--brand-fg-300, #54627b));
268
+ }
269
+
270
+ /* Flexbox + alignment helpers */
271
+ .d-flex {
272
+ display: flex !important;
273
+ }
274
+
275
+ .flex-column {
276
+ flex-direction: column !important;
277
+ }
278
+
279
+ .flex-wrap {
280
+ flex-wrap: wrap !important;
281
+ }
282
+
283
+ .justify-between {
284
+ justify-content: space-between !important;
285
+ }
286
+
287
+ .justify-center {
288
+ justify-content: center !important;
289
+ }
290
+
291
+ .align-center {
292
+ align-items: center !important;
293
+ }
294
+
295
+ .align-start {
296
+ align-items: flex-start !important;
297
+ }
298
+
299
+ .align-end {
300
+ align-items: flex-end !important;
301
+ }
302
+
303
+ .mx-auto {
304
+ margin-left: auto !important;
305
+ margin-right: auto !important;
306
+ }
307
+
308
+ .w-100 {
309
+ width: 100% !important;
310
+ }
311
+
312
+ .rounded {
313
+ border-radius: 12px;
314
+ }
315
+
316
+ .rounded-lg {
317
+ border-radius: 24px;
318
+ }
319
+
320
+ .img-fluid {
321
+ display: block;
322
+ max-width: 100%;
323
+ height: auto;
324
+ }
325
+
326
+ .text-break {
327
+ overflow-wrap: anywhere;
328
+ }
329
+
330
+ .visually-hidden {
331
+ position: absolute;
332
+ width: 1px;
333
+ height: 1px;
334
+ padding: 0;
335
+ margin: -1px;
336
+ overflow: hidden;
337
+ clip: rect(0, 0, 0, 0);
338
+ white-space: nowrap;
339
+ border: 0;
340
+ }
341
+
342
+ /* Legal pages */
@@ -0,0 +1,84 @@
1
+ :root {
2
+ --about-image-surface: rgba(10, 10, 13, 0.94);
3
+ --about-image-placeholder: linear-gradient(135deg, rgba(20, 18, 22, 0.55), rgba(10, 10, 13, 0.75));
4
+ --responsive-picture-surface: var(--about-image-surface);
5
+ --responsive-picture-placeholder: var(--about-image-placeholder);
6
+ --brand-neon-pink: #e44b8d;
7
+ --brand-crimson-red: #d04f4f;
8
+ --brand-sultry-purple: #5c6ac4;
9
+ --brand-electric-blue: #4f6cf0;
10
+ --brand-amber-heat: #f18f3b;
11
+ --brand-deep-violet: #2f3a68;
12
+
13
+ --brand-bg-900: #f5f7ff;
14
+ --brand-bg-800: #edf1ff;
15
+ --brand-bg-700: #dce4ff;
16
+ --brand-fg-050: #243a80;
17
+ --brand-fg-100: #1f2a44;
18
+ --brand-fg-200: #334261;
19
+ --brand-fg-300: #54627b;
20
+
21
+ --brand-radius-sm: 8px;
22
+ --brand-radius-md: 14px;
23
+ --brand-radius-lg: 24px;
24
+
25
+ --brand-gradient-ignition: linear-gradient(135deg, #4f6cf0 0%, #243a80 100%);
26
+ --brand-shadow-glow: 0 16px 32px rgba(28, 42, 96, 0.28);
27
+ --brand-shadow-glow-strong: 0 22px 44px rgba(23, 35, 80, 0.32);
28
+ --brand-chart-track: rgba(236, 241, 255, 0.85);
29
+ --brand-chart-center-text: var(--brand-fg-100);
30
+ --brand-cta-text: #ffffff;
31
+
32
+ --brand-accent-electric: var(--brand-electric-blue);
33
+ --brand-accent-electric-soft: rgba(79, 108, 240, 0.18);
34
+ --brand-accent-warm: var(--brand-amber-heat);
35
+
36
+ --brand-card-soft: #ffffff;
37
+ --brand-card-border: rgba(79, 108, 240, 0.16);
38
+ --brand-card-shadow: 0 18px 40px rgba(15, 23, 42, 0.12);
39
+
40
+ --brand-primary-cta-gradient: var(--brand-gradient-ignition);
41
+ --brand-primary-cta-text: var(--brand-cta-text);
42
+ --brand-primary-cta-shadow: var(--brand-shadow-glow);
43
+ --brand-primary-cta-hover-shadow: var(--brand-shadow-glow-strong);
44
+ --brand-primary-cta-hover-translate: translateY(-2px);
45
+
46
+ --brand-surface-card-bg: var(--brand-card-soft);
47
+ --brand-surface-card-border: var(--brand-card-border);
48
+ --brand-surface-card-shadow: var(--brand-card-shadow);
49
+ --brand-surface-helper-bg: rgba(79, 108, 240, 0.08);
50
+ --brand-surface-helper-hover-bg: rgba(79, 108, 240, 0.14);
51
+ --brand-card-radius: var(--brand-radius-lg);
52
+ --brand-button-radius: var(--brand-radius-md);
53
+
54
+ --brand-icon-badge-bg: rgba(79, 108, 240, 0.12);
55
+ --brand-icon-badge-color: var(--brand-deep-violet);
56
+
57
+ --brand-pill-gradient: linear-gradient(135deg, var(--brand-electric-blue) 0%, #2b3d7b 100%);
58
+ --brand-pill-alt-gradient: linear-gradient(135deg, var(--brand-sultry-purple) 0%, var(--brand-electric-blue) 100%);
59
+ --brand-pill-contrast: #ffffff;
60
+
61
+ --brand-border-highlight: rgba(79, 108, 240, 0.28);
62
+ --brand-border-glow: rgba(79, 108, 240, 0.32);
63
+
64
+ --brand-status-success: #239c65;
65
+ --brand-status-error: var(--brand-crimson-red);
66
+ --brand-status-error-soft: rgba(208, 79, 79, 0.18);
67
+
68
+ --brand-input-bg: #ffffff;
69
+ --brand-input-border: rgba(79, 108, 240, 0.25);
70
+ --brand-input-border-active: rgba(79, 108, 240, 0.45);
71
+ --brand-input-text: var(--brand-fg-100);
72
+ --brand-input-placeholder: rgba(84, 98, 123, 0.64);
73
+ --brand-focus-ring: 0 0 0 2px rgba(79, 108, 240, 0.3);
74
+ --brand-focus-glow: 0 0 22px rgba(79, 108, 240, 0.22);
75
+
76
+ --brand-modal-backdrop: rgba(15, 23, 42, 0.55);
77
+ --brand-modal-surface: rgba(255, 255, 255, 0.97);
78
+ --brand-modal-border: rgba(79, 108, 240, 0.18);
79
+ --brand-modal-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);
80
+ --brand-modal-radius: var(--brand-radius-lg);
81
+
82
+ --brand-countdown-digit: var(--brand-accent-electric);
83
+ --brand-countdown-label: rgba(84, 98, 123, 0.75);
84
+ }
@@ -0,0 +1,246 @@
1
+ // Pure CSS-variable derivation from a theme manifest. Lives in its own
2
+ // file (with no Vite-specific imports) so it can be called from both
3
+ // the runtime themeManager AND the build-time vite plugin, which uses
4
+ // it to emit `virtual:cms-theme-vars.css` — a synchronously-linked
5
+ // stylesheet containing every registered theme's tokens. That, combined
6
+ // with the SSR-rendered `data-site-theme` attribute (see main.js
7
+ // applySiteTheme), eliminates the FOUC dark-themed sites used to suffer.
8
+
9
+ const toKebab = (value = '') =>
10
+ value
11
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
12
+ .replace(/[\s_]+/g, '-')
13
+ .toLowerCase();
14
+
15
+ const withPx = (value) => {
16
+ if (value === undefined || value === null) return undefined;
17
+ if (typeof value === 'number') return `${value}px`;
18
+ return value;
19
+ };
20
+
21
+ const setVar = (vars, name, value) => {
22
+ if (value === undefined || value === null || value === '') return;
23
+ vars[name] = String(value);
24
+ };
25
+
26
+ const assignPrefixed = (vars, entries = {}, prefix = '') => {
27
+ Object.entries(entries).forEach(([key, value]) => {
28
+ setVar(vars, `${prefix}${key}`, value);
29
+ });
30
+ };
31
+
32
+ function setSurfaceVars(vars, prefix, surface = {}) {
33
+ if (!surface) return;
34
+ setVar(vars, `${prefix}-bg`, surface.background);
35
+ setVar(vars, `${prefix}-before`, surface.before);
36
+ setVar(vars, `${prefix}-after`, surface.after);
37
+ }
38
+
39
+ function coerceSurface(value, defaults = {}) {
40
+ if (value && typeof value === 'object') return value;
41
+ if (!value) return defaults;
42
+ return { ...defaults, background: value };
43
+ }
44
+
45
+ function coerceArray(value) {
46
+ if (!value) return undefined;
47
+ return Array.isArray(value) ? value.filter(Boolean).join(', ') : value;
48
+ }
49
+
50
+ export function buildCssVarMap(manifest) {
51
+ if (!manifest) return {};
52
+ const vars = {};
53
+ const tokens = manifest.tokens || {};
54
+ const palette = tokens.palette || {};
55
+ const text = tokens.text || {};
56
+ const surfaces = tokens.surfaces || {};
57
+ const helper = surfaces.helper || {};
58
+ const tabs = surfaces.tabs || {};
59
+ const field = surfaces.field || {};
60
+ const strip = surfaces.strip || {};
61
+ const chrome = surfaces.chrome || {};
62
+ const heroSurface = surfaces.backdropPrimary || {};
63
+ const promoSurface = surfaces.backdropSecondary || {};
64
+ const typography = tokens.typography || {};
65
+ const ctas = tokens.ctas || {};
66
+ const chips = tokens.chips || {};
67
+ const focus = tokens.focus || {};
68
+ const radii = tokens.radii || {};
69
+ const elevation = tokens.elevation || {};
70
+ const utility = tokens.utility || {};
71
+
72
+ assignPrefixed(
73
+ vars,
74
+ Object.fromEntries(
75
+ Object.entries(palette).map(([key, value]) => [`--brand-${toKebab(key)}`, value])
76
+ )
77
+ );
78
+
79
+ setVar(vars, '--brand-accent-electric', palette.primary || palette.accent);
80
+ setVar(vars, '--brand-accent-electric-soft', palette.accentSoft);
81
+ setVar(vars, '--brand-accent-warm', palette.accent || palette.warning);
82
+
83
+ setVar(vars, '--brand-fg-100', text.primary);
84
+ setVar(vars, '--brand-fg-200', text.muted);
85
+ setVar(vars, '--brand-fg-300', text.mutedStrong);
86
+ setVar(vars, '--brand-fg-050', text.inverse);
87
+ setVar(vars, '--brand-bg-900', surfaces.base);
88
+ setVar(vars, '--brand-bg-800', surfaces.baseAlt);
89
+ setVar(vars, '--brand-bg-700', surfaces.raised);
90
+ setVar(vars, '--brand-bg-600', surfaces.sunken);
91
+
92
+ Object.entries(radii).forEach(([key, value]) => {
93
+ setVar(vars, `--brand-radius-${toKebab(key)}`, withPx(value));
94
+ });
95
+ setVar(vars, '--brand-card-radius', withPx(radii.lg || radii.md));
96
+ setVar(vars, '--brand-button-radius', withPx(radii.md || radii.sm));
97
+
98
+ setVar(vars, '--brand-shadow-glow', elevation.raised);
99
+ setVar(vars, '--brand-shadow-glow-strong', elevation.overlay);
100
+ setVar(vars, '--brand-card-shadow', elevation.raised);
101
+ setVar(vars, '--brand-surface-card-shadow', elevation.raised);
102
+
103
+ const cardTextColor = text.card || text.primary;
104
+ setVar(vars, '--brand-card-soft', surfaces.card);
105
+ setVar(vars, '--brand-surface-card-bg', surfaces.card);
106
+ setVar(vars, '--brand-card-border', surfaces.border);
107
+ setVar(vars, '--brand-surface-card-border', surfaces.border);
108
+ setVar(vars, '--brand-card-text', cardTextColor);
109
+ setVar(vars, '--brand-surface-helper-bg', helper.background || surfaces.callout);
110
+ setVar(vars, '--brand-surface-helper-hover-bg', helper.hover || helper.background);
111
+ setVar(vars, '--brand-icon-badge-bg', surfaces.chipAccent || surfaces.chip);
112
+ setVar(vars, '--brand-icon-badge-color', palette.primary || text.primary);
113
+
114
+ setVar(vars, '--brand-border-highlight', surfaces.border);
115
+ setVar(vars, '--brand-border-glow', surfaces.border);
116
+
117
+ setVar(vars, '--brand-status-success', palette.success);
118
+ setVar(vars, '--brand-status-error', palette.critical);
119
+ setVar(vars, '--brand-status-error-soft', palette.criticalSoft);
120
+
121
+ setVar(vars, '--brand-input-bg', field.background || surfaces.input);
122
+ setVar(vars, '--brand-input-border', field.border || utility.inputBorder);
123
+ setVar(vars, '--brand-input-border-active', focus.ring);
124
+ setVar(vars, '--brand-input-text', field.inputColor || utility.inputText || text.primary);
125
+ setVar(
126
+ vars,
127
+ '--brand-input-placeholder',
128
+ field.inputPlaceholder || utility.inputPlaceholder || text.muted
129
+ );
130
+ setVar(vars, '--brand-focus-ring', focus.ring);
131
+ setVar(vars, '--brand-focus-glow', focus.shadowInset);
132
+
133
+ setVar(vars, '--brand-modal-backdrop', surfaces.backdrop);
134
+ setVar(vars, '--brand-modal-surface', utility.modalSurface);
135
+ setVar(vars, '--brand-modal-border', utility.modalBorder);
136
+ setVar(vars, '--brand-modal-shadow', utility.modalShadow);
137
+ setVar(vars, '--brand-modal-radius', withPx(utility.modalRadius));
138
+
139
+ setVar(vars, '--brand-chart-track', utility.chartTrack);
140
+ setVar(vars, '--brand-chart-center-text', utility.chartCenterText);
141
+
142
+ setVar(vars, '--brand-header-bg', chrome.background);
143
+ setVar(vars, '--brand-header-text', chrome.text);
144
+ setVar(vars, '--site-header-shadow', chrome.shadow);
145
+ setVar(vars, '--site-header-shadow-compact', chrome.compactShadow);
146
+
147
+ const primaryCta = ctas.primary || {};
148
+ setVar(vars, '--brand-primary-cta-gradient', primaryCta.bg);
149
+ setVar(vars, '--brand-primary-cta-text', primaryCta.text);
150
+ setVar(vars, '--brand-primary-cta-border', primaryCta.border);
151
+ setVar(vars, '--brand-primary-cta-shadow', primaryCta.shadow);
152
+ setVar(vars, '--brand-primary-cta-hover-shadow', primaryCta.shadow);
153
+ setVar(vars, '--brand-primary-cta-hover-translate', 'translateY(-1px)');
154
+ setVar(vars, '--brand-cta-text', primaryCta.text);
155
+
156
+ setVar(vars, '--helper-strip-bg', helper.background || surfaces.callout);
157
+ setVar(vars, '--helper-strip-border', helper.border || surfaces.border);
158
+ setVar(vars, '--helper-strip-color', helper.text || text.primary);
159
+ setVar(vars, '--helper-strip-hover-bg', helper.hover || helper.background);
160
+ setVar(vars, '--helper-strip-hover-color', helper.hoverColor || helper.text || text.primary);
161
+ setVar(vars, '--helper-strip-link-hover', helper.linkHover || palette.primary);
162
+ setVar(vars, '--helper-strip-heading-color', helper.heading || text.primary);
163
+ setVar(vars, '--helper-strip-body-color', helper.body || text.mutedStrong || text.muted);
164
+
165
+ setVar(vars, '--tabs-bg', tabs.background);
166
+ setVar(vars, '--tabs-border', tabs.border || surfaces.border);
167
+ setVar(vars, '--tabs-shadow', tabs.shadow || elevation.flat);
168
+ setVar(vars, '--tab-color', tabs.tabColor || text.muted);
169
+ setVar(vars, '--tab-active-bg', tabs.activeBackground || palette.primary);
170
+ setVar(vars, '--tab-active-color', tabs.activeColor || palette.inverse || '#fff');
171
+ setVar(vars, '--tab-active-shadow', tabs.activeShadow || elevation.raised);
172
+ setVar(vars, '--tab-step-bg', tabs.stepBackground || helper.background);
173
+ setVar(vars, '--tab-step-border', tabs.stepBorder || helper.border);
174
+ setVar(vars, '--tab-step-color', tabs.stepColor || palette.primary);
175
+ setVar(vars, '--tab-active-step-bg', tabs.activeStepBackground || palette.primary);
176
+ setVar(vars, '--tab-active-step-border', tabs.activeStepBorder || tabs.stepBorder);
177
+ setVar(vars, '--tab-active-step-color', tabs.activeStepColor || palette.inverse || '#fff');
178
+
179
+ setVar(vars, '--field-bg', field.background);
180
+ setVar(vars, '--field-border', field.border || utility.inputBorder);
181
+ setVar(vars, '--field-shadow', field.shadow || elevation.flat);
182
+ setVar(vars, '--field-addon-bg', field.addonBackground || helper.background);
183
+ setVar(vars, '--field-addon-border', field.addonBorder || helper.border);
184
+ setVar(vars, '--field-addon-color', field.addonColor || helper.text || text.primary);
185
+ setVar(vars, '--field-input-color', field.inputColor || text.primary);
186
+ setVar(vars, '--field-input-placeholder', field.inputPlaceholder || text.muted);
187
+
188
+ setVar(vars, '--community-strip-bg', strip.background || surfaces.card);
189
+ setVar(vars, '--community-strip-border', strip.border || surfaces.border);
190
+ setVar(vars, '--community-strip-color', strip.text || text.primary);
191
+
192
+ setVar(vars, '--ui-text-primary', text.primary);
193
+ setVar(vars, '--ui-text-muted', text.muted);
194
+ setVar(vars, '--ui-field-label', text.muted);
195
+ setVar(vars, '--ui-field-value', text.primary);
196
+ setVar(vars, '--ui-status-heading-color', text.accent || palette.primary);
197
+
198
+ setVar(vars, '--brand-countdown-digit', palette.primary);
199
+ setVar(vars, '--brand-countdown-label', text.muted);
200
+
201
+ const secondaryCta = ctas.secondary || {};
202
+ setVar(vars, '--brand-pill-gradient', primaryCta.bg);
203
+ setVar(vars, '--brand-pill-alt-gradient', secondaryCta.bg || primaryCta.bg);
204
+ setVar(vars, '--brand-pill-contrast', primaryCta.text);
205
+
206
+ setVar(vars, '--brand-chip-neutral-bg', chips.neutral?.bg);
207
+ setVar(vars, '--brand-chip-neutral-color', chips.neutral?.text);
208
+ setVar(vars, '--brand-chip-neutral-border', chips.neutral?.border);
209
+
210
+ const heroBg = coerceArray(utility.gradientHero || heroSurface.background);
211
+ const promoBg = coerceArray(utility.gradientPromo || promoSurface.background);
212
+ if (heroBg) setVar(vars, '--brand-gradient-hero', heroBg);
213
+ if (promoBg) setVar(vars, '--brand-gradient-promo', promoBg);
214
+
215
+ setSurfaceVars(vars, '--hero-surface', heroSurface);
216
+ setSurfaceVars(vars, '--promo-surface', promoSurface);
217
+ const bodyBackground = coerceArray(utility.bodyBackground);
218
+ if (bodyBackground) {
219
+ setVar(vars, '--theme-body-background', bodyBackground);
220
+ }
221
+
222
+ const statusHeadline = coerceSurface(utility.statusHeadline, {
223
+ color: palette.primary || text.primary,
224
+ shadow: elevation.raised,
225
+ });
226
+
227
+ setVar(vars, '--status-headline-bg', statusHeadline.background);
228
+ setVar(vars, '--status-headline-color', statusHeadline.color);
229
+ setVar(vars, '--status-headline-shadow', statusHeadline.shadow);
230
+
231
+ setVar(vars, '--status-price-value-color', palette.accent || palette.primary || text.primary);
232
+ setVar(vars, '--status-price-value-shadow', elevation.raised);
233
+
234
+ return vars;
235
+ }
236
+
237
+ /**
238
+ * Render the var map as a CSS rule body.
239
+ * @param {object} vars Map of CSS custom property name → value
240
+ */
241
+ export function renderCssVarRule(selector, vars) {
242
+ const declarations = Object.entries(vars)
243
+ .map(([name, value]) => ` ${name}: ${value};`)
244
+ .join('\n');
245
+ return `${selector} {\n${declarations}\n}`;
246
+ }