@loworbitstudio/visor 1.29.0 → 1.30.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.4.0",
3
- "generated_at": "2026-09-24T04:59:55.147Z",
3
+ "generated_at": "2026-10-03T18:10:41.409Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "changeType": "current",
package/dist/index.js CHANGED
@@ -8761,6 +8761,83 @@ import { existsSync as existsSync31, mkdirSync as mkdirSync14, readFileSync as r
8761
8761
  import { dirname as dirname15, isAbsolute as isAbsolute7, resolve as resolve21 } from "path";
8762
8762
  var ICON_X = `React.createElement("svg", { viewBox: "0 0 256 256", width: "1em", height: "1em", fill: "currentColor", "aria-hidden": true }, React.createElement("path", { d: "M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z" }))`;
8763
8763
  var FIXTURES = {
8764
+ // VI-680: hairline consumers. `__mod` is the component module, so a fixture can
8765
+ // reach the compound parts a single export does not expose.
8766
+ table: {
8767
+ default: {
8768
+ export: "Table",
8769
+ props: `(function () {
8770
+ var h = React.createElement;
8771
+ var row = function (cells, key) {
8772
+ return h(__mod.TableRow, { key: key }, cells.map(function (c, i) { return h(__mod.TableCell, { key: i }, c); }));
8773
+ };
8774
+ return {
8775
+ children: [
8776
+ h(__mod.TableHeader, { key: "h" }, h(__mod.TableRow, null, ["Name", "Role", "Status"].map(function (t) { return h(__mod.TableHead, { key: t }, t); }))),
8777
+ h(__mod.TableBody, { key: "b" }, [
8778
+ row(["Ada Lovelace", "Engineer", "Active"], 1),
8779
+ row(["Grace Hopper", "Admiral", "Active"], 2),
8780
+ row(["Alan Turing", "Researcher", "Away"], 3),
8781
+ ]),
8782
+ ],
8783
+ };
8784
+ })()`
8785
+ }
8786
+ },
8787
+ dialog: {
8788
+ default: {
8789
+ export: "Dialog",
8790
+ props: `(function () {
8791
+ var h = React.createElement;
8792
+ return {
8793
+ open: true,
8794
+ // The trigger gives #root a node; the content itself portals to <body>.
8795
+ children: [h(__mod.DialogTrigger, { key: "tr", asChild: true }, h("button", { type: "button" }, "Open")), h(__mod.DialogContent, { key: "c" }, [
8796
+ h(__mod.DialogHeader, { key: "h" }, [
8797
+ h(__mod.DialogTitle, { key: "t" }, "Invite a teammate"),
8798
+ h(__mod.DialogDescription, { key: "d" }, "They will get an email with a link to join."),
8799
+ ]),
8800
+ h("p", { key: "b" }, "Dialog body copy sits between the header and the footer rule."),
8801
+ h(__mod.DialogFooter, { key: "f" }, h("button", { type: "button" }, "Send invite")),
8802
+ ])],
8803
+ };
8804
+ })()`
8805
+ }
8806
+ },
8807
+ popover: {
8808
+ selection: {
8809
+ export: "PopoverSelectionList",
8810
+ props: `(function () {
8811
+ var h = React.createElement;
8812
+ return {
8813
+ mode: "checkbox",
8814
+ "aria-label": "Filters",
8815
+ children: ["Design", "Engineering", "Operations"].map(function (t, i) {
8816
+ return h(__mod.PopoverSelectionItem, { key: t, selected: i === 0 }, t);
8817
+ }),
8818
+ };
8819
+ })()`
8820
+ }
8821
+ },
8822
+ tabs: {
8823
+ line: {
8824
+ export: "Tabs",
8825
+ props: `(function () {
8826
+ var h = React.createElement;
8827
+ return {
8828
+ defaultValue: "one",
8829
+ children: [
8830
+ h(__mod.TabsList, { key: "l", variant: "line" }, [
8831
+ h(__mod.TabsTrigger, { key: "1", value: "one" }, "Overview"),
8832
+ h(__mod.TabsTrigger, { key: "2", value: "two" }, "Activity"),
8833
+ h(__mod.TabsTrigger, { key: "3", value: "three" }, "Settings"),
8834
+ ]),
8835
+ h(__mod.TabsContent, { key: "c", value: "one" }, "Overview content"),
8836
+ ],
8837
+ };
8838
+ })()`
8839
+ }
8840
+ },
8764
8841
  "stat-card": {
8765
8842
  default: {
8766
8843
  export: "StatCard",
@@ -9112,6 +9189,12 @@ function resolveTokensCssFile(cwd) {
9112
9189
  resolve21(cwd, "node_modules", "@loworbitstudio", "visor-core", "dist", "tokens.css")
9113
9190
  ]);
9114
9191
  }
9192
+ function resolveResetCssFile(cwd) {
9193
+ return firstExisting([
9194
+ resolve21(cwd, "packages", "tokens", "dist", "reset.css"),
9195
+ resolve21(cwd, "node_modules", "@loworbitstudio", "visor-core", "dist", "reset.css")
9196
+ ]);
9197
+ }
9115
9198
  async function loadOptional(moduleName) {
9116
9199
  try {
9117
9200
  return await import(moduleName);
@@ -9135,6 +9218,9 @@ function buildHtml(opts) {
9135
9218
  <head>
9136
9219
  <meta charset="utf-8" />
9137
9220
  <meta name="viewport" content="width=device-width, initial-scale=1" />
9221
+ <style data-visor="reset">
9222
+ ${opts.resetCss}
9223
+ </style>
9138
9224
  <style data-visor="tokens">
9139
9225
  ${opts.tokensCss}
9140
9226
  </style>
@@ -9155,7 +9241,8 @@ html, body { margin: 0; padding: 0; }
9155
9241
  justify-content: center;
9156
9242
  background: var(--surface-page, #ffffff);
9157
9243
  color: var(--text-primary, #111827);
9158
- font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
9244
+ /* Same token the nextjs adapter's origination binds to body (VI-616). */
9245
+ font-family: var(--font-body, var(--font-sans, system-ui, -apple-system, sans-serif));
9159
9246
  }
9160
9247
  #root { width: 100%; max-width: 420px; }
9161
9248
  </style>
@@ -9233,6 +9320,14 @@ async function renderCommand(component, cwd, options) {
9233
9320
  { hint: "Run `npm run build -w packages/tokens` first." }
9234
9321
  );
9235
9322
  }
9323
+ const resetCssFile = resolveResetCssFile(cwd);
9324
+ if (!resetCssFile) {
9325
+ fail3(
9326
+ "RESET_NOT_FOUND",
9327
+ "visor-core reset.css not found. Expected packages/tokens/dist/reset.css.",
9328
+ { hint: "Run `npm run build -w packages/tokens` first." }
9329
+ );
9330
+ }
9236
9331
  const fixtureName = options.fixture ?? "default";
9237
9332
  const componentFixtures = FIXTURES[component];
9238
9333
  const fixture = componentFixtures?.[fixtureName] ?? { props: "{}" };
@@ -9288,6 +9383,7 @@ async function renderCommand(component, cwd, options) {
9288
9383
  }
9289
9384
  const themeClass = `${options.theme}-theme`;
9290
9385
  const html = buildHtml({
9386
+ resetCss: readFileSync33(resetCssFile, "utf-8"),
9291
9387
  tokensCss: readFileSync33(tokensCssFile, "utf-8"),
9292
9388
  themeCss: readFileSync33(themeCssFile, "utf-8"),
9293
9389
  componentCss,
@@ -142,12 +142,12 @@
142
142
  {
143
143
  "path": "components/ui/button/button.tsx",
144
144
  "type": "registry:ui",
145
- "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./button.module.css\"\nimport { Spinner } from \"../spinner/spinner\"\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"../tooltip/tooltip\"\n\nconst buttonVariants = cva(styles.base, {\n variants: {\n variant: {\n default: styles.variantDefault,\n secondary: styles.variantSecondary,\n outline: styles.variantOutline,\n ghost: styles.variantGhost,\n destructive: styles.variantDestructive,\n },\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n dlg: styles.sizeDlg,\n icon: styles.sizeIcon,\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n },\n})\n\n/**\n * Fallback thresholds for the `pending` affordance, in milliseconds.\n *\n * The delay suppresses the busy glyph for actions that resolve fast enough\n * that showing one would read as a flicker; the minimum duration keeps it on\n * screen long enough to be legible once it has appeared. Both are overridable\n * per call site (`pendingDelay` / `pendingMinDuration`) and per theme\n * (`--button-pending-delay` / `--button-pending-min-duration`).\n */\nexport const BUTTON_PENDING_DELAY_MS = 200\nexport const BUTTON_PENDING_MIN_DURATION_MS = 300\n\n/** Parses a CSS time value (`200ms`, `0.2s`, bare `200`) into milliseconds. */\nfunction parseCssDuration(raw: string | null | undefined): number | undefined {\n if (!raw) return undefined\n const match = /^(\\d+(?:\\.\\d+)?)\\s*(ms|s)?$/.exec(raw.trim())\n if (!match) return undefined\n const value = Number.parseFloat(match[1])\n if (!Number.isFinite(value)) return undefined\n return match[2] === \"s\" ? value * 1000 : value\n}\n\n/**\n * Resolution order for a threshold: explicit prop, then the themed custom\n * property read off the live element, then the module fallback.\n *\n * The custom properties are deliberately NOT declared on `.base` — an\n * element-level declaration would shadow anything a theme emits on `:root`,\n * which is the defect VI-625 fixed on Spinner.\n */\nfunction resolveThreshold(\n element: HTMLElement | null,\n customProperty: string,\n propValue: number | undefined,\n fallback: number\n): number {\n if (typeof propValue === \"number\" && Number.isFinite(propValue)) {\n return Math.max(0, propValue)\n }\n if (element && typeof window !== \"undefined\" && typeof window.getComputedStyle === \"function\") {\n const themed = parseCssDuration(\n window.getComputedStyle(element).getPropertyValue(customProperty)\n )\n if (themed !== undefined) return themed\n }\n return fallback\n}\n\n/**\n * Turns a raw `pending` boolean into a *paintable* one: false for the first\n * `delay` ms, then true for at least `minDuration` ms once shown.\n *\n * Without the delay a 150ms action strobes; without the minimum a 250ms action\n * shows a one-frame flash, which reads as a glitch rather than as progress.\n */\nfunction usePendingVisibility(\n pending: boolean,\n elementRef: React.RefObject<HTMLElement | null>,\n delay: number | undefined,\n minDuration: number | undefined\n): boolean {\n const [visible, setVisible] = React.useState(false)\n const visibleRef = React.useRef(false)\n const shownAtRef = React.useRef(0)\n\n const show = React.useCallback((next: boolean) => {\n visibleRef.current = next\n setVisible(next)\n }, [])\n\n React.useEffect(() => {\n if (pending) {\n const delayMs = resolveThreshold(\n elementRef.current,\n \"--button-pending-delay\",\n delay,\n BUTTON_PENDING_DELAY_MS\n )\n if (delayMs <= 0) {\n shownAtRef.current = Date.now()\n show(true)\n return\n }\n const armed = setTimeout(() => {\n shownAtRef.current = Date.now()\n show(true)\n }, delayMs)\n return () => clearTimeout(armed)\n }\n\n // Never painted — nothing to unwind, and no minimum to honour.\n if (!visibleRef.current) return\n\n const minMs = resolveThreshold(\n elementRef.current,\n \"--button-pending-min-duration\",\n minDuration,\n BUTTON_PENDING_MIN_DURATION_MS\n )\n const remaining = minMs - (Date.now() - shownAtRef.current)\n if (remaining <= 0) {\n show(false)\n return\n }\n const held = setTimeout(() => show(false), remaining)\n return () => clearTimeout(held)\n }, [pending, delay, minDuration, elementRef, show])\n\n return visible\n}\n\n/** Spinner ring size that reads correctly inside each button size. */\nconst PENDING_SPINNER_SIZE: Record<string, \"xs\" | \"sm\"> = {\n sm: \"xs\",\n dlg: \"xs\",\n md: \"sm\",\n lg: \"sm\",\n icon: \"sm\",\n}\n\n/**\n * An icon-only button has no text for assistive tech to read, so it needs an\n * `aria-label` (or `aria-labelledby`). Warns once per mount in development;\n * silent in production.\n */\nfunction useIconLabelWarning(\n size: string | null | undefined,\n asChild: boolean | undefined,\n children: React.ReactNode,\n props: React.ButtonHTMLAttributes<HTMLButtonElement>\n) {\n // Under asChild the name can live on the child, which is not inspectable\n // here, so only a plain <button> is checked. A text child or a title is a name.\n const hasText = React.Children.toArray(children).some((c) => typeof c === \"string\" || typeof c === \"number\")\n const unlabelled =\n size === \"icon\" && !asChild && !hasText && !props[\"aria-label\"] && !props[\"aria-labelledby\"] && !props.title\n React.useEffect(() => {\n if (unlabelled && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n '[Button] size=\"icon\" renders no text, so it needs an accessible name. ' +\n \"Pass aria-label (or aria-labelledby) describing the action.\"\n )\n }\n }, [unlabelled])\n}\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean\n /**\n * When true, the button renders in an inert gated state — visually dimmed,\n * cursor not-allowed, and click handlers suppressed. Uses `aria-disabled`\n * instead of the native `disabled` attribute so the button remains\n * keyboard-focusable (required for the tooltip to be accessible).\n *\n * Note: a `<TooltipProvider>` ancestor is required when `gatedReason` is set.\n */\n gated?: boolean\n /**\n * Explanation surfaced in an anchored tooltip when the button is gated.\n * Only rendered when `gated` is also true.\n * Example: \"You can't delete this — you're not an owner\"\n */\n gatedReason?: string\n /**\n * When true, the button reports that its action is in flight: a busy glyph\n * appears, `aria-busy` is set, and activation is suppressed so the action\n * cannot be double-submitted.\n *\n * **The geometry is held.** The idle children stay in the box at zero\n * opacity and the glyph paints over them, so the control never resizes and\n * never shoves its siblings. That is the whole point — a pending state that\n * reflows has not fixed the visible defect.\n *\n * Like `gated`, this does NOT set the native `disabled` attribute: the\n * button stays focusable, which matters because it is the user's focus\n * anchor while they wait.\n */\n pending?: boolean\n /**\n * Optional text shown beside the busy glyph while pending. Purely visual —\n * it is `aria-hidden`, and the accessible name stays the idle label so the\n * control is not renamed mid-action. Pair `pending` with your own\n * `role=\"status\"` region when the message needs to be spoken.\n */\n pendingLabel?: React.ReactNode\n /**\n * Milliseconds to wait before painting the busy glyph. Overrides the themed\n * `--button-pending-delay`. @default 200\n */\n pendingDelay?: number\n /**\n * Milliseconds to keep the busy glyph on screen once painted. Overrides the\n * themed `--button-pending-min-duration`. @default 300\n */\n pendingMinDuration?: number\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant,\n size,\n asChild,\n gated,\n gatedReason,\n pending,\n pendingLabel,\n pendingDelay,\n pendingMinDuration,\n onClick,\n children,\n ...props\n },\n ref\n ) => {\n const Comp = asChild ? Slot : \"button\"\n\n useIconLabelWarning(size, asChild, children, props)\n\n const elementRef = React.useRef<HTMLButtonElement | null>(null)\n const mergeRef = React.useCallback(\n (node: HTMLButtonElement | null) => {\n elementRef.current = node\n if (typeof ref === \"function\") ref(node)\n else if (ref) ref.current = node\n },\n [ref]\n )\n\n const pendingVisible = usePendingVisibility(\n pending === true,\n elementRef,\n pendingDelay,\n pendingMinDuration\n )\n\n // Activation follows what the user can see: suppressed while the action is\n // in flight AND for as long as the glyph is still held on screen, so a\n // control that looks busy never silently accepts a click.\n const inert = gated === true || pending === true || pendingVisible\n\n const handleClick = React.useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n if (inert) {\n /* `preventDefault` as well as swallowing the handler, and this is\n * load-bearing: most buttons `pending` and `gated` exist for are a\n * `type=\"submit\"` inside a form, and suppressing `onClick` alone\n * leaves the browser's own ACTIVATION BEHAVIOUR intact — the form\n * submits anyway, through `onSubmit`, which this never sees. Measured\n * in a consumer: a second click on a pending submit mailed a second\n * one-time code. A keyboard Enter on a focused submit button\n * dispatches a click too, so this covers that path as well. */\n e.preventDefault()\n return\n }\n onClick?.(e)\n },\n [inert, onClick]\n )\n\n // `asChild` hands a single child straight to Slot, so the pending chrome\n // (which needs its own wrapper elements) cannot be injected. The state\n // attributes and activation suppression still apply.\n const showPendingChrome = !asChild && pendingVisible\n const spinnerSize = PENDING_SPINNER_SIZE[size ?? \"md\"] ?? \"sm\"\n\n const button = (\n <Comp\n className={cn(buttonVariants({ variant, size }), className)}\n ref={mergeRef}\n aria-disabled={gated || pendingVisible ? true : undefined}\n aria-busy={pending === true || pendingVisible ? true : undefined}\n data-gated={gated ? \"true\" : undefined}\n data-gated-reason={gated && gatedReason ? gatedReason : undefined}\n data-pending={pendingVisible ? \"true\" : undefined}\n onClick={handleClick}\n {...props}\n >\n {showPendingChrome ? (\n <>\n <span className={styles.pendingLabel} data-slot=\"button-label\">\n {children}\n </span>\n <span\n className={styles.pendingOverlay}\n data-slot=\"button-pending\"\n aria-hidden=\"true\"\n >\n <Spinner size={spinnerSize} />\n {pendingLabel ? (\n <span className={styles.pendingText}>{pendingLabel}</span>\n ) : null}\n </span>\n </>\n ) : (\n children\n )}\n </Comp>\n )\n\n if (gated && gatedReason) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>{gatedReason}</TooltipContent>\n </Tooltip>\n )\n }\n\n return button\n }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\n"
145
+ "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./button.module.css\"\nimport { Spinner } from \"../spinner/spinner\"\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"../tooltip/tooltip\"\n\nconst buttonVariants = cva(styles.base, {\n variants: {\n variant: {\n default: styles.variantDefault,\n secondary: styles.variantSecondary,\n outline: styles.variantOutline,\n ghost: styles.variantGhost,\n destructive: styles.variantDestructive,\n },\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n dlg: styles.sizeDlg,\n icon: styles.sizeIcon,\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n },\n})\n\n/**\n * Fallback thresholds for the `pending` affordance, in milliseconds.\n *\n * The delay suppresses the busy glyph for actions that resolve fast enough\n * that showing one would read as a flicker; the minimum duration keeps it on\n * screen long enough to be legible once it has appeared. Both are overridable\n * per call site (`pendingDelay` / `pendingMinDuration`) and per theme\n * (`--button-pending-delay` / `--button-pending-min-duration`).\n */\nexport const BUTTON_PENDING_DELAY_MS = 200\nexport const BUTTON_PENDING_MIN_DURATION_MS = 300\n\n/** Parses a CSS time value (`200ms`, `0.2s`, bare `200`) into milliseconds. */\nfunction parseCssDuration(raw: string | null | undefined): number | undefined {\n if (!raw) return undefined\n const match = /^(\\d+(?:\\.\\d+)?)\\s*(ms|s)?$/.exec(raw.trim())\n if (!match) return undefined\n const value = Number.parseFloat(match[1])\n if (!Number.isFinite(value)) return undefined\n return match[2] === \"s\" ? value * 1000 : value\n}\n\n/**\n * Resolution order for a threshold: explicit prop, then the themed custom\n * property read off the live element, then the module fallback.\n *\n * The custom properties are deliberately NOT declared on `.base` — an\n * element-level declaration would shadow anything a theme emits on `:root`,\n * which is the defect VI-625 fixed on Spinner.\n */\nfunction resolveThreshold(\n element: HTMLElement | null,\n customProperty: string,\n propValue: number | undefined,\n fallback: number\n): number {\n if (typeof propValue === \"number\" && Number.isFinite(propValue)) {\n return Math.max(0, propValue)\n }\n if (element && typeof window !== \"undefined\" && typeof window.getComputedStyle === \"function\") {\n const themed = parseCssDuration(\n window.getComputedStyle(element).getPropertyValue(customProperty)\n )\n if (themed !== undefined) return themed\n }\n return fallback\n}\n\n/**\n * Turns a raw `pending` boolean into a *paintable* one: false for the first\n * `delay` ms, then true for at least `minDuration` ms once shown.\n *\n * Without the delay a 150ms action strobes; without the minimum a 250ms action\n * shows a one-frame flash, which reads as a glitch rather than as progress.\n */\nfunction usePendingVisibility(\n pending: boolean,\n elementRef: React.RefObject<HTMLElement | null>,\n delay: number | undefined,\n minDuration: number | undefined\n): boolean {\n const [visible, setVisible] = React.useState(false)\n const visibleRef = React.useRef(false)\n const shownAtRef = React.useRef(0)\n\n const show = React.useCallback((next: boolean) => {\n visibleRef.current = next\n setVisible(next)\n }, [])\n\n React.useEffect(() => {\n if (pending) {\n const delayMs = resolveThreshold(\n elementRef.current,\n \"--button-pending-delay\",\n delay,\n BUTTON_PENDING_DELAY_MS\n )\n if (delayMs <= 0) {\n shownAtRef.current = Date.now()\n show(true)\n return\n }\n const armed = setTimeout(() => {\n shownAtRef.current = Date.now()\n show(true)\n }, delayMs)\n return () => clearTimeout(armed)\n }\n\n // Never painted — nothing to unwind, and no minimum to honour.\n if (!visibleRef.current) return\n\n const minMs = resolveThreshold(\n elementRef.current,\n \"--button-pending-min-duration\",\n minDuration,\n BUTTON_PENDING_MIN_DURATION_MS\n )\n const remaining = minMs - (Date.now() - shownAtRef.current)\n if (remaining <= 0) {\n show(false)\n return\n }\n const held = setTimeout(() => show(false), remaining)\n return () => clearTimeout(held)\n }, [pending, delay, minDuration, elementRef, show])\n\n return visible\n}\n\n/** Spinner ring size that reads correctly inside each button size. */\nconst PENDING_SPINNER_SIZE: Record<string, \"xs\" | \"sm\"> = {\n sm: \"xs\",\n dlg: \"xs\",\n md: \"sm\",\n lg: \"sm\",\n icon: \"sm\",\n}\n\n/**\n * An icon-only button has no text for assistive tech to read, so it needs an\n * `aria-label` (or `aria-labelledby`). Warns once per mount in development;\n * silent in production.\n */\nfunction useIconLabelWarning(\n size: string | null | undefined,\n asChild: boolean | undefined,\n children: React.ReactNode,\n props: React.ButtonHTMLAttributes<HTMLButtonElement>\n) {\n // Under asChild the name can live on the child, which is not inspectable\n // here, so only a plain <button> is checked. A text child or a title is a name.\n const hasText = React.Children.toArray(children).some((c) => typeof c === \"string\" || typeof c === \"number\")\n const unlabelled =\n size === \"icon\" && !asChild && !hasText && !props[\"aria-label\"] && !props[\"aria-labelledby\"] && !props.title\n React.useEffect(() => {\n if (unlabelled && process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n '[Button] size=\"icon\" renders no text, so it needs an accessible name. ' +\n \"Pass aria-label (or aria-labelledby) describing the action.\"\n )\n }\n }, [unlabelled])\n}\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean\n /**\n * When true, the button renders in an inert gated state — visually dimmed,\n * cursor not-allowed, and click handlers suppressed. Uses `aria-disabled`\n * instead of the native `disabled` attribute so the button remains\n * keyboard-focusable (required for the tooltip to be accessible).\n *\n * Note: a `<TooltipProvider>` ancestor is required when `gatedReason` is set.\n */\n gated?: boolean\n /**\n * Explanation surfaced in an anchored tooltip when the button is gated.\n * Only rendered when `gated` is also true.\n * Example: \"You can't delete this — you're not an owner\"\n */\n gatedReason?: string\n /**\n * When true, the button reports that its action is in flight: a busy glyph\n * appears, `aria-busy` is set, and activation is suppressed so the action\n * cannot be double-submitted.\n *\n * **The geometry is held.** The idle children stay in the box at zero\n * opacity and the glyph paints over them, so the control never resizes and\n * never shoves its siblings. That is the whole point — a pending state that\n * reflows has not fixed the visible defect.\n *\n * Like `gated`, this does NOT set the native `disabled` attribute: the\n * button stays focusable, which matters because it is the user's focus\n * anchor while they wait.\n */\n pending?: boolean\n /**\n * Optional text shown beside the busy glyph while pending. Purely visual —\n * it is `aria-hidden`, and the accessible name stays the idle label so the\n * control is not renamed mid-action. Pair `pending` with your own\n * `role=\"status\"` region when the message needs to be spoken.\n */\n pendingLabel?: React.ReactNode\n /**\n * Milliseconds to wait before painting the busy glyph. Overrides the themed\n * `--button-pending-delay`. @default 200\n */\n pendingDelay?: number\n /**\n * Milliseconds to keep the busy glyph on screen once painted. Overrides the\n * themed `--button-pending-min-duration`. @default 300\n */\n pendingMinDuration?: number\n}\n\n/**\n * Wrap bare text in a block label so the stylesheet can trim its line box to the\n * capital-to-baseline band (`text-box`, see `.label`). Flexbox centres the line\n * box, whose height comes from the font's ascent and descent rather than from\n * where its capitals sit, so an untrimmed label lands off-centre by an amount\n * that differs per font (VI-682). Elements (icons, spinners) pass through.\n */\nfunction wrapLabel(children: React.ReactNode): React.ReactNode {\n return React.Children.map(children, (child) =>\n typeof child === \"string\" || typeof child === \"number\" ? (\n <span className={styles.label} data-slot=\"button-text\">\n {child}\n </span>\n ) : (\n child\n )\n )\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant,\n size,\n asChild,\n gated,\n gatedReason,\n pending,\n pendingLabel,\n pendingDelay,\n pendingMinDuration,\n onClick,\n children,\n ...props\n },\n ref\n ) => {\n const Comp = asChild ? Slot : \"button\"\n\n useIconLabelWarning(size, asChild, children, props)\n\n const elementRef = React.useRef<HTMLButtonElement | null>(null)\n const mergeRef = React.useCallback(\n (node: HTMLButtonElement | null) => {\n elementRef.current = node\n if (typeof ref === \"function\") ref(node)\n else if (ref) ref.current = node\n },\n [ref]\n )\n\n const pendingVisible = usePendingVisibility(\n pending === true,\n elementRef,\n pendingDelay,\n pendingMinDuration\n )\n\n // Activation follows what the user can see: suppressed while the action is\n // in flight AND for as long as the glyph is still held on screen, so a\n // control that looks busy never silently accepts a click.\n const inert = gated === true || pending === true || pendingVisible\n\n const handleClick = React.useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n if (inert) {\n /* `preventDefault` as well as swallowing the handler, and this is\n * load-bearing: most buttons `pending` and `gated` exist for are a\n * `type=\"submit\"` inside a form, and suppressing `onClick` alone\n * leaves the browser's own ACTIVATION BEHAVIOUR intact — the form\n * submits anyway, through `onSubmit`, which this never sees. Measured\n * in a consumer: a second click on a pending submit mailed a second\n * one-time code. A keyboard Enter on a focused submit button\n * dispatches a click too, so this covers that path as well. */\n e.preventDefault()\n return\n }\n onClick?.(e)\n },\n [inert, onClick]\n )\n\n // `asChild` hands a single child straight to Slot, so the pending chrome\n // (which needs its own wrapper elements) cannot be injected. The state\n // attributes and activation suppression still apply.\n const showPendingChrome = !asChild && pendingVisible\n const spinnerSize = PENDING_SPINNER_SIZE[size ?? \"md\"] ?? \"sm\"\n\n const button = (\n <Comp\n className={cn(buttonVariants({ variant, size }), className)}\n ref={mergeRef}\n aria-disabled={gated || pendingVisible ? true : undefined}\n aria-busy={pending === true || pendingVisible ? true : undefined}\n data-gated={gated ? \"true\" : undefined}\n data-gated-reason={gated && gatedReason ? gatedReason : undefined}\n data-pending={pendingVisible ? \"true\" : undefined}\n onClick={handleClick}\n {...props}\n >\n {showPendingChrome ? (\n <>\n <span className={styles.pendingLabel} data-slot=\"button-label\">\n {wrapLabel(children)}\n </span>\n <span\n className={styles.pendingOverlay}\n data-slot=\"button-pending\"\n aria-hidden=\"true\"\n >\n <Spinner size={spinnerSize} />\n {pendingLabel ? (\n <span className={styles.pendingText}>{pendingLabel}</span>\n ) : null}\n </span>\n </>\n ) : asChild && React.isValidElement<{ children?: React.ReactNode }>(children) ? (\n // Slot merges onto its one child, so the child's own text is wrapped\n // in place; its props and ref are kept by cloneElement.\n React.cloneElement(children, undefined, wrapLabel(children.props.children))\n ) : (\n wrapLabel(children)\n )}\n </Comp>\n )\n\n if (gated && gatedReason) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>{gatedReason}</TooltipContent>\n </Tooltip>\n )\n }\n\n return button\n }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\n"
146
146
  },
147
147
  {
148
148
  "path": "components/ui/button/button.module.css",
149
149
  "type": "registry:ui",
150
- "content": "/* Button base styles */\n.base {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-2, 0.5rem);\n white-space: nowrap;\n border-radius: var(--radius-md, 0.375rem);\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n line-height: 1;\n cursor: pointer;\n /* VI-655: the border only holds box geometry and is always transparent. The\n faces that draw an edge (outline, gated, dlg ghost) draw it on ::after —\n see \"Edged faces\" below — so the button's own outline stays free for the\n focus ring. */\n border: 1px solid transparent;\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), transform var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n text-decoration: none;\n outline: none;\n}\n\n.base:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.base:active:not(:disabled) {\n transform: translateY(1px);\n transition: none;\n}\n\n.base:disabled {\n pointer-events: none;\n opacity: 0.5;\n}\n\n/* Variants */\n.variantDefault {\n background-color: var(--interactive-primary-bg, var(--primary, #111827));\n color: var(--interactive-primary-text, #f9fafb);\n}\n\n.variantSecondary {\n background-color: var(--interactive-secondary-bg, #f3f4f6);\n color: var(--interactive-secondary-text, #111827);\n}\n\n/* Editorial density: secondary on the card tier, ghost truly transparent.\n * Direct values — the editorial design language owns control surfaces, so the\n * theme's --interactive-secondary-bg / --interactive-ghost-bg are deliberately\n * bypassed (matches the blessed overlay, which out-cascaded the palette). */\n:global([data-density=\"editorial\"]) .variantSecondary {\n background-color: var(--surface-card, #ffffff);\n}\n\n:global([data-density=\"editorial\"]) .variantGhost {\n background-color: transparent;\n}\n\n.variantOutline {\n background-color: transparent;\n color: var(--text-primary, #111827);\n}\n\n/* Edged faces (VI-655). The resting edge is an inset outline over the border\n box, on the shared --control-edge-* tokens, so --control-edge-width: 0 on any\n ancestor removes it with no layout shift. Each face sets only the colour, in\n the same source position its border-color used to hold. */\n.variantOutline,\n.base[data-gated=\"true\"],\n.sizeDlg.variantGhost {\n position: relative;\n}\n\n.variantOutline::after,\n.base[data-gated=\"true\"]::after,\n.sizeDlg.variantGhost::after {\n content: \"\";\n position: absolute;\n inset: -1px;\n border-radius: inherit;\n outline: var(--control-edge-width, 1px) var(--control-edge-style, solid) transparent;\n outline-offset: calc(-1 * var(--control-edge-width, 1px));\n pointer-events: none;\n transition: outline-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.variantOutline::after {\n outline-color: var(--control-edge-color, var(--border-strong, #e5e7eb));\n}\n\n.variantGhost {\n background-color: var(--interactive-ghost-bg, transparent);\n color: var(--text-primary, #111827);\n}\n\n.variantDestructive {\n background-color: var(--interactive-destructive-bg, #ef4444);\n color: var(--interactive-destructive-text, #fef2f2);\n}\n\n@media (hover: hover) {\n .variantDefault:hover {\n background-color: var(--interactive-primary-bg-hover, #1f2937);\n }\n\n .variantSecondary:hover {\n background-color: var(--interactive-secondary-bg-hover, #e5e7eb);\n }\n\n .variantOutline:hover {\n background-color: var(--surface-muted, #f3f4f6);\n }\n\n .variantGhost:hover {\n background-color: var(--interactive-ghost-bg-hover, var(--surface-muted, #f3f4f6));\n }\n\n .variantDestructive:hover {\n background-color: var(--interactive-destructive-bg-hover, #dc2626);\n }\n}\n\n/* Open / active state — ghost variant only. OPT-IN, strictly additive.\n A ghost Button reads as \"held\" while its popover/menu is open so the\n trigger no longer needs an inline style. Three opt-in triggers, all\n resolving to the same held look:\n - data-state=\"open\" — forwarded automatically by Radix when the\n Button is the trigger via <Trigger asChild> (Popover/DropdownMenu).\n - data-active=\"true\" — manual non-Radix triggers (mirrors the\n sidebar `data-active` convention).\n - .isActive — className opt-in (mirrors sidebar/section-nav).\n Default ghost rendering is untouched: none of these match unless a\n consumer explicitly sets them. Matches the prototype-overlay held look:\n background var(--surface-subtle), color var(--text-primary). */\n.variantGhost[data-state=\"open\"],\n.variantGhost[data-active=\"true\"],\n.variantGhost.isActive {\n background-color: var(--surface-subtle, var(--surface-muted, #f3f4f6));\n color: var(--text-primary, #111827);\n}\n\n/* Gated state — orthogonal to variant, works with all variants */\n.base[data-gated=\"true\"] {\n opacity: var(--opacity-40, 0.4);\n cursor: not-allowed;\n}\n\n.base[data-gated=\"true\"]::after {\n outline-color: var(--control-edge-color, var(--border-default, #e5e7eb));\n}\n\n/* Editorial density: gated reads like plain disabled (the blessed admin\n builds render gated actions at the 0.5 disabled opacity). */\n:global([data-density=\"editorial\"]) .base[data-gated=\"true\"] {\n opacity: 0.5;\n}\n\n/* Suppress active transform when gated */\n.base[data-gated=\"true\"]:active {\n transform: none;\n transition: opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* Sizes\n Default (no data-density attr) rendering is byte-identical to the prior\n canonical values. Under data-density=\"editorial\" (set on any ancestor,\n typically the app root), the editorial density axis re-drives specific\n metrics to the blessed admin values.\n\n Type and rounding are theme hooks (VI-656): --button-<size>-font-size,\n -text-transform, -letter-spacing and -radius set one size, and the\n unprefixed --button-text-transform / -letter-spacing / -radius set every\n size. Unset, each size keeps its shipped type size and rounding; case and\n tracking live in visor-base, after the dlg size. */\n.sizeSm {\n height: 2rem;\n padding: 0 var(--spacing-3, 0.75rem);\n font-size: var(--button-sm-font-size, var(--font-size-xs, 0.75rem));\n border-radius: var(--button-sm-radius, var(--button-radius, var(--radius-md, 0.375rem)));\n}\n\n.sizeMd {\n height: 2.5rem;\n padding: 0 var(--spacing-4, 1rem);\n line-height: 1;\n font-size: var(--button-md-font-size, var(--font-size-sm, 0.875rem));\n border-radius: var(--button-md-radius, var(--button-radius, var(--radius-md, 0.375rem)));\n}\n\n:global([data-density=\"editorial\"]) .sizeSm {\n font-size: var(--button-sm-font-size, 0.8125rem);\n}\n\n:global([data-density=\"editorial\"]) .sizeMd {\n height: 2.125rem;\n line-height: normal;\n}\n\n.sizeLg {\n height: 3rem;\n padding: 0 var(--spacing-6, 1.5rem);\n font-size: var(--button-lg-font-size, var(--font-size-base, 1rem));\n border-radius: var(--button-lg-radius, var(--button-radius, var(--radius-md, 0.375rem)));\n}\n\n/* Dialog-footer button — dlg-btn (Animal dialog substrate, VI-620).\n Compact modal-action metrics measured from the AN prototype (11.5px / 600,\n ~7px 13px padding, 7px radius), normalized onto Visor tokens: font-size-2xs\n (11px), spacing-2/spacing-3 padding (8/12px), radius-lg (8px). Height is\n auto so the compact padding drives the box. Color faces reuse the existing\n variant axis — primary via variant=\"default\", ghost via variant=\"ghost\"\n (refined below to a hairline-outlined, secondary-ink face), danger via\n variant=\"destructive\" (refined below to a subtle-danger face). */\n.sizeDlg {\n height: auto;\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n font-size: var(--button-dlg-font-size, var(--font-size-2xs, 0.6875rem));\n font-weight: var(--font-weight-semibold, 600);\n line-height: 1;\n border-radius: var(--button-dlg-radius, var(--button-radius, var(--radius-lg, 0.5rem)));\n}\n\n/* Icon-only button — a square mark around one glyph (VI-659). The box is the\n glyph plus a hit-target pad on every side, so width always equals height;\n --button-icon-size sets the glyph and --button-icon-pad the pad. A 1em glyph\n (Phosphor's default) takes the size from font-size; a glyph with its own\n width/height attributes is sized by the svg rule. No edge on ghost; on\n outline the edge is the shared ::after one, so --control-edge-width: 0 turns\n it off. */\n.sizeIcon {\n width: calc(var(--button-icon-size, 1rem) + 2 * var(--button-icon-pad, var(--spacing-2, 0.5rem)));\n height: calc(var(--button-icon-size, 1rem) + 2 * var(--button-icon-pad, var(--spacing-2, 0.5rem)));\n padding: 0;\n flex-shrink: 0;\n font-size: var(--button-icon-size, 1rem);\n border-radius: var(--button-icon-radius, var(--button-radius, var(--radius-md, 0.375rem)));\n}\n\n.sizeIcon > svg {\n width: var(--button-icon-size, 1rem);\n height: var(--button-icon-size, 1rem);\n flex-shrink: 0;\n}\n\n/* Ghost icon ink — the mark sits beside what it acts on, so it rests quieter\n than a labelled ghost button and comes up to primary ink on hover. :where()\n keeps it at one-class weight, so the held (open / active) ghost rule above\n still wins. */\n:where(.sizeIcon).variantGhost {\n color: var(--button-icon-ghost-color, var(--text-secondary, #6b7280));\n}\n\n@media (hover: hover) {\n :where(.sizeIcon).variantGhost:hover {\n color: var(--text-primary, #111827);\n }\n}\n\n/* Case and tracking are inherited properties, so any unlayered fallback would\n override something: `inherit` beats a layered utility, `revert-layer` drops\n an unlayered consumer rule. In visor-base, the lowest Visor layer (the reset's),\n every consumer rule still wins, and when the hook is unset revert-layer\n leaves the element exactly as if these declarations were absent. */\n@layer visor-base {\n .sizeSm {\n text-transform: var(--button-sm-text-transform, var(--button-text-transform, revert-layer));\n letter-spacing: var(--button-sm-letter-spacing, var(--button-letter-spacing, revert-layer));\n }\n\n .sizeMd {\n text-transform: var(--button-md-text-transform, var(--button-text-transform, revert-layer));\n letter-spacing: var(--button-md-letter-spacing, var(--button-letter-spacing, revert-layer));\n }\n\n .sizeLg {\n text-transform: var(--button-lg-text-transform, var(--button-text-transform, revert-layer));\n letter-spacing: var(--button-lg-letter-spacing, var(--button-letter-spacing, revert-layer));\n }\n\n .sizeDlg {\n text-transform: var(--button-dlg-text-transform, var(--button-text-transform, revert-layer));\n letter-spacing: var(--button-dlg-letter-spacing, var(--button-letter-spacing, revert-layer));\n }\n}\n\n/* dlg ghost — transparent well with a hairline outline + secondary ink\n (AN dlg-btn ghost face: transparent / --ink-60 / 1px --hair). Two-class\n specificity keeps this scoped to the dlg size only. */\n.sizeDlg.variantGhost {\n color: var(--text-secondary, #6b7280);\n}\n\n.sizeDlg.variantGhost::after {\n outline-color: var(--control-edge-color, var(--hairline, var(--border-default, #e5e7eb)));\n}\n\n/* dlg danger — subtle-danger face (AN dlg-btn danger face: --red on a faint\n red fill), distinct from the solid `destructive` variant. */\n.sizeDlg.variantDestructive {\n background-color: var(--surface-error-subtle, color-mix(in srgb, var(--destructive, #ef4444) 8%, transparent));\n color: var(--text-error, var(--destructive, #b91c1c));\n}\n\n@media (hover: hover) {\n .sizeDlg.variantDestructive:hover {\n background-color: color-mix(in srgb, var(--destructive, #ef4444) 14%, transparent);\n }\n}\n\n/* Pending state — orthogonal to variant and size (VI-643).\n The idle children stay in the box at zero opacity so the control keeps its\n width, its height and its accessible name; the busy glyph paints over them\n in an absolutely-positioned overlay. A pending state that reflows has not\n fixed the defect it exists for, which is why nothing here is a leading slot\n that adds width.\n\n Themes tune the two timings via --button-pending-delay and\n --button-pending-min-duration. Neither is declared here on purpose: an\n element-level declaration shadows anything a theme emits on :root (VI-625). */\n.base[data-pending=\"true\"] {\n position: relative;\n cursor: progress;\n}\n\n/* No press dip while the action is in flight — the button is not accepting\n the click, so it must not pretend to. */\n.base[data-pending=\"true\"]:active {\n transform: none;\n transition: none;\n}\n\n/* Geometry holder. Mirrors .base's own flex metrics so wrapping the children\n does not change how they lay out. */\n.pendingLabel {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-2, 0.5rem);\n opacity: 0;\n transition: opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.pendingOverlay {\n position: absolute;\n inset: 0;\n box-sizing: border-box;\n padding: inherit;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-2, 0.5rem);\n pointer-events: none;\n}\n\n/* Clamped, so a pending label longer than the idle one can never widen the\n box it is being held inside. */\n.pendingText {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n"
150
+ "content": "/* Button base styles */\n.base {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-2, 0.5rem);\n white-space: nowrap;\n border-radius: var(--radius-md, 0.375rem);\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n line-height: 1;\n cursor: pointer;\n /* VI-655: the border only holds box geometry and is always transparent. The\n faces that draw an edge (outline, gated, dlg ghost) draw it on ::after —\n see \"Edged faces\" below — so the button's own outline stays free for the\n focus ring. */\n border: 1px solid transparent;\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), transform var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n text-decoration: none;\n outline: none;\n}\n\n.base:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.base:active:not(:disabled) {\n transform: translateY(1px);\n transition: none;\n}\n\n.base:disabled {\n pointer-events: none;\n opacity: 0.5;\n}\n\n/* Label (VI-682). Bare text is wrapped in a block span whose line box is trimmed\n to the capital-height band, so the flex container centres the ink and not the\n font's ascent/descent (which put Product Sans ~1px high in a 40px button).\n Without text-box (Firefox) the span is a plain block and rendering is exactly\n what it was before. */\n.label {\n display: block;\n}\n\n@supports (text-box: trim-both cap alphabetic) {\n .label {\n text-box: trim-both cap alphabetic;\n }\n\n /* The compact dlg size is height:auto, so trimming would shrink its box. */\n .sizeDlg .label {\n text-box: normal;\n }\n}\n\n/* Blink only. Blink snaps the text baseline to a whole CSS px, which leaves the\n trimmed ink up to half a pixel off for fractional cap heights (12px type). The\n baseline lands at (box + cap) / 2; this hands back what the snap took. WebKit\n positions the baseline exactly, so correcting there would overshoot. The\n -apple-pay-button-style probe is the WebKit-only property that tells them apart. */\n@supports (text-box: trim-both cap alphabetic) and (not (-apple-pay-button-style: black)) {\n .label {\n transform: translateY(calc((var(--label-box-height) + 1cap) / 2 - round((var(--label-box-height) + 1cap) / 2, 1px)));\n }\n\n .sizeDlg .label {\n transform: none;\n }\n}\n\n/* Variants */\n.variantDefault {\n background-color: var(--interactive-primary-bg, var(--primary, #111827));\n color: var(--interactive-primary-text, #f9fafb);\n}\n\n.variantSecondary {\n background-color: var(--interactive-secondary-bg, #f3f4f6);\n color: var(--interactive-secondary-text, #111827);\n}\n\n/* Editorial density: secondary on the card tier, ghost truly transparent.\n * Direct values — the editorial design language owns control surfaces, so the\n * theme's --interactive-secondary-bg / --interactive-ghost-bg are deliberately\n * bypassed (matches the blessed overlay, which out-cascaded the palette). */\n:global([data-density=\"editorial\"]) .variantSecondary {\n background-color: var(--surface-card, #ffffff);\n}\n\n:global([data-density=\"editorial\"]) .variantGhost {\n background-color: transparent;\n}\n\n.variantOutline {\n background-color: transparent;\n color: var(--text-primary, #111827);\n}\n\n/* Edged faces (VI-655). The resting edge is an inset outline over the border\n box, on the shared --control-edge-* tokens, so --control-edge-width: 0 on any\n ancestor removes it with no layout shift. Each face sets only the colour, in\n the same source position its border-color used to hold. */\n.variantOutline,\n.base[data-gated=\"true\"],\n.sizeDlg.variantGhost {\n position: relative;\n}\n\n.variantOutline::after,\n.base[data-gated=\"true\"]::after,\n.sizeDlg.variantGhost::after {\n content: \"\";\n position: absolute;\n inset: -1px;\n border-radius: inherit;\n outline: var(--control-edge-width, 1px) var(--control-edge-style, solid) transparent;\n outline-offset: calc(-1 * var(--control-edge-width, 1px));\n pointer-events: none;\n transition: outline-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.variantOutline::after {\n outline-color: var(--control-edge-color, var(--border-strong, #e5e7eb));\n}\n\n.variantGhost {\n background-color: var(--interactive-ghost-bg, transparent);\n color: var(--text-primary, #111827);\n}\n\n.variantDestructive {\n background-color: var(--interactive-destructive-bg, #ef4444);\n color: var(--interactive-destructive-text, #fef2f2);\n}\n\n@media (hover: hover) {\n .variantDefault:hover {\n background-color: var(--interactive-primary-bg-hover, #1f2937);\n }\n\n .variantSecondary:hover {\n background-color: var(--interactive-secondary-bg-hover, #e5e7eb);\n }\n\n .variantOutline:hover {\n background-color: var(--surface-muted, #f3f4f6);\n }\n\n .variantGhost:hover {\n background-color: var(--interactive-ghost-bg-hover, var(--surface-muted, #f3f4f6));\n }\n\n .variantDestructive:hover {\n background-color: var(--interactive-destructive-bg-hover, #dc2626);\n }\n}\n\n/* Open / active state — ghost variant only. OPT-IN, strictly additive.\n A ghost Button reads as \"held\" while its popover/menu is open so the\n trigger no longer needs an inline style. Three opt-in triggers, all\n resolving to the same held look:\n - data-state=\"open\" — forwarded automatically by Radix when the\n Button is the trigger via <Trigger asChild> (Popover/DropdownMenu).\n - data-active=\"true\" — manual non-Radix triggers (mirrors the\n sidebar `data-active` convention).\n - .isActive — className opt-in (mirrors sidebar/section-nav).\n Default ghost rendering is untouched: none of these match unless a\n consumer explicitly sets them. Matches the prototype-overlay held look:\n background var(--surface-subtle), color var(--text-primary). */\n.variantGhost[data-state=\"open\"],\n.variantGhost[data-active=\"true\"],\n.variantGhost.isActive {\n background-color: var(--surface-subtle, var(--surface-muted, #f3f4f6));\n color: var(--text-primary, #111827);\n}\n\n/* Gated state — orthogonal to variant, works with all variants */\n.base[data-gated=\"true\"] {\n opacity: var(--opacity-40, 0.4);\n cursor: not-allowed;\n}\n\n.base[data-gated=\"true\"]::after {\n outline-color: var(--control-edge-color, var(--border-default, #e5e7eb));\n}\n\n/* Editorial density: gated reads like plain disabled (the blessed admin\n builds render gated actions at the 0.5 disabled opacity). */\n:global([data-density=\"editorial\"]) .base[data-gated=\"true\"] {\n opacity: 0.5;\n}\n\n/* Suppress active transform when gated */\n.base[data-gated=\"true\"]:active {\n transform: none;\n transition: opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* Sizes\n Default (no data-density attr) rendering is byte-identical to the prior\n canonical values. Under data-density=\"editorial\" (set on any ancestor,\n typically the app root), the editorial density axis re-drives specific\n metrics to the blessed admin values.\n\n Type and rounding are theme hooks (VI-656): --button-<size>-font-size,\n -text-transform, -letter-spacing and -radius set one size, and the\n unprefixed --button-text-transform / -letter-spacing / -radius set every\n size. Unset, each size keeps its shipped type size and rounding; case and\n tracking live in visor-base, after the dlg size. */\n.sizeSm {\n --label-box-height: 2rem;\n height: var(--label-box-height);\n padding: 0 var(--spacing-3, 0.75rem);\n font-size: var(--button-sm-font-size, var(--font-size-xs, 0.75rem));\n border-radius: var(--button-sm-radius, var(--button-radius, var(--radius-md, 0.375rem)));\n}\n\n.sizeMd {\n --label-box-height: 2.5rem;\n height: var(--label-box-height);\n padding: 0 var(--spacing-4, 1rem);\n line-height: 1;\n font-size: var(--button-md-font-size, var(--font-size-sm, 0.875rem));\n border-radius: var(--button-md-radius, var(--button-radius, var(--radius-md, 0.375rem)));\n}\n\n:global([data-density=\"editorial\"]) .sizeSm {\n font-size: var(--button-sm-font-size, 0.8125rem);\n}\n\n:global([data-density=\"editorial\"]) .sizeMd {\n --label-box-height: 2.125rem;\n height: var(--label-box-height);\n line-height: normal;\n}\n\n.sizeLg {\n --label-box-height: 3rem;\n height: var(--label-box-height);\n padding: 0 var(--spacing-6, 1.5rem);\n font-size: var(--button-lg-font-size, var(--font-size-base, 1rem));\n border-radius: var(--button-lg-radius, var(--button-radius, var(--radius-md, 0.375rem)));\n}\n\n/* Dialog-footer button — dlg-btn (Animal dialog substrate, VI-620).\n Compact modal-action metrics measured from the AN prototype (11.5px / 600,\n ~7px 13px padding, 7px radius), normalized onto Visor tokens: font-size-2xs\n (11px), spacing-2/spacing-3 padding (8/12px), radius-lg (8px). Height is\n auto so the compact padding drives the box. Color faces reuse the existing\n variant axis — primary via variant=\"default\", ghost via variant=\"ghost\"\n (refined below to a hairline-outlined, secondary-ink face), danger via\n variant=\"destructive\" (refined below to a subtle-danger face). */\n.sizeDlg {\n height: auto;\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n font-size: var(--button-dlg-font-size, var(--font-size-2xs, 0.6875rem));\n font-weight: var(--font-weight-semibold, 600);\n line-height: 1;\n border-radius: var(--button-dlg-radius, var(--button-radius, var(--radius-lg, 0.5rem)));\n}\n\n/* Icon-only button — a square mark around one glyph (VI-659). The box is the\n glyph plus a hit-target pad on every side, so width always equals height;\n --button-icon-size sets the glyph and --button-icon-pad the pad. A 1em glyph\n (Phosphor's default) takes the size from font-size; a glyph with its own\n width/height attributes is sized by the svg rule. No edge on ghost; on\n outline the edge is the shared ::after one, so --control-edge-width: 0 turns\n it off. */\n.sizeIcon {\n width: calc(var(--button-icon-size, 1rem) + 2 * var(--button-icon-pad, var(--spacing-2, 0.5rem)));\n height: calc(var(--button-icon-size, 1rem) + 2 * var(--button-icon-pad, var(--spacing-2, 0.5rem)));\n padding: 0;\n flex-shrink: 0;\n font-size: var(--button-icon-size, 1rem);\n border-radius: var(--button-icon-radius, var(--button-radius, var(--radius-md, 0.375rem)));\n}\n\n.sizeIcon > svg {\n width: var(--button-icon-size, 1rem);\n height: var(--button-icon-size, 1rem);\n flex-shrink: 0;\n}\n\n/* Ghost icon ink — the mark sits beside what it acts on, so it rests quieter\n than a labelled ghost button and comes up to primary ink on hover. :where()\n keeps it at one-class weight, so the held (open / active) ghost rule above\n still wins. */\n:where(.sizeIcon).variantGhost {\n color: var(--button-icon-ghost-color, var(--text-secondary, #6b7280));\n}\n\n@media (hover: hover) {\n :where(.sizeIcon).variantGhost:hover {\n color: var(--text-primary, #111827);\n }\n}\n\n/* Case and tracking are inherited properties, so any unlayered fallback would\n override something: `inherit` beats a layered utility, `revert-layer` drops\n an unlayered consumer rule. In visor-base, the lowest Visor layer (the reset's),\n every consumer rule still wins, and when the hook is unset revert-layer\n leaves the element exactly as if these declarations were absent. */\n@layer visor-base {\n .sizeSm {\n text-transform: var(--button-sm-text-transform, var(--button-text-transform, revert-layer));\n letter-spacing: var(--button-sm-letter-spacing, var(--button-letter-spacing, revert-layer));\n }\n\n .sizeMd {\n text-transform: var(--button-md-text-transform, var(--button-text-transform, revert-layer));\n letter-spacing: var(--button-md-letter-spacing, var(--button-letter-spacing, revert-layer));\n }\n\n .sizeLg {\n text-transform: var(--button-lg-text-transform, var(--button-text-transform, revert-layer));\n letter-spacing: var(--button-lg-letter-spacing, var(--button-letter-spacing, revert-layer));\n }\n\n .sizeDlg {\n text-transform: var(--button-dlg-text-transform, var(--button-text-transform, revert-layer));\n letter-spacing: var(--button-dlg-letter-spacing, var(--button-letter-spacing, revert-layer));\n }\n}\n\n/* dlg ghost — transparent well with a hairline outline + secondary ink\n (AN dlg-btn ghost face: transparent / --ink-60 / 1px --hair). Two-class\n specificity keeps this scoped to the dlg size only. */\n.sizeDlg.variantGhost {\n color: var(--text-secondary, #6b7280);\n}\n\n.sizeDlg.variantGhost::after {\n outline-color: var(--control-edge-color, var(--hairline, var(--border-default, #e5e7eb)));\n}\n\n/* dlg danger — subtle-danger face (AN dlg-btn danger face: --red on a faint\n red fill), distinct from the solid `destructive` variant. */\n.sizeDlg.variantDestructive {\n background-color: var(--surface-error-subtle, color-mix(in srgb, var(--destructive, #ef4444) 8%, transparent));\n color: var(--text-error, var(--destructive, #b91c1c));\n}\n\n@media (hover: hover) {\n .sizeDlg.variantDestructive:hover {\n background-color: color-mix(in srgb, var(--destructive, #ef4444) 14%, transparent);\n }\n}\n\n/* Pending state — orthogonal to variant and size (VI-643).\n The idle children stay in the box at zero opacity so the control keeps its\n width, its height and its accessible name; the busy glyph paints over them\n in an absolutely-positioned overlay. A pending state that reflows has not\n fixed the defect it exists for, which is why nothing here is a leading slot\n that adds width.\n\n Themes tune the two timings via --button-pending-delay and\n --button-pending-min-duration. Neither is declared here on purpose: an\n element-level declaration shadows anything a theme emits on :root (VI-625). */\n.base[data-pending=\"true\"] {\n position: relative;\n cursor: progress;\n}\n\n/* No press dip while the action is in flight — the button is not accepting\n the click, so it must not pretend to. */\n.base[data-pending=\"true\"]:active {\n transform: none;\n transition: none;\n}\n\n/* Geometry holder. Mirrors .base's own flex metrics so wrapping the children\n does not change how they lay out. */\n.pendingLabel {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-2, 0.5rem);\n opacity: 0;\n transition: opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.pendingOverlay {\n position: absolute;\n inset: 0;\n box-sizing: border-box;\n padding: inherit;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-2, 0.5rem);\n pointer-events: none;\n}\n\n/* Clamped, so a pending label longer than the idle one can never widen the\n box it is being held inside. */\n.pendingText {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n"
151
151
  }
152
152
  ]
153
153
  },
@@ -628,12 +628,12 @@
628
628
  {
629
629
  "path": "components/ui/tooltip/tooltip.tsx",
630
630
  "type": "registry:ui",
631
- "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./tooltip.module.css\"\n\nconst TooltipProvider = TooltipPrimitive.Provider\n\nconst Tooltip = TooltipPrimitive.Root\n\nconst TooltipTrigger = TooltipPrimitive.Trigger\n\nconst TooltipContent = React.forwardRef<\n React.ElementRef<typeof TooltipPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => {\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n ref={ref}\n data-slot=\"tooltip-content\"\n sideOffset={sideOffset}\n className={cn(styles.content, className)}\n {...props}\n />\n </TooltipPrimitive.Portal>\n )\n})\nTooltipContent.displayName = \"TooltipContent\"\n\nexport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }\n"
631
+ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\nimport { cn } from \"../../../lib/utils\"\nimport styles from \"./tooltip.module.css\"\n\nconst TooltipProvider = TooltipPrimitive.Provider\n\nconst Tooltip = TooltipPrimitive.Root\n\nconst TooltipTrigger = TooltipPrimitive.Trigger\n\ninterface TooltipContentProps\n extends React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> {\n /**\n * Element the tooltip portals into. Defaults to `document.body`. Pass the\n * root of a class-scoped theme so the tooltip inherits that theme's tokens.\n */\n container?: React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Portal>[\"container\"]\n}\n\nconst TooltipContent = React.forwardRef<\n React.ElementRef<typeof TooltipPrimitive.Content>,\n TooltipContentProps\n>(({ className, sideOffset = 4, container, ...props }, ref) => {\n return (\n <TooltipPrimitive.Portal container={container}>\n <TooltipPrimitive.Content\n ref={ref}\n data-slot=\"tooltip-content\"\n sideOffset={sideOffset}\n className={cn(styles.content, className)}\n {...props}\n />\n </TooltipPrimitive.Portal>\n )\n})\nTooltipContent.displayName = \"TooltipContent\"\n\nexport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }\nexport type { TooltipContentProps }\n"
632
632
  },
633
633
  {
634
634
  "path": "components/ui/tooltip/tooltip.module.css",
635
635
  "type": "registry:ui",
636
- "content": "/* Tooltip content */\n.content {\n z-index: 50;\n display: inline-flex;\n width: fit-content;\n max-width: 20rem;\n align-items: center;\n gap: calc(var(--spacing-1, 0.25rem) * 1.5);\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--surface-overlay, #111827);\n padding: calc(var(--spacing-1, 0.25rem) * 1.5) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-xs, 0.75rem);\n color: var(--text-inverse, #ffffff);\n box-shadow: var(--shadow-md);\n animation-duration: var(--motion-duration-150, 150ms);\n animation-timing-function: var(--motion-easing-enter, ease-out);\n}\n\n.content[data-state=\"delayed-open\"] {\n animation-name: fadeIn;\n}\n\n.content[data-state=\"closed\"] {\n animation-name: fadeOut;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@keyframes fadeOut {\n from {\n opacity: 1;\n transform: scale(1);\n }\n to {\n opacity: 0;\n transform: scale(0.95);\n }\n}\n"
636
+ "content": "/* Tooltip content */\n.content {\n z-index: 50;\n display: inline-flex;\n width: fit-content;\n max-width: 20rem;\n align-items: center;\n gap: calc(var(--spacing-1, 0.25rem) * 1.5);\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--tooltip-bg, var(--surface-overlay, #111827));\n padding: calc(var(--spacing-1, 0.25rem) * 1.5) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-xs, 0.75rem);\n color: var(--tooltip-text, var(--text-inverse, #ffffff));\n box-shadow: var(--shadow-md);\n animation-duration: var(--motion-duration-150, 150ms);\n animation-timing-function: var(--motion-easing-enter, ease-out);\n}\n\n.content[data-state=\"delayed-open\"] {\n animation-name: fadeIn;\n}\n\n.content[data-state=\"closed\"] {\n animation-name: fadeOut;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@keyframes fadeOut {\n from {\n opacity: 1;\n transform: scale(1);\n }\n to {\n opacity: 0;\n transform: scale(0.95);\n }\n}\n"
637
637
  }
638
638
  ]
639
639
  },
@@ -709,7 +709,7 @@
709
709
  {
710
710
  "path": "components/ui/coherence-check/coherence-check.module.css",
711
711
  "type": "registry:ui",
712
- "content": "/* ─── CheckGroup ─────────────────────────────────────────────────────────── */\n\n.group {\n margin-bottom: var(--spacing-5, 1.25rem);\n}\n\n/* Uppercase group header */\n.groupHeading {\n margin: 0 0 var(--spacing-2, 0.5rem);\n padding: 0 calc(var(--spacing-1, 0.25rem) / 2);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-extrabold, 800);\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: var(--text-tertiary, #9ca3af);\n line-height: 1.4;\n}\n\n/* ─── CheckRow ───────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-3, 0.75rem);\n background: var(--surface-card, #ffffff);\n border: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n border-radius: var(--radius-lg, 0.5rem);\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n margin-bottom: var(--spacing-2, 0.5rem);\n box-shadow: var(--shadow-sm);\n}\n\n/* Last row in a group has no bottom margin */\n.row:last-child {\n margin-bottom: 0;\n}\n\n/* ─── State icon circle ─────────────────────────────────────────────────── */\n\n.icon {\n flex: none;\n width: var(--spacing-6, 1.5rem);\n height: var(--spacing-6, 1.5rem);\n border-radius: var(--radius-full, 9999px);\n display: grid;\n place-items: center;\n margin-top: calc(var(--spacing-1, 0.25rem) / 4);\n color: var(--text-inverse, #ffffff);\n}\n\n.icon svg {\n width: var(--spacing-4, 1rem);\n height: var(--spacing-4, 1rem);\n}\n\n/* State — icon background tints */\n.statePass .icon {\n background: var(--success, #22c55e);\n}\n\n.stateWarn .icon {\n background: var(--warning, #f59e0b);\n}\n\n.stateFail .icon {\n background: var(--destructive, #ef4444);\n}\n\n/* ─── Body (title + description) ─────────────────────────────────────────── */\n\n.body {\n flex: 1;\n min-width: 0;\n}\n\n.rowTitle {\n display: block;\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-bold, 700);\n color: var(--text-primary, #111827);\n line-height: 1.4;\n}\n\n.rowDescription {\n margin: calc(var(--spacing-1, 0.25rem) * 0.75) 0 0;\n font-size: var(--font-size-xs, 0.75rem);\n color: var(--text-secondary, #6b7280);\n line-height: 1.5;\n}\n\n/* Inline code token support within description */\n.rowDescription code {\n font-family: var(--font-mono, ui-monospace, monospace);\n font-size: var(--font-size-xs, 0.75rem);\n background: var(--surface-muted, #f3f4f6);\n padding: var(--stroke-width-thin, 1px) var(--spacing-1, 0.25rem);\n border-radius: var(--radius-sm, 0.125rem);\n}\n\n/* ─── Fix action button (ghost) ──────────────────────────────────────────── */\n\n.fixButton {\n flex: none;\n align-self: center;\n height: var(--spacing-8, 2rem);\n padding: 0 var(--spacing-3, 0.75rem);\n border-radius: var(--radius-md, 0.25rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-bold, 700);\n border: var(--stroke-width-thin, 1px) solid var(--border-default, #e5e7eb);\n background: transparent;\n color: var(--text-primary, #111827);\n cursor: pointer;\n transition:\n background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n border-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n white-space: nowrap;\n}\n\n.fixButton:hover {\n background: var(--surface-muted, #f3f4f6);\n border-color: var(--border-strong, #d1d5db);\n}\n\n.fixButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n"
712
+ "content": "/* ─── CheckGroup ─────────────────────────────────────────────────────────── */\n\n.group {\n margin-bottom: var(--spacing-5, 1.25rem);\n}\n\n/* Uppercase group header */\n.groupHeading {\n margin: 0 0 var(--spacing-2, 0.5rem);\n padding: 0 calc(var(--spacing-1, 0.25rem) / 2);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-extrabold, 800);\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: var(--text-tertiary, #9ca3af);\n line-height: 1.4;\n}\n\n/* ─── CheckRow ───────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-3, 0.75rem);\n background: var(--surface-card, #ffffff);\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, #e5e7eb);\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--radius-lg, 0.5rem);\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n margin-bottom: var(--spacing-2, 0.5rem);\n box-shadow: var(--shadow-sm);\n}\n\n/* Last row in a group has no bottom margin */\n.row:last-child {\n margin-bottom: 0;\n}\n\n/* ─── State icon circle ─────────────────────────────────────────────────── */\n\n.icon {\n flex: none;\n width: var(--spacing-6, 1.5rem);\n height: var(--spacing-6, 1.5rem);\n border-radius: var(--radius-full, 9999px);\n display: grid;\n place-items: center;\n margin-top: calc(var(--spacing-1, 0.25rem) / 4);\n color: var(--text-inverse, #ffffff);\n}\n\n.icon svg {\n width: var(--spacing-4, 1rem);\n height: var(--spacing-4, 1rem);\n}\n\n/* State — icon background tints */\n.statePass .icon {\n background: var(--success, #22c55e);\n}\n\n.stateWarn .icon {\n background: var(--warning, #f59e0b);\n}\n\n.stateFail .icon {\n background: var(--destructive, #ef4444);\n}\n\n/* ─── Body (title + description) ─────────────────────────────────────────── */\n\n.body {\n flex: 1;\n min-width: 0;\n}\n\n.rowTitle {\n display: block;\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-bold, 700);\n color: var(--text-primary, #111827);\n line-height: 1.4;\n}\n\n.rowDescription {\n margin: calc(var(--spacing-1, 0.25rem) * 0.75) 0 0;\n font-size: var(--font-size-xs, 0.75rem);\n color: var(--text-secondary, #6b7280);\n line-height: 1.5;\n}\n\n/* Inline code token support within description */\n.rowDescription code {\n font-family: var(--font-mono, ui-monospace, monospace);\n font-size: var(--font-size-xs, 0.75rem);\n background: var(--surface-muted, #f3f4f6);\n padding: var(--stroke-width-thin, 1px) var(--spacing-1, 0.25rem);\n border-radius: var(--radius-sm, 0.125rem);\n}\n\n/* ─── Fix action button (ghost) ──────────────────────────────────────────── */\n\n.fixButton {\n flex: none;\n align-self: center;\n height: var(--spacing-8, 2rem);\n padding: 0 var(--spacing-3, 0.75rem);\n border-radius: var(--radius-md, 0.25rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-bold, 700);\n border: var(--stroke-width-thin, 1px) solid var(--border-default, #e5e7eb);\n background: transparent;\n color: var(--text-primary, #111827);\n cursor: pointer;\n transition:\n background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n border-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n white-space: nowrap;\n}\n\n.fixButton:hover {\n background: var(--surface-muted, #f3f4f6);\n border-color: var(--border-strong, #d1d5db);\n}\n\n.fixButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n"
713
713
  }
714
714
  ]
715
715
  },
@@ -785,7 +785,7 @@
785
785
  {
786
786
  "path": "components/ui/dialog/dialog.module.css",
787
787
  "type": "registry:ui",
788
- "content": "/* Dialog overlay\n * Default: 4px backdrop blur. Density axis: editorial uses 2px. */\n.overlay {\n position: fixed;\n inset: 0;\n z-index: 50;\n background-color: var(--overlay-bg, rgba(0, 0, 0, 0.8));\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n\n:global([data-density=\"editorial\"]) .overlay {\n backdrop-filter: blur(2px);\n -webkit-backdrop-filter: blur(2px);\n}\n\n.overlay[data-state=\"open\"] {\n animation: fadeIn var(--motion-duration-150, 150ms) var(--motion-easing-enter, ease-out);\n}\n\n.overlay[data-state=\"closed\"] {\n animation: fadeOut var(--motion-duration-150, 150ms) var(--motion-easing-exit, ease-in);\n}\n\n/* Dialog content\n * Default: 32rem max-width, radius-lg, surface-page, spacing-6, shadow-xl.\n * Density axis: editorial uses 480px, radius-xl, surface-card, spacing-8, shadow-lg. */\n.content {\n position: fixed;\n top: 50%;\n left: 50%;\n z-index: 50;\n width: 100%;\n max-width: 32rem;\n transform: translate(-50%, -50%);\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--surface-page, #ffffff);\n padding: var(--spacing-6, 1.5rem);\n box-shadow: var(--shadow-xl);\n transition: opacity var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out), transform var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n:global([data-density=\"editorial\"]) .content {\n /* Floating modal — opaque. --surface-card may be translucent glass per theme\n * (blackout: rgba(255,255,255,0.04)); over the backdrop it reads see-through.\n * --surface-popover is the opaque floating token (== --surface-card on solid\n * themes, so no visual change there). VI-623. */\n background-color: var(--surface-popover, var(--surface-page, #ffffff));\n max-width: 480px;\n border-radius: var(--radius-xl);\n padding: var(--spacing-8);\n box-shadow: var(--shadow-lg);\n}\n\n.content[data-state=\"open\"] {\n animation: contentShow var(--motion-duration-normal, 200ms) var(--motion-easing-enter, ease-out);\n}\n\n.content[data-state=\"closed\"] {\n animation: contentHide var(--motion-duration-150, 150ms) var(--motion-easing-exit, ease-in);\n}\n\n/* Close button */\n.closeButton {\n position: absolute;\n top: 1rem;\n right: 1rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm, 0.25rem);\n border: none;\n background: transparent;\n cursor: pointer;\n opacity: 0.7;\n transition: opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n padding: var(--spacing-1, 0.25rem);\n color: var(--text-primary, #111827);\n}\n\n.closeButton:hover {\n opacity: 1;\n}\n\n.closeButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.closeIcon {\n width: 1rem;\n height: 1rem;\n}\n\n/* Header */\n.header {\n display: flex;\n flex-direction: column;\n gap: calc(var(--spacing-1, 0.25rem) * 1.5);\n margin-bottom: var(--spacing-4, 1rem);\n}\n\n/* Title\n * Default: font-size-lg / semibold. Density axis: editorial uses font-size-2xl / 700. */\n.title {\n font-size: var(--font-size-lg, 1.125rem);\n font-weight: var(--font-weight-semibold, 600);\n color: var(--text-primary, #111827);\n margin: 0;\n}\n\n:global([data-density=\"editorial\"]) .title {\n font-size: var(--font-size-2xl);\n font-weight: var(--font-weight-bold, 700);\n}\n\n/* Description\n * Default: text-secondary. Density axis: editorial uses text-tertiary. */\n.description {\n font-size: var(--font-size-sm, 0.875rem);\n color: var(--text-secondary, #6b7280);\n margin: 0;\n}\n\n:global([data-density=\"editorial\"]) .description {\n color: var(--text-tertiary);\n}\n\n/* Footer */\n.footer {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: var(--spacing-3, 0.75rem);\n padding-top: var(--spacing-3, 0.75rem);\n margin-top: var(--spacing-2, 0.5rem);\n border-top: var(--hairline-width, 1px) solid var(--hairline, var(--border-subtle, rgba(0, 0, 0, 0.06)));\n}\n\n/* Screen reader only */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes fadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n@keyframes contentShow {\n from {\n opacity: 0;\n transform: translate(-50%, calc(-50% + 8px));\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%);\n }\n}\n\n@keyframes contentHide {\n from {\n opacity: 1;\n transform: translate(-50%, -50%);\n }\n to {\n opacity: 0;\n transform: translate(-50%, calc(-50% + 8px));\n }\n}\n"
788
+ "content": "/* Dialog overlay\n * Default: 4px backdrop blur. Density axis: editorial uses 2px. */\n.overlay {\n position: fixed;\n inset: 0;\n z-index: 50;\n background-color: var(--overlay-bg, rgba(0, 0, 0, 0.8));\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n\n:global([data-density=\"editorial\"]) .overlay {\n backdrop-filter: blur(2px);\n -webkit-backdrop-filter: blur(2px);\n}\n\n.overlay[data-state=\"open\"] {\n animation: fadeIn var(--motion-duration-150, 150ms) var(--motion-easing-enter, ease-out);\n}\n\n.overlay[data-state=\"closed\"] {\n animation: fadeOut var(--motion-duration-150, 150ms) var(--motion-easing-exit, ease-in);\n}\n\n/* Dialog content\n * Default: 32rem max-width, radius-lg, surface-page, spacing-6, shadow-xl.\n * Density axis: editorial uses 480px, radius-xl, surface-card, spacing-8, shadow-lg. */\n.content {\n position: fixed;\n top: 50%;\n left: 50%;\n z-index: 50;\n width: 100%;\n max-width: 32rem;\n transform: translate(-50%, -50%);\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--surface-page, #ffffff);\n padding: var(--spacing-6, 1.5rem);\n box-shadow: var(--shadow-xl);\n transition: opacity var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out), transform var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n:global([data-density=\"editorial\"]) .content {\n /* Floating modal — opaque. --surface-card may be translucent glass per theme\n * (blackout: rgba(255,255,255,0.04)); over the backdrop it reads see-through.\n * --surface-popover is the opaque floating token (== --surface-card on solid\n * themes, so no visual change there). VI-623. */\n background-color: var(--surface-popover, var(--surface-page, #ffffff));\n max-width: 480px;\n border-radius: var(--radius-xl);\n padding: var(--spacing-8);\n box-shadow: var(--shadow-lg);\n}\n\n.content[data-state=\"open\"] {\n animation: contentShow var(--motion-duration-normal, 200ms) var(--motion-easing-enter, ease-out);\n}\n\n.content[data-state=\"closed\"] {\n animation: contentHide var(--motion-duration-150, 150ms) var(--motion-easing-exit, ease-in);\n}\n\n/* Close button */\n.closeButton {\n position: absolute;\n top: 1rem;\n right: 1rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm, 0.25rem);\n border: none;\n background: transparent;\n cursor: pointer;\n opacity: 0.7;\n transition: opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n padding: var(--spacing-1, 0.25rem);\n color: var(--text-primary, #111827);\n}\n\n.closeButton:hover {\n opacity: 1;\n}\n\n.closeButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.closeIcon {\n width: 1rem;\n height: 1rem;\n}\n\n/* Header */\n.header {\n display: flex;\n flex-direction: column;\n gap: calc(var(--spacing-1, 0.25rem) * 1.5);\n margin-bottom: var(--spacing-4, 1rem);\n}\n\n/* Title\n * Default: font-size-lg / semibold. Density axis: editorial uses font-size-2xl / 700. */\n.title {\n font-size: var(--font-size-lg, 1.125rem);\n font-weight: var(--font-weight-semibold, 600);\n color: var(--text-primary, #111827);\n margin: 0;\n}\n\n:global([data-density=\"editorial\"]) .title {\n font-size: var(--font-size-2xl);\n font-weight: var(--font-weight-bold, 700);\n}\n\n/* Description\n * Default: text-secondary. Density axis: editorial uses text-tertiary. */\n.description {\n font-size: var(--font-size-sm, 0.875rem);\n color: var(--text-secondary, #6b7280);\n margin: 0;\n}\n\n:global([data-density=\"editorial\"]) .description {\n color: var(--text-tertiary);\n}\n\n/* Footer */\n.footer {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: var(--spacing-3, 0.75rem);\n padding-top: var(--spacing-3, 0.75rem);\n margin-top: var(--spacing-2, 0.5rem);\n border-top: 1px solid transparent;\n border-image: linear-gradient(var(--hairline, var(--border-subtle, rgba(0, 0, 0, 0.06))), var(--hairline, var(--border-subtle, rgba(0, 0, 0, 0.06)))) 1 / var(--hairline-width, 1px) 0 0 0;\n}\n\n/* Screen reader only */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes fadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n@keyframes contentShow {\n from {\n opacity: 0;\n transform: translate(-50%, calc(-50% + 8px));\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%);\n }\n}\n\n@keyframes contentHide {\n from {\n opacity: 1;\n transform: translate(-50%, -50%);\n }\n to {\n opacity: 0;\n transform: translate(-50%, calc(-50% + 8px));\n }\n}\n"
789
789
  }
790
790
  ]
