@salesforce/ui-theme-manager 11.62.1 → 11.64.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.
Files changed (45) hide show
  1. package/dist/authoring/core/ThemeManagerPanelRoot.d.cts +7 -0
  2. package/dist/authoring/core/ThemeManagerPanelRoot.d.ts +7 -0
  3. package/dist/authoring/core/ThemeManagerPanelRoot.d.ts.map +1 -1
  4. package/dist/authoring/core/components/BordersSection.d.ts.map +1 -1
  5. package/dist/authoring/core/components/ButtonSection.d.cts +7 -0
  6. package/dist/authoring/core/components/ButtonSection.d.ts +7 -0
  7. package/dist/authoring/core/components/ButtonSection.d.ts.map +1 -1
  8. package/dist/authoring/core/components/ScaleValueRow.d.cts +43 -0
  9. package/dist/authoring/core/components/ScaleValueRow.d.ts +43 -0
  10. package/dist/authoring/core/components/ScaleValueRow.d.ts.map +1 -0
  11. package/dist/authoring/core/components/SectionTabs.d.cts +23 -0
  12. package/dist/authoring/core/components/SectionTabs.d.ts +23 -0
  13. package/dist/authoring/core/components/SectionTabs.d.ts.map +1 -0
  14. package/dist/authoring/core/components/TypographySection.d.ts.map +1 -1
  15. package/dist/authoring/core/labels.d.cts +22 -5
  16. package/dist/authoring/core/labels.d.ts +22 -5
  17. package/dist/authoring/core/labels.d.ts.map +1 -1
  18. package/dist/authoring/react/index.js +1 -1
  19. package/dist/authoring/react/styles.css +1 -1
  20. package/dist/authoring/web-component/index.d.cts +8 -0
  21. package/dist/authoring/web-component/index.d.ts +8 -0
  22. package/dist/authoring/web-component/index.d.ts.map +1 -1
  23. package/dist/authoring/web-component/index.js +180 -19
  24. package/dist/protocol/brandCss.cjs +38 -1
  25. package/dist/protocol/brandCss.d.cts +19 -0
  26. package/dist/protocol/brandCss.d.ts +19 -0
  27. package/dist/protocol/brandCss.d.ts.map +1 -1
  28. package/dist/protocol/brandCss.js +39 -2
  29. package/dist/protocol/brandSearch.cjs +24 -10
  30. package/dist/protocol/brandSearch.d.cts +24 -0
  31. package/dist/protocol/brandSearch.d.ts +24 -0
  32. package/dist/protocol/brandSearch.d.ts.map +1 -1
  33. package/dist/protocol/brandSearch.js +24 -10
  34. package/dist/protocol/index.cjs +6 -0
  35. package/dist/protocol/index.js +8 -2
  36. package/dist/runtime/previewContent.cjs +496 -217
  37. package/dist/runtime/previewContent.d.cts +31 -12
  38. package/dist/runtime/previewContent.d.ts +31 -12
  39. package/dist/runtime/previewContent.d.ts.map +1 -1
  40. package/dist/runtime/previewContent.js +498 -219
  41. package/dist/runtime/theme-preview-runtime.js +2526 -0
  42. package/package.json +1 -1
  43. package/dist/authoring/core/components/AssetsSection.d.cts +0 -12
  44. package/dist/authoring/core/components/AssetsSection.d.ts +0 -12
  45. package/dist/authoring/core/components/AssetsSection.d.ts.map +0 -1
@@ -1,34 +1,30 @@
1
1
  import { generateFontFaceRules } from "./fontFace.js";
2
- import { brandToVariables } from "../protocol/brandCss.js";
2
+ import { brandToVariables, oklchDisplayVars } from "../protocol/brandCss.js";
3
3
  import { resolveBrandTokens } from "../protocol/brandResolver.js";
4
- import { getBrandLogoUrl, getBrandLogoAltText } from "../protocol/brandTypes.js";
4
+ import { getBrandTitle } from "../protocol/brandTypes.js";
5
5
  import { themeToVariables } from "../protocol/themeCss.js";
