@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/README.md +49 -49
- package/dist/admin.d.mts +71 -6
- package/dist/admin.d.mts.map +1 -1
- package/dist/admin.mjs +1877 -178
- package/dist/admin.mjs.map +1 -1
- package/dist/identity-Bg-G05mb.mjs +199 -0
- package/dist/identity-Bg-G05mb.mjs.map +1 -0
- package/dist/index.d.mts +62 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +507 -90
- package/dist/index.mjs.map +1 -1
- package/dist/library-BGq4wGif.d.mts +189 -0
- package/dist/library-BGq4wGif.d.mts.map +1 -0
- package/dist/map-BUDkX8g1.mjs +111 -0
- package/dist/map-BUDkX8g1.mjs.map +1 -0
- package/dist/react.mjs +3 -58
- package/dist/react.mjs.map +1 -1
- package/dist/theme.d.mts +12 -7
- package/dist/theme.d.mts.map +1 -1
- package/dist/theme.mjs +19 -6
- package/dist/theme.mjs.map +1 -1
- package/package.json +5 -4
- package/dist/types-DWwL-JEr.mjs +0 -76
- package/dist/types-DWwL-JEr.mjs.map +0 -1
- package/dist/types-pFpmDeSA.d.mts +0 -91
- package/dist/types-pFpmDeSA.d.mts.map +0 -1
package/dist/admin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.mjs","names":["CSS","CSS"],"sources":["../src/admin/color-field.tsx","../src/admin/font-field.tsx","../src/admin/contrast-report.tsx"],"sourcesContent":["import { FieldDescription, FieldError, FieldLabel, useField } from \"@payloadcms/ui\";\nimport { isThemeHex } from \"@bison-lab/tokens\";\nimport type { TextFieldClientComponent } from \"payload\";\nimport { useEffect, useId, useState } from \"react\";\n\nconst HEX_ERROR = \"Enter a six-digit hex colour like #1e3a5f\";\n\n/**\n * Swatch, native colour input, and hex text, kept in step. `#rrggbb` on\n * blur; the field's `validate` refuses anything else on save.\n */\nexport const ColorField: TextFieldClientComponent = ({ field, path, readOnly }) => {\n const { value, setValue, showError, errorMessage } = useField<string>({ path });\n const [text, setText] = useState(value ?? \"\");\n const [localError, setLocalError] = useState<string | null>(null);\n const id = useId();\n const hexId = `${id}-hex`;\n const pickerId = `${id}-picker`;\n const required = Boolean(field.required);\n const valid = isThemeHex(value);\n const message = localError ?? errorMessage;\n const show = Boolean(localError) || showError;\n\n useEffect(() => {\n setText(value ?? \"\");\n }, [value]);\n\n function commit(next: string) {\n const trimmed = next.trim();\n if (isThemeHex(trimmed)) {\n setValue(trimmed);\n setLocalError(null);\n return;\n }\n setLocalError(HEX_ERROR);\n }\n\n return (\n <div className=\"field-type bl-color-field\">\n <style href=\"bl-color-field\" precedence=\"default\">\n {CSS}\n </style>\n <FieldLabel htmlFor={hexId} label={field.label} required={required} />\n <div className=\"bl-color-field__control\">\n <span\n className=\"bl-color-field__swatch\"\n aria-hidden=\"true\"\n style={{ background: valid ? value : \"transparent\" }}\n />\n <input\n id={pickerId}\n type=\"color\"\n className=\"bl-color-field__picker\"\n disabled={readOnly}\n value={valid ? value : \"#000000\"}\n aria-label={`${typeof field.label === \"string\" ? field.label : \"Colour\"} picker`}\n onChange={(event) => {\n setValue(event.target.value);\n setText(event.target.value);\n setLocalError(null);\n }}\n />\n <input\n id={hexId}\n type=\"text\"\n className=\"bl-color-field__hex\"\n spellCheck={false}\n autoComplete=\"off\"\n disabled={readOnly}\n value={text}\n onChange={(event) => {\n setText(event.target.value);\n if (isThemeHex(event.target.value.trim())) {\n setValue(event.target.value.trim());\n setLocalError(null);\n }\n }}\n onBlur={() => commit(text)}\n />\n </div>\n <FieldDescription description={field.admin?.description} path={path} />\n <FieldError path={path} showError={show} message={message} />\n </div>\n );\n};\n\nconst CSS = `\n.bl-color-field__control{display:flex;align-items:center;gap:calc(var(--base) * .4)}\n.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)}\n.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}\n.bl-color-field__picker:disabled{cursor:not-allowed;opacity:.6}\n.bl-color-field__hex{flex:1;min-width:0;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-family:var(--font-mono);font-size:13px}\n.bl-color-field__hex:focus{border-color:var(--theme-elevation-400);outline:none;box-shadow:0 0 0 1px var(--theme-elevation-400)}\n.bl-color-field__hex:disabled{color:var(--theme-elevation-400)}\n`;\n","import { FieldDescription, FieldError, FieldLabel, useField } from \"@payloadcms/ui\";\nimport { findFont, fontFaceCss, type FontId } from \"@bison-lab/fonts\";\nimport type { SelectFieldClientComponent } from \"payload\";\nimport { useEffect, useId, useMemo, useRef, useState } from \"react\";\n\nimport { SAME_AS_BODY } from \"../theme/fields\";\n\ntype Option = { value: string; family: string; hint: string; specimen: boolean };\n\n/**\n * Listbox over the catalogue ids in `admin.custom.ids`. Each option is the\n * family name in that face, with the catalogue hint beneath. Faces load\n * from the site's font route the first time the picker opens.\n */\nexport const FontField: SelectFieldClientComponent = ({ field, path, readOnly }) => {\n const { value, setValue, showError, errorMessage } = useField<string>({ path });\n const custom = (field.admin?.custom ?? {}) as {\n fontsBaseUrl?: string;\n ids?: string[];\n sameAsBody?: boolean;\n };\n const ids = custom.ids ?? [];\n const fontsBaseUrl = custom.fontsBaseUrl ?? \"/fonts\";\n const sameAsBody = Boolean(custom.sameAsBody);\n\n const options = useMemo<Option[]>(() => {\n const fromCatalogue: Option[] = ids.flatMap((id) => {\n const font = findFont(id);\n if (!font) return [];\n return [{ value: font.id, family: font.family, hint: font.hint, specimen: true }];\n });\n if (!sameAsBody) return fromCatalogue;\n return [{ value: SAME_AS_BODY, family: \"Same as body\", hint: \"Use the body family for headings\", specimen: false }, ...fromCatalogue];\n }, [ids, sameAsBody]);\n\n const [open, setOpen] = useState(false);\n const [faces, setFaces] = useState(false);\n const [active, setActive] = useState(-1);\n const typed = useRef(\"\");\n const typedAt = useRef(0);\n const id = useId();\n const buttonId = `${id}-button`;\n const listId = `${id}-list`;\n const required = Boolean(field.required);\n\n useEffect(() => {\n if (open) setFaces(true);\n }, [open]);\n\n const selected = options.find((option) => option.value === (value ?? SAME_AS_BODY)) ?? options[0];\n\n function choose(option: Option) {\n setValue(option.value);\n setOpen(false);\n setActive(-1);\n }\n\n function onKeyDown(event: React.KeyboardEvent<HTMLButtonElement>) {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n if (!open) {\n setOpen(true);\n setActive(0);\n return;\n }\n setActive((i) => (options.length ? (i + 1) % options.length : -1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n if (!open) {\n setOpen(true);\n setActive(options.length - 1);\n return;\n }\n setActive((i) => (options.length ? (i <= 0 ? options.length - 1 : i - 1) : -1));\n } else if (event.key === \"Enter\") {\n if (!open) return;\n event.preventDefault();\n const pick = options[active] ?? options[0];\n if (pick) choose(pick);\n } else if (event.key === \"Escape\") {\n if (!open) return;\n event.preventDefault();\n event.stopPropagation();\n setOpen(false);\n setActive(-1);\n } else if (event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\n const now = Date.now();\n typed.current = now - typedAt.current > 600 ? event.key : typed.current + event.key;\n typedAt.current = now;\n const needle = typed.current.toLowerCase();\n const index = options.findIndex((option) => option.family.toLowerCase().startsWith(needle));\n if (index >= 0) {\n if (!open) setOpen(true);\n setActive(index);\n }\n }\n }\n\n const activeId = active >= 0 && open ? `${id}-option-${active}` : undefined;\n\n return (\n <div className=\"field-type bl-font-field\">\n <style href=\"bl-font-field\" precedence=\"default\">\n {CSS}\n </style>\n {faces ? <style data-bl-font-faces=\"\">{fontFaceCss(ids as FontId[], fontsBaseUrl)}</style> : null}\n <FieldLabel htmlFor={buttonId} label={field.label} required={required} />\n <div className=\"bl-font-field__control\">\n <button\n id={buttonId}\n type=\"button\"\n className=\"bl-font-field__trigger\"\n disabled={readOnly}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listId}\n aria-activedescendant={activeId}\n onClick={() => setOpen((was) => !was)}\n onBlur={() => setOpen(false)}\n onKeyDown={onKeyDown}\n >\n {selected?.family ?? \"Choose a font\"}\n </button>\n {open ? (\n <ul id={listId} role=\"listbox\" className=\"bl-font-field__menu\">\n {options.map((option, i) => (\n <li\n key={option.value || \"same\"}\n id={`${id}-option-${i}`}\n role=\"option\"\n aria-selected={option.value === (value ?? SAME_AS_BODY)}\n className=\"bl-font-field__option\"\n onMouseDown={(event) => {\n event.preventDefault();\n choose(option);\n }}\n onMouseEnter={() => setActive(i)}\n >\n <span\n className=\"bl-font-field__family\"\n style={option.specimen ? { fontFamily: `\"${option.family}\"` } : undefined}\n >\n {option.family}\n </span>\n <span className=\"bl-font-field__hint\">{option.hint}</span>\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n <FieldDescription description={field.admin?.description} path={path} />\n <FieldError path={path} showError={showError} message={errorMessage} />\n </div>\n );\n};\n\nconst CSS = `\n.bl-font-field__control{position:relative}\n.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}\n.bl-font-field__trigger:focus{border-color:var(--theme-elevation-400);outline:none;box-shadow:0 0 0 1px var(--theme-elevation-400)}\n.bl-font-field__trigger:disabled{color:var(--theme-elevation-400);cursor:not-allowed}\n.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)}\n.bl-font-field__option{display:flex;flex-direction:column;gap:2px;padding:calc(var(--base) * .35) calc(var(--base) * .6);cursor:pointer}\n.bl-font-field__option[aria-selected=\"true\"],.bl-font-field__option:hover{background:var(--theme-elevation-100)}\n.bl-font-field__family{font-weight:500}\n.bl-font-field__hint{color:var(--theme-elevation-500);font-size:12px}\n`;\n","import { useFormFields } from \"@payloadcms/ui\";\nimport {\n auditTheme,\n contrastLevelLabel,\n contrastVerdict,\n isThemeHex,\n type ContrastCheck,\n type ContrastLevel,\n type GreyScale,\n} from \"@bison-lab/tokens\";\nimport type { UIFieldClientComponent } from \"payload\";\nimport { useRef } from \"react\";\n\nfunction fieldValue(\n fields: Record<string, { value?: unknown } | undefined>,\n path: string,\n): unknown {\n return fields[path]?.value;\n}\n\ntype BrandFormValues = {\n primary: unknown;\n secondary: unknown;\n accent: unknown;\n highlight: unknown;\n success: unknown;\n greyScale: unknown;\n};\n\nfunction isCompleteBrand(values: BrandFormValues): values is BrandFormValues & {\n primary: string;\n secondary: string;\n accent: string;\n highlight: string;\n success: string;\n greyScale: GreyScale;\n} {\n return (\n isThemeHex(values.primary) &&\n isThemeHex(values.secondary) &&\n isThemeHex(values.accent) &&\n isThemeHex(values.highlight) &&\n isThemeHex(values.success) &&\n typeof values.greyScale === \"string\"\n );\n}\n\nfunction LevelIcon({ level }: { level: ContrastLevel }) {\n if (level === \"fail\") {\n return (\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path\n fill=\"currentColor\"\n 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\"\n />\n </svg>\n );\n }\n if (level === \"aa-large\") {\n return (\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M3 8h10v1.5H3z\" />\n </svg>\n );\n }\n return (\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path\n fill=\"currentColor\"\n 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\"\n />\n </svg>\n );\n}\n\n/**\n * Reads the five brand colours and grey scale from the form, runs\n * `auditTheme` client-side, and paints its own rows. Warn only: nothing\n * here touches validity.\n */\nexport const ContrastReport: UIFieldClientComponent = ({ field }) => {\n const target = Number((field.admin?.custom as { target?: number } | undefined)?.target) || 4.5;\n const values = useFormFields(([fields]) => {\n const record = fields as Record<string, { value?: unknown } | undefined>;\n return {\n primary: fieldValue(record, \"brand.primary\"),\n secondary: fieldValue(record, \"brand.secondary\"),\n accent: fieldValue(record, \"brand.accent\"),\n highlight: fieldValue(record, \"brand.highlight\"),\n success: fieldValue(record, \"brand.success\"),\n greyScale: fieldValue(record, \"greyScale\"),\n };\n });\n\n const last = useRef<ContrastCheck[] | null>(null);\n const complete = isCompleteBrand(values);\n if (complete) {\n last.current = auditTheme(\n {\n brandPrimary: values.primary,\n brandSecondary: values.secondary,\n brandAccent: values.accent,\n brandHighlight: values.highlight,\n brandSuccess: values.success,\n greyScale: values.greyScale,\n },\n { target },\n );\n }\n\n const checks = last.current;\n const stale = Boolean(checks) && !complete;\n const failing = checks?.filter((check) => !check.meetsTarget).length ?? 0;\n const summary = !checks\n ? \"Enter five hex colours to measure contrast.\"\n : failing === 0\n ? `Every pairing meets ${target}:1`\n : `${failing} of ${checks.length} pairings below ${target}:1`;\n\n return (\n <div className=\"field-type bl-contrast-report\">\n <style href=\"bl-contrast-report\" precedence=\"default\">\n {CSS}\n </style>\n <p className=\"bl-contrast-report__summary\">{summary}</p>\n {stale ? (\n <p className=\"bl-contrast-report__stale\">Showing the last complete audit while a colour is being typed.</p>\n ) : null}\n {checks ? (\n <ul className=\"bl-contrast-report__list\">\n {checks.flatMap((check) =>\n ([\"light\", \"dark\"] as const).map((mode) => {\n const sample = check[mode];\n const failingRow = sample.ratio < check.target;\n const verdict = contrastVerdict(sample, check.target);\n return (\n <li\n key={`${check.id}-${mode}`}\n className=\"bl-contrast-report__row\"\n data-failing={failingRow ? \"\" : undefined}\n >\n <span\n aria-hidden=\"true\"\n className=\"bl-contrast-report__swatch\"\n style={{\n color: `hsl(${sample.foreground})`,\n backgroundColor: `hsl(${sample.background})`,\n }}\n >\n Aa\n </span>\n <span className=\"bl-contrast-report__ratio\">{sample.ratio.toFixed(2)}:1</span>\n <span className=\"bl-contrast-report__level\">\n <LevelIcon level={sample.level} />\n {contrastLevelLabel(sample.level)}\n </span>\n <span className=\"bl-contrast-report__verdict\">{verdict.text}</span>\n <span className=\"bl-contrast-report__label\">\n {check.label} ({mode === \"light\" ? \"Light\" : \"Dark\"})\n </span>\n </li>\n );\n }),\n )}\n </ul>\n ) : null}\n </div>\n );\n};\n\nconst CSS = `\n.bl-contrast-report{margin:calc(var(--base) * .6) 0}\n.bl-contrast-report__summary{margin:0 0 calc(var(--base) * .3);font-weight:600}\n.bl-contrast-report__stale{margin:0 0 calc(var(--base) * .3);color:var(--theme-elevation-500);font-size:12px}\n.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)}\n.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)}\n.bl-contrast-report__row:first-child{border-top:0}\n.bl-contrast-report__row[data-failing]{background:var(--theme-error-50)}\n.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}\n.bl-contrast-report__ratio{width:4.5rem;font-variant-numeric:tabular-nums}\n.bl-contrast-report__level{display:inline-flex;align-items:center;gap:4px;width:5.5rem}\n.bl-contrast-report__verdict{width:6rem;font-weight:600}\n.bl-contrast-report__label{flex:1;min-width:12rem;color:var(--theme-elevation-500)}\n`;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"admin.mjs","names":["CSS","CSS","CSS","CSS"],"sources":["../src/admin/color-field.tsx","../src/theme/appearance-labels.ts","../src/theme/readability.ts","../src/admin/preview-mode.tsx","../src/admin/readability-list.tsx","../src/admin/color-scale-card.tsx","../src/admin/color-scale-field.tsx","../src/theme/types.ts","../src/theme/library.ts","../src/admin/library-field.tsx","../src/admin/typography-pairing.tsx","../src/admin/font-field.tsx","../src/admin/pairing-field.tsx","../src/admin/appearance-choices.tsx","../src/admin/appearance-field.tsx","../src/admin/grey-scale-field.tsx","../src/admin/contrast-report.tsx","../src/admin/section-heading.tsx","../src/theme/changes.ts","../src/theme/publish.ts","../src/admin/publish-confirm.tsx","../src/admin/theme-tab.ts","../src/admin/publish-child.tsx","../src/admin/save-button.tsx","../src/admin/identity-fallback.tsx"],"sourcesContent":["import { FieldDescription, FieldError, FieldLabel, useField } from \"@payloadcms/ui\";\nimport { isThemeHex } from \"@bison-lab/tokens\";\nimport type { TextFieldClientComponent } from \"payload\";\nimport { useEffect, useId, useState } from \"react\";\n\nconst HEX_ERROR = \"Enter a six-digit hex colour like #1e3a5f\";\n\n/**\n * Swatch, native colour input, and hex text, kept in step. `#rrggbb` on\n * blur; the field's `validate` refuses anything else on save.\n */\nexport const ColorField: TextFieldClientComponent = ({ field, path, readOnly }) => {\n const { value, setValue, showError, errorMessage } = useField<string>({ path });\n const [text, setText] = useState(value ?? \"\");\n const [localError, setLocalError] = useState<string | null>(null);\n const id = useId();\n const hexId = `${id}-hex`;\n const pickerId = `${id}-picker`;\n const required = Boolean(field.required);\n const valid = isThemeHex(value);\n const message = localError ?? errorMessage;\n const show = Boolean(localError) || showError;\n\n useEffect(() => {\n setText(value ?? \"\");\n }, [value]);\n\n function commit(next: string) {\n const trimmed = next.trim();\n if (isThemeHex(trimmed)) {\n setValue(trimmed);\n setLocalError(null);\n return;\n }\n setLocalError(HEX_ERROR);\n }\n\n return (\n <div className=\"field-type bl-color-field\">\n <style href=\"bl-color-field\" precedence=\"default\">\n {CSS}\n </style>\n <FieldLabel htmlFor={hexId} label={field.label} required={required} />\n <div className=\"bl-color-field__control\">\n <span\n className=\"bl-color-field__swatch\"\n aria-hidden=\"true\"\n style={{ background: valid ? value : \"transparent\" }}\n />\n <input\n id={pickerId}\n type=\"color\"\n className=\"bl-color-field__picker\"\n disabled={readOnly}\n value={valid ? value : \"#000000\"}\n aria-label={`${typeof field.label === \"string\" ? field.label : \"Colour\"} picker`}\n onChange={(event) => {\n setValue(event.target.value);\n setText(event.target.value);\n setLocalError(null);\n }}\n />\n <input\n id={hexId}\n type=\"text\"\n className=\"bl-color-field__hex\"\n spellCheck={false}\n autoComplete=\"off\"\n disabled={readOnly}\n value={text}\n onChange={(event) => {\n setText(event.target.value);\n if (isThemeHex(event.target.value.trim())) {\n setValue(event.target.value.trim());\n setLocalError(null);\n }\n }}\n onBlur={() => commit(text)}\n />\n </div>\n <FieldDescription description={field.admin?.description} path={path} />\n <FieldError path={path} showError={show} message={message} />\n </div>\n );\n};\n\nconst CSS = `\n.bl-color-field__control{display:flex;align-items:center;gap:calc(var(--base) * .4)}\n.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)}\n.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}\n.bl-color-field__picker:disabled{cursor:not-allowed;opacity:.6}\n.bl-color-field__hex{flex:1;min-width:0;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-family:var(--font-mono);font-size:13px}\n.bl-color-field__hex:focus{border-color:var(--theme-elevation-400);outline:none;box-shadow:0 0 0 1px var(--theme-elevation-400)}\n.bl-color-field__hex:disabled{color:var(--theme-elevation-400)}\n`;\n","/**\n * Named choices the Appearance child shows. Values stay the token presets;\n * labels match the mock workflow (Soft → pill, Gentle → minimal, Balanced\n * → default, Follow device → system). No rem, ms, or CSS variable tables.\n */\nexport const APPEARANCE_CHOICES = {\n defaultTheme: {\n light: { label: \"Light\", hint: \"Always opens in light mode\" },\n dark: { label: \"Dark\", hint: \"Always opens in dark mode\" },\n system: { label: \"Follow device\", hint: \"Matches the visitor’s system setting\" },\n },\n radius: {\n sharp: { label: \"Sharp\", hint: \"Crisp and structured\" },\n subtle: { label: \"Subtle\", hint: \"Clean with a little softness\" },\n rounded: { label: \"Rounded\", hint: \"Friendly and contemporary\" },\n pill: { label: \"Soft\", hint: \"Very rounded and expressive\" },\n },\n shadow: {\n flat: { label: \"Flat\", hint: \"Minimal visual depth\" },\n subtle: { label: \"Subtle\", hint: \"Light separation between surfaces\" },\n elevated: { label: \"Elevated\", hint: \"More noticeable layering and depth\" },\n },\n motion: {\n snappy: { label: \"Quick\", hint: \"Fast and responsive\" },\n smooth: { label: \"Smooth\", hint: \"Balanced and natural\" },\n minimal: { label: \"Gentle\", hint: \"Slower and more relaxed\" },\n },\n density: {\n compact: { label: \"Compact\", hint: \"Fits more content on screen\" },\n default: { label: \"Balanced\", hint: \"Comfortable for most sites\" },\n spacious: { label: \"Spacious\", hint: \"More breathing room and larger controls\" },\n },\n} as const;\n\nexport const APPEARANCE_FAMILY_COPY: Record<\n keyof typeof APPEARANCE_CHOICES,\n { title: string; hint: string }\n> = {\n defaultTheme: {\n title: \"Default theme\",\n hint: \"Choose how the public site opens for visitors.\",\n },\n radius: {\n title: \"Corner style\",\n hint: \"Controls the shape of buttons, inputs, cards, and panels across the site.\",\n },\n shadow: {\n title: \"Depth\",\n hint: \"Choose how much cards and floating elements stand out from the page.\",\n },\n motion: {\n title: \"Motion\",\n hint: \"Controls how quickly menus, cards, and interface transitions move.\",\n },\n density: {\n title: \"Spacing\",\n hint: \"Choose how compact or roomy forms, buttons, cards, and page sections feel.\",\n },\n};\n\nexport const FONT_ROLE_COPY = {\n heading: {\n label: \"Heading font\",\n hint: \"Used for page titles, section headings, and cards\",\n },\n body: {\n label: \"Body font\",\n hint: \"Used for paragraphs, navigation, forms, and buttons\",\n },\n} as const;\n\nexport const SYSTEM_COLOR_COPY: Record<\n \"primary\" | \"secondary\" | \"accent\" | \"highlight\" | \"success\" | \"destructive\",\n { label: string; hint: string; group: \"brand\" | \"status\" }\n> = {\n primary: { label: \"Primary\", hint: \"Primary actions and key links\", group: \"brand\" },\n secondary: { label: \"Secondary\", hint: \"Supporting accents and eyebrows\", group: \"brand\" },\n accent: { label: \"Accent\", hint: \"Dark brand surfaces\", group: \"brand\" },\n highlight: { label: \"Highlight\", hint: \"Promotional and emphasis fills\", group: \"brand\" },\n success: {\n label: \"Success\",\n hint: \"Theme chrome for confirmations. Not offered as a page-editor fill.\",\n group: \"status\",\n },\n destructive: {\n label: \"Destructive\",\n hint: \"Theme chrome for errors and dangerous actions. Not offered as a page-editor fill.\",\n group: \"status\",\n },\n};\n","import {\n contrastForeground,\n contrastRatio,\n hexToHSL,\n hslToString,\n isThemeHex,\n type HSL,\n} from \"@bison-lab/tokens\";\n\n/** Same threshold `deriveDarkPalette` uses for brand fills. */\nconst DARK_FILL_LIGHTEN_BELOW = 40;\nconst DARK_FILL_LIGHTEN_BY = 25;\n\nexport const READABILITY_TARGET = 7;\n\nexport interface FillLabelSample {\n ratio: number;\n shortfall: number;\n fill: string;\n label: string;\n}\n\nexport interface FillLabelWarning {\n id: string;\n name: string;\n light: FillLabelSample;\n dark: FillLabelSample;\n /** Both modes at or above the target. */\n meets: boolean;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction darkFill(color: HSL): HSL {\n if (color.l < DARK_FILL_LIGHTEN_BELOW) {\n return { ...color, l: clamp(color.l + DARK_FILL_LIGHTEN_BY, 0, 100) };\n }\n return color;\n}\n\nfunction sample(fill: HSL, target: number): FillLabelSample {\n const label = contrastForeground(fill);\n const fillCss = hslToString(fill);\n const ratio = contrastRatio(fillCss, label);\n return {\n ratio,\n shortfall: Math.max(0, Math.round((target - ratio) * 100) / 100),\n fill: fillCss,\n label,\n };\n}\n\n/**\n * Editable fill + the automatic label, Light and Dark. Target 7:1.\n * Meets only when both pass. Warnings do not block save.\n */\nexport function fillLabelWarning(\n id: string,\n name: string,\n hex: string,\n target = READABILITY_TARGET,\n): FillLabelWarning | null {\n if (!isThemeHex(hex)) return null;\n const lightFill = hexToHSL(hex);\n const light = sample(lightFill, target);\n const dark = sample(darkFill(lightFill), target);\n return { id, name, light, dark, meets: light.ratio >= target && dark.ratio >= target };\n}\n\n/** Green/teal family. A warning at admin time, not a schema block. */\nexport function successHueWarning(hex: string): string | null {\n if (!isThemeHex(hex)) return null;\n const { h } = hexToHSL(hex);\n if (h >= 80 && h <= 180) return null;\n return \"Keep success colors recognizable — stay in a green or teal family.\";\n}\n\n/** Red family. A warning at admin time, not a schema block. */\nexport function destructiveHueWarning(hex: string): string | null {\n if (!isThemeHex(hex)) return null;\n const { h } = hexToHSL(hex);\n if (h <= 30 || h >= 330) return null;\n return \"Keep destructive colors recognizable — stay in a red family.\";\n}\n\nexport function automaticLabel(hex: string): string | null {\n if (!isThemeHex(hex)) return null;\n return contrastForeground(hexToHSL(hex));\n}\n\nexport function hslCss(value: string): string {\n return `hsl(${value})`;\n}\n\n/** Which appearances fail, in words. Empty when both pass. */\nexport function hardToReadModes(item: FillLabelWarning): string {\n const light = item.light.shortfall > 0;\n const dark = item.dark.shortfall > 0;\n if (light && dark) return \"in Light and Dark\";\n if (light) return \"in Light\";\n if (dark) return \"in Dark\";\n return \"\";\n}\n\nexport function readabilityHeadline(item: FillLabelWarning): string {\n return item.meets ? `Text on ${item.name} looks readable` : `Text on ${item.name} may be hard to read`;\n}\n\nexport function readabilityModesLine(item: FillLabelWarning): string {\n const modes = hardToReadModes(item);\n return modes ? `Hard to read ${modes}.` : \"Looks good in Light and Dark.\";\n}\n\nexport function readabilityGapPercent(sample: FillLabelSample, target = READABILITY_TARGET): number {\n if (sample.shortfall <= 0) return 0;\n return Math.round((sample.shortfall / target) * 100);\n}\n\nexport function readabilityRatioLine(sample: FillLabelSample, target = READABILITY_TARGET): string {\n const percent = readabilityGapPercent(sample, target);\n if (percent <= 0) return \"Meets the contrast we want\";\n if (percent < 10) return \"A little short of the contrast we want\";\n return `About ${percent}% short of the contrast we want`;\n}\n\nexport const READABILITY_FIX =\n \"Darken or lighten the hex, then regenerate the scale. The site picks light or dark type for you.\";\n\nexport const READABILITY_PUBLISH_NOTE =\n \"You can still publish. To improve a color, darken or lighten it and regenerate.\";\n","import { useEffect, useState } from \"react\";\n\nexport type ThemePreviewMode = \"light\" | \"dark\";\n\nconst ATTR = \"data-bl-theme-preview\";\nconst EVENT = \"bl-theme-preview\";\n\nfunction adminIsDark(): boolean {\n if (typeof document === \"undefined\") return false;\n const root = document.documentElement;\n return (\n root.getAttribute(\"data-theme\") === \"dark\" ||\n root.getAttribute(\"data-theme-pref\") === \"dark\" ||\n root.classList.contains(\"dark\")\n );\n}\n\nfunction readMode(): ThemePreviewMode {\n if (typeof document === \"undefined\") return \"light\";\n const set = document.documentElement.getAttribute(ATTR);\n if (set === \"dark\" || set === \"light\") return set;\n return adminIsDark() ? \"dark\" : \"light\";\n}\n\nexport function setThemePreviewMode(mode: ThemePreviewMode) {\n if (typeof document === \"undefined\") return;\n document.documentElement.setAttribute(ATTR, mode);\n window.dispatchEvent(new Event(EVENT));\n}\n\nexport function useThemePreviewMode(): ThemePreviewMode {\n // Always start on light so SSR and the first client paint match.\n // Admin dark is applied after mount — reading `document` here hydrates\n // `data-on` as \"\" on the server and undefined in the browser.\n const [mode, setMode] = useState<ThemePreviewMode>(\"light\");\n useEffect(() => {\n const sync = () => setMode(readMode());\n sync();\n window.addEventListener(EVENT, sync);\n return () => window.removeEventListener(EVENT, sync);\n }, []);\n return mode;\n}\n\nexport function ThemePreviewToggle() {\n const mode = useThemePreviewMode();\n return (\n <div className=\"bl-preview-toggle\" role=\"group\" aria-label=\"Preview fills in\">\n <span className=\"bl-preview-toggle__label\">Preview fills</span>\n <div className=\"bl-preview-toggle__pills\">\n <button type=\"button\" aria-pressed={mode === \"light\"} onClick={() => setThemePreviewMode(\"light\")}>\n Light\n </button>\n <button type=\"button\" aria-pressed={mode === \"dark\"} onClick={() => setThemePreviewMode(\"dark\")}>\n Dark\n </button>\n </div>\n </div>\n );\n}\n\nexport const THEME_PREVIEW_TOGGLE_CSS = `\n.bl-preview-toggle{display:inline-flex;align-items:center;gap:.6rem}\n.bl-preview-toggle__label{font-size:12px;font-weight:600;color:#a1a1aa}\n.bl-preview-toggle__pills{display:inline-flex;padding:3px;border:1px solid #3f3f46;border-radius:10px;background:#18181b}\n.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}\n.bl-preview-toggle__pills button[aria-pressed=true]{background:#f4f4f5;color:#18181b;box-shadow:0 1px 2px rgba(0,0,0,.25)}\nhtml[data-theme=light] .bl-preview-toggle__label{color:#52525b}\nhtml[data-theme=light] .bl-preview-toggle__pills{border-color:#d4d4d8;background:#e4e4e7}\nhtml[data-theme=light] .bl-preview-toggle__pills button{color:#3f3f46}\nhtml[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)}\n`;\n","import type { FillLabelWarning } from \"../theme/readability\";\nimport {\n hslCss,\n READABILITY_FIX,\n READABILITY_TARGET,\n readabilityHeadline,\n readabilityModesLine,\n readabilityRatioLine,\n} from \"../theme/readability\";\n\nexport function ReadabilityDetail({\n item,\n target = READABILITY_TARGET,\n}: {\n item: FillLabelWarning;\n target?: number;\n}) {\n return (\n <div className=\"bl-readability-detail\">\n <p className=\"bl-readability-detail__lead\">{readabilityModesLine(item)}</p>\n <div className=\"bl-readability-detail__modes\">\n {(\n [\n [\"Light\", item.light],\n [\"Dark\", item.dark],\n ] as const\n ).map(([mode, sample]) => (\n <figure key={mode} data-failing={sample.shortfall > 0 ? \"\" : undefined}>\n <span\n aria-hidden=\"true\"\n className=\"bl-readability__swatch\"\n style={{ background: hslCss(sample.fill), color: hslCss(sample.label) }}\n >\n Aa\n </span>\n <figcaption>\n <strong>{mode}</strong>\n <span>{sample.shortfall > 0 ? \"Hard to read\" : \"Looks good\"}</span>\n <span>{readabilityRatioLine(sample, target)}</span>\n </figcaption>\n </figure>\n ))}\n </div>\n {item.meets ? null : <p className=\"bl-readability-detail__fix\">{READABILITY_FIX}</p>}\n </div>\n );\n}\n\nexport function ReadabilityList({\n warnings,\n target = READABILITY_TARGET,\n}: {\n warnings: FillLabelWarning[];\n target?: number;\n}) {\n const failing = warnings.filter((item) => !item.meets).length;\n return (\n <aside className=\"bl-readability\">\n <div className=\"bl-readability__head\">\n <div>\n <h2>Readability checks</h2>\n <p>Text the site puts on each fill. Warnings do not block save.</p>\n </div>\n <span data-attention={failing ? \"\" : undefined}>\n {failing === 0 ? \"All look readable\" : `${failing} may be hard to read`}\n </span>\n </div>\n <ul>\n {warnings.map((item) => (\n <li key={item.id} data-failing={item.meets ? undefined : \"\"}>\n <strong>{readabilityHeadline(item)}</strong>\n <ReadabilityDetail item={item} target={target} />\n </li>\n ))}\n </ul>\n </aside>\n );\n}\n\nexport const READABILITY_LIST_CSS = `\n.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)}\n.bl-readability__head{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start}\n.bl-readability__head h2{margin:0;font-size:1rem}\n.bl-readability__head p{margin:.35rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}\n.bl-readability__head span{flex:none;font-size:12px;font-weight:600;padding:.2rem .55rem;border-radius:999px;background:var(--theme-elevation-100,#f1f5f9)}\n.bl-readability__head span[data-attention]{background:#fffbeb;color:#92400e}\n.bl-readability ul{margin:.85rem 0 0;padding:0;list-style:none}\n.bl-readability li{padding:.65rem 0;border-top:1px solid var(--theme-elevation-100,#f1f5f9)}\n.bl-readability li:first-child{border-top:0}\n.bl-readability li[data-failing]{background:#fff7ed;margin:0 -.4rem;padding:.65rem .4rem;border-radius:8px}\n.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}\n.bl-readability strong{display:block;font-size:13px}\n.bl-readability-detail__lead{margin:0 0 .65rem;font-size:13px;color:var(--theme-elevation-600,#475569)}\n.bl-readability-detail__modes{display:grid;grid-template-columns:1fr 1fr;gap:.65rem}\n.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}\n.bl-readability-detail__modes figure[data-failing]{border-color:#fdba74;background:#fff7ed}\n.bl-readability-detail__modes figcaption{display:flex;flex-direction:column;gap:.1rem;min-width:0}\n.bl-readability-detail__modes figcaption strong{font-size:12px}\n.bl-readability-detail__modes figcaption span{font-size:11px;color:var(--theme-elevation-500,#64748b)}\n.bl-readability-detail__fix{margin:.7rem 0 0;font-size:13px;color:var(--theme-elevation-600,#475569)}\n`;\n","import {\n includedShadeSteps,\n regenerateColorScale,\n setColorScaleInclude,\n SHADE_STEPS,\n updateColorScale,\n type ColorScaleState,\n type ShadeStep,\n} from \"@bison-lab/tokens\";\nimport { useId, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport {\n automaticLabel,\n fillLabelWarning,\n hslCss,\n readabilityHeadline,\n successHueWarning,\n destructiveHueWarning,\n} from \"../theme/readability\";\nimport { useThemePreviewMode } from \"./preview-mode\";\nimport { ReadabilityDetail, READABILITY_LIST_CSS } from \"./readability-list\";\n\nexport interface ColorScaleCardProps {\n id?: string;\n name: string;\n hint?: string;\n required?: boolean;\n custom?: boolean;\n state: ColorScaleState;\n onChange: (next: ColorScaleState) => void;\n onDelete?: () => void;\n readOnly?: boolean;\n role?: \"success\" | \"destructive\" | null;\n}\n\nexport function ColorScaleCard({\n id,\n name,\n hint,\n required,\n custom,\n state,\n onChange,\n onDelete,\n readOnly,\n role,\n}: ColorScaleCardProps) {\n const label = automaticLabel(state.hex);\n const warning = fillLabelWarning(id ?? name.toLowerCase(), name, state.hex);\n const previewMode = useThemePreviewMode();\n const preview = warning ? (previewMode === \"dark\" ? warning.dark : warning.light) : null;\n const [open, setOpen] = useState(false);\n const titleId = useId();\n const hueWarning =\n role === \"success\" ? successHueWarning(state.hex) : role === \"destructive\" ? destructiveHueWarning(state.hex) : null;\n const included = new Set(includedShadeSteps(state));\n\n function setHex(hex: string) {\n onChange(updateColorScale(state, { hex }));\n }\n\n function setStep(step: ShadeStep) {\n onChange(updateColorScale(state, { sourceStep: step }));\n }\n\n function toggleStep(step: ShadeStep) {\n if (step === state.sourceStep) return;\n const next = new Set(included);\n if (next.has(step)) next.delete(step);\n else next.add(step);\n onChange(setColorScaleInclude(state, SHADE_STEPS.filter((item) => next.has(item))));\n }\n\n return (\n <article className=\"bl-scale-card\" data-preview={previewMode}>\n <header className=\"bl-scale-card__head\">\n <div>\n <div className=\"bl-scale-card__title-row\">\n <h3 className=\"bl-scale-card__title\">{name}</h3>\n {required ? <span className=\"bl-scale-card__badge\">Required</span> : null}\n {custom ? <span className=\"bl-scale-card__badge\">Optional</span> : null}\n {warning ? (\n warning.meets ? (\n <span className=\"bl-scale-card__status\" data-ok=\"\" title={`Text on ${name} looks readable`}>\n <span aria-hidden=\"true\">✓</span>\n <span className=\"bl-scale-card__sr\">Text on {name} looks readable</span>\n </span>\n ) : (\n <button\n type=\"button\"\n className=\"bl-scale-card__badge bl-scale-card__badge--attention\"\n aria-haspopup=\"dialog\"\n onClick={() => setOpen(true)}\n >\n Needs attention\n </button>\n )\n ) : null}\n </div>\n {hint ? <p className=\"bl-scale-card__hint\">{hint}</p> : null}\n </div>\n <div className=\"bl-scale-card__head-actions\">\n <span className=\"bl-scale-card__mark\" style={{ background: state.hex }} aria-hidden=\"true\" />\n {onDelete ? (\n <button type=\"button\" className=\"bl-scale-card__delete\" disabled={readOnly} onClick={onDelete}>\n Delete\n </button>\n ) : null}\n </div>\n </header>\n\n <div className=\"bl-scale-card__row\">\n <label className=\"bl-scale-card__field\">\n <span>Brand hex</span>\n <span className=\"bl-scale-card__hex-wrap\">\n <input\n type=\"color\"\n aria-label={`${name} picker`}\n disabled={readOnly}\n value={state.hex}\n onChange={(event) => setHex(event.target.value)}\n />\n <input\n type=\"text\"\n spellCheck={false}\n autoComplete=\"off\"\n disabled={readOnly}\n value={state.hex}\n onChange={(event) => {\n const next = event.target.value.trim();\n if (next) setHex(next);\n }}\n />\n </span>\n </label>\n <label className=\"bl-scale-card__field\">\n <span>This color is</span>\n <select\n disabled={readOnly}\n value={String(state.sourceStep)}\n onChange={(event) => setStep(Number(event.target.value) as ShadeStep)}\n >\n {SHADE_STEPS.map((step) => (\n <option key={step} value={step}>\n {step}\n </option>\n ))}\n </select>\n </label>\n </div>\n\n <div className=\"bl-scale-card__label-sample\">\n <span\n className=\"bl-scale-card__swatch\"\n style={{\n background: preview ? hslCss(preview.fill) : state.hex,\n color: preview ? hslCss(preview.label) : label ? hslCss(label) : undefined,\n }}\n >\n Aa\n </span>\n <p>\n {previewMode === \"dark\"\n ? \"Previewing this fill in Dark. A dark-enough hex lightens; otherwise it stays the typed color.\"\n : \"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.\"}\n </p>\n </div>\n\n {hueWarning ? <p className=\"bl-scale-card__warn\">{hueWarning}</p> : null}\n\n {state.stale ? (\n <p className=\"bl-scale-card__stale\">This scale is out of date. Regenerate to rebuild 50–950 around the hex.</p>\n ) : null}\n\n <button\n type=\"button\"\n className=\"bl-scale-card__regen\"\n disabled={readOnly || !state.stale}\n onClick={() => onChange(regenerateColorScale(state))}\n >\n Regenerate scale\n </button>\n\n <div className=\"bl-scale-card__scale-head\">\n <span>Generated scale</span>\n <span>\n <button type=\"button\" disabled={readOnly} onClick={() => onChange(setColorScaleInclude(state, \"all\"))}>\n Include all\n </button>\n {\" · \"}\n <button type=\"button\" disabled={readOnly} onClick={() => onChange(setColorScaleInclude(state, \"source\"))}>\n Source only\n </button>\n </span>\n </div>\n <div className=\"bl-scale-card__steps\" role=\"group\" aria-label={`${name} scale steps`}>\n {SHADE_STEPS.map((step) => {\n const source = step === state.sourceStep;\n const on = included.has(step);\n return (\n <button\n key={step}\n type=\"button\"\n className=\"bl-scale-card__step\"\n data-source={source ? \"\" : undefined}\n data-excluded={on ? undefined : \"\"}\n disabled={readOnly || source}\n title={\n source\n ? `Source step ${step} cannot be excluded`\n : on\n ? `Exclude ${step} from page editors`\n : `Include ${step} for page editors`\n }\n onClick={() => toggleStep(step)}\n >\n <span className=\"bl-scale-card__chip\" style={{ background: hslCss(state.scale[step]) }}>\n {source ? null : (\n <span\n className=\"bl-scale-card__intent\"\n data-intent={on ? \"exclude\" : \"include\"}\n aria-hidden=\"true\"\n >\n {on ? (\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\">\n <circle cx=\"12\" cy=\"12\" r=\"8\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.4\" />\n <path d=\"M7.2 16.8 16.8 7.2\" stroke=\"currentColor\" strokeWidth=\"2.4\" />\n </svg>\n ) : (\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\">\n <path d=\"M12 6v12M6 12h12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.6\" />\n </svg>\n )}\n </span>\n )}\n </span>\n <span className=\"bl-scale-card__step-label\">{step}</span>\n </button>\n );\n })}\n </div>\n {open && warning\n ? createPortal(\n <div\n className=\"bl-scale-card__modal\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n onClick={(event) => {\n if (event.target === event.currentTarget) setOpen(false);\n }}\n >\n <div className=\"bl-scale-card__panel\">\n <div className=\"bl-scale-card__panel-head\">\n <h4 id={titleId}>{readabilityHeadline(warning)}</h4>\n <button type=\"button\" onClick={() => setOpen(false)} aria-label=\"Close\">\n ×\n </button>\n </div>\n <ReadabilityDetail item={warning} />\n <button type=\"button\" className=\"bl-scale-card__panel-done\" onClick={() => setOpen(false)}>\n Keep editing this color\n </button>\n </div>\n </div>,\n document.body,\n )\n : null}\n </article>\n );\n}\n\nexport const COLOR_SCALE_CARD_CSS = `\n.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)}\n.bl-scale-card__head{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start}\n.bl-scale-card__title-row{position:relative;display:flex;align-items:center;flex-wrap:wrap;gap:.5rem}\n.bl-scale-card__title{margin:0;font-size:1rem}\n.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)}\n.bl-scale-card__badge--attention{background:#fff1f2;color:#be123c;cursor:pointer}\n.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}\n.bl-scale-card__status[data-ok]{background:#ecfdf5;color:#047857}\n.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}\n.bl-scale-card__hint{margin:.35rem 0 0;color:var(--theme-elevation-500,#64748b);font-size:12px}\n.bl-scale-card__head-actions{display:flex;align-items:center;gap:.5rem}\n.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}\n.bl-scale-card__delete{border:0;background:none;color:#be123c;font:inherit;font-size:13px;font-weight:600;cursor:pointer}\n.bl-scale-card__row{display:grid;grid-template-columns:1fr 8rem;gap:.75rem;margin-top:1rem}\n.bl-scale-card__field{display:flex;flex-direction:column;gap:.35rem;font-size:12px;font-weight:600;color:var(--theme-elevation-600,#475569)}\n.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)}\n.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}\n.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}\n.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}\n.bl-scale-card__label-sample{display:flex;gap:.75rem;align-items:center;margin-top:1rem}\n.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}\n.bl-scale-card__label-sample p{margin:0;font-size:12px;color:var(--theme-elevation-500,#64748b)}\n.bl-scale-card__warn,.bl-scale-card__stale{margin:.75rem 0 0;padding:.65rem .75rem;border-radius:8px;font-size:12px}\n.bl-scale-card__warn{border:1px solid #a7f3d0;background:#ecfdf5;color:#065f46}\n.bl-scale-card__stale{border:1px solid #fde68a;background:#fffbeb;color:#92400e}\n.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}\n.bl-scale-card__regen:disabled{background:#1e293b;color:#f8fafc;opacity:1;cursor:not-allowed}\n.bl-scale-card__scale-head{display:flex;justify-content:space-between;margin:1rem 0 .4rem;font-size:12px;color:var(--theme-elevation-500,#64748b)}\n.bl-scale-card__scale-head button{border:0;background:none;padding:0;font:inherit;color:inherit;text-decoration:underline;cursor:pointer}\n.bl-scale-card__steps{display:grid;grid-template-columns:repeat(11,minmax(0,1fr));gap:.45rem 3px}\n.bl-scale-card__step{display:flex;flex-direction:column;align-items:center;gap:.3rem;border:0;padding:0;background:transparent;cursor:pointer}\n.bl-scale-card__chip{position:relative;display:block;width:100%;height:2.15rem;border-radius:6px;overflow:hidden}\n.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}\n.bl-scale-card__intent[data-intent=include]{color:#047857}\n.bl-scale-card__step:hover:not(:disabled) .bl-scale-card__chip{transform:translateY(-1px)}\n.bl-scale-card__step:hover:not(:disabled) .bl-scale-card__intent{opacity:1}\n.bl-scale-card__step-label{font-size:10px;line-height:1;color:var(--theme-elevation-600,#475569)}\n.bl-scale-card__step[data-source] .bl-scale-card__chip{box-shadow:0 0 0 2px var(--theme-elevation-800,#0f172a)}\n.bl-scale-card__step[data-excluded]{opacity:.55}\n.bl-scale-card__step:disabled{cursor:default}\n.bl-scale-card__modal{position:fixed;inset:0;z-index:100000;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}\n.bl-scale-card__panel{width:min(32rem,100%);padding:1.15rem 1.2rem;border-radius:16px;background:#fff;color:#0f172a}\n.bl-scale-card__panel-head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}\n.bl-scale-card__panel-head h4{margin:0;font-size:1rem}\n.bl-scale-card__panel-head button{border:0;background:none;font:inherit;font-size:1.25rem;line-height:1;color:#64748b;cursor:pointer}\n.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}\n` + READABILITY_LIST_CSS;\n","import { useField } from \"@payloadcms/ui\";\nimport { isThemeHex, SHADE_STEPS, type ColorScaleState, type ShadeStep } from \"@bison-lab/tokens\";\nimport type { GroupFieldClientComponent } from \"payload\";\n\nimport { SYSTEM_COLOR_COPY } from \"../theme/appearance-labels\";\nimport { colorDocFromState, stateFromColorDoc } from \"../theme/map\";\nimport { ColorScaleCard, COLOR_SCALE_CARD_CSS } from \"./color-scale-card\";\n\nconst SYSTEM_ROLES = new Set(Object.keys(SYSTEM_COLOR_COPY));\n\n/**\n * One system (or library row) color: hex, source step, regenerate, stale,\n * include/exclude, automatic label. Writes the group's stored fields.\n */\nexport const ColorScaleField: GroupFieldClientComponent = ({ field, path, readOnly }) => {\n const hex = useField<string>({ path: `${path}.hex` });\n const sourceStep = useField<string | number>({ path: `${path}.sourceStep` });\n const scale = useField<ColorScaleState[\"scale\"]>({ path: `${path}.scale` });\n const stale = useField<boolean>({ path: `${path}.stale` });\n const include = useField<string>({ path: `${path}.include` });\n const includedSteps = useField<ShadeStep[]>({ path: `${path}.includedSteps` });\n const key = path.split(\".\").pop() ?? \"\";\n const copy = SYSTEM_ROLES.has(key) ? SYSTEM_COLOR_COPY[key as keyof typeof SYSTEM_COLOR_COPY] : undefined;\n const state = stateFromColorDoc({\n hex: hex.value,\n sourceStep: sourceStep.value as ShadeStep | undefined,\n scale: scale.value,\n stale: stale.value,\n include: include.value === \"source\" || include.value === \"custom\" || include.value === \"all\" ? include.value : \"all\",\n includedSteps: includedSteps.value,\n });\n\n function write(next: ColorScaleState) {\n const doc = colorDocFromState(next);\n hex.setValue(doc.hex);\n sourceStep.setValue(String(doc.sourceStep));\n scale.setValue(doc.scale);\n stale.setValue(doc.stale);\n include.setValue(doc.include);\n includedSteps.setValue(doc.includedSteps ?? (doc.include === \"source\" ? [doc.sourceStep] : [...SHADE_STEPS]));\n }\n\n const label = copy?.label ?? (typeof field.label === \"string\" ? field.label : key);\n\n return (\n <div className=\"field-type bl-scale-field\">\n <style href=\"bl-scale-card\" precedence=\"default\">\n {COLOR_SCALE_CARD_CSS}\n </style>\n <ColorScaleCard\n id={key}\n name={label}\n hint={copy?.hint}\n required={Boolean(copy) || Boolean(\"required\" in field && field.required)}\n state={isThemeHex(state.hex) ? state : { ...state, hex: \"#000000\" }}\n onChange={write}\n readOnly={readOnly}\n role={key === \"success\" || key === \"destructive\" ? key : null}\n />\n </div>\n );\n};\n","import type { Access } from \"payload\";\nimport type { FontEntry } from \"@bison-lab/fonts\";\nimport type { ColorScale, ColorScaleInclude, ShadeStep, ThemeConfig } from \"@bison-lab/tokens\";\n\nimport type { ResolvedThemeIdentity, ThemeIdentityFallback } from \"./identity\";\n\n/**\n * The Theme document a site's generated types will describe. Optional and\n * nullable, no index signature: a generated Global is assignable to this,\n * never the reverse.\n *\n * Legacy flat `brand.*` hexes, `fonts`, and preset fields at the root stay\n * readable so `getPublishedTheme` still maps a pre-BIS-87 row.\n */\nexport interface ThemeColorDoc {\n hex?: string | null;\n sourceStep?: ShadeStep | string | null;\n scale?: ColorScale | null;\n stale?: boolean | null;\n include?: ColorScaleInclude | \"custom\" | null;\n includedSteps?: ShadeStep[] | string[] | null;\n}\n\nexport interface ThemeLibraryColorDoc extends ThemeColorDoc {\n key?: string | null;\n label?: string | null;\n}\n\nexport interface ThemeDoc {\n colors?: {\n brand?: {\n primary?: ThemeColorDoc | string | null;\n secondary?: ThemeColorDoc | string | null;\n accent?: ThemeColorDoc | string | null;\n highlight?: ThemeColorDoc | string | null;\n success?: ThemeColorDoc | string | null;\n destructive?: ThemeColorDoc | string | null;\n } | null;\n library?: ThemeLibraryColorDoc[] | null;\n greyScale?: ThemeConfig[\"greyScale\"] | null;\n } | null;\n typography?: {\n body?: string | null;\n heading?: string | null;\n } | null;\n appearance?: {\n defaultTheme?: ThemeConfig[\"defaultTheme\"] | null;\n radius?: ThemeConfig[\"radius\"] | null;\n shadow?: ThemeConfig[\"shadow\"] | null;\n motion?: ThemeConfig[\"motion\"] | null;\n density?: ThemeConfig[\"density\"] | null;\n } | null;\n /** @deprecated Pre-BIS-87 flat brand hexes. */\n brand?: {\n primary?: ThemeColorDoc | string | null;\n secondary?: ThemeColorDoc | string | null;\n accent?: ThemeColorDoc | string | null;\n highlight?: ThemeColorDoc | string | null;\n success?: ThemeColorDoc | string | null;\n destructive?: ThemeColorDoc | string | null;\n } | null;\n /** @deprecated Pre-BIS-87; now `colors.greyScale`. */\n greyScale?: ThemeConfig[\"greyScale\"] | null;\n /** @deprecated Pre-BIS-87; now `appearance.*`. */\n radius?: ThemeConfig[\"radius\"] | null;\n shadow?: ThemeConfig[\"shadow\"] | null;\n motion?: ThemeConfig[\"motion\"] | null;\n density?: ThemeConfig[\"density\"] | null;\n defaultTheme?: ThemeConfig[\"defaultTheme\"] | null;\n /** @deprecated Pre-BIS-87; now `typography`. */\n fonts?: {\n body?: string | null;\n heading?: string | null;\n } | null;\n logo?: number | string | ThemeUploadDoc | null;\n favicon?: number | string | ThemeUploadDoc | null;\n logoMark?: number | string | ThemeUploadDoc | null;\n}\n\nexport interface ThemeUploadDoc {\n id?: number | string | null;\n url?: string | null;\n alt?: string | null;\n}\n\n/**\n * What an editor can change. `darkSelector` and `fontWeights` stay on the\n * seed — they mean nothing in the admin — and `themeConfigFromDoc` puts\n * them back.\n */\nexport type EditableTheme = Omit<ThemeConfig, \"darkSelector\" | \"fontWeights\">;\n\nexport interface CreateThemeOptions {\n /**\n * Required, no default. Predicates live in each site's `src/platform`\n * until BIS-43; pass `canManageBrand` as `update` (and typically\n * `isAuthenticated` as `read`).\n */\n access: { read: Access; update: Access };\n /** The site's `bison.config.json`. Every field's `defaultValue`, and the fallback `getPublishedTheme` returns. */\n seed: ThemeConfig;\n /**\n * Destructive seed hex. Required unless `seed.brandDestructive` is set.\n * Not a package default — the site names it.\n */\n destructive?: string;\n /** Default: the whole catalogue. */\n fonts?: readonly FontEntry[];\n /** Where the site's `serveFont` route answers, for the picker's specimens. Default `\"/fonts\"`. */\n fontsBaseUrl?: string;\n /** Fill + automatic label target. Default `7`. Warnings never block save. */\n contrastTarget?: number;\n /**\n * @deprecated Theme has no preview pane (SPI-56 canceled). Kept so a\n * site that still passes it does not fail to boot.\n */\n previewPath?: string;\n /**\n * Adds logo, favicon, and mobile-menu mark upload relations when\n * given. Pass `BRAND_ASSETS_SLUG` (or the slug you gave\n * `createBrandAssets`). Identity is a Theme child with its own Publish.\n */\n logo?: { collection: string };\n /**\n * Fallback art when logo, favicon, or mobile-menu mark is empty. The\n * site passes today's files; `--color-primary-mark` is not a Theme field.\n */\n identity?: { fallback?: ThemeIdentityFallback };\n /** Fires from `afterChange` on every save — Theme has no draft mode. */\n onPublish?: (theme: ThemeConfig, doc: ThemeDoc) => void | Promise<void>;\n}\n\nexport interface ThemeHeadOptions {\n fontsBaseUrl?: string;\n attribute?: \"class\" | \"data-theme\";\n identity?: ResolvedThemeIdentity;\n}\n\nexport interface ThemeHead {\n css: string;\n preloads: { href: string; type: \"font/woff2\" }[];\n identity?: ResolvedThemeIdentity;\n}\n\nexport const THEME_SLUG = \"theme\";\n\nexport const SYSTEM_COLOR_KEYS = [\n \"primary\",\n \"secondary\",\n \"accent\",\n \"highlight\",\n \"success\",\n \"destructive\",\n] as const;\n\nexport type SystemColorKey = (typeof SYSTEM_COLOR_KEYS)[number];\n","import { SHADE_STEPS, type ShadeStep } from \"@bison-lab/tokens\";\n\nimport { SYSTEM_COLOR_KEYS, type ThemeDoc } from \"./types\";\n\nconst STEP_SET = new Set<number>(SHADE_STEPS);\n\n/**\n * Page editors pick `coral-400`, never a raw hex. Rewrite every use of\n * `fromKey` onto `toKey` at the same step: `coral-400` → `highlight-400`.\n * A bare key (`coral`) becomes `toKey`. Anything else is left alone.\n */\nexport function rewriteColorToken(token: string, fromKey: string, toKey: string): string {\n if (token === fromKey) return toKey;\n const dash = token.lastIndexOf(\"-\");\n if (dash <= 0) return token;\n const key = token.slice(0, dash);\n const step = Number(token.slice(dash + 1));\n if (key !== fromKey || !STEP_SET.has(step)) return token;\n return `${toKey}-${step as ShadeStep}`;\n}\n\nexport function themeColorKeys(doc: ThemeDoc | null | undefined): string[] {\n const custom = (Array.isArray(doc?.colors?.library) ? doc.colors.library : [])\n .map((row) => row.key?.trim())\n .filter((key): key is string => Boolean(key));\n return [...SYSTEM_COLOR_KEYS, ...custom];\n}\n\n/**\n * Remove a custom color. Unused: omit `replacement` and the row is gone.\n * In use: pass another system or custom key; the caller rewrites page\n * tokens with `rewriteColorToken`. After this, no library row still\n * has `key`.\n */\nexport function deleteLibraryColor(doc: ThemeDoc, key: string, replacement?: string): ThemeDoc {\n const library = [...(Array.isArray(doc.colors?.library) ? doc.colors.library : [])];\n const index = library.findIndex((row) => row.key === key);\n if (index === -1) {\n throw new Error(`No custom color named ${JSON.stringify(key)}`);\n }\n library.splice(index, 1);\n const next: ThemeDoc = {\n ...doc,\n colors: { ...doc.colors, library },\n };\n if (replacement !== undefined) {\n if (replacement === key) {\n throw new Error(\"Replacement must be a different color\");\n }\n if (!themeColorKeys(next).includes(replacement)) {\n throw new Error(`Replacement ${JSON.stringify(replacement)} is not a system or remaining custom color`);\n }\n }\n return next;\n}\n","import { useField } from \"@payloadcms/ui\";\nimport { createColorScale } from \"@bison-lab/tokens\";\nimport type { ArrayFieldClientComponent } from \"payload\";\nimport { useState } from \"react\";\n\nimport { deleteLibraryColor } from \"../theme/library\";\nimport { colorDocFromState, stateFromColorDoc } from \"../theme/map\";\nimport { SYSTEM_COLOR_KEYS, type ThemeDoc, type ThemeLibraryColorDoc } from \"../theme/types\";\nimport { ColorScaleCard, COLOR_SCALE_CARD_CSS } from \"./color-scale-card\";\n\nfunction slugify(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\n/**\n * Custom colors: the same scale controls as system colors, plus add and\n * in-use delete (one replacement scale; unused rows delete immediately).\n */\nexport const LibraryField: ArrayFieldClientComponent = ({ path, readOnly }) => {\n const { value, setValue } = useField<ThemeLibraryColorDoc[]>({ path });\n const rows = Array.isArray(value) ? value : [];\n const [adding, setAdding] = useState(false);\n const [name, setName] = useState(\"\");\n const [hex, setHex] = useState(\"#E07A5F\");\n const [pendingDelete, setPendingDelete] = useState<ThemeLibraryColorDoc | null>(null);\n const [replacement, setReplacement] = useState(\"\");\n\n function write(next: ThemeLibraryColorDoc[]) {\n setValue(next);\n }\n\n function add() {\n const key = slugify(name);\n if (!key || rows.some((row) => row.key === key)) return;\n write([...rows, { key, label: name.trim(), ...colorDocFromState(createColorScale(hex, 500)) }]);\n setName(\"\");\n setAdding(false);\n }\n\n function remove(row: ThemeLibraryColorDoc, replaceWith?: string) {\n const doc: ThemeDoc = { colors: { library: rows } };\n write(deleteLibraryColor(doc, row.key ?? \"\", replaceWith).colors?.library ?? []);\n setPendingDelete(null);\n setReplacement(\"\");\n }\n\n const replacements = [\n ...SYSTEM_COLOR_KEYS.map((key) => ({ value: key, label: key[0].toUpperCase() + key.slice(1) })),\n ...rows\n .filter((row) => row.key && row.key !== pendingDelete?.key)\n .map((row) => ({ value: row.key ?? \"\", label: row.label ?? row.key ?? \"\" })),\n ];\n\n return (\n <div className=\"field-type bl-library\">\n <style href=\"bl-scale-card\" precedence=\"default\">\n {COLOR_SCALE_CARD_CSS}\n </style>\n <style href=\"bl-library\" precedence=\"default\">\n {CSS}\n </style>\n <div className=\"bl-library__head\">\n <div>\n <h2>Custom colors</h2>\n <p>Named scales that sit with the house colors. Page editors pick them as keys (`coral-400`).</p>\n </div>\n <button type=\"button\" disabled={readOnly} onClick={() => setAdding(true)}>\n Add color\n </button>\n </div>\n {adding ? (\n <div className=\"bl-library__add\">\n <label>\n Name\n <input value={name} onChange={(event) => setName(event.target.value)} />\n </label>\n <label>\n Hex\n <input value={hex} onChange={(event) => setHex(event.target.value)} />\n </label>\n <button type=\"button\" onClick={() => setAdding(false)}>\n Cancel\n </button>\n <button type=\"button\" disabled={!slugify(name)} onClick={add}>\n Create color scale\n </button>\n </div>\n ) : null}\n <div className=\"bl-library__list\">\n {rows.map((row, index) => (\n <ColorScaleCard\n key={row.key ?? index}\n id={row.key ?? `custom-${index}`}\n name={row.label ?? row.key ?? \"Color\"}\n hint={row.key ? `Key: ${row.key}` : undefined}\n custom\n state={stateFromColorDoc(row)}\n onChange={(next) => {\n const copy = [...rows];\n copy[index] = { ...row, ...colorDocFromState(next) };\n write(copy);\n }}\n onDelete={readOnly ? undefined : () => setPendingDelete(row)}\n readOnly={readOnly}\n />\n ))}\n </div>\n {pendingDelete ? (\n <div className=\"bl-library__modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"bl-library-delete-title\">\n <div className=\"bl-library__panel\">\n <h2 id=\"bl-library-delete-title\">Delete {pendingDelete.label ?? pendingDelete.key}</h2>\n <p>\n Unused custom colors delete immediately. If this scale is in use, pick one replacement — every{\" \"}\n <code>{pendingDelete.key}-400</code> becomes <code>{replacement || \"…\"}-400</code>.\n </p>\n <label>\n Replacement scale\n <select value={replacement} onChange={(event) => setReplacement(event.target.value)}>\n <option value=\"\">Choose a scale…</option>\n {replacements.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n <div className=\"bl-library__actions\">\n <button type=\"button\" onClick={() => setPendingDelete(null)}>\n Cancel\n </button>\n <button type=\"button\" onClick={() => remove(pendingDelete)}>\n Delete immediately\n </button>\n <button type=\"button\" disabled={!replacement} onClick={() => remove(pendingDelete, replacement)}>\n Replace & delete\n </button>\n </div>\n </div>\n </div>\n ) : null}\n </div>\n );\n};\n\nconst CSS = `\n.bl-library__head{display:flex;justify-content:space-between;gap:1rem;align-items:flex-start;margin-bottom:.85rem}\n.bl-library__head h2{margin:0;font-size:1.1rem}\n.bl-library__head p{margin:.35rem 0 0;font-size:13px;color:var(--theme-elevation-500,#64748b)}\n.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}\n.bl-library__list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem}\n@media (max-width:860px){.bl-library__list{grid-template-columns:1fr}}\n.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}\n.bl-library__add label{display:flex;flex-direction:column;gap:.3rem;font-size:12px;font-weight:600}\n.bl-library__add input{min-height:2.1rem;padding:.3rem .55rem;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:8px}\n.bl-library__modal{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}\n.bl-library__panel{width:min(28rem,100%);padding:1.2rem;border-radius:16px;background:#fff;color:#0f172a}\n.bl-library__panel h2{margin:0}\n.bl-library__panel p{margin:.5rem 0 0;color:#475569;font-size:14px}\n.bl-library__panel label{display:flex;flex-direction:column;gap:.4rem;margin-top:1rem;font-size:13px;font-weight:600}\n.bl-library__panel select{min-height:2.25rem;border:1px solid #cbd5e1;border-radius:8px;padding:.35rem .5rem}\n.bl-library__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.5rem;margin-top:1rem}\n`;\n","import type { ReactNode } from \"react\";\n\nexport function FontRoleCard({\n title,\n hint,\n required,\n family,\n preview,\n children,\n}: {\n title: string;\n hint: string;\n required?: boolean;\n family: string;\n preview: \"heading\" | \"body\";\n children?: ReactNode;\n}) {\n return (\n <article className=\"bl-font-role\">\n <header className=\"bl-font-role__head\">\n <div>\n <h3>{title}</h3>\n <p>{hint}</p>\n </div>\n {required ? <span className=\"bl-font-role__badge\">Required</span> : null}\n </header>\n {children}\n <div className=\"bl-font-role__preview\">\n <span>Preview</span>\n {preview === \"heading\" ? (\n <p className=\"bl-font-role__heading\" style={{ fontFamily: `\"${family}\"` }}>\n Advancing the work with clarity.\n </p>\n ) : (\n <p className=\"bl-font-role__body\" style={{ fontFamily: `\"${family}\"` }}>\n Clear, comfortable text for longer reading across the site.\n </p>\n )}\n </div>\n </article>\n );\n}\n\nexport function TypographyPairing({\n headingFamily,\n bodyFamily,\n}: {\n headingFamily: string;\n bodyFamily: string;\n}) {\n return (\n <section className=\"bl-pairing\" aria-label=\"Font pairing sample\">\n <header>\n <h2>Pairing sample</h2>\n <p>Heading and body from the approved catalogue. Type scale stays with the library.</p>\n </header>\n <div className=\"bl-pairing__page\">\n <p className=\"bl-pairing__eyebrow\" style={{ fontFamily: `\"${bodyFamily}\"` }}>\n For visitors\n </p>\n <p className=\"bl-pairing__title\" style={{ fontFamily: `\"${headingFamily}\"` }}>\n Stay in motion.\n </p>\n <p className=\"bl-pairing__copy\" style={{ fontFamily: `\"${bodyFamily}\"` }}>\n Heading and body come from the catalogue. This is the pairing, not a full site preview.\n </p>\n <div className=\"bl-pairing__actions\">\n <span className=\"bl-pairing__btn\" style={{ fontFamily: `\"${bodyFamily}\"` }}>\n Primary action\n </span>\n <span className=\"bl-pairing__btn bl-pairing__btn--ghost\" style={{ fontFamily: `\"${bodyFamily}\"` }}>\n Learn more\n </span>\n </div>\n </div>\n </section>\n );\n}\n\nexport const TYPOGRAPHY_PAIRING_CSS = `\n.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)}\n.bl-font-role__head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}\n.bl-font-role__head h3{margin:0;font-size:1rem}\n.bl-font-role__head p{margin:.3rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}\n.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)}\n.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)}\n.bl-font-role__preview > span{display:block;margin-bottom:.4rem;font-size:11px;color:var(--theme-elevation-500,#64748b)}\n.bl-font-role__heading{margin:0;font-size:1.5rem;line-height:1.2;font-weight:600}\n.bl-font-role__body{margin:0;font-size:1rem;line-height:1.6;color:var(--theme-elevation-600,#475569)}\n.bl-pairing{padding:0;border:1px solid var(--theme-elevation-150,#e2e8f0);border-radius:12px;background:var(--theme-elevation-0,#fff);overflow:hidden}\n.bl-pairing > header{padding:1rem 1.15rem;border-bottom:1px solid var(--theme-elevation-150,#e2e8f0)}\n.bl-pairing > header h2{margin:0;font-size:1rem}\n.bl-pairing > header p{margin:.3rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}\n.bl-pairing__page{padding:1.35rem 1.4rem 1.5rem}\n.bl-pairing__eyebrow{margin:0;font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#7c1fa2}\n.bl-pairing__title{margin:.5rem 0 0;font-size:2rem;line-height:1.1;font-weight:600}\n.bl-pairing__copy{margin:.75rem 0 0;max-width:36rem;font-size:1rem;line-height:1.6;color:var(--theme-elevation-600,#475569)}\n.bl-pairing__actions{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:1.1rem}\n.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}\n.bl-pairing__btn--ghost{background:#fff;color:#0f172a;border:1px solid #cbd5e1}\n`;\n","import { FieldDescription, FieldError, FieldLabel, useField } from \"@payloadcms/ui\";\nimport { findFont, fontFaceCss, isFontId, type FontId } from \"@bison-lab/fonts\";\nimport type { SelectFieldClientComponent } from \"payload\";\nimport { useEffect, useId, useMemo, useRef, useState } from \"react\";\n\nimport { FONT_ROLE_COPY } from \"../theme/appearance-labels\";\nimport { SAME_AS_BODY } from \"../theme/fields\";\nimport { FontRoleCard, TYPOGRAPHY_PAIRING_CSS } from \"./typography-pairing\";\n\ntype Option = { value: string; family: string; hint: string; specimen: boolean };\n\n/**\n * Listbox over the catalogue ids in `admin.custom.ids`. Each option is the\n * family name in that face, with the catalogue hint beneath. Faces load\n * from the site's font route for the selected family, and for every\n * catalogue id once the picker opens.\n */\nexport const FontField: SelectFieldClientComponent = ({ field, path, readOnly }) => {\n const { value, setValue, showError, errorMessage } = useField<string>({ path });\n const custom = (field.admin?.custom ?? {}) as {\n fontsBaseUrl?: string;\n ids?: string[];\n sameAsBody?: boolean;\n };\n const ids = custom.ids ?? [];\n const fontsBaseUrl = custom.fontsBaseUrl ?? \"/fonts\";\n const sameAsBody = Boolean(custom.sameAsBody);\n\n const options = useMemo<Option[]>(() => {\n const fromCatalogue: Option[] = ids.flatMap((id) => {\n const font = findFont(id);\n if (!font) return [];\n return [{ value: font.id, family: font.family, hint: font.hint, specimen: true }];\n });\n if (!sameAsBody) return fromCatalogue;\n return [{ value: SAME_AS_BODY, family: \"Same as body\", hint: \"Use the body family for headings\", specimen: false }, ...fromCatalogue];\n }, [ids, sameAsBody]);\n\n const [open, setOpen] = useState(false);\n const [faces, setFaces] = useState(false);\n const [active, setActive] = useState(-1);\n const [query, setQuery] = useState(\"\");\n const [category, setCategory] = useState<\"all\" | \"sans\" | \"serif\" | \"display\" | \"mono\">(\"all\");\n const typed = useRef(\"\");\n const typedAt = useRef(0);\n const id = useId();\n const buttonId = `${id}-button`;\n const listId = `${id}-list`;\n const required = Boolean(field.required);\n\n useEffect(() => {\n if (open) setFaces(true);\n }, [open]);\n\n const selected = options.find((option) => option.value === (value ?? SAME_AS_BODY)) ?? options[0];\n const visible = options.filter((option) => {\n if (query && !option.family.toLowerCase().includes(query.trim().toLowerCase())) return false;\n if (category === \"all\" || !option.specimen) return true;\n const font = findFont(option.value);\n return font?.category === category;\n });\n const role = sameAsBody ? FONT_ROLE_COPY.heading : FONT_ROLE_COPY.body;\n\n function choose(option: Option) {\n setValue(option.value);\n setOpen(false);\n setActive(-1);\n setQuery(\"\");\n setCategory(\"all\");\n }\n\n function onKeyDown(event: React.KeyboardEvent<HTMLButtonElement>) {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n if (!open) {\n setOpen(true);\n setActive(0);\n return;\n }\n setActive((i) => (options.length ? (i + 1) % options.length : -1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n if (!open) {\n setOpen(true);\n setActive(options.length - 1);\n return;\n }\n setActive((i) => (options.length ? (i <= 0 ? options.length - 1 : i - 1) : -1));\n } else if (event.key === \"Enter\") {\n if (!open) return;\n event.preventDefault();\n const pick = options[active] ?? options[0];\n if (pick) choose(pick);\n } else if (event.key === \"Escape\") {\n if (!open) return;\n event.preventDefault();\n event.stopPropagation();\n setOpen(false);\n setActive(-1);\n } else if (event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\n const now = Date.now();\n typed.current = now - typedAt.current > 600 ? event.key : typed.current + event.key;\n typedAt.current = now;\n const needle = typed.current.toLowerCase();\n const index = options.findIndex((option) => option.family.toLowerCase().startsWith(needle));\n if (index >= 0) {\n if (!open) setOpen(true);\n setActive(index);\n }\n }\n }\n\n const activeId = active >= 0 && open ? `${id}-option-${active}` : undefined;\n const faceIds = (faces ? ids : isFontId(value) ? [value] : []) as FontId[];\n\n return (\n <div className=\"field-type bl-font-field\">\n <style href=\"bl-pairing\" precedence=\"default\">\n {TYPOGRAPHY_PAIRING_CSS}\n </style>\n <style href=\"bl-font-field\" precedence=\"default\">\n {CSS}\n </style>\n {faceIds.length > 0 ? <style data-bl-font-faces=\"\">{fontFaceCss(faceIds, fontsBaseUrl)}</style> : null}\n <FontRoleCard\n title={typeof field.label === \"string\" ? field.label : role.label}\n hint={role.hint}\n required={required}\n family={selected?.family ?? \"Inter\"}\n preview={sameAsBody ? \"heading\" : \"body\"}\n >\n <FieldLabel htmlFor={buttonId} label={field.label} required={required} />\n <button\n id={buttonId}\n type=\"button\"\n className=\"bl-font-field__trigger\"\n disabled={readOnly}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listId}\n aria-activedescendant={activeId}\n onClick={() => setOpen((was) => !was)}\n onKeyDown={onKeyDown}\n >\n <span>\n <span className=\"bl-font-field__selected-label\">Selected</span>\n <span\n className=\"bl-font-field__selected-name\"\n style={selected?.specimen ? { fontFamily: `\"${selected.family}\"` } : undefined}\n >\n {selected?.family ?? \"Choose a font\"}\n </span>\n </span>\n <span aria-hidden=\"true\">⌄</span>\n </button>\n </FontRoleCard>\n {open ? (\n <div\n className=\"bl-font-field__modal\"\n onClick={(event) => {\n if (event.target === event.currentTarget) setOpen(false);\n }}\n >\n <div className=\"bl-font-field__panel\">\n <div className=\"bl-font-field__panel-head\">\n <div>\n <p>Choose a font</p>\n <h2>{role.label}</h2>\n </div>\n <button type=\"button\" onClick={() => setOpen(false)} aria-label=\"Close\">\n ×\n </button>\n </div>\n <input\n type=\"search\"\n className=\"bl-font-field__search\"\n placeholder=\"Search fonts…\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n />\n <div className=\"bl-font-field__filters\">\n {([\"all\", \"sans\", \"serif\", \"display\", \"mono\"] as const).map((item) => (\n <button\n key={item}\n type=\"button\"\n data-on={category === item ? \"\" : undefined}\n onClick={() => setCategory(item)}\n >\n {item === \"all\" ? \"All\" : item === \"sans\" ? \"Sans serif\" : item === \"serif\" ? \"Serif\" : item === \"display\" ? \"Display\" : \"Mono\"}\n </button>\n ))}\n </div>\n <ul id={listId} role=\"listbox\" className=\"bl-font-field__menu\">\n {visible.map((option, i) => (\n <li\n key={option.value || \"same\"}\n id={`${id}-option-${i}`}\n role=\"option\"\n aria-selected={option.value === (value ?? SAME_AS_BODY)}\n className=\"bl-font-field__option\"\n onMouseDown={(event) => {\n event.preventDefault();\n choose(option);\n }}\n onMouseEnter={() => setActive(i)}\n >\n <span\n className=\"bl-font-field__family\"\n style={option.specimen ? { fontFamily: `\"${option.family}\"` } : undefined}\n >\n {option.family}\n </span>\n <span className=\"bl-font-field__hint\">{option.hint}</span>\n <span\n className=\"bl-font-field__specimen\"\n style={option.specimen ? { fontFamily: `\"${option.family}\"` } : undefined}\n >\n The quick brown fox jumps over the lazy dog.\n </span>\n </li>\n ))}\n </ul>\n </div>\n </div>\n ) : null}\n <FieldDescription description={field.admin?.description} path={path} />\n <FieldError path={path} showError={showError} message={errorMessage} />\n </div>\n );\n};\n\nconst CSS = `\n.bl-font-field{margin-bottom:1rem}\n.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}\n.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}\n.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)}\n.bl-font-field__trigger:disabled{color:var(--theme-elevation-400,#94a3b8);cursor:not-allowed}\n.bl-font-field__selected-label{display:block;font-size:11px;color:var(--theme-elevation-500,#64748b)}\n.bl-font-field__selected-name{display:block;margin-top:.15rem;font-weight:600}\n.bl-font-field__modal{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}\n.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}\n.bl-font-field__panel-head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}\n.bl-font-field__panel-head p{margin:0;font-size:12px;color:#64748b}\n.bl-font-field__panel-head h2{margin:.2rem 0 0;font-size:1.15rem}\n.bl-font-field__panel-head button{border:0;background:none;font:inherit;font-size:1.25rem;color:#64748b;cursor:pointer}\n.bl-font-field__search{margin-top:.9rem;min-height:2.5rem;padding:.5rem .75rem;border:1px solid #cbd5e1;border-radius:10px;font:inherit}\n.bl-font-field__filters{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.75rem}\n.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}\n.bl-font-field__filters button[data-on]{background:#0f172a;color:#fff}\n.bl-font-field__menu{margin:.75rem 0 0;padding:0;list-style:none;overflow:auto;max-height:18rem}\n.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}\n.bl-font-field__option[aria-selected=\"true\"]{border-color:#7c1fa2;background:#f5f3ff}\n.bl-font-field__family{font-weight:600}\n.bl-font-field__hint{color:#64748b;font-size:12px}\n.bl-font-field__specimen{margin-top:.45rem;font-size:1.25rem;line-height:1.3}\n`;\n","import { useFormFields } from \"@payloadcms/ui\";\nimport { findFont, fontFaceCss, type FontId } from \"@bison-lab/fonts\";\nimport type { UIFieldClientComponent } from \"payload\";\n\nimport { SAME_AS_BODY } from \"../theme/fields\";\nimport { TypographyPairing, TYPOGRAPHY_PAIRING_CSS } from \"./typography-pairing\";\n\n/**\n * Pairing sample only — no weight, letter-spacing, line-height, or health\n * scores. Faces load from the site's font route.\n */\nexport const PairingField: UIFieldClientComponent = ({ field }) => {\n const custom = (field.admin?.custom ?? {}) as { fontsBaseUrl?: string };\n const fontsBaseUrl = custom.fontsBaseUrl ?? \"/fonts\";\n const { body, heading } = useFormFields(([fields]) => {\n const record = fields as Record<string, { value?: unknown } | undefined>;\n return {\n body: String(record[\"typography.body\"]?.value ?? \"\"),\n heading: String(record[\"typography.heading\"]?.value ?? SAME_AS_BODY),\n };\n });\n const headingId = heading || body;\n const bodyFont = findFont(body);\n const headingFont = findFont(headingId);\n const ids = [body, headingId].filter(Boolean) as FontId[];\n\n return (\n <div className=\"field-type bl-pairing-field\">\n <style href=\"bl-pairing\" precedence=\"default\">\n {TYPOGRAPHY_PAIRING_CSS}\n </style>\n {ids.length > 0 ? <style data-bl-font-faces=\"\">{fontFaceCss(ids, fontsBaseUrl)}</style> : null}\n <TypographyPairing\n headingFamily={headingFont?.family ?? \"Georgia\"}\n bodyFamily={bodyFont?.family ?? \"system-ui\"}\n />\n </div>\n );\n};\n","import { APPEARANCE_CHOICES, APPEARANCE_FAMILY_COPY } from \"../theme/appearance-labels\";\n\ntype Family = keyof typeof APPEARANCE_CHOICES;\n\nfunction ChoicePreview({ family, value }: { family: Family; value: string }) {\n return (\n <div className=\"bl-appearance__preview\" data-family={family} data-value={value} aria-hidden=\"true\">\n {family === \"defaultTheme\" && value === \"system\" ? (\n <>\n <div className=\"bl-appearance__pane bl-appearance__pane--light\">\n <i />\n <b />\n </div>\n <div className=\"bl-appearance__pane bl-appearance__pane--dark\">\n <i />\n <b />\n </div>\n </>\n ) : family === \"motion\" ? (\n <span className=\"bl-appearance__dot\" />\n ) : family === \"shadow\" ? (\n <div className=\"bl-appearance__lift\" />\n ) : family === \"radius\" ? (\n <>\n <b>Button</b>\n <i className=\"bl-appearance__input\" />\n <i className=\"bl-appearance__card\" />\n </>\n ) : family === \"density\" ? (\n <>\n <b />\n <i className=\"bl-appearance__input\" />\n <i className=\"bl-appearance__card\" />\n </>\n ) : (\n <>\n <i />\n <i />\n <b />\n </>\n )}\n </div>\n );\n}\n\nexport function AppearanceChoices({\n values,\n onChange,\n readOnly,\n}: {\n values: Record<Family, string>;\n onChange: (name: Family, value: string) => void;\n readOnly?: boolean;\n}) {\n return (\n <div className=\"bl-appearance\">\n <header className=\"bl-appearance__intro\">\n <h2>Set the overall feel of your site</h2>\n <p>\n Choose how rounded, elevated, animated, and spacious the interface feels. You don’t manage the\n technical values — the system handles those.\n </p>\n </header>\n {(Object.keys(APPEARANCE_CHOICES) as Family[]).map((family) => {\n const choices = APPEARANCE_CHOICES[family];\n const copy = APPEARANCE_FAMILY_COPY[family];\n return (\n <fieldset key={family} className=\"bl-appearance__family\">\n <legend>\n <strong>{copy.title}</strong>\n <span>{copy.hint}</span>\n </legend>\n <div className=\"bl-appearance__choices\" data-family={family}>\n {Object.entries(choices).map(([value, item]) => {\n const selected = values[family] === value;\n return (\n <label\n key={value}\n className=\"bl-appearance__choice\"\n data-selected={selected ? \"\" : undefined}\n >\n <input\n type=\"radio\"\n name={family}\n value={value}\n checked={selected}\n disabled={readOnly}\n onChange={() => onChange(family, value)}\n />\n <ChoicePreview family={family} value={value} />\n <span className=\"bl-appearance__copy\">\n <strong>\n {item.label}\n {selected ? <em aria-hidden=\"true\">✓</em> : null}\n </strong>\n <span>{item.hint}</span>\n </span>\n </label>\n );\n })}\n </div>\n {family === \"motion\" ? (\n <p className=\"bl-appearance__note\">\n Visitors who prefer reduced motion will still see fewer animations automatically.\n </p>\n ) : null}\n </fieldset>\n );\n })}\n </div>\n );\n}\n\nexport const APPEARANCE_CHOICES_CSS = `\n.bl-appearance{display:flex;flex-direction:column;gap:1.25rem}\n.bl-appearance__intro h2{margin:0;font-size:1.1rem}\n.bl-appearance__intro p{margin:.35rem 0 0;font-size:13px;color:var(--theme-elevation-500,#64748b);max-width:40rem}\n.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)}\n.bl-appearance__family legend{padding:0;margin-bottom:.85rem}\n.bl-appearance__family legend strong{display:block;font-size:14px}\n.bl-appearance__family legend span{display:block;margin-top:.25rem;font-size:12px;color:var(--theme-elevation-500,#64748b);font-weight:400}\n.bl-appearance__choices{display:grid;gap:.6rem}\n.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))}\n.bl-appearance__choices[data-family=radius]{grid-template-columns:repeat(4,minmax(0,1fr))}\n.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}\n.bl-appearance__choice[data-selected]{box-shadow:inset 0 0 0 2px #0f172a;border-color:#0f172a}\n.bl-appearance__choice input{position:absolute;opacity:0;pointer-events:none}\n.bl-appearance__copy{display:flex;flex-direction:column;gap:.2rem}\n.bl-appearance__copy strong{display:flex;justify-content:space-between;align-items:center;gap:.4rem;font-size:14px}\n.bl-appearance__copy em{font-style:normal}\n.bl-appearance__copy span{font-size:12px;color:var(--theme-elevation-500,#64748b)}\n.bl-appearance__note{margin:.75rem 0 0;font-size:12px;color:var(--theme-elevation-500,#64748b)}\n.bl-appearance__preview{--bl-preview-brand:#db0082;--bl-preview-radius:8px;border-radius:10px;background:#f8fafc;overflow:hidden}\n.bl-appearance__preview[data-family=defaultTheme]{display:grid;min-height:5.5rem;padding:.65rem}\n.bl-appearance__preview[data-family=defaultTheme][data-value=system]{grid-template-columns:1fr 1fr;padding:0}\n.bl-appearance__preview[data-family=defaultTheme][data-value=dark]{background:#020617}\n.bl-appearance__pane{display:flex;flex-direction:column;gap:.45rem;padding:.65rem}\n.bl-appearance__pane--dark{background:#020617}\n.bl-appearance__pane i,.bl-appearance__preview[data-family=defaultTheme] > i{display:block;height:.4rem;width:42%;border-radius:99px;background:#0f172a}\n.bl-appearance__pane i + i,.bl-appearance__preview[data-family=defaultTheme] > i + i{width:64%;background:#cbd5e1}\n.bl-appearance__pane--dark i{background:#fff}\n.bl-appearance__pane--dark i + i{background:#334155}\n.bl-appearance__preview[data-family=defaultTheme][data-value=dark] > i{background:#fff}\n.bl-appearance__preview[data-family=defaultTheme][data-value=dark] > i + i{background:#334155}\n.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)}\n.bl-appearance__pane--dark b{background:var(--bl-preview-brand)}\n.bl-appearance__preview[data-family=radius]{display:flex;flex-direction:column;gap:.45rem;padding:.65rem;background:#fff}\n.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)}\n.bl-appearance__input{display:block;height:1.85rem;border:1px solid #cbd5e1;background:#f8fafc;border-radius:var(--bl-preview-radius)}\n.bl-appearance__card{display:block;height:2.4rem;border:1px solid #e2e8f0;background:#fff;border-radius:var(--bl-preview-radius)}\n.bl-appearance__preview[data-value=sharp]{--bl-preview-radius:0}\n.bl-appearance__preview[data-value=subtle]{--bl-preview-radius:6px}\n.bl-appearance__preview[data-value=rounded]{--bl-preview-radius:12px}\n.bl-appearance__preview[data-value=pill]{--bl-preview-radius:999px}\n.bl-appearance__preview[data-value=pill] .bl-appearance__card{--bl-preview-radius:1.25rem}\n.bl-appearance__preview[data-family=shadow]{display:grid;place-items:center;min-height:6.5rem;padding:.85rem}\n.bl-appearance__lift{width:100%;height:3.5rem;border-radius:10px;background:#fff}\n.bl-appearance__preview[data-value=flat] .bl-appearance__lift{border:1px solid #e2e8f0}\n.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)}\n.bl-appearance__preview[data-value=elevated] .bl-appearance__lift{box-shadow:0 10px 30px rgba(15,23,42,.12)}\n.bl-appearance__preview[data-family=motion]{display:flex;align-items:center;min-height:4.5rem;padding:0 .85rem}\n.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}\n.bl-appearance__preview[data-value=snappy] .bl-appearance__dot{animation-duration:1s}\n.bl-appearance__preview[data-value=minimal] .bl-appearance__dot{animation-duration:2.5s}\n@keyframes bl-appear-dot{0%,100%{transform:translateX(0)}50%{transform:translateX(4.5rem)}}\n.bl-appearance__preview[data-family=density]{display:flex;flex-direction:column;background:#f8fafc}\n.bl-appearance__preview[data-value=compact]{gap:.4rem;padding:.55rem}\n.bl-appearance__preview[data-value=default]{gap:.65rem;padding:.8rem}\n.bl-appearance__preview[data-value=spacious]{gap:.9rem;padding:1.05rem}\n.bl-appearance__preview[data-family=density] b{display:block;height:1.7rem;background:var(--bl-preview-brand);border-radius:6px}\n.bl-appearance__preview[data-value=compact] b,.bl-appearance__preview[data-value=compact] .bl-appearance__input{height:1.45rem}\n.bl-appearance__preview[data-value=spacious] b,.bl-appearance__preview[data-value=spacious] .bl-appearance__input{height:2.1rem}\n@media (max-width:720px){\n .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}\n}\n@media (prefers-reduced-motion:reduce){\n .bl-appearance__dot{animation:none}\n}\n`;\n","import { useField } from \"@payloadcms/ui\";\nimport type { GroupFieldClientComponent } from \"payload\";\n\nimport { APPEARANCE_CHOICES } from \"../theme/appearance-labels\";\nimport { AppearanceChoices, APPEARANCE_CHOICES_CSS } from \"./appearance-choices\";\n\ntype Family = keyof typeof APPEARANCE_CHOICES;\n\n/**\n * Named appearance choices. Soft → pill, Gentle → minimal, Balanced →\n * default, Follow device → system. No rem or ms.\n */\nexport const AppearanceField: GroupFieldClientComponent = ({ path, readOnly }) => {\n const defaultTheme = useField<string>({ path: `${path}.defaultTheme` });\n const radius = useField<string>({ path: `${path}.radius` });\n const shadow = useField<string>({ path: `${path}.shadow` });\n const motion = useField<string>({ path: `${path}.motion` });\n const density = useField<string>({ path: `${path}.density` });\n const fields = { defaultTheme, radius, shadow, motion, density };\n\n function onChange(name: Family, value: string) {\n fields[name].setValue(value);\n }\n\n return (\n <div className=\"field-type bl-appearance-field\">\n <style href=\"bl-appearance\" precedence=\"default\">\n {APPEARANCE_CHOICES_CSS}\n </style>\n <AppearanceChoices\n values={{\n defaultTheme: defaultTheme.value ?? \"system\",\n radius: radius.value ?? \"rounded\",\n shadow: shadow.value ?? \"subtle\",\n motion: motion.value ?? \"smooth\",\n density: density.value ?? \"default\",\n }}\n onChange={onChange}\n readOnly={readOnly}\n />\n </div>\n );\n};\n","import { GREY_SCALES, presetHints, type GreyScale } from \"@bison-lab/tokens\";\nimport { useField } from \"@payloadcms/ui\";\nimport type { SelectFieldClientComponent } from \"payload\";\n\nconst FAMILIES = Object.keys(presetHints.greyScale) as GreyScale[];\nconst SWATCHES = [\"50\", \"200\", \"400\", \"700\", \"950\"] as const;\n\nexport function GreyScaleChoices({\n value,\n onChange,\n readOnly,\n}: {\n value: GreyScale;\n onChange: (next: GreyScale) => void;\n readOnly?: boolean;\n}) {\n return (\n <section className=\"bl-grey\">\n <header className=\"bl-grey__head\">\n <div>\n <h2>Gray family</h2>\n <p>Choose the neutral family used for page backgrounds, cards, borders, inputs, and muted text.</p>\n </div>\n <span className=\"bl-grey__badge\">Required</span>\n </header>\n <div className=\"bl-grey__choices\">\n {FAMILIES.map((id) => {\n const copy = presetHints.greyScale[id];\n const selected = value === id;\n return (\n <label key={id} className=\"bl-grey__choice\" data-selected={selected ? \"\" : undefined}>\n <input\n type=\"radio\"\n name=\"greyScale\"\n value={id}\n checked={selected}\n disabled={readOnly}\n onChange={() => onChange(id)}\n />\n <span className=\"bl-grey__ramp\" aria-hidden=\"true\">\n {SWATCHES.map((step) => (\n <i key={step} style={{ background: `hsl(${GREY_SCALES[id][step]})` }} />\n ))}\n </span>\n <strong>\n {copy.label}\n {selected ? <em aria-hidden=\"true\">✓</em> : null}\n </strong>\n <span>{copy.hint}</span>\n </label>\n );\n })}\n </div>\n </section>\n );\n}\n\n/**\n * Visual picker over the five grey-scale presets. Replaces the stock select\n * on Colors; the stored value is still `colors.greyScale`.\n */\nexport const GreyScaleField: SelectFieldClientComponent = ({ path, readOnly }) => {\n const { value, setValue } = useField<GreyScale>({ path });\n\n return (\n <div className=\"field-type bl-grey-field\">\n <style href=\"bl-grey\" precedence=\"default\">\n {GREY_SCALE_FIELD_CSS}\n </style>\n <GreyScaleChoices value={value ?? \"slate\"} onChange={setValue} readOnly={readOnly} />\n </div>\n );\n};\n\nexport const GREY_SCALE_FIELD_CSS = `\n.bl-grey{margin:0 0 1.75rem}\n.bl-grey__head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;margin-bottom:.85rem}\n.bl-grey__head h2{margin:0;font-size:1.1rem}\n.bl-grey__head p{margin:.35rem 0 0;max-width:40rem;font-size:13px;color:var(--theme-elevation-500,#64748b)}\n.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)}\n.bl-grey__choices{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:.6rem}\n.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}\n.bl-grey__choice[data-selected]{box-shadow:inset 0 0 0 2px #0f172a;border-color:#0f172a}\n.bl-grey__choice input{position:absolute;opacity:0;pointer-events:none}\n.bl-grey__ramp{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:3px}\n.bl-grey__ramp i{display:block;height:2rem;border-radius:6px}\n.bl-grey__choice strong{display:flex;justify-content:space-between;align-items:center;gap:.4rem;font-size:14px}\n.bl-grey__choice em{font-style:normal}\n.bl-grey__choice > span{font-size:12px;color:var(--theme-elevation-500,#64748b)}\n[data-bl-theme-preview=dark] .bl-grey__head p,[data-bl-theme-preview=dark] .bl-grey__choice > span{color:#94a3b8}\n[data-bl-theme-preview=dark] .bl-grey__choice{background:#0f172a;border-color:#334155;color:#f8fafc}\n[data-bl-theme-preview=dark] .bl-grey__choice[data-selected]{box-shadow:inset 0 0 0 2px #f8fafc;border-color:#f8fafc}\n[data-bl-theme-preview=dark] .bl-grey__badge{background:#1e293b;color:#94a3b8}\n@media (max-width:720px){.bl-grey__choices{grid-template-columns:1fr}}\n`;\n","import type { UIFieldClientComponent } from \"payload\";\n\n/**\n * Contrast used to be a Colors-tab list. Readability now lives on each\n * color card: Needs attention opens a popup. This field stays in the\n * schema so `contrastTarget` is still a Theme option; it paints nothing.\n */\nexport const ContrastReport: UIFieldClientComponent = () => null;\n","import type { UIFieldClientComponent } from \"payload\";\n\n/**\n * Unnamed UI heading between Theme fields. No document path.\n */\nexport const SectionHeading: UIFieldClientComponent = ({ field }) => {\n const custom = (field.admin?.custom ?? {}) as { title?: string; hint?: string };\n if (!custom.title) return null;\n return (\n <header className=\"bl-section-heading\">\n <style href=\"bl-section-heading\" precedence=\"default\">\n {CSS}\n </style>\n <h2>{custom.title}</h2>\n {custom.hint ? <p>{custom.hint}</p> : null}\n </header>\n );\n};\n\nconst CSS = `\n.bl-section-heading{margin:1.25rem 0 .65rem}\n.bl-section-heading:first-child{margin-top:0}\n.bl-section-heading h2{margin:0;font-size:1.1rem}\n.bl-section-heading p{margin:.35rem 0 0;font-size:13px;color:var(--theme-elevation-500,#64748b);max-width:40rem}\n`;\n","import { SYSTEM_COLOR_KEYS, type ThemeDoc, type ThemeLibraryColorDoc } from \"./types\";\nimport type { ThemeChild } from \"./publish\";\n\n/** Payload may hand an empty array field as `{}`. `.map` on that crashes the Theme form. */\nexport function libraryRows(value: unknown): ThemeLibraryColorDoc[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction colorLine(name: string, before: unknown, after: unknown): string | null {\n const a = before && typeof before === \"object\" ? before : { hex: before };\n const b = after && typeof after === \"object\" ? after : { hex: after };\n const left = a as { hex?: unknown; sourceStep?: unknown };\n const right = b as { hex?: unknown; sourceStep?: unknown };\n if (left.hex === right.hex && String(left.sourceStep) === String(right.sourceStep)) return null;\n return `${name}: ${String(left.hex ?? \"—\")} → ${String(right.hex ?? \"—\")}`;\n}\n\n/**\n * Human lines for the Publish confirmation. Only this child's\n * differences; other responsibilities are not listed.\n */\nexport function themeChildChanges(child: ThemeChild, published: ThemeDoc, incoming: ThemeDoc): string[] {\n if (child === \"colors\") {\n const lines: string[] = [];\n for (const key of SYSTEM_COLOR_KEYS) {\n const line = colorLine(\n key[0].toUpperCase() + key.slice(1),\n published.colors?.brand?.[key],\n incoming.colors?.brand?.[key],\n );\n if (line) lines.push(line);\n }\n const beforeKeys = libraryRows(published.colors?.library)\n .map((row) => row.key)\n .join(\", \");\n const afterKeys = libraryRows(incoming.colors?.library)\n .map((row) => row.key)\n .join(\", \");\n if (beforeKeys !== afterKeys) lines.push(`Custom colors: ${beforeKeys || \"none\"} → ${afterKeys || \"none\"}`);\n if ((published.colors?.greyScale ?? \"\") !== (incoming.colors?.greyScale ?? \"\")) {\n lines.push(`Gray family: ${published.colors?.greyScale ?? \"—\"} → ${incoming.colors?.greyScale ?? \"—\"}`);\n }\n return lines;\n }\n if (child === \"typography\") {\n const lines: string[] = [];\n if ((published.typography?.heading ?? \"\") !== (incoming.typography?.heading ?? \"\")) {\n lines.push(`Heading: ${published.typography?.heading || \"same as body\"} → ${incoming.typography?.heading || \"same as body\"}`);\n }\n if ((published.typography?.body ?? \"\") !== (incoming.typography?.body ?? \"\")) {\n lines.push(`Body: ${published.typography?.body ?? \"—\"} → ${incoming.typography?.body ?? \"—\"}`);\n }\n return lines;\n }\n if (child === \"appearance\") {\n const keys = [\"defaultTheme\", \"radius\", \"shadow\", \"motion\", \"density\"] as const;\n return keys.flatMap((key) => {\n const left = published.appearance?.[key] ?? \"\";\n const right = incoming.appearance?.[key] ?? \"\";\n return left === right ? [] : [`${key}: ${left || \"—\"} → ${right || \"—\"}`];\n });\n }\n const lines: string[] = [];\n if (uploadId(published.logo) !== uploadId(incoming.logo)) lines.push(\"Logo updated\");\n if (uploadId(published.favicon) !== uploadId(incoming.favicon)) lines.push(\"Favicon updated\");\n if (uploadId(published.logoMark) !== uploadId(incoming.logoMark)) lines.push(\"Mobile menu logo updated\");\n return lines;\n}\n\nfunction uploadId(value: ThemeDoc[\"logo\"]): string {\n if (value && typeof value === \"object\" && value.id != null) return String(value.id);\n if (typeof value === \"string\" || typeof value === \"number\") return String(value);\n return \"\";\n}\n","import type { ThemeDoc } from \"./types\";\n\nexport const THEME_CHILDREN = [\"colors\", \"typography\", \"appearance\", \"identity\"] as const;\n\nexport type ThemeChild = (typeof THEME_CHILDREN)[number];\n\n/**\n * Publishing a child writes that slice only. Other responsibilities stay\n * as they already are on the stored document. Unsaved form state on a\n * different child never goes live. A first save (no stored row) still\n * writes only the named child; `getPublishedTheme` fills the rest from\n * the seed.\n */\nexport function publishThemeChild(stored: ThemeDoc, incoming: ThemeDoc, child: ThemeChild): ThemeDoc {\n if (child === \"colors\") return { ...stored, colors: incoming.colors };\n if (child === \"typography\") return { ...stored, typography: incoming.typography };\n if (child === \"appearance\") return { ...stored, appearance: incoming.appearance };\n return { ...stored, logo: incoming.logo, favicon: incoming.favicon, logoMark: incoming.logoMark };\n}\n\nexport function isThemeChild(value: unknown): value is ThemeChild {\n return typeof value === \"string\" && (THEME_CHILDREN as readonly string[]).includes(value);\n}\n","import type { FillLabelWarning } from \"../theme/readability\";\nimport {\n hardToReadModes,\n hslCss,\n READABILITY_PUBLISH_NOTE,\n readabilityHeadline,\n} from \"../theme/readability\";\n\nexport interface PublishConfirmProps {\n childLabel: string;\n changes: string[];\n shorts?: FillLabelWarning[];\n onKeepEditing: () => void;\n onPublish: () => void;\n busy?: boolean;\n}\n\nconst OTHER_CHILDREN: Record<string, string> = {\n Colors: \"Typography, Appearance, and Identity stay as they are.\",\n Typography: \"Colors, Appearance, and Identity stay as they are.\",\n Appearance: \"Colors, Typography, and Identity stay as they are.\",\n Identity: \"Colors, Typography, and Appearance stay as they are.\",\n};\n\nexport function PublishConfirm({\n childLabel,\n changes,\n shorts,\n onKeepEditing,\n onPublish,\n busy,\n}: PublishConfirmProps) {\n const remaining = shorts?.filter((item) => !item.meets) ?? [];\n return (\n <div className=\"bl-publish\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"bl-publish-title\">\n <div className=\"bl-publish__panel\">\n <h2 id=\"bl-publish-title\">Publish {childLabel}</h2>\n <p>\n These {childLabel.toLowerCase()} changes go live now.{\" \"}\n {OTHER_CHILDREN[childLabel] ?? \"The other Theme sections stay as they are.\"}\n </p>\n {changes.length === 0 ? (\n <p className=\"bl-publish__empty\">No changes to publish.</p>\n ) : (\n <ul>\n {changes.map((change) => (\n <li key={change}>{change}</li>\n ))}\n </ul>\n )}\n {remaining.length > 0 ? (\n <div className=\"bl-publish__shorts\">\n <strong>Some colors may be hard to read</strong>\n <ul>\n {remaining.map((item) => (\n <li key={item.id}>\n <span\n aria-hidden=\"true\"\n className=\"bl-publish__swatch\"\n style={{ background: hslCss(item.light.fill), color: hslCss(item.light.label) }}\n >\n Aa\n </span>\n <span>\n {readabilityHeadline(item).replace(/^Text on /, \"\")} {hardToReadModes(item)}.\n </span>\n </li>\n ))}\n </ul>\n <p>{READABILITY_PUBLISH_NOTE}</p>\n </div>\n ) : null}\n <div className=\"bl-publish__actions\">\n <button type=\"button\" onClick={onKeepEditing}>\n Keep editing\n </button>\n <button type=\"button\" disabled={busy || changes.length === 0} onClick={onPublish}>\n {remaining.length > 0 ? \"Publish anyway\" : \"Publish\"}\n </button>\n </div>\n </div>\n </div>\n );\n}\n\nexport const PUBLISH_CONFIRM_CSS = `\n.bl-publish{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:1rem;background:rgba(15,23,42,.45)}\n.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)}\n.bl-publish__panel h2{margin:0}\n.bl-publish__panel p{margin:.5rem 0 0;color:#475569}\n.bl-publish__empty{font-style:italic}\n.bl-publish__panel > ul{margin:.75rem 0 0;padding-left:1.1rem}\n.bl-publish__shorts{margin-top:1rem;padding:.85rem .9rem;border-radius:10px;background:#fffbeb}\n.bl-publish__shorts strong{display:block}\n.bl-publish__shorts ul{margin:.65rem 0 0;padding:0;list-style:none}\n.bl-publish__shorts li{display:flex;gap:.65rem;align-items:center;margin-top:.45rem;font-size:13px}\n.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}\n.bl-publish__shorts p{margin:.65rem 0 0;font-size:13px}\n.bl-publish__actions{display:flex;justify-content:flex-end;gap:.6rem;margin-top:1.1rem}\n.bl-publish__actions button{min-height:2.25rem;padding:.4rem .9rem;border-radius:8px;border:1px solid #cbd5e1;background:#fff;font:inherit;cursor:pointer}\n.bl-publish__actions button:last-child{border:0;background:#0f172a;color:#fff;font-weight:600}\n.bl-publish__actions button:disabled{opacity:.45;cursor:not-allowed}\n`;\n","import { THEME_CHILDREN, type ThemeChild } from \"../theme/publish\";\n\nexport const THEME_TAB_LABELS = {\n colors: \"Colors\",\n typography: \"Typography\",\n appearance: \"Appearance\",\n identity: \"Identity\",\n} as const satisfies Record<ThemeChild, string>;\n\n/**\n * The selected Payload field tab on Theme. Defaults to Colors when the\n * tabs have not painted yet (SSR, first paint).\n */\nexport function readActiveThemeTab(root: ParentNode | null | undefined = typeof document === \"undefined\" ? null : document): ThemeChild {\n if (!root) return \"colors\";\n const selected =\n root.querySelector('.tabs-field__tab-button[aria-selected=\"true\"]') ??\n root.querySelector(\".tabs-field__tab-button--active\") ??\n root.querySelector('[role=\"tab\"][aria-selected=\"true\"]');\n const text = selected?.textContent?.replace(/\\s+/g, \" \").trim();\n return THEME_CHILDREN.find((id) => THEME_TAB_LABELS[id] === text) ?? \"colors\";\n}\n","import { useField, useForm, useFormFields } from \"@payloadcms/ui\";\nimport type { UIFieldClientComponent } from \"payload\";\nimport { useMemo, useState } from \"react\";\n\nimport { SYSTEM_COLOR_COPY } from \"../theme/appearance-labels\";\nimport { libraryRows, themeChildChanges } from \"../theme/changes\";\nimport { isThemeChild, type ThemeChild } from \"../theme/publish\";\nimport { fillLabelWarning, type FillLabelWarning } from \"../theme/readability\";\nimport { SYSTEM_COLOR_KEYS, type ThemeColorDoc, type ThemeDoc, type ThemeLibraryColorDoc } from \"../theme/types\";\nimport { ThemePreviewToggle, THEME_PREVIEW_TOGGLE_CSS } from \"./preview-mode\";\nimport { PublishConfirm, PUBLISH_CONFIRM_CSS } from \"./publish-confirm\";\nimport { THEME_TAB_LABELS } from \"./theme-tab\";\n\nfunction asColor(value: unknown): ThemeColorDoc | undefined {\n if (value && typeof value === \"object\") return value as ThemeColorDoc;\n if (typeof value === \"string\") return { hex: value };\n return undefined;\n}\n\nfunction formDoc(fields: Record<string, { value?: unknown } | undefined>): ThemeDoc {\n const brand = Object.fromEntries(\n SYSTEM_COLOR_KEYS.map((key) => [\n key,\n {\n hex: fields[`colors.brand.${key}.hex`]?.value as string | undefined,\n sourceStep: fields[`colors.brand.${key}.sourceStep`]?.value as ThemeColorDoc[\"sourceStep\"],\n },\n ]),\n ) as NonNullable<NonNullable<ThemeDoc[\"colors\"]>[\"brand\"]>;\n return {\n colors: {\n brand,\n library: libraryRows(fields[\"colors.library\"]?.value),\n greyScale: fields[\"colors.greyScale\"]?.value as NonNullable<ThemeDoc[\"colors\"]>[\"greyScale\"],\n },\n typography: {\n body: fields[\"typography.body\"]?.value as string | undefined,\n heading: fields[\"typography.heading\"]?.value as string | undefined,\n },\n appearance: {\n defaultTheme: fields[\"appearance.defaultTheme\"]?.value as NonNullable<ThemeDoc[\"appearance\"]>[\"defaultTheme\"],\n radius: fields[\"appearance.radius\"]?.value as NonNullable<ThemeDoc[\"appearance\"]>[\"radius\"],\n shadow: fields[\"appearance.shadow\"]?.value as NonNullable<ThemeDoc[\"appearance\"]>[\"shadow\"],\n motion: fields[\"appearance.motion\"]?.value as NonNullable<ThemeDoc[\"appearance\"]>[\"motion\"],\n density: fields[\"appearance.density\"]?.value as NonNullable<ThemeDoc[\"appearance\"]>[\"density\"],\n },\n logo: fields.logo?.value as ThemeDoc[\"logo\"],\n favicon: fields.favicon?.value as ThemeDoc[\"favicon\"],\n logoMark: fields.logoMark?.value as ThemeDoc[\"logoMark\"],\n };\n}\n\nfunction publishedDoc(fields: Record<string, { initialValue?: unknown; value?: unknown } | undefined>): ThemeDoc {\n return formDoc(\n Object.fromEntries(Object.entries(fields).map(([path, field]) => [path, { value: field?.initialValue }])),\n );\n}\n\nfunction shortsFrom(doc: ThemeDoc): FillLabelWarning[] {\n const brand = doc.colors?.brand ?? {};\n const custom = libraryRows(doc.colors?.library);\n return [\n ...SYSTEM_COLOR_KEYS.flatMap((key) => {\n const hex = asColor(brand[key])?.hex;\n const warning = typeof hex === \"string\" ? fillLabelWarning(key, SYSTEM_COLOR_COPY[key].label, hex) : null;\n return warning ? [warning] : [];\n }),\n ...custom.flatMap((row, index) => {\n const warning = row.hex ? fillLabelWarning(`library-${index}`, row.label ?? row.key ?? \"Custom\", row.hex) : null;\n return warning ? [warning] : [];\n }),\n ];\n}\n\n/**\n * Publish for the open Theme tab. Lives in Payload's document Save\n * slot (`ThemeSaveButton`) so the header matches a Pages document.\n * Sets `publishChild` then submits; `beforeChange` writes only that slice.\n */\nexport function ThemePublishControls({ child }: { child: ThemeChild }) {\n const label = THEME_TAB_LABELS[child];\n const { submit } = useForm();\n const publishChild = useField<string>({ path: \"publishChild\" });\n const snapshot = useFormFields(([fields]) => {\n const record = fields as Record<string, { initialValue?: unknown; value?: unknown } | undefined>;\n return { incoming: formDoc(record), published: publishedDoc(record) };\n });\n const [open, setOpen] = useState(false);\n const [busy, setBusy] = useState(false);\n\n const changes = useMemo(\n () => themeChildChanges(child, snapshot.published, snapshot.incoming),\n [child, snapshot],\n );\n const shorts = child === \"colors\" ? shortsFrom(snapshot.incoming) : undefined;\n\n async function publish() {\n setBusy(true);\n publishChild.setValue(child);\n try {\n await submit();\n setOpen(false);\n } finally {\n setBusy(false);\n }\n }\n\n return (\n <div className=\"bl-publish-child\">\n <style href=\"bl-publish\" precedence=\"default\">\n {PUBLISH_CONFIRM_CSS}\n </style>\n <style href=\"bl-publish-child\" precedence=\"default\">\n {`${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}`}\n </style>\n {child === \"colors\" ? <ThemePreviewToggle /> : null}\n <button type=\"button\" className=\"bl-publish-child__button\" onClick={() => setOpen(true)}>\n Publish {label}\n </button>\n {open ? (\n <PublishConfirm\n childLabel={label}\n changes={changes}\n shorts={shorts}\n busy={busy}\n onKeepEditing={() => setOpen(false)}\n onPublish={() => {\n void publish();\n }}\n />\n ) : null}\n </div>\n );\n}\n\n/** @deprecated Theme publish is the document Save button */\nexport const PublishChild: UIFieldClientComponent = ({ field }) => {\n const child = (field.admin?.custom as { child?: string } | undefined)?.child;\n return <ThemePublishControls child={isThemeChild(child) ? child : \"colors\"} />;\n};\n","import { useEffect, useState } from \"react\";\n\nimport type { ThemeChild } from \"../theme/publish\";\nimport { ThemePublishControls } from \"./publish-child\";\nimport { readActiveThemeTab } from \"./theme-tab\";\n\n/**\n * Payload's document Save slot — the same control row as Pages'\n * \"Publish changes\". Theme has no drafts; this publishes only the\n * open field tab. The tab is read after mount so SSR matches.\n */\nexport function ThemeSaveButton() {\n const [child, setChild] = useState<ThemeChild>(\"colors\");\n\n useEffect(() => {\n const sync = () => setChild(readActiveThemeTab());\n sync();\n const tabs = document.querySelector(\".tabs-field\");\n const target = tabs ?? document.body;\n const observer = new MutationObserver(sync);\n observer.observe(target, {\n subtree: true,\n attributes: true,\n attributeFilter: [\"aria-selected\", \"class\"],\n childList: true,\n });\n target.addEventListener(\"click\", sync);\n return () => {\n observer.disconnect();\n target.removeEventListener(\"click\", sync);\n };\n }, []);\n\n return <ThemePublishControls child={child} />;\n}\n\n/** Import-map alias — Theme still points `SaveButton` at this name. */\nexport const HiddenSaveButton = ThemeSaveButton;\n","import type { UIFieldClientComponent } from \"payload\";\n\nimport type { ThemeIdentityFallback } from \"../theme/identity\";\n\n/**\n * Identity child when the site has not registered a cupboard yet.\n * Shows the fallback lockup and mark; uploads wait on `logo.collection`.\n */\nexport const IdentityFallback: UIFieldClientComponent = ({ field }) => {\n const fallback = (field.admin?.custom as { fallback?: ThemeIdentityFallback } | undefined)?.fallback;\n return (\n <section className=\"bl-identity-fallback\">\n <style href=\"bl-identity-fallback\" precedence=\"default\">\n {CSS}\n </style>\n <header>\n <h2>Site identity</h2>\n <p>Today's lockup and mark until a brand editor uploads replacements.</p>\n </header>\n <div className=\"bl-identity-fallback__grid\">\n {fallback?.lockup ? (\n <figure>\n <img src={fallback.lockup.url} alt={fallback.lockup.alt} />\n <figcaption>Lockup</figcaption>\n </figure>\n ) : null}\n {fallback?.mark ? (\n <figure>\n <img src={fallback.mark.url} alt={fallback.mark.alt} />\n <figcaption>Mark</figcaption>\n </figure>\n ) : null}\n </div>\n </section>\n );\n};\n\nconst CSS = `\n.bl-identity-fallback header h2{margin:0;font-size:1.1rem}\n.bl-identity-fallback header p{margin:.35rem 0 1rem;color:var(--theme-elevation-600,#475569)}\n.bl-identity-fallback__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:1rem}\n.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)}\n.bl-identity-fallback img{display:block;max-width:100%;height:4rem;object-fit:contain}\n.bl-identity-fallback figcaption{margin-top:.5rem;font-size:.8rem;color:var(--theme-elevation-600,#475569)}\n`;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { SHADE_STEPS, createColorScale, isThemeHex } from "@bison-lab/tokens";
|
|
2
|
+
//#region src/theme/fields.ts
|
|
3
|
+
const THEME_COLOR_FIELD = "@bison-lab/payload-core/admin#ColorField";
|
|
4
|
+
const THEME_COLOR_SCALE_FIELD = "@bison-lab/payload-core/admin#ColorScaleField";
|
|
5
|
+
const THEME_LIBRARY_FIELD = "@bison-lab/payload-core/admin#LibraryField";
|
|
6
|
+
const THEME_FONT_FIELD = "@bison-lab/payload-core/admin#FontField";
|
|
7
|
+
const THEME_PAIRING_FIELD = "@bison-lab/payload-core/admin#PairingField";
|
|
8
|
+
const THEME_APPEARANCE_FIELD = "@bison-lab/payload-core/admin#AppearanceField";
|
|
9
|
+
const THEME_GREY_SCALE_FIELD = "@bison-lab/payload-core/admin#GreyScaleField";
|
|
10
|
+
const THEME_CONTRAST_REPORT = "@bison-lab/payload-core/admin#ContrastReport";
|
|
11
|
+
const THEME_SECTION_HEADING = "@bison-lab/payload-core/admin#SectionHeading";
|
|
12
|
+
const THEME_PUBLISH_FIELD = "@bison-lab/payload-core/admin#PublishChild";
|
|
13
|
+
const THEME_SAVE_BUTTON = "@bison-lab/payload-core/admin#HiddenSaveButton";
|
|
14
|
+
const THEME_IDENTITY_FALLBACK = "@bison-lab/payload-core/admin#IdentityFallback";
|
|
15
|
+
function headingSelectValue(heading) {
|
|
16
|
+
return heading ?? "";
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/theme/map.ts
|
|
20
|
+
function pick(value, fallback) {
|
|
21
|
+
return value == null ? fallback : value;
|
|
22
|
+
}
|
|
23
|
+
function headingFromDoc(heading, seed) {
|
|
24
|
+
if (heading === void 0) return seed;
|
|
25
|
+
if (heading === null || heading === "") return null;
|
|
26
|
+
return heading;
|
|
27
|
+
}
|
|
28
|
+
function hexFromColor(value, fallback) {
|
|
29
|
+
if (typeof value === "string") return value || fallback;
|
|
30
|
+
return pick(value?.hex, fallback);
|
|
31
|
+
}
|
|
32
|
+
function colorDocFromHex(hex, sourceStep = 500) {
|
|
33
|
+
return colorDocFromState(createColorScale(hex, sourceStep));
|
|
34
|
+
}
|
|
35
|
+
function colorDocFromState(state) {
|
|
36
|
+
return {
|
|
37
|
+
hex: state.hex,
|
|
38
|
+
sourceStep: state.sourceStep,
|
|
39
|
+
scale: state.scale,
|
|
40
|
+
stale: state.stale,
|
|
41
|
+
include: Array.isArray(state.include) ? "custom" : state.include,
|
|
42
|
+
includedSteps: Array.isArray(state.include) ? [...state.include] : void 0
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Nested Theme document → flat `ThemeConfig`. Null or missing editor
|
|
47
|
+
* fields take the seed; `darkSelector` and `fontWeights` always come from
|
|
48
|
+
* the seed. Reads the BIS-87 groups and the pre-BIS-87 flat fields.
|
|
49
|
+
*/
|
|
50
|
+
function themeConfigFromDoc(doc, seed) {
|
|
51
|
+
const brand = doc?.colors?.brand ?? doc?.brand;
|
|
52
|
+
const typography = doc?.typography ?? doc?.fonts;
|
|
53
|
+
const appearance = doc?.appearance;
|
|
54
|
+
return {
|
|
55
|
+
brandPrimary: hexFromColor(brand?.primary, seed.brandPrimary),
|
|
56
|
+
brandSecondary: hexFromColor(brand?.secondary, seed.brandSecondary),
|
|
57
|
+
brandAccent: hexFromColor(brand?.accent, seed.brandAccent),
|
|
58
|
+
brandHighlight: hexFromColor(brand?.highlight, seed.brandHighlight),
|
|
59
|
+
brandSuccess: hexFromColor(brand?.success, seed.brandSuccess),
|
|
60
|
+
brandDestructive: hexFromColor(brand?.destructive, seed.brandDestructive ?? "") || seed.brandDestructive,
|
|
61
|
+
greyScale: pick(doc?.colors?.greyScale ?? doc?.greyScale, seed.greyScale),
|
|
62
|
+
radius: pick(appearance?.radius ?? doc?.radius, seed.radius),
|
|
63
|
+
shadow: pick(appearance?.shadow ?? doc?.shadow, seed.shadow),
|
|
64
|
+
motion: pick(appearance?.motion ?? doc?.motion, seed.motion),
|
|
65
|
+
density: pick(appearance?.density ?? doc?.density, seed.density),
|
|
66
|
+
defaultTheme: pick(appearance?.defaultTheme ?? doc?.defaultTheme, seed.defaultTheme),
|
|
67
|
+
fontBody: pick(typography?.body, seed.fontBody),
|
|
68
|
+
fontHeading: headingFromDoc(typography?.heading, seed.fontHeading),
|
|
69
|
+
darkSelector: seed.darkSelector,
|
|
70
|
+
fontWeights: seed.fontWeights
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** The document `seedTheme` writes so a fresh Global matches the seed config. */
|
|
74
|
+
function docFromConfig(config, destructive) {
|
|
75
|
+
const destructiveHex = destructive ?? config.brandDestructive;
|
|
76
|
+
if (!destructiveHex) throw new Error("docFromConfig requires a destructive hex on the config or as the second argument");
|
|
77
|
+
return {
|
|
78
|
+
colors: {
|
|
79
|
+
brand: {
|
|
80
|
+
primary: colorDocFromHex(config.brandPrimary),
|
|
81
|
+
secondary: colorDocFromHex(config.brandSecondary),
|
|
82
|
+
accent: colorDocFromHex(config.brandAccent),
|
|
83
|
+
highlight: colorDocFromHex(config.brandHighlight),
|
|
84
|
+
success: colorDocFromHex(config.brandSuccess),
|
|
85
|
+
destructive: colorDocFromHex(destructiveHex)
|
|
86
|
+
},
|
|
87
|
+
library: [],
|
|
88
|
+
greyScale: config.greyScale
|
|
89
|
+
},
|
|
90
|
+
typography: {
|
|
91
|
+
body: config.fontBody,
|
|
92
|
+
heading: config.fontHeading
|
|
93
|
+
},
|
|
94
|
+
appearance: {
|
|
95
|
+
defaultTheme: config.defaultTheme,
|
|
96
|
+
radius: config.radius,
|
|
97
|
+
shadow: config.shadow,
|
|
98
|
+
motion: config.motion,
|
|
99
|
+
density: config.density
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function validateThemeHex(value) {
|
|
104
|
+
if (!isThemeHex(value)) return "Enter a six-digit hex colour like #1e3a5f";
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
function validateSourceIncluded(value, { siblingData }) {
|
|
108
|
+
const include = siblingData?.include;
|
|
109
|
+
if (include === "all" || include === "source") return true;
|
|
110
|
+
const source = Number(siblingData?.sourceStep);
|
|
111
|
+
const steps = Array.isArray(value) ? value.map(Number) : [];
|
|
112
|
+
if (!SHADE_STEPS.includes(source)) return true;
|
|
113
|
+
if (!steps.includes(source)) return "The source step cannot be excluded";
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/theme/types.ts
|
|
118
|
+
const THEME_SLUG = "theme";
|
|
119
|
+
const SYSTEM_COLOR_KEYS = [
|
|
120
|
+
"primary",
|
|
121
|
+
"secondary",
|
|
122
|
+
"accent",
|
|
123
|
+
"highlight",
|
|
124
|
+
"success",
|
|
125
|
+
"destructive"
|
|
126
|
+
];
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/theme/library.ts
|
|
129
|
+
const STEP_SET = new Set(SHADE_STEPS);
|
|
130
|
+
/**
|
|
131
|
+
* Page editors pick `coral-400`, never a raw hex. Rewrite every use of
|
|
132
|
+
* `fromKey` onto `toKey` at the same step: `coral-400` → `highlight-400`.
|
|
133
|
+
* A bare key (`coral`) becomes `toKey`. Anything else is left alone.
|
|
134
|
+
*/
|
|
135
|
+
function rewriteColorToken(token, fromKey, toKey) {
|
|
136
|
+
if (token === fromKey) return toKey;
|
|
137
|
+
const dash = token.lastIndexOf("-");
|
|
138
|
+
if (dash <= 0) return token;
|
|
139
|
+
const key = token.slice(0, dash);
|
|
140
|
+
const step = Number(token.slice(dash + 1));
|
|
141
|
+
if (key !== fromKey || !STEP_SET.has(step)) return token;
|
|
142
|
+
return `${toKey}-${step}`;
|
|
143
|
+
}
|
|
144
|
+
function themeColorKeys(doc) {
|
|
145
|
+
const custom = (Array.isArray(doc?.colors?.library) ? doc.colors.library : []).map((row) => row.key?.trim()).filter((key) => Boolean(key));
|
|
146
|
+
return [...SYSTEM_COLOR_KEYS, ...custom];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Remove a custom color. Unused: omit `replacement` and the row is gone.
|
|
150
|
+
* In use: pass another system or custom key; the caller rewrites page
|
|
151
|
+
* tokens with `rewriteColorToken`. After this, no library row still
|
|
152
|
+
* has `key`.
|
|
153
|
+
*/
|
|
154
|
+
function deleteLibraryColor(doc, key, replacement) {
|
|
155
|
+
const library = [...Array.isArray(doc.colors?.library) ? doc.colors.library : []];
|
|
156
|
+
const index = library.findIndex((row) => row.key === key);
|
|
157
|
+
if (index === -1) throw new Error(`No custom color named ${JSON.stringify(key)}`);
|
|
158
|
+
library.splice(index, 1);
|
|
159
|
+
const next = {
|
|
160
|
+
...doc,
|
|
161
|
+
colors: {
|
|
162
|
+
...doc.colors,
|
|
163
|
+
library
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
if (replacement !== void 0) {
|
|
167
|
+
if (replacement === key) throw new Error("Replacement must be a different color");
|
|
168
|
+
if (!themeColorKeys(next).includes(replacement)) throw new Error(`Replacement ${JSON.stringify(replacement)} is not a system or remaining custom color`);
|
|
169
|
+
}
|
|
170
|
+
return next;
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/theme/identity.ts
|
|
174
|
+
function fromUpload(value, fallback) {
|
|
175
|
+
if (value && typeof value === "object") {
|
|
176
|
+
const upload = value;
|
|
177
|
+
if (upload.url) return {
|
|
178
|
+
url: upload.url,
|
|
179
|
+
alt: upload.alt ?? fallback?.alt ?? ""
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return fallback ?? null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Uploaded logo, favicon, and mobile-menu mark, or the fallback the site
|
|
186
|
+
* passed into `createTheme`. Empty fields keep the fallback;
|
|
187
|
+
* `--color-primary-mark` is not a Theme field.
|
|
188
|
+
*/
|
|
189
|
+
function resolveThemeIdentity(doc, fallback) {
|
|
190
|
+
return {
|
|
191
|
+
lockup: fromUpload(doc?.logo, fallback?.lockup),
|
|
192
|
+
mark: fromUpload(doc?.logoMark, fallback?.mark),
|
|
193
|
+
favicon: fromUpload(doc?.favicon, fallback?.favicon)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
export { headingSelectValue as C, THEME_SECTION_HEADING as S, THEME_IDENTITY_FALLBACK as _, SYSTEM_COLOR_KEYS as a, THEME_PUBLISH_FIELD as b, themeConfigFromDoc as c, THEME_APPEARANCE_FIELD as d, THEME_COLOR_FIELD as f, THEME_GREY_SCALE_FIELD as g, THEME_FONT_FIELD as h, themeColorKeys as i, validateSourceIncluded as l, THEME_CONTRAST_REPORT as m, deleteLibraryColor as n, THEME_SLUG as o, THEME_COLOR_SCALE_FIELD as p, rewriteColorToken as r, docFromConfig as s, resolveThemeIdentity as t, validateThemeHex as u, THEME_LIBRARY_FIELD as v, THEME_SAVE_BUTTON as x, THEME_PAIRING_FIELD as y };
|
|
198
|
+
|
|
199
|
+
//# sourceMappingURL=identity-Bg-G05mb.mjs.map
|