791
791
  },
@@ -838,7 +838,7 @@
838
838
  {
839
839
  "path": "components/ui/dropdown-menu/dropdown-menu.module.css",
840
840
  "type": "registry:ui",
841
- "content": "/* Dropdown menu content\n *\n * Density axis: editorial admin values are baked in as a first-class density\n * layer, switched by data-density=\"editorial\" on any ancestor. Default rendering\n * is byte-identical to the pre-refactor canonical fallbacks. Theme-agnostic:\n * editorial values resolve from palette tokens (--surface-elev, --hairline,\n * --text-tertiary, --text-muted, --destructive) — correct on entr AND animal. */\n.content {\n z-index: 50;\n min-width: 12rem;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n gap: 0;\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--surface-popover, var(--surface-card, #ffffff));\n color: var(--text-primary, #111827);\n padding: var(--spacing-1, 0.25rem);\n box-shadow:\n 0 0 #0000,\n var(--shadow-lg);\n}\n\n:global([data-density=\"editorial\"]) .content {\n gap: var(--stroke-width-thin, 1px);\n border-radius: var(--radius-md);\n background: var(--surface-elev, color-mix(in srgb, var(--surface-card), var(--surface-muted, var(--surface-card))));\n padding: calc(var(--spacing-1, 0.25rem) * 1.5);\n box-shadow:\n inset 0 0 0 1px var(--hairline, var(--border-subtle, transparent)),\n var(--shadow-lg);\n}\n\n.content[data-state=\"open\"] {\n animation: contentShow var(--motion-duration-fast, 100ms) var(--motion-easing-enter, ease-out);\n}\n\n.content[data-state=\"closed\"] {\n animation: contentHide var(--motion-duration-fast, 100ms) var(--motion-easing-exit, ease-in);\n}\n\n/* Breakout variant — use inside scroll-clipped containers (ex: DataTable rows) */\n.contentBreakout {\n z-index: 200;\n box-shadow: var(--shadow-xl, 0 28px 64px rgba(0, 0, 0, 0.55), 0 8px 16px rgba(0, 0, 0, 0.30), 0 0 0 1px rgba(255, 255, 255, 0.06));\n}\n\n/* Sub content */\n.subContent {\n z-index: 50;\n min-width: 9rem;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n gap: 0;\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--surface-popover, var(--surface-card, #ffffff));\n color: var(--text-primary, #111827);\n padding: var(--spacing-1, 0.25rem);\n box-shadow:\n 0 0 #0000,\n var(--shadow-lg);\n}\n\n:global([data-density=\"editorial\"]) .subContent {\n gap: var(--stroke-width-thin, 1px);\n border-radius: var(--radius-md);\n background: var(--surface-elev, color-mix(in srgb, var(--surface-card), var(--surface-muted, var(--surface-card))));\n padding: calc(var(--spacing-1, 0.25rem) * 1.5);\n box-shadow:\n inset 0 0 0 1px var(--hairline, var(--border-subtle, transparent)),\n var(--shadow-lg);\n}\n\n.subContent[data-state=\"open\"] {\n animation: contentShow var(--motion-duration-fast, 100ms) var(--motion-easing-enter, ease-out);\n}\n\n.subContent[data-state=\"closed\"] {\n animation: contentHide var(--motion-duration-fast, 100ms) var(--motion-easing-exit, ease-in);\n}\n\n/* Menu item\n * Default: canonical spacing, radius-md, font-size-sm, interactive-hover bg.\n * Density axis: editorial uses 13px, 8px 10px padding, radius-sm, surface-subtle hover. */\n.item {\n position: relative;\n display: flex;\n cursor: default;\n user-select: none;\n align-items: center;\n gap: calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n border-radius: var(--radius-md, 0.375rem);\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-sm, 0.875rem);\n outline: none;\n transition: background-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out);\n}\n\n:global([data-density=\"editorial\"]) .item {\n font-size: 13px;\n padding: var(--spacing-2, 0.5rem) calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n gap: var(--spacing-3);\n border-radius: var(--radius-sm);\n}\n\n/* Leading icon — default: inherit item color and 1em size.\n * Density axis: editorial uses text-tertiary color and 16px size. */\n.item > svg {\n flex-shrink: 0;\n color: currentColor;\n width: 1em;\n height: 1em;\n}\n\n:global([data-density=\"editorial\"]) .item > svg {\n color: var(--text-tertiary);\n width: 16px;\n height: 16px;\n}\n\n.item:focus,\n.item[data-highlighted] {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n color: var(--text-primary, #111827);\n}\n\n:global([data-density=\"editorial\"]) .item:focus,\n:global([data-density=\"editorial\"]) .item[data-highlighted] {\n background-color: var(--surface-subtle);\n}\n\n.item[data-disabled] {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.itemDestructive {\n color: var(--text-error, var(--destructive, #ef4444));\n}\n\n/* Destructive icons track the destructive color, overriding the tertiary tint. */\n.itemDestructive > svg {\n color: var(--text-error, var(--destructive, #ef4444));\n}\n\n.itemDestructive:focus,\n.itemDestructive[data-highlighted] {\n background-color: var(--surface-error-subtle, rgba(239, 68, 68, 0.1));\n color: var(--text-error, var(--destructive, #ef4444));\n}\n\n:global([data-density=\"editorial\"]) .itemDestructive:focus,\n:global([data-density=\"editorial\"]) .itemDestructive[data-highlighted] {\n background-color: color-mix(in srgb, var(--destructive) 14%, transparent);\n}\n\n.itemInset {\n padding-left: calc(var(--spacing-8, 2rem) + var(--spacing-1, 0.25rem) * 1.5);\n}\n\n/* Checkbox and radio items */\n.checkboxItem,\n.radioItem {\n position: relative;\n display: flex;\n cursor: default;\n user-select: none;\n align-items: center;\n gap: calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n border-radius: var(--radius-md, 0.375rem);\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n padding-right: var(--spacing-8, 2rem);\n font-size: var(--font-size-sm, 0.875rem);\n outline: none;\n transition: background-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out);\n}\n\n:global([data-density=\"editorial\"]) .checkboxItem,\n:global([data-density=\"editorial\"]) .radioItem {\n font-size: 13px;\n padding: var(--spacing-2, 0.5rem) calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n padding-right: var(--spacing-8, 2rem);\n gap: var(--spacing-3);\n border-radius: var(--radius-sm);\n}\n\n.checkboxItem:focus,\n.checkboxItem[data-highlighted],\n.radioItem:focus,\n.radioItem[data-highlighted] {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n color: var(--text-primary, #111827);\n}\n\n:global([data-density=\"editorial\"]) .checkboxItem:focus,\n:global([data-density=\"editorial\"]) .checkboxItem[data-highlighted],\n:global([data-density=\"editorial\"]) .radioItem:focus,\n:global([data-density=\"editorial\"]) .radioItem[data-highlighted] {\n background-color: var(--surface-subtle);\n}\n\n.checkboxItem[data-disabled],\n.radioItem[data-disabled] {\n pointer-events: none;\n opacity: 0.5;\n}\n\n/* Item indicator */\n.itemIndicator {\n pointer-events: none;\n position: absolute;\n right: 0.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1rem;\n height: 1rem;\n}\n\n/* Label\n * Default: canonical 12px / text-secondary / sentence-case.\n * Density axis: editorial uses 11px / uppercase / 0.08em tracking / text-tertiary / 6px 10px 2px padding. */\n.label {\n padding: calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: inherit;\n letter-spacing: normal;\n text-transform: none;\n color: var(--text-secondary, #6b7280);\n}\n\n:global([data-density=\"editorial\"]) .label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-tertiary);\n padding: calc(var(--spacing-1, 0.25rem) * 1.5) calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2) calc(var(--spacing-1, 0.25rem) / 2);\n}\n\n/* Separator\n * Default: spacing-1 margin, border-default color.\n * Density axis: editorial uses 4px 2px margin, hairline color. */\n.separator {\n height: 1px;\n margin: var(--spacing-1, 0.25rem) calc(-1 * var(--spacing-1, 0.25rem));\n background-color: var(--border-default, #e5e7eb);\n}\n\n:global([data-density=\"editorial\"]) .separator {\n background-color: var(--hairline, var(--border-subtle, transparent));\n margin: var(--spacing-1, 0.25rem) calc(var(--spacing-1, 0.25rem) / 2);\n}\n\n/* Shortcut\n * Default: 12px / 0.1em tracking / text-secondary.\n * Density axis: editorial uses 11px / 0.04em tracking / text-muted. */\n.shortcut {\n margin-left: auto;\n font-size: var(--font-size-xs, 0.75rem);\n letter-spacing: 0.1em;\n color: var(--text-secondary, #6b7280);\n}\n\n:global([data-density=\"editorial\"]) .shortcut {\n font-size: 11px;\n color: var(--text-muted);\n letter-spacing: 0.04em;\n}\n\n/* Sub trigger */\n.subTrigger {\n display: flex;\n cursor: default;\n user-select: none;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n border-radius: var(--radius-md, 0.375rem);\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-sm, 0.875rem);\n outline: none;\n transition: background-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out);\n}\n\n:global([data-density=\"editorial\"]) .subTrigger {\n font-size: 13px;\n padding: var(--spacing-2, 0.5rem) calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n gap: var(--spacing-3);\n border-radius: var(--radius-sm);\n}\n\n.subTrigger > svg:not(.subTriggerIcon) {\n flex-shrink: 0;\n color: currentColor;\n width: 1em;\n height: 1em;\n}\n\n:global([data-density=\"editorial\"]) .subTrigger > svg:not(.subTriggerIcon) {\n color: var(--text-tertiary);\n width: 16px;\n height: 16px;\n}\n\n.subTrigger:focus,\n.subTrigger[data-state=\"open\"] {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n color: var(--text-primary, #111827);\n}\n\n:global([data-density=\"editorial\"]) .subTrigger:focus,\n:global([data-density=\"editorial\"]) .subTrigger[data-state=\"open\"] {\n background-color: var(--surface-subtle);\n}\n\n.subTriggerIcon {\n margin-left: auto;\n width: 1rem;\n height: 1rem;\n color: currentColor;\n}\n\n@keyframes contentShow {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@keyframes contentHide {\n from {\n opacity: 1;\n transform: scale(1);\n }\n to {\n opacity: 0;\n transform: scale(0.95);\n }\n}\n"
841
+ "content": "/* Dropdown menu content\n *\n * Density axis: editorial admin values are baked in as a first-class density\n * layer, switched by data-density=\"editorial\" on any ancestor. Default rendering\n * is byte-identical to the pre-refactor canonical fallbacks. Theme-agnostic:\n * editorial values resolve from palette tokens (--surface-elev, --hairline,\n * --text-tertiary, --text-muted, --destructive) — correct on entr AND animal. */\n.content {\n z-index: 50;\n min-width: 12rem;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n gap: 0;\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--surface-popover, var(--surface-card, #ffffff));\n color: var(--text-primary, #111827);\n padding: var(--spacing-1, 0.25rem);\n box-shadow:\n 0 0 #0000,\n var(--shadow-lg);\n}\n\n:global([data-density=\"editorial\"]) .content {\n gap: var(--stroke-width-thin, 1px);\n border-radius: var(--radius-md);\n background: var(--surface-elev, color-mix(in srgb, var(--surface-card), var(--surface-muted, var(--surface-card))));\n padding: calc(var(--spacing-1, 0.25rem) * 1.5);\n box-shadow:\n inset 0 0 0 var(--hairline-width, 1px) var(--hairline, var(--border-subtle, transparent)),\n var(--shadow-lg);\n}\n\n.content[data-state=\"open\"] {\n animation: contentShow var(--motion-duration-fast, 100ms) var(--motion-easing-enter, ease-out);\n}\n\n.content[data-state=\"closed\"] {\n animation: contentHide var(--motion-duration-fast, 100ms) var(--motion-easing-exit, ease-in);\n}\n\n/* Breakout variant — use inside scroll-clipped containers (ex: DataTable rows) */\n.contentBreakout {\n z-index: 200;\n box-shadow: var(--shadow-xl, 0 28px 64px rgba(0, 0, 0, 0.55), 0 8px 16px rgba(0, 0, 0, 0.30), 0 0 0 1px rgba(255, 255, 255, 0.06));\n}\n\n/* Sub content */\n.subContent {\n z-index: 50;\n min-width: 9rem;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n gap: 0;\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--surface-popover, var(--surface-card, #ffffff));\n color: var(--text-primary, #111827);\n padding: var(--spacing-1, 0.25rem);\n box-shadow:\n 0 0 #0000,\n var(--shadow-lg);\n}\n\n:global([data-density=\"editorial\"]) .subContent {\n gap: var(--stroke-width-thin, 1px);\n border-radius: var(--radius-md);\n background: var(--surface-elev, color-mix(in srgb, var(--surface-card), var(--surface-muted, var(--surface-card))));\n padding: calc(var(--spacing-1, 0.25rem) * 1.5);\n box-shadow:\n inset 0 0 0 var(--hairline-width, 1px) var(--hairline, var(--border-subtle, transparent)),\n var(--shadow-lg);\n}\n\n.subContent[data-state=\"open\"] {\n animation: contentShow var(--motion-duration-fast, 100ms) var(--motion-easing-enter, ease-out);\n}\n\n.subContent[data-state=\"closed\"] {\n animation: contentHide var(--motion-duration-fast, 100ms) var(--motion-easing-exit, ease-in);\n}\n\n/* Menu item\n * Default: canonical spacing, radius-md, font-size-sm, interactive-hover bg.\n * Density axis: editorial uses 13px, 8px 10px padding, radius-sm, surface-subtle hover. */\n.item {\n position: relative;\n display: flex;\n cursor: default;\n user-select: none;\n align-items: center;\n gap: calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n border-radius: var(--radius-md, 0.375rem);\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-sm, 0.875rem);\n outline: none;\n transition: background-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out);\n}\n\n:global([data-density=\"editorial\"]) .item {\n font-size: 13px;\n padding: var(--spacing-2, 0.5rem) calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n gap: var(--spacing-3);\n border-radius: var(--radius-sm);\n}\n\n/* Leading icon — default: inherit item color and 1em size.\n * Density axis: editorial uses text-tertiary color and 16px size. */\n.item > svg {\n flex-shrink: 0;\n color: currentColor;\n width: 1em;\n height: 1em;\n}\n\n:global([data-density=\"editorial\"]) .item > svg {\n color: var(--text-tertiary);\n width: 16px;\n height: 16px;\n}\n\n.item:focus,\n.item[data-highlighted] {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n color: var(--text-primary, #111827);\n}\n\n:global([data-density=\"editorial\"]) .item:focus,\n:global([data-density=\"editorial\"]) .item[data-highlighted] {\n background-color: var(--surface-subtle);\n}\n\n.item[data-disabled] {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.itemDestructive {\n color: var(--text-error, var(--destructive, #ef4444));\n}\n\n/* Destructive icons track the destructive color, overriding the tertiary tint. */\n.itemDestructive > svg {\n color: var(--text-error, var(--destructive, #ef4444));\n}\n\n.itemDestructive:focus,\n.itemDestructive[data-highlighted] {\n background-color: var(--surface-error-subtle, rgba(239, 68, 68, 0.1));\n color: var(--text-error, var(--destructive, #ef4444));\n}\n\n:global([data-density=\"editorial\"]) .itemDestructive:focus,\n:global([data-density=\"editorial\"]) .itemDestructive[data-highlighted] {\n background-color: color-mix(in srgb, var(--destructive) 14%, transparent);\n}\n\n.itemInset {\n padding-left: calc(var(--spacing-8, 2rem) + var(--spacing-1, 0.25rem) * 1.5);\n}\n\n/* Checkbox and radio items */\n.checkboxItem,\n.radioItem {\n position: relative;\n display: flex;\n cursor: default;\n user-select: none;\n align-items: center;\n gap: calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n border-radius: var(--radius-md, 0.375rem);\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n padding-right: var(--spacing-8, 2rem);\n font-size: var(--font-size-sm, 0.875rem);\n outline: none;\n transition: background-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out);\n}\n\n:global([data-density=\"editorial\"]) .checkboxItem,\n:global([data-density=\"editorial\"]) .radioItem {\n font-size: 13px;\n padding: var(--spacing-2, 0.5rem) calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n padding-right: var(--spacing-8, 2rem);\n gap: var(--spacing-3);\n border-radius: var(--radius-sm);\n}\n\n.checkboxItem:focus,\n.checkboxItem[data-highlighted],\n.radioItem:focus,\n.radioItem[data-highlighted] {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n color: var(--text-primary, #111827);\n}\n\n:global([data-density=\"editorial\"]) .checkboxItem:focus,\n:global([data-density=\"editorial\"]) .checkboxItem[data-highlighted],\n:global([data-density=\"editorial\"]) .radioItem:focus,\n:global([data-density=\"editorial\"]) .radioItem[data-highlighted] {\n background-color: var(--surface-subtle);\n}\n\n.checkboxItem[data-disabled],\n.radioItem[data-disabled] {\n pointer-events: none;\n opacity: 0.5;\n}\n\n/* Item indicator */\n.itemIndicator {\n pointer-events: none;\n position: absolute;\n right: 0.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1rem;\n height: 1rem;\n}\n\n/* Label\n * Default: canonical 12px / text-secondary / sentence-case.\n * Density axis: editorial uses 11px / uppercase / 0.08em tracking / text-tertiary / 6px 10px 2px padding. */\n.label {\n padding: calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: inherit;\n letter-spacing: normal;\n text-transform: none;\n color: var(--text-secondary, #6b7280);\n}\n\n:global([data-density=\"editorial\"]) .label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-tertiary);\n padding: calc(var(--spacing-1, 0.25rem) * 1.5) calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2) calc(var(--spacing-1, 0.25rem) / 2);\n}\n\n/* Separator\n * Default: spacing-1 margin, border-default color.\n * Density axis: editorial uses 4px 2px margin, hairline color. */\n.separator {\n height: 1px;\n margin: var(--spacing-1, 0.25rem) calc(-1 * var(--spacing-1, 0.25rem));\n background-color: var(--border-default, #e5e7eb);\n}\n\n:global([data-density=\"editorial\"]) .separator {\n /* The 1px box is geometry; the line is an inset shadow whose spread is\n * --hairline-width, so 0 empties the box and nothing moves. */\n background-color: transparent;\n box-shadow: inset 0 0 0 var(--hairline-width, 1px) var(--hairline, var(--border-subtle, transparent));\n margin: var(--spacing-1, 0.25rem) calc(var(--spacing-1, 0.25rem) / 2);\n}\n\n/* Shortcut\n * Default: 12px / 0.1em tracking / text-secondary.\n * Density axis: editorial uses 11px / 0.04em tracking / text-muted. */\n.shortcut {\n margin-left: auto;\n font-size: var(--font-size-xs, 0.75rem);\n letter-spacing: 0.1em;\n color: var(--text-secondary, #6b7280);\n}\n\n:global([data-density=\"editorial\"]) .shortcut {\n font-size: 11px;\n color: var(--text-muted);\n letter-spacing: 0.04em;\n}\n\n/* Sub trigger */\n.subTrigger {\n display: flex;\n cursor: default;\n user-select: none;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n border-radius: var(--radius-md, 0.375rem);\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n font-size: var(--font-size-sm, 0.875rem);\n outline: none;\n transition: background-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out), color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out);\n}\n\n:global([data-density=\"editorial\"]) .subTrigger {\n font-size: 13px;\n padding: var(--spacing-2, 0.5rem) calc(var(--spacing-2, 0.5rem) + var(--spacing-1, 0.25rem) / 2);\n gap: var(--spacing-3);\n border-radius: var(--radius-sm);\n}\n\n.subTrigger > svg:not(.subTriggerIcon) {\n flex-shrink: 0;\n color: currentColor;\n width: 1em;\n height: 1em;\n}\n\n:global([data-density=\"editorial\"]) .subTrigger > svg:not(.subTriggerIcon) {\n color: var(--text-tertiary);\n width: 16px;\n height: 16px;\n}\n\n.subTrigger:focus,\n.subTrigger[data-state=\"open\"] {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n color: var(--text-primary, #111827);\n}\n\n:global([data-density=\"editorial\"]) .subTrigger:focus,\n:global([data-density=\"editorial\"]) .subTrigger[data-state=\"open\"] {\n background-color: var(--surface-subtle);\n}\n\n.subTriggerIcon {\n margin-left: auto;\n width: 1rem;\n height: 1rem;\n color: currentColor;\n}\n\n@keyframes contentShow {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@keyframes contentHide {\n from {\n opacity: 1;\n transform: scale(1);\n }\n to {\n opacity: 0;\n transform: scale(0.95);\n }\n}\n"
842
842
  }
