{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "footer-columns",
  "type": "registry:block",
  "title": "Column footer with newsletter",
  "description": "Brand blurb with an inline newsletter form, three link columns, payment marks, and legal row.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "footer",
    "any"
  ],
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json",
    "https://storekit-eight.vercel.app/r/sk-analytics.json"
  ],
  "files": [
    {
      "path": "footer/footer-columns.tsx",
      "content": "\"use client\"\n\nimport { useId, useState } from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { ArrowRight, Check } from \"lucide-react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Four-column footer: brand blurb with an inline newsletter form, then Shop,\n   Support, and Company. The bottom row carries generic payment marks (drawn\n   shapes, no third-party logos), legal links, and a supplement disclaimer line.\n   `onSubscribe` may return a promise; the button shows loading until it\n   settles, then the form swaps to a confirmation. */\n\ntype Column = { title: string; links: { label: string; href: string }[] }\n\nconst ring =\n  \"outline-none focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper\"\n\nfunction PaymentMark({ kind }: { kind: \"card\" | \"chip\" | \"bank\" | \"wallet\" }) {\n  const label = { card: \"Credit card\", chip: \"Debit card\", bank: \"Bank transfer\", wallet: \"Digital wallet\" }[kind]\n  return (\n    <span role=\"img\" aria-label={label} className=\"grid h-7 w-11 place-items-center rounded-[min(var(--sk-radius-btn),6px)] border border-sk-line bg-sk-card text-sk-muted\">\n      <svg width=\"28\" height=\"16\" viewBox=\"0 0 28 16\" fill=\"none\" aria-hidden>\n        {kind === \"card\" ? (\n          <>\n            <circle cx=\"11\" cy=\"8\" r=\"5\" fill=\"currentColor\" opacity=\"0.55\" />\n            <circle cx=\"17\" cy=\"8\" r=\"5\" fill=\"currentColor\" opacity=\"0.3\" />\n          </>\n        ) : kind === \"chip\" ? (\n          <>\n            <rect x=\"3\" y=\"4\" width=\"7\" height=\"6\" rx=\"1.5\" stroke=\"currentColor\" strokeWidth=\"1.2\" />\n            <rect x=\"13\" y=\"10\" width=\"12\" height=\"1.6\" rx=\"0.8\" fill=\"currentColor\" opacity=\"0.5\" />\n            <rect x=\"13\" y=\"5\" width=\"8\" height=\"1.6\" rx=\"0.8\" fill=\"currentColor\" opacity=\"0.3\" />\n          </>\n        ) : kind === \"bank\" ? (\n          <>\n            <path d=\"M14 2.5L23 6.5H5L14 2.5Z\" fill=\"currentColor\" opacity=\"0.55\" />\n            <rect x=\"7\" y=\"7.5\" width=\"1.6\" height=\"5\" fill=\"currentColor\" opacity=\"0.5\" />\n            <rect x=\"13.2\" y=\"7.5\" width=\"1.6\" height=\"5\" fill=\"currentColor\" opacity=\"0.5\" />\n            <rect x=\"19.4\" y=\"7.5\" width=\"1.6\" height=\"5\" fill=\"currentColor\" opacity=\"0.5\" />\n            <rect x=\"5\" y=\"13\" width=\"18\" height=\"1.4\" fill=\"currentColor\" opacity=\"0.55\" />\n          </>\n        ) : (\n          <>\n            <rect x=\"5\" y=\"3\" width=\"18\" height=\"11\" rx=\"2\" stroke=\"currentColor\" strokeWidth=\"1.2\" />\n            <rect x=\"15\" y=\"6.5\" width=\"8\" height=\"4\" rx=\"1\" fill=\"currentColor\" opacity=\"0.45\" />\n          </>\n        )}\n      </svg>\n    </span>\n  )\n}\n\nexport function FooterColumns({\n  brandName = \"Morrow\",\n  markSrc,\n  blurb = \"Daily wellness, made simple. Clean ingredients, third-party tested and made in the USA.\",\n  columns = [\n    { title: \"Shop\", links: [{ label: \"All products\", href: \"#shop\" }, { label: \"Powders\", href: \"#powders\" }, { label: \"Drinks\", href: \"#drinks\" }, { label: \"Bundles\", href: \"#bundles\" }] },\n    { title: \"Support\", links: [{ label: \"Lab results\", href: \"#lab-results\" }, { label: \"Shipping\", href: \"#shipping\" }, { label: \"Returns\", href: \"#returns\" }, { label: \"Contact\", href: \"#contact\" }] },\n    { title: \"Company\", links: [{ label: \"About\", href: \"#about\" }, { label: \"Quality\", href: \"#quality\" }, { label: \"Wholesale\", href: \"#wholesale\" }, { label: \"Journal\", href: \"#journal\" }] },\n  ],\n  legal = [\n    { label: \"Terms\", href: \"#terms\" },\n    { label: \"Privacy\", href: \"#privacy\" },\n    { label: \"Refunds\", href: \"#refunds\" },\n  ],\n  disclaimer = \"These statements have not been evaluated by the FDA. Not intended to diagnose, treat, cure, or prevent any disease.\",\n  onSubscribe = () => new Promise<void>((r) => setTimeout(r, 900)),\n  className,\n}: {\n  brandName?: string\n  markSrc?: string\n  blurb?: string\n  columns?: Column[]\n  legal?: { label: string; href: string }[]\n  /** Small print under the bottom row. */\n  disclaimer?: string\n  onSubscribe?: (email: string) => void | Promise<void>\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"footer\", \"footer-columns\")\n  const emailId = useId()\n  const [email, setEmail] = useState(\"\")\n  const [state, setState] = useState<\"idle\" | \"loading\" | \"done\">(\"idle\")\n\n  const submit = async (e: React.FormEvent) => {\n    e.preventDefault()\n    if (state !== \"idle\") return\n    setState(\"loading\")\n    try {\n      await onSubscribe(email)\n      setState(\"done\")\n      sk.emit(\"convert\", \"email_submit\")\n    } catch {\n      setState(\"idle\")\n    }\n  }\n\n  const year = new Date().getFullYear()\n\n  return (\n    <footer ref={sk.ref} {...sk.attrs} className={cn(\"border-t border-sk-line bg-sk-paper text-sk-ink\", className)}>\n      <div className=\"mx-auto grid max-w-6xl gap-10 px-4 py-14 sm:px-8 md:grid-cols-[1.6fr_3fr]\">\n        <div className=\"max-w-sm\">\n          <a href=\"/\" className={cn(\"inline-flex min-h-11 items-center gap-2.5 rounded-sk-btn pr-1\", ring)}>\n            {markSrc ? <img src={markSrc} alt=\"\" className=\"h-7 w-7 object-contain\" /> : null}\n            <span className=\"font-sk-display text-[15px] font-semibold tracking-[-0.02em]\">{brandName}</span>\n          </a>\n          <p className=\"mt-3 text-sm leading-[1.5] text-sk-muted\">{blurb}</p>\n\n          <div className=\"mt-6\">\n            <AnimatePresence mode=\"wait\" initial={false}>\n              {state === \"done\" ? (\n                <motion.p\n                  key=\"done\"\n                  role=\"status\"\n                  initial={{ opacity: 0, y: reduce ? 0 : 8 }}\n                  animate={{ opacity: 1, y: 0 }}\n                  transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}\n                  className=\"flex h-11 items-center gap-2 text-sm\"\n                >\n                  <span className=\"grid h-5 w-5 place-items-center rounded-full bg-sk-savings/15 text-sk-savings\">\n                    <Check size={12} aria-hidden />\n                  </span>\n                  You are on the list. New flavors land in your inbox first.\n                </motion.p>\n              ) : (\n                <motion.form\n                  key=\"form\"\n                  onSubmit={submit}\n                  exit={{ opacity: 0, y: reduce ? 0 : -4, transition: { duration: 0.15 } }}\n                  className=\"relative\"\n                >\n                  <label htmlFor={emailId} className=\"mb-1.5 block text-[13px] font-medium\">New flavors and restocks</label>\n                  <input\n                    id={emailId}\n                    type=\"email\"\n                    required\n                    autoComplete=\"email\"\n                    value={email}\n                    onChange={(e) => setEmail(e.target.value)}\n                    placeholder=\"you@lab.edu\"\n                    className=\"h-11 w-full rounded-sk-btn border border-sk-line bg-sk-card pl-3.5 pr-12 text-base text-sk-ink outline-none sm:text-sm transition-[border-color,box-shadow] duration-200 placeholder:text-sk-muted/70 focus:border-sk-accent focus:ring-[3px] focus:ring-sk-accent/15\"\n                  />\n                  <button\n                    type=\"submit\"\n                    aria-label=\"Subscribe\"\n                    disabled={state === \"loading\"}\n                    aria-busy={state === \"loading\" || undefined}\n                    className={cn(\n                      \"absolute bottom-1 right-1 grid h-9 w-9 place-items-center rounded-sk-btn bg-sk-accent text-sk-accent-ink transition-[transform,filter] duration-200 hover:brightness-[1.06] active:scale-[0.97] disabled:opacity-70\",\n                      ring\n                    )}\n                  >\n                    {state === \"loading\" ? (\n                      <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n                    ) : (\n                      <ArrowRight size={16} aria-hidden />\n                    )}\n                  </button>\n                </motion.form>\n              )}\n            </AnimatePresence>\n          </div>\n        </div>\n\n        <div className=\"grid grid-cols-2 gap-x-6 gap-y-8 sm:grid-cols-3\">\n          {columns.map((c) => (\n            <nav key={c.title} aria-label={c.title}>\n              <p className=\"font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted\">{c.title}</p>\n              <ul className=\"mt-3 grid\">\n                {c.links.map((l) => (\n                  <li key={l.href}>\n                    <a href={l.href} className={cn(\"inline-flex min-h-10 items-center rounded-sk-btn text-sm text-sk-ink/75 transition-colors duration-200 hover:text-sk-ink\", ring)}>\n                      {l.label}\n                    </a>\n                  </li>\n                ))}\n              </ul>\n            </nav>\n          ))}\n        </div>\n      </div>\n\n      <div className=\"border-t border-sk-line\">\n        <div className=\"mx-auto flex max-w-6xl flex-col gap-4 px-4 py-6 sm:px-8 md:flex-row md:items-center md:justify-between\">\n          <div className=\"flex items-center gap-2\" aria-label=\"Accepted payment methods\" role=\"list\">\n            {([\"card\", \"chip\", \"bank\", \"wallet\"] as const).map((k) => (\n              <span role=\"listitem\" key={k}><PaymentMark kind={k} /></span>\n            ))}\n          </div>\n          <ul className=\"flex flex-wrap items-center gap-x-4 text-[13px] text-sk-muted\">\n            <li className=\"w-full sm:w-auto\">&copy; {year} {brandName}</li>\n            {legal.map((l) => (\n              <li key={l.href}>\n                <a href={l.href} className={cn(\"inline-flex min-h-10 items-center rounded-sk-btn transition-colors duration-200 hover:text-sk-ink\", ring)}>{l.label}</a>\n              </li>\n            ))}\n          </ul>\n        </div>\n        <p className=\"mx-auto max-w-6xl px-4 pb-6 font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted sm:px-8\">\n          {disclaimer}\n        </p>\n      </div>\n    </footer>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/storekit/footer/footer-columns.tsx"
    }
  ],
  "docs": "onSubscribe may return a promise; the submit button shows loading until it settles.",
  "meta": {
    "section": "footer",
    "variant": "columns",
    "niche": [
      "any"
    ],
    "commerce": [
      "newsletter",
      "payments"
    ],
    "previewHeight": 520
  }
}