{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "template-auth-kit",
  "title": "Auth Kit: complete sign-in flow",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "Seven auth pages in three switchable layouts (centered, split with art, frosted glass): sign in, sign up with a live password checklist, six-digit verification, forgot and reset password, team invite and a three-step onboarding.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/aurora-background.json",
    "https://ui.ballmac.com/r/input-otp.json",
    "https://ui.ballmac.com/r/segmented-control.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/templates/auth-kit/auth-kit-fonts.ts",
      "content": "// Ballmac UI: Auth Kit template fonts. https://ui.ballmac.com/templates/template-auth-kit\nimport { Outfit } from \"next/font/google\"\n\nexport const authSans = Outfit({ variable: \"--auth-sans\", subsets: [\"latin\"], display: \"swap\", preload: false })\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/auth-kit/auth-kit-fonts.ts"
    },
    {
      "path": "registry/ballmac/components/templates/auth-kit/auth-kit-theme.tsx",
      "content": "// Ballmac UI: Auth Kit template frame. https://ui.ballmac.com/templates/template-auth-kit\n\"use client\"\n\nimport * as React from \"react\"\nimport { Eye, EyeOff, Loader2 } from \"lucide-react\"\n\nimport { AuroraBackground } from \"@/components/ballmac/aurora-background\"\nimport { authSans } from \"@/components/ballmac/templates/auth-kit/auth-kit-fonts\"\nimport { cn } from \"@/lib/utils\"\n\ntype AuthLayout = \"centered\" | \"split\" | \"glass\"\ntype AuthPage = \"sign-in\" | \"sign-up\" | \"verify\" | \"forgot\" | \"reset\" | \"invite\" | \"onboarding\"\ntype AuthHrefs = Record<AuthPage, string>\n\nconst defaultHrefs: AuthHrefs = {\n  \"sign-in\": \"/auth/sign-in\",\n  \"sign-up\": \"/auth/sign-up\",\n  verify: \"/auth/verify\",\n  forgot: \"/auth/forgot\",\n  reset: \"/auth/reset\",\n  invite: \"/auth/invite\",\n  onboarding: \"/auth/onboarding\",\n}\n\n/** Auth Kit's palette: soft ivory and ink with one indigo. Friendly, clear and calm. */\nconst authCss = `\n.auth-theme,body:not(:has([data-gallery])):has(.auth-theme){--background:oklch(0.982 0.006 90);--foreground:oklch(0.21 0.03 275);--card:oklch(1 0 0);--card-foreground:oklch(0.21 0.03 275);--popover:oklch(1 0 0);--popover-foreground:oklch(0.21 0.03 275);--primary:oklch(0.46 0.2 275);--primary-foreground:oklch(0.99 0.004 90);--secondary:oklch(0.958 0.01 90);--secondary-foreground:oklch(0.21 0.03 275);--muted:oklch(0.958 0.01 90);--muted-foreground:oklch(0.47 0.03 275);--accent:oklch(0.94 0.014 280);--accent-foreground:oklch(0.21 0.03 275);--border:oklch(0.21 0.03 275 / 12%);--input:oklch(0.21 0.03 275 / 20%);--ring:oklch(0.55 0.2 275);--surface:oklch(0.968 0.01 90);--destructive:oklch(0.53 0.22 25);--chart-1:oklch(0.5 0.2 275);--chart-2:oklch(0.48 0.12 165);--chart-3:oklch(0.62 0.14 75);--chart-4:oklch(0.52 0.19 340);--chart-5:oklch(0.52 0.12 235);--radius:0.875rem}\n.dark .auth-theme,.dark body:not(:has([data-gallery])):has(.auth-theme){--background:oklch(0.17 0.022 275);--foreground:oklch(0.95 0.008 90);--card:oklch(0.21 0.026 275);--card-foreground:oklch(0.95 0.008 90);--popover:oklch(0.23 0.028 275);--popover-foreground:oklch(0.95 0.008 90);--primary:oklch(0.76 0.14 275);--primary-foreground:oklch(0.19 0.03 275);--secondary:oklch(0.26 0.028 275);--secondary-foreground:oklch(0.95 0.008 90);--muted:oklch(0.25 0.028 275);--muted-foreground:oklch(0.73 0.025 275);--accent:oklch(0.29 0.034 275);--accent-foreground:oklch(0.95 0.008 90);--border:oklch(1 0 0 / 11%);--input:oklch(1 0 0 / 16%);--ring:oklch(0.74 0.14 275);--surface:oklch(0.19 0.024 275);--destructive:oklch(0.7 0.19 25);--chart-1:oklch(0.76 0.14 275);--chart-2:oklch(0.78 0.13 165);--chart-3:oklch(0.83 0.13 85);--chart-4:oklch(0.76 0.15 340);--chart-5:oklch(0.76 0.11 235)}\nbody:not(:has([data-gallery])):has(.auth-theme){font-family:var(--auth-sans),ui-sans-serif,system-ui,sans-serif}\n`\n\nfunction AuthMark({ className }: { className?: string }) {\n  return (\n    <svg viewBox=\"0 0 28 28\" aria-hidden=\"true\" className={cn(\"size-8\", className)} fill=\"none\">\n      <rect width=\"28\" height=\"28\" rx=\"9\" fill=\"var(--primary)\" />\n      <circle cx=\"14\" cy=\"12\" r=\"3.6\" stroke=\"var(--primary-foreground)\" strokeWidth=\"2\" />\n      <path d=\"M14 15.6V21M14 18.5h2.6\" stroke=\"var(--primary-foreground)\" strokeWidth=\"2\" strokeLinecap=\"round\" />\n    </svg>\n  )\n}\n\ntype AuthFrameProps = React.ComponentProps<\"div\"> & {\n  /** How the page is composed. */\n  layout?: AuthLayout\n  /** Brand name beside the mark. */\n  brand?: string\n  /** Where the brand links to. */\n  homeHref?: string\n}\n\nconst quote = { text: \"We replaced three login screens with one in an afternoon, and support tickets about passwords dropped by half.\", name: \"Sasha Petrova\", role: \"Head of Product, Fernhill\" }\n\n/** The page around an auth form: centred card, split with art, or frosted glass over a soft aurora. */\nfunction AuthFrame({ layout = \"centered\", brand = \"Keystone\", homeHref = \"#\", className, style, children, ...props }: AuthFrameProps) {\n  React.useEffect(() => {\n    const cls = authSans.variable\n    if (!cls) return\n    document.body.classList.add(cls)\n    return () => document.body.classList.remove(cls)\n  }, [])\n  const logo = (\n    <a href={homeHref} className=\"focus-visible:ring-ring/50 inline-flex items-center gap-2.5 rounded-lg outline-none focus-visible:ring-[3px]\">\n      <AuthMark /> <span className=\"text-xl font-semibold tracking-tight\">{brand}</span>\n    </a>\n  )\n  return (\n    <div\n      data-slot=\"auth-kit\"\n      data-layout={layout}\n      className={cn(\"auth-theme bg-background text-foreground relative isolate min-h-dvh overflow-x-clip\", authSans.variable, className)}\n      style={{ fontFamily: \"var(--auth-sans), ui-sans-serif, system-ui, sans-serif\", ...style }}\n      {...props}\n    >\n      <style>{authCss}</style>\n      {layout === \"centered\" && (\n        <main className=\"flex min-h-dvh flex-col items-center justify-center px-4 py-12\">\n          <div aria-hidden=\"true\" className=\"absolute inset-0 -z-10 [background-image:radial-gradient(var(--border)_1px,transparent_1px)] [background-size:22px_22px] [mask-image:radial-gradient(ellipse_at_center,black,transparent_70%)]\" />\n          <div className=\"mb-8\">{logo}</div>\n          <div className=\"bg-card w-full max-w-md rounded-3xl border p-7 shadow-[0_24px_60px_-30px_oklch(0.4_0.1_275/0.35)] sm:p-9\">{children}</div>\n          <p className=\"text-muted-foreground mt-8 text-xs\">Protected by two-factor authentication · <a href=\"#\" className=\"underline underline-offset-4\">Privacy</a></p>\n        </main>\n      )}\n      {layout === \"split\" && (\n        <main className=\"grid min-h-dvh lg:grid-cols-[minmax(0,1fr)_minmax(0,1.05fr)]\">\n          <div className=\"flex flex-col px-6 py-8 sm:px-12 lg:px-16\">\n            {logo}\n            <div className=\"mx-auto flex w-full max-w-sm flex-1 flex-col justify-center py-12\">{children}</div>\n            <p className=\"text-muted-foreground text-xs\">© 2026 {brand}. All rights reserved.</p>\n          </div>\n          <aside aria-hidden=\"true\" className=\"bg-primary text-primary-foreground relative hidden overflow-hidden lg:block\">\n            <div className=\"absolute inset-0 [background-image:radial-gradient(circle_at_20%_20%,var(--chart-4)_0,transparent_45%),radial-gradient(circle_at_85%_70%,var(--chart-5)_0,transparent_50%)] opacity-45\" />\n            <div className=\"absolute inset-0 [background-image:linear-gradient(oklch(1_0_0/0.07)_1px,transparent_1px),linear-gradient(90deg,oklch(1_0_0/0.07)_1px,transparent_1px)] [background-size:48px_48px]\" />\n            <figure className=\"relative flex h-full flex-col justify-end p-14\">\n              <blockquote className=\"max-w-md text-3xl leading-snug font-medium tracking-[-0.02em] text-balance\">“{quote.text}”</blockquote>\n              <figcaption className=\"mt-6 text-sm opacity-85\"><span className=\"font-semibold\">{quote.name}</span> · {quote.role}</figcaption>\n            </figure>\n          </aside>\n        </main>\n      )}\n      {layout === \"glass\" && (\n        <main className=\"flex min-h-dvh flex-col items-center justify-center px-4 py-12\">\n          <AuroraBackground aria-hidden=\"true\" className=\"absolute inset-0 -z-10\" colors={[\"var(--chart-1)\", \"var(--chart-4)\", \"var(--chart-5)\"]} intensity={0.6} radialMask={false} />\n          <div className=\"mb-8\">{logo}</div>\n          <div className=\"bg-card/55 w-full max-w-md rounded-3xl border p-7 shadow-[0_30px_80px_-30px_oklch(0.3_0.1_275/0.5)] backdrop-blur-2xl backdrop-saturate-150 sm:p-9\">{children}</div>\n        </main>\n      )}\n    </div>\n  )\n}\n\nconst inputClass = \"bg-background focus-visible:ring-ring/50 placeholder:text-muted-foreground h-11 w-full rounded-xl border px-3.5 text-[15px] outline-none transition-shadow focus-visible:ring-[3px] aria-[invalid=true]:border-destructive\"\n\n/** A labelled text field with an inline error. */\nfunction AuthField({ label, error, hint, id, className, ...props }: Omit<React.ComponentProps<\"input\">, \"id\"> & { label: string; error?: string; hint?: string; id: string }) {\n  return (\n    <div className={className}>\n      <label htmlFor={id} className=\"text-sm font-medium\">{label}</label>\n      <input id={id} aria-invalid={!!error} aria-describedby={error ? `${id}-err` : hint ? `${id}-hint` : undefined} className={cn(inputClass, \"mt-1.5\")} {...props} />\n      {error ? <p id={`${id}-err`} className=\"text-destructive mt-1.5 text-sm\">{error}</p> : hint ? <p id={`${id}-hint`} className=\"text-muted-foreground mt-1.5 text-sm\">{hint}</p> : null}\n    </div>\n  )\n}\n\n/** A password field with a show and hide button. */\nfunction AuthPassword({ label = \"Password\", error, id, ...props }: Omit<React.ComponentProps<\"input\">, \"id\" | \"type\"> & { label?: string; error?: string; id: string }) {\n  const [show, setShow] = React.useState(false)\n  return (\n    <div>\n      <label htmlFor={id} className=\"text-sm font-medium\">{label}</label>\n      <div className=\"relative mt-1.5\">\n        <input id={id} type={show ? \"text\" : \"password\"} aria-invalid={!!error} aria-describedby={error ? `${id}-err` : undefined} className={cn(inputClass, \"pe-11\")} {...props} />\n        <button type=\"button\" onClick={() => setShow((v) => !v)} aria-label={show ? \"Hide password\" : \"Show password\"} aria-pressed={show} className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 absolute top-1/2 end-1.5 inline-flex size-8 -translate-y-1/2 items-center justify-center rounded-lg outline-none focus-visible:ring-[3px]\">\n          {show ? <EyeOff className=\"size-4\" aria-hidden=\"true\" /> : <Eye className=\"size-4\" aria-hidden=\"true\" />}\n        </button>\n      </div>\n      {error && <p id={`${id}-err`} className=\"text-destructive mt-1.5 text-sm\">{error}</p>}\n    </div>\n  )\n}\n\n/** The main button of a form. */\nfunction AuthButton({ busy, className, children, ...props }: React.ComponentProps<\"button\"> & { busy?: boolean }) {\n  return (\n    <button disabled={busy || props.disabled} className={cn(\"bg-primary text-primary-foreground focus-visible:ring-ring/50 inline-flex h-11 w-full items-center justify-center gap-2 rounded-xl text-[15px] font-semibold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px] disabled:opacity-60\", className)} {...props}>\n      {busy && <Loader2 className=\"size-4 motion-safe:animate-spin\" aria-hidden=\"true\" />}\n      {children}\n    </button>\n  )\n}\n\n/** Password rules, scored live. */\nconst rules = [\n  { id: \"len\", label: \"At least 10 characters\", test: (p: string) => p.length >= 10 },\n  { id: \"case\", label: \"An upper and a lower case letter\", test: (p: string) => /[a-z]/.test(p) && /[A-Z]/.test(p) },\n  { id: \"num\", label: \"A number\", test: (p: string) => /\\d/.test(p) },\n  { id: \"sym\", label: \"A symbol\", test: (p: string) => /[^A-Za-z0-9]/.test(p) },\n]\n\n/** A strength bar and checklist for a new password. */\nfunction PasswordStrength({ value, id }: { value: string; id: string }) {\n  const passed = rules.filter((r) => r.test(value)).length\n  const label = value.length === 0 ? \"Enter a password\" : passed <= 1 ? \"Weak\" : passed <= 3 ? \"Fair\" : \"Strong\"\n  const tone = passed <= 1 ? \"bg-destructive\" : passed <= 3 ? \"bg-chart-3\" : \"bg-chart-2\"\n  return (\n    <div id={id} className=\"mt-3\">\n      <div className=\"flex gap-1.5\" aria-hidden=\"true\">\n        {rules.map((r, i) => <span key={r.id} className={cn(\"h-1.5 flex-1 rounded-full transition-colors duration-300 motion-reduce:transition-none\", i < passed && value ? tone : \"bg-muted\")} />)}\n      </div>\n      <p className=\"mt-2 text-sm font-medium\" role=\"status\">Strength: {label}</p>\n      <ul className=\"text-muted-foreground mt-2 grid gap-1 text-sm\">\n        {rules.map((r) => {\n          const ok = r.test(value)\n          return <li key={r.id} className={cn(\"flex items-center gap-2\", ok && \"text-foreground\")}><span className={cn(\"flex size-4 items-center justify-center rounded-full border text-[10px]\", ok ? \"bg-chart-2 text-background border-transparent\" : \"\")} aria-hidden=\"true\">{ok ? \"✓\" : \"\"}</span>{r.label}<span className=\"sr-only\">{ok ? \" (met)\" : \" (not met)\"}</span></li>\n        })}\n      </ul>\n    </div>\n  )\n}\n\nexport { AuthButton, AuthField, AuthFrame, AuthMark, AuthPassword, PasswordStrength, defaultHrefs as authDefaultHrefs, rules as passwordRules, type AuthFrameProps, type AuthHrefs, type AuthLayout, type AuthPage }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/auth-kit/auth-kit-theme.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/auth-kit/auth-kit-forms.tsx",
      "content": "// Ballmac UI: Auth Kit forms. https://ui.ballmac.com/templates/template-auth-kit\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowLeft, ArrowRight, Check, MailCheck, ShieldCheck } from \"lucide-react\"\n\nimport { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from \"@/components/ballmac/input-otp\"\nimport { AuthButton, AuthField, AuthPassword, PasswordStrength, authDefaultHrefs, passwordRules, type AuthHrefs } from \"@/components/ballmac/templates/auth-kit/auth-kit-theme\"\nimport { cn } from \"@/lib/utils\"\n\ntype FormProps = { hrefs?: Partial<AuthHrefs> }\nconst h = (hrefs: FormProps[\"hrefs\"]) => ({ ...authDefaultHrefs, ...hrefs })\nconst link = \"text-primary focus-visible:ring-ring/50 rounded font-medium underline-offset-4 outline-none hover:underline focus-visible:ring-[3px]\"\nconst emailOk = (v: string) => /^\\S+@\\S+\\.\\S+$/.test(v.trim())\n\n/** A short simulated request so busy and success states are visible. Swap for your API call. */\nfunction useFake() {\n  const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n  React.useEffect(() => () => clearTimeout(timer.current), [])\n  return (done: () => void, ms = 700) => {\n    clearTimeout(timer.current)\n    timer.current = setTimeout(done, ms)\n  }\n}\n\nfunction Heading({ title, children }: { title: string; children?: React.ReactNode }) {\n  return (\n    <header className=\"mb-7\">\n      <h1 className=\"text-[1.75rem] leading-tight font-semibold tracking-[-0.03em] text-balance\">{title}</h1>\n      {children && <p className=\"text-muted-foreground mt-2 text-pretty\">{children}</p>}\n    </header>\n  )\n}\n\nfunction Success({ title, children, action }: { title: string; children: React.ReactNode; action?: React.ReactNode }) {\n  return (\n    <div role=\"status\">\n      <span className=\"bg-chart-2/15 text-chart-2 flex size-12 items-center justify-center rounded-2xl\"><Check className=\"size-6\" aria-hidden=\"true\" /></span>\n      <h1 className=\"mt-5 text-[1.75rem] leading-tight font-semibold tracking-[-0.03em]\">{title}</h1>\n      <p className=\"text-muted-foreground mt-2 text-pretty\">{children}</p>\n      {action && <div className=\"mt-6\">{action}</div>}\n    </div>\n  )\n}\n\n/** Sign in with email and password, or a provider. Validates before it submits. */\nfunction SignInForm({ hrefs }: FormProps) {\n  const links = h(hrefs)\n  const fake = useFake()\n  const [errors, setErrors] = React.useState<{ email?: string; password?: string }>({})\n  const [busy, setBusy] = React.useState(false)\n  const [done, setDone] = React.useState(false)\n  function submit(e: React.FormEvent<HTMLFormElement>) {\n    e.preventDefault()\n    const f = new FormData(e.currentTarget)\n    const email = String(f.get(\"email\") ?? \"\")\n    const password = String(f.get(\"password\") ?? \"\")\n    const next: typeof errors = {}\n    if (!emailOk(email)) next.email = \"Enter an email like you@company.com.\"\n    if (!password) next.password = \"Enter your password.\"\n    setErrors(next)\n    if (next.email || next.password) {\n      ;(e.currentTarget.elements.namedItem(next.email ? \"email\" : \"password\") as HTMLElement | null)?.focus()\n      return\n    }\n    setBusy(true)\n    fake(() => { setBusy(false); setDone(true) })\n  }\n  if (done) return <Success title=\"You’re in\" action={<a className={link} href=\"#\">Continue to your workspace →</a>}>Welcome back. We’ll take you to where you left off.</Success>\n  return (\n    <>\n      <Heading title=\"Welcome back\">Sign in to continue to your workspace.</Heading>\n      <div className=\"grid grid-cols-2 gap-3\">\n        {[\"Google\", \"GitHub\"].map((p) => <button key={p} type=\"button\" className=\"hover:bg-accent focus-visible:ring-ring/50 h-11 rounded-xl border text-sm font-medium outline-none transition-colors focus-visible:ring-[3px]\">{p}</button>)}\n      </div>\n      <div className=\"text-muted-foreground my-6 flex items-center gap-3 text-xs\"><span className=\"bg-border h-px flex-1\" />or with email<span className=\"bg-border h-px flex-1\" /></div>\n      <form onSubmit={submit} noValidate className=\"space-y-4\">\n        <AuthField id=\"si-email\" name=\"email\" type=\"email\" label=\"Email\" autoComplete=\"email\" error={errors.email} />\n        <div>\n          <AuthPassword id=\"si-pass\" name=\"password\" autoComplete=\"current-password\" error={errors.password} />\n          <div className=\"mt-2.5 flex items-center justify-between text-sm\">\n            <label className=\"flex cursor-pointer items-center gap-2\"><input type=\"checkbox\" name=\"remember\" className=\"accent-primary size-4\" />Keep me signed in</label>\n            <a href={links.forgot} className={link}>Forgot password?</a>\n          </div>\n        </div>\n        <AuthButton type=\"submit\" busy={busy}>{busy ? \"Signing in…\" : \"Sign in\"}</AuthButton>\n      </form>\n      <p className=\"text-muted-foreground mt-6 text-center text-sm\">New here? <a href={links[\"sign-up\"]} className={link}>Create an account</a></p>\n    </>\n  )\n}\n\n/** Create an account with a live password checklist and a terms checkbox. */\nfunction SignUpForm({ hrefs }: FormProps) {\n  const links = h(hrefs)\n  const fake = useFake()\n  const [pw, setPw] = React.useState(\"\")\n  const [errors, setErrors] = React.useState<{ name?: string; email?: string; password?: string; terms?: string }>({})\n  const [busy, setBusy] = React.useState(false)\n  const [email, setEmail] = React.useState<string | null>(null)\n  function submit(e: React.FormEvent<HTMLFormElement>) {\n    e.preventDefault()\n    const f = new FormData(e.currentTarget)\n    const name = String(f.get(\"name\") ?? \"\").trim()\n    const mail = String(f.get(\"email\") ?? \"\").trim()\n    const next: typeof errors = {}\n    if (!name) next.name = \"Tell us your name.\"\n    if (!emailOk(mail)) next.email = \"Enter an email like you@company.com.\"\n    if (!passwordRules.every((r) => r.test(pw))) next.password = \"Meet every rule below to continue.\"\n    if (!f.get(\"terms\")) next.terms = \"Agree to the terms to continue.\"\n    setErrors(next)\n    const first = ([\"name\", \"email\", \"password\", \"terms\"] as const).find((k) => next[k])\n    if (first) {\n      ;(e.currentTarget.elements.namedItem(first) as HTMLElement | null)?.focus()\n      return\n    }\n    setBusy(true)\n    fake(() => { setBusy(false); setEmail(mail) })\n  }\n  if (email) return <Success title=\"Check your email\" action={<a className={link} href={links.verify}>Enter the 6-digit code →</a>}>We sent a code to <span className=\"text-foreground font-medium\">{email}</span>. It expires in 10 minutes.</Success>\n  return (\n    <>\n      <Heading title=\"Create your account\">Free for 14 days. No card needed.</Heading>\n      <form onSubmit={submit} noValidate className=\"space-y-4\">\n        <AuthField id=\"su-name\" name=\"name\" label=\"Full name\" autoComplete=\"name\" error={errors.name} />\n        <AuthField id=\"su-email\" name=\"email\" type=\"email\" label=\"Work email\" autoComplete=\"email\" error={errors.email} />\n        <div>\n          <AuthPassword id=\"su-pass\" name=\"password\" label=\"Password\" autoComplete=\"new-password\" value={pw} onChange={(e) => setPw(e.target.value)} error={errors.password} />\n          <PasswordStrength id=\"su-strength\" value={pw} />\n        </div>\n        <div>\n          <label className=\"flex cursor-pointer items-start gap-2.5 text-sm\"><input type=\"checkbox\" name=\"terms\" aria-invalid={!!errors.terms} className=\"accent-primary mt-0.5 size-4\" /><span>I agree to the <a href=\"#\" className={link}>Terms</a> and <a href=\"#\" className={link}>Privacy Policy</a>.</span></label>\n          {errors.terms && <p className=\"text-destructive mt-1.5 text-sm\">{errors.terms}</p>}\n        </div>\n        <AuthButton type=\"submit\" busy={busy}>{busy ? \"Creating account…\" : \"Create account\"}</AuthButton>\n      </form>\n      <p className=\"text-muted-foreground mt-6 text-center text-sm\">Already have an account? <a href={links[\"sign-in\"]} className={link}>Sign in</a></p>\n    </>\n  )\n}\n\n/** Verify with a 6-digit code: auto-submits when complete, and offers a resend after a countdown. */\nfunction VerifyForm({ hrefs }: FormProps) {\n  const links = h(hrefs)\n  const [code, setCode] = React.useState(\"\")\n  const [state, setState] = React.useState<\"idle\" | \"error\" | \"ok\">(\"idle\")\n  const [wait, setWait] = React.useState(30)\n  React.useEffect(() => {\n    if (wait <= 0) return\n    const id = setTimeout(() => setWait((w) => w - 1), 1000)\n    return () => clearTimeout(id)\n  }, [wait])\n  if (state === \"ok\") return <Success title=\"Email verified\" action={<a className={link} href={links.onboarding}>Set up your workspace →</a>}>Thanks. Your account is ready.</Success>\n  return (\n    <>\n      <span className=\"bg-primary/10 text-primary mb-5 flex size-12 items-center justify-center rounded-2xl\"><MailCheck className=\"size-6\" aria-hidden=\"true\" /></span>\n      <Heading title=\"Enter your code\">We sent a 6-digit code to <span className=\"text-foreground font-medium\">mina@fieldnote.example</span>. Try 123456.</Heading>\n      <label htmlFor=\"vf-code\" className=\"sr-only\">6-digit code</label>\n      <InputOTP id=\"vf-code\" maxLength={6} value={code} aria-invalid={state === \"error\" || undefined} aria-describedby=\"vf-status\" pattern=\"^[0-9]*$\" onChange={(v) => { setCode(v); setState(\"idle\") }} onComplete={(v) => setState(v === \"123456\" ? \"ok\" : \"error\")} containerClassName=\"justify-center\">\n        <InputOTPGroup>{[0, 1, 2].map((i) => <InputOTPSlot key={i} index={i} invalid={state === \"error\"} className=\"size-12 text-lg\" />)}</InputOTPGroup>\n        <InputOTPSeparator />\n        <InputOTPGroup>{[3, 4, 5].map((i) => <InputOTPSlot key={i} index={i} invalid={state === \"error\"} className=\"size-12 text-lg\" />)}</InputOTPGroup>\n      </InputOTP>\n      <p id=\"vf-status\" role=\"status\" className={cn(\"mt-4 text-center text-sm\", state === \"error\" ? \"text-destructive\" : \"text-muted-foreground\")}>{state === \"error\" ? \"That code isn’t right. Check it and try again.\" : \"Paste the code, or type it in.\"}</p>\n      <p className=\"text-muted-foreground mt-6 text-center text-sm\">\n        Didn’t get it?{\" \"}\n        <button type=\"button\" disabled={wait > 0} onClick={() => { setWait(30); setCode(\"\"); setState(\"idle\") }} className={cn(link, \"disabled:text-muted-foreground disabled:no-underline\")}>{wait > 0 ? `Resend in ${wait}s` : \"Resend the code\"}</button>\n      </p>\n      <p className=\"mt-3 text-center text-sm\"><a href={links[\"sign-in\"]} className=\"text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5\"><ArrowLeft className=\"size-3.5 rtl:rotate-180\" aria-hidden=\"true\" />Back to sign in</a></p>\n    </>\n  )\n}\n\n/** Ask for a reset link. */\nfunction ForgotForm({ hrefs }: FormProps) {\n  const links = h(hrefs)\n  const fake = useFake()\n  const [error, setError] = React.useState(\"\")\n  const [busy, setBusy] = React.useState(false)\n  const [sent, setSent] = React.useState<string | null>(null)\n  function submit(e: React.FormEvent<HTMLFormElement>) {\n    e.preventDefault()\n    const mail = String(new FormData(e.currentTarget).get(\"email\") ?? \"\").trim()\n    if (!emailOk(mail)) {\n      setError(\"Enter an email like you@company.com.\")\n      ;(e.currentTarget.elements.namedItem(\"email\") as HTMLElement | null)?.focus()\n      return\n    }\n    setError(\"\")\n    setBusy(true)\n    fake(() => { setBusy(false); setSent(mail) })\n  }\n  if (sent) return <Success title=\"Check your email\" action={<button type=\"button\" onClick={() => setSent(null)} className={link}>Use a different email</button>}>If an account exists for <span className=\"text-foreground font-medium\">{sent}</span>, a reset link is on its way. It works for one hour.</Success>\n  return (\n    <>\n      <Heading title=\"Forgot your password?\">No problem. Tell us your email and we’ll send a link to choose a new one.</Heading>\n      <form onSubmit={submit} noValidate className=\"space-y-4\">\n        <AuthField id=\"fp-email\" name=\"email\" type=\"email\" label=\"Email\" autoComplete=\"email\" error={error} />\n        <AuthButton type=\"submit\" busy={busy}>{busy ? \"Sending…\" : \"Send reset link\"}</AuthButton>\n      </form>\n      <p className=\"mt-6 text-center text-sm\"><a href={links[\"sign-in\"]} className=\"text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5\"><ArrowLeft className=\"size-3.5 rtl:rotate-180\" aria-hidden=\"true\" />Back to sign in</a></p>\n    </>\n  )\n}\n\n/** Choose a new password, with confirmation. */\nfunction ResetForm({ hrefs }: FormProps) {\n  const links = h(hrefs)\n  const fake = useFake()\n  const [pw, setPw] = React.useState(\"\")\n  const [errors, setErrors] = React.useState<{ password?: string; confirm?: string }>({})\n  const [busy, setBusy] = React.useState(false)\n  const [done, setDone] = React.useState(false)\n  function submit(e: React.FormEvent<HTMLFormElement>) {\n    e.preventDefault()\n    const confirm = String(new FormData(e.currentTarget).get(\"confirm\") ?? \"\")\n    const next: typeof errors = {}\n    if (!passwordRules.every((r) => r.test(pw))) next.password = \"Meet every rule below to continue.\"\n    else if (confirm !== pw) next.confirm = \"The passwords don’t match.\"\n    setErrors(next)\n    if (next.password || next.confirm) {\n      ;(e.currentTarget.elements.namedItem(next.password ? \"password\" : \"confirm\") as HTMLElement | null)?.focus()\n      return\n    }\n    setBusy(true)\n    fake(() => { setBusy(false); setDone(true) })\n  }\n  if (done) return <Success title=\"Password updated\" action={<a className={link} href={links[\"sign-in\"]}>Sign in with your new password →</a>}>You’ve been signed out everywhere else for your safety.</Success>\n  return (\n    <>\n      <Heading title=\"Choose a new password\">Pick something you haven’t used before.</Heading>\n      <form onSubmit={submit} noValidate className=\"space-y-4\">\n        <div>\n          <AuthPassword id=\"rs-pass\" name=\"password\" label=\"New password\" autoComplete=\"new-password\" value={pw} onChange={(e) => setPw(e.target.value)} error={errors.password} />\n          <PasswordStrength id=\"rs-strength\" value={pw} />\n        </div>\n        <AuthPassword id=\"rs-confirm\" name=\"confirm\" label=\"Confirm password\" autoComplete=\"new-password\" error={errors.confirm} />\n        <AuthButton type=\"submit\" busy={busy}>{busy ? \"Saving…\" : \"Update password\"}</AuthButton>\n      </form>\n    </>\n  )\n}\n\n/** Accept an invitation to a team. */\nfunction InviteForm({ hrefs }: FormProps) {\n  const fake = useFake()\n  const [pw, setPw] = React.useState(\"\")\n  const [error, setError] = React.useState(\"\")\n  const [busy, setBusy] = React.useState(false)\n  const [done, setDone] = React.useState(false)\n  function submit(e: React.FormEvent<HTMLFormElement>) {\n    e.preventDefault()\n    if (!passwordRules.every((r) => r.test(pw))) {\n      setError(\"Meet every rule below to continue.\")\n      ;(e.currentTarget.elements.namedItem(\"password\") as HTMLElement | null)?.focus()\n      return\n    }\n    setError(\"\")\n    setBusy(true)\n    fake(() => { setBusy(false); setDone(true) })\n  }\n  const links = h(hrefs)\n  if (done) return <Success title=\"Welcome to Fieldnote Goods\" action={<a className={link} href={links.onboarding}>Finish setting up →</a>}>You’ve joined as an editor.</Success>\n  return (\n    <>\n      <div className=\"bg-secondary mb-6 flex items-center gap-3 rounded-2xl p-3.5\">\n        <span className=\"bg-chart-4/25 flex size-11 items-center justify-center rounded-xl text-sm font-semibold\" aria-hidden=\"true\">FG</span>\n        <p className=\"text-sm text-pretty\"><span className=\"font-semibold\">Maya Okafor</span> invited you to join <span className=\"font-semibold\">Fieldnote Goods</span> as an editor.</p>\n      </div>\n      <Heading title=\"Accept your invitation\">Create a password for <span className=\"text-foreground font-medium\">dev@fieldnote.example</span>.</Heading>\n      <form onSubmit={submit} noValidate className=\"space-y-4\">\n        <AuthField id=\"iv-name\" name=\"name\" label=\"Full name\" defaultValue=\"Dev Patel\" autoComplete=\"name\" />\n        <div>\n          <AuthPassword id=\"iv-pass\" name=\"password\" autoComplete=\"new-password\" value={pw} onChange={(e) => setPw(e.target.value)} error={error} />\n          <PasswordStrength id=\"iv-strength\" value={pw} />\n        </div>\n        <AuthButton type=\"submit\" busy={busy}>{busy ? \"Joining…\" : \"Join Fieldnote Goods\"}</AuthButton>\n      </form>\n    </>\n  )\n}\n\nconst steps = [\"You\", \"Workspace\", \"Team\"]\n\n/** Three short steps to set up a workspace, with progress and a slug that follows the name. */\nfunction OnboardingForm({ hrefs }: FormProps) {\n  const links = h(hrefs)\n  const [step, setStep] = React.useState(0)\n  const [name, setName] = React.useState(\"Mina Kovac\")\n  const [role, setRole] = React.useState(\"Design\")\n  const [ws, setWs] = React.useState(\"Fieldnote Goods\")\n  const [invites, setInvites] = React.useState(\"\")\n  const [error, setError] = React.useState(\"\")\n  const [done, setDone] = React.useState(false)\n  const head = React.useRef<HTMLHeadingElement>(null)\n  const slug = ws.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\")\n  const emails = invites.split(/[\\s,]+/).filter(Boolean)\n  const roles = [\"Design\", \"Engineering\", \"Product\", \"Marketing\", \"Operations\"]\n\n  function next() {\n    if (step === 0 && !name.trim()) return setError(\"Tell us your name.\")\n    if (step === 1 && !ws.trim()) return setError(\"Give your workspace a name.\")\n    if (step === 2 && emails.some((m) => !emailOk(m))) return setError(\"One of those doesn’t look like an email address.\")\n    setError(\"\")\n    if (step === 2) return setDone(true)\n    setStep((s) => s + 1)\n    requestAnimationFrame(() => head.current?.focus())\n  }\n  if (done) return <Success title=\"You’re all set\" action={<a className={link} href=\"#\">Open {ws || \"your workspace\"} →</a>}>{emails.length > 0 ? `We sent ${emails.length} invitation${emails.length === 1 ? \"\" : \"s\"}.` : \"You can invite your team any time.\"}</Success>\n  return (\n    <>\n      <ol className=\"mb-7 flex items-center gap-2\" aria-label=\"Setup progress\">\n        {steps.map((s, i) => (\n          <li key={s} aria-current={i === step ? \"step\" : undefined} className=\"flex flex-1 items-center gap-2 text-xs font-medium\">\n            <span className={cn(\"flex size-6 shrink-0 items-center justify-center rounded-full border text-[11px]\", i < step ? \"bg-primary text-primary-foreground border-transparent\" : i === step ? \"border-primary text-primary\" : \"text-muted-foreground\")}>{i < step ? <Check className=\"size-3.5\" aria-label=\"done\" /> : i + 1}</span>\n            <span className={i === step ? \"text-foreground\" : \"text-muted-foreground\"}>{s}</span>\n            {i < steps.length - 1 && <span className=\"bg-border h-px flex-1\" aria-hidden=\"true\" />}\n          </li>\n        ))}\n      </ol>\n      <header className=\"mb-6\">\n        <h1 ref={head} tabIndex={-1} className=\"text-[1.75rem] leading-tight font-semibold tracking-[-0.03em] outline-none\">{step === 0 ? \"Tell us about you\" : step === 1 ? \"Name your workspace\" : \"Invite your team\"}</h1>\n        <p className=\"text-muted-foreground mt-2 text-pretty\">{step === 0 ? \"So your teammates know who you are.\" : step === 1 ? \"You can change this any time.\" : \"They’ll get an email with a link to join. You can skip this.\"}</p>\n      </header>\n      <div className=\"space-y-4\">\n        {step === 0 && (\n          <>\n            <AuthField id=\"ob-name\" label=\"Full name\" value={name} onChange={(e) => setName(e.target.value)} autoComplete=\"name\" error={error} />\n            <fieldset><legend className=\"text-sm font-medium\">Your team</legend>\n              <div className=\"mt-2 flex flex-wrap gap-2\">\n                {roles.map((r) => <label key={r} className={cn(\"focus-within:ring-ring/50 cursor-pointer rounded-full border px-3.5 py-1.5 text-sm transition-colors focus-within:ring-[3px]\", role === r ? \"bg-primary text-primary-foreground border-transparent\" : \"hover:bg-accent\")}><input type=\"radio\" name=\"role\" value={r} checked={role === r} onChange={() => setRole(r)} className=\"sr-only\" />{r}</label>)}\n              </div>\n            </fieldset>\n          </>\n        )}\n        {step === 1 && (\n          <>\n            <AuthField id=\"ob-ws\" label=\"Workspace name\" value={ws} onChange={(e) => setWs(e.target.value)} error={error} hint={slug ? `Your address: ${slug}.keystone.app` : undefined} />\n          </>\n        )}\n        {step === 2 && (\n          <div>\n            <label htmlFor=\"ob-inv\" className=\"text-sm font-medium\">Email addresses</label>\n            <textarea id=\"ob-inv\" rows={3} value={invites} onChange={(e) => setInvites(e.target.value)} placeholder=\"dev@company.com, priya@company.com\" aria-invalid={!!error} className=\"bg-background focus-visible:ring-ring/50 placeholder:text-muted-foreground mt-1.5 w-full resize-none rounded-xl border p-3.5 text-[15px] outline-none focus-visible:ring-[3px] aria-[invalid=true]:border-destructive\" />\n            <p className={cn(\"mt-1.5 text-sm\", error ? \"text-destructive\" : \"text-muted-foreground\")} role=\"status\">{error || (emails.length ? `${emails.length} ${emails.length === 1 ? \"person\" : \"people\"} will be invited` : \"Separate addresses with commas or spaces.\")}</p>\n          </div>\n        )}\n      </div>\n      <div className=\"mt-7 flex items-center justify-between gap-3\">\n        {step > 0 ? <button type=\"button\" onClick={() => { setError(\"\"); setStep((s) => s - 1) }} className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 inline-flex items-center gap-1.5 rounded-lg px-1 text-sm outline-none focus-visible:ring-[3px]\"><ArrowLeft className=\"size-4 rtl:rotate-180\" aria-hidden=\"true\" />Back</button> : <button type=\"button\" onClick={() => setDone(true)} className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 rounded px-1 text-sm outline-none focus-visible:ring-[3px]\">Skip for now</button>}\n        <AuthButton type=\"button\" onClick={next} className=\"w-auto px-6\">{step === 2 ? (emails.length ? \"Send invitations\" : \"Finish\") : \"Continue\"}{step < 2 && <ArrowRight className=\"size-4 rtl:rotate-180\" aria-hidden=\"true\" />}</AuthButton>\n      </div>\n      <p className=\"text-muted-foreground mt-6 flex items-center justify-center gap-1.5 text-xs\"><ShieldCheck className=\"size-3.5\" aria-hidden=\"true\" />Step {step + 1} of {steps.length}</p>\n    </>\n  )\n}\n\nexport { ForgotForm, InviteForm, OnboardingForm, ResetForm, SignInForm, SignUpForm, VerifyForm, type FormProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/auth-kit/auth-kit-forms.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/auth-kit/auth-kit-pages.tsx",
      "content": "// Ballmac UI: Auth Kit pages. https://ui.ballmac.com/templates/template-auth-kit\n\"use client\"\n\nimport * as React from \"react\"\n\nimport { SegmentedControl, SegmentedControlItem } from \"@/components/ballmac/segmented-control\"\nimport { ForgotForm, InviteForm, OnboardingForm, ResetForm, SignInForm, SignUpForm, VerifyForm } from \"@/components/ballmac/templates/auth-kit/auth-kit-forms\"\nimport { AuthFrame, authDefaultHrefs, type AuthHrefs, type AuthLayout, type AuthPage } from \"@/components/ballmac/templates/auth-kit/auth-kit-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst forms: Record<AuthPage, React.ComponentType<{ hrefs?: Partial<AuthHrefs> }>> = {\n  \"sign-in\": SignInForm,\n  \"sign-up\": SignUpForm,\n  verify: VerifyForm,\n  forgot: ForgotForm,\n  reset: ResetForm,\n  invite: InviteForm,\n  onboarding: OnboardingForm,\n}\n\nconst labels: Record<AuthPage, string> = { \"sign-in\": \"Sign in\", \"sign-up\": \"Sign up\", verify: \"Verify\", forgot: \"Forgot\", reset: \"Reset\", invite: \"Invite\", onboarding: \"Onboarding\" }\nconst defaultLayout: Record<AuthPage, AuthLayout> = { \"sign-in\": \"split\", \"sign-up\": \"centered\", verify: \"glass\", forgot: \"centered\", reset: \"centered\", invite: \"split\", onboarding: \"glass\" }\n\ntype AuthKitPageProps = React.ComponentProps<\"div\"> & {\n  /** Which page to show. */\n  page: AuthPage\n  /** How the page is composed. Defaults to the layout that suits the page. */\n  layout?: AuthLayout\n  /** Override where pages live (used by previews). */\n  hrefs?: Partial<AuthHrefs>\n}\n\n/** One page of the auth flow in a chosen layout. */\nfunction AuthKitPage({ page, layout, hrefs, ...props }: AuthKitPageProps) {\n  const Form = forms[page]\n  return (\n    <AuthFrame layout={layout ?? defaultLayout[page]} {...props}>\n      <Form hrefs={hrefs} />\n    </AuthFrame>\n  )\n}\n\ntype AuthKitPlaygroundProps = React.ComponentProps<\"div\"> & {\n  /** Page shown first. */\n  defaultPage?: AuthPage\n  /** Layout shown first. */\n  defaultLayout?: AuthLayout\n}\n\n/** Every page and every layout in one place: pick a flow and a layout from the bar and see the result. */\nfunction AuthKitPlayground({ defaultPage = \"sign-in\", defaultLayout: initialLayout = \"split\", ...props }: AuthKitPlaygroundProps) {\n  const [page, setPage] = React.useState<AuthPage>(defaultPage)\n  const [layout, setLayout] = React.useState<AuthLayout>(initialLayout)\n  const Form = forms[page]\n  // Links between forms switch the page here instead of navigating away.\n  const hrefs = Object.fromEntries((Object.keys(forms) as AuthPage[]).map((k) => [k, `#${k}`])) as AuthHrefs\n  React.useEffect(() => {\n    const onHash = () => {\n      const k = window.location.hash.slice(1) as AuthPage\n      if (k in forms) setPage(k)\n    }\n    window.addEventListener(\"hashchange\", onHash)\n    return () => window.removeEventListener(\"hashchange\", onHash)\n  }, [])\n  return (\n    <AuthFrame layout={layout} {...props}>\n      <Form key={page} hrefs={hrefs} />\n      <div role=\"region\" aria-label=\"Auth Kit controls\" className=\"bg-popover/90 fixed inset-x-3 top-3 z-50 mx-auto flex max-w-3xl flex-col gap-3 rounded-2xl border p-3 shadow-[0_20px_50px_-12px_rgb(0_0_0/0.35)] backdrop-blur-xl sm:flex-row sm:items-center\">\n        <div role=\"group\" aria-label=\"Page\" className=\"flex gap-1 overflow-x-auto [scrollbar-width:none]\">\n          {(Object.keys(forms) as AuthPage[]).map((k) => (\n            <button key={k} type=\"button\" aria-pressed={page === k} onClick={() => setPage(k)} className={cn(\"focus-visible:ring-ring/50 h-8 shrink-0 rounded-lg px-3 text-[13px] font-medium outline-none transition-colors focus-visible:ring-[3px]\", page === k ? \"bg-primary text-primary-foreground\" : \"hover:bg-accent text-muted-foreground\")}>{labels[k]}</button>\n          ))}\n        </div>\n        <SegmentedControl aria-label=\"Layout\" value={layout} onValueChange={(v) => setLayout(v as AuthLayout)} size=\"sm\" className=\"sm:ms-auto\">\n          <SegmentedControlItem value=\"centered\">Centered</SegmentedControlItem>\n          <SegmentedControlItem value=\"split\">Split</SegmentedControlItem>\n          <SegmentedControlItem value=\"glass\">Glass</SegmentedControlItem>\n        </SegmentedControl>\n      </div>\n    </AuthFrame>\n  )\n}\n\nexport { AuthKitPage, AuthKitPlayground, authDefaultHrefs, type AuthKitPageProps, type AuthKitPlaygroundProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/auth-kit/auth-kit-pages.tsx"
    },
    {
      "path": "registry/ballmac/app/auth/sign-in/page.tsx",
      "content": "// Ballmac UI: Auth Kit template route. https://ui.ballmac.com/templates/template-auth-kit\nimport type { Metadata } from \"next\"\n\nimport { AuthKitPage } from \"@/components/ballmac/templates/auth-kit/auth-kit-pages\"\n\nexport const metadata: Metadata = {\n  title: \"Sign in\",\n  description: \"Welcome back. Sign in to continue.\",\n}\n\nexport default function Page() {\n  return <AuthKitPage page=\"sign-in\" />\n}\n",
      "type": "registry:page",
      "target": "app/auth/sign-in/page.tsx"
    },
    {
      "path": "registry/ballmac/app/auth/sign-up/page.tsx",
      "content": "// Ballmac UI: Auth Kit template route. https://ui.ballmac.com/templates/template-auth-kit\nimport type { Metadata } from \"next\"\n\nimport { AuthKitPage } from \"@/components/ballmac/templates/auth-kit/auth-kit-pages\"\n\nexport const metadata: Metadata = {\n  title: \"Create your account\",\n  description: \"Free for 14 days. No card needed.\",\n}\n\nexport default function Page() {\n  return <AuthKitPage page=\"sign-up\" />\n}\n",
      "type": "registry:page",
      "target": "app/auth/sign-up/page.tsx"
    },
    {
      "path": "registry/ballmac/app/auth/verify/page.tsx",
      "content": "// Ballmac UI: Auth Kit template route. https://ui.ballmac.com/templates/template-auth-kit\nimport type { Metadata } from \"next\"\n\nimport { AuthKitPage } from \"@/components/ballmac/templates/auth-kit/auth-kit-pages\"\n\nexport const metadata: Metadata = {\n  title: \"Verify your email\",\n  description: \"Enter the 6-digit code we sent you.\",\n}\n\nexport default function Page() {\n  return <AuthKitPage page=\"verify\" />\n}\n",
      "type": "registry:page",
      "target": "app/auth/verify/page.tsx"
    },
    {
      "path": "registry/ballmac/app/auth/forgot/page.tsx",
      "content": "// Ballmac UI: Auth Kit template route. https://ui.ballmac.com/templates/template-auth-kit\nimport type { Metadata } from \"next\"\n\nimport { AuthKitPage } from \"@/components/ballmac/templates/auth-kit/auth-kit-pages\"\n\nexport const metadata: Metadata = {\n  title: \"Forgot your password?\",\n  description: \"Get a link to choose a new password.\",\n}\n\nexport default function Page() {\n  return <AuthKitPage page=\"forgot\" />\n}\n",
      "type": "registry:page",
      "target": "app/auth/forgot/page.tsx"
    },
    {
      "path": "registry/ballmac/app/auth/reset/page.tsx",
      "content": "// Ballmac UI: Auth Kit template route. https://ui.ballmac.com/templates/template-auth-kit\nimport type { Metadata } from \"next\"\n\nimport { AuthKitPage } from \"@/components/ballmac/templates/auth-kit/auth-kit-pages\"\n\nexport const metadata: Metadata = {\n  title: \"Choose a new password\",\n  description: \"Pick a password you haven't used before.\",\n}\n\nexport default function Page() {\n  return <AuthKitPage page=\"reset\" />\n}\n",
      "type": "registry:page",
      "target": "app/auth/reset/page.tsx"
    },
    {
      "path": "registry/ballmac/app/auth/invite/page.tsx",
      "content": "// Ballmac UI: Auth Kit template route. https://ui.ballmac.com/templates/template-auth-kit\nimport type { Metadata } from \"next\"\n\nimport { AuthKitPage } from \"@/components/ballmac/templates/auth-kit/auth-kit-pages\"\n\nexport const metadata: Metadata = {\n  title: \"Accept your invitation\",\n  description: \"Join your team's workspace.\",\n}\n\nexport default function Page() {\n  return <AuthKitPage page=\"invite\" />\n}\n",
      "type": "registry:page",
      "target": "app/auth/invite/page.tsx"
    },
    {
      "path": "registry/ballmac/app/auth/onboarding/page.tsx",
      "content": "// Ballmac UI: Auth Kit template route. https://ui.ballmac.com/templates/template-auth-kit\nimport type { Metadata } from \"next\"\n\nimport { AuthKitPage } from \"@/components/ballmac/templates/auth-kit/auth-kit-pages\"\n\nexport const metadata: Metadata = {\n  title: \"Set up your workspace\",\n  description: \"Three short steps to get started.\",\n}\n\nexport default function Page() {\n  return <AuthKitPage page=\"onboarding\" />\n}\n",
      "type": "registry:page",
      "target": "app/auth/onboarding/page.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "template",
      "auth",
      "login",
      "signup",
      "verification",
      "onboarding",
      "password",
      "otp"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "Installs a complete authentication flow. AuthKitPage renders one page in a layout (centered, split or glass); AuthKitPlayground lets you try every combination. Replace the simulated requests in auth-kit-forms.tsx with your auth provider.",
      "whenToUse": [
        "Any product that needs sign in, sign up, verification and onboarding that look consistent",
        "Trying layouts quickly before you commit to one"
      ],
      "whenNotToUse": [
        "A single login box (use login-1 or login-2)"
      ],
      "composesWith": [
        "login-2",
        "verify-1",
        "onboarding-1"
      ],
      "a11y": [
        {
          "keys": "Tab / Shift+Tab",
          "action": "Moves through fields; the first invalid field is focused after a failed submit"
        },
        {
          "keys": "Paste in the code field",
          "action": "Fills all six digits at once"
        }
      ],
      "customization": [
        "layout: centered, split or glass on each AuthKitPage",
        "Edit authCss for the light and dark palettes",
        "Replace the quote in the split layout"
      ]
    },
    "examples": [
      "template-auth-kit-demo",
      "template-auth-kit-sign-in",
      "template-auth-kit-sign-up",
      "template-auth-kit-verify",
      "template-auth-kit-forgot",
      "template-auth-kit-reset",
      "template-auth-kit-invite",
      "template-auth-kit-onboarding"
    ],
    "url": "https://ui.ballmac.com/components/template-auth-kit"
  },
  "docs": "Routes are at /auth/sign-in, /auth/sign-up, /auth/verify, /auth/forgot, /auth/reset, /auth/invite and /auth/onboarding. Each form simulates its request with a short timer: replace the useFake() calls in auth-kit-forms.tsx with your API.",
  "categories": [
    "templates"
  ],
  "type": "registry:block"
}