843
843
  ]
844
844
  },
@@ -864,7 +864,7 @@
864
864
  {
865
865
  "path": "components/ui/tabs/tabs.module.css",
866
866
  "type": "registry:ui",
867
- "content": "/* Tabs root */\n.root {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Tabs list */\n.list {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--tabs-list-radius, var(--radius-full, 9999px));\n padding: var(--tabs-list-padding, calc(var(--spacing-1, 0.25rem) * 0.75));\n color: var(--tabs-list-color, var(--text-secondary, #6b7280));\n height: var(--tabs-list-height, 2.25rem);\n width: fit-content;\n}\n\n.listVariantDefault {\n background-color: var(--tabs-list-bg, var(--surface-muted, #f3f4f6));\n}\n\n.listVariantLine {\n position: relative;\n gap: var(--spacing-2, 0.5rem);\n background-color: transparent;\n border-radius: 0;\n border-bottom: var(--tabs-line-border-bottom, 1px solid var(--hairline, var(--border-default, #e5e7eb)));\n height: auto;\n padding-bottom: 0;\n width: 100%;\n justify-content: flex-start;\n}\n\n/* Line variant: content-width triggers with comfortable padding — the strip\n reads as an underlined rail, not a segmented control. */\n.listVariantLine .trigger {\n flex: 0 0 auto;\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n}\n\n/* Tabs trigger */\n.trigger {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: calc(var(--spacing-1, 0.25rem) * 1.5);\n border-radius: var(--tabs-trigger-radius, var(--radius-md, 0.375rem));\n border: 1px solid transparent;\n padding: var(--tabs-trigger-padding, var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem));\n font-size: var(--tabs-trigger-font-size, var(--font-size-sm, 0.875rem));\n font-weight: var(--tabs-trigger-font-weight, var(--font-weight-medium, 500));\n letter-spacing: var(--tabs-trigger-letter-spacing, inherit);\n text-transform: var(--tabs-trigger-text-transform, inherit);\n white-space: nowrap;\n color: var(--tabs-trigger-color, var(--text-secondary, #6b7280));\n background: transparent;\n cursor: pointer;\n transition: color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), border-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), transform var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n outline: none;\n flex: 1;\n height: calc(100% - 2px);\n}\n\n.trigger:active:not(:disabled) {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .trigger:hover {\n color: var(--text-primary, #111827);\n }\n}\n\n.trigger:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.trigger:disabled {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.trigger[data-state=\"active\"] {\n background-color: var(--tabs-trigger-active-bg, var(--surface-page, #ffffff));\n color: var(--tabs-trigger-active-color, var(--text-primary, #111827));\n box-shadow: var(--tabs-trigger-active-shadow, var(--shadow-sm));\n}\n\n/* Trigger label — wraps children to allow count pill alongside */\n.label {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Count pill */\n.count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n padding: 0 var(--spacing-2, 0.5rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-medium, 500);\n font-variant-numeric: tabular-nums;\n line-height: 1.5;\n vertical-align: baseline;\n}\n\n.countNeutral {\n background-color: var(--surface-muted, #f3f4f6);\n color: var(--text-secondary, #6b7280);\n}\n\n.countPrimary {\n background-color: var(--surface-accent-subtle, #eff6ff);\n color: var(--text-link, #2563eb);\n}\n\n/* Active-state re-tone: regardless of countTone, re-tone to primary on active */\n.trigger[data-state=\"active\"] .count {\n background-color: var(--surface-accent-subtle, #eff6ff);\n color: var(--text-link, #2563eb);\n}\n\n/* Tabs content */\n.content {\n flex: 1;\n font-size: var(--font-size-sm, 0.875rem);\n outline: none;\n}\n\n.content:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Line variant: active trigger loses shadow/bg, just text color */\n.listVariantLine .trigger[data-state=\"active\"] {\n background-color: transparent;\n box-shadow: none;\n}\n\n/* Animated sliding indicator for line variant */\n.indicator {\n position: absolute;\n bottom: -1px;\n height: var(--tabs-indicator-height, 2px);\n /* Default: text-primary */\n background-color: var(--tabs-indicator-color, var(--text-primary, #111827));\n border-radius: var(--radius-full, 9999px);\n transition: left var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out),\n width var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* Density axis — editorial: brand primary underline for dark admin surfaces */\n:global([data-density=\"editorial\"]) .indicator {\n background-color: var(--primary);\n}\n"
867
+ "content": "/* Tabs root */\n.root {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Tabs list */\n.list {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--tabs-list-radius, var(--radius-full, 9999px));\n padding: var(--tabs-list-padding, calc(var(--spacing-1, 0.25rem) * 0.75));\n color: var(--tabs-list-color, var(--text-secondary, #6b7280));\n height: var(--tabs-list-height, 2.25rem);\n width: fit-content;\n}\n\n.listVariantDefault {\n background-color: var(--tabs-list-bg, var(--surface-muted, #f3f4f6));\n}\n\n.listVariantLine {\n position: relative;\n gap: var(--spacing-2, 0.5rem);\n background-color: transparent;\n border-radius: 0;\n /* The border is geometry; the rail rule is painted by border-image at\n --hairline-width, so --hairline-width: 0 removes it with no layout shift.\n A bound --tabs-line-border-bottom (a full border shorthand) is not valid\n as a border-image, so binding it drops the painted rule and draws the\n bound border instead. */\n border-bottom: var(--tabs-line-border-bottom, 1px solid transparent);\n border-image: var(\n --tabs-line-border-bottom,\n linear-gradient(var(--hairline, var(--border-default, #e5e7eb)), var(--hairline, var(--border-default, #e5e7eb))) 1 / 0 0 var(--hairline-width, 1px) 0\n );\n height: auto;\n padding-bottom: 0;\n width: 100%;\n justify-content: flex-start;\n}\n\n/* Line variant: content-width triggers with comfortable padding — the strip\n reads as an underlined rail, not a segmented control. */\n.listVariantLine .trigger {\n flex: 0 0 auto;\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n}\n\n/* Tabs trigger */\n.trigger {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: calc(var(--spacing-1, 0.25rem) * 1.5);\n border-radius: var(--tabs-trigger-radius, var(--radius-md, 0.375rem));\n border: 1px solid transparent;\n padding: var(--tabs-trigger-padding, var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem));\n font-size: var(--tabs-trigger-font-size, var(--font-size-sm, 0.875rem));\n font-weight: var(--tabs-trigger-font-weight, var(--font-weight-medium, 500));\n letter-spacing: var(--tabs-trigger-letter-spacing, inherit);\n text-transform: var(--tabs-trigger-text-transform, inherit);\n white-space: nowrap;\n color: var(--tabs-trigger-color, var(--text-secondary, #6b7280));\n background: transparent;\n cursor: pointer;\n transition: color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), border-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out), transform var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n outline: none;\n flex: 1;\n height: calc(100% - 2px);\n}\n\n.trigger:active:not(:disabled) {\n transform: translateY(1px);\n transition: none;\n}\n\n@media (hover: hover) {\n .trigger:hover {\n color: var(--text-primary, #111827);\n }\n}\n\n.trigger:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.trigger:disabled {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.trigger[data-state=\"active\"] {\n background-color: var(--tabs-trigger-active-bg, var(--surface-page, #ffffff));\n color: var(--tabs-trigger-active-color, var(--text-primary, #111827));\n box-shadow: var(--tabs-trigger-active-shadow, var(--shadow-sm));\n}\n\n/* Trigger label — wraps children to allow count pill alongside */\n.label {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Count pill */\n.count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n padding: 0 var(--spacing-2, 0.5rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-medium, 500);\n font-variant-numeric: tabular-nums;\n line-height: 1.5;\n vertical-align: baseline;\n}\n\n.countNeutral {\n background-color: var(--surface-muted, #f3f4f6);\n color: var(--text-secondary, #6b7280);\n}\n\n.countPrimary {\n background-color: var(--surface-accent-subtle, #eff6ff);\n color: var(--text-link, #2563eb);\n}\n\n/* Active-state re-tone: regardless of countTone, re-tone to primary on active */\n.trigger[data-state=\"active\"] .count {\n background-color: var(--surface-accent-subtle, #eff6ff);\n color: var(--text-link, #2563eb);\n}\n\n/* Tabs content */\n.content {\n flex: 1;\n font-size: var(--font-size-sm, 0.875rem);\n outline: none;\n}\n\n.content:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Line variant: active trigger loses shadow/bg, just text color */\n.listVariantLine .trigger[data-state=\"active\"] {\n background-color: transparent;\n box-shadow: none;\n}\n\n/* Animated sliding indicator for line variant */\n.indicator {\n position: absolute;\n bottom: -1px;\n height: var(--tabs-indicator-height, 2px);\n /* Default: text-primary */\n background-color: var(--tabs-indicator-color, var(--text-primary, #111827));\n border-radius: var(--radius-full, 9999px);\n transition: left var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out),\n width var(--motion-duration-normal, 200ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* Density axis — editorial: brand primary underline for dark admin surfaces */\n:global([data-density=\"editorial\"]) .indicator {\n background-color: var(--primary);\n}\n"
868
868
  }
