{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "context-meter",
  "title": "Context meter",
  "description": "Context meter is a production-ready ai component from BJORK UI, packaged for shadcn registries.",
  "dependencies": [
    "clsx",
    "framer-motion",
    "next-themes",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "components/bjork-ui/ai/context-meter.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport { LiveRegion } from \"@/components/bjork-ui/_core/a11y\";\nimport { easeCss } from \"@/components/bjork-ui/_core/motion\";\nimport {\n  AI_PANEL,\n  FOCUS_RING,\n  PRESS,\n  formatTokens,\n  useAiTone,\n  useControllable,\n  useHydrated,\n  useReduceMotion,\n  type BjorkTone,\n} from \"@/components/bjork-ui/ai/_shared\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface ContextSegment {\n  id: string;\n  label: string;\n  tokens: number;\n  /** Space held back rather than spent, such as room for the reply. Drawn hatched. */\n  reserved?: boolean;\n  /** Override the fill. Defaults to the next tonal step of the accent. */\n  color?: string;\n}\n\nexport type ContextLevel = \"ok\" | \"warn\" | \"danger\";\n\nexport interface ContextMeterProps {\n  segments: ContextSegment[];\n  /** Context window size in tokens. */\n  limit: number;\n  /** \"full\" draws the bar and legend. \"compact\" is a ring and number that opens the breakdown in a popover. */\n  variant?: \"full\" | \"compact\";\n  /** Fraction of the limit where the meter turns amber. Default 0.75. */\n  warnAt?: number;\n  /** Fraction of the limit where the meter turns red. Default 0.9. */\n  dangerAt?: number;\n  /** Header label. Default \"Context\". */\n  label?: string;\n  /** Compact only: controlled popover state. */\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  /** Compact only: which side the popover prefers. It flips when there is no room. Default \"top\". */\n  side?: \"top\" | \"bottom\";\n  /** Which edge of the trigger the popover lines up with. \"end\" suits a trigger at the right of a footer. Default \"end\". */\n  align?: \"start\" | \"end\";\n  tone?: BjorkTone;\n  className?: string;\n}\n\nexport const SAMPLE_CONTEXT: ContextSegment[] = [\n  { id: \"system\", label: \"System prompt\", tokens: 6_800 },\n  { id: \"conversation\", label: \"Conversation\", tokens: 71_400 },\n  { id: \"files\", label: \"Attached files\", tokens: 46_200 },\n  { id: \"tools\", label: \"Tool results\", tokens: 15_600 },\n  { id: \"reserved\", label: \"Reserved for output\", tokens: 16_000, reserved: true },\n];\n\n// Tonal steps of the accent, then neutrals. Never a rainbow: every fill reads as the same family.\nconst STEPS = [\n  \"var(--bjork-text-medium)\",\n  \"var(--bjork-accent)\",\n  \"color-mix(in srgb, var(--bjork-accent) 58%, transparent)\",\n  \"color-mix(in srgb, var(--bjork-accent) 32%, transparent)\",\n  \"var(--bjork-text-soft)\",\n  \"var(--bjork-text-faint)\",\n];\nconst HATCH = \"repeating-linear-gradient(-45deg, var(--bjork-text-soft) 0 1.5px, transparent 1.5px 4px)\";\n\nconst LEVEL_WORD: Record<ContextLevel, string> = { ok: \"\", warn: \"Getting full\", danger: \"Will summarize soon\" };\nconst LEVEL_COLOR: Record<ContextLevel, string> = {\n  ok: \"var(--bjork-text-muted)\",\n  warn: \"var(--bjork-warning)\",\n  danger: \"var(--bjork-error)\",\n};\n\nfunction fillOf(seg: ContextSegment, i: number): string {\n  if (seg.reserved) return HATCH;\n  return seg.color ?? STEPS[i % STEPS.length];\n}\n\nfunction pct(n: number): string {\n  if (n > 0 && n < 0.01) return \"<1%\";\n  return `${Math.round(n * 100)}%`;\n}\n\nfunction levelOf(ratio: number, warnAt: number, dangerAt: number): ContextLevel {\n  if (ratio >= dangerAt) return \"danger\";\n  if (ratio >= warnAt) return \"warn\";\n  return \"ok\";\n}\n\n/**\n * How much of the context window a conversation is using, split by what is using it. The bar and legend are\n * linked: pointing at either highlights the same category. Crossing the warn and danger lines is announced.\n */\nexport function ContextMeter({\n  segments,\n  limit,\n  variant = \"full\",\n  warnAt = 0.75,\n  dangerAt = 0.9,\n  label = \"Context\",\n  open,\n  defaultOpen = false,\n  onOpenChange,\n  side = \"top\",\n  align = \"end\",\n  tone: toneProp,\n  className,\n}: ContextMeterProps) {\n  const { pal, style } = useAiTone(toneProp);\n  const used = segments.reduce((sum, s) => sum + Math.max(0, s.tokens), 0);\n  const ratio = limit > 0 ? used / limit : 0;\n  const level = levelOf(ratio, warnAt, dangerAt);\n\n  // Announce threshold transitions only, derived during render.\n  const [announce, setAnnounce] = useState({ level, message: \"\" });\n  if (announce.level !== level) {\n    const message =\n      level === \"ok\" ? \"Context usage back under the limit\" : `Context ${pct(ratio)} used. ${LEVEL_WORD[level]}`;\n    setAnnounce({ level, message });\n  }\n\n  const shared = { segments, limit, used, ratio, level, warnAt, dangerAt, label };\n\n  return (\n    <div\n      className={cn(\n        \"relative font-bjork-alpha text-[color:var(--bjork-text)]\",\n        // Size containment would collapse the inline compact trigger to zero width, so only the full meter is a container.\n        variant === \"full\" ? cn(AI_PANEL, \"@container w-full max-w-[520px]\") : \"inline-flex shrink-0\",\n        className,\n      )}\n      style={style}\n    >\n      {variant === \"full\" ? (\n        <MeterBody {...shared} />\n      ) : (\n        <CompactMeter\n          {...shared}\n          open={open}\n          defaultOpen={defaultOpen}\n          onOpenChange={onOpenChange}\n          side={side}\n          align={align}\n          ringTrack={pal.border}\n        />\n      )}\n      <LiveRegion message={announce.message} />\n    </div>\n  );\n}\n\ninterface BodyProps {\n  segments: ContextSegment[];\n  limit: number;\n  used: number;\n  ratio: number;\n  level: ContextLevel;\n  warnAt: number;\n  dangerAt: number;\n  label: string;\n}\n\nfunction MeterBody({ segments, limit, used, ratio, level, warnAt, dangerAt, label }: BodyProps) {\n  const reduce = useReduceMotion();\n  const hydrated = useHydrated();\n  const [hot, setHot] = useState<string | null>(null);\n  const [pinned, setPinned] = useState<string | null>(null);\n  const active = hot ?? pinned;\n  const labelId = useId();\n\n  const free = Math.max(0, limit - used);\n  const scaleBase = Math.max(limit, used);\n  // Each segment is a full-width layer that is scaled and shifted, so value changes animate on the compositor.\n  const starts = segments.reduce<number[]>(\n    (acc, seg, i) => [...acc, i === 0 ? 0 : acc[i - 1] + Math.max(0, segments[i - 1].tokens)],\n    [],\n  );\n  const layers = segments.map((seg, i) => ({\n    seg,\n    i,\n    start: starts[i] / scaleBase,\n    frac: Math.max(0, seg.tokens) / scaleBase,\n  }));\n  const transition = reduce ? \"none\" : `transform 520ms ${easeCss.out}, opacity 160ms ease-out`;\n  const levelColor = LEVEL_COLOR[level];\n  const valueText = `${used.toLocaleString(\"en-US\")} of ${limit.toLocaleString(\"en-US\")} tokens, ${pct(ratio)}${\n    level === \"ok\" ? \"\" : `. ${LEVEL_WORD[level]}`\n  }`;\n\n  return (\n    <div className=\"w-full\">\n      <div className=\"flex min-w-0 items-baseline justify-between gap-3 pb-2.5\">\n        <div className=\"flex min-w-0 items-baseline gap-2\">\n          <span id={labelId} className=\"text-[14px] font-semibold leading-5\">\n            {label}\n          </span>\n          <span\n            className=\"truncate font-mono text-[10px] uppercase leading-5 tracking-[0.08em] transition-colors duration-200\"\n            style={{ color: levelColor }}\n          >\n            {level === \"ok\" ? \"\" : LEVEL_WORD[level]}\n          </span>\n        </div>\n        <span className=\"shrink-0 whitespace-nowrap font-mono text-[12px] leading-5 tabular-nums\">\n          <span style={{ color: level === \"ok\" ? \"var(--bjork-text)\" : levelColor }}>{formatTokens(used)}</span>\n          <span className=\"text-[color:var(--bjork-text-faint)]\"> / {formatTokens(limit)}</span>\n          <span className=\"ml-2 inline-block min-w-[4ch] text-right text-[color:var(--bjork-text-muted)]\">\n            {pct(ratio)}\n          </span>\n        </span>\n      </div>\n\n      <div\n        role=\"meter\"\n        aria-labelledby={labelId}\n        aria-valuemin={0}\n        aria-valuemax={limit}\n        aria-valuenow={Math.min(used, limit)}\n        aria-valuetext={valueText}\n        className=\"relative\"\n        onPointerLeave={() => setHot(null)}\n      >\n        <div className=\"relative h-2.5 overflow-hidden rounded-full bg-[color:var(--bjork-hair)]\">\n          {layers.map(({ seg, i, start, frac }) => (\n            <div\n              key={seg.id}\n              aria-hidden=\"true\"\n              data-segment={seg.id}\n              onPointerEnter={() => setHot(seg.id)}\n              className=\"absolute inset-0 origin-left\"\n              style={{\n                transform: `translateX(${(hydrated ? start : 0) * 100}%) scaleX(${hydrated ? frac : 0})`,\n                background: fillOf(seg, i),\n                opacity: active && active !== seg.id ? 0.28 : 1,\n                transition,\n              }}\n            />\n          ))}\n          {/* 2px surface-coloured cuts between segments, moved with transforms like the fills. */}\n          {layers.slice(1).map(({ seg, start }) => (\n            <div\n              key={`cut-${seg.id}`}\n              aria-hidden=\"true\"\n              className=\"pointer-events-none absolute inset-0\"\n              style={{ transform: `translateX(${(hydrated ? start : 0) * 100}%)`, transition }}\n            >\n              <span className=\"absolute inset-y-0 left-0 w-0.5 -translate-x-1/2 bg-[color:var(--bjork-ring-offset)]\" />\n            </div>\n          ))}\n        </div>\n        {/* Threshold ticks under the bar. */}\n        <div aria-hidden=\"true\" className=\"relative h-2\">\n          {[warnAt, dangerAt].map((t, i) => (\n            <span\n              key={t}\n              className=\"absolute top-1 h-1.5 w-px -translate-x-1/2\"\n              style={{\n                left: `${Math.min(1, t) * 100}%`,\n                background: i === 0 ? \"var(--bjork-warning)\" : \"var(--bjork-error)\",\n                opacity: level === \"ok\" ? 0.45 : 0.9,\n              }}\n            />\n          ))}\n        </div>\n      </div>\n\n      <ul className=\"mt-1.5 flex flex-col\" aria-label={`${label} breakdown`}>\n        {layers.map(({ seg, i }) => {\n          const on = active === seg.id;\n          return (\n            <li key={seg.id}>\n              <button\n                type=\"button\"\n                aria-pressed={pinned === seg.id}\n                onPointerEnter={() => setHot(seg.id)}\n                onPointerLeave={() => setHot(null)}\n                onFocus={() => setHot(seg.id)}\n                onBlur={() => setHot(null)}\n                onClick={() => setPinned((p) => (p === seg.id ? null : seg.id))}\n                className={cn(\n                  \"group flex h-8 w-full cursor-pointer items-center gap-2.5 rounded-[7px] px-2 text-left transition-[background-color,opacity] duration-150\",\n                  FOCUS_RING,\n                  on ? \"bg-[color:var(--bjork-surface-active)]\" : \"hover:bg-[color:var(--bjork-surface-active)]\",\n                  active && !on && \"opacity-55\",\n                )}\n              >\n                <span\n                  aria-hidden=\"true\"\n                  className=\"size-2.5 shrink-0 rounded-[3px]\"\n                  style={{\n                    background: fillOf(seg, i),\n                    boxShadow: seg.reserved ? \"inset 0 0 0 1px var(--bjork-text-faint)\" : undefined,\n                  }}\n                />\n                <span className=\"min-w-0 flex-1 truncate text-[13px] leading-5 text-[color:var(--bjork-text-medium)] group-aria-pressed:text-[color:var(--bjork-text)]\">\n                  {seg.label}\n                </span>\n                <span className=\"min-w-[6ch] shrink-0 text-right font-mono text-[12px] leading-5 tabular-nums text-[color:var(--bjork-text)]\">\n                  {formatTokens(seg.tokens)}\n                </span>\n                <span className=\"min-w-[4ch] shrink-0 text-right font-mono text-[11px] leading-5 tabular-nums text-[color:var(--bjork-text-faint)]\">\n                  {pct(seg.tokens / Math.max(1, limit))}\n                </span>\n              </button>\n            </li>\n          );\n        })}\n        <li className=\"mt-1 flex h-8 items-center gap-2.5 border-t border-[color:var(--bjork-border)] px-2 pt-1\">\n          <span aria-hidden=\"true\" className=\"size-2.5 shrink-0 rounded-[3px] bg-[color:var(--bjork-hair)]\" />\n          <span className=\"min-w-0 flex-1 truncate text-[13px] leading-5 text-[color:var(--bjork-text-muted)]\">\n            Free\n          </span>\n          <span className=\"min-w-[6ch] shrink-0 text-right font-mono text-[12px] leading-5 tabular-nums text-[color:var(--bjork-text-muted)]\">\n            {formatTokens(free)}\n          </span>\n          <span className=\"min-w-[4ch] shrink-0 text-right font-mono text-[11px] leading-5 tabular-nums text-[color:var(--bjork-text-faint)]\">\n            {pct(free / Math.max(1, limit))}\n          </span>\n        </li>\n      </ul>\n    </div>\n  );\n}\n\ninterface CompactProps extends BodyProps {\n  open?: boolean;\n  defaultOpen: boolean;\n  onOpenChange?: (open: boolean) => void;\n  side: \"top\" | \"bottom\";\n  align: \"start\" | \"end\";\n  ringTrack: string;\n}\n\nconst RING = 18;\nconst RING_STROKE = 2;\n\nfunction CompactMeter({ open, defaultOpen, onOpenChange, side, align, ringTrack, ...body }: CompactProps) {\n  const reduce = useReduceMotion();\n  const hydrated = useHydrated();\n  const [isOpen, setOpen] = useControllable(open, defaultOpen, onOpenChange);\n  const [placement, setPlacement] = useState<\"top\" | \"bottom\">(side);\n  const wrapRef = useRef<HTMLDivElement>(null);\n  const triggerRef = useRef<HTMLButtonElement>(null);\n  const panelId = useId();\n\n  const r = (RING - RING_STROKE) / 2;\n  const c = 2 * Math.PI * r;\n  const shown = Math.min(1, body.ratio);\n  const color = body.level === \"ok\" ? \"var(--bjork-accent)\" : LEVEL_COLOR[body.level];\n\n  const toggle = () => {\n    if (!isOpen) {\n      // Flip to the other side when the preferred one has no room for a ~360px panel.\n      const rect = triggerRef.current?.getBoundingClientRect();\n      if (rect) {\n        const roomTop = rect.top;\n        const roomBottom = window.innerHeight - rect.bottom;\n        const cramped = side === \"top\" ? roomTop < 360 && roomBottom > roomTop : roomBottom < 360 && roomTop > roomBottom;\n        setPlacement(cramped ? (side === \"top\" ? \"bottom\" : \"top\") : side);\n      }\n    }\n    setOpen(!isOpen);\n  };\n\n  useEffect(() => {\n    if (!isOpen) return;\n    const onDown = (e: PointerEvent) => {\n      if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) setOpen(false);\n    };\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key !== \"Escape\") return;\n      setOpen(false);\n      triggerRef.current?.focus();\n    };\n    document.addEventListener(\"pointerdown\", onDown);\n    document.addEventListener(\"keydown\", onKey);\n    return () => {\n      document.removeEventListener(\"pointerdown\", onDown);\n      document.removeEventListener(\"keydown\", onKey);\n    };\n  }, [isOpen, setOpen]);\n\n  return (\n    <div ref={wrapRef} className=\"relative\">\n      <button\n        ref={triggerRef}\n        type=\"button\"\n        aria-expanded={isOpen}\n        aria-controls={panelId}\n        aria-haspopup=\"dialog\"\n        aria-label={`${body.label}: ${formatTokens(body.used)} of ${formatTokens(body.limit)} tokens, ${pct(body.ratio)}`}\n        onClick={toggle}\n        className={cn(\n          \"flex h-7 cursor-pointer items-center gap-1.5 rounded-full pl-1.5 pr-2.5 text-[color:var(--bjork-text-muted)] transition-colors duration-150 hover:bg-[color:var(--bjork-surface-active)] hover:text-[color:var(--bjork-text)]\",\n          isOpen && \"bg-[color:var(--bjork-surface-active)] text-[color:var(--bjork-text)]\",\n          FOCUS_RING,\n          PRESS,\n        )}\n      >\n        <svg width={RING} height={RING} viewBox={`0 0 ${RING} ${RING}`} aria-hidden=\"true\" className=\"-rotate-90\">\n          <circle cx={RING / 2} cy={RING / 2} r={r} fill=\"none\" stroke={ringTrack} strokeWidth={RING_STROKE} />\n          <circle\n            cx={RING / 2}\n            cy={RING / 2}\n            r={r}\n            fill=\"none\"\n            stroke={color}\n            strokeWidth={RING_STROKE}\n            strokeLinecap=\"round\"\n            strokeDasharray={c}\n            strokeDashoffset={c * (1 - (hydrated ? shown : 0))}\n            style={{ transition: reduce ? \"none\" : `stroke-dashoffset 520ms ${easeCss.out}, stroke 200ms ease-out` }}\n          />\n        </svg>\n        <span className=\"min-w-[4ch] font-mono text-[11px] leading-4 tabular-nums\" style={{ color: body.level === \"ok\" ? undefined : color }}>\n          {pct(body.ratio)}\n        </span>\n      </button>\n\n      <div\n        id={panelId}\n        role=\"dialog\"\n        aria-label={`${body.label} usage`}\n        hidden={!isOpen}\n        className={cn(\n          \"absolute z-30 w-[min(320px,calc(100vw-32px))] rounded-[12px] border border-[color:var(--bjork-border)] bg-[color:var(--bjork-menu)] p-3 pt-2.5 shadow-[var(--bjork-shadow-menu)] backdrop-blur-md\",\n          align === \"end\" ? \"right-0\" : \"left-0\",\n          placement === \"top\" ? \"bottom-full mb-2\" : \"top-full mt-2\",\n          placement === \"top\"\n            ? align === \"end\" ? \"origin-bottom-right\" : \"origin-bottom-left\"\n            : align === \"end\" ? \"origin-top-right\" : \"origin-top-left\",\n          !reduce && \"motion-safe:animate-[bjork-ctx-pop_180ms_cubic-bezier(0.23,1,0.32,1)]\",\n        )}\n      >\n        <style href=\"bjork-context-meter-pop\" precedence=\"default\">\n          {\"@keyframes bjork-ctx-pop{from{opacity:0;transform:scale(0.96)}to{opacity:1;transform:none}}\"}\n        </style>\n        {isOpen && <MeterBody {...body} />}\n      </div>\n    </div>\n  );\n}\n\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/ai/context-meter.tsx"
    },
    {
      "path": "components/bjork-ui/_core/a11y.tsx",
      "content": "import type { ReactNode } from \"react\";\n\n// Text for assistive tech only.\nexport function VisuallyHidden({ children }: { children: ReactNode }) {\n  return <span className=\"sr-only\">{children}</span>;\n}\n\n// Announces `message` when it changes. Same text re-rendered does not re-announce.\nexport function LiveRegion({\n  message,\n  politeness = \"polite\",\n}: {\n  message: string;\n  politeness?: \"polite\" | \"assertive\";\n}) {\n  return (\n    <span role=\"status\" aria-live={politeness} aria-atomic=\"true\" className=\"sr-only\">\n      {message}\n    </span>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/_core/a11y.tsx"
    },
    {
      "path": "components/bjork-ui/_core/motion.ts",
      "content": "// Shared springs, easings and durations. Components import these instead of inventing values.\n\nexport const springs = {\n  press: { type: \"spring\", stiffness: 500, damping: 30, mass: 0.6 },\n  snappy: { type: \"spring\", stiffness: 400, damping: 25, mass: 1 },\n  standard: { type: \"spring\", stiffness: 300, damping: 30, mass: 0.8 },\n  soft: { type: \"spring\", stiffness: 200, damping: 25, mass: 1.2 },\n  settle: { type: \"spring\", stiffness: 380, damping: 30, mass: 0.8 },\n  blurIn: { type: \"spring\", stiffness: 400, damping: 28, mass: 0.6 },\n} as const;\n\nexport const ease = {\n  out: [0.23, 1, 0.32, 1],\n  inOut: [0.77, 0, 0.175, 1],\n  drawer: [0.32, 0.72, 0, 1],\n} as const;\n\nexport const easeCss = {\n  out: \"cubic-bezier(0.23,1,0.32,1)\",\n  inOut: \"cubic-bezier(0.77,0,0.175,1)\",\n  drawer: \"cubic-bezier(0.32,0.72,0,1)\",\n} as const;\n\nexport const durations = { press: 140, tooltip: 160, menu: 200, panel: 280 } as const; // ms\n\n// Cubic bezier easing for canvas and rAF code. Returns progress y for input x in [0, 1].\nexport function cubicBezier(x1: number, y1: number, x2: number, y2: number): (t: number) => number {\n  const cx = 3 * x1;\n  const bx = 3 * (x2 - x1) - cx;\n  const ax = 1 - cx - bx;\n  const cy = 3 * y1;\n  const by = 3 * (y2 - y1) - cy;\n  const ay = 1 - cy - by;\n  const sampleX = (s: number) => ((ax * s + bx) * s + cx) * s;\n  const sampleY = (s: number) => ((ay * s + by) * s + cy) * s;\n  const sampleDX = (s: number) => (3 * ax * s + 2 * bx) * s + cx;\n\n  const solveX = (x: number) => {\n    let s = x;\n    for (let i = 0; i < 8; i++) {\n      const err = sampleX(s) - x;\n      if (Math.abs(err) < 1e-6) return s;\n      const d = sampleDX(s);\n      if (Math.abs(d) < 1e-6) break;\n      s -= err / d;\n    }\n    let lo = 0;\n    let hi = 1;\n    s = x;\n    while (lo < hi) {\n      const v = sampleX(s);\n      if (Math.abs(v - x) < 1e-6) return s;\n      if (x > v) lo = s;\n      else hi = s;\n      s = (lo + hi) / 2;\n      if (hi - lo < 1e-7) break;\n    }\n    return s;\n  };\n\n  return (t: number) => {\n    if (t <= 0) return 0;\n    if (t >= 1) return 1;\n    return sampleY(solveX(t));\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/_core/motion.ts"
    },
    {
      "path": "components/bjork-ui/ai/_shared.ts",
      "content": "\"use client\";\n\n// Shared plumbing for the bjork-ui AI components: tone tokens, reduced motion, controllable state and\n// number formatting. Every AI component imports from here so they read as one family.\n\nimport {\n  useCallback,\n  useLayoutEffect,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type CSSProperties,\n} from \"react\";\nimport { useReducedMotion } from \"framer-motion\";\nimport { BJORK_PALETTE, type BjorkTone } from \"@/components/bjork-ui/_core/palette\";\nimport { useBjorkTone } from \"@/components/bjork-ui/_core/tone\";\nimport { surfaceVars } from \"@/components/bjork-ui/_core/surface\";\n\nexport type { BjorkTone };\nexport type BjorkPalette = (typeof BJORK_PALETTE)[BjorkTone];\n\nconst noopSubscribe = () => () => {};\n\n/** False on the server and the first client render, true after hydration. */\nexport function useHydrated(): boolean {\n  return useSyncExternalStore(\n    noopSubscribe,\n    () => true,\n    () => false,\n  );\n}\n\n/** Hydration-safe reduced motion: both sides report \"not reduced\" until mount, then the real value applies. */\nexport function useReduceMotion(): boolean {\n  const hydrated = useHydrated();\n  const reduced = useReducedMotion();\n  return hydrated && reduced === true;\n}\n\n/**\n * Resolves the tone and returns the CSS custom properties for it. Spread `style` on the component root so an\n * explicit `tone` prop wins over the page theme, and status colours (success, warning, error) are always defined.\n */\nexport function useAiTone(toneProp?: BjorkTone) {\n  const tone = useBjorkTone(toneProp);\n  const pal = BJORK_PALETTE[tone];\n  const style = {\n    ...surfaceVars(tone),\n    \"--bjork-text\": pal.text,\n    \"--bjork-text-medium\": pal.textMedium,\n    \"--bjork-text-muted\": pal.textMuted,\n    \"--bjork-text-soft\": pal.textSoft,\n    \"--bjork-text-faint\": pal.textFaint,\n    \"--bjork-hair\": pal.hair,\n    \"--bjork-border\": pal.border,\n    \"--bjork-border-strong\": pal.borderStrong,\n    \"--bjork-surface\": pal.surface,\n    \"--bjork-raised\": pal.raised,\n    \"--bjork-surface-active\": pal.active,\n    \"--bjork-accent\": pal.accent,\n    \"--bjork-accent-ink\": pal.accentInk,\n    \"--bjork-accent-fill\": pal.accentFill,\n    \"--bjork-accent-soft\": pal.accentSoft,\n    \"--bjork-accent-muted\": pal.accentMuted,\n    \"--bjork-accent-foreground\": pal.accentFg,\n    \"--bjork-success\": pal.success,\n    \"--bjork-warning\": pal.warning,\n    \"--bjork-error\": pal.error,\n    \"--bjork-ring-offset\": pal.bg,\n    colorScheme: tone,\n  } as CSSProperties;\n  return { tone, pal, style };\n}\n\n/** Classes for the shared keyboard focus ring. */\nexport const FOCUS_RING =\n  \"outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--bjork-accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--bjork-ring-offset)]\";\n\n/** Classes for the shared press feedback. */\nexport const PRESS = \"transition-transform duration-150 ease-out active:scale-[0.97] motion-reduce:transition-none\";\n\n/**\n * Controlled-or-uncontrolled state. Pass `value` to control it; `onChange` fires for every change either way.\n */\nexport function useControllable<T>(\n  value: T | undefined,\n  defaultValue: T,\n  onChange?: (next: T) => void,\n): [T, (next: T) => void] {\n  const controlled = value !== undefined;\n  const [inner, setInner] = useState(defaultValue);\n  const current = controlled ? value : inner;\n  const onChangeRef = useRef(onChange);\n  useLayoutEffect(() => {\n    onChangeRef.current = onChange;\n  });\n  const set = useCallback(\n    (next: T) => {\n      if (!controlled) setInner(next);\n      onChangeRef.current?.(next);\n    },\n    [controlled],\n  );\n  return [current, set];\n}\n\n/** 1234 -> \"1,234\", 18400 -> \"18.4k\", 1_200_000 -> \"1.2M\". */\nexport function formatTokens(n: number): string {\n  if (n < 10_000) return Math.round(n).toLocaleString(\"en-US\");\n  if (n < 1_000_000) return `${(n / 1000).toFixed(n < 100_000 ? 1 : 0).replace(/\\.0$/, \"\")}k`;\n  return `${(n / 1_000_000).toFixed(n < 10_000_000 ? 2 : 1).replace(/\\.?0+$/, \"\")}M`;\n}\n\n/** US dollars with sub-cent precision when it matters: 0.0042 -> \"$0.0042\", 12.5 -> \"$12.50\". */\nexport function formatCost(usd: number): string {\n  if (usd === 0) return \"$0.00\";\n  if (Math.abs(usd) < 0.01) return `$${usd.toFixed(4)}`;\n  if (Math.abs(usd) < 1) return `$${usd.toFixed(3)}`;\n  return `$${usd.toLocaleString(\"en-US\", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;\n}\n\n/** 640 -> \"640ms\", 4200 -> \"4.2s\", 95000 -> \"1m 35s\". */\nexport function formatDuration(ms: number): string {\n  if (ms < 1000) return `${Math.round(ms)}ms`;\n  if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;\n  const m = Math.floor(ms / 60_000);\n  const s = Math.round((ms % 60_000) / 1000);\n  return `${m}m ${s}s`;\n}\n\n/** 2_400_000 bytes -> \"2.4 MB\". */\nexport function formatBytes(bytes: number): string {\n  if (bytes < 1024) return `${bytes} B`;\n  const units = [\"KB\", \"MB\", \"GB\"];\n  let v = bytes / 1024;\n  let i = 0;\n  while (v >= 1024 && i < units.length - 1) {\n    v /= 1024;\n    i++;\n  }\n  return `${v.toFixed(v < 10 ? 1 : 0)} ${units[i]}`;\n}\n\n/** Shimmer keyframes for \"working\" text. Mount once with <style href=... precedence=\"default\">. */\nexport const SHIMMER_TEXT_CSS =\n  \"@keyframes bjork-ai-shimmer{from{background-position:100% 0}to{background-position:0% 0}}\" +\n  \".bjork-ai-shimmer{background-image:linear-gradient(90deg,var(--bjork-text-muted) 0%,var(--bjork-text-muted) 40%,var(--bjork-text) 50%,var(--bjork-text-muted) 60%,var(--bjork-text-muted) 100%);background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;animation:bjork-ai-shimmer 1.8s linear infinite}\" +\n  \"@media (prefers-reduced-motion: reduce){.bjork-ai-shimmer{animation:none;background:none;color:var(--bjork-text-muted)}}\";\n\n/** CANON §2. Panel = a component's own root surface; Card = a standalone card; Tile = a card inside a panel; Well = recessed code/field. */\nexport const AI_PANEL = \"rounded-[20px] border border-[color:var(--bjork-border-muted)] bg-[color:var(--bjork-surface)] p-4 shadow-[var(--bjork-shadow-surface)]\";\nexport const AI_CARD = \"rounded-[18px] border border-[color:var(--bjork-border-muted)] bg-[color:var(--bjork-surface)] shadow-[var(--bjork-shadow-surface)]\";\nexport const AI_TILE = \"rounded-[12px] border border-[color:var(--bjork-border-muted)] bg-[color:var(--bjork-surface)] shadow-[var(--bjork-shadow-surface)]\";\nexport const AI_WELL = \"rounded-[8px] border border-[color:var(--bjork-border)] bg-[color:var(--bjork-field-inset)] shadow-[var(--bjork-shadow-inset)]\";\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/ai/_shared.ts"
    },
    {
      "path": "components/bjork-ui/_core/palette.ts",
      "content": "export type BjorkTone = \"dark\" | \"light\";\n\n// Canvas, WebGL and SVG attribute colours. DOM code uses the CSS tokens with these as fallbacks.\nexport const BJORK_PALETTE = {\n  dark: {\n    bg: \"#050505\", stage: \"#111111\", surface: \"#121212\", raised: \"#161616\", active: \"#202020\",\n    border: \"#232323\", borderStrong: \"#343434\",\n    text: \"#ededed\", textMedium: \"rgba(237,237,237,0.72)\", textMuted: \"rgba(237,237,237,0.6)\",\n    textSoft: \"rgba(237,237,237,0.5)\", textFaint: \"rgba(237,237,237,0.22)\", hair: \"rgba(237,237,237,0.12)\",\n    accent: \"#ec5c13\", accentInk: \"#ec5c13\", accentFill: \"#b84a12\", accentSoft: \"rgba(236,92,19,0.12)\", accentMuted: \"rgba(236,92,19,0.24)\",\n    accentFg: \"#fff2ea\", success: \"#4cc38a\", warning: \"#f2b544\", error: \"#ff5c4d\",\n  },\n  light: {\n    bg: \"#f7f5ef\", stage: \"#f7f5ef\", surface: \"#fffcf6\", raised: \"#f5efe3\", active: \"#efe7d8\",\n    border: \"#eee6db\", borderStrong: \"#e1d7c8\",\n    text: \"#171717\", textMedium: \"rgba(23,23,23,0.78)\", textMuted: \"rgba(23,23,23,0.68)\",\n    textSoft: \"rgba(23,23,23,0.62)\", textFaint: \"rgba(23,23,23,0.22)\", hair: \"rgba(23,23,23,0.10)\",\n    accent: \"#ec7d43\", accentInk: \"#b4531f\", accentFill: \"#ec7d43\", accentSoft: \"rgba(236,125,67,0.10)\", accentMuted: \"rgba(236,125,67,0.20)\",\n    accentFg: \"#3f2112\", success: \"#1f8a55\", warning: \"#a86b00\", error: \"#c8361f\",\n  },\n} as const;\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/_core/palette.ts"
    },
    {
      "path": "components/bjork-ui/_core/tone.ts",
      "content": "\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\nimport { useTheme } from \"next-themes\";\nimport type { BjorkTone } from \"./palette\";\n\nconst subscribeNoop = () => () => {};\nconst getMounted = () => true;\nconst getServerMounted = () => false;\n\n/**\n * Resolves the tone for a component. An explicit `tone` always wins.\n * Otherwise it follows next-themes' resolved theme, but only after mount,\n * so the server render and first client render agree on dark.\n */\nexport function useBjorkTone(tone?: BjorkTone): BjorkTone {\n  const { resolvedTheme } = useTheme();\n  const mounted = useSyncExternalStore(subscribeNoop, getMounted, getServerMounted);\n  if (tone) return tone;\n  if (!mounted) return \"dark\";\n  return resolvedTheme === \"light\" ? \"light\" : \"dark\";\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/_core/tone.ts"
    },
    {
      "path": "components/bjork-ui/_core/surface.ts",
      "content": "import type { CSSProperties } from \"react\";\nimport type { BjorkTone } from \"./palette\";\n\n// The library surface (CANON.md §1). Values mirror app/globals.css :root and .dark. Change both together.\nexport const BJORK_SURFACE = {\n  light: {\n    surface: \"#fffcf6\", surfaceMuted: \"rgba(250, 246, 237, 0.94)\", surfaceHover: \"#f5efe3\", surfaceActive: \"#efe7d8\",\n    panel: \"#f8f2e7\", menu: \"rgba(255, 252, 246, 0.98)\", field: \"rgba(255, 252, 246, 0.96)\",\n    fieldMuted: \"rgba(248, 242, 231, 0.82)\", fieldInset: \"rgba(239, 231, 216, 0.72)\",\n    border: \"#eee6db\", borderMuted: \"#f5ede2\", borderStrong: \"#e1d7c8\",\n    shadowSurface: \"inset 0 7px 14px rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.92), inset 1px 0 0 rgba(88, 72, 49, 0.026), inset -1px 0 0 rgba(255, 255, 255, 0.68), 0 14px 22px -9px rgba(66, 52, 33, 0.11)\",\n    shadowSoft: \"inset 0 1px 0 rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.86)\",\n    shadowPanel: \"inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.64), 0 18px 38px -30px rgba(66, 52, 33, 0.2)\",\n    shadowMenu: \"inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 0 12px 24px rgba(88, 72, 49, 0.022), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.62), 0 22px 44px -26px rgba(66, 52, 33, 0.22)\",\n    shadowInset: \"inset 0 1px 10px rgba(88, 72, 49, 0.12), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.72)\",\n  },\n  dark: {\n    surface: \"#121212\", surfaceMuted: \"rgba(22, 22, 22, 0.92)\", surfaceHover: \"#161616\", surfaceActive: \"#202020\",\n    panel: \"#0d0d0d\", menu: \"rgba(18, 18, 18, 0.98)\", field: \"rgba(18, 18, 18, 0.95)\",\n    fieldMuted: \"rgba(18, 18, 18, 0.55)\", fieldInset: \"rgba(9, 9, 9, 0.75)\",\n    border: \"#232323\", borderMuted: \"#1c1c1c\", borderStrong: \"#343434\",\n    shadowSurface: \"inset 0 7px 14px rgba(255, 255, 255, 0.03), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.06), 0 14px 20px -6px rgba(0, 0, 0, 0.45)\",\n    shadowSoft: \"inset 0 1px 0 rgba(255, 255, 255, 0.045)\",\n    shadowPanel: \"inset 0 1px 0 rgba(255, 255, 255, 0.035), 0 18px 36px -28px rgba(0, 0, 0, 0.9)\",\n    shadowMenu: \"inset 0 1px 0 rgba(255, 255, 255, 0.055), inset 0 12px 24px rgba(255, 255, 255, 0.018), inset 0 -18px 26px rgba(0, 0, 0, 0.24), 0 22px 42px -22px rgba(0, 0, 0, 0.9)\",\n    shadowInset: \"inset 0 1px 10px rgba(0, 0, 0, 0.45)\",\n  },\n} as const;\n\n/** Writes the surface tokens for a pinned tone as --bjork-* custom properties. Spread first; kit-specific keys may override. */\nexport function surfaceVars(tone: BjorkTone): CSSProperties {\n  const s = BJORK_SURFACE[tone];\n  return {\n    \"--bjork-surface\": s.surface, \"--bjork-surface-muted\": s.surfaceMuted, \"--bjork-surface-hover\": s.surfaceHover,\n    \"--bjork-surface-active\": s.surfaceActive, \"--bjork-panel\": s.panel, \"--bjork-menu\": s.menu, \"--bjork-field\": s.field,\n    \"--bjork-field-muted\": s.fieldMuted, \"--bjork-field-inset\": s.fieldInset, \"--bjork-border\": s.border,\n    \"--bjork-border-muted\": s.borderMuted, \"--bjork-border-strong\": s.borderStrong,\n    \"--bjork-shadow-surface\": s.shadowSurface, \"--bjork-shadow-soft\": s.shadowSoft, \"--bjork-shadow-panel\": s.shadowPanel,\n    \"--bjork-shadow-menu\": s.shadowMenu, \"--bjork-shadow-inset\": s.shadowInset,\n  } as CSSProperties;\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/_core/surface.ts"
    },
    {
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n",
      "type": "registry:lib",
      "target": "@lib/utils.ts"
    }
  ],
  "cssVars": {
    "light": {
      "bjork-bg": "#f7f3ea",
      "bjork-surface": "#fffcf6",
      "bjork-surface-muted": "rgba(250, 246, 237, 0.94)",
      "bjork-surface-hover": "#f5efe3",
      "bjork-surface-active": "#efe7d8",
      "bjork-panel": "#f8f2e7",
      "bjork-menu": "rgba(255, 252, 246, 0.98)",
      "bjork-field": "rgba(255, 252, 246, 0.96)",
      "bjork-field-muted": "rgba(248, 242, 231, 0.82)",
      "bjork-field-inset": "rgba(239, 231, 216, 0.72)",
      "bjork-error-bg": "#fff0e8",
      "bjork-text": "#171717",
      "bjork-text-strong": "rgba(23, 23, 23, 0.9)",
      "bjork-text-medium": "rgba(23, 23, 23, 0.78)",
      "bjork-text-muted": "rgba(23, 23, 23, 0.68)",
      "bjork-text-soft": "rgba(23, 23, 23, 0.62)",
      "bjork-text-faint": "rgba(23, 23, 23, 0.22)",
      "bjork-inverted-text": "#fff8f0",
      "bjork-border": "#eee6db",
      "bjork-border-muted": "#f5ede2",
      "bjork-border-strong": "#e1d7c8",
      "bjork-thumb": "#a49b8e",
      "bjork-track": "#ded6ca",
      "bjork-accent": "#ec7d43",
      "bjork-accent-hover": "#f0935f",
      "bjork-accent-soft": "rgba(236, 125, 67, 0.1)",
      "bjork-accent-muted": "rgba(236, 125, 67, 0.2)",
      "bjork-accent-badge": "rgba(236, 125, 67, 0.16)",
      "bjork-accent-badge-foreground": "#8d421d",
      "bjork-accent-foreground": "#3f2112",
      "bjork-accent-fill": "#ec7d43",
      "bjork-accent-fill-hover": "#f0935f",
      "bjork-shadow-surface": "inset 0 7px 14px rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.92), inset 1px 0 0 rgba(88, 72, 49, 0.026), inset -1px 0 0 rgba(255, 255, 255, 0.68), 0 14px 22px -9px rgba(66, 52, 33, 0.11)",
      "bjork-shadow-soft": "inset 0 1px 0 rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.86)",
      "bjork-shadow-panel": "inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.64), 0 18px 38px -30px rgba(66, 52, 33, 0.2)",
      "bjork-shadow-menu": "inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 0 12px 24px rgba(88, 72, 49, 0.022), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.62), 0 22px 44px -26px rgba(66, 52, 33, 0.22)",
      "bjork-shadow-inset": "inset 0 1px 10px rgba(88, 72, 49, 0.12), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.72)",
      "bjork-ring-offset": "#f7f3ea"
    },
    "dark": {
      "bjork-bg": "#111111",
      "bjork-surface": "#121212",
      "bjork-surface-muted": "rgba(22, 22, 22, 0.92)",
      "bjork-surface-hover": "#161616",
      "bjork-surface-active": "#202020",
      "bjork-panel": "#0d0d0d",
      "bjork-menu": "rgba(18, 18, 18, 0.98)",
      "bjork-field": "rgba(18, 18, 18, 0.95)",
      "bjork-field-muted": "rgba(18, 18, 18, 0.55)",
      "bjork-field-inset": "rgba(9, 9, 9, 0.75)",
      "bjork-error-bg": "#130d0a",
      "bjork-text": "#ededed",
      "bjork-text-strong": "rgba(237, 237, 237, 0.9)",
      "bjork-text-medium": "rgba(237, 237, 237, 0.72)",
      "bjork-text-muted": "rgba(237, 237, 237, 0.6)",
      "bjork-text-soft": "rgba(237, 237, 237, 0.5)",
      "bjork-text-faint": "rgba(237, 237, 237, 0.22)",
      "bjork-inverted-text": "#080808",
      "bjork-border": "#232323",
      "bjork-border-muted": "#1c1c1c",
      "bjork-border-strong": "#343434",
      "bjork-thumb": "#626262",
      "bjork-track": "#090909",
      "bjork-accent": "#ec5c13",
      "bjork-accent-hover": "#f07832",
      "bjork-accent-soft": "rgba(236, 92, 19, 0.12)",
      "bjork-accent-muted": "rgba(236, 92, 19, 0.24)",
      "bjork-accent-badge": "#b84a12",
      "bjork-accent-badge-foreground": "#fff2ea",
      "bjork-accent-foreground": "#fff2ea",
      "bjork-accent-fill": "#b84a12",
      "bjork-accent-fill-hover": "#a84a14",
      "bjork-shadow-surface": "inset 0 7px 14px rgba(255, 255, 255, 0.03), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.06), 0 14px 20px -6px rgba(0, 0, 0, 0.45)",
      "bjork-shadow-soft": "inset 0 1px 0 rgba(255, 255, 255, 0.045)",
      "bjork-shadow-panel": "inset 0 1px 0 rgba(255, 255, 255, 0.035), 0 18px 36px -28px rgba(0, 0, 0, 0.9)",
      "bjork-shadow-menu": "inset 0 1px 0 rgba(255, 255, 255, 0.055), inset 0 12px 24px rgba(255, 255, 255, 0.018), inset 0 -18px 26px rgba(0, 0, 0, 0.24), 0 22px 42px -22px rgba(0, 0, 0, 0.9)",
      "bjork-shadow-inset": "inset 0 1px 10px rgba(0, 0, 0, 0.45)",
      "bjork-ring-offset": "#080808"
    }
  },
  "css": {
    ".bjork-range": {
      "box-shadow": "inset 0 1px 1px rgba(255, 255, 255, 0.04), 0 0 14px rgba(189, 69, 20, 0.06)"
    },
    ".bjork-range::-webkit-slider-thumb": {
      "appearance": "none",
      "width": "16px",
      "height": "8px",
      "border": "0",
      "border-radius": "999px",
      "background": "var(--bjork-thumb)",
      "box-shadow": "inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.42)"
    },
    ".bjork-range::-moz-range-thumb": {
      "width": "16px",
      "height": "8px",
      "border": "0",
      "border-radius": "999px",
      "background": "var(--bjork-thumb)",
      "box-shadow": "inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.42)"
    },
    ".bjork-layered-button-accent, .bjork-layered-button-raised": {
      "position": "relative",
      "isolation": "isolate",
      "appearance": "none",
      "overflow": "hidden",
      "border": "0",
      "border-radius": "13px",
      "box-shadow": "inset 0 1px 0 #ffffff38, 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a",
      "transition": "filter 150ms ease-out, box-shadow 150ms ease-out"
    },
    ".bjork-layered-button-accent::after, .bjork-layered-button-raised::after": {
      "position": "absolute",
      "inset": "0",
      "pointer-events": "none",
      "content": "\"\"",
      "border": "1.5px solid transparent",
      "border-radius": "inherit",
      "background": "linear-gradient(#ffffffb8, #0000003d 41% 75%, #ffffff47) border-box",
      "mix-blend-mode": "overlay",
      "-webkit-mask-image": "linear-gradient(#000, #000), linear-gradient(#000, #000)",
      "mask-image": "linear-gradient(#000, #000), linear-gradient(#000, #000)",
      "-webkit-mask-clip": "padding-box, border-box",
      "mask-clip": "padding-box, border-box",
      "-webkit-mask-origin": "padding-box, border-box",
      "mask-origin": "padding-box, border-box",
      "-webkit-mask-composite": "xor",
      "mask-composite": "exclude"
    },
    ".bjork-layered-button-accent": {
      "color": "oklch(100% 0 0)",
      "text-shadow": "0 -1px #00000040",
      "background": "radial-gradient(ellipse at -20px top, rgba(255, 255, 255, 0.14), transparent), linear-gradient(180deg, #ec5c13 0%, #ec5c13 62%, color-mix(in oklab, #ec5c13 84%, black 16%) 100%)"
    },
    ".bjork-layered-button-raised": {
      "color": "oklch(100% 0 0)",
      "text-shadow": "0 -1px #00000026",
      "background": "radial-gradient(ellipse at -20px top, #ffffff38, #fff0), linear-gradient(180deg, oklch(0.1881 0.006 265), oklch(0.5493 0.0081 265))"
    },
    ".dark .bjork-layered-button-raised": {
      "color": "oklch(0.2574 0.0056 265)",
      "text-shadow": "0 -1px #00000026",
      "background": "radial-gradient(ellipse at -20px top, #ffffff38, #fff0), linear-gradient(180deg, color-mix(in oklab, oklch(0.9674 0.0013 265) 80%, oklch(0.2574 0.0056 265) 20%), color-mix(in oklab, oklch(0.669 0.0107 265) 80%, oklch(0.2574 0.0056 265) 20%))"
    },
    "@media (hover: hover)": {
      ".bjork-layered-button-accent:hover": {
        "filter": "brightness(1.08) saturate(1.04)"
      },
      ".bjork-layered-button-raised:hover": {
        "filter": "brightness(1.08) contrast(0.9)"
      }
    }
  },
  "meta": {
    "collection": "AI",
    "registryUrl": "https://ui.isaiahbjork.com/context-meter.json",
    "installCommand": "npx shadcn@latest add https://ui.isaiahbjork.com/context-meter.json"
  },
  "type": "registry:component"
}