{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agency-footer",
  "title": "Agency: Footer",
  "description": "A newsletter sign-up, the studio's links and address, and the legal line with its local time.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://eased.dev/r/eased-tokens.json",
    "https://eased.dev/r/eased-motion.json",
    "https://eased.dev/r/agency-core.json",
    "https://eased.dev/r/button.json",
    "https://eased.dev/r/field.json",
    "https://eased.dev/r/input.json"
  ],
  "files": [
    {
      "path": "src/registry/eased/templates/agency/footer.tsx",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useRef, useState, useSyncExternalStore, type FormEvent } from \"react\";\nimport { Button } from \"../../button/button\";\nimport { Field } from \"../../field/field\";\nimport { Input } from \"../../input/input\";\nimport { enter, exitFast, reducedFade, useSafeReducedMotion } from \"../../lib/motion\";\nimport { brand, footer } from \"./content\";\nimport { COLUMN, LABEL, useHref } from \"./kit\";\n\nconst LINK = \"focus-ring rounded-control-xs transition-colors duration-(--eased-duration-fast) ease-standard hover-fine:text-text\";\n\n/**\n * The newsletter on the left, the studio's links and address on the right, then the legal line\n * with the studio's local time, so a client abroad knows whether anyone is in, and the legal pages.\n */\nexport function Footer() {\n  const to = useHref();\n  return (\n    <footer className={`${COLUMN} pt-group sm:pt-group-lg`}>\n      <div className=\"grid gap-group border-t border-line-subtle pt-pad-lg pb-group lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:gap-group-lg\">\n        <Newsletter />\n        <div className=\"grid grid-cols-2 gap-pad-lg sm:grid-cols-3\">\n          {footer.columns.map((column) => (\n            <nav key={column.title} aria-label={column.title} className=\"flex flex-col gap-in\">\n              <h2 className={LABEL}>{column.title}</h2>\n              <ul className=\"flex flex-col gap-2 text-ui text-text-secondary\">\n                {column.links.map((link) => (\n                  <li key={link.label}>\n                    <a href={to(link.href)} className={LINK}>\n                      {link.label}\n                    </a>\n                  </li>\n                ))}\n              </ul>\n            </nav>\n          ))}\n          <div className=\"flex flex-col gap-in\">\n            <h2 className={LABEL}>{brand.city}</h2>\n            <address className=\"flex flex-col gap-2 text-ui text-text-secondary not-italic\">\n              {footer.address.map((line) => (\n                <span key={line}>{line}</span>\n              ))}\n            </address>\n          </div>\n        </div>\n      </div>\n      <div className=\"flex flex-wrap items-center justify-between gap-x-pad-md gap-y-in border-t border-line-subtle py-pad-md text-ui-sm text-text-muted\">\n        <p className=\"tabular-nums\">\n          © {footer.year} {brand.company}\n        </p>\n        <div className=\"flex flex-wrap items-center gap-x-pad-md gap-y-in\">\n          <LocalTime />\n          {footer.legal.map((link) => (\n            <a key={link.label} href={to(link.href)} className={LINK}>\n              {link.label}\n            </a>\n          ))}\n        </div>\n      </div>\n    </footer>\n  );\n}\n\nconst TIME = new Intl.DateTimeFormat(\"en-GB\", { hour: \"2-digit\", minute: \"2-digit\", timeZone: brand.timeZone });\n\nconst subscribeMinute = (onChange: () => void) => {\n  const id = window.setInterval(onChange, 15_000);\n  return () => window.clearInterval(id);\n};\n\n/** The studio's time, read on the client only (the server's clock would not match the visitor's render). */\nfunction LocalTime() {\n  const time = useSyncExternalStore(subscribeMinute, () => TIME.format(new Date()), () => \"\");\n  return (\n    <p className=\"tabular-nums\">\n      {brand.city} {time && <time>{time}</time>}\n    </p>\n  );\n}\n\n/**\n * Sign up for field notes: one field, checked on submit (rule 72), its error inline under it.\n * Once in, the form gives way to one line of thanks. Wire `onSubmit` to your mailing list.\n */\nfunction Newsletter() {\n  const reduced = useSafeReducedMotion();\n  const input = useRef<HTMLInputElement>(null);\n  const [error, setError] = useState<string>();\n  const [done, setDone] = useState(false);\n\n  const submit = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const email = new FormData(e.currentTarget).get(\"email\")?.toString().trim() ?? \"\";\n    if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) {\n      setError(email ? \"That address looks incomplete.\" : \"Enter your email address.\");\n      input.current?.focus();\n      return;\n    }\n    setDone(true);\n  };\n\n  return (\n    <div className=\"flex flex-col gap-pad-base\">\n      <div className=\"flex flex-col gap-1\">\n        <h2 className=\"text-ui-md font-medium text-text\">{footer.newsletter.title}</h2>\n        <p className=\"max-w-sm text-ui text-pretty text-text-secondary\">{footer.newsletter.body}</p>\n      </div>\n      <AnimatePresence mode=\"wait\" initial={false}>\n        {done ? (\n          <motion.p\n            key=\"done\"\n            role=\"status\"\n            className=\"text-ui text-text\"\n            initial={{ opacity: 0, y: reduced ? 0 : 4 }}\n            animate={{ opacity: 1, y: 0 }}\n            transition={reduced ? reducedFade : enter}\n          >\n            {footer.newsletter.done}\n          </motion.p>\n        ) : (\n          <motion.form key=\"form\" noValidate onSubmit={submit} className=\"flex max-w-md items-start gap-2\" exit={{ opacity: 0, transition: exitFast }}>\n            <Field error={error} className=\"min-w-0 flex-1\">\n              <Input\n                ref={input}\n                name=\"email\"\n                type=\"email\"\n                autoComplete=\"email\"\n                aria-label={footer.newsletter.placeholder}\n                placeholder={footer.newsletter.placeholder}\n                onChange={() => setError(undefined)}\n              />\n            </Field>\n            <Button type=\"submit\" variant=\"primary\">\n              {footer.newsletter.action}\n            </Button>\n          </motion.form>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/eased/templates/agency/footer.tsx"
    }
  ],
  "meta": {
    "template": "agency",
    "kind": "template-section",
    "industries": [
      "media"
    ]
  },
  "type": "registry:block"
}