869
869
  ]
870
870
  },
@@ -1042,7 +1042,7 @@
1042
1042
  {
1043
1043
  "path": "components/ui/popover/popover.module.css",
1044
1044
  "type": "registry:ui",
1045
- "content": "/* Popover content */\n.content {\n z-index: 50;\n min-width: 12rem;\n overflow: hidden;\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--surface-popover, var(--surface-card, #ffffff));\n color: var(--text-primary, #111827);\n padding: var(--spacing-4, 1rem);\n box-shadow: var(--shadow-lg);\n}\n\n/* Footer — right-aligned action row separated from the body by a top border.\n Negative horizontal margins let the border span the full popover width while\n the action row stays aligned with body content (offsets PopoverContent's\n padding: var(--spacing-4)). */\n.footer {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n gap: var(--spacing-2, 0.5rem);\n margin-top: var(--spacing-3, 0.75rem);\n padding-top: var(--spacing-3, 0.75rem);\n margin-inline: calc(-1 * var(--spacing-4, 1rem));\n padding-inline: var(--spacing-4, 1rem);\n border-top: 1px solid var(--border-default, #e5e7eb);\n}\n\n.content[data-state=\"open\"] {\n animation: contentShow var(--motion-duration-fast, 100ms) var(--motion-easing-enter, ease-out);\n}\n\n.content[data-state=\"closed\"] {\n animation: contentHide var(--motion-duration-fast, 100ms) var(--motion-easing-exit, ease-in);\n}\n\n@keyframes contentShow {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@keyframes contentHide {\n from {\n opacity: 1;\n transform: scale(1);\n }\n to {\n opacity: 0;\n transform: scale(0.95);\n }\n}\n\n/* ─── SelectionList ─────────────────────────────────────────────────── */\n\n.selectionLabel {\n padding: var(--spacing-1, 0.25rem) var(--spacing-3, 0.75rem);\n padding-bottom: var(--spacing-2, 0.5rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: 600;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n color: var(--text-secondary, #6b7280);\n}\n\n.selectionList {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-1, 0.25rem);\n outline: none;\n}\n\n.selectionItem {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n border-radius: var(--radius-md, 0.375rem);\n font-size: var(--font-size-sm, 0.875rem);\n color: var(--text-primary, #111827);\n cursor: pointer;\n outline: none;\n user-select: none;\n transition: background-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out);\n}\n\n.selectionItem:hover {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n}\n\n.selectionItem:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--primary, #6366f1);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.selectionItemSelected {\n background-color: var(--surface-subtle, #f9fafb);\n}\n\n.selectionItemSelected:hover {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n}\n\n.selectionItemDisabled {\n opacity: var(--opacity-40, 0.4);\n pointer-events: none;\n cursor: default;\n}\n\n.selectionItemLeadingIcon {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 1rem;\n height: 1rem;\n color: var(--text-secondary, #6b7280);\n}\n\n/* Indicator plate — always rendered for stable row geometry */\n.selectionIndicator {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 1rem; /* 16px */\n height: 1rem; /* 16px */\n transition:\n background-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out),\n border-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* Checkbox: rounded-4px plate */\n.selectionIndicatorCheckbox {\n border-radius: 4px;\n border: var(--stroke-width-thin, 1px) solid var(--hairline-strong, #d1d5db);\n background-color: var(--surface-card, #ffffff);\n color: transparent;\n}\n\n.selectionIndicatorCheckbox.selectionIndicatorSelected {\n background-color: var(--primary, #6366f1);\n border-color: var(--primary, #6366f1);\n color: var(--interactive-primary-text, #f9fafb);\n}\n\n.selectionIndicatorCheckbox svg {\n width: 0.625rem; /* 10px */\n height: 0.625rem;\n}\n\n/* Radio: circular plate with ring */\n.selectionIndicatorRadio {\n border-radius: 9999px;\n border: var(--stroke-width-thin, 1px) solid var(--hairline-strong, #d1d5db);\n background-color: var(--surface-card, #ffffff);\n}\n\n.selectionIndicatorRadio.selectionIndicatorSelected {\n border: 1.5px solid var(--primary, #6366f1);\n}\n\n/* Radio filled dot when selected */\n.selectionRadioDot {\n display: block;\n width: 0.5rem; /* 8px */\n height: 0.5rem;\n border-radius: 9999px;\n background-color: var(--primary, #6366f1);\n}\n\n.selectionItemLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Count badge — right-aligned, tabular numerals, muted */\n.selectionCount {\n margin-left: auto;\n flex-shrink: 0;\n font-size: var(--font-size-xs, 0.75rem);\n font-variant-numeric: tabular-nums;\n color: var(--text-secondary, #6b7280);\n}\n"
1045
+ "content": "/* Popover content */\n.content {\n z-index: 50;\n min-width: 12rem;\n overflow: hidden;\n border-radius: var(--radius-lg, 0.5rem);\n background-color: var(--surface-popover, var(--surface-card, #ffffff));\n color: var(--text-primary, #111827);\n padding: var(--spacing-4, 1rem);\n box-shadow: var(--shadow-lg);\n}\n\n/* Footer — right-aligned action row separated from the body by a top border.\n Negative horizontal margins let the border span the full popover width while\n the action row stays aligned with body content (offsets PopoverContent's\n padding: var(--spacing-4)). */\n.footer {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n gap: var(--spacing-2, 0.5rem);\n margin-top: var(--spacing-3, 0.75rem);\n padding-top: var(--spacing-3, 0.75rem);\n margin-inline: calc(-1 * var(--spacing-4, 1rem));\n padding-inline: var(--spacing-4, 1rem);\n border-top: 1px solid var(--border-default, #e5e7eb);\n}\n\n.content[data-state=\"open\"] {\n animation: contentShow var(--motion-duration-fast, 100ms) var(--motion-easing-enter, ease-out);\n}\n\n.content[data-state=\"closed\"] {\n animation: contentHide var(--motion-duration-fast, 100ms) var(--motion-easing-exit, ease-in);\n}\n\n@keyframes contentShow {\n from {\n opacity: 0;\n transform: scale(0.95);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n\n@keyframes contentHide {\n from {\n opacity: 1;\n transform: scale(1);\n }\n to {\n opacity: 0;\n transform: scale(0.95);\n }\n}\n\n/* ─── SelectionList ─────────────────────────────────────────────────── */\n\n.selectionLabel {\n padding: var(--spacing-1, 0.25rem) var(--spacing-3, 0.75rem);\n padding-bottom: var(--spacing-2, 0.5rem);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: 600;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n color: var(--text-secondary, #6b7280);\n}\n\n.selectionList {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-1, 0.25rem);\n outline: none;\n}\n\n.selectionItem {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n border-radius: var(--radius-md, 0.375rem);\n font-size: var(--font-size-sm, 0.875rem);\n color: var(--text-primary, #111827);\n cursor: pointer;\n outline: none;\n user-select: none;\n transition: background-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out);\n}\n\n.selectionItem:hover {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n}\n\n.selectionItem:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--primary, #6366f1);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.selectionItemSelected {\n background-color: var(--surface-subtle, #f9fafb);\n}\n\n.selectionItemSelected:hover {\n background-color: var(--surface-interactive-hover, #f3f4f6);\n}\n\n.selectionItemDisabled {\n opacity: var(--opacity-40, 0.4);\n pointer-events: none;\n cursor: default;\n}\n\n.selectionItemLeadingIcon {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 1rem;\n height: 1rem;\n color: var(--text-secondary, #6b7280);\n}\n\n/* Indicator plate — always rendered for stable row geometry */\n.selectionIndicator {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 1rem; /* 16px */\n height: 1rem; /* 16px */\n transition:\n background-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out),\n border-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out),\n outline-color var(--motion-duration-fast, 100ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* Checkbox: rounded-4px plate */\n.selectionIndicatorCheckbox {\n border-radius: 4px;\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline-strong, #d1d5db);\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n background-color: var(--surface-card, #ffffff);\n color: transparent;\n}\n\n.selectionIndicatorCheckbox.selectionIndicatorSelected {\n background-color: var(--primary, #6366f1);\n outline-color: var(--primary, #6366f1);\n color: var(--interactive-primary-text, #f9fafb);\n}\n\n.selectionIndicatorCheckbox svg {\n width: 0.625rem; /* 10px */\n height: 0.625rem;\n}\n\n/* Radio: circular plate with ring */\n.selectionIndicatorRadio {\n border-radius: 9999px;\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline-strong, #d1d5db);\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n background-color: var(--surface-card, #ffffff);\n}\n\n.selectionIndicatorRadio.selectionIndicatorSelected {\n border: 1.5px solid var(--primary, #6366f1);\n outline: none;\n}\n\n/* Radio filled dot when selected */\n.selectionRadioDot {\n display: block;\n width: 0.5rem; /* 8px */\n height: 0.5rem;\n border-radius: 9999px;\n background-color: var(--primary, #6366f1);\n}\n\n.selectionItemLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Count badge — right-aligned, tabular numerals, muted */\n.selectionCount {\n margin-left: auto;\n flex-shrink: 0;\n font-size: var(--font-size-xs, 0.75rem);\n font-variant-numeric: tabular-nums;\n color: var(--text-secondary, #6b7280);\n}\n"
1046
1046
  }
1047
1047
  ]
1048
1048
  },
@@ -1177,7 +1177,7 @@
1177
1177
  {
1178
1178
  "path": "components/ui/table/table.module.css",
1179
1179
  "type": "registry:ui",
1180
- "content": "/* Table container — radius/shadow are theme-driven so a consumer that wraps the\n table in its own rounded shell (for example with a bulk bar on top) can flatten them.\n Under the editorial density axis these default to 0/none (flush admin shell).\n\n VI-625: every declaration below reads a component-scoped `--table-*` hook with\n its previous expression as the fallback, so a theme that binds nothing renders\n identically and a theme that binds the `table` family retunes every admin list\n page at once. Contract: the component-tokens module in packages/theme-engine/src */\n.container {\n position: relative;\n width: 100%;\n overflow-x: auto;\n overflow-y: hidden;\n border-radius: var(--dt-container-radius, var(--radius-lg, 0.5rem));\n box-shadow: var(--dt-container-shadow, var(--shadow-sm));\n}\n\n/* Editorial density — bakes the editorial-admin surface values into the density\n axis. Ancestor [data-density=\"editorial\"] (set by DataTable's density prop or\n an app-root attribute) triggers these rules. The var() reads keep --dt-* hooks\n working for per-theme overrides; the fallbacks resolve to the editorial values\n when no hook is set.\n Turbopack pure-selector rule: never bare :global — always paired with a local class. */\n:global([data-density=\"editorial\"]) .container {\n border-radius: var(--dt-container-radius, 0);\n box-shadow: var(--dt-container-shadow, none);\n}\n\n:global([data-density=\"editorial\"]) .head {\n background-color: var(--table-head-bg, var(--dt-header-bg, var(--surface-card)));\n}\n\n:global([data-density=\"editorial\"]) .cell {\n background-color: var(--table-cell-bg, var(--dt-row-bg, color-mix(in srgb, var(--surface-page) 75%, var(--surface-card) 25%)));\n}\n\n/* Table */\n.table {\n width: 100%;\n caption-side: bottom;\n border-collapse: collapse;\n font-size: var(--table-font-size, var(--font-size-sm, 0.875rem));\n color: var(--table-text-color, var(--text-primary, #111827));\n}\n\n/* TableHeader */\n.header tr {\n border-bottom: 1px solid var(--border-default, #e5e7eb);\n}\n\n/* TableBody */\n.body tr:last-child {\n border-bottom: none;\n}\n\n/* TableFooter */\n.footer {\n border-top: 1px solid var(--border-default, #e5e7eb);\n background-color: var(--table-footer-bg, var(--surface-muted, #f3f4f6));\n font-weight: var(--font-weight-medium, 500);\n}\n\n.footer tr:last-child {\n border-bottom: none;\n}\n\n/* TableRow */\n.row {\n border-bottom: var(--table-row-border, 1px solid var(--border-default, #e5e7eb));\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.row:not([data-slot=\"data-table-group-row\"]):hover {\n background-color: var(--table-row-hover-bg, var(--surface-muted, #f3f4f6));\n}\n\n.row[data-state=\"selected\"] {\n background-color: var(--table-row-selected-bg, var(--surface-muted, #f3f4f6));\n}\n\n/* TableHead — bg via --table-head-bg → --dt-header-bg (theme-driven; transparent\n default). The type hooks (family/size/letter-spacing/text-transform) fall back\n to `inherit`, which is the initial computed value for each of those inherited\n properties — so an unbound theme renders exactly as before they existed. */\n.head {\n height: var(--table-head-height, 3rem);\n padding-left: var(--table-head-padding-x, var(--spacing-3, 0.75rem));\n padding-right: var(--table-head-padding-x, var(--spacing-3, 0.75rem));\n text-align: left;\n vertical-align: middle;\n font-family: var(--table-head-font-family, inherit);\n font-size: var(--table-head-font-size, inherit);\n font-weight: var(--table-head-font-weight, var(--font-weight-medium, 500));\n letter-spacing: var(--table-head-letter-spacing, inherit);\n text-transform: var(--table-head-text-transform, inherit);\n white-space: nowrap;\n color: var(--table-head-color, var(--text-primary, #111827));\n background-color: var(--table-head-bg, var(--dt-header-bg, transparent));\n}\n\n/* TableCell — bg via --table-cell-bg → --dt-row-bg (theme-driven; transparent\n default). Painting on the cell gives a uniform row surface regardless of tr\n selected/hover state. */\n.cell {\n padding: var(--table-cell-padding, var(--spacing-3, 0.75rem));\n vertical-align: middle;\n font-size: var(--table-cell-font-size, inherit);\n color: var(--table-cell-color, var(--text-primary, #111827));\n background-color: var(--table-cell-bg, var(--dt-row-bg, transparent));\n border-top: var(--table-cell-border-top, 1px solid var(--hairline, transparent));\n}\n\n/* TableCaption */\n.caption {\n margin-top: var(--spacing-4, 1rem);\n font-size: var(--font-size-sm, 0.875rem);\n color: var(--table-caption-color, var(--text-secondary, #6b7280));\n}\n"
1180
+ "content": "/* Table container — radius/shadow are theme-driven so a consumer that wraps the\n table in its own rounded shell (for example with a bulk bar on top) can flatten them.\n Under the editorial density axis these default to 0/none (flush admin shell).\n\n VI-625: every declaration below reads a component-scoped `--table-*` hook with\n its previous expression as the fallback, so a theme that binds nothing renders\n identically and a theme that binds the `table` family retunes every admin list\n page at once. Contract: the component-tokens module in packages/theme-engine/src */\n.container {\n position: relative;\n width: 100%;\n overflow-x: auto;\n overflow-y: hidden;\n border-radius: var(--dt-container-radius, var(--radius-lg, 0.5rem));\n box-shadow: var(--dt-container-shadow, var(--shadow-sm));\n}\n\n/* Editorial density — bakes the editorial-admin surface values into the density\n axis. Ancestor [data-density=\"editorial\"] (set by DataTable's density prop or\n an app-root attribute) triggers these rules. The var() reads keep --dt-* hooks\n working for per-theme overrides; the fallbacks resolve to the editorial values\n when no hook is set.\n Turbopack pure-selector rule: never bare :global — always paired with a local class. */\n:global([data-density=\"editorial\"]) .container {\n border-radius: var(--dt-container-radius, 0);\n box-shadow: var(--dt-container-shadow, none);\n}\n\n:global([data-density=\"editorial\"]) .head {\n background-color: var(--table-head-bg, var(--dt-header-bg, var(--surface-card)));\n}\n\n:global([data-density=\"editorial\"]) .cell {\n background-color: var(--table-cell-bg, var(--dt-row-bg, color-mix(in srgb, var(--surface-page) 75%, var(--surface-card) 25%)));\n}\n\n/* Table */\n.table {\n width: 100%;\n caption-side: bottom;\n border-collapse: collapse;\n font-size: var(--table-font-size, var(--font-size-sm, 0.875rem));\n color: var(--table-text-color, var(--text-primary, #111827));\n}\n\n/* TableHeader */\n.header tr {\n border-bottom: 1px solid var(--border-default, #e5e7eb);\n}\n\n/* TableBody */\n.body tr:last-child {\n border-bottom: none;\n}\n\n/* TableFooter */\n.footer {\n border-top: 1px solid var(--border-default, #e5e7eb);\n background-color: var(--table-footer-bg, var(--surface-muted, #f3f4f6));\n font-weight: var(--font-weight-medium, 500);\n}\n\n.footer tr:last-child {\n border-bottom: none;\n}\n\n/* TableRow */\n.row {\n border-bottom: var(--table-row-border, 1px solid var(--border-default, #e5e7eb));\n transition: background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.row:not([data-slot=\"data-table-group-row\"]):hover {\n background-color: var(--table-row-hover-bg, var(--surface-muted, #f3f4f6));\n}\n\n.row[data-state=\"selected\"] {\n background-color: var(--table-row-selected-bg, var(--surface-muted, #f3f4f6));\n}\n\n/* TableHead — bg via --table-head-bg → --dt-header-bg (theme-driven; transparent\n default). The type hooks (family/size/letter-spacing/text-transform) fall back\n to `inherit`, which is the initial computed value for each of those inherited\n properties — so an unbound theme renders exactly as before they existed. */\n.head {\n height: var(--table-head-height, 3rem);\n padding-left: var(--table-head-padding-x, var(--spacing-3, 0.75rem));\n padding-right: var(--table-head-padding-x, var(--spacing-3, 0.75rem));\n text-align: left;\n vertical-align: middle;\n font-family: var(--table-head-font-family, inherit);\n font-size: var(--table-head-font-size, inherit);\n font-weight: var(--table-head-font-weight, var(--font-weight-medium, 500));\n letter-spacing: var(--table-head-letter-spacing, inherit);\n text-transform: var(--table-head-text-transform, inherit);\n white-space: nowrap;\n color: var(--table-head-color, var(--text-primary, #111827));\n background-color: var(--table-head-bg, var(--dt-header-bg, transparent));\n}\n\n/* TableCell — bg via --table-cell-bg → --dt-row-bg (theme-driven; transparent\n default). Painting on the cell gives a uniform row surface regardless of tr\n selected/hover state. */\n.cell {\n padding: var(--table-cell-padding, var(--spacing-3, 0.75rem));\n vertical-align: middle;\n font-size: var(--table-cell-font-size, inherit);\n color: var(--table-cell-color, var(--text-primary, #111827));\n background-color: var(--table-cell-bg, var(--dt-row-bg, transparent));\n border-top: var(--table-cell-border-top, var(--hairline-width, 1px) solid var(--hairline, transparent));\n}\n\n/* TableCaption */\n.caption {\n margin-top: var(--spacing-4, 1rem);\n font-size: var(--font-size-sm, 0.875rem);\n color: var(--table-caption-color, var(--text-secondary, #6b7280));\n}\n"
1181
1181
  }
1182
1182
  ]
1183
1183
  },
