{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "marketing-pricing",
  "title": "Marketing pricing",
  "description": "Marketing pricing is a production-ready blocks component from BJORK UI, packaged for shadcn registries.",
  "dependencies": [
    "clsx",
    "framer-motion",
    "lucide-react",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "components/bjork-ui/blocks/marketing-pricing.tsx",
      "content": "\"use client\";\n\nimport { Fragment, useId, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { Check, ChevronDown, Minus } from \"lucide-react\";\nimport {\n  blockFrame,\n  blockToneProps,\n  focusRing,\n  Reveal,\n  SectionHeader,\n  useBlockReducedMotion,\n  type BlockTone,\n} from \"@/components/bjork-ui/blocks/marketing-kit\";\nimport { cn } from \"@/lib/utils\";\n\nexport type PricingBillingPeriod = \"monthly\" | \"annual\";\n\nexport interface MarketingPricingPlan {\n  id: string;\n  name: string;\n  blurb: string;\n  /** Price per month when billed monthly. `null` shows `customLabel`. */\n  monthly: number | null;\n  /** Effective price per month when billed annually. */\n  annual: number | null;\n  customLabel?: string;\n  /** Shown under the price, such as \"per month, 5 seats included\". */\n  unit?: string;\n  cta: { label: string; href: string };\n  highlighted?: boolean;\n  /** Badge on the highlighted plan. */\n  badge?: string;\n  /** Line above the feature list, such as \"Everything in Hobby, plus\". */\n  featuresHeading?: string;\n  features: string[];\n}\n\nexport type ComparisonValue = boolean | string;\n\nexport interface ComparisonGroup {\n  title: string;\n  rows: { label: string; values: Record<string, ComparisonValue> }[];\n}\n\nexport interface MarketingPricingProps {\n  eyebrow?: string;\n  title: ReactNode;\n  description?: ReactNode;\n  plans: MarketingPricingPlan[];\n  /** Feature comparison under the cards. Omit to hide it. */\n  comparison?: ComparisonGroup[];\n  defaultCompareOpen?: boolean;\n  defaultBilling?: PricingBillingPeriod;\n  /** Controlled billing period. */\n  billing?: PricingBillingPeriod;\n  onBillingChange?: (billing: PricingBillingPeriod) => void;\n  /** Called when a plan's button is pressed, before the link navigates. */\n  onPlanSelect?: (planId: string, billing: PricingBillingPeriod) => void;\n  /** Label next to the toggle, such as \"2 months free\". Defaults to the largest saving. */\n  savingsLabel?: string;\n  currency?: string;\n  footnote?: ReactNode;\n  tone?: BlockTone;\n  /** Play the in-view and price motion. Turn off for screenshots. */\n  animate?: boolean;\n  className?: string;\n}\n\nexport const MARKETING_PRICING_SAMPLE: MarketingPricingProps = {\n  eyebrow: \"Pricing\",\n  title: \"Pay for traces, not seats.\",\n  description:\n    \"Every plan includes the whole team. Start free, and move up when your agents do more than you can read by hand.\",\n  plans: [\n    {\n      id: \"hobby\",\n      name: \"Hobby\",\n      blurb: \"For a side project or a first agent.\",\n      monthly: 0,\n      annual: 0,\n      unit: \"free forever\",\n      cta: { label: \"Start free\", href: \"#signup\" },\n      features: [\"10,000 traces a month\", \"7-day retention\", \"Replay and search\", \"Community support\"],\n    },\n    {\n      id: \"team\",\n      name: \"Team\",\n      blurb: \"For agents your customers talk to.\",\n      monthly: 79,\n      annual: 64,\n      unit: \"per month, whole team\",\n      cta: { label: \"Start 14-day trial\", href: \"#trial\" },\n      highlighted: true,\n      badge: \"Most teams\",\n      featuresHeading: \"Everything in Hobby, plus\",\n      features: [\n        \"500,000 traces a month\",\n        \"30-day retention\",\n        \"Alerts on any span\",\n        \"Cost per run and per user\",\n        \"Masking rules for personal data\",\n      ],\n    },\n    {\n      id: \"scale\",\n      name: \"Scale\",\n      blurb: \"For many agents across many teams.\",\n      monthly: 349,\n      annual: 279,\n      unit: \"per month, whole org\",\n      cta: { label: \"Choose Scale\", href: \"#scale\" },\n      featuresHeading: \"Everything in Team, plus\",\n      features: [\"5M traces a month\", \"1-year retention\", \"SSO and audit log\", \"Dedicated region\", \"Support within 4 hours\"],\n    },\n  ],\n  comparison: [\n    {\n      title: \"Tracing\",\n      rows: [\n        { label: \"Traces per month\", values: { hobby: \"10k\", team: \"500k\", scale: \"5M\" } },\n        { label: \"Retention\", values: { hobby: \"7 days\", team: \"30 days\", scale: \"1 year\" } },\n        { label: \"Replay from any step\", values: { hobby: true, team: true, scale: true } },\n        { label: \"OpenTelemetry export\", values: { hobby: false, team: true, scale: true } },\n      ],\n    },\n    {\n      title: \"Monitoring\",\n      rows: [\n        { label: \"Alerts\", values: { hobby: false, team: \"25 rules\", scale: \"Unlimited\" } },\n        { label: \"Cost tracking\", values: { hobby: \"Per run\", team: \"Per run and user\", scale: \"Per run and user\" } },\n        { label: \"Evaluations\", values: { hobby: false, team: false, scale: true } },\n      ],\n    },\n    {\n      title: \"Security\",\n      rows: [\n        { label: \"Personal data masking\", values: { hobby: \"Default rules\", team: \"Custom rules\", scale: \"Custom rules\" } },\n        { label: \"SSO and SCIM\", values: { hobby: false, team: false, scale: true } },\n        { label: \"Audit log\", values: { hobby: false, team: false, scale: true } },\n      ],\n    },\n  ],\n  footnote: \"Prices in USD before tax. Traces over your plan are billed at $0.40 per thousand, and we email you before that happens.\",\n};\n\n/**\n * A pricing section: billing toggle, plan cards with prices that roll when the period changes, and a\n * feature comparison that opens below. The comparison is a real table, scrollable with a pinned first\n * column on narrow screens.\n */\nexport function MarketingPricing({\n  eyebrow,\n  title,\n  description,\n  plans,\n  comparison,\n  defaultCompareOpen = false,\n  defaultBilling = \"annual\",\n  billing: controlledBilling,\n  onBillingChange,\n  onPlanSelect,\n  savingsLabel,\n  currency = \"$\",\n  footnote,\n  tone = \"auto\",\n  animate = true,\n  className,\n}: MarketingPricingProps) {\n  const titleId = useId();\n  const toneProps = blockToneProps(tone);\n  const [uncontrolled, setUncontrolled] = useState<PricingBillingPeriod>(defaultBilling);\n  const billing = controlledBilling ?? uncontrolled;\n  const setBilling = (next: PricingBillingPeriod) => {\n    if (controlledBilling === undefined) setUncontrolled(next);\n    onBillingChange?.(next);\n  };\n\n  const bestSaving = plans.reduce((best, plan) => {\n    if (!plan.monthly || plan.annual === null) return best;\n    return Math.max(best, Math.round((1 - plan.annual / plan.monthly) * 100));\n  }, 0);\n  const savings = savingsLabel ?? (bestSaving > 0 ? `Save ${bestSaving}%` : undefined);\n\n  const columns =\n    plans.length >= 4\n      ? \"@4xl:max-w-[1120px] @4xl:grid-cols-4\"\n      : plans.length === 3\n        ? \"@4xl:max-w-[1120px] @4xl:grid-cols-3\"\n        : \"@3xl:max-w-[760px] @3xl:grid-cols-2\";\n\n  return (\n    <section\n      aria-labelledby={titleId}\n      {...toneProps}\n      className={cn(\n        \"@container w-full bg-[var(--bjork-bg)] py-20 font-bjork-alpha text-[color:var(--bjork-text)] @3xl:py-28\",\n        toneProps.className,\n        className,\n      )}\n    >\n      <div className={blockFrame}>\n        <SectionHeader id={titleId} eyebrow={eyebrow} title={title} description={description} align=\"center\" className=\"mx-auto\">\n          <div className=\"mt-4 flex flex-wrap items-center justify-center gap-3\">\n            <BillingToggle value={billing} onChange={setBilling} animate={animate} />\n            {savings ? (\n              <span\n                className={cn(\n                  \"rounded-full border border-[color:var(--bjork-accent-muted)] bg-[var(--bjork-accent-soft)] px-2.5 py-1 font-mono text-[11px] text-[color:var(--bjork-accent)] transition-opacity duration-200\",\n                  billing === \"annual\" ? \"opacity-100\" : \"opacity-60\",\n                )}\n              >\n                {savings}\n                <span className=\"sr-only\"> with annual billing</span>\n              </span>\n            ) : null}\n          </div>\n        </SectionHeader>\n\n        <ul className={cn(\"mx-auto mt-12 grid max-w-[480px] grid-cols-1 gap-3 @3xl:mt-16\", columns)}>\n          {plans.map((plan, index) => {\n            const card = (\n              <PlanCard plan={plan} billing={billing} currency={currency} animate={animate} onPlanSelect={onPlanSelect} />\n            );\n            return animate ? (\n              <Reveal key={plan.id} as=\"li\" delay={index * 0.06} className=\"flex\">\n                {card}\n              </Reveal>\n            ) : (\n              <li key={plan.id} className=\"flex\">\n                {card}\n              </li>\n            );\n          })}\n        </ul>\n\n        {comparison && comparison.length > 0 ? (\n          <Comparison plans={plans} groups={comparison} defaultOpen={defaultCompareOpen} />\n        ) : null}\n\n        {footnote ? (\n          <p className=\"mx-auto mt-10 max-w-[64ch] text-center text-[13px] leading-6 text-[color:var(--bjork-text-muted)]\">\n            {footnote}\n          </p>\n        ) : null}\n      </div>\n    </section>\n  );\n}\n\nconst PERIODS: { value: PricingBillingPeriod; label: string }[] = [\n  { value: \"monthly\", label: \"Monthly\" },\n  { value: \"annual\", label: \"Annual\" },\n];\n\nfunction BillingToggle({\n  value,\n  onChange,\n  animate,\n}: {\n  value: PricingBillingPeriod;\n  onChange: (value: PricingBillingPeriod) => void;\n  animate: boolean;\n}) {\n  const reduce = useBlockReducedMotion();\n  const pillId = useId();\n  const refs = useRef<(HTMLButtonElement | null)[]>([]);\n\n  // Radio group keyboard model: arrows move the selection and the focus together.\n  const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n    const keys = [\"ArrowLeft\", \"ArrowRight\", \"ArrowUp\", \"ArrowDown\", \"Home\", \"End\"];\n    if (!keys.includes(event.key)) return;\n    event.preventDefault();\n    let next = index;\n    if (event.key === \"Home\") next = 0;\n    else if (event.key === \"End\") next = PERIODS.length - 1;\n    else next = (index + (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\" ? -1 : 1) + PERIODS.length) % PERIODS.length;\n    onChange(PERIODS[next].value);\n    refs.current[next]?.focus();\n  };\n\n  return (\n    <div\n      role=\"radiogroup\"\n      aria-label=\"Billing period\"\n      className=\"relative flex rounded-[13px] border border-[color:var(--bjork-border)] bg-[var(--bjork-field-inset)] p-1 shadow-[var(--bjork-shadow-inset)]\"\n    >\n      {PERIODS.map((period, index) => {\n        const checked = value === period.value;\n        return (\n          <button\n            key={period.value}\n            ref={(node) => {\n              refs.current[index] = node;\n            }}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={checked}\n            tabIndex={checked ? 0 : -1}\n            onClick={() => onChange(period.value)}\n            onKeyDown={(event) => onKeyDown(event, index)}\n            className={cn(\n              \"relative h-9 rounded-[10px] px-4 text-[13.5px] font-medium transition-colors duration-150\",\n              checked ? \"text-[color:var(--bjork-text)]\" : \"text-[color:var(--bjork-text-muted)] hover:text-[color:var(--bjork-text-medium)]\",\n              focusRing,\n            )}\n          >\n            {checked ? (\n              <motion.span\n                layoutId={animate && !reduce ? pillId : undefined}\n                transition={{ type: \"spring\", stiffness: 420, damping: 34 }}\n                className=\"absolute inset-0 rounded-[10px] border border-[color:var(--bjork-border)] bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-surface)]\"\n              />\n            ) : null}\n            <span className=\"relative\">{period.label}</span>\n          </button>\n        );\n      })}\n    </div>\n  );\n}\n\nfunction formatPrice(value: number, currency: string) {\n  return `${currency}${value.toLocaleString(\"en-US\")}`;\n}\n\nfunction PlanCard({\n  plan,\n  billing,\n  currency,\n  animate,\n  onPlanSelect,\n}: {\n  plan: MarketingPricingPlan;\n  billing: PricingBillingPeriod;\n  currency: string;\n  animate: boolean;\n  onPlanSelect?: (planId: string, billing: PricingBillingPeriod) => void;\n}) {\n  const reduce = useBlockReducedMotion();\n  const nameId = useId();\n  const price = billing === \"annual\" ? plan.annual : plan.monthly;\n  const priceText = price === null ? plan.customLabel ?? \"Custom\" : formatPrice(price, currency);\n  const roll = animate && !reduce;\n  const direction = billing === \"annual\" ? 1 : -1;\n  const yearly =\n    billing === \"annual\" && plan.annual ? `${formatPrice(plan.annual * 12, currency)} billed yearly` : null;\n  const monthlyNote =\n    billing === \"monthly\" && plan.annual && plan.monthly && plan.annual < plan.monthly\n      ? `${formatPrice(plan.annual, currency)}/mo billed yearly`\n      : null;\n\n  return (\n    <article\n      aria-labelledby={nameId}\n      className={cn(\n        \"relative flex w-full flex-col rounded-[22px] border p-6 @xl:p-7\",\n        plan.highlighted\n          ? \"border-[color:var(--bjork-accent-muted)] bg-[var(--bjork-surface)] shadow-[0_0_0_4px_var(--bjork-accent-soft),var(--bjork-shadow-panel)]\"\n          : \"border-[color:var(--bjork-border)] bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-panel)]\",\n      )}\n    >\n      <div className=\"flex items-center justify-between gap-3\">\n        <h3 id={nameId} className=\"text-[17px] font-semibold tracking-[-0.02em]\">\n          {plan.name}\n        </h3>\n        {plan.highlighted && plan.badge ? (\n          <span className=\"rounded-full bg-[var(--bjork-accent-soft)] px-2.5 py-0.5 font-mono text-[10.5px] uppercase tracking-[0.1em] text-[color:var(--bjork-accent)]\">\n            {plan.badge}\n          </span>\n        ) : null}\n      </div>\n      <p className=\"mt-1.5 text-[14px] leading-6 text-[color:var(--bjork-text-muted)]\">{plan.blurb}</p>\n\n      <div className=\"mt-6 flex items-end gap-2\">\n        <span className=\"relative inline-flex h-[52px] items-end overflow-hidden font-bjork-display text-[48px] font-semibold leading-none tracking-[-0.045em] tabular-nums\">\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            <motion.span\n              key={priceText}\n              initial={roll ? { y: 28 * direction, opacity: 0 } : { opacity: 0 }}\n              animate={{ y: 0, opacity: 1 }}\n              exit={roll ? { y: -28 * direction, opacity: 0 } : { opacity: 0 }}\n              transition={roll ? { type: \"spring\", stiffness: 380, damping: 32 } : { duration: 0.15 }}\n              className=\"block\"\n            >\n              {priceText}\n            </motion.span>\n          </AnimatePresence>\n        </span>\n      </div>\n      <p className=\"mt-2 min-h-[40px] text-[13px] leading-5 text-[color:var(--bjork-text-muted)]\">\n        {plan.unit}\n        {yearly || monthlyNote ? (\n          <span className=\"block text-[color:var(--bjork-text-soft)]\">{yearly ?? monthlyNote}</span>\n        ) : null}\n      </p>\n\n      <a\n        href={plan.cta.href}\n        onClick={() => onPlanSelect?.(plan.id, billing)}\n        aria-describedby={nameId}\n        className={cn(\n          \"mt-6 inline-flex h-11 items-center justify-center rounded-[13px] px-4 text-[14px] font-medium tracking-[-0.01em] transition-[transform,background-color,border-color] duration-150 active:scale-[0.97] motion-reduce:transition-none motion-reduce:active:scale-100\",\n          plan.highlighted\n            ? \"bjork-layered-button-accent text-white\"\n            : \"border border-[color:var(--bjork-border-strong)] bg-[var(--bjork-surface)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-soft)] hover:bg-[var(--bjork-surface-hover)]\",\n          focusRing,\n        )}\n      >\n        {plan.cta.label}\n      </a>\n\n      <div className=\"mt-7 border-t border-[color:var(--bjork-border-muted)] pt-6\">\n        {plan.featuresHeading ? (\n          <p className=\"mb-3 text-[12.5px] font-medium text-[color:var(--bjork-text-medium)]\">{plan.featuresHeading}</p>\n        ) : null}\n        <ul className=\"flex flex-col gap-2.5\">\n          {plan.features.map((feature) => (\n            <li key={feature} className=\"flex items-start gap-2.5 text-[14px] leading-5 text-[color:var(--bjork-text-medium)]\">\n              <Check\n                aria-hidden\n                className={cn(\n                  \"mt-0.5 size-4 shrink-0\",\n                  plan.highlighted ? \"text-[color:var(--bjork-accent)]\" : \"text-[color:var(--bjork-text-soft)]\",\n                )}\n              />\n              {feature}\n            </li>\n          ))}\n        </ul>\n      </div>\n    </article>\n  );\n}\n\nfunction Comparison({\n  plans,\n  groups,\n  defaultOpen,\n}: {\n  plans: MarketingPricingPlan[];\n  groups: ComparisonGroup[];\n  defaultOpen: boolean;\n}) {\n  const [open, setOpen] = useState(defaultOpen);\n  const reduce = useBlockReducedMotion();\n  const panelId = useId();\n  const captionId = useId();\n\n  return (\n    <div className=\"mx-auto mt-10 max-w-[1120px]\">\n      <div className=\"flex justify-center\">\n        <button\n          type=\"button\"\n          aria-expanded={open}\n          aria-controls={panelId}\n          onClick={() => setOpen((value) => !value)}\n          className={cn(\n            \"group/compare inline-flex h-10 items-center gap-2 rounded-full px-4 text-[14px] text-[color:var(--bjork-text-medium)] transition-colors duration-150 hover:bg-[var(--bjork-surface-hover)] hover:text-[color:var(--bjork-text)]\",\n            focusRing,\n          )}\n        >\n          {open ? \"Hide the full comparison\" : \"Compare every feature\"}\n          <ChevronDown\n            aria-hidden\n            className={cn(\n              \"size-4 transition-transform duration-200 ease-out motion-reduce:transition-none\",\n              open && \"rotate-180\",\n            )}\n          />\n        </button>\n      </div>\n\n      <AnimatePresence initial={false}>\n        {open ? (\n          <motion.div\n            key=\"panel\"\n            id={panelId}\n            initial={reduce ? { opacity: 0 } : { opacity: 0, height: 0 }}\n            animate={reduce ? { opacity: 1 } : { opacity: 1, height: \"auto\" }}\n            exit={reduce ? { opacity: 0 } : { opacity: 0, height: 0 }}\n            transition={{ duration: reduce ? 0.15 : 0.35, ease: [0.23, 1, 0.32, 1] }}\n            className=\"overflow-hidden\"\n          >\n            <div\n              role=\"region\"\n              aria-labelledby={captionId}\n              tabIndex={0}\n              className={cn(\n                \"mt-6 overflow-x-auto rounded-[20px] border border-[color:var(--bjork-border)] bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-panel)]\",\n                focusRing,\n              )}\n            >\n              <table className=\"w-full min-w-[560px] border-collapse text-left text-[14px]\">\n                <caption id={captionId} className=\"sr-only\">\n                  Feature comparison by plan\n                </caption>\n                <thead>\n                  <tr className=\"border-b border-[color:var(--bjork-border)]\">\n                    <th\n                      scope=\"col\"\n                      className=\"sticky left-0 z-10 w-[34%] bg-[var(--bjork-surface)] px-5 py-4 font-mono text-[10.5px] font-normal uppercase tracking-[0.12em] text-[color:var(--bjork-text-soft)]\"\n                    >\n                      Feature\n                    </th>\n                    {plans.map((plan) => (\n                      <th\n                        key={plan.id}\n                        scope=\"col\"\n                        className={cn(\n                          \"px-4 py-4 text-center text-[14px] font-semibold\",\n                          plan.highlighted && \"text-[color:var(--bjork-accent)]\",\n                        )}\n                      >\n                        {plan.name}\n                      </th>\n                    ))}\n                  </tr>\n                </thead>\n                <tbody>\n                  {groups.map((group) => (\n                    <Fragment key={group.title}>\n                      <tr className=\"bg-[var(--bjork-panel)]\">\n                        <th\n                          scope=\"colgroup\"\n                          colSpan={plans.length + 1}\n                          className=\"px-0 py-2.5 font-mono text-[10.5px] font-normal uppercase tracking-[0.12em] text-[color:var(--bjork-text-muted)]\"\n                        >\n                          {/* A full-width cell cannot pin, so the label inside it does. */}\n                          <span className=\"sticky left-0 inline-block px-5\">{group.title}</span>\n                        </th>\n                      </tr>\n                      {group.rows.map((row) => (\n                        <tr key={row.label} className=\"border-t border-[color:var(--bjork-border-muted)]\">\n                          <th\n                            scope=\"row\"\n                            className=\"sticky left-0 z-10 bg-[var(--bjork-surface)] px-5 py-3.5 font-normal text-[color:var(--bjork-text-medium)]\"\n                          >\n                            {row.label}\n                          </th>\n                          {plans.map((plan) => (\n                            <td\n                              key={plan.id}\n                              className={cn(\n                                \"px-4 py-3.5 text-center tabular-nums\",\n                                plan.highlighted && \"bg-[color-mix(in_srgb,var(--bjork-accent-soft)_55%,transparent)]\",\n                              )}\n                            >\n                              <ComparisonCell value={row.values[plan.id]} highlighted={plan.highlighted} />\n                            </td>\n                          ))}\n                        </tr>\n                      ))}\n                    </Fragment>\n                  ))}\n                </tbody>\n              </table>\n            </div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  );\n}\n\nfunction ComparisonCell({ value, highlighted }: { value: ComparisonValue | undefined; highlighted?: boolean }) {\n  if (value === true) {\n    return (\n      <>\n        <Check\n          aria-hidden\n          className={cn(\n            \"mx-auto size-4\",\n            highlighted ? \"text-[color:var(--bjork-accent)]\" : \"text-[color:var(--bjork-text-medium)]\",\n          )}\n        />\n        <span className=\"sr-only\">Included</span>\n      </>\n    );\n  }\n  if (value === false || value === undefined) {\n    return (\n      <>\n        <Minus aria-hidden className=\"mx-auto size-4 text-[color:var(--bjork-text-faint)]\" />\n        <span className=\"sr-only\">Not included</span>\n      </>\n    );\n  }\n  return <span className=\"text-[13.5px] text-[color:var(--bjork-text)]\">{value}</span>;\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/blocks/marketing-pricing.tsx"
    },
    {
      "path": "components/bjork-ui/blocks/marketing-kit.tsx",
      "content": "\"use client\";\n\nimport {\n  useSyncExternalStore,\n  type CSSProperties,\n  type MouseEventHandler,\n  type ReactNode,\n} from \"react\";\nimport { motion, useReducedMotion } from \"framer-motion\";\nimport { ArrowRight } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Shared pieces for the marketing blocks: tone scoping, hydration-safe reduced motion,\n * the section heading, call-to-action links and the in-view reveal.\n */\n\nexport type BlockTone = \"light\" | \"dark\" | \"auto\";\n\nconst subscribeNoop = () => () => {};\n\n/** `true` once hydrated and the visitor asked for reduced motion. The server and first client render agree on `false`. */\nexport function useBlockReducedMotion(): boolean {\n  const hydrated = useSyncExternalStore(subscribeNoop, () => true, () => false);\n  const prefers = useReducedMotion();\n  return hydrated && prefers === true;\n}\n\n/**\n * Scopes the library tokens to one block. \"light\" and \"dark\" re-declare every `--bjork-*` token on the\n * section through the `.light` / `.dark` classes; \"auto\" inherits whatever the page sets.\n * Blocks only colour through these tokens, never through `dark:` variants, so a forced tone holds.\n */\nexport function blockToneProps(tone: BlockTone): { className?: string; style?: CSSProperties } {\n  if (tone === \"auto\") return {};\n  return { className: tone, style: { colorScheme: tone } };\n}\n\n/** Focus ring used by every interactive element in the blocks. */\nexport const focusRing =\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/** Section padding and width that every block shares, so stacked blocks line up. */\nexport const blockFrame = \"mx-auto w-full max-w-[1200px] px-5 @2xl:px-8 @5xl:px-10\";\n\nexport interface BlockLink {\n  label: string;\n  href: string;\n  /** Optional click handler; the link still navigates unless you call `preventDefault`. */\n  onClick?: MouseEventHandler<HTMLAnchorElement>;\n}\n\nexport function Eyebrow({ children, className }: { children: ReactNode; className?: string }) {\n  return (\n    <p\n      className={cn(\n        \"flex items-center gap-2 font-mono text-[11px] uppercase leading-4 tracking-[0.12em] text-[color:var(--bjork-text-muted)]\",\n        className,\n      )}\n    >\n      <span aria-hidden className=\"size-1.5 rounded-full bg-[color:var(--bjork-accent)]\" />\n      {children}\n    </p>\n  );\n}\n\nexport interface SectionHeaderProps {\n  id: string;\n  eyebrow?: ReactNode;\n  title: ReactNode;\n  description?: ReactNode;\n  align?: \"start\" | \"center\";\n  as?: \"h1\" | \"h2\" | \"h3\";\n  className?: string;\n  children?: ReactNode;\n}\n\nexport function SectionHeader({\n  id,\n  eyebrow,\n  title,\n  description,\n  align = \"start\",\n  as: Heading = \"h2\",\n  className,\n  children,\n}: SectionHeaderProps) {\n  return (\n    <div\n      className={cn(\n        \"flex flex-col gap-4\",\n        align === \"center\" ? \"items-center text-center\" : \"items-start text-left\",\n        className,\n      )}\n    >\n      {eyebrow ? <Eyebrow>{eyebrow}</Eyebrow> : null}\n      <Heading\n        id={id}\n        className=\"max-w-[20ch] text-balance font-bjork-display text-[34px] font-semibold leading-[1.02] tracking-[-0.035em] text-[color:var(--bjork-text)] @xl:text-[44px] @4xl:text-[52px]\"\n      >\n        {title}\n      </Heading>\n      {description ? (\n        <p className=\"max-w-[58ch] text-pretty text-[16px] leading-7 text-[color:var(--bjork-text-medium)] @xl:text-[17px]\">\n          {description}\n        </p>\n      ) : null}\n      {children}\n    </div>\n  );\n}\n\ntype CtaVariant = \"primary\" | \"secondary\" | \"ghost\";\n\nconst ctaBase =\n  \"group/cta relative inline-flex h-11 shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-[13px] px-[18px] text-[14px] font-medium tracking-[-0.01em] transition-[transform,background-color,border-color,color] duration-150 ease-out active:scale-[0.97] motion-reduce:transition-none motion-reduce:active:scale-100\";\n\nconst ctaVariants: Record<CtaVariant, string> = {\n  primary: \"bjork-layered-button-accent text-white\",\n  secondary:\n    \"border border-[color:var(--bjork-border-strong)] bg-[var(--bjork-surface)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-soft)] hover:border-[color:var(--bjork-text-faint)] hover:bg-[var(--bjork-surface-hover)]\",\n  ghost:\n    \"px-2 text-[color:var(--bjork-text-medium)] hover:text-[color:var(--bjork-text)]\",\n};\n\nexport function CtaLink({\n  link,\n  variant = \"primary\",\n  arrow = false,\n  className,\n}: {\n  link: BlockLink;\n  variant?: CtaVariant;\n  arrow?: boolean;\n  className?: string;\n}) {\n  return (\n    <a\n      href={link.href}\n      onClick={link.onClick}\n      className={cn(ctaBase, ctaVariants[variant], focusRing, className)}\n    >\n      <span>{link.label}</span>\n      {arrow ? (\n        <ArrowRight\n          aria-hidden\n          className=\"size-4 transition-transform duration-200 ease-out group-hover/cta:translate-x-0.5 motion-reduce:transition-none\"\n        />\n      ) : null}\n    </a>\n  );\n}\n\n/**\n * Fades and lifts its children the first time they scroll into view. Under reduced motion only the\n * fade runs, with no movement.\n */\nexport function Reveal({\n  children,\n  delay = 0,\n  className,\n  as = \"div\",\n}: {\n  children: ReactNode;\n  delay?: number;\n  className?: string;\n  as?: \"div\" | \"li\";\n}) {\n  const reduce = useBlockReducedMotion();\n  const Component = as === \"li\" ? motion.li : motion.div;\n  return (\n    <Component\n      className={className}\n      initial={{ opacity: 0, y: 14 }}\n      whileInView={{ opacity: 1, y: 0 }}\n      viewport={{ once: true, amount: 0.2 }}\n      transition={{\n        opacity: { duration: reduce ? 0.2 : 0.5, ease: [0.23, 1, 0.32, 1], delay: reduce ? 0 : delay },\n        y: reduce ? { duration: 0 } : { type: \"spring\", stiffness: 260, damping: 30, mass: 0.9, delay },\n      }}\n    >\n      {children}\n    </Component>\n  );\n}\n\n/** A small product mark used by the sample content. Swap it for your logo. */\nexport function SampleMark({ className }: { className?: string }) {\n  return (\n    <svg viewBox=\"0 0 24 24\" aria-hidden className={cn(\"size-6\", className)}>\n      <rect x=\"1\" y=\"1\" width=\"22\" height=\"22\" rx=\"7\" fill=\"var(--bjork-text)\" />\n      <path\n        d=\"M6.5 15.5h3l2-7 2 7h4\"\n        fill=\"none\"\n        stroke=\"var(--bjork-bg)\"\n        strokeWidth=\"1.9\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <circle cx=\"17.5\" cy=\"15.5\" r=\"1.6\" fill=\"var(--bjork-accent)\" />\n    </svg>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/blocks/marketing-kit.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"
    }
  ],
  "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": "Blocks",
    "registryUrl": "https://ui.isaiahbjork.com/marketing-pricing.json",
    "installCommand": "npx shadcn@latest add https://ui.isaiahbjork.com/marketing-pricing.json"
  },
  "type": "registry:block"
}