@bison-lab/payload-core 3.8.0 → 3.10.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/dist/admin.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  "use client";
2
- import { FieldDescription, FieldError, FieldLabel, useField, useFormFields } from "@payloadcms/ui";
3
- import { auditTheme, contrastLevelLabel, contrastVerdict, isThemeHex } from "@bison-lab/tokens";
2
+ import { r as stateFromColorDoc, t as colorDocFromState } from "./map-BUDkX8g1.mjs";
3
+ import { FieldDescription, FieldError, FieldLabel, useField, useForm, useFormFields } from "@payloadcms/ui";
4
+ import { GREY_SCALES, SHADE_STEPS, contrastForeground, contrastRatio, createColorScale, hexToHSL, hslToString, includedShadeSteps, isThemeHex, presetHints, regenerateColorScale, setColorScaleInclude, updateColorScale } from "@bison-lab/tokens";
4
5
  import { useEffect, useId, useMemo, useRef, useState } from "react";
5
- import { jsx, jsxs } from "react/jsx-runtime";
6
- import { findFont, fontFaceCss } from "@bison-lab/fonts";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { createPortal } from "react-dom";
8
+ import { findFont, fontFaceCss, isFontId } from "@bison-lab/fonts";
7
9
  //#region src/admin/color-field.tsx
8
10
  const HEX_ERROR = "Enter a six-digit hex colour like #1e3a5f";
9
11
  /**
@@ -39,7 +41,7 @@ const ColorField = ({ field, path, readOnly }) => {
39
41
  /* @__PURE__ */ jsx("style", {
40
42
  href: "bl-color-field",
41
43
  precedence: "default",
42
- children: CSS$2
44
+ children: CSS$4
43
45
  }),
44
46
  /* @__PURE__ */ jsx(FieldLabel, {
45
47
  htmlFor: hexId,
@@ -98,7 +100,7 @@ const ColorField = ({ field, path, readOnly }) => {
98
100
  ]
99
101
  });
100
102
  };
101
- const CSS$2 = `
103
+ const CSS$4 = `
102
104
  .bl-color-field__control{display:flex;align-items:center;gap:calc(var(--base) * .4)}
103
105
  .bl-color-field__swatch{flex:none;width:calc(var(--base) * 1.6);height:calc(var(--base) * 1.6);border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:var(--theme-elevation-50)}
104
106
  .bl-color-field__picker{flex:none;width:calc(var(--base) * 2);height:calc(var(--base) * 2);padding:0;border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:transparent;cursor:pointer}
@@ -108,11 +110,1050 @@ const CSS$2 = `
108
110
  .bl-color-field__hex:disabled{color:var(--theme-elevation-400)}
109
111
  `;
110
112
  //#endregion