@@ -2188,7 +2188,7 @@
2188
2188
  {
2189
2189
  "path": "components/ui/editable-block/editable-block.module.css",
2190
2190
  "type": "registry:ui",
2191
- "content": "/* EditableBlock\n *\n * Canvas brand block — a card tile with an uppercase label header (+ done\n * check), a value body, a hover-revealed edit icon, and an editing state\n * (focus ring, inline input + save button, AI action slot).\n *\n * Visual source: docs/design/brand-workbench/journey (HTML) L274-285\n * (prototype selectors: block, bk, bv, ed, editrow, aibtn)\n *\n * All values flow through CSS custom properties. No hard-coded colors or\n * magic numbers — every value traces to a design token or is documented.\n */\n\n/* ─── Root tile ─────────────────────────────────────────────────────────── */\n\n.root {\n background: var(--surface-card, #ffffff);\n border: var(--stroke-width-thin, 1px) solid var(--hairline, rgba(0,0,0,0.06));\n border-radius: var(--radius-lg, 0.75rem);\n padding: var(--spacing-3_5, 0.875rem); /* prototype: 14px */\n box-shadow: var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05));\n min-height: 6rem; /* prototype: 96px */\n position: relative;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n transition:\n border-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n box-shadow var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* ─── State: default (hover) ────────────────────────────────────────────── */\n\n.stateDefault:hover {\n border-color: var(--border-default, #e5e7eb);\n}\n\n/* Reveal edit icon on hover — use CSS opacity so resting state is still\n in the DOM (avoids SSR/hydration race on mount). */\n.stateDefault:hover .editIcon {\n opacity: var(--opacity-80, 0.8);\n}\n\n/* ─── State: done ───────────────────────────────────────────────────────── */\n\n.stateDone:hover {\n border-color: var(--border-default, #e5e7eb);\n}\n\n.stateDone:hover .editIcon {\n opacity: var(--opacity-80, 0.8);\n}\n\n/* ─── State: editing ────────────────────────────────────────────────────── */\n\n.stateEditing {\n /* Focus-ring shadow — exception per token-rules Rule 2 (focus ring variant) */\n border-color: var(--primary, #111827);\n box-shadow: 0 0 0 var(--spacing-1, 0.25rem) var(--interactive-primary-soft, color-mix(in srgb, var(--interactive-primary-bg, #111827) 12%, transparent));\n cursor: default;\n}\n\n.stateEditing .label {\n color: var(--primary, #111827);\n}\n\n/* ─── Header row ────────────────────────────────────────────────────────── */\n\n.header {\n display: flex;\n align-items: center;\n gap: var(--spacing-1_5, 0.375rem); /* prototype: 7px ≈ 1.5 × 4px */\n margin-bottom: var(--spacing-2, 0.5rem); /* prototype: 8px */\n}\n\n/* ─── Label ─────────────────────────────────────────────────────────────── */\n\n.label {\n font-size: var(--font-size-2xs, 0.65625rem); /* prototype: 10.5px */\n font-weight: 800;\n letter-spacing: 0.07em;\n text-transform: uppercase;\n color: var(--text-tertiary, #6b7280);\n transition: color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* ─── Done check icon ───────────────────────────────────────────────────── */\n\n.doneCheck {\n display: inline-flex;\n align-items: center;\n color: var(--success, #22c55e);\n}\n\n.doneCheck > svg {\n width: var(--spacing-3, 0.75rem); /* prototype: 12px */\n height: var(--spacing-3, 0.75rem);\n stroke-width: 3;\n}\n\n/* ─── Value body ────────────────────────────────────────────────────────── */\n\n.value {\n font-size: var(--font-size-sm, 0.84375rem); /* prototype: 13.5px ≈ sm */\n color: var(--text-primary, #111827);\n line-height: var(--line-height-snug, 1.45);\n flex: 1;\n}\n\n/* ─── Hover-revealed edit icon ──────────────────────────────────────────── */\n\n.editIcon {\n position: absolute;\n top: var(--spacing-2_5, 0.6875rem); /* prototype: 11px */\n right: var(--spacing-2_5, 0.6875rem);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--text-tertiary, #6b7280);\n /* Resting state opacity=0 via CSS (visible on hover via .stateDefault:hover rule).\n Must remain in DOM at rest for accessibility & SSR hydration safety. */\n opacity: 0;\n background: none;\n border: none;\n padding: 0;\n cursor: pointer;\n transition: opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.editIcon:focus-visible {\n opacity: var(--opacity-80, 0.8);\n outline: var(--focus-ring-width, 2px) solid var(--primary, #111827);\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-sm, 0.25rem);\n}\n\n.editIcon > svg {\n width: var(--spacing-3_5, 0.875rem); /* prototype: 14px */\n height: var(--spacing-3_5, 0.875rem);\n}\n\n/* ─── Edit row (input + save button) ────────────────────────────────────── */\n\n.editRow {\n display: flex;\n gap: var(--spacing-1_5, 0.375rem); /* prototype: 7px */\n margin-top: var(--spacing-2_5, 0.625rem); /* prototype: 10px */\n}\n\n/* Inline edit input */\n.editInput {\n flex: 1;\n height: var(--spacing-8, 2rem); /* prototype: 32px */\n border-radius: var(--radius-md, 0.5rem);\n background: var(--surface-subtle, #f9fafb);\n border: var(--stroke-width-thin, 1px) solid\n color-mix(in srgb, var(--primary, #111827) 30%, transparent);\n font-size: var(--font-size-sm, 0.84375rem); /* prototype: 13px */\n font-weight: 600;\n color: var(--text-primary, #111827);\n padding: 0 var(--spacing-2_5, 0.625rem); /* prototype: 0 10px */\n outline: none;\n}\n\n.editInput:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--primary, #111827);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Save button (checkmark) */\n.saveButton {\n width: var(--spacing-8, 2rem); /* prototype: 32px */\n height: var(--spacing-8, 2rem);\n border-radius: var(--radius-md, 0.5rem);\n background: var(--primary, #111827);\n color: var(--primary-text, #ffffff);\n display: grid;\n place-items: center;\n flex: none;\n border: none;\n cursor: pointer;\n transition:\n background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.saveButton:hover {\n background: color-mix(in srgb, var(--primary, #111827) 88%, white);\n}\n\n.saveButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--primary, #111827);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.saveButton > svg {\n width: var(--spacing-3_5, 0.9375rem); /* prototype: 15px */\n height: var(--spacing-3_5, 0.9375rem);\n stroke-width: 2.4;\n}\n\n/* ─── AI action button ───────────────────────────────────────────────────── */\n\n.aiAction {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-1_5, 0.375rem); /* prototype: 6px */\n margin-top: var(--spacing-2, 0.5rem); /* prototype: 9px ≈ spacing-2 */\n font-size: var(--font-size-xs, 0.71875rem); /* prototype: 11.5px */\n font-weight: 700;\n color: var(--primary, #111827);\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n transition: opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.aiAction:hover {\n opacity: var(--opacity-80, 0.8);\n}\n\n.aiAction:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--primary, #111827);\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-sm, 0.25rem);\n}\n\n.aiAction > svg {\n width: var(--spacing-3, 0.8125rem); /* prototype: 13px */\n height: var(--spacing-3, 0.8125rem);\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .root,\n .label,\n .editIcon,\n .saveButton,\n .aiAction {\n transition: none;\n }\n}\n"
2191
+ "content": "/* EditableBlock\n *\n * Canvas brand block — a card tile with an uppercase label header (+ done\n * check), a value body, a hover-revealed edit icon, and an editing state\n * (focus ring, inline input + save button, AI action slot).\n *\n * Visual source: docs/design/brand-workbench/journey (HTML) L274-285\n * (prototype selectors: block, bk, bv, ed, editrow, aibtn)\n *\n * All values flow through CSS custom properties. No hard-coded colors or\n * magic numbers — every value traces to a design token or is documented.\n */\n\n/* ─── Root tile ─────────────────────────────────────────────────────────── */\n\n.root {\n background: var(--surface-card, #ffffff);\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, rgba(0,0,0,0.06));\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--radius-lg, 0.75rem);\n padding: var(--spacing-3_5, 0.875rem); /* prototype: 14px */\n box-shadow: var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05));\n min-height: 6rem; /* prototype: 96px */\n position: relative;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n transition:\n outline-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n box-shadow var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* ─── State: default (hover) ────────────────────────────────────────────── */\n\n.stateDefault:hover {\n outline-color: var(--border-default, #e5e7eb);\n}\n\n/* Reveal edit icon on hover — use CSS opacity so resting state is still\n in the DOM (avoids SSR/hydration race on mount). */\n.stateDefault:hover .editIcon {\n opacity: var(--opacity-80, 0.8);\n}\n\n/* ─── State: done ───────────────────────────────────────────────────────── */\n\n.stateDone:hover {\n outline-color: var(--border-default, #e5e7eb);\n}\n\n.stateDone:hover .editIcon {\n opacity: var(--opacity-80, 0.8);\n}\n\n/* ─── State: editing ────────────────────────────────────────────────────── */\n\n.stateEditing {\n /* Focus-ring shadow — exception per token-rules Rule 2 (focus ring variant) */\n outline-color: var(--primary, #111827);\n box-shadow: 0 0 0 var(--spacing-1, 0.25rem) var(--interactive-primary-soft, color-mix(in srgb, var(--interactive-primary-bg, #111827) 12%, transparent));\n cursor: default;\n}\n\n.stateEditing .label {\n color: var(--primary, #111827);\n}\n\n/* ─── Header row ────────────────────────────────────────────────────────── */\n\n.header {\n display: flex;\n align-items: center;\n gap: var(--spacing-1_5, 0.375rem); /* prototype: 7px ≈ 1.5 × 4px */\n margin-bottom: var(--spacing-2, 0.5rem); /* prototype: 8px */\n}\n\n/* ─── Label ─────────────────────────────────────────────────────────────── */\n\n.label {\n font-size: var(--font-size-2xs, 0.65625rem); /* prototype: 10.5px */\n font-weight: 800;\n letter-spacing: 0.07em;\n text-transform: uppercase;\n color: var(--text-tertiary, #6b7280);\n transition: color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n/* ─── Done check icon ───────────────────────────────────────────────────── */\n\n.doneCheck {\n display: inline-flex;\n align-items: center;\n color: var(--success, #22c55e);\n}\n\n.doneCheck > svg {\n width: var(--spacing-3, 0.75rem); /* prototype: 12px */\n height: var(--spacing-3, 0.75rem);\n stroke-width: 3;\n}\n\n/* ─── Value body ────────────────────────────────────────────────────────── */\n\n.value {\n font-size: var(--font-size-sm, 0.84375rem); /* prototype: 13.5px ≈ sm */\n color: var(--text-primary, #111827);\n line-height: var(--line-height-snug, 1.45);\n flex: 1;\n}\n\n/* ─── Hover-revealed edit icon ──────────────────────────────────────────── */\n\n.editIcon {\n position: absolute;\n top: var(--spacing-2_5, 0.6875rem); /* prototype: 11px */\n right: var(--spacing-2_5, 0.6875rem);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--text-tertiary, #6b7280);\n /* Resting state opacity=0 via CSS (visible on hover via .stateDefault:hover rule).\n Must remain in DOM at rest for accessibility & SSR hydration safety. */\n opacity: 0;\n background: none;\n border: none;\n padding: 0;\n cursor: pointer;\n transition: opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.editIcon:focus-visible {\n opacity: var(--opacity-80, 0.8);\n outline: var(--focus-ring-width, 2px) solid var(--primary, #111827);\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-sm, 0.25rem);\n}\n\n.editIcon > svg {\n width: var(--spacing-3_5, 0.875rem); /* prototype: 14px */\n height: var(--spacing-3_5, 0.875rem);\n}\n\n/* ─── Edit row (input + save button) ────────────────────────────────────── */\n\n.editRow {\n display: flex;\n gap: var(--spacing-1_5, 0.375rem); /* prototype: 7px */\n margin-top: var(--spacing-2_5, 0.625rem); /* prototype: 10px */\n}\n\n/* Inline edit input */\n.editInput {\n flex: 1;\n height: var(--spacing-8, 2rem); /* prototype: 32px */\n border-radius: var(--radius-md, 0.5rem);\n background: var(--surface-subtle, #f9fafb);\n border: var(--stroke-width-thin, 1px) solid\n color-mix(in srgb, var(--primary, #111827) 30%, transparent);\n font-size: var(--font-size-sm, 0.84375rem); /* prototype: 13px */\n font-weight: 600;\n color: var(--text-primary, #111827);\n padding: 0 var(--spacing-2_5, 0.625rem); /* prototype: 0 10px */\n outline: none;\n}\n\n.editInput:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--primary, #111827);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Save button (checkmark) */\n.saveButton {\n width: var(--spacing-8, 2rem); /* prototype: 32px */\n height: var(--spacing-8, 2rem);\n border-radius: var(--radius-md, 0.5rem);\n background: var(--primary, #111827);\n color: var(--primary-text, #ffffff);\n display: grid;\n place-items: center;\n flex: none;\n border: none;\n cursor: pointer;\n transition:\n background-color var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out),\n opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.saveButton:hover {\n background: color-mix(in srgb, var(--primary, #111827) 88%, white);\n}\n\n.saveButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--primary, #111827);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n.saveButton > svg {\n width: var(--spacing-3_5, 0.9375rem); /* prototype: 15px */\n height: var(--spacing-3_5, 0.9375rem);\n stroke-width: 2.4;\n}\n\n/* ─── AI action button ───────────────────────────────────────────────────── */\n\n.aiAction {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-1_5, 0.375rem); /* prototype: 6px */\n margin-top: var(--spacing-2, 0.5rem); /* prototype: 9px ≈ spacing-2 */\n font-size: var(--font-size-xs, 0.71875rem); /* prototype: 11.5px */\n font-weight: 700;\n color: var(--primary, #111827);\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n transition: opacity var(--motion-duration-150, 150ms) var(--motion-easing-default, ease-in-out);\n}\n\n.aiAction:hover {\n opacity: var(--opacity-80, 0.8);\n}\n\n.aiAction:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--primary, #111827);\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-sm, 0.25rem);\n}\n\n.aiAction > svg {\n width: var(--spacing-3, 0.8125rem); /* prototype: 13px */\n height: var(--spacing-3, 0.8125rem);\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .root,\n .label,\n .editIcon,\n .saveButton,\n .aiAction {\n transition: none;\n }\n}\n"
2192
2192
  }
2193
2193
  ]
2194
2194
  },
@@ -2363,7 +2363,7 @@
2363
2363
  {
2364
2364
  "path": "components/ui/specimen-card/specimen-card.module.css",
2365
2365
  "type": "registry:ui",
2366
- "content": "/* Specimen Card\n *\n * A labeled frame that pairs a context label + \"feel\" descriptor with\n * arbitrary live component children — proving how a brand voice/tone\n * renders on real components.\n *\n * Two uses:\n * (a) Tone-by-context grid — card frame with uppercase context token +\n * italic feel descriptor above the live specimen.\n * (b) Speaking block — same frame with multiple specimens stacked and\n * an optional voice-key attribution footer.\n *\n * All values flow through CSS custom properties so themes can tune\n * without forking the component.\n */\n\n.card {\n background: var(--surface-card, #ffffff);\n border: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n border-radius: var(--radius-lg, 0.5rem);\n padding: var(--spacing-3, 0.75rem);\n box-shadow: var(--shadow-sm, 0 1px 2px rgba(0,0,0,0.06));\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Label row — context token + feel descriptor */\n.labelRow {\n display: flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Context label — uppercase small caps, secondary color */\n.contextLabel {\n font-size: 11px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--text-secondary, #6b7280);\n line-height: 1;\n}\n\n/* Feel descriptor — italic, tertiary, pushed to the end */\n.feelText {\n margin-left: auto;\n font-size: 11px;\n color: var(--text-tertiary, #9ca3af);\n font-style: italic;\n line-height: 1;\n}\n\n/* Body — vertical stack of live specimen children */\n.body {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Footer — hairline-separated voice key attribution */\n.footer {\n border-top: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n margin-top: var(--spacing-1, 0.25rem);\n padding-top: var(--spacing-2, 0.5rem);\n font-size: 10.5px;\n color: var(--text-tertiary, #9ca3af);\n display: flex;\n align-items: center;\n gap: var(--spacing-1-5, 0.375rem);\n line-height: 1.4;\n}\n"
2366
+ "content": "/* Specimen Card\n *\n * A labeled frame that pairs a context label + \"feel\" descriptor with\n * arbitrary live component children — proving how a brand voice/tone\n * renders on real components.\n *\n * Two uses:\n * (a) Tone-by-context grid — card frame with uppercase context token +\n * italic feel descriptor above the live specimen.\n * (b) Speaking block — same frame with multiple specimens stacked and\n * an optional voice-key attribution footer.\n *\n * All values flow through CSS custom properties so themes can tune\n * without forking the component.\n */\n\n.card {\n background: var(--surface-card, #ffffff);\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, #e5e7eb);\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--radius-lg, 0.5rem);\n padding: var(--spacing-3, 0.75rem);\n box-shadow: var(--shadow-sm, 0 1px 2px rgba(0,0,0,0.06));\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Label row — context token + feel descriptor */\n.labelRow {\n display: flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Context label — uppercase small caps, secondary color */\n.contextLabel {\n font-size: 11px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--text-secondary, #6b7280);\n line-height: 1;\n}\n\n/* Feel descriptor — italic, tertiary, pushed to the end */\n.feelText {\n margin-left: auto;\n font-size: 11px;\n color: var(--text-tertiary, #9ca3af);\n font-style: italic;\n line-height: 1;\n}\n\n/* Body — vertical stack of live specimen children */\n.body {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n}\n\n/* Footer — hairline-separated voice key attribution */\n.footer {\n border-top: var(--stroke-width-thin, 1px) solid transparent;\n border-image: linear-gradient(var(--hairline, #e5e7eb), var(--hairline, #e5e7eb)) 1 / var(--hairline-width, var(--stroke-width-thin, 1px)) 0 0 0;\n margin-top: var(--spacing-1, 0.25rem);\n padding-top: var(--spacing-2, 0.5rem);\n font-size: 10.5px;\n color: var(--text-tertiary, #9ca3af);\n display: flex;\n align-items: center;\n gap: var(--spacing-1-5, 0.375rem);\n line-height: 1.4;\n}\n"
2367
2367
  }
2368
2368
  ]
2369
2369
  },
@@ -2537,7 +2537,7 @@
2537
2537
  {
2538
2538
  "path": "components/ui/bulk-action-bar/bulk-action-bar.module.css",
2539
2539
  "type": "registry:ui",
2540
- "content": "/* Bulk Action Bar: appears when rows are selected. Sticky by default. */\n.base {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: var(--spacing-4, 1rem);\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n background-color: var(--surface-card, #ffffff);\n border-top: 1px solid var(--border-default, #e5e7eb);\n color: var(--text-primary, #111827);\n box-shadow: var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1));\n border-radius: var(--radius-lg, 0.75rem);\n}\n\n/* Sticky variant — pinned to the viewport bottom. */\n.sticky {\n position: fixed;\n bottom: var(--spacing-4, 1rem);\n left: var(--spacing-4, 1rem);\n right: var(--spacing-4, 1rem);\n /*\n * Toolbar layer. No --z-index-toolbar token exists yet in\n * @loworbitstudio/visor-core — promote to a token once the z-index scale\n * is formalized.\n */\n z-index: 50;\n animation: bulk-action-bar-enter var(--motion-duration-moderate, 300ms)\n var(--motion-easing-standard, ease-out);\n}\n\n/* Inline variant — flows in the document, no fixed positioning. */\n.inline {\n position: relative;\n width: 100%;\n}\n\n/* Flat/embedded variant — no shadow, no radius, border-top hairline only.\n Use inside table cards or panels where the floating card look is wrong. */\n.flat {\n box-shadow: none;\n border-radius: 0;\n}\n\n/* Editorial density — the blessed admin strip: the inline bar sheds the\n floating-card chrome entirely (flat, card-tier fill, hairline below, dense\n padding), the count reads 13px tertiary, and actions are pushed to the right\n edge and read muted. Matches the blessed admin-ui reference builds. */\n:global([data-density=\"editorial\"]) .inline {\n background-color: var(--surface-card, #ffffff);\n box-shadow: none;\n border-radius: 0;\n border-top: none;\n border-bottom: 1px solid var(--hairline, var(--border-default, #e5e7eb));\n padding: var(--spacing-2, 0.5rem) var(--spacing-5, 1.25rem);\n}\n\n:global([data-density=\"editorial\"]) .count {\n font-size: 0.8125rem;\n color: var(--text-tertiary, #6b7280);\n}\n\n:global([data-density=\"editorial\"]) .actions {\n justify-content: flex-end;\n}\n\n/* Action buttons read muted under editorial; a destructive action keeps its\n own inline color (style attribute wins over this). */\n:global([data-density=\"editorial\"]) .actions button {\n color: var(--text-tertiary, #6b7280);\n}\n\n/* Selection count label */\n.count {\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--text-primary, #111827);\n line-height: var(--line-height-tight, 1.25);\n white-space: nowrap;\n flex: 0 0 auto;\n}\n\n/* Actions cluster — horizontal, wraps on narrow rows */\n.actions {\n display: flex;\n flex-direction: row;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--spacing-2, 0.5rem);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* Dismiss button — sits flush on the right */\n.dismiss {\n flex: 0 0 auto;\n margin-left: auto;\n}\n\n.dismiss svg {\n width: var(--spacing-4, 1rem);\n height: var(--spacing-4, 1rem);\n}\n\n/* Entrance animation: slide up + fade in. */\n@keyframes bulk-action-bar-enter {\n from {\n opacity: 0;\n transform: translateY(var(--spacing-4, 1rem));\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Respect prefers-reduced-motion — skip the slide, keep it mounted. */\n@media (prefers-reduced-motion: reduce) {\n .sticky {\n animation: none;\n }\n}\n"
2540
+ "content": "/* Bulk Action Bar: appears when rows are selected. Sticky by default. */\n.base {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: var(--spacing-4, 1rem);\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n background-color: var(--surface-card, #ffffff);\n border-top: 1px solid var(--border-default, #e5e7eb);\n color: var(--text-primary, #111827);\n box-shadow: var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1));\n border-radius: var(--radius-lg, 0.75rem);\n}\n\n/* Sticky variant — pinned to the viewport bottom. */\n.sticky {\n position: fixed;\n bottom: var(--spacing-4, 1rem);\n left: var(--spacing-4, 1rem);\n right: var(--spacing-4, 1rem);\n /*\n * Toolbar layer. No --z-index-toolbar token exists yet in\n * @loworbitstudio/visor-core — promote to a token once the z-index scale\n * is formalized.\n */\n z-index: 50;\n animation: bulk-action-bar-enter var(--motion-duration-moderate, 300ms)\n var(--motion-easing-standard, ease-out);\n}\n\n/* Inline variant — flows in the document, no fixed positioning. */\n.inline {\n position: relative;\n width: 100%;\n}\n\n/* Flat/embedded variant — no shadow, no radius, border-top hairline only.\n Use inside table cards or panels where the floating card look is wrong. */\n.flat {\n box-shadow: none;\n border-radius: 0;\n}\n\n/* Editorial density — the blessed admin strip: the inline bar sheds the\n floating-card chrome entirely (flat, card-tier fill, hairline below, dense\n padding), the count reads 13px tertiary, and actions are pushed to the right\n edge and read muted. Matches the blessed admin-ui reference builds. */\n:global([data-density=\"editorial\"]) .inline {\n background-color: var(--surface-card, #ffffff);\n box-shadow: none;\n border-radius: 0;\n border-top: none;\n border-bottom: 1px solid transparent;\n border-image: linear-gradient(var(--hairline, var(--border-default, #e5e7eb)), var(--hairline, var(--border-default, #e5e7eb))) 1 / 0 0 var(--hairline-width, 1px) 0;\n padding: var(--spacing-2, 0.5rem) var(--spacing-5, 1.25rem);\n}\n\n:global([data-density=\"editorial\"]) .count {\n font-size: 0.8125rem;\n color: var(--text-tertiary, #6b7280);\n}\n\n:global([data-density=\"editorial\"]) .actions {\n justify-content: flex-end;\n}\n\n/* Action buttons read muted under editorial; a destructive action keeps its\n own inline color (style attribute wins over this). */\n:global([data-density=\"editorial\"]) .actions button {\n color: var(--text-tertiary, #6b7280);\n}\n\n/* Selection count label */\n.count {\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n color: var(--text-primary, #111827);\n line-height: var(--line-height-tight, 1.25);\n white-space: nowrap;\n flex: 0 0 auto;\n}\n\n/* Actions cluster — horizontal, wraps on narrow rows */\n.actions {\n display: flex;\n flex-direction: row;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--spacing-2, 0.5rem);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* Dismiss button — sits flush on the right */\n.dismiss {\n flex: 0 0 auto;\n margin-left: auto;\n}\n\n.dismiss svg {\n width: var(--spacing-4, 1rem);\n height: var(--spacing-4, 1rem);\n}\n\n/* Entrance animation: slide up + fade in. */\n@keyframes bulk-action-bar-enter {\n from {\n opacity: 0;\n transform: translateY(var(--spacing-4, 1rem));\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Respect prefers-reduced-motion — skip the slide, keep it mounted. */\n@media (prefers-reduced-motion: reduce) {\n .sticky {\n animation: none;\n }\n}\n"
2541
2541
  }
2542
2542
  ]
2543
2543
  },
@@ -2876,7 +2876,7 @@
2876
2876
  {
2877
2877
  "path": "components/ui/section-nav/section-nav.module.css",
2878
2878
  "type": "registry:ui",
2879
- "content": "/* SectionNav — link/anchor-based section sub-navigation.\n *\n * A horizontal strip of links: leading Phosphor icon + label + optional\n * trailing count pill. The active item gets text-primary, a static 2px\n * primary underline, and a primary-tinted count pill.\n *\n * Distinct from Tabs: items navigate via href (next/link asChild), there are\n * no content panels, and the underline is static (not an animated indicator).\n *\n * Theme-agnostic: all values reference CSS custom property tokens.\n */\n\n/* Root — the nav strip with a hairline baseline the active underline sits on. */\n.root {\n display: flex;\n align-items: stretch;\n gap: var(--spacing-2, 0.5rem);\n border-bottom: var(--stroke-width-thin, 1px) solid\n var(--hairline, var(--border-default, #e5e7eb));\n /* No overflow-x: a scroll container would clip the items' -1px overlap of\n this hairline, shaving the bottom pixel off the active underline. */\n}\n\n/* Item — a single link: icon + label + optional count pill. */\n.item {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n line-height: 1;\n white-space: nowrap;\n color: var(--text-tertiary, #9ca3af);\n text-decoration: none;\n cursor: pointer;\n /* Reserve the 2px underline so resting/active items don't shift vertically. */\n border-bottom: 2px solid transparent;\n margin-bottom: calc(-1 * var(--stroke-width-thin, 1px));\n background: transparent;\n transition: color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease),\n border-color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease);\n}\n\n.item:hover {\n color: var(--text-primary, #111827);\n}\n\n.item:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-sm, 0.25rem);\n}\n\n/* Active — text-primary + 2px primary underline. */\n.itemActive {\n color: var(--text-primary, #111827);\n border-bottom-color: var(--primary, #2563eb);\n}\n\n/* Leading icon — tertiary at rest, recolors with the active item. */\n.icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: var(--text-tertiary, #9ca3af);\n}\n\n.itemActive .icon {\n color: var(--text-primary, #111827);\n}\n\n/* Label text. */\n.label {\n min-width: 0;\n}\n\n/* Trailing count pill. */\n.count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n padding: var(--stroke-width-thin, 1px) calc(var(--spacing-1, 0.25rem) * 1.5);\n font-size: 11px;\n font-weight: var(--font-weight-medium, 500);\n font-variant-numeric: tabular-nums;\n line-height: normal;\n}\n\n/* Neutral (resting) count pill — subtle surface, tertiary text. */\n.countNeutral {\n background-color: var(--surface-subtle, #f3f4f6);\n color: var(--text-tertiary, #9ca3af);\n}\n\n/* Primary-tinted count pill — active item. */\n.countActive {\n background-color: color-mix(in srgb, var(--primary, #2563eb) 22%, transparent);\n color: var(--primary, #2563eb);\n}\n"
2879
+ "content": "/* SectionNav — link/anchor-based section sub-navigation.\n *\n * A horizontal strip of links: leading Phosphor icon + label + optional\n * trailing count pill. The active item gets text-primary, a static 2px\n * primary underline, and a primary-tinted count pill.\n *\n * Distinct from Tabs: items navigate via href (next/link asChild), there are\n * no content panels, and the underline is static (not an animated indicator).\n *\n * Theme-agnostic: all values reference CSS custom property tokens.\n */\n\n/* Root — the nav strip with a hairline baseline the active underline sits on. */\n.root {\n display: flex;\n align-items: stretch;\n gap: var(--spacing-2, 0.5rem);\n border-bottom: var(--stroke-width-thin, 1px) solid transparent;\n border-image: linear-gradient(var(--hairline, var(--border-default, #e5e7eb)), var(--hairline, var(--border-default, #e5e7eb))) 1 / 0 0 var(--hairline-width, var(--stroke-width-thin, 1px)) 0;\n /* No overflow-x: a scroll container would clip the items' -1px overlap of\n this hairline, shaving the bottom pixel off the active underline. */\n}\n\n/* Item — a single link: icon + label + optional count pill. */\n.item {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n font-size: var(--font-size-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n line-height: 1;\n white-space: nowrap;\n color: var(--text-tertiary, #9ca3af);\n text-decoration: none;\n cursor: pointer;\n /* Reserve the 2px underline so resting/active items don't shift vertically. */\n border-bottom: 2px solid transparent;\n margin-bottom: calc(-1 * var(--stroke-width-thin, 1px));\n background: transparent;\n transition: color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease),\n border-color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease);\n}\n\n.item:hover {\n color: var(--text-primary, #111827);\n}\n\n.item:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-sm, 0.25rem);\n}\n\n/* Active — text-primary + 2px primary underline. */\n.itemActive {\n color: var(--text-primary, #111827);\n border-bottom-color: var(--primary, #2563eb);\n}\n\n/* Leading icon — tertiary at rest, recolors with the active item. */\n.icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: var(--text-tertiary, #9ca3af);\n}\n\n.itemActive .icon {\n color: var(--text-primary, #111827);\n}\n\n/* Label text. */\n.label {\n min-width: 0;\n}\n\n/* Trailing count pill. */\n.count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n padding: var(--stroke-width-thin, 1px) calc(var(--spacing-1, 0.25rem) * 1.5);\n font-size: 11px;\n font-weight: var(--font-weight-medium, 500);\n font-variant-numeric: tabular-nums;\n line-height: normal;\n}\n\n/* Neutral (resting) count pill — subtle surface, tertiary text. */\n.countNeutral {\n background-color: var(--surface-subtle, #f3f4f6);\n color: var(--text-tertiary, #9ca3af);\n}\n\n/* Primary-tinted count pill — active item. */\n.countActive {\n background-color: color-mix(in srgb, var(--primary, #2563eb) 22%, transparent);\n color: var(--primary, #2563eb);\n}\n"
2880
2880
  }
2881
2881
  ]
2882
2882
  },