6
- function escapeHtmlAttr(value) {
6
+ function escapeHtml(value) {
7
7
  return String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
8
8
  }
9
9
  function getPreviewHtml(theme, options) {
10
10
  const variables = themeToVariables(theme);
11
- return generatePreviewHtml(variables, options);
11
+ return generatePreviewHtml(variables, options, void 0, theme.metadata?.label);
12
12
  }
13
13
  function getBrandPreviewHtml(brand, options) {
14
14
  const resolved = resolveBrandTokens(brand);
15
15
  const variables = brandToVariables(resolved);
16
- return generatePreviewHtml(variables, options, resolved);
16
+ return generatePreviewHtml(variables, options, resolved, getBrandTitle(resolved));
17
17
  }
18
- function generatePreviewHtml(variables, options, brand) {
19
- const cssVariables = Object.entries(variables).map(([key, value]) => ` ${key}: ${value};`).join("\n");
18
+ function generatePreviewHtml(variables, options, brand, title) {
19
+ const allVariables = { ...variables, ...oklchDisplayVars(variables) };
20
+ const cssVariables = Object.entries(allVariables).map(([key, value]) => ` ${key}: ${value};`).join("\n");
20
21
  const fontFaceRules = brand ? generateFontFaceRules(brand) : "";
21
22
  const scriptSrc = options?.runtimeScriptUri || "/_sfdc/theme-preview-runtime.js";
22
23
  const nonce = options?.nonce || "";
23
24
  const cspSource = options?.cspSource || "";
25
+ const scriptTag = options?.omitRuntimeScript ? "" : `<script${nonce ? ` nonce="${nonce}"` : ""} src="${scriptSrc}"><\/script>`;
24
26
  const cspMeta = cspSource ? ` <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}' ${cspSource}; img-src https: data: ${cspSource}; font-src https: data:;">` : "";
25
- const heroImage = options?.imageUris?.hero;
26
- const section3Image = options?.imageUris?.section3Left;
27
- const section4Image = options?.imageUris?.section4Right;
28
- const logoUrl = brand ? getBrandLogoUrl(brand) : void 0;
29
- const logoAlt = brand ? getBrandLogoAltText(brand) : "";
30
- const heroImg = heroImage ? `<img data-brand-hero src="${heroImage}" alt=""${logoUrl ? " hidden" : ""} />` : "";
31
- const logoImg = logoUrl ? `<img class="brand-logo" data-brand-logo src="${escapeHtmlAttr(logoUrl)}" alt="${escapeHtmlAttr(logoAlt)}" />` : "";
27
+ const brandName = escapeHtml(title && title.trim().length > 0 ? title : "Your Brand");
32
28
  return `<!DOCTYPE html>
33
29
  <html lang="en">
34
30
  <head>
@@ -50,295 +46,578 @@ ${cssVariables}
50
46
  body {
51
47
  font-family: var(--tm-font-body);
52
48
  font-size: var(--tm-font-size-base);
49
+ line-height: var(--tm-line-height, 1.5);
53
50
  color: var(--tm-color-text);
54
- background-color: var(--tm-color-background);
55
- line-height: var(--tm-line-height);
51
+ /* Neutral page behind the card; the card itself carries the brand surface. */
52
+ background: #0f1115;
56
53
  -webkit-font-smoothing: antialiased;
57
54
  -moz-osx-font-smoothing: grayscale;
58
- margin: 0;
59
- padding: 0;
55
+ padding: 1rem;
56
+ display: flex;
57
+ justify-content: center;
60
58
  }
61
59
 
62
- /* ===== BRAND PREVIEW SECTIONS ===== */
63
- .brand-section {
64
- padding: 1.5rem;
65
- background-color: var(--tm-color-background);
66
- color: var(--tm-color-text);
67
- border-radius: var(--tm-border-radius-square, 0);
68
- border-width: var(--tm-border-weight-none, 0);
69
- margin: 0;
70
- transition: all 0.25s ease;
60
+ /* ===== CARD SHELL ===== */
61
+ .snapshot-card {
62
+ width: 100%;
63
+ max-width: 340px;
64
+ background: #16181d;
65
+ border-radius: 18px;
66
+ padding: 8px;
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: 8px;
70
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
71
71
  }
72
72
 
73
- .brand-section img {
74
- max-width: 100%;
75
- height: auto;
76
- display: block;
77
- border-radius: var(--tm-border-radius-square, 0);
73
+ /* ===== HEADER: "Live snapshot" + "See full preview" ===== */
74
+ .snapshot-header {
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: space-between;
78
+ padding: 8px 10px;
79
+ color: #b7bcc7;
80
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
81
+ font-size: 0.78rem;
78
82
  }
79
83
 
80
- /* Brand logo replaces the top hero image: half the container width, capped
81
- at 200px tall, centered. object-fit keeps aspect ratio when the height cap
82
- applies to a tall logo. */
83
- .brand-logo {
84
- width: 50%;
85
- max-width: 50%;
86
- height: auto;
87
- max-height: 200px;
88
- object-fit: contain;
89
- margin: 0 auto;
90
- display: block;
84
+ .snapshot-header .label {
85
+ display: inline-flex;
86
+ align-items: center;
87
+ gap: 6px;
91
88
  }
92
89
 
93
- [hidden] {
94
- display: none !important;
90
+ .snapshot-header .eye {
91
+ opacity: 0.7;
95
92
  }
96
93
 
97
- .brand-two-column {
98
- display: grid;
99
- grid-template-columns: 1fr 1fr;
100
- gap: 0.75rem;
94
+ .snapshot-header .full-preview {
95
+ color: #d7dae1;
96
+ opacity: 0.85;
97
+ white-space: nowrap;
98
+ cursor: pointer;
99
+ background: none;
100
+ border: none;
101
+ font: inherit;
102
+ padding: 0;
101
103
  }
102
104
 
103
- @media (max-width: 768px) {
104
- .brand-two-column {
105
- grid-template-columns: 1fr;
106
- }
105
+ .snapshot-header .full-preview:hover {
106
+ opacity: 1;
107
+ text-decoration: underline;
107
108
  }
108
109
 
109
- /* ===== TYPOGRAPHY ===== */
110
- .brand-h1 {
111
- font-family: var(--tm-heading1-font, var(--tm-font-heading));
112
- font-size: var(--tm-heading1-size, 2rem);
113
- font-weight: var(--tm-heading1-weight, var(--tm-heading-weight));
114
- line-height: var(--tm-heading1-line-height, 1.2);
110
+ /* ===== BRAND SURFACE (the white inner area) ===== */
111
+ .brand-surface {
112
+ background: var(--tm-color-background);
115
113
  color: var(--tm-color-text);
116
- margin: 0 0 0.75rem 0;
117
- text-align: left;
118
- transition: all 0.25s ease;
114
+ border-radius: 12px;
115
+ padding: 18px 16px 20px;
116
+ display: flex;
117
+ flex-direction: column;
118
+ gap: 16px;
119
+ transition: background-color 0.25s ease, color 0.25s ease;
119
120
  }
120
121
 
121
- .brand-h2 {
122
- font-family: var(--tm-heading2-font, var(--tm-font-heading));
123
- font-size: var(--tm-heading2-size, 1.75rem);
124
- font-weight: var(--tm-heading2-weight, var(--tm-heading-weight));
125
- line-height: var(--tm-heading2-line-height, 1.2);
122
+ /* Brand title with accent dot */
123
+ .brand-title {
124
+ display: flex;
125
+ align-items: center;
126
+ gap: 9px;
127
+ /* Drive the specimen off --tm-font-body: it's the variable the panel's
128
+ single "Base font family" control actually edits. --tm-font-heading is
129
+ derived from heading1.fontFamily, which many brands hardcode (not a ref
130
+ to baseFontFamily), so keying headings off it makes the font dropdown
131
+ look broken. When a dedicated heading-font control exists, restore
132
+ var(--tm-font-heading, var(--tm-font-body)) here and below. */
133
+ font-family: var(--tm-font-body);
134
+ font-weight: var(--tm-heading-weight, 700);
135
+ font-size: 1.15rem;
126
136
  color: var(--tm-color-text);
127
- margin: 0.75rem 0 0 0;
128
- text-align: left;
137
+ }
138
+
139
+ .brand-title .dot {
140
+ width: 10px;
141
+ height: 10px;
142
+ border-radius: 50%;
143
+ background: var(--tm-color-accent, var(--tm-color-primary));
144
+ flex: 0 0 auto;
145
+ transition: background-color 0.25s ease;
146
+ }
147
+
148
+ /* Action row: primary pill + accent chip */
149
+ .action-row {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 12px;
153
+ }
154
+
155
+ .btn-primary {
156
+ padding: var(--tm-button-padding-y, 0.6rem) var(--tm-button-padding-x, 1.1rem);
157
+ background: var(--tm-button-primary-bg, var(--tm-color-primary));
158
+ color: var(--tm-button-primary-text, #ffffff);
159
+ border: var(--tm-button-primary-border-width, 0) solid var(--tm-button-primary-border, transparent);
160
+ border-radius: var(--tm-button-radius, var(--tm-border-radius, 999px));
161
+ font-family: var(--tm-font-body);
162
+ font-weight: var(--tm-body-weight, 600);
163
+ font-size: 0.9rem;
164
+ cursor: pointer;
129
165
  transition: all 0.25s ease;
130
166
  }
131
167
 
132
- .brand-h3 {
133
- font-family: var(--tm-heading3-font, var(--tm-font-heading));
134
- font-size: var(--tm-heading3-size, 1.5rem);
135
- font-weight: var(--tm-heading3-weight, var(--tm-heading-weight));
136
- line-height: var(--tm-heading3-line-height, 1.2);
137
- color: var(--tm-color-text);
138
- margin: 0 0 0.75rem 0;
139
- text-align: left;
168
+ .chip-accent {
169
+ padding: 0.4rem 0.75rem;
170
+ background: var(--tm-color-accent, var(--tm-color-primary));
171
+ color: var(--tm-color-background);
172
+ border-radius: 999px;
173
+ font-family: var(--tm-font-body);
174
+ font-weight: 700;
175
+ font-size: 0.72rem;
176
+ letter-spacing: 0.06em;
177
+ text-transform: uppercase;
140
178
  transition: all 0.25s ease;
141
179
  }
142
180
 
143
- .brand-h4 {
144
- font-family: var(--tm-heading4-font, var(--tm-font-heading));
145
- font-size: var(--tm-heading4-size, 1.25rem);
146
- font-weight: var(--tm-heading4-weight, var(--tm-heading-weight));
147
- line-height: var(--tm-heading4-line-height, 1.2);
181
+ /* Typography specimen card */
182
+ .type-specimen {
183
+ /* Plain fallback first; color-mix wins where supported (Chrome 111+). */
184
+ border: 1px solid rgba(0, 0, 0, 0.12);
185
+ border: 1px solid color-mix(in srgb, var(--tm-color-text) 12%, transparent);
186
+ border-radius: var(--tm-border-radius-round, 12px);
187
+ padding: 16px;
188
+ display: flex;
189
+ flex-direction: column;
190
+ gap: 6px;
191
+ }
192
+
193
+ /* The specimen font-size reads the SAME scale vars the panel's size sliders
194
+ edit (--tm-heading*-size / --tm-paragraph*-size), so dragging a slider is
195
+ visible here. Fallbacks keep the original showcase sizing when a var is
196
+ absent (e.g. the Theme path, which doesn't emit the scale). */
197
+ .type-specimen .aa {
198
+ font-family: var(--tm-font-body);
199
+ font-weight: var(--tm-heading-weight, 700);
200
+ font-size: var(--tm-heading1-size, 2.4rem);
201
+ line-height: 1;
148
202
  color: var(--tm-color-text);
149
- margin: 0 0 0.75rem 0;
150
- text-align: left;
151
- transition: all 0.25s ease;
152
203
  }
153
204
 
154
- .brand-h5 {
155
- font-family: var(--tm-heading5-font, var(--tm-font-heading));
156
- font-size: var(--tm-heading5-size, 1.1rem);
157
- font-weight: var(--tm-heading5-weight, var(--tm-heading-weight));
158
- line-height: var(--tm-heading5-line-height, 1.2);
205
+ .type-specimen .headline {
206
+ font-family: var(--tm-font-body);
207
+ font-weight: var(--tm-heading-weight, 700);
208
+ font-size: var(--tm-heading2-size, 1.35rem);
159
209
  color: var(--tm-color-text);
160
- margin: 0.75rem 0;
161
- text-align: center;
162
- padding: 0.75rem;
163
- transition: all 0.25s ease;
164
210
  }
165
211
 
166
- .brand-h6 {
167
- font-family: var(--tm-heading6-font, var(--tm-font-heading));
168
- font-size: var(--tm-heading6-size, 1rem);
169
- font-weight: var(--tm-heading6-weight, var(--tm-heading-weight));
170
- line-height: var(--tm-heading6-line-height, 1.2);
212
+ .type-specimen .body {
213
+ font-family: var(--tm-font-body);
214
+ font-weight: var(--tm-body-weight, 400);
215
+ font-size: var(--tm-paragraph1-size, 0.9rem);
216
+ line-height: var(--tm-line-height, 1.5);
217
+ /* Plain fallback first; color-mix (softer muted text) wins where supported. */
171
218
  color: var(--tm-color-text);
172
- margin: 0 0 0.75rem 0;
173
- text-align: center;
174
- transition: all 0.25s ease;
219
+ color: color-mix(in srgb, var(--tm-color-text) 65%, transparent);
220
+ }
221
+
222
+ /* ===== PALETTE ===== */
223
+ .palette {
224
+ display: flex;
225
+ flex-direction: column;
226
+ gap: 9px;
227
+ padding: 0 4px;
228
+ }
229
+
230
+ .palette-title {
231
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
232
+ font-size: 0.68rem;
233
+ font-weight: 600;
234
+ letter-spacing: 0.14em;
235
+ text-transform: uppercase;
236
+ color: #8b909b;
237
+ margin-bottom: 2px;
175
238
  }
176
239
 
177
- .brand-p1 {
178
- font-family: var(--tm-paragraph1-font, var(--tm-font-body));
179
- font-size: var(--tm-paragraph1-size, 1rem);
180
- font-weight: var(--tm-paragraph1-weight, var(--tm-body-weight));
181
- line-height: var(--tm-paragraph1-line-height, var(--tm-line-height));
240
+ .palette-row {
241
+ display: grid;
242
+ grid-template-columns: 18px 90px 1fr;
243
+ align-items: center;
244
+ gap: 10px;
245
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
246
+ font-size: 0.8rem;
247
+ color: #c7ccd6;
248
+ }
249
+
250
+ .palette-row .swatch {
251
+ width: 18px;
252
+ height: 18px;
253
+ border-radius: 5px;
254
+ border: 1px solid rgba(255, 255, 255, 0.12);
255
+ transition: background-color 0.25s ease;
256
+ }
257
+
258
+ .palette-row .oklch {
259
+ font-family: "SFMono-Regular", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
260
+ font-size: 0.76rem;
261
+ color: #9aa0ac;
262
+ }
263
+ /* Live-updating OKLCH label, sourced from a --tm-*-oklch CSS variable so a
264
+ color edit re-renders the text without any script. */
265
+ .palette-row .oklch::before {
266
+ content: var(--oklch-label, "");
267
+ }
268
+
269
+ /* ===== FORM ===== */
270
+ .form-field {
271
+ display: flex;
272
+ flex-direction: column;
273
+ gap: 6px;
274
+ padding: 0 4px 4px;
275
+ }
276
+
277
+ .form-field label {
278
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
279
+ font-size: 0.8rem;
280
+ font-weight: 600;
281
+ color: #d7dae1;
282
+ }
283
+
284
+ .form-field input {
285
+ width: 100%;
286
+ padding: 10px 12px;
287
+ background: transparent;
288
+ border: 1px solid rgba(255, 255, 255, 0.14);
289
+ border-radius: var(--tm-border-radius, 8px);
290
+ color: #d7dae1;
291
+ font-family: var(--tm-font-body);
292
+ font-size: 0.85rem;
293
+ }
294
+
295
+ .form-field input::placeholder {
296
+ color: #6a6f7a;
297
+ }
298
+
299
+ *, *::before, *::after {
300
+ transition-property: color, background-color, border-color;
301
+ transition-duration: 0.25s;
302
+ transition-timing-function: ease;
303
+ }
304
+ </style>
305
+ </head>
306
+ <body>
307
+ <div class="snapshot-card">
308
+ <div class="snapshot-header">
309
+ <span class="label"><span class="eye" aria-hidden="true">&#128065;</span> Live snapshot</span>
310
+ <button type="button" class="full-preview">See full preview <span aria-hidden="true">&rarr;</span></button>
311
+ </div>
312
+
313
+ <div class="brand-surface">
314
+ <div class="brand-title"><span class="dot"></span>${brandName}</div>
315
+
316
+ <div class="action-row">
317
+ <button class="btn-primary">Primary action</button>
318
+ <span class="chip-accent">Accent</span>
319
+ </div>
320
+
321
+ <div class="type-specimen">
322
+ <div class="aa">Aa</div>
323
+ <div class="headline">Built on trust</div>
324
+ <div class="body">Surfaces stay calm, decisions stay legible. Every token here is the live draft.</div>
325
+ </div>
326
+ </div>
327
+
328
+ <div class="palette">
329
+ <div class="palette-title">Palette</div>
330
+ <div class="palette-row"><span class="swatch" style="background: var(--tm-color-primary);"></span><span>Primary</span><span class="oklch" style="--oklch-label: var(--tm-color-primary-oklch, '');"></span></div>
331
+ <div class="palette-row"><span class="swatch" style="background: var(--tm-color-secondary);"></span><span>Secondary</span><span class="oklch" style="--oklch-label: var(--tm-color-secondary-oklch, '');"></span></div>
332
+ <div class="palette-row"><span class="swatch" style="background: var(--tm-color-accent);"></span><span>Accent</span><span class="oklch" style="--oklch-label: var(--tm-color-accent-oklch, '');"></span></div>
333
+ <div class="palette-row"><span class="swatch" style="background: var(--tm-color-background);"></span><span>Background</span><span class="oklch" style="--oklch-label: var(--tm-color-background-oklch, '');"></span></div>
334
+ <div class="palette-row"><span class="swatch" style="background: var(--tm-color-surface);"></span><span>Muted</span><span class="oklch" style="--oklch-label: var(--tm-color-surface-oklch, '');"></span></div>
335
+ </div>
336
+
337
+ <div class="form-field">
338
+ <label for="work-email">Work email</label>
339
+ <input id="work-email" type="email" placeholder="name@company.com" readonly />
340
+ </div>
341
+ </div>
342
+
343
+ ${scriptTag}
344
+ </body>
345
+ </html>`;
346
+ }
347
+ function getBrandFullPreviewHtml(brand, options) {
348
+ const resolved = resolveBrandTokens(brand);
349
+ const variables = brandToVariables(resolved);
350
+ return generateFullPreviewHtml(variables, options, resolved, getBrandTitle(resolved));
351
+ }
352
+ function generateFullPreviewHtml(variables, options, brand, title) {
353
+ const allVariables = { ...variables, ...oklchDisplayVars(variables) };
354
+ const cssVariables = Object.entries(allVariables).map(([key, value]) => ` ${key}: ${value};`).join("\n");
355
+ const fontFaceRules = brand ? generateFontFaceRules(brand) : "";
356
+ const scriptSrc = options?.runtimeScriptUri || "/_sfdc/theme-preview-runtime.js";
357
+ const nonce = options?.nonce || "";
358
+ const cspSource = options?.cspSource || "";
359
+ const cspMeta = cspSource ? ` <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}' ${cspSource}; img-src https: data: ${cspSource}; font-src https: data:;">` : "";
360
+ const brandName = escapeHtml(title && title.trim().length > 0 ? title : "Your Brand");
361
+ return `<!DOCTYPE html>
362
+ <html lang="en">
363
+ <head>
364
+ <meta charset="UTF-8">
365
+ ${cspMeta}
366
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
367
+ <title>Brand Preview</title>
368
+ <style>${fontFaceRules}
369
+ :root {
370
+ ${cssVariables}
371
+ }
372
+
373
+ * {
374
+ margin: 0;
375
+ padding: 0;
376
+ box-sizing: border-box;
377
+ }
378
+
379
+ body {
380
+ font-family: var(--tm-font-body);
381
+ font-size: var(--tm-font-size-base);
382
+ line-height: var(--tm-line-height, 1.5);
182
383
  color: var(--tm-color-text);
183
- margin: 0 0 0.75rem 0;
184
- text-align: left;
185
- transition: all 0.25s ease;
384
+ background: var(--tm-color-background);
385
+ -webkit-font-smoothing: antialiased;
386
+ -moz-osx-font-smoothing: grayscale;
387
+ min-height: 100vh;
186
388
  }
187
389
 
188
- .brand-p1.center {
189
- text-align: center;
390
+ .fp-page {
391
+ max-width: 1120px;
392
+ margin: 0 auto;
393
+ padding: 40px 56px 64px;
190
394
  }
191
395
 
192
- .brand-p2 {
193
- font-family: var(--tm-paragraph2-font, var(--tm-font-body));
194
- font-size: var(--tm-paragraph2-size, 0.875rem);
195
- font-weight: var(--tm-paragraph2-weight, var(--tm-body-weight));
196
- line-height: var(--tm-paragraph2-line-height, var(--tm-line-height));
396
+ /* Monospace chrome: the "PREVIEW · MARKETING/INDEX.TSX · OKLCH(...)" line. */
397
+ .fp-eyebrow {
398
+ display: flex;
399
+ align-items: center;
400
+ gap: 12px;
401
+ font-family: "SFMono-Regular", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
402
+ font-size: 0.72rem;
403
+ letter-spacing: 0.12em;
404
+ text-transform: uppercase;
405
+ color: color-mix(in srgb, var(--tm-color-text) 55%, transparent);
197
406
  color: var(--tm-color-text);
198
- margin: 0;
199
- text-align: center;
200
- transition: all 0.25s ease;
407
+ opacity: 0.55;
408
+ margin-bottom: 28px;
409
+ }
410
+
411
+ .fp-eyebrow .dot {
412
+ width: 8px;
413
+ height: 8px;
414
+ border-radius: 50%;
415
+ background: var(--tm-color-accent, var(--tm-color-primary));
416
+ opacity: 1;
417
+ transition: background-color 0.25s ease;
418
+ }
419
+
420
+ /* The trailing OKLCH(...) value is a live token label. */
421
+ .fp-eyebrow .token::before {
422
+ content: var(--tm-color-primary-oklch, "");
201
423
  }
202
424
 
203
- /* ===== BUTTONS ===== */
204
- .brand-btn-primary {
425
+ .fp-chip {
205
426
  display: inline-block;
206
- padding: var(--tm-button-padding-y, 0.75rem) var(--tm-button-padding-x, 1.5rem);
207
- background-color: var(--tm-button-primary-bg, var(--tm-color-primary));
427
+ padding: 0.5rem 0.9rem;
428
+ background: var(--tm-color-accent, var(--tm-color-primary));
429
+ color: var(--tm-color-background);
430
+ border-radius: 999px;
431
+ font-family: var(--tm-font-body);
432
+ font-weight: 700;
433
+ font-size: 0.72rem;
434
+ letter-spacing: 0.08em;
435
+ text-transform: uppercase;
436
+ margin-bottom: 24px;
437
+ transition: all 0.25s ease;
438
+ }
439
+
440
+ .fp-hero-title {
441
+ /* Base font family drives all specimen text — see the card note. */
442
+ font-family: var(--tm-font-body);
443
+ font-weight: var(--tm-heading-weight, 800);
444
+ font-size: clamp(2.6rem, 5.2vw, 4.4rem);
445
+ line-height: 1.02;
446
+ letter-spacing: -0.02em;
447
+ color: var(--tm-color-text);
448
+ max-width: 14ch;
449
+ margin-bottom: 28px;
450
+ }
451
+
452
+ .fp-hero-body {
453
+ font-family: var(--tm-font-body);
454
+ font-weight: var(--tm-body-weight, 400);
455
+ font-size: 1.15rem;
456
+ line-height: var(--tm-line-height, 1.6);
457
+ color: var(--tm-color-text);
458
+ opacity: 0.62;
459
+ max-width: 46ch;
460
+ margin-bottom: 32px;
461
+ }
462
+
463
+ .fp-actions {
464
+ display: flex;
465
+ flex-wrap: wrap;
466
+ gap: 14px;
467
+ margin-bottom: 64px;
468
+ }
469
+
470
+ .fp-btn-primary {
471
+ padding: var(--tm-button-padding-y, 0.85rem) var(--tm-button-padding-x, 1.4rem);
472
+ background: var(--tm-button-primary-bg, var(--tm-color-primary));
208
473
  color: var(--tm-button-primary-text, #ffffff);
209
474
  border: var(--tm-button-primary-border-width, 0) solid var(--tm-button-primary-border, transparent);
210
- border-radius: var(--tm-button-radius, var(--tm-border-radius));
475
+ border-radius: var(--tm-button-radius, var(--tm-border-radius, 10px));
211
476
  font-family: var(--tm-font-body);
212
- font-size: 1rem;
213
- font-weight: var(--tm-body-weight);
214
- text-decoration: none;
477
+ font-weight: var(--tm-body-weight, 600);
478
+ font-size: 0.95rem;
215
479
  cursor: pointer;
216
480
  transition: all 0.25s ease;
217
- margin: 0 0 0.75rem 0;
218
481
  }
219
482
 
220
- .brand-btn-primary:hover {
221
- opacity: 0.9;
222
- transform: translateY(-1px);
223
- }
224
-
225
- .brand-btn-secondary {
226
- display: inline-block;
227
- padding: var(--tm-button-secondary-padding-y, var(--tm-button-padding-y, 0.75rem)) var(--tm-button-secondary-padding-x, var(--tm-button-padding-x, 1.5rem));
228
- background-color: var(--tm-button-secondary-bg, transparent);
229
- color: var(--tm-button-secondary-text, var(--tm-color-primary));
230
- border: var(--tm-button-secondary-border-width, 1px) solid var(--tm-button-secondary-border, var(--tm-color-primary));
231
- border-radius: var(--tm-button-secondary-radius, var(--tm-button-radius, var(--tm-border-radius)));
483
+ .fp-btn-secondary {
484
+ padding: var(--tm-button-secondary-padding-y, var(--tm-button-padding-y, 0.85rem)) var(--tm-button-secondary-padding-x, var(--tm-button-padding-x, 1.4rem));
485
+ background: var(--tm-button-secondary-bg, var(--tm-color-accent, var(--tm-color-primary)));
486
+ color: var(--tm-button-secondary-text, var(--tm-color-background));
487
+ border: var(--tm-button-secondary-border-width, 0) solid var(--tm-button-secondary-border, transparent);
488
+ border-radius: var(--tm-button-secondary-radius, var(--tm-button-radius, var(--tm-border-radius, 10px)));
232
489
  font-family: var(--tm-font-body);
233
- font-size: 1rem;
234
- font-weight: var(--tm-body-weight);
235
- text-decoration: none;
490
+ font-weight: var(--tm-body-weight, 600);
491
+ font-size: 0.95rem;
236
492
  cursor: pointer;
237
493
  transition: all 0.25s ease;
238
- margin: 0 0 0.75rem 0;
239
494
  }
240
495
 
241
- .brand-btn-secondary:hover {
242
- opacity: 0.9;
243
- transform: translateY(-1px);
496
+ /* Feature grid */
497
+ .fp-grid {
498
+ display: grid;
499
+ grid-template-columns: repeat(3, 1fr);
500
+ gap: 20px;
501
+ margin-bottom: 56px;
244
502
  }
245
503
 
246
- .brand-btn-tertiary {
247
- display: inline-block;
248
- padding: var(--tm-button-tertiary-padding-y, var(--tm-button-padding-y, 0.75rem)) var(--tm-button-tertiary-padding-x, var(--tm-button-padding-x, 1.5rem));
249
- background-color: var(--tm-button-tertiary-bg, transparent);
250
- color: var(--tm-button-tertiary-text, var(--tm-color-primary));
251
- border: var(--tm-button-tertiary-border-width, 0) solid var(--tm-button-tertiary-border, transparent);
252
- border-radius: var(--tm-button-tertiary-radius, var(--tm-button-radius, var(--tm-border-radius)));
504
+ @media (max-width: 900px) {
505
+ .fp-page { padding: 32px 24px 48px; }
506
+ .fp-grid { grid-template-columns: 1fr; }
507
+ }
508
+
509
+ .fp-card {
510
+ background: color-mix(in srgb, var(--tm-color-text) 4%, var(--tm-color-background));
511
+ border: 1px solid color-mix(in srgb, var(--tm-color-text) 8%, transparent);
512
+ border-radius: var(--tm-border-radius-round, var(--tm-border-radius, 16px));
513
+ padding: 24px;
514
+ transition: background-color 0.25s ease, border-color 0.25s ease;
515
+ }
516
+
517
+ .fp-card .icon {
518
+ display: inline-flex;
519
+ align-items: center;
520
+ justify-content: center;
521
+ width: 40px;
522
+ height: 40px;
523
+ border-radius: 10px;
524
+ background: var(--tm-color-text);
525
+ color: var(--tm-color-background);
526
+ font-size: 1.1rem;
527
+ margin-bottom: 20px;
528
+ transition: background-color 0.25s ease, color 0.25s ease;
529
+ }
530
+
531
+ .fp-card h3 {
253
532
  font-family: var(--tm-font-body);
254
- font-size: 1rem;
255
- font-weight: var(--tm-body-weight);
256
- text-decoration: none;
257
- cursor: pointer;
258
- transition: all 0.25s ease;
259
- margin: 0 0 0.75rem 0;
533
+ font-weight: var(--tm-heading-weight, 700);
534
+ font-size: 1.1rem;
535
+ color: var(--tm-color-text);
536
+ margin-bottom: 10px;
260
537
  }
261
538
 
262
- .brand-btn-tertiary:hover {
263
- opacity: 0.9;
264
- transform: translateY(-1px);
539
+ .fp-card p {
540
+ font-family: var(--tm-font-body);
541
+ font-size: 0.9rem;
542
+ line-height: var(--tm-line-height, 1.55);
543
+ color: var(--tm-color-text);
544
+ opacity: 0.58;
265
545
  }
266
546
 
267
- /* ===== SECTION 5 BORDERED BOX ===== */
268
- .brand-bordered-box {
269
- border-radius: var(--tm-border-radius-round, var(--tm-border-radius));
270
- border: var(--tm-border-weight-medium, 2px) solid var(--tm-color-text-secondary, var(--tm-color-surface));
271
- padding: 0 0.75rem 0.75rem 0.75rem;
272
- background-color: var(--tm-color-background);
273
- transition: all 0.25s ease;
547
+ /* Footer with token chrome */
548
+ .fp-footer {
549
+ display: flex;
550
+ align-items: center;
551
+ justify-content: space-between;
552
+ flex-wrap: wrap;
553
+ gap: 12px;
554
+ padding-top: 24px;
555
+ border-top: 1px solid color-mix(in srgb, var(--tm-color-text) 10%, transparent);
556
+ font-family: "SFMono-Regular", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
557
+ font-size: 0.78rem;
558
+ color: var(--tm-color-text);
559
+ opacity: 0.5;
560
+ }
561
+
562
+ .fp-footer .radius::before {
563
+ content: "--radius: " var(--tm-border-radius-label, "");
274
564
  }
275
565
 
276
- /* ===== TRANSITIONS FOR LIVE PREVIEW ===== */
277
566
  *, *::before, *::after {
278
- transition-property: color, background-color, border-color, box-shadow, transform;
567
+ transition-property: color, background-color, border-color;
279
568
  transition-duration: 0.25s;
280
569
  transition-timing-function: ease;
281
570
  }
282
571
  </style>
283
572
  </head>
284
573
  <body>
285
-
286
- <!-- SECTION 1: Hero Image (a brand logo replaces it when present) -->
287
- <section class="brand-section" data-brand-hero-section>
288
- ${heroImg}${logoImg}
289
- </section>
290
-
291
- <!-- SECTION 2: H1 + Paragraph 1 + Primary Button -->
292
- <section class="brand-section">
293
- <h1 class="brand-h1">Heading 1</h1>
294
- <p class="brand-p1">Here is an example of Paragraph 1 text, along with an example of a Primary button. When you customize most brand values, this panel shows your changes.</p>
295
- <button class="brand-btn-primary">Button</button>
296
- <h2 class="brand-h2">Heading 2</h2>
297
- </section>
298
-
299
- <!-- SECTION 3: Two-column (Image Left, H3+P1+Secondary Button Right) -->
300
- <section class="brand-section brand-two-column">
301
- <div>
302
- ${section3Image ? `<img src="${section3Image}" alt="" style="margin-right: 0.375rem;" />` : ""}
303
- </div>
304
- <div style="padding-left: 0.375rem;">
305
- <h3 class="brand-h3">Heading 3</h3>
306
- <p class="brand-p1">Here's some more Paragraph 1 text, along with an example of a Secondary button.</p>
307
- <button class="brand-btn-secondary">Button</button>
574
+ <main class="fp-page">
575
+ <div class="fp-eyebrow">
576
+ <span class="dot"></span>
577
+ <span>Preview &middot; marketing/index.tsx &middot; <span class="token"></span></span>
308
578
  </div>
309
- </section>
310
-
311
- <!-- SECTION 4: Two-column (H4+P1+Tertiary Button Left, Image Right) -->
312
- <section class="brand-section brand-two-column">
313
- <div style="padding-right: 0.375rem;">
314
- <h4 class="brand-h4">Heading 4</h4>
315
- <p class="brand-p1">Following this Paragraph 1 text is a Tertiary button. This button type often has no border, but you can add one.</p>
316
- <button class="brand-btn-tertiary">Button</button>
317
- </div>
318
- <div>
319
- ${section4Image ? `<img src="${section4Image}" alt="" style="margin-left: 0.375rem;" />` : ""}
579
+
580
+ <span class="fp-chip"><span aria-hidden="true">&#10022;</span> High-Intensity Wellness</span>
581
+
582
+ <h1 class="fp-hero-title">${brandName}, with the calm of a system that remembers.</h1>
583
+
584
+ <p class="fp-hero-body">Built for teams that move quickly without breaking trust. Every surface stays legible, every decision stays reviewable, every ship stays accountable.</p>
585
+
586
+ <div class="fp-actions">
587
+ <button class="fp-btn-primary">Get started <span aria-hidden="true">&#8599;</span></button>
588
+ <button class="fp-btn-secondary">Talk to ${brandName}</button>
320
589
  </div>
321
- </section>
322
590
 
323
- <!-- SECTION 5: Centered Bordered Box (H5 + P1 showing border-radius + border-weight) -->
324
- <section class="brand-section">
325
- <div class="brand-bordered-box">
326
- <h5 class="brand-h5">Heading 5</h5>
327
- <p class="brand-p1 center">The border around this heading and Paragraph 1 text updates to show changes that you make to the border radius and the Medium border weight.</p>
591
+ <div class="fp-grid">
592
+ <div class="fp-card">
593
+ <span class="icon" aria-hidden="true">&#9673;</span>
594
+ <h3>Plan-first execution</h3>
595
+ <p>Every step approved before it runs. No silent writes, no surprise deploys.</p>
596
+ </div>
597
+ <div class="fp-card">
598
+ <span class="icon" aria-hidden="true">&#9776;</span>
599
+ <h3>Org-aware automation</h3>
600
+ <p>Sandbox is sandbox, prod is prod. Type-safe operations across every connected workspace.</p>
601
+ </div>
602
+ <div class="fp-card">
603
+ <span class="icon" aria-hidden="true">&#10022;</span>
604
+ <h3>Reviewable artifacts</h3>
605
+ <p>Diffs, plans, and results stay durable so the team can audit a session a quarter later.</p>
606
+ </div>
328
607
  </div>
329
- </section>
330
608
 
331
- <!-- SECTION 6: Footer (H6 + Paragraph 2) -->
332
- <section class="brand-section">
333
- <h6 class="brand-h6">Heading 6</h6>
334
- <p class="brand-p2">Here's an example of Paragraph 2 text. This text is often smaller than Paragraph 1 text, perfect for content that you want to be slightly less prominent.</p>
335
- </section>
609
+ <div class="fp-footer">
610
+ <span>&copy; ${brandName}. Tokens loaded from tokens.css.</span>
611
+ <span class="radius"></span>
612
+ </div>
613
+ </main>
336
614
 
337
615
  <script${nonce ? ` nonce="${nonce}"` : ""} src="${scriptSrc}"><\/script>
338
616
  </body>
339
617
  </html>`;
340
618
  }
341
619
  export {
620
+ getBrandFullPreviewHtml,
342
621
  getBrandPreviewHtml,
343
622
  getPreviewHtml
344
623
  };