113
+ //#region src/theme/appearance-labels.ts
114
+ /**
115
+ * Named choices the Appearance child shows. Values stay the token presets;
116
+ * labels match the mock workflow (Soft → pill, Gentle → minimal, Balanced
117
+ * → default, Follow device → system). No rem, ms, or CSS variable tables.
118
+ */
119
+ const APPEARANCE_CHOICES = {
120
+ defaultTheme: {
121
+ light: {
122
+ label: "Light",
123
+ hint: "Always opens in light mode"
124
+ },
125
+ dark: {
126
+ label: "Dark",
127
+ hint: "Always opens in dark mode"
128
+ },
129
+ system: {
130
+ label: "Follow device",
131
+ hint: "Matches the visitor’s system setting"
132
+ }
133
+ },
134
+ radius: {
135
+ sharp: {
136
+ label: "Sharp",
137
+ hint: "Crisp and structured"
138
+ },
139
+ subtle: {
140
+ label: "Subtle",
141
+ hint: "Clean with a little softness"
142
+ },
143
+ rounded: {
144
+ label: "Rounded",
145
+ hint: "Friendly and contemporary"
146
+ },
147
+ pill: {
148
+ label: "Soft",
149
+ hint: "Very rounded and expressive"
150
+ }
151
+ },
152
+ shadow: {
153
+ flat: {
154
+ label: "Flat",
155
+ hint: "Minimal visual depth"
156
+ },
157
+ subtle: {
158
+ label: "Subtle",
159
+ hint: "Light separation between surfaces"
160
+ },
161
+ elevated: {
162
+ label: "Elevated",
163
+ hint: "More noticeable layering and depth"
164
+ }
165
+ },
166
+ motion: {
167
+ snappy: {
168
+ label: "Quick",
169
+ hint: "Fast and responsive"
170
+ },
171
+ smooth: {
172
+ label: "Smooth",
173
+ hint: "Balanced and natural"
174
+ },
175
+ minimal: {
176
+ label: "Gentle",
177
+ hint: "Slower and more relaxed"
178
+ }
179
+ },
180
+ density: {
181
+ compact: {
182
+ label: "Compact",
183
+ hint: "Fits more content on screen"
184
+ },
185
+ default: {
186
+ label: "Balanced",
187
+ hint: "Comfortable for most sites"
188
+ },
189
+ spacious: {
190
+ label: "Spacious",
191
+ hint: "More breathing room and larger controls"
192
+ }
193
+ }
194
+ };
195
+ const APPEARANCE_FAMILY_COPY = {
196
+ defaultTheme: {
197
+ title: "Default theme",
198
+ hint: "Choose how the public site opens for visitors."
199
+ },
200
+ radius: {
201
+ title: "Corner style",
202
+ hint: "Controls the shape of buttons, inputs, cards, and panels across the site."
203
+ },
204
+ shadow: {
205
+ title: "Depth",
206
+ hint: "Choose how much cards and floating elements stand out from the page."
207
+ },
208
+ motion: {
209
+ title: "Motion",
210
+ hint: "Controls how quickly menus, cards, and interface transitions move."
211
+ },
212
+ density: {
213
+ title: "Spacing",
214
+ hint: "Choose how compact or roomy forms, buttons, cards, and page sections feel."
215
+ }
216
+ };
217
+ const FONT_ROLE_COPY = {
218
+ heading: {
219
+ label: "Heading font",
220
+ hint: "Used for page titles, section headings, and cards"
221
+ },
222
+ body: {
223
+ label: "Body font",
224
+ hint: "Used for paragraphs, navigation, forms, and buttons"
225
+ }
226
+ };
227
+ const SYSTEM_COLOR_COPY = {
228
+ primary: {
229
+ label: "Primary",
230
+ hint: "Primary actions and key links",
231
+ group: "brand"
232
+ },
233
+ secondary: {
234
+ label: "Secondary",
235
+ hint: "Supporting accents and eyebrows",
236
+ group: "brand"
237
+ },
238
+ accent: {
239
+ label: "Accent",
240
+ hint: "Dark brand surfaces",
241
+ group: "brand"
242
+ },
243
+ highlight: {
244
+ label: "Highlight",
245
+ hint: "Promotional and emphasis fills",
246
+ group: "brand"
247
+ },
248
+ success: {
249
+ label: "Success",
250
+ hint: "Theme chrome for confirmations. Not offered as a page-editor fill.",
251
+ group: "status"
252
+ },
253
+ destructive: {
254
+ label: "Destructive",
255
+ hint: "Theme chrome for errors and dangerous actions. Not offered as a page-editor fill.",
256
+ group: "status"
257
+ }
258
+ };
259
+ //#endregion
260
+ //#region src/theme/readability.ts
261
+ /** Same threshold `deriveDarkPalette` uses for brand fills. */
262
+ const DARK_FILL_LIGHTEN_BELOW = 40;
263
+ const DARK_FILL_LIGHTEN_BY = 25;
264
+ function clamp(value, min, max) {
265
+ return Math.min(max, Math.max(min, value));
266
+ }
267
+ function darkFill(color) {
268
+ if (color.l < DARK_FILL_LIGHTEN_BELOW) return {
269
+ ...color,
270
+ l: clamp(color.l + DARK_FILL_LIGHTEN_BY, 0, 100)
271
+ };
272
+ return color;
273
+ }
274
+ function sample(fill, target) {
275
+ const label = contrastForeground(fill);
276
+ const fillCss = hslToString(fill);
277
+ const ratio = contrastRatio(fillCss, label);
278
+ return {
279
+ ratio,
280
+ shortfall: Math.max(0, Math.round((target - ratio) * 100) / 100),
281
+ fill: fillCss,
282
+ label
283
+ };
284
+ }
285
+ /**
286
+ * Editable fill + the automatic label, Light and Dark. Target 7:1.
287
+ * Meets only when both pass. Warnings do not block save.
288
+ */
289
+ function fillLabelWarning(id, name, hex, target = 7) {
290
+ if (!isThemeHex(hex)) return null;
291
+ const lightFill = hexToHSL(hex);
292
+ const light = sample(lightFill, target);
293
+ const dark = sample(darkFill(lightFill), target);
294
+ return {
295
+ id,
296
+ name,
297
+ light,
298
+ dark,
299
+ meets: light.ratio >= target && dark.ratio >= target
300
+ };
301
+ }
302
+ /** Green/teal family. A warning at admin time, not a schema block. */
303
+ function successHueWarning(hex) {
304
+ if (!isThemeHex(hex)) return null;
305
+ const { h } = hexToHSL(hex);
306
+ if (h >= 80 && h <= 180) return null;
307
+ return "Keep success colors recognizable — stay in a green or teal family.";
308
+ }
309
+ /** Red family. A warning at admin time, not a schema block. */
310
+ function destructiveHueWarning(hex) {
311
+ if (!isThemeHex(hex)) return null;
312
+ const { h } = hexToHSL(hex);
313
+ if (h <= 30 || h >= 330) return null;
314
+ return "Keep destructive colors recognizable — stay in a red family.";
315
+ }
316
+ function automaticLabel(hex) {
317
+ if (!isThemeHex(hex)) return null;
318
+ return contrastForeground(hexToHSL(hex));
319
+ }
320
+ function hslCss(value) {
321
+ return `hsl(${value})`;
322
+ }
323
+ /** Which appearances fail, in words. Empty when both pass. */
324
+ function hardToReadModes(item) {
325
+ const light = item.light.shortfall > 0;
326
+ const dark = item.dark.shortfall > 0;
327
+ if (light && dark) return "in Light and Dark";
328
+ if (light) return "in Light";
329
+ if (dark) return "in Dark";
330
+ return "";
331
+ }
332
+ function readabilityHeadline(item) {
333
+ return item.meets ? `Text on ${item.name} looks readable` : `Text on ${item.name} may be hard to read`;
334
+ }
335
+ function readabilityModesLine(item) {
336
+ const modes = hardToReadModes(item);
337
+ return modes ? `Hard to read ${modes}.` : "Looks good in Light and Dark.";
338
+ }
339
+ function readabilityGapPercent(sample, target = 7) {
340
+ if (sample.shortfall <= 0) return 0;
341
+ return Math.round(sample.shortfall / target * 100);
342
+ }
343
+ function readabilityRatioLine(sample, target = 7) {
344
+ const percent = readabilityGapPercent(sample, target);
345
+ if (percent <= 0) return "Meets the contrast we want";
346
+ if (percent < 10) return "A little short of the contrast we want";
347
+ return `About ${percent}% short of the contrast we want`;
348
+ }
349
+ const READABILITY_FIX = "Darken or lighten the hex, then regenerate the scale. The site picks light or dark type for you.";
350
+ const READABILITY_PUBLISH_NOTE = "You can still publish. To improve a color, darken or lighten it and regenerate.";
351
+ //#endregion
352
+ //#region src/admin/preview-mode.tsx
353
+ const ATTR = "data-bl-theme-preview";
354
+ const EVENT = "bl-theme-preview";
355
+ function adminIsDark() {
356
+ if (typeof document === "undefined") return false;
357
+ const root = document.documentElement;
358
+ return root.getAttribute("data-theme") === "dark" || root.getAttribute("data-theme-pref") === "dark" || root.classList.contains("dark");
359
+ }
360
+ function readMode() {
361
+ if (typeof document === "undefined") return "light";
362
+ const set = document.documentElement.getAttribute(ATTR);
363
+ if (set === "dark" || set === "light") return set;
364
+ return adminIsDark() ? "dark" : "light";
365
+ }
366
+ function setThemePreviewMode(mode) {
367
+ if (typeof document === "undefined") return;
368
+ document.documentElement.setAttribute(ATTR, mode);
369
+ window.dispatchEvent(new Event(EVENT));
370
+ }
371
+ function useThemePreviewMode() {
372
+ const [mode, setMode] = useState("light");
373
+ useEffect(() => {
374
+ const sync = () => setMode(readMode());
375
+ sync();
376
+ window.addEventListener(EVENT, sync);
377
+ return () => window.removeEventListener(EVENT, sync);
378
+ }, []);
379
+ return mode;
380
+ }
381
+ function ThemePreviewToggle() {
382
+ const mode = useThemePreviewMode();
383
+ return /* @__PURE__ */ jsxs("div", {
384
+ className: "bl-preview-toggle",
385
+ role: "group",
386
+ "aria-label": "Preview fills in",
387
+ children: [/* @__PURE__ */ jsx("span", {
388
+ className: "bl-preview-toggle__label",
389
+ children: "Preview fills"
390
+ }), /* @__PURE__ */ jsxs("div", {
391
+ className: "bl-preview-toggle__pills",
392
+ children: [/* @__PURE__ */ jsx("button", {
393
+ type: "button",
394
+ "aria-pressed": mode === "light",
395
+ onClick: () => setThemePreviewMode("light"),
396
+ children: "Light"
397
+ }), /* @__PURE__ */ jsx("button", {
398
+ type: "button",
399
+ "aria-pressed": mode === "dark",
400
+ onClick: () => setThemePreviewMode("dark"),
401
+ children: "Dark"
402
+ })]
403
+ })]
404
+ });
405
+ }
406
+ const THEME_PREVIEW_TOGGLE_CSS = `
407
+ .bl-preview-toggle{display:inline-flex;align-items:center;gap:.6rem}
408
+ .bl-preview-toggle__label{font-size:12px;font-weight:600;color:#a1a1aa}
409
+ .bl-preview-toggle__pills{display:inline-flex;padding:3px;border:1px solid #3f3f46;border-radius:10px;background:#18181b}
410
+ .bl-preview-toggle__pills button{min-height:2rem;padding:.25rem .8rem;border:0;border-radius:7px;background:transparent;color:#d4d4d8;font:inherit;font-size:13px;font-weight:600;cursor:pointer}
411
+ .bl-preview-toggle__pills button[aria-pressed=true]{background:#f4f4f5;color:#18181b;box-shadow:0 1px 2px rgba(0,0,0,.25)}
412
+ html[data-theme=light] .bl-preview-toggle__label{color:#52525b}
413
+ html[data-theme=light] .bl-preview-toggle__pills{border-color:#d4d4d8;background:#e4e4e7}
414
+ html[data-theme=light] .bl-preview-toggle__pills button{color:#3f3f46}
415
+ html[data-theme=light] .bl-preview-toggle__pills button[aria-pressed=true]{background:#fff;color:#18181b;box-shadow:0 1px 2px rgba(15,23,42,.12)}
416
+ `;
417
+ //#endregion
418
+ //#region src/admin/readability-list.tsx
419
+ function ReadabilityDetail({ item, target = 7 }) {
420
+ return /* @__PURE__ */ jsxs("div", {
421
+ className: "bl-readability-detail",
422
+ children: [
423
+ /* @__PURE__ */ jsx("p", {
424
+ className: "bl-readability-detail__lead",
425
+ children: readabilityModesLine(item)
426
+ }),
427
+ /* @__PURE__ */ jsx("div", {
428
+ className: "bl-readability-detail__modes",
429
+ children: [["Light", item.light], ["Dark", item.dark]].map(([mode, sample]) => /* @__PURE__ */ jsxs("figure", {
430
+ "data-failing": sample.shortfall > 0 ? "" : void 0,
431
+ children: [/* @__PURE__ */ jsx("span", {
432
+ "aria-hidden": "true",
433
+ className: "bl-readability__swatch",
434
+ style: {
435
+ background: hslCss(sample.fill),
436
+ color: hslCss(sample.label)
437
+ },
438
+ children: "Aa"
439
+ }), /* @__PURE__ */ jsxs("figcaption", { children: [
440
+ /* @__PURE__ */ jsx("strong", { children: mode }),
441
+ /* @__PURE__ */ jsx("span", { children: sample.shortfall > 0 ? "Hard to read" : "Looks good" }),
442
+ /* @__PURE__ */ jsx("span", { children: readabilityRatioLine(sample, target) })
443
+ ] })]
444
+ }, mode))
445
+ }),
446
+ item.meets ? null : /* @__PURE__ */ jsx("p", {
447
+ className: "bl-readability-detail__fix",
448
+ children: READABILITY_FIX
449
+ })
450
+ ]
451
+ });
452
+ }
453
+ const READABILITY_LIST_CSS = `
454
+ .bl-readability{padding:calc(var(--base,16px) * .8);border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:var(--style-radius-m,12px);background:var(--theme-elevation-0,#fff)}
455
+ .bl-readability__head{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start}
456
+ .bl-readability__head h2{margin:0;font-size:1rem}
457
+ .bl-readability__head p{margin:.35rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}
458
+ .bl-readability__head span{flex:none;font-size:12px;font-weight:600;padding:.2rem .55rem;border-radius:999px;background:var(--theme-elevation-100,#f1f5f9)}
459
+ .bl-readability__head span[data-attention]{background:#fffbeb;color:#92400e}
460
+ .bl-readability ul{margin:.85rem 0 0;padding:0;list-style:none}
461
+ .bl-readability li{padding:.65rem 0;border-top:1px solid var(--theme-elevation-100,#f1f5f9)}
462
+ .bl-readability li:first-child{border-top:0}
463
+ .bl-readability li[data-failing]{background:#fff7ed;margin:0 -.4rem;padding:.65rem .4rem;border-radius:8px}
464
+ .bl-readability__swatch{flex:none;display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2rem;border-radius:6px;font-weight:700;font-size:13px}
465
+ .bl-readability strong{display:block;font-size:13px}
466
+ .bl-readability-detail__lead{margin:0 0 .65rem;font-size:13px;color:var(--theme-elevation-600,#475569)}
467
+ .bl-readability-detail__modes{display:grid;grid-template-columns:1fr 1fr;gap:.65rem}
468
+ .bl-readability-detail__modes figure{display:flex;gap:.6rem;align-items:center;margin:0;padding:.55rem .6rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:10px;background:#fff}
469
+ .bl-readability-detail__modes figure[data-failing]{border-color:#fdba74;background:#fff7ed}
470
+ .bl-readability-detail__modes figcaption{display:flex;flex-direction:column;gap:.1rem;min-width:0}
471
+ .bl-readability-detail__modes figcaption strong{font-size:12px}
472
+ .bl-readability-detail__modes figcaption span{font-size:11px;color:var(--theme-elevation-500,#64748b)}
473
+ .bl-readability-detail__fix{margin:.7rem 0 0;font-size:13px;color:var(--theme-elevation-600,#475569)}
474
+ `;
475
+ //#endregion
476
+ //#region src/admin/color-scale-card.tsx
477
+ function ColorScaleCard({ id, name, hint, required, custom, state, onChange, onDelete, readOnly, role }) {
478
+ const label = automaticLabel(state.hex);
479
+ const warning = fillLabelWarning(id ?? name.toLowerCase(), name, state.hex);
480
+ const previewMode = useThemePreviewMode();
481
+ const preview = warning ? previewMode === "dark" ? warning.dark : warning.light : null;
482
+ const [open, setOpen] = useState(false);
483
+ const titleId = useId();
484
+ const hueWarning = role === "success" ? successHueWarning(state.hex) : role === "destructive" ? destructiveHueWarning(state.hex) : null;
485
+ const included = new Set(includedShadeSteps(state));
486
+ function setHex(hex) {
487
+ onChange(updateColorScale(state, { hex }));
488
+ }
489
+ function setStep(step) {
490
+ onChange(updateColorScale(state, { sourceStep: step }));
491
+ }
492
+ function toggleStep(step) {
493
+ if (step === state.sourceStep) return;
494
+ const next = new Set(included);
495
+ if (next.has(step)) next.delete(step);
496
+ else next.add(step);
497
+ onChange(setColorScaleInclude(state, SHADE_STEPS.filter((item) => next.has(item))));
498
+ }
499
+ return /* @__PURE__ */ jsxs("article", {
500
+ className: "bl-scale-card",
501
+ "data-preview": previewMode,
502
+ children: [
503
+ /* @__PURE__ */ jsxs("header", {
504
+ className: "bl-scale-card__head",
505
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
506
+ className: "bl-scale-card__title-row",
507
+ children: [
508
+ /* @__PURE__ */ jsx("h3", {
509
+ className: "bl-scale-card__title",
510
+ children: name
511
+ }),
512
+ required ? /* @__PURE__ */ jsx("span", {
513
+ className: "bl-scale-card__badge",
514
+ children: "Required"
515
+ }) : null,
516
+ custom ? /* @__PURE__ */ jsx("span", {
517
+ className: "bl-scale-card__badge",
518
+ children: "Optional"
519
+ }) : null,
520
+ warning ? warning.meets ? /* @__PURE__ */ jsxs("span", {
521
+ className: "bl-scale-card__status",
522
+ "data-ok": "",
523
+ title: `Text on ${name} looks readable`,
524
+ children: [/* @__PURE__ */ jsx("span", {
525
+ "aria-hidden": "true",
526
+ children: "✓"
527
+ }), /* @__PURE__ */ jsxs("span", {
528
+ className: "bl-scale-card__sr",
529
+ children: [
530
+ "Text on ",
531
+ name,
532
+ " looks readable"
533
+ ]
534
+ })]
535
+ }) : /* @__PURE__ */ jsx("button", {
536
+ type: "button",
537
+ className: "bl-scale-card__badge bl-scale-card__badge--attention",
538
+ "aria-haspopup": "dialog",
539
+ onClick: () => setOpen(true),
540
+ children: "Needs attention"
541
+ }) : null
542
+ ]
543
+ }), hint ? /* @__PURE__ */ jsx("p", {
544
+ className: "bl-scale-card__hint",
545
+ children: hint
546
+ }) : null] }), /* @__PURE__ */ jsxs("div", {
547
+ className: "bl-scale-card__head-actions",
548
+ children: [/* @__PURE__ */ jsx("span", {
549
+ className: "bl-scale-card__mark",
550
+ style: { background: state.hex },
551
+ "aria-hidden": "true"
552
+ }), onDelete ? /* @__PURE__ */ jsx("button", {
553
+ type: "button",
554
+ className: "bl-scale-card__delete",
555
+ disabled: readOnly,
556
+ onClick: onDelete,
557
+ children: "Delete"
558
+ }) : null]
559
+ })]
560
+ }),
561
+ /* @__PURE__ */ jsxs("div", {
562
+ className: "bl-scale-card__row",
563
+ children: [/* @__PURE__ */ jsxs("label", {
564
+ className: "bl-scale-card__field",
565
+ children: [/* @__PURE__ */ jsx("span", { children: "Brand hex" }), /* @__PURE__ */ jsxs("span", {
566
+ className: "bl-scale-card__hex-wrap",
567
+ children: [/* @__PURE__ */ jsx("input", {
568
+ type: "color",
569
+ "aria-label": `${name} picker`,
570
+ disabled: readOnly,
571
+ value: state.hex,
572
+ onChange: (event) => setHex(event.target.value)
573
+ }), /* @__PURE__ */ jsx("input", {
574
+ type: "text",
575
+ spellCheck: false,
576
+ autoComplete: "off",
577
+ disabled: readOnly,
578
+ value: state.hex,
579
+ onChange: (event) => {
580
+ const next = event.target.value.trim();
581
+ if (next) setHex(next);
582
+ }
583
+ })]
584
+ })]
585
+ }), /* @__PURE__ */ jsxs("label", {
586
+ className: "bl-scale-card__field",
587
+ children: [/* @__PURE__ */ jsx("span", { children: "This color is" }), /* @__PURE__ */ jsx("select", {
588
+ disabled: readOnly,
589
+ value: String(state.sourceStep),
590
+ onChange: (event) => setStep(Number(event.target.value)),
591
+ children: SHADE_STEPS.map((step) => /* @__PURE__ */ jsx("option", {
592
+ value: step,
593
+ children: step
594
+ }, step))
595
+ })]
596
+ })]
597
+ }),
598
+ /* @__PURE__ */ jsxs("div", {
599
+ className: "bl-scale-card__label-sample",
600
+ children: [/* @__PURE__ */ jsx("span", {
601
+ className: "bl-scale-card__swatch",
602
+ style: {
603
+ background: preview ? hslCss(preview.fill) : state.hex,
604
+ color: preview ? hslCss(preview.label) : label ? hslCss(label) : void 0
605
+ },
606
+ children: "Aa"
607
+ }), /* @__PURE__ */ jsx("p", { children: previewMode === "dark" ? "Previewing this fill in Dark. A dark-enough hex lightens; otherwise it stays the typed color." : "The site chooses light or dark type on this fill. Darken or lighten the hex and regenerate to flip it. There is no ink picker." })]
608
+ }),
609
+ hueWarning ? /* @__PURE__ */ jsx("p", {
610
+ className: "bl-scale-card__warn",
611
+ children: hueWarning
612
+ }) : null,
613
+ state.stale ? /* @__PURE__ */ jsx("p", {
614
+ className: "bl-scale-card__stale",
615
+ children: "This scale is out of date. Regenerate to rebuild 50–950 around the hex."
616
+ }) : null,
617
+ /* @__PURE__ */ jsx("button", {
618
+ type: "button",
619
+ className: "bl-scale-card__regen",
620
+ disabled: readOnly || !state.stale,
621
+ onClick: () => onChange(regenerateColorScale(state)),
622
+ children: "Regenerate scale"
623
+ }),
624
+ /* @__PURE__ */ jsxs("div", {
625
+ className: "bl-scale-card__scale-head",
626
+ children: [/* @__PURE__ */ jsx("span", { children: "Generated scale" }), /* @__PURE__ */ jsxs("span", { children: [
627
+ /* @__PURE__ */ jsx("button", {
628
+ type: "button",
629
+ disabled: readOnly,
630
+ onClick: () => onChange(setColorScaleInclude(state, "all")),
631
+ children: "Include all"
632
+ }),
633
+ " · ",
634
+ /* @__PURE__ */ jsx("button", {
635
+ type: "button",
636
+ disabled: readOnly,
637
+ onClick: () => onChange(setColorScaleInclude(state, "source")),
638
+ children: "Source only"
639
+ })
640
+ ] })]
641
+ }),
642
+ /* @__PURE__ */ jsx("div", {
643
+ className: "bl-scale-card__steps",
644
+ role: "group",
645
+ "aria-label": `${name} scale steps`,
646
+ children: SHADE_STEPS.map((step) => {
647
+ const source = step === state.sourceStep;
648
+ const on = included.has(step);
649
+ return /* @__PURE__ */ jsxs("button", {
650
+ type: "button",
651
+ className: "bl-scale-card__step",
652
+ "data-source": source ? "" : void 0,
653
+ "data-excluded": on ? void 0 : "",
654
+ disabled: readOnly || source,
655
+ title: source ? `Source step ${step} cannot be excluded` : on ? `Exclude ${step} from page editors` : `Include ${step} for page editors`,
656
+ onClick: () => toggleStep(step),
657
+ children: [/* @__PURE__ */ jsx("span", {
658
+ className: "bl-scale-card__chip",
659
+ style: { background: hslCss(state.scale[step]) },
660
+ children: source ? null : /* @__PURE__ */ jsx("span", {
661
+ className: "bl-scale-card__intent",
662
+ "data-intent": on ? "exclude" : "include",
663
+ "aria-hidden": "true",
664
+ children: on ? /* @__PURE__ */ jsxs("svg", {
665
+ viewBox: "0 0 24 24",
666
+ width: "16",
667
+ height: "16",
668
+ children: [/* @__PURE__ */ jsx("circle", {
669
+ cx: "12",
670
+ cy: "12",
671
+ r: "8",
672
+ fill: "none",
673
+ stroke: "currentColor",
674
+ strokeWidth: "2.4"
675
+ }), /* @__PURE__ */ jsx("path", {
676
+ d: "M7.2 16.8 16.8 7.2",
677
+ stroke: "currentColor",
678
+ strokeWidth: "2.4"
679
+ })]
680
+ }) : /* @__PURE__ */ jsx("svg", {
681
+ viewBox: "0 0 24 24",
682
+ width: "16",
683
+ height: "16",
684
+ children: /* @__PURE__ */ jsx("path", {
685
+ d: "M12 6v12M6 12h12",
686
+ fill: "none",
687
+ stroke: "currentColor",
688
+ strokeWidth: "2.6"
689
+ })
690
+ })
691
+ })
692
+ }), /* @__PURE__ */ jsx("span", {
693
+ className: "bl-scale-card__step-label",
694
+ children: step
695
+ })]
696
+ }, step);
697
+ })
698
+ }),
699
+ open && warning ? createPortal(/* @__PURE__ */ jsx("div", {
700
+ className: "bl-scale-card__modal",
701
+ role: "dialog",
702
+ "aria-modal": "true",
703
+ "aria-labelledby": titleId,
704
+ onClick: (event) => {
705
+ if (event.target === event.currentTarget) setOpen(false);
706
+ },
707
+ children: /* @__PURE__ */ jsxs("div", {
708
+ className: "bl-scale-card__panel",
709
+ children: [
710
+ /* @__PURE__ */ jsxs("div", {
711
+ className: "bl-scale-card__panel-head",
712
+ children: [/* @__PURE__ */ jsx("h4", {
713
+ id: titleId,
714
+ children: readabilityHeadline(warning)
715
+ }), /* @__PURE__ */ jsx("button", {
716
+ type: "button",
717
+ onClick: () => setOpen(false),
718
+ "aria-label": "Close",
719
+ children: "×"
720
+ })]
721
+ }),
722
+ /* @__PURE__ */ jsx(ReadabilityDetail, { item: warning }),
723
+ /* @__PURE__ */ jsx("button", {
724
+ type: "button",
725
+ className: "bl-scale-card__panel-done",
726
+ onClick: () => setOpen(false),
727
+ children: "Keep editing this color"
728
+ })
729
+ ]
730
+ })
731
+ }), document.body) : null
732
+ ]
733
+ });
734
+ }
735
+ const COLOR_SCALE_CARD_CSS = `
736
+ .bl-scale-card{padding:calc(var(--base,16px) * .8);border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:var(--style-radius-m,12px);background:var(--theme-elevation-0,#fff)}
737
+ .bl-scale-card__head{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start}
738
+ .bl-scale-card__title-row{position:relative;display:flex;align-items:center;flex-wrap:wrap;gap:.5rem}
739
+ .bl-scale-card__title{margin:0;font-size:1rem}
740
+ .bl-scale-card__badge{font:inherit;font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:.15rem .45rem;border-radius:999px;border:0;background:var(--theme-elevation-100,#f1f5f9);color:var(--theme-elevation-500,#64748b)}
741
+ .bl-scale-card__badge--attention{background:#fff1f2;color:#be123c;cursor:pointer}
742
+ .bl-scale-card__status{display:inline-grid;place-items:center;width:1.4rem;height:1.4rem;border-radius:999px;border:0;font:inherit;font-weight:700;line-height:1}
743
+ .bl-scale-card__status[data-ok]{background:#ecfdf5;color:#047857}
744
+ .bl-scale-card__sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
745
+ .bl-scale-card__hint{margin:.35rem 0 0;color:var(--theme-elevation-500,#64748b);font-size:12px}
746
+ .bl-scale-card__head-actions{display:flex;align-items:center;gap:.5rem}
747
+ .bl-scale-card__mark{display:block;width:2.5rem;height:2.5rem;border-radius:8px;border:1px solid rgba(15,23,42,.12);flex:none}
748
+ .bl-scale-card__delete{border:0;background:none;color:#be123c;font:inherit;font-size:13px;font-weight:600;cursor:pointer}
749
+ .bl-scale-card__row{display:grid;grid-template-columns:1fr 8rem;gap:.75rem;margin-top:1rem}
750
+ .bl-scale-card__field{display:flex;flex-direction:column;gap:.35rem;font-size:12px;font-weight:600;color:var(--theme-elevation-600,#475569)}
751
+ .bl-scale-card__hex-wrap{display:flex;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:8px;overflow:hidden;background:var(--theme-input-bg,#fff)}
752
+ .bl-scale-card__hex-wrap input[type=color]{width:2.75rem;height:2.25rem;padding:0;border:0;border-right:1px solid var(--theme-elevation-150,#e2e8f0);background:transparent;cursor:pointer}
753
+ .bl-scale-card__hex-wrap input[type=text]{flex:1;min-width:0;border:0;padding:.4rem .7rem;font-family:var(--font-mono,ui-monospace,monospace);font-size:13px;background:transparent;color:inherit}
754
+ .bl-scale-card__field select{min-height:2.25rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:8px;padding:.35rem .5rem;background:var(--theme-input-bg,#fff);color:inherit}
755
+ .bl-scale-card__label-sample{display:flex;gap:.75rem;align-items:center;margin-top:1rem}
756
+ .bl-scale-card__swatch{flex:none;display:inline-flex;align-items:center;justify-content:center;width:3rem;height:2.25rem;border-radius:8px;border:1px solid var(--theme-elevation-150,#e2e8f0);font-weight:700}
757
+ .bl-scale-card__label-sample p{margin:0;font-size:12px;color:var(--theme-elevation-500,#64748b)}
758
+ .bl-scale-card__warn,.bl-scale-card__stale{margin:.75rem 0 0;padding:.65rem .75rem;border-radius:8px;font-size:12px}
759
+ .bl-scale-card__warn{border:1px solid #a7f3d0;background:#ecfdf5;color:#065f46}
760
+ .bl-scale-card__stale{border:1px solid #fde68a;background:#fffbeb;color:#92400e}
761
+ .bl-scale-card__regen{width:100%;margin-top:.85rem;min-height:2.25rem;border:0;border-radius:8px;background:#0f172a;color:#fff;font:inherit;font-weight:600;cursor:pointer}
762
+ .bl-scale-card__regen:disabled{background:#1e293b;color:#f8fafc;opacity:1;cursor:not-allowed}
763
+ .bl-scale-card__scale-head{display:flex;justify-content:space-between;margin:1rem 0 .4rem;font-size:12px;color:var(--theme-elevation-500,#64748b)}
764
+ .bl-scale-card__scale-head button{border:0;background:none;padding:0;font:inherit;color:inherit;text-decoration:underline;cursor:pointer}
765
+ .bl-scale-card__steps{display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:.45rem 3px}
766
+ .bl-scale-card__step{display:flex;flex-direction:column;align-items:center;gap:.3rem;border:0;padding:0;background:transparent;cursor:pointer}
767
+ .bl-scale-card__chip{position:relative;display:block;width:100%;height:2.15rem;border-radius:6px;overflow:hidden}
768
+ .bl-scale-card__intent{position:absolute;top:3px;right:3px;width:1.15rem;height:1.15rem;border-radius:999px;display:grid;place-items:center;opacity:0;color:#be123c;background:#fff;box-shadow:0 1px 3px rgba(15,23,42,.28);transition:opacity .15s ease,transform .15s ease;pointer-events:none}
769
+ .bl-scale-card__intent[data-intent=include]{color:#047857}
770
+ .bl-scale-card__step:hover:not(:disabled) .bl-scale-card__chip{transform:translateY(-1px)}
771
+ .bl-scale-card__step:hover:not(:disabled) .bl-scale-card__intent{opacity:1}
772
+ .bl-scale-card__step-label{font-size:10px;line-height:1;color:var(--theme-elevation-600,#475569)}
773
+ .bl-scale-card__step[data-source] .bl-scale-card__chip{box-shadow:0 0 0 2px var(--theme-elevation-800,#0f172a)}
774
+ .bl-scale-card__step[data-excluded]{opacity:.55}
775
+ .bl-scale-card__step:disabled{cursor:default}
776
+ .bl-scale-card__modal{position:fixed;inset:0;z-index:100000;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}
777
+ .bl-scale-card__panel{width:min(32rem,100%);padding:1.15rem 1.2rem;border-radius:16px;background:#fff;color:#0f172a}
778
+ .bl-scale-card__panel-head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}
779
+ .bl-scale-card__panel-head h4{margin:0;font-size:1rem}
780
+ .bl-scale-card__panel-head button{border:0;background:none;font:inherit;font-size:1.25rem;line-height:1;color:#64748b;cursor:pointer}
781
+ .bl-scale-card__panel-done{margin-top:1rem;min-height:2.1rem;padding:.35rem .85rem;border:0;border-radius:8px;background:#0f172a;color:#fff;font:inherit;font-weight:600;cursor:pointer}
782
+ ` + READABILITY_LIST_CSS;
783
+ //#endregion
784
+ //#region src/admin/color-scale-field.tsx
785
+ const SYSTEM_ROLES = new Set(Object.keys(SYSTEM_COLOR_COPY));
786
+ /**
787
+ * One system (or library row) color: hex, source step, regenerate, stale,
788
+ * include/exclude, automatic label. Writes the group's stored fields.
789
+ */
790
+ const ColorScaleField = ({ field, path, readOnly }) => {
791
+ const hex = useField({ path: `${path}.hex` });
792
+ const sourceStep = useField({ path: `${path}.sourceStep` });
793
+ const scale = useField({ path: `${path}.scale` });
794
+ const stale = useField({ path: `${path}.stale` });
795
+ const include = useField({ path: `${path}.include` });
796
+ const includedSteps = useField({ path: `${path}.includedSteps` });
797
+ const key = path.split(".").pop() ?? "";
798
+ const copy = SYSTEM_ROLES.has(key) ? SYSTEM_COLOR_COPY[key] : void 0;
799
+ const state = stateFromColorDoc({
800
+ hex: hex.value,
801
+ sourceStep: sourceStep.value,
802
+ scale: scale.value,
803
+ stale: stale.value,
804
+ include: include.value === "source" || include.value === "custom" || include.value === "all" ? include.value : "all",
805
+ includedSteps: includedSteps.value
806
+ });
807
+ function write(next) {
808
+ const doc = colorDocFromState(next);
809
+ hex.setValue(doc.hex);
810
+ sourceStep.setValue(String(doc.sourceStep));
811
+ scale.setValue(doc.scale);
812
+ stale.setValue(doc.stale);
813
+ include.setValue(doc.include);
814
+ includedSteps.setValue(doc.includedSteps ?? (doc.include === "source" ? [doc.sourceStep] : [...SHADE_STEPS]));
815
+ }
816
+ const label = copy?.label ?? (typeof field.label === "string" ? field.label : key);
817
+ return /* @__PURE__ */ jsxs("div", {
818
+ className: "field-type bl-scale-field",
819
+ children: [/* @__PURE__ */ jsx("style", {
820
+ href: "bl-scale-card",
821
+ precedence: "default",
822
+ children: COLOR_SCALE_CARD_CSS
823
+ }), /* @__PURE__ */ jsx(ColorScaleCard, {
824
+ id: key,
825
+ name: label,
826
+ hint: copy?.hint,
827
+ required: Boolean(copy) || Boolean("required" in field && field.required),
828
+ state: isThemeHex(state.hex) ? state : {
829
+ ...state,
830
+ hex: "#000000"
831
+ },
832
+ onChange: write,
833
+ readOnly,
834
+ role: key === "success" || key === "destructive" ? key : null
835
+ })]
836
+ });
837
+ };
838
+ //#endregion
839
+ //#region src/theme/types.ts
840
+ const SYSTEM_COLOR_KEYS = [
841
+ "primary",
842
+ "secondary",
843
+ "accent",
844
+ "highlight",
845
+ "success",
846
+ "destructive"
847
+ ];
848
+ function themeColorKeys(doc) {
849
+ const custom = (Array.isArray(doc?.colors?.library) ? doc.colors.library : []).map((row) => row.key?.trim()).filter((key) => Boolean(key));
850
+ return [...SYSTEM_COLOR_KEYS, ...custom];
851
+ }
852
+ /**
853
+ * Remove a custom color. Unused: omit `replacement` and the row is gone.
854
+ * In use: pass another system or custom key; the caller rewrites page
855
+ * tokens with `rewriteColorToken`. After this, no library row still
856
+ * has `key`.
857
+ */
858
+ function deleteLibraryColor(doc, key, replacement) {
859
+ const library = [...Array.isArray(doc.colors?.library) ? doc.colors.library : []];
860
+ const index = library.findIndex((row) => row.key === key);
861
+ if (index === -1) throw new Error(`No custom color named ${JSON.stringify(key)}`);
862
+ library.splice(index, 1);
863
+ const next = {
864
+ ...doc,
865
+ colors: {
866
+ ...doc.colors,
867
+ library
868
+ }
869
+ };
870
+ if (replacement !== void 0) {
871
+ if (replacement === key) throw new Error("Replacement must be a different color");
872
+ if (!themeColorKeys(next).includes(replacement)) throw new Error(`Replacement ${JSON.stringify(replacement)} is not a system or remaining custom color`);
873
+ }
874
+ return next;
875
+ }
876
+ //#endregion
877
+ //#region src/admin/library-field.tsx
878
+ function slugify(value) {
879
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
880
+ }
881
+ /**
882
+ * Custom colors: the same scale controls as system colors, plus add and
883
+ * in-use delete (one replacement scale; unused rows delete immediately).
884
+ */
885
+ const LibraryField = ({ path, readOnly }) => {
886
+ const { value, setValue } = useField({ path });
887
+ const rows = Array.isArray(value) ? value : [];
888
+ const [adding, setAdding] = useState(false);
889
+ const [name, setName] = useState("");
890
+ const [hex, setHex] = useState("#E07A5F");
891
+ const [pendingDelete, setPendingDelete] = useState(null);
892
+ const [replacement, setReplacement] = useState("");
893
+ function write(next) {
894
+ setValue(next);
895
+ }
896
+ function add() {
897
+ const key = slugify(name);
898
+ if (!key || rows.some((row) => row.key === key)) return;
899
+ write([...rows, {
900
+ key,
901
+ label: name.trim(),
902
+ ...colorDocFromState(createColorScale(hex, 500))
903
+ }]);
904
+ setName("");
905
+ setAdding(false);
906
+ }
907
+ function remove(row, replaceWith) {
908
+ write(deleteLibraryColor({ colors: { library: rows } }, row.key ?? "", replaceWith).colors?.library ?? []);
909
+ setPendingDelete(null);
910
+ setReplacement("");
911
+ }
912
+ const replacements = [...SYSTEM_COLOR_KEYS.map((key) => ({
913
+ value: key,
914
+ label: key[0].toUpperCase() + key.slice(1)
915
+ })), ...rows.filter((row) => row.key && row.key !== pendingDelete?.key).map((row) => ({
916
+ value: row.key ?? "",
917
+ label: row.label ?? row.key ?? ""
918
+ }))];
919
+ return /* @__PURE__ */ jsxs("div", {
920
+ className: "field-type bl-library",
921
+ children: [
922
+ /* @__PURE__ */ jsx("style", {
923
+ href: "bl-scale-card",
924
+ precedence: "default",
925
+ children: COLOR_SCALE_CARD_CSS
926
+ }),
927
+ /* @__PURE__ */ jsx("style", {
928
+ href: "bl-library",
929
+ precedence: "default",
930
+ children: CSS$3
931
+ }),
932
+ /* @__PURE__ */ jsxs("div", {
933
+ className: "bl-library__head",
934
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", { children: "Custom colors" }), /* @__PURE__ */ jsx("p", { children: "Named scales that sit with the house colors. Page editors pick them as keys (`coral-400`)." })] }), /* @__PURE__ */ jsx("button", {
935
+ type: "button",
936
+ disabled: readOnly,
937
+ onClick: () => setAdding(true),
938
+ children: "Add color"
939
+ })]
940
+ }),
941
+ adding ? /* @__PURE__ */ jsxs("div", {
942
+ className: "bl-library__add",
943
+ children: [
944
+ /* @__PURE__ */ jsxs("label", { children: ["Name", /* @__PURE__ */ jsx("input", {
945
+ value: name,
946
+ onChange: (event) => setName(event.target.value)
947
+ })] }),
948
+ /* @__PURE__ */ jsxs("label", { children: ["Hex", /* @__PURE__ */ jsx("input", {
949
+ value: hex,
950
+ onChange: (event) => setHex(event.target.value)
951
+ })] }),
952
+ /* @__PURE__ */ jsx("button", {
953
+ type: "button",
954
+ onClick: () => setAdding(false),
955
+ children: "Cancel"
956
+ }),
957
+ /* @__PURE__ */ jsx("button", {
958
+ type: "button",
959
+ disabled: !slugify(name),
960
+ onClick: add,
961
+ children: "Create color scale"
962
+ })
963
+ ]
964
+ }) : null,
965
+ /* @__PURE__ */ jsx("div", {
966
+ className: "bl-library__list",
967
+ children: rows.map((row, index) => /* @__PURE__ */ jsx(ColorScaleCard, {
968
+ id: row.key ?? `custom-${index}`,
969
+ name: row.label ?? row.key ?? "Color",
970
+ hint: row.key ? `Key: ${row.key}` : void 0,
971
+ custom: true,
972
+ state: stateFromColorDoc(row),
973
+ onChange: (next) => {
974
+ const copy = [...rows];
975
+ copy[index] = {
976
+ ...row,
977
+ ...colorDocFromState(next)
978
+ };
979
+ write(copy);
980
+ },
981
+ onDelete: readOnly ? void 0 : () => setPendingDelete(row),
982
+ readOnly
983
+ }, row.key ?? index))
984
+ }),
985
+ pendingDelete ? /* @__PURE__ */ jsx("div", {
986
+ className: "bl-library__modal",
987
+ role: "dialog",
988
+ "aria-modal": "true",
989
+ "aria-labelledby": "bl-library-delete-title",
990
+ children: /* @__PURE__ */ jsxs("div", {
991
+ className: "bl-library__panel",
992
+ children: [
993
+ /* @__PURE__ */ jsxs("h2", {
994
+ id: "bl-library-delete-title",
995
+ children: ["Delete ", pendingDelete.label ?? pendingDelete.key]
996
+ }),
997
+ /* @__PURE__ */ jsxs("p", { children: [
998
+ "Unused custom colors delete immediately. If this scale is in use, pick one replacement — every",
999
+ " ",
1000
+ /* @__PURE__ */ jsxs("code", { children: [pendingDelete.key, "-400"] }),
1001
+ " becomes ",
1002
+ /* @__PURE__ */ jsxs("code", { children: [replacement || "…", "-400"] }),
1003
+ "."
1004
+ ] }),
1005
+ /* @__PURE__ */ jsxs("label", { children: ["Replacement scale", /* @__PURE__ */ jsxs("select", {
1006
+ value: replacement,
1007
+ onChange: (event) => setReplacement(event.target.value),
1008
+ children: [/* @__PURE__ */ jsx("option", {
1009
+ value: "",
1010
+ children: "Choose a scale…"
1011
+ }), replacements.map((option) => /* @__PURE__ */ jsx("option", {
1012
+ value: option.value,
1013
+ children: option.label
1014
+ }, option.value))]
1015
+ })] }),
1016
+ /* @__PURE__ */ jsxs("div", {
1017
+ className: "bl-library__actions",
1018
+ children: [
1019
+ /* @__PURE__ */ jsx("button", {
1020
+ type: "button",
1021
+ onClick: () => setPendingDelete(null),
1022
+ children: "Cancel"
1023
+ }),
1024
+ /* @__PURE__ */ jsx("button", {
1025
+ type: "button",
1026
+ onClick: () => remove(pendingDelete),
1027
+ children: "Delete immediately"
1028
+ }),
1029
+ /* @__PURE__ */ jsx("button", {
1030
+ type: "button",
1031
+ disabled: !replacement,
1032
+ onClick: () => remove(pendingDelete, replacement),
1033
+ children: "Replace & delete"
1034
+ })
1035
+ ]
1036
+ })
1037
+ ]
1038
+ })
1039
+ }) : null
1040
+ ]
1041
+ });
1042
+ };
1043
+ const CSS$3 = `
1044
+ .bl-library__head{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start;margin-bottom:.85rem}
1045
+ .bl-library__head h2{margin:0;font-size:1.1rem}
1046
+ .bl-library__head p{margin:.35rem 0 0;font-size:13px;color:var(--theme-elevation-500,#64748b)}
1047
+ .bl-library__head button,.bl-library__add button,.bl-library__actions button{min-height:2.1rem;padding:.35rem .75rem;border-radius:8px;border:1px solid var(--theme-elevation-150,#e2e8f0);background:var(--theme-elevation-0,#fff);font:inherit;cursor:pointer}
1048
+ .bl-library__list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem}
1049
+ @media (max-width:860px){.bl-library__list{grid-template-columns:1fr}}
1050
+ .bl-library__add{display:flex;flex-wrap:wrap;gap:.6rem;align-items:end;margin-bottom:.85rem;padding:.85rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px}
1051
+ .bl-library__add label{display:flex;flex-direction:column;gap:.3rem;font-size:12px;font-weight:600}
1052
+ .bl-library__add input{min-height:2.1rem;padding:.3rem .55rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:8px}
1053
+ .bl-library__modal{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}
1054
+ .bl-library__panel{width:min(28rem,100%);padding:1.2rem;border-radius:16px;background:#fff;color:#0f172a}
1055
+ .bl-library__panel h2{margin:0}
1056
+ .bl-library__panel p{margin:.5rem 0 0;color:#475569;font-size:14px}
1057
+ .bl-library__panel label{display:flex;flex-direction:column;gap:.4rem;margin-top:1rem;font-size:13px;font-weight:600}
1058
+ .bl-library__panel select{min-height:2.25rem;border:1px solid #cbd5e1;border-radius:8px;padding:.35rem .5rem}
1059
+ .bl-library__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.5rem;margin-top:1rem}
1060
+ `;
1061
+ //#endregion
1062
+ //#region src/admin/typography-pairing.tsx
1063
+ function FontRoleCard({ title, hint, required, family, preview, children }) {
1064
+ return /* @__PURE__ */ jsxs("article", {
1065
+ className: "bl-font-role",
1066
+ children: [
1067
+ /* @__PURE__ */ jsxs("header", {
1068
+ className: "bl-font-role__head",
1069
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", { children: title }), /* @__PURE__ */ jsx("p", { children: hint })] }), required ? /* @__PURE__ */ jsx("span", {
1070
+ className: "bl-font-role__badge",
1071
+ children: "Required"
1072
+ }) : null]
1073
+ }),
1074
+ children,
1075
+ /* @__PURE__ */ jsxs("div", {
1076
+ className: "bl-font-role__preview",
1077
+ children: [/* @__PURE__ */ jsx("span", { children: "Preview" }), preview === "heading" ? /* @__PURE__ */ jsx("p", {
1078
+ className: "bl-font-role__heading",
1079
+ style: { fontFamily: `"${family}"` },
1080
+ children: "Advancing the work with clarity."
1081
+ }) : /* @__PURE__ */ jsx("p", {
1082
+ className: "bl-font-role__body",
1083
+ style: { fontFamily: `"${family}"` },
1084
+ children: "Clear, comfortable text for longer reading across the site."
1085
+ })]
1086
+ })
1087
+ ]
1088
+ });
1089
+ }
1090
+ function TypographyPairing({ headingFamily, bodyFamily }) {
1091
+ return /* @__PURE__ */ jsxs("section", {
1092
+ className: "bl-pairing",
1093
+ "aria-label": "Font pairing sample",
1094
+ children: [/* @__PURE__ */ jsxs("header", { children: [/* @__PURE__ */ jsx("h2", { children: "Pairing sample" }), /* @__PURE__ */ jsx("p", { children: "Heading and body from the approved catalogue. Type scale stays with the library." })] }), /* @__PURE__ */ jsxs("div", {
1095
+ className: "bl-pairing__page",
1096
+ children: [
1097
+ /* @__PURE__ */ jsx("p", {
1098
+ className: "bl-pairing__eyebrow",
1099
+ style: { fontFamily: `"${bodyFamily}"` },
1100
+ children: "For visitors"
1101
+ }),
1102
+ /* @__PURE__ */ jsx("p", {
1103
+ className: "bl-pairing__title",
1104
+ style: { fontFamily: `"${headingFamily}"` },
1105
+ children: "Stay in motion."
1106
+ }),
1107
+ /* @__PURE__ */ jsx("p", {
1108
+ className: "bl-pairing__copy",
1109
+ style: { fontFamily: `"${bodyFamily}"` },
1110
+ children: "Heading and body come from the catalogue. This is the pairing, not a full site preview."
1111
+ }),
1112
+ /* @__PURE__ */ jsxs("div", {
1113
+ className: "bl-pairing__actions",
1114
+ children: [/* @__PURE__ */ jsx("span", {
1115
+ className: "bl-pairing__btn",
1116
+ style: { fontFamily: `"${bodyFamily}"` },
1117
+ children: "Primary action"
1118
+ }), /* @__PURE__ */ jsx("span", {
1119
+ className: "bl-pairing__btn bl-pairing__btn--ghost",
1120
+ style: { fontFamily: `"${bodyFamily}"` },
1121
+ children: "Learn more"
1122
+ })]
1123
+ })
1124
+ ]
1125
+ })]
1126
+ });
1127
+ }
1128
+ const TYPOGRAPHY_PAIRING_CSS = `
1129
+ .bl-font-role{padding:1.1rem 1.15rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff)}
1130
+ .bl-font-role__head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}
1131
+ .bl-font-role__head h3{margin:0;font-size:1rem}
1132
+ .bl-font-role__head p{margin:.3rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}
1133
+ .bl-font-role__badge{font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:.15rem .45rem;border-radius:999px;background:var(--theme-elevation-100,#f1f5f9);color:var(--theme-elevation-500,#64748b)}
1134
+ .bl-font-role__preview{margin-top:1rem;padding:.85rem .9rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-50,#f8fafc)}
1135
+ .bl-font-role__preview > span{display:block;margin-bottom:.4rem;font-size:11px;color:var(--theme-elevation-500,#64748b)}
1136
+ .bl-font-role__heading{margin:0;font-size:1.5rem;line-height:1.2;font-weight:600}
1137
+ .bl-font-role__body{margin:0;font-size:1rem;line-height:1.6;color:var(--theme-elevation-600,#475569)}
1138
+ .bl-pairing{padding:0;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff);overflow:hidden}
1139
+ .bl-pairing > header{padding:1rem 1.15rem;border-bottom:1px solid var(--theme-elevation-150,#e2e8f0)}
1140
+ .bl-pairing > header h2{margin:0;font-size:1rem}
1141
+ .bl-pairing > header p{margin:.3rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}
1142
+ .bl-pairing__page{padding:1.35rem 1.4rem 1.5rem}
1143
+ .bl-pairing__eyebrow{margin:0;font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#7c1fa2}
1144
+ .bl-pairing__title{margin:.5rem 0 0;font-size:2rem;line-height:1.1;font-weight:600}
1145
+ .bl-pairing__copy{margin:.75rem 0 0;max-width:36rem;font-size:1rem;line-height:1.6;color:var(--theme-elevation-600,#475569)}
1146
+ .bl-pairing__actions{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:1.1rem}
1147
+ .bl-pairing__btn{display:inline-flex;align-items:center;min-height:2.15rem;padding:.4rem .85rem;border-radius:8px;background:#db0082;color:#fff;font-size:14px;font-weight:600}
1148
+ .bl-pairing__btn--ghost{background:#fff;color:#0f172a;border:1px solid #cbd5e1}
1149
+ `;
1150
+ //#endregion
111
1151
  //#region src/admin/font-field.tsx