@@ -2901,7 +2901,7 @@
2901
2901
  {
2902
2902
  "path": "components/ui/doc-nav/doc-nav.module.css",
2903
2903
  "type": "registry:ui",
2904
- "content": "/* DocNav — manifest-driven, grouped/collapsible doc navigation.\n *\n * Renders peer, collapsible groups from a manifest slice: a pinned Shared set,\n * accordion product groups (one open at a time), and an Appendix bucket for\n * ad-hoc docs. The group row wraps (flex-wrap) and is NEVER an overflow-x\n * scroll strip — the run-off-the-edge defect this component exists to kill.\n *\n * Theme-agnostic: every value references a CSS custom property token.\n */\n\n/* Root — the wrapping row of peer groups. */\n.root {\n /* Accent-pop indirection (B2): the active pill / caret / hub / pinned frame\n * all route through this single token so a consumer (DocFrame) can retint the\n * whole nav in one place. Defaults to the theme's vivid `--accent` — not\n * `--primary`, which reads dull on muted-primary themes such as Strata. */\n --doc-nav-accent: var(--accent, #2563eb);\n /* Group cluster radius — a controlled, fixed radius the doc-nav owns, NOT the\n * theme `--radius-*` scale, which decorative themes inflate far past what a\n * dense nav cluster wants (for example Strata inflates `--radius-xl` to 32px).\n * The approved design pins ~12px per theme for exactly this reason; any single\n * theme radius token renders ~6px on some themes and ~32px on others.\n * Overridable by a consumer/theme via this variable. */\n --doc-nav-group-radius: 0.75rem;\n /* Resting pill fill — a recessed well, darker than the group's `--surface-card`\n * in BOTH modes. The surface ramp inverts light↔dark (page is darkest in dark\n * but lightest in light), so a single surface token can't stay \"below\" the card;\n * mixing the card toward neutral-950 darkens it directionally in either mode.\n * Overridable by a consumer/theme. */\n --doc-nav-pill-bg: color-mix(\n in srgb,\n var(--surface-card, #111827),\n var(--color-neutral-950, #030712) 20%\n );\n display: flex;\n flex-wrap: wrap;\n align-items: flex-start;\n gap: var(--spacing-2, 0.5rem);\n /* Deliberately no overflow-x: groups wrap down, never scroll sideways. */\n}\n\n/* Group — a bordered, surface-backed cluster: header + pill row. */\n.group {\n display: inline-flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-2, 0.5rem);\n border: var(--stroke-width-thin, 1px) solid\n var(--hairline, var(--border-default, #e5e7eb));\n border-radius: var(--doc-nav-group-radius, var(--radius-md, 0.375rem));\n background: var(--surface-card, #ffffff);\n}\n\n/* Pinned — the Shared group stays open; a subtle frame keyed to the group hue.\n * Routed through overridable tokens (P5) so borderless themes (Animal / ENTR)\n * can null the frame — set `--doc-nav-pin-border: transparent` and\n * `--doc-nav-pin-bg: var(--surface-card)`. Default derives from the group's\n * shared hue, not `--primary` (which read as an unwanted border on those\n * borderless themes). */\n.groupPinned {\n border-color: var(\n --doc-nav-pin-border,\n color-mix(in srgb, var(--doc-nav-group-accent, var(--info, #0ea5e9)) 32%, transparent)\n );\n background: var(\n --doc-nav-pin-bg,\n color-mix(in srgb, var(--doc-nav-group-accent, var(--info, #0ea5e9)) 6%, var(--surface-card, #ffffff))\n );\n}\n\n/* Collapsed — a group reduces to a single pill-shaped chip. */\n.groupCollapsed {\n padding: 0;\n border-color: transparent;\n background: transparent;\n}\n\n/* Header — the group label row. As a chip when collapsed, plain when open. */\n.head {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.14em;\n text-transform: uppercase;\n /* Collapse the label's line box to the glyph height so the all-caps text\n * optically centers against the dot/caret (matching `.pill`). Without this the\n * caps ride high — the empty descender space pushes the line-box center below\n * the visual center of the caps. */\n line-height: 1;\n color: var(--text-secondary, #4b5563);\n}\n\n/* Button variant — collapsible group heads are clickable. */\n.headButton {\n cursor: pointer;\n border: 0;\n background: transparent;\n transition: color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease);\n}\n\n.headButton:hover {\n color: var(--text-primary, #111827);\n}\n\n.headButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid\n var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-sm, 0.25rem);\n}\n\n/* Collapsed group head — reads as a resting chip. */\n.groupCollapsed .head {\n border: var(--stroke-width-thin, 1px) solid\n var(--hairline, var(--border-default, #e5e7eb));\n border-radius: var(--radius-full, 9999px);\n /* A collapsed chip stands alone in the nav row, so it carries more vertical\n * padding than the tight open-group label (the design gives it ~7px 12px). */\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n /* The chip reads as the group's card surface (matching the design's chip-bg),\n * not a lighter subtle surface — `--surface-subtle` resolves as a mid-gray in\n * dark themes. */\n background: var(--surface-card, #111827);\n}\n\n/* Caret — accent-popped fill glyph (P6). */\n.caret {\n flex-shrink: 0;\n color: var(--doc-nav-accent, var(--accent, #2563eb));\n}\n\n/* Scope dot — a per-group grouping cue, color-coded with a soft glow (P1).\n * `color` carries the group hue so `currentColor` drives both the fill and the\n * glow; a consumer overrides the whole group via `--doc-nav-group-accent`.\n * Default (product groups) is the vivid accent; pinned/appendix retint below. */\n.scopeDot {\n width: var(--spacing-2, 0.5rem);\n height: var(--spacing-2, 0.5rem);\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n color: var(--doc-nav-group-accent, var(--accent, #2563eb));\n background: currentColor;\n box-shadow: 0 0 var(--spacing-2, 0.5rem)\n color-mix(in srgb, currentColor 60%, transparent);\n}\n\n/* Shared (pinned) group reads in the info hue. */\n.groupPinned .scopeDot {\n color: var(--doc-nav-group-accent, var(--info, #0ea5e9));\n}\n\n/* Appendix reads muted and un-glowed — an ad-hoc bucket, not a product. */\n.group[data-role=\"appendix\"] .scopeDot {\n color: var(--doc-nav-group-accent, var(--text-tertiary, #9ca3af));\n box-shadow: none;\n}\n\n/* Group label text. */\n.groupLabel {\n min-width: 0;\n}\n\n/* Trailing count badge — a hairline mono pill on a collapsed group (N2). */\n.count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n min-width: var(--spacing-4, 1rem);\n padding: 0 calc(var(--spacing-1, 0.25rem) * 1.5);\n border: var(--stroke-width-thin, 1px) solid\n var(--hairline, var(--border-default, #e5e7eb));\n border-radius: var(--radius-full, 9999px);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n /* ~9px — a quarter down from the xs step, matching the design's tiny badge. */\n font-size: calc(var(--font-size-xs, 0.75rem) * 0.75);\n font-variant-numeric: tabular-nums;\n color: var(--text-tertiary, #9ca3af);\n}\n\n/* Pill row — wraps within the group. */\n.pills {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-1, 0.25rem);\n}\n\n/* Pill — a single doc link. Mono · UPPERCASE · letter-spaced (B1). */\n.pill {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: calc(var(--spacing-1, 0.25rem) * 1.5) var(--spacing-3, 0.75rem);\n border: var(--stroke-width-thin, 1px) solid transparent;\n border-radius: var(--radius-full, 9999px);\n background: var(--doc-nav-pill-bg, var(--surface-card, #111827));\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.05em;\n text-transform: uppercase;\n line-height: 1;\n white-space: nowrap;\n color: var(--text-secondary, #4b5563);\n text-decoration: none;\n cursor: pointer;\n transition: color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease),\n border-color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease),\n background-color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease);\n}\n\n.pill:hover {\n color: var(--text-primary, #111827);\n border-color: var(--hairline, var(--border-default, #e5e7eb));\n}\n\n.pill:focus-visible {\n outline: var(--focus-ring-width, 2px) solid\n var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Shared-group pills carry the group's tint at rest (P4) — hover/active win. */\n.groupPinned .pill:not(.pillActive) {\n color: var(--doc-nav-group-accent, var(--info, #0ea5e9));\n}\n\n.groupPinned .pill:not(.pillActive):hover {\n color: var(--text-primary, #111827);\n}\n\n/* Active — the current doc, resolved from currentPath. Contrast-safe (VI-612):\n * the label is `--text-primary` (always legible), NOT raw `--doc-nav-accent`,\n * which is unreadable when a theme's accent is white/near-white (Blacklight, say)\n * on a light card. The active pill reads as active via a distinct opaque surface\n * plus an accent ring blended with a hairline so it never fully vanishes. */\n.pillActive {\n color: var(--text-primary, #111827);\n border-color: color-mix(\n in srgb,\n var(--doc-nav-accent, var(--accent, #2563eb)) 55%,\n var(--hairline, var(--border-default, #e5e7eb))\n );\n background: var(\n --surface-selected,\n color-mix(\n in srgb,\n var(--surface-card, #111827),\n var(--doc-nav-accent, var(--accent, #2563eb)) 14%\n )\n );\n}\n\n/* Hub — the group's overview entry gets a leading accent dot with a glow (N1).\n * `color` carries the accent so `currentColor` drives fill + glow together. */\n.pillHub::before {\n content: \"\";\n width: calc(var(--spacing-1, 0.25rem) * 1.25);\n height: calc(var(--spacing-1, 0.25rem) * 1.25);\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n color: var(--doc-nav-accent, var(--accent, #2563eb));\n background: currentColor;\n box-shadow: 0 0 var(--spacing-2, 0.5rem)\n color-mix(in srgb, currentColor 60%, transparent);\n}\n\n/* Leading order number — mono, a step under the pill (B1). */\n.pillNum {\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: calc(var(--font-size-xs, 0.75rem) * 0.8);\n font-variant-numeric: tabular-nums;\n color: var(--text-tertiary, #9ca3af);\n}\n\n.pillActive .pillNum {\n color: var(--doc-nav-accent, var(--accent, #2563eb));\n}\n\n/* Label text. */\n.pillLabel {\n min-width: 0;\n}\n\n/* External-link badge. */\n.pillExternal {\n flex-shrink: 0;\n color: var(--text-tertiary, #9ca3af);\n}\n\n/* Visually-hidden text for assistive technology. */\n.srOnly {\n position: absolute;\n width: var(--stroke-width-thin, 1px);\n height: var(--stroke-width-thin, 1px);\n padding: 0;\n margin: calc(-1 * var(--stroke-width-thin, 1px));\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n"
2904
+ "content": "/* DocNav — manifest-driven, grouped/collapsible doc navigation.\n *\n * Renders peer, collapsible groups from a manifest slice: a pinned Shared set,\n * accordion product groups (one open at a time), and an Appendix bucket for\n * ad-hoc docs. The group row wraps (flex-wrap) and is NEVER an overflow-x\n * scroll strip — the run-off-the-edge defect this component exists to kill.\n *\n * Theme-agnostic: every value references a CSS custom property token.\n */\n\n/* Root — the wrapping row of peer groups. */\n.root {\n /* Accent-pop indirection (B2): the active pill / caret / hub / pinned frame\n * all route through this single token so a consumer (DocFrame) can retint the\n * whole nav in one place. Defaults to the theme's vivid `--accent` — not\n * `--primary`, which reads dull on muted-primary themes such as Strata. */\n --doc-nav-accent: var(--accent, #2563eb);\n /* Group cluster radius — a controlled, fixed radius the doc-nav owns, NOT the\n * theme `--radius-*` scale, which decorative themes inflate far past what a\n * dense nav cluster wants (for example Strata inflates `--radius-xl` to 32px).\n * The approved design pins ~12px per theme for exactly this reason; any single\n * theme radius token renders ~6px on some themes and ~32px on others.\n * Overridable by a consumer/theme via this variable. */\n --doc-nav-group-radius: 0.75rem;\n /* Resting pill fill — a recessed well, darker than the group's `--surface-card`\n * in BOTH modes. The surface ramp inverts light↔dark (page is darkest in dark\n * but lightest in light), so a single surface token can't stay \"below\" the card;\n * mixing the card toward neutral-950 darkens it directionally in either mode.\n * Overridable by a consumer/theme. */\n --doc-nav-pill-bg: color-mix(\n in srgb,\n var(--surface-card, #111827),\n var(--color-neutral-950, #030712) 20%\n );\n display: flex;\n flex-wrap: wrap;\n align-items: flex-start;\n gap: var(--spacing-2, 0.5rem);\n /* Deliberately no overflow-x: groups wrap down, never scroll sideways. */\n}\n\n/* Group — a bordered, surface-backed cluster: header + pill row. */\n.group {\n display: inline-flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-2, 0.5rem);\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, var(--border-default, #e5e7eb));\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--doc-nav-group-radius, var(--radius-md, 0.375rem));\n background: var(--surface-card, #ffffff);\n}\n\n/* Pinned — the Shared group stays open; a subtle frame keyed to the group hue.\n * Routed through overridable tokens (P5) so borderless themes (Animal / ENTR)\n * can null the frame — set `--doc-nav-pin-border: transparent` and\n * `--doc-nav-pin-bg: var(--surface-card)`. Default derives from the group's\n * shared hue, not `--primary` (which read as an unwanted border on those\n * borderless themes). */\n.groupPinned {\n outline-color: var(\n --doc-nav-pin-border,\n color-mix(in srgb, var(--doc-nav-group-accent, var(--info, #0ea5e9)) 32%, transparent)\n );\n background: var(\n --doc-nav-pin-bg,\n color-mix(in srgb, var(--doc-nav-group-accent, var(--info, #0ea5e9)) 6%, var(--surface-card, #ffffff))\n );\n}\n\n/* Collapsed — a group reduces to a single pill-shaped chip. */\n.groupCollapsed {\n padding: 0;\n outline-color: transparent;\n background: transparent;\n}\n\n/* Header — the group label row. As a chip when collapsed, plain when open. */\n.head {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.14em;\n text-transform: uppercase;\n /* Collapse the label's line box to the glyph height so the all-caps text\n * optically centers against the dot/caret (matching `.pill`). Without this the\n * caps ride high — the empty descender space pushes the line-box center below\n * the visual center of the caps. */\n line-height: 1;\n color: var(--text-secondary, #4b5563);\n}\n\n/* Button variant — collapsible group heads are clickable. */\n.headButton {\n cursor: pointer;\n border: 0;\n background: transparent;\n transition: color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease);\n}\n\n.headButton:hover {\n color: var(--text-primary, #111827);\n}\n\n.headButton:focus-visible {\n outline: var(--focus-ring-width, 2px) solid\n var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-sm, 0.25rem);\n}\n\n/* Collapsed group head — reads as a resting chip. */\n.groupCollapsed .head {\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, var(--border-default, #e5e7eb));\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--radius-full, 9999px);\n /* A collapsed chip stands alone in the nav row, so it carries more vertical\n * padding than the tight open-group label (the design gives it ~7px 12px). */\n padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);\n /* The chip reads as the group's card surface (matching the design's chip-bg),\n * not a lighter subtle surface — `--surface-subtle` resolves as a mid-gray in\n * dark themes. */\n background: var(--surface-card, #111827);\n}\n\n/* Caret — accent-popped fill glyph (P6). */\n.caret {\n flex-shrink: 0;\n color: var(--doc-nav-accent, var(--accent, #2563eb));\n}\n\n/* Scope dot — a per-group grouping cue, color-coded with a soft glow (P1).\n * `color` carries the group hue so `currentColor` drives both the fill and the\n * glow; a consumer overrides the whole group via `--doc-nav-group-accent`.\n * Default (product groups) is the vivid accent; pinned/appendix retint below. */\n.scopeDot {\n width: var(--spacing-2, 0.5rem);\n height: var(--spacing-2, 0.5rem);\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n color: var(--doc-nav-group-accent, var(--accent, #2563eb));\n background: currentColor;\n box-shadow: 0 0 var(--spacing-2, 0.5rem)\n color-mix(in srgb, currentColor 60%, transparent);\n}\n\n/* Shared (pinned) group reads in the info hue. */\n.groupPinned .scopeDot {\n color: var(--doc-nav-group-accent, var(--info, #0ea5e9));\n}\n\n/* Appendix reads muted and un-glowed — an ad-hoc bucket, not a product. */\n.group[data-role=\"appendix\"] .scopeDot {\n color: var(--doc-nav-group-accent, var(--text-tertiary, #9ca3af));\n box-shadow: none;\n}\n\n/* Group label text. */\n.groupLabel {\n min-width: 0;\n}\n\n/* Trailing count badge — a hairline mono pill on a collapsed group (N2). */\n.count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n min-width: var(--spacing-4, 1rem);\n padding: 0 calc(var(--spacing-1, 0.25rem) * 1.5);\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, var(--border-default, #e5e7eb));\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--radius-full, 9999px);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n /* ~9px — a quarter down from the xs step, matching the design's tiny badge. */\n font-size: calc(var(--font-size-xs, 0.75rem) * 0.75);\n font-variant-numeric: tabular-nums;\n color: var(--text-tertiary, #9ca3af);\n}\n\n/* Pill row — wraps within the group. */\n.pills {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-1, 0.25rem);\n}\n\n/* Pill — a single doc link. Mono · UPPERCASE · letter-spaced (B1). */\n.pill {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: calc(var(--spacing-1, 0.25rem) * 1.5) var(--spacing-3, 0.75rem);\n border: var(--stroke-width-thin, 1px) solid transparent;\n border-radius: var(--radius-full, 9999px);\n background: var(--doc-nav-pill-bg, var(--surface-card, #111827));\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.05em;\n text-transform: uppercase;\n line-height: 1;\n white-space: nowrap;\n color: var(--text-secondary, #4b5563);\n text-decoration: none;\n cursor: pointer;\n transition: color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease),\n border-color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease),\n background-color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease);\n}\n\n.pill:hover {\n color: var(--text-primary, #111827);\n border-color: var(--hairline, var(--border-default, #e5e7eb));\n}\n\n.pill:focus-visible {\n outline: var(--focus-ring-width, 2px) solid\n var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Shared-group pills carry the group's tint at rest (P4) — hover/active win. */\n.groupPinned .pill:not(.pillActive) {\n color: var(--doc-nav-group-accent, var(--info, #0ea5e9));\n}\n\n.groupPinned .pill:not(.pillActive):hover {\n color: var(--text-primary, #111827);\n}\n\n/* Active — the current doc, resolved from currentPath. Contrast-safe (VI-612):\n * the label is `--text-primary` (always legible), NOT raw `--doc-nav-accent`,\n * which is unreadable when a theme's accent is white/near-white (Blacklight, say)\n * on a light card. The active pill reads as active via a distinct opaque surface\n * plus an accent ring blended with a hairline so it never fully vanishes. */\n.pillActive {\n color: var(--text-primary, #111827);\n border-color: color-mix(\n in srgb,\n var(--doc-nav-accent, var(--accent, #2563eb)) 55%,\n var(--hairline, var(--border-default, #e5e7eb))\n );\n background: var(\n --surface-selected,\n color-mix(\n in srgb,\n var(--surface-card, #111827),\n var(--doc-nav-accent, var(--accent, #2563eb)) 14%\n )\n );\n}\n\n/* Hub — the group's overview entry gets a leading accent dot with a glow (N1).\n * `color` carries the accent so `currentColor` drives fill + glow together. */\n.pillHub::before {\n content: \"\";\n width: calc(var(--spacing-1, 0.25rem) * 1.25);\n height: calc(var(--spacing-1, 0.25rem) * 1.25);\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n color: var(--doc-nav-accent, var(--accent, #2563eb));\n background: currentColor;\n box-shadow: 0 0 var(--spacing-2, 0.5rem)\n color-mix(in srgb, currentColor 60%, transparent);\n}\n\n/* Leading order number — mono, a step under the pill (B1). */\n.pillNum {\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: calc(var(--font-size-xs, 0.75rem) * 0.8);\n font-variant-numeric: tabular-nums;\n color: var(--text-tertiary, #9ca3af);\n}\n\n.pillActive .pillNum {\n color: var(--doc-nav-accent, var(--accent, #2563eb));\n}\n\n/* Label text. */\n.pillLabel {\n min-width: 0;\n}\n\n/* External-link badge. */\n.pillExternal {\n flex-shrink: 0;\n color: var(--text-tertiary, #9ca3af);\n}\n\n/* Visually-hidden text for assistive technology. */\n.srOnly {\n position: absolute;\n width: var(--stroke-width-thin, 1px);\n height: var(--stroke-width-thin, 1px);\n padding: 0;\n margin: calc(-1 * var(--stroke-width-thin, 1px));\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n"
2905
2905
  }
2906
2906
  ]
2907
2907
  },
@@ -2927,7 +2927,7 @@
2927
2927
  {
2928
2928
  "path": "components/ui/doc-frame/doc-frame.module.css",
2929
2929
  "type": "registry:ui",
2930
- "content": "/* DocFrame — the themed doc-page shell that wraps DocNav.\n *\n * A sticky header (a flexible brand/logo slot + an OVERVIEW home pill + a\n * right-aligned meta slot, then the DocNav slot) above a content wrapper (the\n * doc as children). The header sticks to the page scroll so the brand + nav\n * never leave view. Theme-agnostic: every value references a CSS custom property\n * token, so the shell adopts the active project theme.\n *\n * No ancestor sets overflow: hidden/auto — that would create a scroll container\n * and break the viewport-anchored sticky header.\n */\n\n/* Shell root — a centered, max-width column. */\n.frame {\n /* A tight, controlled glyph radius the frame owns — NOT the theme `--radius-*`\n * scale, which decorative themes inflate far past what a 24px brand chip wants\n * (Strata inflates `--radius-md` to 10px, `--radius-xl` to 32px). Overridable\n * by a consumer/theme via this variable. */\n --doc-frame-glyph-radius: 0.375rem;\n display: flex;\n flex-direction: column;\n min-height: 100%;\n max-width: var(--doc-frame-max-width, 75rem);\n margin-inline: auto;\n background: var(--surface-page, #ffffff);\n color: var(--text-primary, #111827);\n}\n\n/* Sticky chrome — the header row + nav row. The page surface (opaque, so\n * scrolled content never bleeds through) sits below the card-surfaced nav\n * groups + chips, which read lighter on top. Sticks to the nearest scroll\n * ancestor (the page/viewport). */\n.chrome {\n position: sticky;\n top: 0;\n z-index: 10;\n display: flex;\n flex-direction: column;\n background: var(--surface-page, #ffffff);\n border-bottom: var(--stroke-width-thin, 1px) solid\n var(--hairline, var(--border-default, #e5e7eb));\n}\n\n/* Header row — brand/logo slot, then the OVERVIEW home pill, a flexible spacer,\n * and the right-aligned meta slot. A hairline rule separates it from the nav. */\n.headerRow {\n display: flex;\n align-items: center;\n gap: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n flex-wrap: wrap;\n padding: var(--spacing-3, 0.75rem) var(--spacing-5, 1.25rem);\n border-bottom: var(--stroke-width-thin, 1px) solid\n var(--hairline, var(--border-default, #e5e7eb));\n}\n\n.brandSlot {\n display: inline-flex;\n align-items: center;\n min-width: 0;\n}\n\n.spacer {\n flex: 1 1 auto;\n}\n\n/* Nav row — holds the DocNav slot. */\n.navRow {\n display: flex;\n min-width: 0;\n padding: var(--spacing-3, 0.75rem) var(--spacing-5, 1.25rem);\n}\n\n/* Content wrapper — the doc, as children. */\n.content {\n flex: 1 1 auto;\n padding: var(--spacing-6, 1.5rem) var(--spacing-5, 1.25rem)\n var(--spacing-8, 2rem);\n}\n\n/* ─── brand slot ─────────────────────────────────────────────────────────── */\n\n/* Wraps the theme-logo layer + the text wordmark. */\n.brand {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n min-width: 0;\n}\n\n/* Text wordmark — the resting/fallback brand (a leading glyph chip + the name).\n * Mode-adaptive via --text-primary. Set in the theme's display face so a text\n * wordmark carries the theme's brand character (say, Animal's condensed\n * \"Sequel 100 Black 95\" rather than the body \"Ratio\"); degrades to the heading\n * face, then the ambient sans, on themes that ship no --font-display — matching\n * the display-font convention in stat-card / page-header / section-intro. */\n.wordmark {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n font-family: var(--font-display, var(--font-family-heading, inherit));\n font-size: var(--font-size-lg, 1.125rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: -0.01em;\n line-height: 1.1;\n color: var(--text-primary, #111827);\n}\n\n/* Brand glyph — a single-letter mark in the theme's primary brand color, its\n * guaranteed-contrasting on-primary text. A controlled small radius (see\n * `--doc-frame-glyph-radius`), not the ballooning theme radius scale. */\n.glyph {\n display: grid;\n place-items: center;\n flex-shrink: 0;\n width: var(--spacing-6, 1.5rem);\n height: var(--spacing-6, 1.5rem);\n border-radius: var(--doc-frame-glyph-radius, 0.375rem);\n background: var(--primary, #111827);\n color: var(--primary-text, #ffffff);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-bold, 700);\n line-height: 1;\n}\n\n/* Theme-logo layer — the active theme's brand logo SVG (mode-aware via the\n * --brand-logo custom property). Hidden until the client effect confirms the\n * theme ships one AND it loads, so the resting state is always the visible\n * wordmark and a failed load never leaves an empty slot. */\n.themeLogo {\n display: none;\n height: var(--doc-frame-logo-height, var(--spacing-5, 1.25rem));\n /* An explicit min-width keeps a masked/tinted mark from collapsing to a\n * zero-width box on themes that ship no --brand-logo-aspect-ratio. */\n min-width: var(--spacing-8, 2rem);\n aspect-ratio: var(--brand-logo-aspect-ratio, 3 / 1);\n background-image: var(--brand-logo);\n background-repeat: no-repeat;\n background-position: left center;\n background-size: contain;\n}\n\n/* When the theme provides --brand-logo, show the image and drop the wordmark. */\n.brand[data-theme-logo] .themeLogo {\n display: inline-block;\n}\n\n.brand[data-theme-logo] .wordmark {\n display: none;\n}\n\n/* ─── header chrome — home pill + meta ───────────────────────────────────── */\n\n/* OVERVIEW / home pill — a bordered mono chip with a leading compass glyph. */\n.home {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n flex-shrink: 0;\n /* ~6px 12px — a compact chip that clears the mono caps. */\n padding: calc(var(--spacing-1, 0.25rem) * 1.5) var(--spacing-3, 0.75rem);\n border: var(--stroke-width-thin, 1px) solid\n var(--hairline, var(--border-default, #e5e7eb));\n border-radius: var(--radius-full, 9999px);\n background: var(--surface-card, #ffffff);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.06em;\n /* Collapse the line box to the glyph height so the all-caps label optically\n * centers against the compass icon. */\n line-height: 1;\n text-transform: uppercase;\n color: var(--text-tertiary, #9ca3af);\n text-decoration: none;\n white-space: nowrap;\n transition: color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease),\n border-color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease);\n}\n\n.home:hover {\n color: var(--text-primary, #111827);\n border-color: var(--hairline-strong, var(--border-default, #d1d5db));\n}\n\n.home:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Compass glyph — a hair larger than the caps (the design's 13px vs 11px);\n * inherits the pill color via currentColor. */\n.homeIcon {\n flex-shrink: 0;\n width: var(--font-size-sm, 0.875rem);\n height: var(--font-size-sm, 0.875rem);\n}\n\n/* Meta slot — a right-aligned breadcrumb/status line, mono UPPERCASE, tertiary. */\n.meta {\n flex-shrink: 0;\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.12em;\n line-height: 1;\n text-transform: uppercase;\n color: var(--text-tertiary, #9ca3af);\n white-space: nowrap;\n}\n\n/* ─── mobile ─────────────────────────────────────────────────────────────── */\n\n@media (max-width: 640px) {\n .headerRow {\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n }\n\n .navRow {\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n }\n\n .content {\n padding: var(--spacing-5, 1.25rem) var(--spacing-4, 1rem)\n var(--spacing-6, 1.5rem);\n }\n}\n"
2930
+ "content": "/* DocFrame — the themed doc-page shell that wraps DocNav.\n *\n * A sticky header (a flexible brand/logo slot + an OVERVIEW home pill + a\n * right-aligned meta slot, then the DocNav slot) above a content wrapper (the\n * doc as children). The header sticks to the page scroll so the brand + nav\n * never leave view. Theme-agnostic: every value references a CSS custom property\n * token, so the shell adopts the active project theme.\n *\n * No ancestor sets overflow: hidden/auto — that would create a scroll container\n * and break the viewport-anchored sticky header.\n */\n\n/* Shell root — a centered, max-width column. */\n.frame {\n /* A tight, controlled glyph radius the frame owns — NOT the theme `--radius-*`\n * scale, which decorative themes inflate far past what a 24px brand chip wants\n * (Strata inflates `--radius-md` to 10px, `--radius-xl` to 32px). Overridable\n * by a consumer/theme via this variable. */\n --doc-frame-glyph-radius: 0.375rem;\n display: flex;\n flex-direction: column;\n min-height: 100%;\n max-width: var(--doc-frame-max-width, 75rem);\n margin-inline: auto;\n background: var(--surface-page, #ffffff);\n color: var(--text-primary, #111827);\n}\n\n/* Sticky chrome — the header row + nav row. The page surface (opaque, so\n * scrolled content never bleeds through) sits below the card-surfaced nav\n * groups + chips, which read lighter on top. Sticks to the nearest scroll\n * ancestor (the page/viewport). */\n.chrome {\n position: sticky;\n top: 0;\n z-index: 10;\n display: flex;\n flex-direction: column;\n background: var(--surface-page, #ffffff);\n border-bottom: var(--stroke-width-thin, 1px) solid transparent;\n border-image: linear-gradient(var(--hairline, var(--border-default, #e5e7eb)), var(--hairline, var(--border-default, #e5e7eb))) 1 / 0 0 var(--hairline-width, var(--stroke-width-thin, 1px)) 0;\n}\n\n/* Header row — brand/logo slot, then the OVERVIEW home pill, a flexible spacer,\n * and the right-aligned meta slot. A hairline rule separates it from the nav. */\n.headerRow {\n display: flex;\n align-items: center;\n gap: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n flex-wrap: wrap;\n padding: var(--spacing-3, 0.75rem) var(--spacing-5, 1.25rem);\n border-bottom: var(--stroke-width-thin, 1px) solid transparent;\n border-image: linear-gradient(var(--hairline, var(--border-default, #e5e7eb)), var(--hairline, var(--border-default, #e5e7eb))) 1 / 0 0 var(--hairline-width, var(--stroke-width-thin, 1px)) 0;\n}\n\n.brandSlot {\n display: inline-flex;\n align-items: center;\n min-width: 0;\n}\n\n.spacer {\n flex: 1 1 auto;\n}\n\n/* Nav row — holds the DocNav slot. */\n.navRow {\n display: flex;\n min-width: 0;\n padding: var(--spacing-3, 0.75rem) var(--spacing-5, 1.25rem);\n}\n\n/* Content wrapper — the doc, as children. */\n.content {\n flex: 1 1 auto;\n padding: var(--spacing-6, 1.5rem) var(--spacing-5, 1.25rem)\n var(--spacing-8, 2rem);\n}\n\n/* ─── brand slot ─────────────────────────────────────────────────────────── */\n\n/* Wraps the theme-logo layer + the text wordmark. */\n.brand {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n min-width: 0;\n}\n\n/* Text wordmark — the resting/fallback brand (a leading glyph chip + the name).\n * Mode-adaptive via --text-primary. Set in the theme's display face so a text\n * wordmark carries the theme's brand character (say, Animal's condensed\n * \"Sequel 100 Black 95\" rather than the body \"Ratio\"); degrades to the heading\n * face, then the ambient sans, on themes that ship no --font-display — matching\n * the display-font convention in stat-card / page-header / section-intro. */\n.wordmark {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n font-family: var(--font-display, var(--font-family-heading, inherit));\n font-size: var(--font-size-lg, 1.125rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: -0.01em;\n line-height: 1.1;\n color: var(--text-primary, #111827);\n}\n\n/* Brand glyph — a single-letter mark in the theme's primary brand color, its\n * guaranteed-contrasting on-primary text. A controlled small radius (see\n * `--doc-frame-glyph-radius`), not the ballooning theme radius scale. */\n.glyph {\n display: grid;\n place-items: center;\n flex-shrink: 0;\n width: var(--spacing-6, 1.5rem);\n height: var(--spacing-6, 1.5rem);\n border-radius: var(--doc-frame-glyph-radius, 0.375rem);\n background: var(--primary, #111827);\n color: var(--primary-text, #ffffff);\n font-size: var(--font-size-xs, 0.75rem);\n font-weight: var(--font-weight-bold, 700);\n line-height: 1;\n}\n\n/* Theme-logo layer — the active theme's brand logo SVG (mode-aware via the\n * --brand-logo custom property). Hidden until the client effect confirms the\n * theme ships one AND it loads, so the resting state is always the visible\n * wordmark and a failed load never leaves an empty slot. */\n.themeLogo {\n display: none;\n height: var(--doc-frame-logo-height, var(--spacing-5, 1.25rem));\n /* An explicit min-width keeps a masked/tinted mark from collapsing to a\n * zero-width box on themes that ship no --brand-logo-aspect-ratio. */\n min-width: var(--spacing-8, 2rem);\n aspect-ratio: var(--brand-logo-aspect-ratio, 3 / 1);\n background-image: var(--brand-logo);\n background-repeat: no-repeat;\n background-position: left center;\n background-size: contain;\n}\n\n/* When the theme provides --brand-logo, show the image and drop the wordmark. */\n.brand[data-theme-logo] .themeLogo {\n display: inline-block;\n}\n\n.brand[data-theme-logo] .wordmark {\n display: none;\n}\n\n/* ─── header chrome — home pill + meta ───────────────────────────────────── */\n\n/* OVERVIEW / home pill — a bordered mono chip with a leading compass glyph. */\n.home {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n flex-shrink: 0;\n /* ~6px 12px — a compact chip that clears the mono caps. */\n padding: calc(var(--spacing-1, 0.25rem) * 1.5) var(--spacing-3, 0.75rem);\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, var(--border-default, #e5e7eb));\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--radius-full, 9999px);\n background: var(--surface-card, #ffffff);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.06em;\n /* Collapse the line box to the glyph height so the all-caps label optically\n * centers against the compass icon. */\n line-height: 1;\n text-transform: uppercase;\n color: var(--text-tertiary, #9ca3af);\n text-decoration: none;\n white-space: nowrap;\n transition: color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease),\n outline-color var(--motion-duration-fast, 150ms)\n var(--motion-easing-standard, ease);\n}\n\n.home:hover {\n color: var(--text-primary, #111827);\n outline-color: var(--hairline-strong, var(--border-default, #d1d5db));\n}\n\n.home:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Compass glyph — a hair larger than the caps (the design's 13px vs 11px);\n * inherits the pill color via currentColor. */\n.homeIcon {\n flex-shrink: 0;\n width: var(--font-size-sm, 0.875rem);\n height: var(--font-size-sm, 0.875rem);\n}\n\n/* Meta slot — a right-aligned breadcrumb/status line, mono UPPERCASE, tertiary. */\n.meta {\n flex-shrink: 0;\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.12em;\n line-height: 1;\n text-transform: uppercase;\n color: var(--text-tertiary, #9ca3af);\n white-space: nowrap;\n}\n\n/* ─── mobile ─────────────────────────────────────────────────────────────── */\n\n@media (max-width: 640px) {\n .headerRow {\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n }\n\n .navRow {\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n }\n\n .content {\n padding: var(--spacing-5, 1.25rem) var(--spacing-4, 1rem)\n var(--spacing-6, 1.5rem);\n }\n}\n"
2931
2931
  }
2932
2932
  ]
2933
2933
  },
