{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pricing-table",
  "title": "Pricing table",
  "description": "Pricing table is a production-ready tables component from BJORK UI, packaged for shadcn registries.",
  "dependencies": [
    "clsx",
    "framer-motion",
    "lucide-react",
    "next-themes",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "components/bjork-ui/tables/pricing-table.tsx",
      "content": "\"use client\";\n\nimport {\n  useCallback,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type KeyboardEvent,\n} from \"react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport { useTheme } from \"next-themes\";\nimport { Check, ChevronDown, Info, Minus } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { useElementSize } from \"../_core/canvas\";\nimport {\n  type BjorkTableThemeMode,\n  getBjorkSignalPalette,\n  getBjorkTablePalette,\n  useBjorkTableIsDark,\n} from \"./table-theme\";\n\nexport type PricingBilling = \"monthly\" | \"annual\";\n\nexport interface PricingPlan {\n  id: string;\n  name: string;\n  blurb: string;\n  /** Price per unit per month when billed monthly. `null` renders `customPriceLabel`. */\n  monthlyPrice: number | null;\n  /** Effective price per unit per month when billed annually. */\n  annualPrice: number | null;\n  /** Shown after the price, e.g. \"per seat / mo\". */\n  priceSuffix?: string;\n  /** Shown instead of a number when the price is `null`, e.g. \"Custom\". */\n  customPriceLabel?: string;\n  ctaLabel: string;\n  highlighted?: boolean;\n  /** Badge on the highlighted column. Defaults to \"Recommended\". */\n  badge?: string;\n}\n\nexport type PricingFeatureValue = boolean | string | number;\n\nexport interface PricingFeature {\n  label: string;\n  hint?: string;\n  values: Record<string, PricingFeatureValue>;\n}\n\nexport interface PricingFeatureGroup {\n  title: string;\n  features: PricingFeature[];\n}\n\nexport interface PricingTableProps {\n  plans?: PricingPlan[];\n  featureGroups?: PricingFeatureGroup[];\n  /** Uncontrolled initial billing period. */\n  defaultBilling?: PricingBilling;\n  /** Controlled billing period. */\n  billing?: PricingBilling;\n  onBillingChange?: (billing: PricingBilling) => void;\n  onPlanSelect?: (planId: string, billing: PricingBilling) => void;\n  /** Titles of groups that start collapsed. */\n  defaultCollapsedGroups?: string[];\n  /** Plan shown first in the narrow, one-plan-at-a-time layout. Defaults to the highlighted plan. */\n  defaultMobilePlanId?: string;\n  currency?: string;\n  caption?: string;\n  /** Height of the scrolling feature list. The plan header stays pinned inside it. */\n  maxHeight?: number;\n  loading?: boolean;\n  className?: string;\n  theme?: BjorkTableThemeMode;\n  enableAnimations?: boolean;\n}\n\nexport const PRICING_TABLE_SAMPLE_PLANS: PricingPlan[] = [\n  {\n    id: \"hobby\",\n    name: \"Hobby\",\n    blurb: \"For side projects and prototypes.\",\n    monthlyPrice: 0,\n    annualPrice: 0,\n    priceSuffix: \"free forever\",\n    ctaLabel: \"Start free\",\n  },\n  {\n    id: \"team\",\n    name: \"Team\",\n    blurb: \"Targeting and rollouts for teams.\",\n    monthlyPrice: 24,\n    annualPrice: 19,\n    priceSuffix: \"per seat / month\",\n    ctaLabel: \"Start trial\",\n    highlighted: true,\n  },\n  {\n    id: \"scale\",\n    name: \"Scale\",\n    blurb: \"Experiments, audit trails and SSO.\",\n    monthlyPrice: 64,\n    annualPrice: 51,\n    priceSuffix: \"per seat / month\",\n    ctaLabel: \"Choose Scale\",\n  },\n  {\n    id: \"enterprise\",\n    name: \"Enterprise\",\n    blurb: \"Dedicated regions, custom terms.\",\n    monthlyPrice: null,\n    annualPrice: null,\n    customPriceLabel: \"Custom\",\n    ctaLabel: \"Talk to sales\",\n  },\n];\n\nexport const PRICING_TABLE_SAMPLE_GROUPS: PricingFeatureGroup[] = [\n  {\n    title: \"Flags & targeting\",\n    features: [\n      {\n        label: \"Feature flags\",\n        values: { hobby: 25, team: \"Unlimited\", scale: \"Unlimited\", enterprise: \"Unlimited\" },\n      },\n      {\n        label: \"Monthly evaluations\",\n        hint: \"Every time an SDK resolves a flag for a user. Cached client evaluations are free.\",\n        values: { hobby: \"1M\", team: \"50M\", scale: \"500M\", enterprise: \"Custom\" },\n      },\n      {\n        label: \"Percentage rollouts\",\n        values: { hobby: true, team: true, scale: true, enterprise: true },\n      },\n      {\n        label: \"Segment targeting\",\n        hint: \"Target by plan, region, device or any attribute you send with the context.\",\n        values: { hobby: false, team: true, scale: true, enterprise: true },\n      },\n      {\n        label: \"Scheduled releases\",\n        values: { hobby: false, team: true, scale: true, enterprise: true },\n      },\n      {\n        label: \"Environments\",\n        values: { hobby: 2, team: 5, scale: 20, enterprise: \"Unlimited\" },\n      },\n    ],\n  },\n  {\n    title: \"Experimentation\",\n    features: [\n      {\n        label: \"A/B experiments\",\n        values: { hobby: false, team: 3, scale: \"Unlimited\", enterprise: \"Unlimited\" },\n      },\n      {\n        label: \"Sequential testing\",\n        hint: \"Peek at results any time without inflating false positives.\",\n        values: { hobby: false, team: false, scale: true, enterprise: true },\n      },\n      {\n        label: \"Warehouse metrics\",\n        values: { hobby: false, team: false, scale: true, enterprise: true },\n      },\n    ],\n  },\n  {\n    title: \"Governance\",\n    features: [\n      {\n        label: \"Audit log retention\",\n        values: { hobby: \"7 days\", team: \"90 days\", scale: \"1 year\", enterprise: \"7 years\" },\n      },\n      {\n        label: \"Change approvals\",\n        hint: \"Require a second reviewer before a flag changes in protected environments.\",\n        values: { hobby: false, team: false, scale: true, enterprise: true },\n      },\n      {\n        label: \"SAML SSO & SCIM\",\n        values: { hobby: false, team: false, scale: true, enterprise: true },\n      },\n      {\n        label: \"Data residency\",\n        values: { hobby: false, team: false, scale: false, enterprise: \"EU, US, APAC\" },\n      },\n    ],\n  },\n  {\n    title: \"Support\",\n    features: [\n      {\n        label: \"Community forum\",\n        values: { hobby: true, team: true, scale: true, enterprise: true },\n      },\n      {\n        label: \"Response time\",\n        values: { hobby: false, team: \"1 business day\", scale: \"4 hours\", enterprise: \"1 hour\" },\n      },\n      {\n        label: \"Uptime SLA\",\n        values: { hobby: false, team: false, scale: \"99.95%\", enterprise: \"99.99%\" },\n      },\n    ],\n  },\n];\n\nconst subscribeNoop = () => () => {};\n\n// False during SSR and hydration, true afterwards.\nfunction useMounted() {\n  return useSyncExternalStore(subscribeNoop, () => true, () => false);\n}\n\nfunction formatPrice(value: number, currency: string) {\n  return new Intl.NumberFormat(\"en-US\", {\n    style: \"currency\",\n    currency,\n    maximumFractionDigits: 0,\n  }).format(value);\n}\n\nexport function PricingTable({\n  plans = PRICING_TABLE_SAMPLE_PLANS,\n  featureGroups = PRICING_TABLE_SAMPLE_GROUPS,\n  defaultBilling = \"annual\",\n  billing: controlledBilling,\n  onBillingChange,\n  onPlanSelect,\n  defaultCollapsedGroups = [],\n  defaultMobilePlanId,\n  currency = \"USD\",\n  caption = \"Plan comparison\",\n  maxHeight = 560,\n  loading = false,\n  className,\n  theme = \"auto\",\n  enableAnimations = true,\n}: PricingTableProps) {\n  const mounted = useMounted();\n  const { resolvedTheme } = useTheme();\n  const forcedTheme = theme === \"auto\" ? undefined : theme;\n  const detectedIsDark = useBjorkTableIsDark(resolvedTheme, forcedTheme);\n  const isDark = !mounted && theme === \"auto\" ? true : detectedIsDark;\n  const palette = getBjorkTablePalette(isDark);\n  const green = getBjorkSignalPalette(\"green\", isDark);\n  const shouldReduceMotion = useReducedMotion();\n  const shouldAnimate = enableAnimations && !shouldReduceMotion;\n\n  const [uncontrolledBilling, setUncontrolledBilling] = useState<PricingBilling>(defaultBilling);\n  const billing = controlledBilling ?? uncontrolledBilling;\n  // Keyboard changes swap prices instantly; pointer changes get the slide.\n  const [instant, setInstant] = useState(false);\n  const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set(defaultCollapsedGroups));\n  const highlightedPlan = plans.find((plan) => plan.highlighted) ?? plans[0];\n  const [mobilePlanId, setMobilePlanId] = useState<string | undefined>(\n    defaultMobilePlanId ?? highlightedPlan?.id\n  );\n  const activeMobilePlanId = plans.some((plan) => plan.id === mobilePlanId)\n    ? mobilePlanId\n    : highlightedPlan?.id;\n  const [compact, setCompact] = useState(false);\n  const [openHint, setOpenHint] = useState<string | null>(null);\n  const scrollRef = useRef<HTMLDivElement>(null);\n  const rootRef = useRef<HTMLDivElement>(null);\n  const { width: rootWidth } = useElementSize(rootRef);\n  const layout = rootWidth > 0 && rootWidth < 480 ? \"narrow\" : \"wide\";\n  const uid = useId();\n\n  const savings = useMemo(() => {\n    let best = 0;\n    for (const plan of plans) {\n      if (plan.monthlyPrice && plan.annualPrice !== null && plan.monthlyPrice > 0) {\n        best = Math.max(best, 1 - plan.annualPrice / plan.monthlyPrice);\n      }\n    }\n    return Math.round(best * 100);\n  }, [plans]);\n\n  const setBilling = useCallback(\n    (next: PricingBilling, fromKeyboard: boolean) => {\n      setInstant(fromKeyboard);\n      if (controlledBilling === undefined) setUncontrolledBilling(next);\n      onBillingChange?.(next);\n    },\n    [controlledBilling, onBillingChange]\n  );\n\n  const toggleGroup = (title: string) => {\n    setCollapsed((prev) => {\n      const next = new Set(prev);\n      if (next.has(title)) next.delete(title);\n      else next.add(title);\n      return next;\n    });\n  };\n\n  const onScroll = () => {\n    const top = scrollRef.current?.scrollTop ?? 0;\n    setCompact((prev) => (prev ? top > 4 : top > 24));\n  };\n\n  useEffect(() => {\n    if (!openHint) return;\n    const onKey = (event: globalThis.KeyboardEvent) => {\n      if (event.key === \"Escape\") setOpenHint(null);\n    };\n    window.addEventListener(\"keydown\", onKey);\n    return () => window.removeEventListener(\"keydown\", onKey);\n  }, [openHint]);\n\n  const billingOptions: { value: PricingBilling; label: string }[] = [\n    { value: \"monthly\", label: \"Monthly\" },\n    { value: \"annual\", label: \"Annual\" },\n  ];\n\n  const onRadioKey = <T extends string>(\n    event: KeyboardEvent<HTMLButtonElement>,\n    values: T[],\n    current: T,\n    select: (value: T) => void\n  ) => {\n    const index = values.indexOf(current);\n    let next = -1;\n    if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") next = (index + 1) % values.length;\n    if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") next = (index - 1 + values.length) % values.length;\n    if (event.key === \"Home\") next = 0;\n    if (event.key === \"End\") next = values.length - 1;\n    if (next < 0) return;\n    event.preventDefault();\n    select(values[next]);\n    const group = event.currentTarget.parentElement;\n    const target = group?.querySelectorAll<HTMLButtonElement>('[role=\"radio\"]')[next];\n    target?.focus();\n  };\n\n  const surface = isDark ? \"bg-[#111]\" : \"bg-[#fffcf6]\";\n  const strongText = isDark ? \"text-[#ededed]\" : \"text-[#171717]\";\n  const accentEdge = isDark ? \"border-[#ec5c13]/55\" : \"border-[#bd4514]/45\";\n  const accentWash = isDark ? \"bg-[#ec5c13]/[0.045]\" : \"bg-[#ec5c13]/[0.04]\";\n  const focusRing =\n    \"outline-none focus-visible:ring-2 focus-visible:ring-[#ec5c13]/50 focus-visible:ring-offset-0\";\n\n  // Last visible row closes the highlighted column's border.\n  const visibleGroups = featureGroups.map((group) => ({\n    group,\n    open: !collapsed.has(group.title),\n  }));\n  const lastOpenGroupIndex = visibleGroups.reduce(\n    (last, entry, index) => (entry.open && entry.group.features.length > 0 ? index : last),\n    -1\n  );\n\n  const planColClass = (plan: PricingPlan) =>\n    cn(plan.id !== activeMobilePlanId && \"group-data-[layout=narrow]/pt:hidden\");\n\n  const highlightCellClass = (plan: PricingPlan) =>\n    plan.highlighted ? cn(\"relative border-x\", accentEdge) : \"\";\n\n  const renderValue = (value: PricingFeatureValue | undefined) => {\n    if (value === true) {\n      return (\n        <span className=\"inline-flex items-center justify-center\">\n          <span\n            className={cn(\n              \"inline-flex size-[18px] items-center justify-center rounded-full\",\n              isDark ? \"bg-[#ec5c13]/14 text-[#d86a2c]\" : \"bg-[#ec5c13]/12 text-[#bd4514]\"\n            )}\n          >\n            <Check className=\"size-3\" strokeWidth={2.6} aria-hidden=\"true\" />\n          </span>\n          <span className=\"sr-only\">Included</span>\n        </span>\n      );\n    }\n    if (value === false || value === undefined || value === \"\") {\n      return (\n        <span className=\"inline-flex items-center justify-center\">\n          <Minus className={cn(\"size-3.5\", isDark ? \"text-[#ededed]/22\" : \"text-[#171717]/24\")} aria-hidden=\"true\" />\n          <span className=\"sr-only\">Not included</span>\n        </span>\n      );\n    }\n    return (\n      <span className={cn(\"text-[13px] tabular-nums\", palette.primaryText)}>\n        {typeof value === \"number\" ? value.toLocaleString(\"en-US\") : value}\n      </span>\n    );\n  };\n\n  if (!loading && plans.length === 0) {\n    return (\n      <div\n        ref={rootRef}\n        className={cn(\n          \"mx-auto flex w-full max-w-[1040px] flex-col items-center justify-center gap-1 rounded-[18px] border px-6 py-14 text-center\",\n          palette.container,\n          className\n        )}\n      >\n        <p className={cn(\"text-[14px] font-medium\", palette.primaryText)}>No plans to compare</p>\n        <p className={cn(\"text-[13px]\", palette.secondaryText)}>Add at least one plan to render the comparison.</p>\n      </div>\n    );\n  }\n\n  return (\n    <div ref={rootRef} data-layout={layout} className={cn(\"group/pt mx-auto w-full max-w-[1040px]\", className)}>\n      <div className={cn(\"relative overflow-hidden rounded-[18px] border\", palette.container)}>\n        {/* Toolbar */}\n        <div\n          className={cn(\n            \"flex flex-wrap items-center justify-between gap-3 border-b px-4 py-3 group-data-[layout=wide]/pt:px-5\",\n            palette.divider\n          )}\n        >\n          <div className=\"min-w-0\">\n            <p className={cn(\"text-[13px] font-medium\", palette.primaryText)}>{caption}</p>\n            <p className={cn(\"text-[12px]\", palette.secondaryText)}>\n              Prices in {currency}. Cancel any time.\n            </p>\n          </div>\n          <div className=\"flex items-center gap-2\">\n            <div\n              role=\"radiogroup\"\n              aria-label=\"Billing period\"\n              className={cn(\"inline-flex rounded-[10px] border p-0.5\", palette.divider, palette.mutedSurface)}\n            >\n              {billingOptions.map((option) => {\n                const checked = billing === option.value;\n                return (\n                  <button\n                    key={option.value}\n                    type=\"button\"\n                    role=\"radio\"\n                    aria-checked={checked}\n                    tabIndex={checked ? 0 : -1}\n                    onClick={(event) => setBilling(option.value, event.detail === 0)}\n                    onKeyDown={(event) =>\n                      onRadioKey(\n                        event,\n                        billingOptions.map((o) => o.value),\n                        billing,\n                        (value) => setBilling(value, true)\n                      )\n                    }\n                    className={cn(\n                      \"relative rounded-[8px] px-3 py-1 text-[12.5px] font-medium transition-colors\",\n                      focusRing,\n                      checked ? strongText : palette.secondaryText\n                    )}\n                  >\n                    {checked && (\n                      <motion.span\n                        layoutId={shouldAnimate && !instant ? `${uid}-billing-pill` : undefined}\n                        transition={{ type: \"spring\", stiffness: 520, damping: 40 }}\n                        className={cn(\n                          \"absolute inset-0 rounded-[8px] border\",\n                          isDark\n                            ? \"border-[#2a2a2a] bg-[#202020]\"\n                            : \"border-[#eadfce] bg-[#fffcf6] shadow-[var(--bjork-shadow-soft)]\"\n                        )}\n                        aria-hidden=\"true\"\n                      />\n                    )}\n                    <span className=\"relative\">{option.label}</span>\n                  </button>\n                );\n              })}\n            </div>\n            {savings > 0 && (\n              <span\n                className={cn(\n                  \"rounded-[8px] border px-2 py-[3px] text-[11px] font-medium tabular-nums transition-opacity\",\n                  green.bgColor,\n                  green.borderColor,\n                  green.textColor,\n                  billing === \"annual\" ? \"opacity-100\" : \"opacity-70\"\n                )}\n              >\n                Save {savings}%<span className=\"sr-only\"> with annual billing</span>\n              </span>\n            )}\n          </div>\n        </div>\n\n        {/* Narrow layout: one plan at a time */}\n        {plans.length > 1 && (\n          <div className={cn(\"hidden border-b px-3 py-2.5 group-data-[layout=narrow]/pt:block\", palette.divider)}>\n            <div\n              role=\"radiogroup\"\n              aria-label=\"Plan to compare\"\n              className={cn(\"grid rounded-[10px] border p-0.5\", palette.divider, palette.mutedSurface)}\n              style={{ gridTemplateColumns: `repeat(${plans.length}, minmax(0, 1fr))` }}\n            >\n              {plans.map((plan) => {\n                const checked = plan.id === activeMobilePlanId;\n                return (\n                  <button\n                    key={plan.id}\n                    type=\"button\"\n                    role=\"radio\"\n                    aria-checked={checked}\n                    tabIndex={checked ? 0 : -1}\n                    onClick={() => setMobilePlanId(plan.id)}\n                    onKeyDown={(event) =>\n                      onRadioKey(\n                        event,\n                        plans.map((p) => p.id),\n                        activeMobilePlanId ?? plans[0].id,\n                        setMobilePlanId\n                      )\n                    }\n                    className={cn(\n                      \"relative truncate rounded-[8px] px-1.5 py-1.5 text-[12px] font-medium transition-colors\",\n                      focusRing,\n                      checked ? strongText : palette.secondaryText\n                    )}\n                  >\n                    {checked && (\n                      <span\n                        className={cn(\n                          \"absolute inset-0 rounded-[8px] border\",\n                          plan.highlighted\n                            ? cn(accentEdge, isDark ? \"bg-[#202020]\" : \"bg-[#fffcf6]\")\n                            : isDark\n                              ? \"border-[#2a2a2a] bg-[#202020]\"\n                              : \"border-[#eadfce] bg-[#fffcf6] shadow-[var(--bjork-shadow-soft)]\"\n                        )}\n                        aria-hidden=\"true\"\n                      />\n                    )}\n                    <span className=\"relative\">{plan.name}</span>\n                  </button>\n                );\n              })}\n            </div>\n          </div>\n        )}\n\n        <div\n          ref={scrollRef}\n          onScroll={onScroll}\n          role=\"region\"\n          aria-label={`${caption}, scrollable`}\n          tabIndex={0}\n          className={cn(\"relative overflow-auto overscroll-contain\", focusRing, \"focus-visible:ring-inset\")}\n          style={{ maxHeight }}\n        >\n          <table className=\"w-full min-w-0 table-fixed border-separate border-spacing-0 group-data-[layout=wide]/pt:min-w-[640px]\">\n            <caption className=\"sr-only\">\n              {caption}, billed {billing === \"annual\" ? \"annually\" : \"monthly\"}\n            </caption>\n            <thead>\n              <tr>\n                <th\n                  scope=\"col\"\n                  className={cn(\n                    \"sticky top-0 z-20 w-[52%] border-b px-4 pb-4 pt-7 text-left align-bottom group-data-[layout=wide]/pt:w-[25%] group-data-[layout=wide]/pt:px-5\",\n                    surface,\n                    palette.divider\n                  )}\n                >\n                  <span className={cn(\"text-[11px] font-medium uppercase tracking-[0.08em]\", palette.secondaryText)}>\n                    Features\n                  </span>\n                </th>\n                {plans.map((plan) => {\n                  const price = billing === \"annual\" ? plan.annualPrice : plan.monthlyPrice;\n                  const priceText =\n                    price === null ? plan.customPriceLabel ?? \"Custom\" : formatPrice(price, currency);\n                  return (\n                    <th\n                      key={plan.id}\n                      scope=\"col\"\n                      className={cn(\n                        \"sticky top-0 z-20 border-b px-3 pb-4 pt-7 text-left align-top font-normal group-data-[layout=wide]/pt:px-4\",\n                        surface,\n                        palette.divider,\n                        planColClass(plan),\n                        plan.highlighted && cn(\"rounded-t-[14px] border-x border-t\", accentEdge)\n                      )}\n                    >\n                      {plan.highlighted && (\n                        <span aria-hidden=\"true\" className={cn(\"pointer-events-none absolute inset-0 rounded-t-[13px]\", accentWash)} />\n                      )}\n                      {plan.highlighted && (\n                        <span\n                          className={cn(\n                            \"absolute left-1/2 top-0 -translate-x-1/2 whitespace-nowrap rounded-b-[7px] border border-t-0 px-2 pb-[2px] pt-[1px] text-[11px] font-medium uppercase tracking-[0.08em]\",\n                            accentEdge,\n                            isDark ? \"bg-[#1d130d] text-[#d86a2c]\" : \"bg-[#fbeee3] text-[#bd4514]\"\n                          )}\n                        >\n                          {plan.badge ?? \"Recommended\"}\n                        </span>\n                      )}\n                      <div className=\"relative flex min-w-0 flex-col\">\n                        <span className={cn(\"block truncate text-[14px] font-semibold tracking-tight\", strongText)}>\n                          {plan.name}\n                        </span>\n                        <div\n                          className={cn(\n                            \"grid transition-[grid-template-rows,opacity] duration-200 ease-out motion-reduce:transition-none\",\n                            compact ? \"grid-rows-[0fr] opacity-0\" : \"grid-rows-[1fr] opacity-100\"\n                          )}\n                        >\n                          <p className={cn(\"min-h-0 overflow-hidden text-[12px] leading-[1.4]\", palette.secondaryText)}>\n                            <span className=\"block pt-1\">{plan.blurb}</span>\n                          </p>\n                        </div>\n                        <div className=\"mt-3 flex items-baseline\">\n                          <span className=\"relative inline-flex h-[30px] shrink-0 items-baseline overflow-hidden\">\n                            <AnimatePresence initial={false} mode=\"popLayout\">\n                              <motion.span\n                                key={`${billing}-${priceText}`}\n                                initial={shouldAnimate && !instant ? { y: billing === \"annual\" ? 14 : -14, opacity: 0 } : false}\n                                animate={{ y: 0, opacity: 1 }}\n                                exit={\n                                  shouldAnimate && !instant\n                                    ? { y: billing === \"annual\" ? -14 : 14, opacity: 0 }\n                                    : { opacity: 0, transition: { duration: 0 } }\n                                }\n                                transition={{ type: \"spring\", stiffness: 520, damping: 38 }}\n                                className={cn(\"text-[26px] font-semibold leading-[30px] tracking-tight tabular-nums\", strongText)}\n                              >\n                                {priceText}\n                              </motion.span>\n                            </AnimatePresence>\n                          </span>\n                        </div>\n                        <p className={cn(\"h-4 truncate text-[11.5px] leading-4\", palette.secondaryText)}>\n                          {plan.priceSuffix ?? \"\\u00a0\"}\n                        </p>\n                        <p className={cn(\"h-4 truncate text-[11px] leading-4 tabular-nums\", palette.secondaryText)}>\n                          {billing === \"annual\" && plan.monthlyPrice && plan.annualPrice !== null && plan.annualPrice < plan.monthlyPrice\n                            ? `${formatPrice(plan.monthlyPrice, currency)} billed monthly`\n                            : billing === \"monthly\" && plan.annualPrice && plan.monthlyPrice && plan.annualPrice < plan.monthlyPrice\n                              ? `${formatPrice(plan.annualPrice, currency)} billed yearly`\n                              : \" \"}\n                        </p>\n                        <button\n                          type=\"button\"\n                          onClick={() => onPlanSelect?.(plan.id, billing)}\n                          aria-label={`${plan.ctaLabel}, ${plan.name} plan`}\n                          className={cn(\n                            \"mt-3 inline-flex h-8 w-full items-center justify-center whitespace-nowrap rounded-[9px] border px-1.5 text-[12.5px] font-medium transition-[background-color,transform] active:scale-[0.98]\",\n                            focusRing,\n                            plan.highlighted\n                              ? isDark\n                                ? \"border-[#ec5c13] bg-[#ec5c13] text-[#111] hover:bg-[#d86a2c]\"\n                                : \"border-[#bd4514] bg-[#bd4514] text-[#fffcf6] hover:bg-[#d86a2c]\"\n                              : palette.control\n                          )}\n                        >\n                          <span className=\"truncate\">{plan.ctaLabel}</span>\n                        </button>\n                      </div>\n                    </th>\n                  );\n                })}\n              </tr>\n            </thead>\n\n            {loading ? (\n              <tbody aria-busy=\"true\">\n                {Array.from({ length: 7 }, (_, row) => (\n                  <tr key={row}>\n                    <td className={cn(\"border-b px-4 py-3.5 group-data-[layout=wide]/pt:px-5\", palette.divider)}>\n                      <span\n                        className={cn(\"block h-3 animate-pulse rounded-full motion-reduce:animate-none\", palette.mutedSurface)}\n                        style={{ width: `${48 + ((row * 17) % 36)}%` }}\n                      />\n                    </td>\n                    {plans.map((plan) => (\n                      <td\n                        key={plan.id}\n                        className={cn(\"border-b px-3 py-3.5 text-center\", palette.divider, planColClass(plan), highlightCellClass(plan))}\n                      >\n                        <span className={cn(\"mx-auto block h-3 w-10 animate-pulse rounded-full motion-reduce:animate-none\", palette.mutedSurface)} />\n                      </td>\n                    ))}\n                  </tr>\n                ))}\n              </tbody>\n            ) : (\n              visibleGroups.map(({ group, open }, groupIndex) => {\n                const groupId = `${uid}-group-${groupIndex}`;\n                return (\n                  <tbody key={group.title}>\n                    <tr>\n                      <th\n                        scope=\"colgroup\"\n                        colSpan={layout === \"narrow\" ? 2 : plans.length + 1}\n                        className={cn(\"p-0 text-left\", palette.mutedSurface)}\n                      >\n                        <button\n                          type=\"button\"\n                          aria-expanded={open}\n                          aria-controls={groupId}\n                          onClick={() => toggleGroup(group.title)}\n                          className={cn(\n                            \"flex w-full items-center gap-2 border-b px-4 py-2 text-left group-data-[layout=wide]/pt:px-5\",\n                            palette.divider,\n                            focusRing,\n                            \"focus-visible:ring-inset\"\n                          )}\n                        >\n                          <ChevronDown\n                            aria-hidden=\"true\"\n                            className={cn(\n                              \"size-3.5 transition-transform duration-200 motion-reduce:transition-none\",\n                              palette.secondaryText,\n                              !open && \"-rotate-90\"\n                            )}\n                          />\n                          <span className={cn(\"text-[11px] font-medium uppercase tracking-[0.08em]\", palette.secondaryText)}>\n                            {group.title}\n                          </span>\n                          <span className={cn(\"ml-auto text-[11px] tabular-nums\", palette.secondaryText)}>\n                            {group.features.length}\n                          </span>\n                        </button>\n                      </th>\n                    </tr>\n                    {group.features.map((feature, featureIndex) => {\n                      const hintKey = `${groupIndex}-${featureIndex}`;\n                      const hintId = `${uid}-hint-${hintKey}`;\n                      const isLastRow = groupIndex === lastOpenGroupIndex && featureIndex === group.features.length - 1;\n                      return (\n                        <motion.tr\n                          key={feature.label}\n                          id={featureIndex === 0 ? groupId : undefined}\n                          hidden={!open}\n                          initial={false}\n                          animate={open ? { opacity: 1 } : { opacity: 0 }}\n                          transition={shouldAnimate ? { duration: 0.18, delay: featureIndex * 0.02 } : { duration: 0 }}\n                          className={cn(\"group/row transition-colors\", isDark ? \"hover:bg-[#181818]/70\" : \"hover:bg-[#f8f2e7]/72\")}\n                        >\n                          <th\n                            scope=\"row\"\n                            className={cn(\n                              \"border-b px-4 py-3 text-left text-[13px] font-normal group-data-[layout=wide]/pt:px-5\",\n                              palette.divider,\n                              palette.primaryText\n                            )}\n                          >\n                            <span className=\"relative inline-flex max-w-full items-center gap-1.5\">\n                              <span className=\"min-w-0\">{feature.label}</span>\n                              {feature.hint && (\n                                <span className=\"relative inline-flex\">\n                                  <button\n                                    type=\"button\"\n                                    aria-label={`About ${feature.label}`}\n                                    aria-describedby={openHint === hintKey ? hintId : undefined}\n                                    onMouseEnter={() => setOpenHint(hintKey)}\n                                    onMouseLeave={() => setOpenHint((current) => (current === hintKey ? null : current))}\n                                    onFocus={() => setOpenHint(hintKey)}\n                                    onBlur={() => setOpenHint((current) => (current === hintKey ? null : current))}\n                                    onClick={() => setOpenHint((current) => (current === hintKey ? null : hintKey))}\n                                    className={cn(\n                                      \"inline-flex size-5 items-center justify-center rounded-full transition-colors\",\n                                      focusRing,\n                                      isDark ? \"text-[#ededed]/30 hover:text-[#ededed]/70\" : \"text-[#171717]/30 hover:text-[#171717]/70\"\n                                    )}\n                                  >\n                                    <Info className=\"size-3.5\" aria-hidden=\"true\" />\n                                  </button>\n                                  <AnimatePresence>\n                                    {openHint === hintKey && (\n                                      <motion.span\n                                        id={hintId}\n                                        role=\"tooltip\"\n                                        initial={shouldAnimate ? { opacity: 0, y: 4 } : { opacity: 1 }}\n                                        animate={{ opacity: 1, y: 0 }}\n                                        exit={shouldAnimate ? { opacity: 0, y: 2, transition: { duration: 0.1 } } : { opacity: 0, transition: { duration: 0 } }}\n                                        transition={{ type: \"spring\", stiffness: 520, damping: 36 }}\n                                        className={cn(\n                                          \"absolute left-1/2 top-full z-30 mt-1.5 w-[220px] -translate-x-1/2 rounded-[10px] border px-3 py-2 text-[12px] font-normal leading-[1.45] group-data-[layout=narrow]/pt:left-auto group-data-[layout=narrow]/pt:right-[-60px] group-data-[layout=narrow]/pt:translate-x-0\",\n                                          palette.menu\n                                        )}\n                                      >\n                                        {feature.hint}\n                                      </motion.span>\n                                    )}\n                                  </AnimatePresence>\n                                </span>\n                              )}\n                            </span>\n                          </th>\n                          {plans.map((plan) => (\n                            <td\n                              key={plan.id}\n                              className={cn(\n                                \"border-b px-3 py-3 text-center\",\n                                palette.divider,\n                                planColClass(plan),\n                                highlightCellClass(plan),\n                                plan.highlighted && accentWash,\n                                plan.highlighted && isLastRow && cn(\"rounded-b-[14px]\", accentEdge)\n                              )}\n                            >\n                              {renderValue(feature.values[plan.id])}\n                            </td>\n                          ))}\n                        </motion.tr>\n                      );\n                    })}\n                  </tbody>\n                );\n              })\n            )}\n          </table>\n          {!loading && featureGroups.length === 0 && (\n            <p className={cn(\"px-5 py-10 text-center text-[13px]\", palette.secondaryText)}>\n              No features listed for these plans yet.\n            </p>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/tables/pricing-table.tsx"
    },
    {
      "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"
    },
    {
      "path": "components/bjork-ui/_core/canvas.ts",
      "content": "\"use client\";\n\nimport { useEffect, useState, type RefObject } from \"react\";\nimport { isCoarsePointer } from \"./loop\";\n\n// Caps DPR at 1.5 on coarse pointers and 2 otherwise.\nexport function defaultMaxDpr(): number {\n  return isCoarsePointer() ? 1.5 : 2;\n}\n\n// Sets the backing size for a CSS box and returns the DPR used.\nexport function sizeCanvas(\n  canvas: HTMLCanvasElement,\n  cssW: number,\n  cssH: number,\n  maxDpr: number = defaultMaxDpr(),\n): number {\n  const dpr = Math.min(typeof window !== \"undefined\" ? window.devicePixelRatio || 1 : 1, maxDpr);\n  canvas.width = Math.max(1, Math.round(cssW * dpr));\n  canvas.height = Math.max(1, Math.round(cssH * dpr));\n  canvas.style.width = `${cssW}px`;\n  canvas.style.height = `${cssH}px`;\n  return dpr;\n}\n\n// Rounded size of an element, kept current with a ResizeObserver.\nexport function useElementSize(ref: RefObject<HTMLElement | null>): { width: number; height: number } {\n  const [size, setSize] = useState({ width: 0, height: 0 });\n\n  useEffect(() => {\n    const el = ref.current;\n    if (!el || typeof ResizeObserver === \"undefined\") return;\n    const observer = new ResizeObserver((entries) => {\n      const box = entries[0]?.contentRect;\n      if (!box) return;\n      const width = Math.round(box.width);\n      const height = Math.round(box.height);\n      setSize((prev) => (prev.width === width && prev.height === height ? prev : { width, height }));\n    });\n    observer.observe(el);\n    return () => observer.disconnect();\n  }, [ref]);\n\n  return size;\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/_core/canvas.ts"
    },
    {
      "path": "components/bjork-ui/_core/loop.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, type RefObject } from \"react\";\n\nexport interface VisibleLoopOptions {\n  enabled?: boolean;\n  fpsCap?: number;\n  rootMargin?: string;\n}\n\n/**\n * Runs `frame(dt, t)` on requestAnimationFrame while the element is near the viewport,\n * the tab is visible and `enabled` is not false. Return `false` from `frame` to go idle\n * until `wake()` is called. Writes `data-loop` on the element: running, idle or paused.\n */\nexport function useVisibleLoop(\n  ref: RefObject<HTMLElement | null>,\n  frame: (dt: number, t: number) => boolean | void,\n  opts?: VisibleLoopOptions,\n): { wake: () => void } {\n  const enabled = opts?.enabled !== false;\n  const fpsCap = opts?.fpsCap ?? 0;\n  const rootMargin = opts?.rootMargin ?? \"96px\";\n\n  const frameRef = useRef(frame);\n  useEffect(() => {\n    frameRef.current = frame;\n  }, [frame]);\n\n  const wakeRef = useRef<() => void>(() => {});\n  const wake = useCallback(() => wakeRef.current(), []);\n\n  useEffect(() => {\n    const el = ref.current;\n    if (!el || typeof window === \"undefined\") return;\n\n    let wants = true; // false once the frame callback returns false\n    let inView = false;\n    let raf = 0;\n    let last = 0;\n    let t = 0;\n    const minFrame = fpsCap > 0 ? 1000 / fpsCap : 0;\n\n    const canRun = () => enabled && inView && document.visibilityState === \"visible\";\n    const stop = () => {\n      if (raf) cancelAnimationFrame(raf);\n      raf = 0;\n    };\n\n    const update = () => {\n      if (!enabled) {\n        stop();\n        el.dataset.loop = \"paused\";\n        return;\n      }\n      if (!wants) {\n        stop();\n        el.dataset.loop = \"idle\";\n        return;\n      }\n      if (!canRun()) {\n        stop();\n        el.dataset.loop = \"paused\";\n        return;\n      }\n      el.dataset.loop = \"running\";\n      if (!raf) {\n        last = performance.now();\n        raf = requestAnimationFrame(tick);\n      }\n    };\n\n    const tick = (now: number) => {\n      raf = 0;\n      if (!canRun() || !wants) {\n        update();\n        return;\n      }\n      const elapsed = now - last;\n      if (minFrame && elapsed < minFrame - 0.5) {\n        raf = requestAnimationFrame(tick);\n        return;\n      }\n      last = now;\n      const dt = Math.min(0.05, Math.max(0, elapsed / 1000));\n      t += dt;\n      const keep = frameRef.current(dt, t);\n      if (keep === false) {\n        wants = false;\n        update();\n        return;\n      }\n      raf = requestAnimationFrame(tick);\n    };\n\n    const observer = new IntersectionObserver(\n      ([entry]) => {\n        inView = entry.isIntersecting;\n        update();\n      },\n      { rootMargin },\n    );\n    observer.observe(el);\n\n    const onVisibility = () => update();\n    document.addEventListener(\"visibilitychange\", onVisibility);\n\n    wakeRef.current = () => {\n      wants = true;\n      update();\n    };\n\n    update();\n\n    return () => {\n      stop();\n      observer.disconnect();\n      document.removeEventListener(\"visibilitychange\", onVisibility);\n      wakeRef.current = () => {};\n    };\n  }, [ref, enabled, fpsCap, rootMargin]);\n\n  return useMemo(() => ({ wake }), [wake]);\n}\n\n// matchMedia based check. False on the server.\nexport function isCoarsePointer(): boolean {\n  if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return false;\n  return window.matchMedia(\"(pointer: coarse)\").matches;\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/_core/loop.ts"
    },
    {
      "path": "components/bjork-ui/tables/table-theme.ts",
      "content": "import { useEffect, useState } from \"react\";\n\nexport type BjorkTableThemeMode = \"light\" | \"dark\" | \"auto\";\n\nexport function useBjorkTableIsDark(\n  resolvedTheme?: string,\n  forcedTheme?: Exclude<BjorkTableThemeMode, \"auto\">\n) {\n  const [documentTheme, setDocumentTheme] = useState<\"light\" | \"dark\" | null>(null);\n  const [previewTheme, setPreviewTheme] = useState<\"light\" | \"dark\" | null>(null);\n  // Server and first client render must agree, otherwise React keeps the\n  // server-rendered (dark) classes when the real theme resolves to light.\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    setMounted(true);\n    const syncDocumentTheme = () => {\n      const root = document.documentElement;\n      const searchParams = new URLSearchParams(window.location.search);\n      const theme = searchParams.get(\"theme\");\n\n      setPreviewTheme(theme === \"light\" || theme === \"dark\" ? theme : null);\n\n      setDocumentTheme(\n        root.classList.contains(\"light\")\n          ? \"light\"\n          : root.classList.contains(\"dark\")\n            ? \"dark\"\n            : null\n      );\n    };\n\n    syncDocumentTheme();\n\n    const observer = new MutationObserver(syncDocumentTheme);\n    observer.observe(document.documentElement, {\n      attributes: true,\n      attributeFilter: [\"class\"],\n    });\n\n    return () => observer.disconnect();\n  }, []);\n\n  const activeTheme =\n    forcedTheme ?? previewTheme ?? documentTheme ?? (mounted ? resolvedTheme : undefined);\n\n  return activeTheme !== \"light\";\n}\n\nexport function getBjorkTablePalette(isDark: boolean) {\n  return {\n    container: isDark\n      ? \"border-[#1c1c1c] bg-[#121212] shadow-[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      : \"border-[#f5ede2] bg-[#fffcf6] shadow-[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    header: isDark\n      ? \"border-[#232323] bg-[#181818] text-[#ededed]/42\"\n      : \"border-[#f1e8dc] bg-[#fbf7ef] text-[#171717]/60\",\n    row: isDark\n      ? \"border-[#232323] hover:bg-[#181818]/70\"\n      : \"border-[#f1e8dc] hover:bg-[#f8f2e7]/72\",\n    selectedRow: isDark\n      ? \"border-[#2a2a2a] bg-[#181818]\"\n      : \"border-[#eadfce] bg-[#f8f2e7]\",\n    control: isDark\n      ? \"border-[#232323] bg-[#181818] text-[#ededed]/70 hover:bg-[#202020] shadow-[inset_0_1px_0_rgba(255,255,255,0.045)]\"\n      : \"border-[#eadfce] bg-[#fffcf6] text-[#171717]/68 shadow-[var(--bjork-shadow-soft)] hover:bg-[#f8f2e7]\",\n    menu: isDark\n      ? \"border-[#232323] bg-[#181818] text-[#ededed]/76 shadow-[inset_0_1px_0_rgba(255,255,255,0.055),inset_0_12px_24px_rgba(255,255,255,0.018),0_22px_42px_-22px_rgba(0,0,0,0.9)]\"\n      : \"border-[#eadfce] bg-[#fffcf6] text-[#171717]/76 shadow-[inset_0_1px_0_rgba(88,72,49,0.04),inset_0_12px_24px_rgba(88,72,49,0.022),0_22px_44px_-26px_rgba(66,52,33,0.22)]\",\n    menuItem: isDark ? \"hover:bg-[#232323]\" : \"hover:bg-[#f8f2e7]\",\n    menuActive: isDark ? \"bg-[#232323]\" : \"bg-[#f8f2e7]\",\n    divider: isDark ? \"border-[#232323]\" : \"border-[#f1e8dc]\",\n    primaryText: isDark ? \"text-[#ededed]/90\" : \"text-[#171717]/86\",\n    secondaryText: isDark ? \"text-[#ededed]/38\" : \"text-[#171717]/60\",\n    mutedSurface: isDark ? \"bg-[#181818]\" : \"bg-[#f8f2e7]\",\n    popover: isDark ? \"bg-[#111] text-[#ededed]\" : \"bg-[#fffcf6] text-[#171717]\",\n    accent: isDark ? \"text-[#d86a2c]\" : \"text-[#bd4514]\",\n    accentBg: isDark ? \"bg-[#ec5c13]/12\" : \"bg-[#ec5c13]/14\",\n    accentBorder: isDark ? \"border-[#ec5c13]/24\" : \"border-[#bd4514]/22\",\n    checkboxAccent: isDark ? \"#d86a2c\" : \"#bd4514\",\n  };\n}\n\nexport function getBjorkSignalPalette(\n  tone: \"green\" | \"red\" | \"orange\" | \"neutral\",\n  isDark: boolean\n) {\n  const palettes = {\n    green: {\n      bgColor: isDark ? \"bg-[#3e6f52]/18\" : \"bg-[#dce8dc]\",\n      borderColor: isDark ? \"border-[#5f9f78]/28\" : \"border-[#bad0bd]\",\n      textColor: isDark ? \"text-[#77b58b]\" : \"text-[#47785c]\",\n      dotColor: isDark ? \"bg-[#77b58b]\" : \"bg-[#47785c]\",\n      color: isDark ? \"#77b58b\" : \"#47785c\",\n    },\n    red: {\n      bgColor: isDark ? \"bg-[#87463f]/18\" : \"bg-[#eadad5]\",\n      borderColor: isDark ? \"border-[#c46a61]/28\" : \"border-[#d7bcb4]\",\n      textColor: isDark ? \"text-[#cf7c72]\" : \"text-[#9d5149]\",\n      dotColor: isDark ? \"bg-[#cf7c72]\" : \"bg-[#9d5149]\",\n      color: isDark ? \"#cf7c72\" : \"#9d5149\",\n    },\n    orange: {\n      bgColor: isDark ? \"bg-[#ec5c13]/14\" : \"bg-[#ead9cb]\",\n      borderColor: isDark ? \"border-[#ec5c13]/28\" : \"border-[#d5b59f]\",\n      textColor: isDark ? \"text-[#d86a2c]\" : \"text-[#9f4317]\",\n      dotColor: isDark ? \"bg-[#d86a2c]\" : \"bg-[#9f4317]\",\n      color: isDark ? \"#d86a2c\" : \"#9f4317\",\n    },\n    neutral: {\n      bgColor: isDark ? \"bg-[#ededed]/8\" : \"bg-[#ded7ca]\",\n      borderColor: isDark ? \"border-[#ededed]/14\" : \"border-[#cfc6b7]\",\n      textColor: isDark ? \"text-[#ededed]/58\" : \"text-[#171717]/60\",\n      dotColor: isDark ? \"bg-[#ededed]/45\" : \"bg-[#171717]/42\",\n      color: isDark ? \"#a3a3a3\" : \"#706b62\",\n    },\n  } as const;\n\n  return palettes[tone];\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/tables/table-theme.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": "Tables",
    "registryUrl": "https://ui.isaiahbjork.com/pricing-table.json",
    "installCommand": "npx shadcn@latest add https://ui.isaiahbjork.com/pricing-table.json"
  },
  "type": "registry:component"
}