112
1152
  /**
113
1153
  * Listbox over the catalogue ids in `admin.custom.ids`. Each option is the
114
1154
  * family name in that face, with the catalogue hint beneath. Faces load
115
- * from the site's font route the first time the picker opens.
1155
+ * from the site's font route for the selected family, and for every
1156
+ * catalogue id once the picker opens.
116
1157
  */
117
1158
  const FontField = ({ field, path, readOnly }) => {
118
1159
  const { value, setValue, showError, errorMessage } = useField({ path });
@@ -142,6 +1183,8 @@ const FontField = ({ field, path, readOnly }) => {
142
1183
  const [open, setOpen] = useState(false);
143
1184
  const [faces, setFaces] = useState(false);
144
1185
  const [active, setActive] = useState(-1);
1186
+ const [query, setQuery] = useState("");
1187
+ const [category, setCategory] = useState("all");
145
1188
  const typed = useRef("");
146
1189
  const typedAt = useRef(0);
147
1190
  const id = useId();
@@ -152,10 +1195,18 @@ const FontField = ({ field, path, readOnly }) => {
152
1195
  if (open) setFaces(true);
153
1196
  }, [open]);
154
1197
  const selected = options.find((option) => option.value === (value ?? "")) ?? options[0];
1198
+ const visible = options.filter((option) => {
1199
+ if (query && !option.family.toLowerCase().includes(query.trim().toLowerCase())) return false;
1200
+ if (category === "all" || !option.specimen) return true;
1201
+ return findFont(option.value)?.category === category;
1202
+ });
1203
+ const role = sameAsBody ? FONT_ROLE_COPY.heading : FONT_ROLE_COPY.body;
155
1204
  function choose(option) {
156
1205
  setValue(option.value);
157
1206
  setOpen(false);
158
1207
  setActive(-1);
1208
+ setQuery("");
1209
+ setCategory("all");
159
1210
  }
160
1211
  function onKeyDown(event) {
161
1212
  if (event.key === "ArrowDown") {
@@ -198,26 +1249,35 @@ const FontField = ({ field, path, readOnly }) => {
198
1249
  }
199
1250
  }
200
1251
  const activeId = active >= 0 && open ? `${id}-option-${active}` : void 0;
1252
+ const faceIds = faces ? ids : isFontId(value) ? [value] : [];
201
1253
  return /* @__PURE__ */ jsxs("div", {
202
1254
  className: "field-type bl-font-field",
203
1255
  children: [
1256
+ /* @__PURE__ */ jsx("style", {
1257
+ href: "bl-pairing",
1258
+ precedence: "default",
1259
+ children: TYPOGRAPHY_PAIRING_CSS
1260
+ }),
204
1261
  /* @__PURE__ */ jsx("style", {
205
1262
  href: "bl-font-field",
206
1263
  precedence: "default",
207
- children: CSS$1
1264
+ children: CSS$2
208
1265
  }),
209
- faces ? /* @__PURE__ */ jsx("style", {
1266
+ faceIds.length > 0 ? /* @__PURE__ */ jsx("style", {
210
1267
  "data-bl-font-faces": "",
211
- children: fontFaceCss(ids, fontsBaseUrl)
1268
+ children: fontFaceCss(faceIds, fontsBaseUrl)
212
1269
  }) : null,
213
- /* @__PURE__ */ jsx(FieldLabel, {
214
- htmlFor: buttonId,
215
- label: field.label,
216
- required
217
- }),
218
- /* @__PURE__ */ jsxs("div", {
219
- className: "bl-font-field__control",
220
- children: [/* @__PURE__ */ jsx("button", {
1270
+ /* @__PURE__ */ jsxs(FontRoleCard, {
1271
+ title: typeof field.label === "string" ? field.label : role.label,
1272
+ hint: role.hint,
1273
+ required,
1274
+ family: selected?.family ?? "Inter",
1275
+ preview: sameAsBody ? "heading" : "body",
1276
+ children: [/* @__PURE__ */ jsx(FieldLabel, {
1277
+ htmlFor: buttonId,
1278
+ label: field.label,
1279
+ required
1280
+ }), /* @__PURE__ */ jsxs("button", {
221
1281
  id: buttonId,
222
1282
  type: "button",
223
1283
  className: "bl-font-field__trigger",
@@ -227,34 +1287,94 @@ const FontField = ({ field, path, readOnly }) => {
227
1287
  "aria-controls": listId,
228
1288
  "aria-activedescendant": activeId,
229
1289
  onClick: () => setOpen((was) => !was),
230
- onBlur: () => setOpen(false),
231
1290
  onKeyDown,
232
- children: selected?.family ?? "Choose a font"
233
- }), open ? /* @__PURE__ */ jsx("ul", {
234
- id: listId,
235
- role: "listbox",
236
- className: "bl-font-field__menu",
237
- children: options.map((option, i) => /* @__PURE__ */ jsxs("li", {
238
- id: `${id}-option-${i}`,
239
- role: "option",
240
- "aria-selected": option.value === (value ?? ""),
241
- className: "bl-font-field__option",
242
- onMouseDown: (event) => {
243
- event.preventDefault();
244
- choose(option);
245
- },
246
- onMouseEnter: () => setActive(i),
247
- children: [/* @__PURE__ */ jsx("span", {
248
- className: "bl-font-field__family",
249
- style: option.specimen ? { fontFamily: `"${option.family}"` } : void 0,
250
- children: option.family
251
- }), /* @__PURE__ */ jsx("span", {
252
- className: "bl-font-field__hint",
253
- children: option.hint
254
- })]
255
- }, option.value || "same"))
256
- }) : null]
1291
+ children: [/* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
1292
+ className: "bl-font-field__selected-label",
1293
+ children: "Selected"
1294
+ }), /* @__PURE__ */ jsx("span", {
1295
+ className: "bl-font-field__selected-name",
1296
+ style: selected?.specimen ? { fontFamily: `"${selected.family}"` } : void 0,
1297
+ children: selected?.family ?? "Choose a font"
1298
+ })] }), /* @__PURE__ */ jsx("span", {
1299
+ "aria-hidden": "true",
1300
+ children: ""
1301
+ })]
1302
+ })]
257
1303
  }),
1304
+ open ? /* @__PURE__ */ jsx("div", {
1305
+ className: "bl-font-field__modal",
1306
+ onClick: (event) => {
1307
+ if (event.target === event.currentTarget) setOpen(false);
1308
+ },
1309
+ children: /* @__PURE__ */ jsxs("div", {
1310
+ className: "bl-font-field__panel",
1311
+ children: [
1312
+ /* @__PURE__ */ jsxs("div", {
1313
+ className: "bl-font-field__panel-head",
1314
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", { children: "Choose a font" }), /* @__PURE__ */ jsx("h2", { children: role.label })] }), /* @__PURE__ */ jsx("button", {
1315
+ type: "button",
1316
+ onClick: () => setOpen(false),
1317
+ "aria-label": "Close",
1318
+ children: "×"
1319
+ })]
1320
+ }),
1321
+ /* @__PURE__ */ jsx("input", {
1322
+ type: "search",
1323
+ className: "bl-font-field__search",
1324
+ placeholder: "Search fonts…",
1325
+ value: query,
1326
+ onChange: (event) => setQuery(event.target.value)
1327
+ }),
1328
+ /* @__PURE__ */ jsx("div", {
1329
+ className: "bl-font-field__filters",
1330
+ children: [
1331
+ "all",
1332
+ "sans",
1333
+ "serif",
1334
+ "display",
1335
+ "mono"
1336
+ ].map((item) => /* @__PURE__ */ jsx("button", {
1337
+ type: "button",
1338
+ "data-on": category === item ? "" : void 0,
1339
+ onClick: () => setCategory(item),
1340
+ children: item === "all" ? "All" : item === "sans" ? "Sans serif" : item === "serif" ? "Serif" : item === "display" ? "Display" : "Mono"
1341
+ }, item))
1342
+ }),
1343
+ /* @__PURE__ */ jsx("ul", {
1344
+ id: listId,
1345
+ role: "listbox",
1346
+ className: "bl-font-field__menu",
1347
+ children: visible.map((option, i) => /* @__PURE__ */ jsxs("li", {
1348
+ id: `${id}-option-${i}`,
1349
+ role: "option",
1350
+ "aria-selected": option.value === (value ?? ""),
1351
+ className: "bl-font-field__option",
1352
+ onMouseDown: (event) => {
1353
+ event.preventDefault();
1354
+ choose(option);
1355
+ },
1356
+ onMouseEnter: () => setActive(i),
1357
+ children: [
1358
+ /* @__PURE__ */ jsx("span", {
1359
+ className: "bl-font-field__family",
1360
+ style: option.specimen ? { fontFamily: `"${option.family}"` } : void 0,
1361
+ children: option.family
1362
+ }),
1363
+ /* @__PURE__ */ jsx("span", {
1364
+ className: "bl-font-field__hint",
1365
+ children: option.hint
1366
+ }),
1367
+ /* @__PURE__ */ jsx("span", {
1368
+ className: "bl-font-field__specimen",
1369
+ style: option.specimen ? { fontFamily: `"${option.family}"` } : void 0,
1370
+ children: "The quick brown fox jumps over the lazy dog."
1371
+ })
1372
+ ]
1373
+ }, option.value || "same"))
1374
+ })
1375
+ ]
1376
+ })
1377
+ }) : null,
258
1378
  /* @__PURE__ */ jsx(FieldDescription, {
259
1379
  description: field.admin?.description,
260
1380
  path
@@ -267,167 +1387,746 @@ const FontField = ({ field, path, readOnly }) => {
267
1387
  ]
268
1388
  });
269
1389
  };
270
- const CSS$1 = `
271
- .bl-font-field__control{position:relative}
272
- .bl-font-field__trigger{display:flex;align-items:center;width:100%;min-height:calc(var(--base) * 2);padding:calc(var(--base) * .25) calc(var(--base) * .6);border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:var(--theme-input-bg);color:var(--theme-elevation-800);font:inherit;text-align:left;cursor:pointer}
273
- .bl-font-field__trigger:focus{border-color:var(--theme-elevation-400);outline:none;box-shadow:0 0 0 1px var(--theme-elevation-400)}
274
- .bl-font-field__trigger:disabled{color:var(--theme-elevation-400);cursor:not-allowed}
275
- .bl-font-field__menu{position:absolute;left:0;right:0;top:calc(100% + 4px);z-index:10;margin:0;padding:calc(var(--base) * .2) 0;list-style:none;max-height:18rem;overflow:auto;border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:var(--theme-elevation-0);box-shadow:0 8px 24px var(--theme-overlay)}
276
- .bl-font-field__option{display:flex;flex-direction:column;gap:2px;padding:calc(var(--base) * .35) calc(var(--base) * .6);cursor:pointer}
277
- .bl-font-field__option[aria-selected="true"],.bl-font-field__option:hover{background:var(--theme-elevation-100)}
278
- .bl-font-field__family{font-weight:500}
279
- .bl-font-field__hint{color:var(--theme-elevation-500);font-size:12px}
1390
+ const CSS$2 = `
1391
+ .bl-font-field{margin-bottom:1rem}
1392
+ .bl-font-field .field-label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
1393
+ .bl-font-field__trigger{display:flex;align-items:center;justify-content:space-between;width:100%;margin-top:1rem;min-height:3.25rem;padding:.7rem 1rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-input-bg,#fff);color:inherit;font:inherit;text-align:left;cursor:pointer}
1394
+ .bl-font-field__trigger:focus{border-color:var(--theme-elevation-400,#94a3b8);outline:none;box-shadow:0 0 0 1px var(--theme-elevation-400,#94a3b8)}
1395
+ .bl-font-field__trigger:disabled{color:var(--theme-elevation-400,#94a3b8);cursor:not-allowed}
1396
+ .bl-font-field__selected-label{display:block;font-size:11px;color:var(--theme-elevation-500,#64748b)}
1397
+ .bl-font-field__selected-name{display:block;margin-top:.15rem;font-weight:600}
1398
+ .bl-font-field__modal{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}
1399
+ .bl-font-field__panel{width:min(36rem,100%);max-height:min(36rem,90vh);display:flex;flex-direction:column;padding:1.15rem 1.2rem;border-radius:16px;background:#fff;color:#0f172a}
1400
+ .bl-font-field__panel-head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}
1401
+ .bl-font-field__panel-head p{margin:0;font-size:12px;color:#64748b}
1402
+ .bl-font-field__panel-head h2{margin:.2rem 0 0;font-size:1.15rem}
1403
+ .bl-font-field__panel-head button{border:0;background:none;font:inherit;font-size:1.25rem;color:#64748b;cursor:pointer}
1404
+ .bl-font-field__search{margin-top:.9rem;min-height:2.5rem;padding:.5rem .75rem;border:1px solid #cbd5e1;border-radius:10px;font:inherit}
1405
+ .bl-font-field__filters{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.75rem}
1406
+ .bl-font-field__filters button{min-height:1.8rem;padding:.2rem .7rem;border:0;border-radius:999px;background:#f1f5f9;color:#475569;font:inherit;font-size:12px;cursor:pointer}
1407
+ .bl-font-field__filters button[data-on]{background:#0f172a;color:#fff}
1408
+ .bl-font-field__menu{margin:.75rem 0 0;padding:0;list-style:none;overflow:auto;max-height:18rem}
1409
+ .bl-font-field__option{display:flex;flex-direction:column;gap:2px;margin-bottom:.5rem;padding:.75rem .85rem;border:1px solid #e2e8f0;border-radius:12px;cursor:pointer}
1410
+ .bl-font-field__option[aria-selected="true"]{border-color:#7c1fa2;background:#f5f3ff}
1411
+ .bl-font-field__family{font-weight:600}
1412
+ .bl-font-field__hint{color:#64748b;font-size:12px}
1413
+ .bl-font-field__specimen{margin-top:.45rem;font-size:1.25rem;line-height:1.3}
280
1414
  `;
281
1415
  //#endregion
282
- //#region src/admin/contrast-report.tsx
283
- function fieldValue(fields, path) {
284
- return fields[path]?.value;
1416
+ //#region src/admin/pairing-field.tsx
1417
+ /**
1418
+ * Pairing sample only — no weight, letter-spacing, line-height, or health
1419
+ * scores. Faces load from the site's font route.
1420
+ */
1421
+ const PairingField = ({ field }) => {
1422
+ const fontsBaseUrl = (field.admin?.custom ?? {}).fontsBaseUrl ?? "/fonts";
1423
+ const { body, heading } = useFormFields(([fields]) => {
1424
+ const record = fields;
1425
+ return {
1426
+ body: String(record["typography.body"]?.value ?? ""),
1427
+ heading: String(record["typography.heading"]?.value ?? "")
1428
+ };
1429
+ });
1430
+ const headingId = heading || body;
1431
+ const bodyFont = findFont(body);
1432
+ const headingFont = findFont(headingId);
1433
+ const ids = [body, headingId].filter(Boolean);
1434
+ return /* @__PURE__ */ jsxs("div", {
1435
+ className: "field-type bl-pairing-field",
1436
+ children: [
1437
+ /* @__PURE__ */ jsx("style", {
1438
+ href: "bl-pairing",
1439
+ precedence: "default",
1440
+ children: TYPOGRAPHY_PAIRING_CSS
1441
+ }),
1442
+ ids.length > 0 ? /* @__PURE__ */ jsx("style", {
1443
+ "data-bl-font-faces": "",
1444
+ children: fontFaceCss(ids, fontsBaseUrl)
1445
+ }) : null,
1446
+ /* @__PURE__ */ jsx(TypographyPairing, {
1447
+ headingFamily: headingFont?.family ?? "Georgia",
1448
+ bodyFamily: bodyFont?.family ?? "system-ui"
1449
+ })
1450
+ ]
1451
+ });
1452
+ };
1453
+ //#endregion
1454
+ //#region src/admin/appearance-choices.tsx
1455
+ function ChoicePreview({ family, value }) {
1456
+ return /* @__PURE__ */ jsx("div", {
1457
+ className: "bl-appearance__preview",
1458
+ "data-family": family,
1459
+ "data-value": value,
1460
+ "aria-hidden": "true",
1461
+ children: family === "defaultTheme" && value === "system" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
1462
+ className: "bl-appearance__pane bl-appearance__pane--light",
1463
+ children: [/* @__PURE__ */ jsx("i", {}), /* @__PURE__ */ jsx("b", {})]
1464
+ }), /* @__PURE__ */ jsxs("div", {
1465
+ className: "bl-appearance__pane bl-appearance__pane--dark",
1466
+ children: [/* @__PURE__ */ jsx("i", {}), /* @__PURE__ */ jsx("b", {})]
1467
+ })] }) : family === "motion" ? /* @__PURE__ */ jsx("span", { className: "bl-appearance__dot" }) : family === "shadow" ? /* @__PURE__ */ jsx("div", { className: "bl-appearance__lift" }) : family === "radius" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1468
+ /* @__PURE__ */ jsx("b", { children: "Button" }),
1469
+ /* @__PURE__ */ jsx("i", { className: "bl-appearance__input" }),
1470
+ /* @__PURE__ */ jsx("i", { className: "bl-appearance__card" })
1471
+ ] }) : family === "density" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1472
+ /* @__PURE__ */ jsx("b", {}),
1473
+ /* @__PURE__ */ jsx("i", { className: "bl-appearance__input" }),
1474
+ /* @__PURE__ */ jsx("i", { className: "bl-appearance__card" })
1475
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1476
+ /* @__PURE__ */ jsx("i", {}),
1477
+ /* @__PURE__ */ jsx("i", {}),
1478
+ /* @__PURE__ */ jsx("b", {})
1479
+ ] })
1480
+ });
285
1481
  }
286
- function isCompleteBrand(values) {
287
- return isThemeHex(values.primary) && isThemeHex(values.secondary) && isThemeHex(values.accent) && isThemeHex(values.highlight) && isThemeHex(values.success) && typeof values.greyScale === "string";
1482
+ function AppearanceChoices({ values, onChange, readOnly }) {
1483
+ return /* @__PURE__ */ jsxs("div", {
1484
+ className: "bl-appearance",
1485
+ children: [/* @__PURE__ */ jsxs("header", {
1486
+ className: "bl-appearance__intro",
1487
+ children: [/* @__PURE__ */ jsx("h2", { children: "Set the overall feel of your site" }), /* @__PURE__ */ jsx("p", { children: "Choose how rounded, elevated, animated, and spacious the interface feels. You don’t manage the technical values — the system handles those." })]
1488
+ }), Object.keys(APPEARANCE_CHOICES).map((family) => {
1489
+ const choices = APPEARANCE_CHOICES[family];
1490
+ const copy = APPEARANCE_FAMILY_COPY[family];
1491
+ return /* @__PURE__ */ jsxs("fieldset", {
1492
+ className: "bl-appearance__family",
1493
+ children: [
1494
+ /* @__PURE__ */ jsxs("legend", { children: [/* @__PURE__ */ jsx("strong", { children: copy.title }), /* @__PURE__ */ jsx("span", { children: copy.hint })] }),
1495
+ /* @__PURE__ */ jsx("div", {
1496
+ className: "bl-appearance__choices",
1497
+ "data-family": family,
1498
+ children: Object.entries(choices).map(([value, item]) => {
1499
+ const selected = values[family] === value;
1500
+ return /* @__PURE__ */ jsxs("label", {
1501
+ className: "bl-appearance__choice",
1502
+ "data-selected": selected ? "" : void 0,
1503
+ children: [
1504
+ /* @__PURE__ */ jsx("input", {
1505
+ type: "radio",
1506
+ name: family,
1507
+ value,
1508
+ checked: selected,
1509
+ disabled: readOnly,
1510
+ onChange: () => onChange(family, value)
1511
+ }),
1512
+ /* @__PURE__ */ jsx(ChoicePreview, {
1513
+ family,
1514
+ value
1515
+ }),
1516
+ /* @__PURE__ */ jsxs("span", {
1517
+ className: "bl-appearance__copy",
1518
+ children: [/* @__PURE__ */ jsxs("strong", { children: [item.label, selected ? /* @__PURE__ */ jsx("em", {
1519
+ "aria-hidden": "true",
1520
+ children: "✓"
1521
+ }) : null] }), /* @__PURE__ */ jsx("span", { children: item.hint })]
1522
+ })
1523
+ ]
1524
+ }, value);
1525
+ })
1526
+ }),
1527
+ family === "motion" ? /* @__PURE__ */ jsx("p", {
1528
+ className: "bl-appearance__note",
1529
+ children: "Visitors who prefer reduced motion will still see fewer animations automatically."
1530
+ }) : null
1531
+ ]
1532
+ }, family);
1533
+ })]
1534
+ });
288
1535
  }
289
- function LevelIcon({ level }) {
290
- if (level === "fail") return /* @__PURE__ */ jsx("svg", {
291
- viewBox: "0 0 16 16",
292
- width: "14",
293
- height: "14",
294
- "aria-hidden": "true",
295
- children: /* @__PURE__ */ jsx("path", {
296
- fill: "currentColor",
297
- d: "M8 1.5 1.5 14h13L8 1.5zm0 4.2c.4 0 .7.3.7.8v3.2c0 .4-.3.8-.7.8s-.8-.4-.8-.8V6.5c0-.5.4-.8.8-.8zm0 6.3a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6z"
298
- })
1536
+ const APPEARANCE_CHOICES_CSS = `
1537
+ .bl-appearance{display:flex;flex-direction:column;gap:1.25rem}
1538
+ .bl-appearance__intro h2{margin:0;font-size:1.1rem}
1539
+ .bl-appearance__intro p{margin:.35rem 0 0;font-size:13px;color:var(--theme-elevation-500,#64748b);max-width:40rem}
1540
+ .bl-appearance__family{margin:0;padding:1rem 1.1rem 1.15rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff)}
1541
+ .bl-appearance__family legend{padding:0;margin-bottom:.85rem}
1542
+ .bl-appearance__family legend strong{display:block;font-size:14px}
1543
+ .bl-appearance__family legend span{display:block;margin-top:.25rem;font-size:12px;color:var(--theme-elevation-500,#64748b);font-weight:400}
1544
+ .bl-appearance__choices{display:grid;gap:.6rem}
1545
+ .bl-appearance__choices[data-family=defaultTheme],.bl-appearance__choices[data-family=shadow],.bl-appearance__choices[data-family=motion],.bl-appearance__choices[data-family=density]{grid-template-columns:repeat(3,minmax(0,1fr))}
1546
+ .bl-appearance__choices[data-family=radius]{grid-template-columns:repeat(4,minmax(0,1fr))}
1547
+ .bl-appearance__choice{display:flex;flex-direction:column;gap:.65rem;padding:.75rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff);cursor:pointer}
1548
+ .bl-appearance__choice[data-selected]{box-shadow:inset 0 0 0 2px #0f172a;border-color:#0f172a}
1549
+ .bl-appearance__choice input{position:absolute;opacity:0;pointer-events:none}
1550
+ .bl-appearance__copy{display:flex;flex-direction:column;gap:.2rem}
1551
+ .bl-appearance__copy strong{display:flex;justify-content:space-between;align-items:center;gap:.4rem;font-size:14px}
1552
+ .bl-appearance__copy em{font-style:normal}
1553
+ .bl-appearance__copy span{font-size:12px;color:var(--theme-elevation-500,#64748b)}
1554
+ .bl-appearance__note{margin:.75rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}
1555
+ .bl-appearance__preview{--bl-preview-brand:#db0082;--bl-preview-radius:8px;border-radius:10px;background:#f8fafc;overflow:hidden}
1556
+ .bl-appearance__preview[data-family=defaultTheme]{display:grid;min-height:5.5rem;padding:.65rem}
1557
+ .bl-appearance__preview[data-family=defaultTheme][data-value=system]{grid-template-columns:1fr 1fr;padding:0}
1558
+ .bl-appearance__preview[data-family=defaultTheme][data-value=dark]{background:#020617}
1559
+ .bl-appearance__pane{display:flex;flex-direction:column;gap:.45rem;padding:.65rem}
1560
+ .bl-appearance__pane--dark{background:#020617}
1561
+ .bl-appearance__pane i,.bl-appearance__preview[data-family=defaultTheme] > i{display:block;height:.4rem;width:42%;border-radius:99px;background:#0f172a}
1562
+ .bl-appearance__pane i + i,.bl-appearance__preview[data-family=defaultTheme] > i + i{width:64%;background:#cbd5e1}
1563
+ .bl-appearance__pane--dark i{background:#fff}
1564
+ .bl-appearance__pane--dark i + i{background:#334155}
1565
+ .bl-appearance__preview[data-family=defaultTheme][data-value=dark] > i{background:#fff}
1566
+ .bl-appearance__preview[data-family=defaultTheme][data-value=dark] > i + i{background:#334155}
1567
+ .bl-appearance__pane b,.bl-appearance__preview[data-family=defaultTheme] > b{display:block;height:1.6rem;margin-top:auto;border-radius:6px;background:var(--bl-preview-brand)}
1568
+ .bl-appearance__pane--dark b{background:var(--bl-preview-brand)}
1569
+ .bl-appearance__preview[data-family=radius]{display:flex;flex-direction:column;gap:.45rem;padding:.65rem;background:#fff}
1570
+ .bl-appearance__preview[data-family=radius] b{display:grid;place-items:center;height:1.85rem;background:var(--bl-preview-brand);color:#fff;font-size:10px;font-weight:600;border-radius:var(--bl-preview-radius)}
1571
+ .bl-appearance__input{display:block;height:1.85rem;border:1px solid #cbd5e1;background:#f8fafc;border-radius:var(--bl-preview-radius)}
1572
+ .bl-appearance__card{display:block;height:2.4rem;border:1px solid #e2e8f0;background:#fff;border-radius:var(--bl-preview-radius)}
1573
+ .bl-appearance__preview[data-value=sharp]{--bl-preview-radius:0}
1574
+ .bl-appearance__preview[data-value=subtle]{--bl-preview-radius:6px}
1575
+ .bl-appearance__preview[data-value=rounded]{--bl-preview-radius:12px}
1576
+ .bl-appearance__preview[data-value=pill]{--bl-preview-radius:999px}
1577
+ .bl-appearance__preview[data-value=pill] .bl-appearance__card{--bl-preview-radius:1.25rem}
1578
+ .bl-appearance__preview[data-family=shadow]{display:grid;place-items:center;min-height:6.5rem;padding:.85rem}
1579
+ .bl-appearance__lift{width:100%;height:3.5rem;border-radius:10px;background:#fff}
1580
+ .bl-appearance__preview[data-value=flat] .bl-appearance__lift{border:1px solid #e2e8f0}
1581
+ .bl-appearance__preview[data-value=subtle] .bl-appearance__lift{box-shadow:0 1px 2px rgba(15,23,42,.06),0 4px 12px rgba(15,23,42,.06)}
1582
+ .bl-appearance__preview[data-value=elevated] .bl-appearance__lift{box-shadow:0 10px 30px rgba(15,23,42,.12)}
1583
+ .bl-appearance__preview[data-family=motion]{display:flex;align-items:center;min-height:4.5rem;padding:0 .85rem}
1584
+ .bl-appearance__dot{width:1.15rem;height:1.15rem;border-radius:999px;background:var(--bl-preview-brand);animation:bl-appear-dot 1.8s ease-in-out infinite}
1585
+ .bl-appearance__preview[data-value=snappy] .bl-appearance__dot{animation-duration:1s}
1586
+ .bl-appearance__preview[data-value=minimal] .bl-appearance__dot{animation-duration:2.5s}
1587
+ @keyframes bl-appear-dot{0%,100%{transform:translateX(0)}50%{transform:translateX(4.5rem)}}
1588
+ .bl-appearance__preview[data-family=density]{display:flex;flex-direction:column;background:#f8fafc}
1589
+ .bl-appearance__preview[data-value=compact]{gap:.4rem;padding:.55rem}
1590
+ .bl-appearance__preview[data-value=default]{gap:.65rem;padding:.8rem}
1591
+ .bl-appearance__preview[data-value=spacious]{gap:.9rem;padding:1.05rem}
1592
+ .bl-appearance__preview[data-family=density] b{display:block;height:1.7rem;background:var(--bl-preview-brand);border-radius:6px}
1593
+ .bl-appearance__preview[data-value=compact] b,.bl-appearance__preview[data-value=compact] .bl-appearance__input{height:1.45rem}
1594
+ .bl-appearance__preview[data-value=spacious] b,.bl-appearance__preview[data-value=spacious] .bl-appearance__input{height:2.1rem}
1595
+ @media (max-width:720px){
1596
+ .bl-appearance__choices[data-family=defaultTheme],.bl-appearance__choices[data-family=shadow],.bl-appearance__choices[data-family=motion],.bl-appearance__choices[data-family=density],.bl-appearance__choices[data-family=radius]{grid-template-columns:1fr}
1597
+ }
1598
+ @media (prefers-reduced-motion:reduce){
1599
+ .bl-appearance__dot{animation:none}
1600
+ }
1601
+ `;
1602
+ //#endregion
1603
+ //#region src/admin/appearance-field.tsx
1604
+ /**
1605
+ * Named appearance choices. Soft → pill, Gentle → minimal, Balanced →
1606
+ * default, Follow device → system. No rem or ms.
1607
+ */
1608
+ const AppearanceField = ({ path, readOnly }) => {
1609
+ const defaultTheme = useField({ path: `${path}.defaultTheme` });
1610
+ const radius = useField({ path: `${path}.radius` });
1611
+ const shadow = useField({ path: `${path}.shadow` });
1612
+ const motion = useField({ path: `${path}.motion` });
1613
+ const density = useField({ path: `${path}.density` });
1614
+ const fields = {
1615
+ defaultTheme,
1616
+ radius,
1617
+ shadow,
1618
+ motion,
1619
+ density
1620
+ };
1621
+ function onChange(name, value) {
1622
+ fields[name].setValue(value);
1623
+ }
1624
+ return /* @__PURE__ */ jsxs("div", {
1625
+ className: "field-type bl-appearance-field",
1626
+ children: [/* @__PURE__ */ jsx("style", {
1627
+ href: "bl-appearance",
1628
+ precedence: "default",
1629
+ children: APPEARANCE_CHOICES_CSS
1630
+ }), /* @__PURE__ */ jsx(AppearanceChoices, {
1631
+ values: {
1632
+ defaultTheme: defaultTheme.value ?? "system",
1633
+ radius: radius.value ?? "rounded",
1634
+ shadow: shadow.value ?? "subtle",
1635
+ motion: motion.value ?? "smooth",
1636
+ density: density.value ?? "default"
1637
+ },
1638
+ onChange,
1639
+ readOnly
1640
+ })]
299
1641
  });
300
- if (level === "aa-large") return /* @__PURE__ */ jsx("svg", {
301
- viewBox: "0 0 16 16",
302
- width: "14",
303
- height: "14",
304
- "aria-hidden": "true",
305
- children: /* @__PURE__ */ jsx("path", {
306
- fill: "currentColor",
307
- d: "M3 8h10v1.5H3z"
308
- })
1642
+ };
1643
+ //#endregion
1644
+ //#region src/admin/grey-scale-field.tsx
1645
+ const FAMILIES = Object.keys(presetHints.greyScale);
1646
+ const SWATCHES = [
1647
+ "50",
1648
+ "200",
1649
+ "400",
1650
+ "700",
1651
+ "950"
1652
+ ];
1653
+ function GreyScaleChoices({ value, onChange, readOnly }) {
1654
+ return /* @__PURE__ */ jsxs("section", {
1655
+ className: "bl-grey",
1656
+ children: [/* @__PURE__ */ jsxs("header", {
1657
+ className: "bl-grey__head",
1658
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", { children: "Gray family" }), /* @__PURE__ */ jsx("p", { children: "Choose the neutral family used for page backgrounds, cards, borders, inputs, and muted text." })] }), /* @__PURE__ */ jsx("span", {
1659
+ className: "bl-grey__badge",
1660
+ children: "Required"
1661
+ })]
1662
+ }), /* @__PURE__ */ jsx("div", {
1663
+ className: "bl-grey__choices",
1664
+ children: FAMILIES.map((id) => {
1665
+ const copy = presetHints.greyScale[id];
1666
+ const selected = value === id;
1667
+ return /* @__PURE__ */ jsxs("label", {
1668
+ className: "bl-grey__choice",
1669
+ "data-selected": selected ? "" : void 0,
1670
+ children: [
1671
+ /* @__PURE__ */ jsx("input", {
1672
+ type: "radio",
1673
+ name: "greyScale",
1674
+ value: id,
1675
+ checked: selected,
1676
+ disabled: readOnly,
1677
+ onChange: () => onChange(id)
1678
+ }),
1679
+ /* @__PURE__ */ jsx("span", {
1680
+ className: "bl-grey__ramp",
1681
+ "aria-hidden": "true",
1682
+ children: SWATCHES.map((step) => /* @__PURE__ */ jsx("i", { style: { background: `hsl(${GREY_SCALES[id][step]})` } }, step))
1683
+ }),
1684
+ /* @__PURE__ */ jsxs("strong", { children: [copy.label, selected ? /* @__PURE__ */ jsx("em", {
1685
+ "aria-hidden": "true",
1686
+ children: "✓"
1687
+ }) : null] }),
1688
+ /* @__PURE__ */ jsx("span", { children: copy.hint })
1689
+ ]
1690
+ }, id);
1691
+ })
1692
+ })]
309
1693
  });
310
- return /* @__PURE__ */ jsx("svg", {
311
- viewBox: "0 0 16 16",
312
- width: "14",
313
- height: "14",
314
- "aria-hidden": "true",
315
- children: /* @__PURE__ */ jsx("path", {
316
- fill: "currentColor",
317
- d: "M8 1.2a6.8 6.8 0 1 1 0 13.6A6.8 6.8 0 0 1 8 1.2zm3.1 4.3L7.2 9.4 5 7.2l-.9.9 3.1 3.1 4.8-4.8-.9-.9z"
1694
+ }
1695
+ /**
1696
+ * Visual picker over the five grey-scale presets. Replaces the stock select
1697
+ * on Colors; the stored value is still `colors.greyScale`.
1698
+ */
1699
+ const GreyScaleField = ({ path, readOnly }) => {
1700
+ const { value, setValue } = useField({ path });
1701
+ return /* @__PURE__ */ jsxs("div", {
1702
+ className: "field-type bl-grey-field",
1703
+ children: [/* @__PURE__ */ jsx("style", {
1704
+ href: "bl-grey",
1705
+ precedence: "default",
1706
+ children: GREY_SCALE_FIELD_CSS
1707
+ }), /* @__PURE__ */ jsx(GreyScaleChoices, {
1708
+ value: value ?? "slate",
1709
+ onChange: setValue,
1710
+ readOnly
1711
+ })]
1712
+ });
1713
+ };
1714
+ const GREY_SCALE_FIELD_CSS = `
1715
+ .bl-grey{margin:0 0 1.75rem}
1716
+ .bl-grey__head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;margin-bottom:.85rem}
1717
+ .bl-grey__head h2{margin:0;font-size:1.1rem}
1718
+ .bl-grey__head p{margin:.35rem 0 0;max-width:40rem;font-size:13px;color:var(--theme-elevation-500,#64748b)}
1719
+ .bl-grey__badge{font:inherit;font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:.15rem .45rem;border-radius:999px;background:var(--theme-elevation-100,#f1f5f9);color:var(--theme-elevation-500,#64748b)}
1720
+ .bl-grey__choices{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:.6rem}
1721
+ .bl-grey__choice{display:flex;flex-direction:column;gap:.45rem;padding:.75rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff);cursor:pointer}
1722
+ .bl-grey__choice[data-selected]{box-shadow:inset 0 0 0 2px #0f172a;border-color:#0f172a}
1723
+ .bl-grey__choice input{position:absolute;opacity:0;pointer-events:none}
1724
+ .bl-grey__ramp{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:3px}
1725
+ .bl-grey__ramp i{display:block;height:2rem;border-radius:6px}
1726
+ .bl-grey__choice strong{display:flex;justify-content:space-between;align-items:center;gap:.4rem;font-size:14px}
1727
+ .bl-grey__choice em{font-style:normal}
1728
+ .bl-grey__choice > span{font-size:12px;color:var(--theme-elevation-500,#64748b)}
1729
+ [data-bl-theme-preview=dark] .bl-grey__head p,[data-bl-theme-preview=dark] .bl-grey__choice > span{color:#94a3b8}
1730
+ [data-bl-theme-preview=dark] .bl-grey__choice{background:#0f172a;border-color:#334155;color:#f8fafc}
1731
+ [data-bl-theme-preview=dark] .bl-grey__choice[data-selected]{box-shadow:inset 0 0 0 2px #f8fafc;border-color:#f8fafc}
1732
+ [data-bl-theme-preview=dark] .bl-grey__badge{background:#1e293b;color:#94a3b8}
1733
+ @media (max-width:720px){.bl-grey__choices{grid-template-columns:1fr}}
1734
+ `;
1735
+ //#endregion
1736
+ //#region src/admin/contrast-report.tsx
1737
+ /**
1738
+ * Contrast used to be a Colors-tab list. Readability now lives on each
1739
+ * color card: Needs attention opens a popup. This field stays in the
1740
+ * schema so `contrastTarget` is still a Theme option; it paints nothing.
1741
+ */
1742
+ const ContrastReport = () => null;
1743
+ //#endregion
1744
+ //#region src/admin/section-heading.tsx
1745
+ /**
1746
+ * Unnamed UI heading between Theme fields. No document path.
1747
+ */
1748
+ const SectionHeading = ({ field }) => {
1749
+ const custom = field.admin?.custom ?? {};
1750
+ if (!custom.title) return null;
1751
+ return /* @__PURE__ */ jsxs("header", {
1752
+ className: "bl-section-heading",
1753
+ children: [
1754
+ /* @__PURE__ */ jsx("style", {
1755
+ href: "bl-section-heading",
1756
+ precedence: "default",
1757
+ children: CSS$1
1758
+ }),
1759
+ /* @__PURE__ */ jsx("h2", { children: custom.title }),
1760
+ custom.hint ? /* @__PURE__ */ jsx("p", { children: custom.hint }) : null
1761
+ ]
1762
+ });
1763
+ };
1764
+ const CSS$1 = `
1765
+ .bl-section-heading{margin:1.25rem 0 .65rem}
1766
+ .bl-section-heading:first-child{margin-top:0}
1767
+ .bl-section-heading h2{margin:0;font-size:1.1rem}
1768
+ .bl-section-heading p{margin:.35rem 0 0;font-size:13px;color:var(--theme-elevation-500,#64748b);max-width:40rem}
1769
+ `;
1770
+ //#endregion
1771
+ //#region src/theme/changes.ts
1772
+ /** Payload may hand an empty array field as `{}`. `.map` on that crashes the Theme form. */
1773
+ function libraryRows(value) {
1774
+ return Array.isArray(value) ? value : [];
1775
+ }
1776
+ function colorLine(name, before, after) {
1777
+ const a = before && typeof before === "object" ? before : { hex: before };
1778
+ const b = after && typeof after === "object" ? after : { hex: after };
1779
+ const left = a;
1780
+ const right = b;
1781
+ if (left.hex === right.hex && String(left.sourceStep) === String(right.sourceStep)) return null;
1782
+ return `${name}: ${String(left.hex ?? "—")} → ${String(right.hex ?? "—")}`;
1783
+ }
1784
+ /**
1785
+ * Human lines for the Publish confirmation. Only this child's
1786
+ * differences; other responsibilities are not listed.
1787
+ */
1788
+ function themeChildChanges(child, published, incoming) {
1789
+ if (child === "colors") {
1790
+ const lines = [];
1791
+ for (const key of SYSTEM_COLOR_KEYS) {
1792
+ const line = colorLine(key[0].toUpperCase() + key.slice(1), published.colors?.brand?.[key], incoming.colors?.brand?.[key]);
1793
+ if (line) lines.push(line);
1794
+ }
1795
+ const beforeKeys = libraryRows(published.colors?.library).map((row) => row.key).join(", ");
1796
+ const afterKeys = libraryRows(incoming.colors?.library).map((row) => row.key).join(", ");
1797
+ if (beforeKeys !== afterKeys) lines.push(`Custom colors: ${beforeKeys || "none"} → ${afterKeys || "none"}`);
1798
+ if ((published.colors?.greyScale ?? "") !== (incoming.colors?.greyScale ?? "")) lines.push(`Gray family: ${published.colors?.greyScale ?? "—"} → ${incoming.colors?.greyScale ?? "—"}`);
1799
+ return lines;
1800
+ }
1801
+ if (child === "typography") {
1802
+ const lines = [];
1803
+ if ((published.typography?.heading ?? "") !== (incoming.typography?.heading ?? "")) lines.push(`Heading: ${published.typography?.heading || "same as body"} → ${incoming.typography?.heading || "same as body"}`);
1804
+ if ((published.typography?.body ?? "") !== (incoming.typography?.body ?? "")) lines.push(`Body: ${published.typography?.body ?? "—"} → ${incoming.typography?.body ?? "—"}`);
1805
+ return lines;
1806
+ }
1807
+ if (child === "appearance") return [
1808
+ "defaultTheme",
1809
+ "radius",
1810
+ "shadow",
1811
+ "motion",
1812
+ "density"
1813
+ ].flatMap((key) => {
1814
+ const left = published.appearance?.[key] ?? "";
1815
+ const right = incoming.appearance?.[key] ?? "";
1816
+ return left === right ? [] : [`${key}: ${left || "—"} → ${right || "—"}`];
1817
+ });
1818
+ const lines = [];
1819
+ if (uploadId(published.logo) !== uploadId(incoming.logo)) lines.push("Logo updated");
1820
+ if (uploadId(published.favicon) !== uploadId(incoming.favicon)) lines.push("Favicon updated");
1821
+ if (uploadId(published.logoMark) !== uploadId(incoming.logoMark)) lines.push("Mobile menu logo updated");
1822
+ return lines;
1823
+ }
1824
+ function uploadId(value) {
1825
+ if (value && typeof value === "object" && value.id != null) return String(value.id);
1826
+ if (typeof value === "string" || typeof value === "number") return String(value);
1827
+ return "";
1828
+ }
1829
+ //#endregion
1830
+ //#region src/theme/publish.ts
1831
+ const THEME_CHILDREN = [
1832
+ "colors",
1833
+ "typography",
1834
+ "appearance",
1835
+ "identity"
1836
+ ];
1837
+ function isThemeChild(value) {
1838
+ return typeof value === "string" && THEME_CHILDREN.includes(value);
1839
+ }
1840
+ //#endregion
1841
+ //#region src/admin/publish-confirm.tsx
1842
+ const OTHER_CHILDREN = {
1843
+ Colors: "Typography, Appearance, and Identity stay as they are.",
1844
+ Typography: "Colors, Appearance, and Identity stay as they are.",
1845
+ Appearance: "Colors, Typography, and Identity stay as they are.",
1846
+ Identity: "Colors, Typography, and Appearance stay as they are."
1847
+ };
1848
+ function PublishConfirm({ childLabel, changes, shorts, onKeepEditing, onPublish, busy }) {
1849
+ const remaining = shorts?.filter((item) => !item.meets) ?? [];
1850
+ return /* @__PURE__ */ jsx("div", {
1851
+ className: "bl-publish",
1852
+ role: "dialog",
1853
+ "aria-modal": "true",
1854
+ "aria-labelledby": "bl-publish-title",
1855
+ children: /* @__PURE__ */ jsxs("div", {
1856
+ className: "bl-publish__panel",
1857
+ children: [
1858
+ /* @__PURE__ */ jsxs("h2", {
1859
+ id: "bl-publish-title",
1860
+ children: ["Publish ", childLabel]
1861
+ }),
1862
+ /* @__PURE__ */ jsxs("p", { children: [
1863
+ "These ",
1864
+ childLabel.toLowerCase(),
1865
+ " changes go live now.",
1866
+ " ",
1867
+ OTHER_CHILDREN[childLabel] ?? "The other Theme sections stay as they are."
1868
+ ] }),
1869
+ changes.length === 0 ? /* @__PURE__ */ jsx("p", {
1870
+ className: "bl-publish__empty",
1871
+ children: "No changes to publish."
1872
+ }) : /* @__PURE__ */ jsx("ul", { children: changes.map((change) => /* @__PURE__ */ jsx("li", { children: change }, change)) }),
1873
+ remaining.length > 0 ? /* @__PURE__ */ jsxs("div", {
1874
+ className: "bl-publish__shorts",
1875
+ children: [
1876
+ /* @__PURE__ */ jsx("strong", { children: "Some colors may be hard to read" }),
1877
+ /* @__PURE__ */ jsx("ul", { children: remaining.map((item) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
1878
+ "aria-hidden": "true",
1879
+ className: "bl-publish__swatch",
1880
+ style: {
1881
+ background: hslCss(item.light.fill),
1882
+ color: hslCss(item.light.label)
1883
+ },
1884
+ children: "Aa"
1885
+ }), /* @__PURE__ */ jsxs("span", { children: [
1886
+ readabilityHeadline(item).replace(/^Text on /, ""),
1887
+ " ",
1888
+ hardToReadModes(item),
1889
+ "."
1890
+ ] })] }, item.id)) }),
1891
+ /* @__PURE__ */ jsx("p", { children: READABILITY_PUBLISH_NOTE })
1892
+ ]
1893
+ }) : null,
1894
+ /* @__PURE__ */ jsxs("div", {
1895
+ className: "bl-publish__actions",
1896
+ children: [/* @__PURE__ */ jsx("button", {
1897
+ type: "button",
1898
+ onClick: onKeepEditing,
1899
+ children: "Keep editing"
1900
+ }), /* @__PURE__ */ jsx("button", {
1901
+ type: "button",
1902
+ disabled: busy || changes.length === 0,
1903
+ onClick: onPublish,
1904
+ children: remaining.length > 0 ? "Publish anyway" : "Publish"
1905
+ })]
1906
+ })
1907
+ ]
318
1908
  })
319
1909
  });
320
1910
  }
1911
+ const PUBLISH_CONFIRM_CSS = `
1912
+ .bl-publish{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}
1913
+ .bl-publish__panel{width:min(36rem,100%);padding:1.25rem 1.35rem;border-radius:16px;background:#fff;color:#0f172a;box-shadow:0 24px 60px rgba(15,23,42,.2)}
1914
+ .bl-publish__panel h2{margin:0}
1915
+ .bl-publish__panel p{margin:.5rem 0 0;color:#475569}
1916
+ .bl-publish__empty{font-style:italic}
1917
+ .bl-publish__panel > ul{margin:.75rem 0 0;padding-left:1.1rem}
1918
+ .bl-publish__shorts{margin-top:1rem;padding:.85rem .9rem;border-radius:10px;background:#fffbeb}
1919
+ .bl-publish__shorts strong{display:block}
1920
+ .bl-publish__shorts ul{margin:.65rem 0 0;padding:0;list-style:none}
1921
+ .bl-publish__shorts li{display:flex;gap:.65rem;align-items:center;margin-top:.45rem;font-size:13px}
1922
+ .bl-publish__swatch{flex:none;display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:1.6rem;border-radius:6px;font-weight:700;font-size:12px}
1923
+ .bl-publish__shorts p{margin:.65rem 0 0;font-size:13px}
1924
+ .bl-publish__actions{display:flex;justify-content:flex-end;gap:.6rem;margin-top:1.1rem}
1925
+ .bl-publish__actions button{min-height:2.25rem;padding:.4rem .9rem;border-radius:8px;border:1px solid #cbd5e1;background:#fff;font:inherit;cursor:pointer}
1926
+ .bl-publish__actions button:last-child{border:0;background:#0f172a;color:#fff;font-weight:600}
1927
+ .bl-publish__actions button:disabled{opacity:.45;cursor:not-allowed}
1928
+ `;
1929
+ //#endregion
1930
+ //#region src/admin/theme-tab.ts
1931
+ const THEME_TAB_LABELS = {
1932
+ colors: "Colors",
1933
+ typography: "Typography",
1934
+ appearance: "Appearance",
1935
+ identity: "Identity"
1936
+ };
1937
+ /**
1938
+ * The selected Payload field tab on Theme. Defaults to Colors when the
1939
+ * tabs have not painted yet (SSR, first paint).
1940
+ */
1941
+ function readActiveThemeTab(root = typeof document === "undefined" ? null : document) {
1942
+ if (!root) return "colors";
1943
+ const text = (root.querySelector(".tabs-field__tab-button[aria-selected=\"true\"]") ?? root.querySelector(".tabs-field__tab-button--active") ?? root.querySelector("[role=\"tab\"][aria-selected=\"true\"]"))?.textContent?.replace(/\s+/g, " ").trim();
1944
+ return THEME_CHILDREN.find((id) => THEME_TAB_LABELS[id] === text) ?? "colors";
1945
+ }
1946
+ //#endregion
1947
+ //#region src/admin/publish-child.tsx
1948
+ function asColor(value) {
1949
+ if (value && typeof value === "object") return value;
1950
+ if (typeof value === "string") return { hex: value };
1951
+ }
1952
+ function formDoc(fields) {
1953
+ return {
1954
+ colors: {
1955
+ brand: Object.fromEntries(SYSTEM_COLOR_KEYS.map((key) => [key, {
1956
+ hex: fields[`colors.brand.${key}.hex`]?.value,
1957
+ sourceStep: fields[`colors.brand.${key}.sourceStep`]?.value
1958
+ }])),
1959
+ library: libraryRows(fields["colors.library"]?.value),
1960
+ greyScale: fields["colors.greyScale"]?.value
1961
+ },
1962
+ typography: {
1963
+ body: fields["typography.body"]?.value,
1964
+ heading: fields["typography.heading"]?.value
1965
+ },
1966
+ appearance: {
1967
+ defaultTheme: fields["appearance.defaultTheme"]?.value,
1968
+ radius: fields["appearance.radius"]?.value,
1969
+ shadow: fields["appearance.shadow"]?.value,
1970
+ motion: fields["appearance.motion"]?.value,
1971
+ density: fields["appearance.density"]?.value
1972
+ },
1973
+ logo: fields.logo?.value,
1974
+ favicon: fields.favicon?.value,
1975
+ logoMark: fields.logoMark?.value
1976
+ };
1977
+ }
1978
+ function publishedDoc(fields) {
1979
+ return formDoc(Object.fromEntries(Object.entries(fields).map(([path, field]) => [path, { value: field?.initialValue }])));
1980
+ }
1981
+ function shortsFrom(doc) {
1982
+ const brand = doc.colors?.brand ?? {};
1983
+ const custom = libraryRows(doc.colors?.library);
1984
+ return [...SYSTEM_COLOR_KEYS.flatMap((key) => {
1985
+ const hex = asColor(brand[key])?.hex;
1986
+ const warning = typeof hex === "string" ? fillLabelWarning(key, SYSTEM_COLOR_COPY[key].label, hex) : null;
1987
+ return warning ? [warning] : [];
1988
+ }), ...custom.flatMap((row, index) => {
1989
+ const warning = row.hex ? fillLabelWarning(`library-${index}`, row.label ?? row.key ?? "Custom", row.hex) : null;
1990
+ return warning ? [warning] : [];
1991
+ })];
1992
+ }
321
1993
  /**
322
- * Reads the five brand colours and grey scale from the form, runs
323
- * `auditTheme` client-side, and paints its own rows. Warn only: nothing
324
- * here touches validity.
1994
+ * Publish for the open Theme tab. Lives in Payload's document Save
1995
+ * slot (`ThemeSaveButton`) so the header matches a Pages document.
1996
+ * Sets `publishChild` then submits; `beforeChange` writes only that slice.
325
1997
  */
326
- const ContrastReport = ({ field }) => {
327
- const target = Number((field.admin?.custom)?.target) || 4.5;
328
- const values = useFormFields(([fields]) => {
1998
+ function ThemePublishControls({ child }) {
1999
+ const label = THEME_TAB_LABELS[child];
2000
+ const { submit } = useForm();
2001
+ const publishChild = useField({ path: "publishChild" });
2002
+ const snapshot = useFormFields(([fields]) => {
329
2003
  const record = fields;
330
2004
  return {
331
- primary: fieldValue(record, "brand.primary"),
332
- secondary: fieldValue(record, "brand.secondary"),
333
- accent: fieldValue(record, "brand.accent"),
334
- highlight: fieldValue(record, "brand.highlight"),
335
- success: fieldValue(record, "brand.success"),
336
- greyScale: fieldValue(record, "greyScale")
2005
+ incoming: formDoc(record),
2006
+ published: publishedDoc(record)
337
2007
  };
338
2008
  });
339
- const last = useRef(null);
340
- const complete = isCompleteBrand(values);
341
- if (complete) last.current = auditTheme({
342
- brandPrimary: values.primary,
343
- brandSecondary: values.secondary,
344
- brandAccent: values.accent,
345
- brandHighlight: values.highlight,
346
- brandSuccess: values.success,
347
- greyScale: values.greyScale
348
- }, { target });
349
- const checks = last.current;
350
- const stale = Boolean(checks) && !complete;
351
- const failing = checks?.filter((check) => !check.meetsTarget).length ?? 0;
352
- const summary = !checks ? "Enter five hex colours to measure contrast." : failing === 0 ? `Every pairing meets ${target}:1` : `${failing} of ${checks.length} pairings below ${target}:1`;
2009
+ const [open, setOpen] = useState(false);
2010
+ const [busy, setBusy] = useState(false);
2011
+ const changes = useMemo(() => themeChildChanges(child, snapshot.published, snapshot.incoming), [child, snapshot]);
2012
+ const shorts = child === "colors" ? shortsFrom(snapshot.incoming) : void 0;
2013
+ async function publish() {
2014
+ setBusy(true);
2015
+ publishChild.setValue(child);
2016
+ try {
2017
+ await submit();
2018
+ setOpen(false);
2019
+ } finally {
2020
+ setBusy(false);
2021
+ }
2022
+ }
353
2023
  return /* @__PURE__ */ jsxs("div", {
354
- className: "field-type bl-contrast-report",
2024
+ className: "bl-publish-child",
355
2025
  children: [
356
2026
  /* @__PURE__ */ jsx("style", {
357
- href: "bl-contrast-report",
2027
+ href: "bl-publish",
358
2028
  precedence: "default",
359
- children: CSS
2029
+ children: PUBLISH_CONFIRM_CSS
360
2030
  }),
361
- /* @__PURE__ */ jsx("p", {
362
- className: "bl-contrast-report__summary",
363
- children: summary
2031
+ /* @__PURE__ */ jsx("style", {
2032
+ href: "bl-publish-child",
2033
+ precedence: "default",
2034
+ children: `${THEME_PREVIEW_TOGGLE_CSS}.bl-publish-child{display:flex;align-items:center;gap:.75rem}.bl-publish-child__button{min-height:2.25rem;padding:.4rem 1rem;border:0;border-radius:8px;background:#a80063;color:#fff;font:inherit;font-weight:600;cursor:pointer}`
364
2035
  }),
365
- stale ? /* @__PURE__ */ jsx("p", {
366
- className: "bl-contrast-report__stale",
367
- children: "Showing the last complete audit while a colour is being typed."
368
- }) : null,
369
- checks ? /* @__PURE__ */ jsx("ul", {
370
- className: "bl-contrast-report__list",
371
- children: checks.flatMap((check) => ["light", "dark"].map((mode) => {
372
- const sample = check[mode];
373
- const failingRow = sample.ratio < check.target;
374
- const verdict = contrastVerdict(sample, check.target);
375
- return /* @__PURE__ */ jsxs("li", {
376
- className: "bl-contrast-report__row",
377
- "data-failing": failingRow ? "" : void 0,
378
- children: [
379
- /* @__PURE__ */ jsx("span", {
380
- "aria-hidden": "true",
381
- className: "bl-contrast-report__swatch",
382
- style: {
383
- color: `hsl(${sample.foreground})`,
384
- backgroundColor: `hsl(${sample.background})`
385
- },
386
- children: "Aa"
387
- }),
388
- /* @__PURE__ */ jsxs("span", {
389
- className: "bl-contrast-report__ratio",
390
- children: [sample.ratio.toFixed(2), ":1"]
391
- }),
392
- /* @__PURE__ */ jsxs("span", {
393
- className: "bl-contrast-report__level",
394
- children: [/* @__PURE__ */ jsx(LevelIcon, { level: sample.level }), contrastLevelLabel(sample.level)]
395
- }),
396
- /* @__PURE__ */ jsx("span", {
397
- className: "bl-contrast-report__verdict",
398
- children: verdict.text
399
- }),
400
- /* @__PURE__ */ jsxs("span", {
401
- className: "bl-contrast-report__label",
402
- children: [
403
- check.label,
404
- " (",
405
- mode === "light" ? "Light" : "Dark",
406
- ")"
407
- ]
408
- })
409
- ]
410
- }, `${check.id}-${mode}`);
411
- }))
2036
+ child === "colors" ? /* @__PURE__ */ jsx(ThemePreviewToggle, {}) : null,
2037
+ /* @__PURE__ */ jsxs("button", {
2038
+ type: "button",
2039
+ className: "bl-publish-child__button",
2040
+ onClick: () => setOpen(true),
2041
+ children: ["Publish ", label]
2042
+ }),
2043
+ open ? /* @__PURE__ */ jsx(PublishConfirm, {
2044
+ childLabel: label,
2045
+ changes,
2046
+ shorts,
2047
+ busy,
2048
+ onKeepEditing: () => setOpen(false),
2049
+ onPublish: () => {
2050
+ publish();
2051
+ }
412
2052
  }) : null
413
2053
  ]
414
2054
  });
2055
+ }
2056
+ /** @deprecated Theme publish is the document Save button */
2057
+ const PublishChild = ({ field }) => {
2058
+ const child = (field.admin?.custom)?.child;
2059
+ return /* @__PURE__ */ jsx(ThemePublishControls, { child: isThemeChild(child) ? child : "colors" });
2060
+ };
2061
+ //#endregion
2062
+ //#region src/admin/save-button.tsx
2063
+ /**
2064
+ * Payload's document Save slot — the same control row as Pages'
2065
+ * "Publish changes". Theme has no drafts; this publishes only the
2066
+ * open field tab. The tab is read after mount so SSR matches.
2067
+ */
2068
+ function ThemeSaveButton() {
2069
+ const [child, setChild] = useState("colors");
2070
+ useEffect(() => {
2071
+ const sync = () => setChild(readActiveThemeTab());
2072
+ sync();
2073
+ const target = document.querySelector(".tabs-field") ?? document.body;
2074
+ const observer = new MutationObserver(sync);
2075
+ observer.observe(target, {
2076
+ subtree: true,
2077
+ attributes: true,
2078
+ attributeFilter: ["aria-selected", "class"],
2079
+ childList: true
2080
+ });
2081
+ target.addEventListener("click", sync);
2082
+ return () => {
2083
+ observer.disconnect();
2084
+ target.removeEventListener("click", sync);
2085
+ };
2086
+ }, []);
2087
+ return /* @__PURE__ */ jsx(ThemePublishControls, { child });
2088
+ }
2089
+ /** Import-map alias — Theme still points `SaveButton` at this name. */
2090
+ const HiddenSaveButton = ThemeSaveButton;
2091
+ //#endregion
2092
+ //#region src/admin/identity-fallback.tsx
2093
+ /**
2094
+ * Identity child when the site has not registered a cupboard yet.
2095
+ * Shows the fallback lockup and mark; uploads wait on `logo.collection`.
2096
+ */
2097
+ const IdentityFallback = ({ field }) => {
2098
+ const fallback = (field.admin?.custom)?.fallback;
2099
+ return /* @__PURE__ */ jsxs("section", {
2100
+ className: "bl-identity-fallback",
2101
+ children: [
2102
+ /* @__PURE__ */ jsx("style", {
2103
+ href: "bl-identity-fallback",
2104
+ precedence: "default",
2105
+ children: CSS
2106
+ }),
2107
+ /* @__PURE__ */ jsxs("header", { children: [/* @__PURE__ */ jsx("h2", { children: "Site identity" }), /* @__PURE__ */ jsx("p", { children: "Today's lockup and mark until a brand editor uploads replacements." })] }),
2108
+ /* @__PURE__ */ jsxs("div", {
2109
+ className: "bl-identity-fallback__grid",
2110
+ children: [fallback?.lockup ? /* @__PURE__ */ jsxs("figure", { children: [/* @__PURE__ */ jsx("img", {
2111
+ src: fallback.lockup.url,
2112
+ alt: fallback.lockup.alt
2113
+ }), /* @__PURE__ */ jsx("figcaption", { children: "Lockup" })] }) : null, fallback?.mark ? /* @__PURE__ */ jsxs("figure", { children: [/* @__PURE__ */ jsx("img", {
2114
+ src: fallback.mark.url,
2115
+ alt: fallback.mark.alt
2116
+ }), /* @__PURE__ */ jsx("figcaption", { children: "Mark" })] }) : null]
2117
+ })
2118
+ ]
2119
+ });
415
2120
  };
416
2121
  const CSS = `
417
- .bl-contrast-report{margin:calc(var(--base) * .6) 0}
418
- .bl-contrast-report__summary{margin:0 0 calc(var(--base) * .3);font-weight:600}
419
- .bl-contrast-report__stale{margin:0 0 calc(var(--base) * .3);color:var(--theme-elevation-500);font-size:12px}
420
- .bl-contrast-report__list{margin:0;padding:0;list-style:none;border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:var(--theme-elevation-0)}
421
- .bl-contrast-report__row{display:flex;flex-wrap:wrap;align-items:center;gap:calc(var(--base) * .4);padding:calc(var(--base) * .35) calc(var(--base) * .6);border-top:1px solid var(--theme-elevation-100)}
422
- .bl-contrast-report__row:first-child{border-top:0}
423
- .bl-contrast-report__row[data-failing]{background:var(--theme-error-50)}
424
- .bl-contrast-report__swatch{flex:none;display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:1.75rem;border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);font-weight:600}
425
- .bl-contrast-report__ratio{width:4.5rem;font-variant-numeric:tabular-nums}
426
- .bl-contrast-report__level{display:inline-flex;align-items:center;gap:4px;width:5.5rem}
427
- .bl-contrast-report__verdict{width:6rem;font-weight:600}
428
- .bl-contrast-report__label{flex:1;min-width:12rem;color:var(--theme-elevation-500)}
2122
+ .bl-identity-fallback header h2{margin:0;font-size:1.1rem}
2123
+ .bl-identity-fallback header p{margin:.35rem 0 1rem;color:var(--theme-elevation-600,#475569)}
2124
+ .bl-identity-fallback__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:1rem}
2125
+ .bl-identity-fallback figure{margin:0;padding:1rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:10px;background:var(--theme-elevation-50,#f8fafc)}
2126
+ .bl-identity-fallback img{display:block;max-width:100%;height:4rem;object-fit:contain}
2127
+ .bl-identity-fallback figcaption{margin-top:.5rem;font-size:.8rem;color:var(--theme-elevation-600,#475569)}
429
2128
  `;
430
2129
  //#endregion
431
- export { ColorField, ContrastReport, FontField };
2130
+ export { AppearanceField, ColorField, ColorScaleField, ContrastReport, FontField, GreyScaleField, HiddenSaveButton, IdentityFallback, LibraryField, PairingField, PublishChild, SectionHeading, ThemeSaveButton };
432
2131
 
433
2132
  //# sourceMappingURL=admin.mjs.map