@@ -2952,7 +2952,7 @@
2952
2952
  {
2953
2953
  "path": "components/ui/fidelity-mirror/fidelity-mirror.module.css",
2954
2954
  "type": "registry:ui",
2955
- "content": "/* FidelityMirror — the design-left / built-right comparison surface.\n *\n * Renders the pure DESIGN source (left) beside the BUILT render (right) in\n * recessed capture wells, with a verdict/diff indicator, numbered delta\n * callouts, a classed delta legend, and a drag-to-reveal overlay mode. Composes\n * inside DocFrame's content, and goes full-bleed on widescreen so each pane is\n * ~50vw (the compare is meant for fullscreen monitors).\n *\n * Theme-agnostic: every value references a Visor token. The card/cluster radius\n * and the recessed well fill are component-OWNED (fixed) tokens, NOT the theme\n * `--radius-*` scale or a bare `--surface-*` token — decorative themes inflate\n * `--radius-xl` to 32px and lift `--surface-subtle` ABOVE the card in dark mode,\n * which is precisely the drift this component exists to surface.\n */\n\n/* Root — the framed compare. */\n.root {\n /* Fixed cluster radius the component OWNS — NOT the theme `--radius-*` scale,\n * which decorative themes inflate far past what a dense compare cluster wants\n * (Space inflates `--radius-xl` to 32px). Overridable by a consumer/theme. */\n --fm-radius: 0.75rem;\n --fm-well-radius: 0.625rem;\n /* Widescreen gutter/padding — the outer left/right padding AND the center\n * gutter resolve to the SAME value, so each pane is ~50vw: (100vw - 3*pad)/2. */\n --fm-pad: clamp(1.25rem, 3.2vw, 3.5rem);\n /* Recessed well fill — darker than `--surface-card` in BOTH modes (the DocNav\n * pill-bg convention). The surface ramp inverts light<->dark, so a single\n * surface token can't stay \"below\" the card; mixing the card toward\n * neutral-950 darkens it directionally in either mode. Overridable. */\n --fm-well: color-mix(\n in srgb,\n var(--surface-card, #111827),\n var(--color-neutral-950, #030712) 22%\n );\n /* Source identity hues (kept across split + overlay): design = info, built =\n * accent. These carry SOURCE identity, not decoration — do not retint. */\n --fm-design-hue: var(--info, #0ea5e9);\n --fm-built-hue: var(--accent, #2563eb);\n\n display: flex;\n flex-direction: column;\n border: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n border-radius: var(--fm-radius);\n background: var(--surface-card, #ffffff);\n box-shadow: var(--shadow-md);\n overflow: hidden;\n font-family: var(--font-sans, inherit);\n}\n\n/* Widescreen full-bleed — escape DocFrame's centered column and span the\n * viewport. Header + legend align to the panes' outer edge via `--fm-pad`; the\n * panes grid uses `gap: var(--fm-pad)` so each column resolves to ~50vw. */\n.bleed {\n width: 100vw;\n margin-left: calc(50% - 50vw);\n border-radius: 0;\n border-inline: 0;\n}\n.bleed .bar {\n padding-inline: var(--fm-pad);\n}\n.bleed .panes {\n padding-inline: var(--fm-pad);\n gap: var(--fm-pad);\n}\n.bleed .legend {\n padding-inline: var(--fm-pad);\n}\n.bleed .overlay {\n padding-inline: var(--fm-pad);\n}\n\n/* ── Header bar ──────────────────────────────────────────────────────────── */\n.bar {\n display: flex;\n align-items: center;\n gap: var(--spacing-3, 0.75rem);\n flex-wrap: wrap;\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n border-bottom: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n}\n.titleWrap {\n display: inline-flex;\n flex-direction: column;\n gap: calc(var(--spacing-1, 0.25rem) * 1.5);\n min-width: 0;\n}\n.title {\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-sm, 0.8125rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.02em;\n line-height: 1;\n color: var(--text-primary, #111827);\n}\n.subtitle {\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.12em;\n text-transform: uppercase;\n line-height: 1;\n color: var(--text-tertiary, #9ca3af);\n}\n\n/* Spacer — pushes the status + toggle to the right edge. */\n.spacer {\n flex: 1 1 auto;\n}\n\n/* Platform chip — mono caps pill, hairline, card bg. */\n.chip {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: calc(var(--spacing-1, 0.25rem) * 1.25) var(--spacing-3, 0.75rem);\n border: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n border-radius: var(--radius-full, 9999px);\n background: var(--surface-card, #ffffff);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.06em;\n line-height: 1;\n text-transform: uppercase;\n white-space: nowrap;\n color: var(--text-secondary, #4b5563);\n}\n.chipDot {\n width: 0.375rem;\n height: 0.375rem;\n border-radius: var(--radius-full, 9999px);\n background: var(--success, #22c55e);\n box-shadow: 0 0 0.375rem color-mix(in srgb, var(--success, #22c55e) 70%, transparent);\n}\n.chipIcon {\n width: 0.75rem;\n height: 0.75rem;\n color: var(--text-tertiary, #9ca3af);\n}\n\n/* Status pill — the diff indicator, tinted by verdict. */\n.status {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: calc(var(--spacing-1, 0.25rem) * 1.25) var(--spacing-3, 0.75rem);\n border: var(--stroke-width-thin, 1px) solid transparent;\n border-radius: var(--radius-full, 9999px);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.08em;\n line-height: 1;\n text-transform: uppercase;\n white-space: nowrap;\n}\n.status[data-verdict=\"match\"] {\n color: var(--success, #22c55e);\n border-color: color-mix(in srgb, var(--success, #22c55e) 40%, transparent);\n background: color-mix(in srgb, var(--success, #22c55e) 12%, var(--surface-card, #ffffff));\n}\n.status[data-verdict=\"drift\"] {\n color: var(--warning, #f59e0b);\n border-color: color-mix(in srgb, var(--warning, #f59e0b) 42%, transparent);\n background: color-mix(in srgb, var(--warning, #f59e0b) 12%, var(--surface-card, #ffffff));\n}\n.status[data-verdict=\"fail\"] {\n color: var(--destructive, #ef4444);\n border-color: color-mix(in srgb, var(--destructive, #ef4444) 42%, transparent);\n background: color-mix(in srgb, var(--destructive, #ef4444) 12%, var(--surface-card, #ffffff));\n}\n.statusGlyph {\n width: 0.625rem;\n height: 0.625rem;\n border-radius: var(--radius-full, 9999px);\n background: currentColor;\n box-shadow: 0 0 0.4375rem currentColor;\n}\n.score {\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.02em;\n opacity: var(--opacity-80, 0.85);\n}\n\n/* Segmented compare-mode toggle — split / overlay. */\n.seg {\n display: inline-flex;\n gap: calc(var(--spacing-1, 0.25rem) * 0.5);\n padding: calc(var(--spacing-1, 0.25rem) * 0.75);\n border: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n border-radius: var(--radius-full, 9999px);\n background: var(--fm-well);\n}\n.segBtn {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: calc(var(--spacing-1, 0.25rem) * 1.25) calc(var(--spacing-1, 0.25rem) * 2.5);\n border: 0;\n border-radius: var(--radius-full, 9999px);\n background: transparent;\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.06em;\n text-transform: uppercase;\n line-height: 1;\n color: var(--text-tertiary, #9ca3af);\n cursor: pointer;\n transition: color var(--motion-duration-fast, 150ms) var(--motion-easing-standard, ease),\n background-color var(--motion-duration-fast, 150ms) var(--motion-easing-standard, ease);\n}\n.segIcon {\n width: 0.8125rem;\n height: 0.8125rem;\n}\n.segBtn[aria-pressed=\"true\"] {\n color: var(--text-primary, #111827);\n background: var(--surface-card, #ffffff);\n box-shadow: var(--shadow-md);\n}\n.segBtn:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* ── Panes: side-by-side ─────────────────────────────────────────────────── */\n.panes {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: var(--spacing-4, 1rem);\n padding: var(--spacing-4, 1rem);\n align-items: start;\n}\n.pane {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n min-width: 0;\n}\n.paneLabel {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.14em;\n text-transform: uppercase;\n line-height: 1;\n color: var(--text-secondary, #4b5563);\n}\n.scopeDot {\n width: 0.5rem;\n height: 0.5rem;\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n background: currentColor;\n box-shadow: 0 0 0.5rem color-mix(in srgb, currentColor 60%, transparent);\n}\n.pane[data-side=\"design\"] .scopeDot {\n color: var(--fm-design-hue);\n}\n.pane[data-side=\"built\"] .scopeDot {\n color: var(--fm-built-hue);\n}\n.paneMeta {\n margin-left: auto;\n color: var(--text-tertiary, #9ca3af);\n letter-spacing: 0.1em;\n font-weight: var(--font-weight-medium, 500);\n}\n\n/* Capture well — the recessed frame that holds a captured surface. */\n.well {\n position: relative;\n border: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n border-radius: var(--fm-well-radius);\n background: var(--fm-well);\n padding: var(--spacing-3, 0.75rem);\n overflow: hidden;\n}\n.pane[data-side=\"built\"] .well {\n box-shadow: inset 0 0 0 var(--stroke-width-thin, 1px)\n color-mix(in srgb, var(--fm-built-hue) 22%, transparent);\n}\n\n/* Captured media — an image or a live-route/external iframe fills the well. */\n.media {\n display: block;\n width: 100%;\n height: auto;\n border: 0;\n border-radius: calc(var(--fm-well-radius) - 0.25rem);\n}\n\n/* Device bezel — native / flutter screenshots read inside a subtle frame. */\n.bezel {\n width: 10.5rem;\n margin: 0 auto;\n border: 0.375rem solid color-mix(in srgb, var(--text-primary, #111827) 78%, transparent);\n border-radius: 1.625rem;\n overflow: hidden;\n background: var(--surface-page, #ffffff);\n box-shadow: var(--shadow-lg);\n}\n.bezel .media {\n border-radius: 0;\n}\n\n/* Delta callout marker — a numbered accent dot pinned over a drift location. */\n.delta {\n position: absolute;\n z-index: 3;\n display: grid;\n place-items: center;\n width: 1.25rem;\n height: 1.25rem;\n border-radius: var(--radius-full, 9999px);\n background: var(--warning, #f59e0b);\n color: var(--color-neutral-950, #030712);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-bold, 700);\n line-height: 1;\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning, #f59e0b) 30%, transparent),\n var(--shadow-md);\n}\n\n/* ── Overlay / slider mode ───────────────────────────────────────────────── */\n.overlay {\n position: relative;\n padding: var(--spacing-4, 1rem);\n}\n.overlayFrame {\n position: relative;\n border: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n border-radius: var(--fm-well-radius);\n background: var(--fm-well);\n overflow: hidden;\n}\n.layer {\n padding: var(--spacing-3, 0.75rem);\n}\n/* Clear space below the corner ribbons so they never collide with content. */\n.overlayFrame .layer {\n padding-top: var(--spacing-10, 2.5rem);\n}\n.layerTop {\n position: absolute;\n inset: 0;\n /* Revealed portion driven by `--fm-split` (design shows on the left). */\n clip-path: inset(0 calc(100% - var(--fm-split, 55%)) 0 0);\n}\n.handle {\n position: absolute;\n top: 0;\n bottom: 0;\n left: var(--fm-split, 55%);\n width: 2px;\n background: var(--accent, #2563eb);\n box-shadow: 0 0 0.625rem color-mix(in srgb, var(--accent, #2563eb) 70%, transparent);\n z-index: 4;\n}\n.grip {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n display: grid;\n place-items: center;\n width: 1.75rem;\n height: 1.75rem;\n border-radius: var(--radius-full, 9999px);\n background: var(--accent, #2563eb);\n color: var(--color-white, #ffffff);\n box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, #2563eb) 26%, transparent),\n var(--shadow-md);\n}\n.gripIcon {\n width: 0.9375rem;\n height: 0.9375rem;\n}\n.ribbon {\n position: absolute;\n z-index: 5;\n top: var(--spacing-3, 0.75rem);\n padding: var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem);\n border-radius: var(--radius-full, 9999px);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: calc(var(--font-size-2xs, 0.6875rem) * 0.9);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.12em;\n text-transform: uppercase;\n line-height: 1;\n color: var(--color-white, #ffffff);\n}\n.ribbon[data-side=\"design\"] {\n left: var(--spacing-3, 0.75rem);\n background: color-mix(in srgb, var(--fm-design-hue) 85%, var(--color-neutral-950, #030712));\n}\n.ribbon[data-side=\"built\"] {\n right: var(--spacing-3, 0.75rem);\n background: color-mix(in srgb, var(--fm-built-hue) 85%, var(--color-neutral-950, #030712));\n}\n.range {\n width: 100%;\n margin-top: var(--spacing-3, 0.75rem);\n accent-color: var(--accent, #2563eb);\n cursor: ew-resize;\n}\n.range:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* ── Delta legend ────────────────────────────────────────────────────────── */\n.legend {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n border-top: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n}\n.legendHead {\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-tertiary, #9ca3af);\n}\n.legendRow {\n display: flex;\n align-items: center;\n gap: var(--spacing-3, 0.75rem);\n}\n.legendNum {\n flex-shrink: 0;\n display: grid;\n place-items: center;\n width: 1.125rem;\n height: 1.125rem;\n border-radius: var(--radius-full, 9999px);\n background: var(--warning, #f59e0b);\n color: var(--color-neutral-950, #030712);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: calc(var(--font-size-2xs, 0.6875rem) * 0.9);\n font-weight: var(--font-weight-bold, 700);\n}\n.tag {\n flex-shrink: 0;\n padding: calc(var(--spacing-1, 0.25rem) * 0.75) var(--spacing-2, 0.5rem);\n border: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n border-radius: var(--radius-sm, 0.25rem);\n background: var(--surface-subtle, #f3f4f6);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: calc(var(--font-size-2xs, 0.6875rem) * 0.9);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--text-secondary, #4b5563);\n}\n.legendText {\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-sm, 0.8125rem);\n color: var(--text-secondary, #4b5563);\n}\n.legendText b {\n color: var(--text-primary, #111827);\n font-weight: var(--font-weight-bold, 700);\n}\n\n/* Visually-hidden text for assistive technology. */\n.srOnly {\n position: absolute;\n width: var(--stroke-width-thin, 1px);\n height: var(--stroke-width-thin, 1px);\n padding: 0;\n margin: calc(-1 * var(--stroke-width-thin, 1px));\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n/* Responsive: panes stack under 768px (design over built) — NEVER a sideways\n * scroll strip (the defect the doc-set surfaces exist to kill). */\n@media (max-width: 767px) {\n .panes {\n grid-template-columns: 1fr;\n }\n .bar {\n gap: var(--spacing-2, 0.5rem);\n }\n}\n"
2955
+ "content": "/* FidelityMirror — the design-left / built-right comparison surface.\n *\n * Renders the pure DESIGN source (left) beside the BUILT render (right) in\n * recessed capture wells, with a verdict/diff indicator, numbered delta\n * callouts, a classed delta legend, and a drag-to-reveal overlay mode. Composes\n * inside DocFrame's content, and goes full-bleed on widescreen so each pane is\n * ~50vw (the compare is meant for fullscreen monitors).\n *\n * Theme-agnostic: every value references a Visor token. The card/cluster radius\n * and the recessed well fill are component-OWNED (fixed) tokens, NOT the theme\n * `--radius-*` scale or a bare `--surface-*` token — decorative themes inflate\n * `--radius-xl` to 32px and lift `--surface-subtle` ABOVE the card in dark mode,\n * which is precisely the drift this component exists to surface.\n */\n\n/* Root — the framed compare. */\n.root {\n /* Fixed cluster radius the component OWNS — NOT the theme `--radius-*` scale,\n * which decorative themes inflate far past what a dense compare cluster wants\n * (Space inflates `--radius-xl` to 32px). Overridable by a consumer/theme. */\n --fm-radius: 0.75rem;\n --fm-well-radius: 0.625rem;\n /* Widescreen gutter/padding — the outer left/right padding AND the center\n * gutter resolve to the SAME value, so each pane is ~50vw: (100vw - 3*pad)/2. */\n --fm-pad: clamp(1.25rem, 3.2vw, 3.5rem);\n /* Recessed well fill — darker than `--surface-card` in BOTH modes (the DocNav\n * pill-bg convention). The surface ramp inverts light<->dark, so a single\n * surface token can't stay \"below\" the card; mixing the card toward\n * neutral-950 darkens it directionally in either mode. Overridable. */\n --fm-well: color-mix(\n in srgb,\n var(--surface-card, #111827),\n var(--color-neutral-950, #030712) 22%\n );\n /* Source identity hues (kept across split + overlay): design = info, built =\n * accent. These carry SOURCE identity, not decoration — do not retint. */\n --fm-design-hue: var(--info, #0ea5e9);\n --fm-built-hue: var(--accent, #2563eb);\n\n display: flex;\n flex-direction: column;\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, #e5e7eb);\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--fm-radius);\n background: var(--surface-card, #ffffff);\n box-shadow: var(--shadow-md);\n overflow: hidden;\n font-family: var(--font-sans, inherit);\n}\n\n/* Widescreen full-bleed — escape DocFrame's centered column and span the\n * viewport. Header + legend align to the panes' outer edge via `--fm-pad`; the\n * panes grid uses `gap: var(--fm-pad)` so each column resolves to ~50vw. */\n.bleed {\n width: 100vw;\n margin-left: calc(50% - 50vw);\n border-radius: 0;\n border-inline: 0;\n}\n.bleed .bar {\n padding-inline: var(--fm-pad);\n}\n.bleed .panes {\n padding-inline: var(--fm-pad);\n gap: var(--fm-pad);\n}\n.bleed .legend {\n padding-inline: var(--fm-pad);\n}\n.bleed .overlay {\n padding-inline: var(--fm-pad);\n}\n\n/* ── Header bar ──────────────────────────────────────────────────────────── */\n.bar {\n display: flex;\n align-items: center;\n gap: var(--spacing-3, 0.75rem);\n flex-wrap: wrap;\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n border-bottom: var(--stroke-width-thin, 1px) solid transparent;\n border-image: linear-gradient(var(--hairline, #e5e7eb), var(--hairline, #e5e7eb)) 1 / 0 0 var(--hairline-width, var(--stroke-width-thin, 1px)) 0;\n}\n.titleWrap {\n display: inline-flex;\n flex-direction: column;\n gap: calc(var(--spacing-1, 0.25rem) * 1.5);\n min-width: 0;\n}\n.title {\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-sm, 0.8125rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.02em;\n line-height: 1;\n color: var(--text-primary, #111827);\n}\n.subtitle {\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.12em;\n text-transform: uppercase;\n line-height: 1;\n color: var(--text-tertiary, #9ca3af);\n}\n\n/* Spacer — pushes the status + toggle to the right edge. */\n.spacer {\n flex: 1 1 auto;\n}\n\n/* Platform chip — mono caps pill, hairline, card bg. */\n.chip {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: calc(var(--spacing-1, 0.25rem) * 1.25) var(--spacing-3, 0.75rem);\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, #e5e7eb);\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--radius-full, 9999px);\n background: var(--surface-card, #ffffff);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.06em;\n line-height: 1;\n text-transform: uppercase;\n white-space: nowrap;\n color: var(--text-secondary, #4b5563);\n}\n.chipDot {\n width: 0.375rem;\n height: 0.375rem;\n border-radius: var(--radius-full, 9999px);\n background: var(--success, #22c55e);\n box-shadow: 0 0 0.375rem color-mix(in srgb, var(--success, #22c55e) 70%, transparent);\n}\n.chipIcon {\n width: 0.75rem;\n height: 0.75rem;\n color: var(--text-tertiary, #9ca3af);\n}\n\n/* Status pill — the diff indicator, tinted by verdict. */\n.status {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: calc(var(--spacing-1, 0.25rem) * 1.25) var(--spacing-3, 0.75rem);\n border: var(--stroke-width-thin, 1px) solid transparent;\n border-radius: var(--radius-full, 9999px);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.08em;\n line-height: 1;\n text-transform: uppercase;\n white-space: nowrap;\n}\n.status[data-verdict=\"match\"] {\n color: var(--success, #22c55e);\n border-color: color-mix(in srgb, var(--success, #22c55e) 40%, transparent);\n background: color-mix(in srgb, var(--success, #22c55e) 12%, var(--surface-card, #ffffff));\n}\n.status[data-verdict=\"drift\"] {\n color: var(--warning, #f59e0b);\n border-color: color-mix(in srgb, var(--warning, #f59e0b) 42%, transparent);\n background: color-mix(in srgb, var(--warning, #f59e0b) 12%, var(--surface-card, #ffffff));\n}\n.status[data-verdict=\"fail\"] {\n color: var(--destructive, #ef4444);\n border-color: color-mix(in srgb, var(--destructive, #ef4444) 42%, transparent);\n background: color-mix(in srgb, var(--destructive, #ef4444) 12%, var(--surface-card, #ffffff));\n}\n.statusGlyph {\n width: 0.625rem;\n height: 0.625rem;\n border-radius: var(--radius-full, 9999px);\n background: currentColor;\n box-shadow: 0 0 0.4375rem currentColor;\n}\n.score {\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.02em;\n opacity: var(--opacity-80, 0.85);\n}\n\n/* Segmented compare-mode toggle — split / overlay. */\n.seg {\n display: inline-flex;\n gap: calc(var(--spacing-1, 0.25rem) * 0.5);\n padding: calc(var(--spacing-1, 0.25rem) * 0.75);\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, #e5e7eb);\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--radius-full, 9999px);\n background: var(--fm-well);\n}\n.segBtn {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: calc(var(--spacing-1, 0.25rem) * 1.25) calc(var(--spacing-1, 0.25rem) * 2.5);\n border: 0;\n border-radius: var(--radius-full, 9999px);\n background: transparent;\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-medium, 500);\n letter-spacing: 0.06em;\n text-transform: uppercase;\n line-height: 1;\n color: var(--text-tertiary, #9ca3af);\n cursor: pointer;\n transition: color var(--motion-duration-fast, 150ms) var(--motion-easing-standard, ease),\n background-color var(--motion-duration-fast, 150ms) var(--motion-easing-standard, ease);\n}\n.segIcon {\n width: 0.8125rem;\n height: 0.8125rem;\n}\n.segBtn[aria-pressed=\"true\"] {\n color: var(--text-primary, #111827);\n background: var(--surface-card, #ffffff);\n box-shadow: var(--shadow-md);\n}\n.segBtn:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* ── Panes: side-by-side ─────────────────────────────────────────────────── */\n.panes {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: var(--spacing-4, 1rem);\n padding: var(--spacing-4, 1rem);\n align-items: start;\n}\n.pane {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n min-width: 0;\n}\n.paneLabel {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.14em;\n text-transform: uppercase;\n line-height: 1;\n color: var(--text-secondary, #4b5563);\n}\n.scopeDot {\n width: 0.5rem;\n height: 0.5rem;\n flex-shrink: 0;\n border-radius: var(--radius-full, 9999px);\n background: currentColor;\n box-shadow: 0 0 0.5rem color-mix(in srgb, currentColor 60%, transparent);\n}\n.pane[data-side=\"design\"] .scopeDot {\n color: var(--fm-design-hue);\n}\n.pane[data-side=\"built\"] .scopeDot {\n color: var(--fm-built-hue);\n}\n.paneMeta {\n margin-left: auto;\n color: var(--text-tertiary, #9ca3af);\n letter-spacing: 0.1em;\n font-weight: var(--font-weight-medium, 500);\n}\n\n/* Capture well — the recessed frame that holds a captured surface. */\n.well {\n position: relative;\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, #e5e7eb);\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--fm-well-radius);\n background: var(--fm-well);\n padding: var(--spacing-3, 0.75rem);\n overflow: hidden;\n}\n.pane[data-side=\"built\"] .well {\n box-shadow: inset 0 0 0 var(--stroke-width-thin, 1px)\n color-mix(in srgb, var(--fm-built-hue) 22%, transparent);\n}\n\n/* Captured media — an image or a live-route/external iframe fills the well. */\n.media {\n display: block;\n width: 100%;\n height: auto;\n border: 0;\n border-radius: calc(var(--fm-well-radius) - 0.25rem);\n}\n\n/* Device bezel — native / flutter screenshots read inside a subtle frame. */\n.bezel {\n width: 10.5rem;\n margin: 0 auto;\n border: 0.375rem solid color-mix(in srgb, var(--text-primary, #111827) 78%, transparent);\n border-radius: 1.625rem;\n overflow: hidden;\n background: var(--surface-page, #ffffff);\n box-shadow: var(--shadow-lg);\n}\n.bezel .media {\n border-radius: 0;\n}\n\n/* Delta callout marker — a numbered accent dot pinned over a drift location. */\n.delta {\n position: absolute;\n z-index: 3;\n display: grid;\n place-items: center;\n width: 1.25rem;\n height: 1.25rem;\n border-radius: var(--radius-full, 9999px);\n background: var(--warning, #f59e0b);\n color: var(--color-neutral-950, #030712);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-bold, 700);\n line-height: 1;\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning, #f59e0b) 30%, transparent),\n var(--shadow-md);\n}\n\n/* ── Overlay / slider mode ───────────────────────────────────────────────── */\n.overlay {\n position: relative;\n padding: var(--spacing-4, 1rem);\n}\n.overlayFrame {\n position: relative;\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, #e5e7eb);\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--fm-well-radius);\n background: var(--fm-well);\n overflow: hidden;\n}\n.layer {\n padding: var(--spacing-3, 0.75rem);\n}\n/* Clear space below the corner ribbons so they never collide with content. */\n.overlayFrame .layer {\n padding-top: var(--spacing-10, 2.5rem);\n}\n.layerTop {\n position: absolute;\n inset: 0;\n /* Revealed portion driven by `--fm-split` (design shows on the left). */\n clip-path: inset(0 calc(100% - var(--fm-split, 55%)) 0 0);\n}\n.handle {\n position: absolute;\n top: 0;\n bottom: 0;\n left: var(--fm-split, 55%);\n width: 2px;\n background: var(--accent, #2563eb);\n box-shadow: 0 0 0.625rem color-mix(in srgb, var(--accent, #2563eb) 70%, transparent);\n z-index: 4;\n}\n.grip {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n display: grid;\n place-items: center;\n width: 1.75rem;\n height: 1.75rem;\n border-radius: var(--radius-full, 9999px);\n background: var(--accent, #2563eb);\n color: var(--color-white, #ffffff);\n box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, #2563eb) 26%, transparent),\n var(--shadow-md);\n}\n.gripIcon {\n width: 0.9375rem;\n height: 0.9375rem;\n}\n.ribbon {\n position: absolute;\n z-index: 5;\n top: var(--spacing-3, 0.75rem);\n padding: var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem);\n border-radius: var(--radius-full, 9999px);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: calc(var(--font-size-2xs, 0.6875rem) * 0.9);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.12em;\n text-transform: uppercase;\n line-height: 1;\n color: var(--color-white, #ffffff);\n}\n.ribbon[data-side=\"design\"] {\n left: var(--spacing-3, 0.75rem);\n background: color-mix(in srgb, var(--fm-design-hue) 85%, var(--color-neutral-950, #030712));\n}\n.ribbon[data-side=\"built\"] {\n right: var(--spacing-3, 0.75rem);\n background: color-mix(in srgb, var(--fm-built-hue) 85%, var(--color-neutral-950, #030712));\n}\n.range {\n width: 100%;\n margin-top: var(--spacing-3, 0.75rem);\n accent-color: var(--accent, #2563eb);\n cursor: ew-resize;\n}\n.range:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, currentColor);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* ── Delta legend ────────────────────────────────────────────────────────── */\n.legend {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);\n border-top: var(--stroke-width-thin, 1px) solid transparent;\n border-image: linear-gradient(var(--hairline, #e5e7eb), var(--hairline, #e5e7eb)) 1 / var(--hairline-width, var(--stroke-width-thin, 1px)) 0 0 0;\n}\n.legendHead {\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-2xs, 0.6875rem);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--text-tertiary, #9ca3af);\n}\n.legendRow {\n display: flex;\n align-items: center;\n gap: var(--spacing-3, 0.75rem);\n}\n.legendNum {\n flex-shrink: 0;\n display: grid;\n place-items: center;\n width: 1.125rem;\n height: 1.125rem;\n border-radius: var(--radius-full, 9999px);\n background: var(--warning, #f59e0b);\n color: var(--color-neutral-950, #030712);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: calc(var(--font-size-2xs, 0.6875rem) * 0.9);\n font-weight: var(--font-weight-bold, 700);\n}\n.tag {\n flex-shrink: 0;\n padding: calc(var(--spacing-1, 0.25rem) * 0.75) var(--spacing-2, 0.5rem);\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline, #e5e7eb);\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n border-radius: var(--radius-sm, 0.25rem);\n background: var(--surface-subtle, #f3f4f6);\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: calc(var(--font-size-2xs, 0.6875rem) * 0.9);\n font-weight: var(--font-weight-bold, 700);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--text-secondary, #4b5563);\n}\n.legendText {\n font-family: var(--font-mono, ui-monospace, \"SFMono-Regular\", Menlo, monospace);\n font-size: var(--font-size-sm, 0.8125rem);\n color: var(--text-secondary, #4b5563);\n}\n.legendText b {\n color: var(--text-primary, #111827);\n font-weight: var(--font-weight-bold, 700);\n}\n\n/* Visually-hidden text for assistive technology. */\n.srOnly {\n position: absolute;\n width: var(--stroke-width-thin, 1px);\n height: var(--stroke-width-thin, 1px);\n padding: 0;\n margin: calc(-1 * var(--stroke-width-thin, 1px));\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n/* Responsive: panes stack under 768px (design over built) — NEVER a sideways\n * scroll strip (the defect the doc-set surfaces exist to kill). */\n@media (max-width: 767px) {\n .panes {\n grid-template-columns: 1fr;\n }\n .bar {\n gap: var(--spacing-2, 0.5rem);\n }\n}\n"
2956
2956
  }
2957
2957
  ]
2958
2958
  },
@@ -3027,7 +3027,7 @@
3027
3027
  {
3028
3028
  "path": "components/ui/infographic-bar/infographic-bar.module.css",
3029
3029
  "type": "registry:ui",
3030
- "content": "/* InfographicBar: lays out N stat-cards as a single continuous band.\n *\n * Corner strategy: the band owns the frame, radius, and elevation. A rounded\n * `border-radius` plus `overflow: hidden` clips the cells so the OUTER corners\n * round and the INNER corners stay square — no per-cell :first/:last radius\n * math, and it holds for any N. Each cell sheds its own border/radius/shadow\n * (see `.cell`) so the band reads as one surface, not bordered cards butting\n * together.\n *\n * Equal columns without knowing N: `grid-auto-flow: column` +\n * `grid-auto-columns: minmax(0, 1fr)`. The `minmax(0, …)` floor lets long\n * values shrink instead of forcing the band to overflow.\n */\n.bar {\n display: grid;\n grid-auto-flow: column;\n grid-auto-columns: minmax(0, 1fr);\n background-color: var(--surface-card, #ffffff);\n border: var(--stroke-width-thin, 1px) solid var(--border-default, #e5e7eb);\n border-radius: var(--radius-lg, 0.75rem);\n box-shadow: var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05));\n overflow: hidden;\n}\n\n/* Cell chrome reset — the band owns the chrome, so each stat-card drops its own\n * border, radius, and shadow. Scoped under `.bar` (a local class) to satisfy\n * the Turbopack pure-selector rule and to out-specify stat-card's own\n * base and variant rules. The cell BACKGROUND is left untouched so the\n * stat-card highlight variant still tints its cell.\n */\n.bar .cell {\n border: none;\n border-radius: 0;\n box-shadow: none;\n}\n\n/* Hairline divider on the leading edge of every cell after the first — this is\n * what makes the middle cells read as continuous rather than separate. Uses\n * `--hairline` (the house soft-separator token, softer than a full border) so\n * it stays subtle, and exposes `--infographic-bar-divider` so a theme or\n * consumer can retune it — including to `transparent` for a fully seamless\n * borderless band. The outer frame above references `--border-default`, which\n * borderless themes set to `transparent`, so the band melts into the page.\n */\n.bar .cell + .cell {\n border-left: var(--stroke-width-thin, 1px) solid\n var(--infographic-bar-divider, var(--hairline, #e5e7eb));\n}\n"
3030
+ "content": "/* InfographicBar: lays out N stat-cards as a single continuous band.\n *\n * Corner strategy: the band owns the frame, radius, and elevation. A rounded\n * `border-radius` plus `overflow: hidden` clips the cells so the OUTER corners\n * round and the INNER corners stay square — no per-cell :first/:last radius\n * math, and it holds for any N. Each cell sheds its own border/radius/shadow\n * (see `.cell`) so the band reads as one surface, not bordered cards butting\n * together.\n *\n * Equal columns without knowing N: `grid-auto-flow: column` +\n * `grid-auto-columns: minmax(0, 1fr)`. The `minmax(0, …)` floor lets long\n * values shrink instead of forcing the band to overflow.\n */\n.bar {\n display: grid;\n grid-auto-flow: column;\n grid-auto-columns: minmax(0, 1fr);\n background-color: var(--surface-card, #ffffff);\n border: var(--stroke-width-thin, 1px) solid var(--border-default, #e5e7eb);\n border-radius: var(--radius-lg, 0.75rem);\n box-shadow: var(--shadow-xs, 0 1px 2px 0 rgb(0 0 0 / 0.05));\n overflow: hidden;\n}\n\n/* Cell chrome reset — the band owns the chrome, so each stat-card drops its own\n * border, radius, and shadow. Scoped under `.bar` (a local class) to satisfy\n * the Turbopack pure-selector rule and to out-specify stat-card's own\n * base and variant rules. The cell BACKGROUND is left untouched so the\n * stat-card highlight variant still tints its cell.\n */\n.bar .cell {\n border: none;\n border-radius: 0;\n box-shadow: none;\n}\n\n/* Hairline divider on the leading edge of every cell after the first — this is\n * what makes the middle cells read as continuous rather than separate. Uses\n * `--hairline` (the house soft-separator token, softer than a full border) so\n * it stays subtle, and exposes `--infographic-bar-divider` so a theme or\n * consumer can retune it — including to `transparent` for a fully seamless\n * borderless band. The outer frame above references `--border-default`, which\n * borderless themes set to `transparent`, so the band melts into the page.\n */\n.bar .cell + .cell {\n border-left: var(--stroke-width-thin, 1px) solid transparent;\n border-image: linear-gradient(var(--infographic-bar-divider, var(--hairline, #e5e7eb)), var(--infographic-bar-divider, var(--hairline, #e5e7eb))) 1 / 0 0 0 var(--hairline-width, var(--stroke-width-thin, 1px));\n}\n"
3031
3031
  }
3032
3032
  ]
3033
3033
  },
@@ -3345,7 +3345,7 @@
3345
3345
  {
3346
3346
  "path": "components/ui/structured-prompt/structured-prompt.module.css",
3347
3347
  "type": "registry:ui",
3348
- "content": "/* StructuredPrompt\n *\n * Inline \"mad-lib\" fill-in-the-blank card used in brand elicitation and\n * structured-prompt workflows. A card with an eyebrow header, flowing prose\n * at tall line-height, and inline slot chips (filled or empty) embedded in\n * the prose, plus a hint footer.\n */\n\n.root {\n width: 100%;\n border-radius: var(--radius-xl, 1rem);\n background: var(--surface-card, #ffffff);\n border: var(--stroke-width-thin, 1px) solid var(--border-default, #e5e7eb);\n box-shadow: var(--shadow-sm, 0 1px 2px 0 rgba(0, 0, 0, 0.05));\n overflow: hidden;\n}\n\n/* Header bar — small icon + uppercase eyebrow label, hairline bottom border */\n.header {\n display: flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-2-5, 0.625rem) var(--spacing-3-5, 0.875rem);\n border-bottom: var(--stroke-width-thin, 1px) solid var(--hairline, #e5e7eb);\n /* 11.5px — no line-height token matches this tall-prose context; intentional */\n font-size: 11.5px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--text-tertiary, #6b7280);\n}\n\n.headerIcon {\n display: inline-flex;\n align-items: center;\n /* Use accent color for the eyebrow icon, matching the design spec */\n color: var(--surface-accent-default, #3b82f6);\n flex-shrink: 0;\n /* 13px icon — sized to match the 11.5px label rhythm */\n font-size: 13px;\n line-height: 1;\n}\n\n.headerLabel {\n flex: 1;\n min-width: 0;\n}\n\n/* Body — prose container with tall line-height so inline chips breathe */\n.body {\n padding: var(--spacing-3-5, 0.875rem);\n /* 15px — closest to --font-size-base but intentionally smaller for prose density */\n font-size: 15px;\n /* 2.05 line-height — no token matches; intentional value from design spec (elicit-core HiFi prototype) */\n line-height: 2.05;\n color: var(--text-primary, #111827);\n}\n\n/* Slot chip — inline; rendered inside flowing prose text */\n.slot {\n display: inline-flex;\n align-items: center;\n /* min-height 30px — no spacing token; intentional to match chip rhythm */\n min-height: 30px;\n padding: var(--spacing-0-5, 0.125rem) var(--spacing-2-5, 0.625rem);\n margin: 0 var(--spacing-0-5, 0.125rem);\n border-radius: var(--radius-md, 0.5rem);\n border-width: var(--stroke-width-thin, 1px);\n border-style: solid;\n /* 14px — intentionally one step below body prose for visual hierarchy */\n font-size: 14px;\n cursor: default;\n vertical-align: middle;\n line-height: 1.4;\n text-decoration: none;\n -webkit-appearance: none;\n appearance: none;\n}\n\n/* Focus ring — only when rendered as <button> */\n.slot:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, #3b82f6);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Filled slot — accent-subtle bg + accent-default border + text-link color + 600 weight.\n * Uses the same token triplet as Chip's selected state so it adapts correctly in dark mode.\n */\n.slotFilled {\n background: var(--surface-accent-subtle, #eff6ff);\n border-color: var(--surface-accent-default, #3b82f6);\n color: var(--text-link, #2563eb);\n font-weight: var(--font-weight-semibold, 600);\n}\n\n/* Empty slot — dashed border, muted bg, italic, tertiary text */\n.slotEmpty {\n color: var(--text-tertiary, #6b7280);\n font-style: italic;\n font-weight: var(--font-weight-medium, 500);\n background: var(--surface-subtle, #f9fafb);\n border-style: dashed;\n border-color: var(--border-default, #e5e7eb);\n cursor: text;\n}\n\n/* Hint footer — small tertiary text below the body */\n.hint {\n padding: 0 var(--spacing-3-5, 0.875rem) var(--spacing-3, 0.75rem);\n /* 11.5px — matches header eyebrow size; intentional */\n font-size: 11.5px;\n color: var(--text-tertiary, #6b7280);\n}\n\n/* Respect reduced motion */\n@media (prefers-reduced-motion: reduce) {\n .slot {\n transition: none;\n }\n}\n"
3348
+ "content": "/* StructuredPrompt\n *\n * Inline \"mad-lib\" fill-in-the-blank card used in brand elicitation and\n * structured-prompt workflows. A card with an eyebrow header, flowing prose\n * at tall line-height, and inline slot chips (filled or empty) embedded in\n * the prose, plus a hint footer.\n */\n\n.root {\n width: 100%;\n border-radius: var(--radius-xl, 1rem);\n background: var(--surface-card, #ffffff);\n border: var(--stroke-width-thin, 1px) solid var(--border-default, #e5e7eb);\n box-shadow: var(--shadow-sm, 0 1px 2px 0 rgba(0, 0, 0, 0.05));\n overflow: hidden;\n}\n\n/* Header bar — small icon + uppercase eyebrow label, hairline bottom border */\n.header {\n display: flex;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n padding: var(--spacing-2-5, 0.625rem) var(--spacing-3-5, 0.875rem);\n border-bottom: var(--stroke-width-thin, 1px) solid transparent;\n border-image: linear-gradient(var(--hairline, #e5e7eb), var(--hairline, #e5e7eb)) 1 / 0 0 var(--hairline-width, var(--stroke-width-thin, 1px)) 0;\n /* 11.5px — no line-height token matches this tall-prose context; intentional */\n font-size: 11.5px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--text-tertiary, #6b7280);\n}\n\n.headerIcon {\n display: inline-flex;\n align-items: center;\n /* Use accent color for the eyebrow icon, matching the design spec */\n color: var(--surface-accent-default, #3b82f6);\n flex-shrink: 0;\n /* 13px icon — sized to match the 11.5px label rhythm */\n font-size: 13px;\n line-height: 1;\n}\n\n.headerLabel {\n flex: 1;\n min-width: 0;\n}\n\n/* Body — prose container with tall line-height so inline chips breathe */\n.body {\n padding: var(--spacing-3-5, 0.875rem);\n /* 15px — closest to --font-size-base but intentionally smaller for prose density */\n font-size: 15px;\n /* 2.05 line-height — no token matches; intentional value from design spec (elicit-core HiFi prototype) */\n line-height: 2.05;\n color: var(--text-primary, #111827);\n}\n\n/* Slot chip — inline; rendered inside flowing prose text */\n.slot {\n display: inline-flex;\n align-items: center;\n /* min-height 30px — no spacing token; intentional to match chip rhythm */\n min-height: 30px;\n padding: var(--spacing-0-5, 0.125rem) var(--spacing-2-5, 0.625rem);\n margin: 0 var(--spacing-0-5, 0.125rem);\n border-radius: var(--radius-md, 0.5rem);\n border-width: var(--stroke-width-thin, 1px);\n border-style: solid;\n /* 14px — intentionally one step below body prose for visual hierarchy */\n font-size: 14px;\n cursor: default;\n vertical-align: middle;\n line-height: 1.4;\n text-decoration: none;\n -webkit-appearance: none;\n appearance: none;\n}\n\n/* Focus ring — only when rendered as <button> */\n.slot:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--border-focus, #3b82f6);\n outline-offset: var(--focus-ring-offset, 2px);\n}\n\n/* Filled slot — accent-subtle bg + accent-default border + text-link color + 600 weight.\n * Uses the same token triplet as Chip's selected state so it adapts correctly in dark mode.\n */\n.slotFilled {\n background: var(--surface-accent-subtle, #eff6ff);\n border-color: var(--surface-accent-default, #3b82f6);\n color: var(--text-link, #2563eb);\n font-weight: var(--font-weight-semibold, 600);\n}\n\n/* Empty slot — dashed border, muted bg, italic, tertiary text */\n.slotEmpty {\n color: var(--text-tertiary, #6b7280);\n font-style: italic;\n font-weight: var(--font-weight-medium, 500);\n background: var(--surface-subtle, #f9fafb);\n border-style: dashed;\n border-color: var(--border-default, #e5e7eb);\n cursor: text;\n}\n\n/* Hint footer — small tertiary text below the body */\n.hint {\n padding: 0 var(--spacing-3-5, 0.875rem) var(--spacing-3, 0.75rem);\n /* 11.5px — matches header eyebrow size; intentional */\n font-size: 11.5px;\n color: var(--text-tertiary, #6b7280);\n}\n\n/* Respect reduced motion */\n@media (prefers-reduced-motion: reduce) {\n .slot {\n transition: none;\n }\n}\n"
3349
3349
  }
3350
3350
  ]
3351
3351
  },
@@ -4220,7 +4220,7 @@
4220
4220
  {
4221
4221
  "path": "blocks/dialog-form/dialog-form.module.css",
4222
4222
  "type": "registry:block",
4223
- "content": "/* DialogForm block — Animal dialog substrate shell (VI-620).\n *\n * Restyles the `dialog` atom's DialogContent into the compact admin modal\n * panel, then adds the header / title / body / footer chrome the 10 hand-rolled\n * Animal modals converge onto. Every value traces to a Visor token so the\n * active theme (Animal, or any other) swaps the whole shell — no bespoke CSS.\n */\n\n/* Compact panel — overrides the atom's default 32rem / spacing-6 content. The\n * max-width is the `width` axis (--widthSm/Md/Lg below); the hairline border is a\n * token seam (--dialog-form-panel-border) a theme or the `border=\"none\"` variant\n * can null out — AN-237 removed it (the hairline reads as chrome the design\n * doesn't want) without editing this copy. */\n.panel {\n border-radius: var(--radius-xl, 0.75rem);\n padding: var(--spacing-5, 1.25rem);\n /* Floating panel — MUST be an opaque surface. `--surface-card` is a card-in-\n * flow token a theme may make translucent glass (blackout: rgba(255,255,255,\n * 0.04)); over the dimmed backdrop it has no opaque layer behind it and reads\n * see-through. `--surface-popover` is the opaque floating-panel token and is\n * identical to `--surface-card` on solid themes, so only glass themes change. */\n background: var(--surface-popover, var(--surface-page, #ffffff));\n border: var(\n --dialog-form-panel-border,\n var(--stroke-width-thin, 1px) solid\n var(--hairline, var(--border-default, #e5e7eb))\n );\n box-shadow: var(--shadow-lg);\n}\n\n/* Width axis — max-width only. Token-backed so a theme can retune each step;\n * `md` (default) preserves the VI-620 30rem panel. */\n.widthSm {\n max-width: var(--dialog-form-width-sm, 24rem);\n}\n\n.widthMd {\n max-width: var(--dialog-form-width-md, 30rem);\n}\n\n.widthLg {\n max-width: var(--dialog-form-width-lg, 40rem);\n}\n\n/* Borderless variant — nulls the panel-border seam (border=\"none\"). Consumers\n * can equivalently set --dialog-form-panel-border: none in a theme. */\n.panelBorderless {\n --dialog-form-panel-border: none;\n}\n\n/* Header — title + optional description, tight stack above the body. */\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-1, 0.25rem);\n margin-bottom: var(--spacing-4, 1rem);\n}\n\n/* Title — dlg-title. Bold, primary ink. Size axis reconciles the two AN\n * sources: the 13.5px surface scale (default `sm`) and reuse.html's 15px\n * shared/confirm dialogs (`md`). Normalized onto the --text-N chrome scale. */\n.title {\n font-weight: var(--font-weight-bold, 700);\n color: var(--text-primary, #111827);\n margin: 0;\n line-height: 1.3;\n}\n\n.titleSm {\n font-size: var(--text-13, 0.8125rem);\n}\n\n.titleMd {\n font-size: var(--text-16, 1rem);\n}\n\n/* Body — vertical stack of DialogField blocks. */\n.body {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-4, 1rem);\n}\n\n/* Footer — right-aligned dlg-btn action row. */\n.footer {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n margin-top: var(--spacing-5, 1.25rem);\n}\n"
4223
+ "content": "/* DialogForm block — Animal dialog substrate shell (VI-620).\n *\n * Restyles the `dialog` atom's DialogContent into the compact admin modal\n * panel, then adds the header / title / body / footer chrome the 10 hand-rolled\n * Animal modals converge onto. Every value traces to a Visor token so the\n * active theme (Animal, or any other) swaps the whole shell — no bespoke CSS.\n */\n\n/* Compact panel — overrides the atom's default 32rem / spacing-6 content. The\n * max-width is the `width` axis (--widthSm/Md/Lg below); the hairline border is a\n * token seam (--dialog-form-panel-border) a theme or the `border=\"none\"` variant\n * can null out — AN-237 removed it (the hairline reads as chrome the design\n * doesn't want) without editing this copy. */\n.panel {\n border-radius: var(--radius-xl, 0.75rem);\n padding: var(--spacing-5, 1.25rem);\n /* Floating panel — MUST be an opaque surface. `--surface-card` is a card-in-\n * flow token a theme may make translucent glass (blackout: rgba(255,255,255,\n * 0.04)); over the dimmed backdrop it has no opaque layer behind it and reads\n * see-through. `--surface-popover` is the opaque floating-panel token and is\n * identical to `--surface-card` on solid themes, so only glass themes change. */\n background: var(--surface-popover, var(--surface-page, #ffffff));\n /* The border is geometry; the visible hairline is an inset outline at\n * --hairline-width, so --hairline-width: 0 removes it with no layout shift.\n * A bound --dialog-form-panel-border (a full border shorthand, or `none`) is\n * not a valid outline-style, so binding it drops the outline and draws the\n * bound border instead. */\n border: var(--dialog-form-panel-border, var(--stroke-width-thin, 1px) solid transparent);\n outline-style: var(--dialog-form-panel-border, solid);\n outline-width: var(--hairline-width, var(--stroke-width-thin, 1px));\n outline-color: var(--hairline, var(--border-default, #e5e7eb));\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n box-shadow: var(--shadow-lg);\n}\n\n/* Width axis — max-width only. Token-backed so a theme can retune each step;\n * `md` (default) preserves the VI-620 30rem panel. */\n.widthSm {\n max-width: var(--dialog-form-width-sm, 24rem);\n}\n\n.widthMd {\n max-width: var(--dialog-form-width-md, 30rem);\n}\n\n.widthLg {\n max-width: var(--dialog-form-width-lg, 40rem);\n}\n\n/* Borderless variant — nulls the panel-border seam (border=\"none\"). Consumers\n * can equivalently set --dialog-form-panel-border: none in a theme. */\n.panelBorderless {\n --dialog-form-panel-border: none;\n}\n\n/* Header — title + optional description, tight stack above the body. */\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-1, 0.25rem);\n margin-bottom: var(--spacing-4, 1rem);\n}\n\n/* Title — dlg-title. Bold, primary ink. Size axis reconciles the two AN\n * sources: the 13.5px surface scale (default `sm`) and reuse.html's 15px\n * shared/confirm dialogs (`md`). Normalized onto the --text-N chrome scale. */\n.title {\n font-weight: var(--font-weight-bold, 700);\n color: var(--text-primary, #111827);\n margin: 0;\n line-height: 1.3;\n}\n\n.titleSm {\n font-size: var(--text-13, 0.8125rem);\n}\n\n.titleMd {\n font-size: var(--text-16, 1rem);\n}\n\n/* Body — vertical stack of DialogField blocks. */\n.body {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-4, 1rem);\n}\n\n/* Footer — right-aligned dlg-btn action row. */\n.footer {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: var(--spacing-2, 0.5rem);\n margin-top: var(--spacing-5, 1.25rem);\n}\n"
4224
4224
  }
4225
4225
  ]
4226
4226
  },
@@ -5185,7 +5185,7 @@
5185
5185
  {
5186
5186
  "path": "components/visual/browser-frame/browser-frame.module.css",
5187
5187
  "type": "registry:ui",
5188
- "content": "/*\n * BrowserFrame — browser-chrome mockup frame.\n *\n * Ported from Blacklight EpkFrame (VI-570). Elevation is kept OUT so consumers\n * can compose with the lit / lit-soft / lit-strong utilities from visor-core/utilities.\n *\n * --browser-frame-bg: deep near-black background of the frame shell.\n * Override to use a theme surface token when the consumer has one.\n *\n * --browser-frame-focus-color: focus outline color on the URL pill link.\n * Defaults to var(--accent, currentColor) so Blacklight can bind its\n * keyed per-artist accent by setting this on the frame element.\n */\n\n.frame {\n overflow: hidden;\n border-radius: var(--radius-xl, 1rem);\n border: var(--stroke-width-thin) solid var(--hairline-strong, rgb(255 255 255 / var(--opacity-10, 0.10)));\n background-color: var(--browser-frame-bg, #0a0a0b);\n}\n\n/* Chrome bar */\n.chrome {\n position: relative;\n display: flex;\n height: var(--spacing-10, 40px);\n align-items: center;\n justify-content: center;\n border-bottom: var(--stroke-width-thin) solid var(--hairline, rgb(255 255 255 / var(--opacity-5, 0.05)));\n padding-left: var(--spacing-3, 12px);\n padding-right: var(--spacing-3, 12px);\n}\n\n/* Traffic-light dots cluster */\n.dots {\n position: absolute;\n left: var(--spacing-3_5, 14px);\n display: flex;\n align-items: center;\n gap: var(--spacing-1, 4px);\n}\n\n.dot {\n display: block;\n height: 10px;\n width: 10px;\n border-radius: 50%;\n background-color: rgb(255 255 255 / var(--opacity-12, 0.12));\n}\n\n/* URL pill (non-linked) */\n.pill {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-1, 4px);\n border-radius: var(--radius-md, 0.375rem);\n background-color: rgb(255 255 255 / var(--opacity-5, 0.05));\n padding-left: var(--spacing-3, 12px);\n padding-right: var(--spacing-3, 12px);\n padding-top: var(--spacing-1, 4px);\n padding-bottom: var(--spacing-1, 4px);\n font-size: 0.6875rem; /* 11px — intentional: mockup chrome reads at near-system scale */\n font-weight: var(--font-weight-light, 300);\n letter-spacing: 0.025em;\n color: rgb(255 255 255 / var(--opacity-60, 0.60));\n}\n\n.pillIcon {\n /* Phosphor icon inherits currentColor; the lock stays on the text baseline */\n flex-shrink: 0;\n}\n\n/* URL pill as a link */\n.pillLink {\n text-decoration: none;\n color: inherit;\n transition: color var(--motion-duration-fast, 150ms) var(--motion-easing-standard, ease);\n}\n\n.pillLink:hover .pill {\n color: rgb(255 255 255 / var(--opacity-80, 0.80));\n}\n\n.pillLink:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--browser-frame-focus-color, var(--accent, currentColor));\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-md, 0.375rem);\n}\n"
5188
+ "content": "/*\n * BrowserFrame — browser-chrome mockup frame.\n *\n * Ported from Blacklight EpkFrame (VI-570). Elevation is kept OUT so consumers\n * can compose with the lit / lit-soft / lit-strong utilities from visor-core/utilities.\n *\n * --browser-frame-bg: deep near-black background of the frame shell.\n * Override to use a theme surface token when the consumer has one.\n *\n * --browser-frame-focus-color: focus outline color on the URL pill link.\n * Defaults to var(--accent, currentColor) so Blacklight can bind its\n * keyed per-artist accent by setting this on the frame element.\n */\n\n.frame {\n overflow: hidden;\n border-radius: var(--radius-xl, 1rem);\n border: var(--stroke-width-thin, 1px) solid transparent;\n outline: var(--hairline-width, var(--stroke-width-thin, 1px)) solid var(--hairline-strong, rgb(255 255 255 / var(--opacity-10, 0.10)));\n outline-offset: calc(-1 * var(--hairline-width, var(--stroke-width-thin, 1px)));\n background-color: var(--browser-frame-bg, #0a0a0b);\n}\n\n/* Chrome bar */\n.chrome {\n position: relative;\n display: flex;\n height: var(--spacing-10, 40px);\n align-items: center;\n justify-content: center;\n border-bottom: var(--stroke-width-thin, 1px) solid transparent;\n border-image: linear-gradient(var(--hairline, rgb(255 255 255 / var(--opacity-5, 0.05))), var(--hairline, rgb(255 255 255 / var(--opacity-5, 0.05)))) 1 / 0 0 var(--hairline-width, var(--stroke-width-thin, 1px)) 0;\n padding-left: var(--spacing-3, 12px);\n padding-right: var(--spacing-3, 12px);\n}\n\n/* Traffic-light dots cluster */\n.dots {\n position: absolute;\n left: var(--spacing-3_5, 14px);\n display: flex;\n align-items: center;\n gap: var(--spacing-1, 4px);\n}\n\n.dot {\n display: block;\n height: 10px;\n width: 10px;\n border-radius: 50%;\n background-color: rgb(255 255 255 / var(--opacity-12, 0.12));\n}\n\n/* URL pill (non-linked) */\n.pill {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-1, 4px);\n border-radius: var(--radius-md, 0.375rem);\n background-color: rgb(255 255 255 / var(--opacity-5, 0.05));\n padding-left: var(--spacing-3, 12px);\n padding-right: var(--spacing-3, 12px);\n padding-top: var(--spacing-1, 4px);\n padding-bottom: var(--spacing-1, 4px);\n font-size: 0.6875rem; /* 11px — intentional: mockup chrome reads at near-system scale */\n font-weight: var(--font-weight-light, 300);\n letter-spacing: 0.025em;\n color: rgb(255 255 255 / var(--opacity-60, 0.60));\n}\n\n.pillIcon {\n /* Phosphor icon inherits currentColor; the lock stays on the text baseline */\n flex-shrink: 0;\n}\n\n/* URL pill as a link */\n.pillLink {\n text-decoration: none;\n color: inherit;\n transition: color var(--motion-duration-fast, 150ms) var(--motion-easing-standard, ease);\n}\n\n.pillLink:hover .pill {\n color: rgb(255 255 255 / var(--opacity-80, 0.80));\n}\n\n.pillLink:focus-visible {\n outline: var(--focus-ring-width, 2px) solid var(--browser-frame-focus-color, var(--accent, currentColor));\n outline-offset: var(--focus-ring-offset, 2px);\n border-radius: var(--radius-md, 0.375rem);\n}\n"
5189
5189
  }
5190
5190
  ]
5191
5191
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.4.0",
3
- "generated_at": "2026-09-24T04:59:55.138Z",
3
+ "generated_at": "2026-10-03T18:10:41.404Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "category": "specimen",
@@ -995,6 +995,7 @@
995
995
  "--font-size-sm",
996
996
  "--font-weight-medium",
997
997
  "--hairline",
998
+ "--hairline-width",
998
999
  "--line-height-tight",
999
1000
  "--motion-duration-moderate",
1000
1001
  "--motion-easing-standard",
@@ -1155,6 +1156,7 @@
1155
1156
  "--interactive-secondary-bg",
1156
1157
  "--interactive-secondary-bg-hover",
1157
1158
  "--interactive-secondary-text",
1159
+ "--label-box-height",
1158
1160
  "--motion-duration-150",
1159
1161
  "--motion-easing-default",
1160
1162
  "--opacity-40",
@@ -2073,6 +2075,7 @@
2073
2075
  "--font-weight-bold",
2074
2076
  "--font-weight-extrabold",
2075
2077
  "--hairline",
2078
+ "--hairline-width",
2076
2079
  "--motion-duration-150",
2077
2080
  "--motion-easing-default",
2078
2081
  "--radius-full",
@@ -3649,6 +3652,7 @@
3649
3652
  "--font-weight-medium",
3650
3653
  "--hairline",
3651
3654
  "--hairline-strong",
3655
+ "--hairline-width",
3652
3656
  "--motion-duration-fast",
3653
3657
  "--motion-easing-standard",
3654
3658
  "--primary",
@@ -3744,6 +3748,7 @@
3744
3748
  "--font-weight-bold",
3745
3749
  "--font-weight-medium",
3746
3750
  "--hairline",
3751
+ "--hairline-width",
3747
3752
  "--info",
3748
3753
  "--motion-duration-fast",
3749
3754
  "--motion-easing-standard",
@@ -3871,6 +3876,7 @@
3871
3876
  "--font-size-sm",
3872
3877
  "--font-size-xs",
3873
3878
  "--hairline",
3879
+ "--hairline-width",
3874
3880
  "--motion-duration-fast",
3875
3881
  "--motion-easing-default",
3876
3882
  "--motion-easing-enter",
@@ -3975,6 +3981,7 @@
3975
3981
  "--font-size-sm",
3976
3982
  "--font-size-xs",
3977
3983
  "--hairline",
3984
+ "--hairline-width",
3978
3985
  "--interactive-primary-bg",
3979
3986
  "--interactive-primary-soft",
3980
3987
  "--line-height-snug",
@@ -4355,6 +4362,7 @@
4355
4362
  "--font-weight-bold",
4356
4363
  "--font-weight-medium",
4357
4364
  "--hairline",
4365
+ "--hairline-width",
4358
4366
  "--info",
4359
4367
  "--motion-duration-fast",
4360
4368
  "--motion-easing-standard",
@@ -5293,6 +5301,7 @@
5293
5301
  "tokens_used": [
5294
5302
  "--border-default",
5295
5303
  "--hairline",
5304
+ "--hairline-width",
5296
5305
  "--infographic-bar-divider",
5297
5306
  "--radius-lg",
5298
5307
  "--shadow-xs",
@@ -6959,6 +6968,7 @@
6959
6968
  "--font-size-sm",
6960
6969
  "--font-size-xs",
6961
6970
  "--hairline-strong",
6971
+ "--hairline-width",
6962
6972
  "--interactive-primary-text",
6963
6973
  "--motion-duration-fast",
6964
6974
  "--motion-easing-default",
@@ -7647,6 +7657,7 @@
7647
7657
  "--font-size-sm",
7648
7658
  "--font-weight-medium",
7649
7659
  "--hairline",
7660
+ "--hairline-width",
7650
7661
  "--motion-duration-fast",
7651
7662
  "--motion-easing-standard",
7652
7663
  "--primary",
@@ -8646,6 +8657,7 @@
8646
8657
  ],
8647
8658
  "tokens_used": [
8648
8659
  "--hairline",
8660
+ "--hairline-width",
8649
8661
  "--radius-lg",
8650
8662
  "--shadow-sm",
8651
8663
  "--spacing-1",
@@ -9431,6 +9443,7 @@
9431
9443
  "--font-weight-medium",
9432
9444
  "--font-weight-semibold",
9433
9445
  "--hairline",
9446
+ "--hairline-width",
9434
9447
  "--radius-md",
9435
9448
  "--radius-xl",
9436
9449
  "--shadow-sm",
@@ -9721,6 +9734,7 @@
9721
9734
  "--font-size-sm",
9722
9735
  "--font-weight-medium",
9723
9736
  "--hairline",
9737
+ "--hairline-width",
9724
9738
  "--motion-duration-150",
9725
9739
  "--motion-easing-default",
9726
9740
  "--radius-lg",
@@ -9840,6 +9854,7 @@
9840
9854
  "--font-size-xs",
9841
9855
  "--font-weight-medium",
9842
9856
  "--hairline",
9857
+ "--hairline-width",
9843
9858
  "--motion-duration-150",
9844
9859
  "--motion-duration-normal",
9845
9860
  "--motion-easing-default",
@@ -10651,6 +10666,12 @@
10651
10666
  "type": "number",
10652
10667
  "default": "4",
10653
10668
  "description": "Distance in pixels between trigger and tooltip"
10669
+ },
10670
+ {
10671
+ "name": "container",
10672
+ "type": "HTMLElement | null",
10673
+ "default": "document.body",
10674
+ "description": "Element TooltipContent portals into. Pass the root of a class-scoped theme so the tooltip reads that theme's tokens instead of the page's.\n"
10654
10675
  }
10655
10676
  ],
10656
10677
  "sub_components": [
@@ -10668,7 +10689,7 @@
10668
10689
  },
10669
10690
  {
10670
10691
  "name": "TooltipContent",
10671
- "description": "The floating label rendered in a portal."
10692
+ "description": "The floating label rendered in a portal (document.body unless `container` is set)."
10672
10693
  }
10673
10694
  ],
10674
10695
  "dependencies": [
@@ -10683,7 +10704,9 @@
10683
10704
  "--spacing-1",
10684
10705
  "--spacing-3",
10685
10706
  "--surface-overlay",
10686
- "--text-inverse"
10707
+ "--text-inverse",
10708
+ "--tooltip-bg",
10709
+ "--tooltip-text"
10687
10710
  ],
10688
10711
  "example": "<TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant=\"ghost\" size=\"sm\"><TrashIcon /></Button>\n </TooltipTrigger>\n <TooltipContent>Delete item</TooltipContent>\n </Tooltip>\n</TooltipProvider>\n"
10689
10712
  },
@@ -11462,6 +11485,7 @@
11462
11485
  "--font-weight-light",
11463
11486
  "--hairline",
11464
11487
  "--hairline-strong",
11488
+ "--hairline-width",
11465
11489
  "--motion-duration-fast",
11466
11490
  "--motion-easing-standard",
11467
11491
  "--opacity-10",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "CLI for the Visor design system — add components, hooks, and utilities to your project.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,7 +46,7 @@
46
46
  "dependencies": {
47
47
  "@aws-sdk/client-s3": "^3.1021.0",
48
48
  "@babel/parser": "^7.26.0",
49
- "@loworbitstudio/visor-theme-engine": "^0.23.0",
49
+ "@loworbitstudio/visor-theme-engine": "^0.24.0",
50
50
  "commander": "^13.1.0",
51
51
  "diff": "^8.0.4",
52
52
  "picocolors": "^1.1.1",