{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "template-publication",
  "title": "Marginalia: magazine",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "An editorial five-page magazine: a masthead between double rules, a lead story, an article with reading progress, drop cap and margin notes, section pages with sort and load more, author pages and an issues shelf with subscriptions.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/copy-button.json",
    "https://ui.ballmac.com/r/scroll-progress.json",
    "https://ui.ballmac.com/r/media.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/templates/publication/publication-fonts.ts",
      "content": "// Ballmac UI: Publication template fonts. https://ui.ballmac.com/templates/template-publication\nimport { DM_Serif_Display, Lora, Public_Sans } from \"next/font/google\"\n\nexport const pubDisplay = DM_Serif_Display({ variable: \"--pub-display\", subsets: [\"latin\"], weight: \"400\", style: [\"normal\", \"italic\"], display: \"swap\", preload: false })\nexport const pubBody = Lora({ variable: \"--pub-body\", subsets: [\"latin\"], display: \"swap\", preload: false })\nexport const pubSans = Public_Sans({ variable: \"--pub-sans\", subsets: [\"latin\"], display: \"swap\", preload: false })\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/publication/publication-fonts.ts"
    },
    {
      "path": "registry/ballmac/components/templates/publication/publication-data.ts",
      "content": "import type { MediaSource } from \"@/components/ballmac/media\"\n\n// Ballmac UI: Publication sample content. https://ui.ballmac.com/templates/template-publication\n\nexport type Section = \"Essays\" | \"Culture\" | \"Science\" | \"Technology\" | \"Interviews\"\nexport const sections: Section[] = [\"Essays\", \"Culture\", \"Science\", \"Technology\", \"Interviews\"]\n\nexport type Author = { id: string; name: string; role: string; bio: string; tone: number }\nexport const authors: Author[] = [\n  { id: \"ines\", name: \"Inês Marques\", role: \"Senior editor\", bio: \"Writes about cities, memory and the objects we keep. Previously at a daily paper in Porto.\", tone: 1 },\n  { id: \"tomas\", name: \"Tomás Reyes\", role: \"Science correspondent\", bio: \"Reports on ecology and the people who count things for a living.\", tone: 2 },\n  { id: \"amara\", name: \"Amara Okafor\", role: \"Contributing writer\", bio: \"Essays on technology, work and the slow end of the internet.\", tone: 3 },\n  { id: \"elena\", name: \"Elena Rossi\", role: \"Culture editor\", bio: \"Edits the culture desk. Cooks, reads, rarely sleeps in October.\", tone: 4 },\n]\n\nexport type Article = { id: string; section: Section; kicker: string; title: string; dek: string; author: string; date: string; read: number; art: number; popular?: number; image?: MediaSource; imageAlt?: string }\n\nexport const articles: Article[] = [\n  { id: \"a1\", section: \"Essays\", kicker: \"The long view\", title: \"The case for the unfinished city\", dek: \"What a scaffold-covered street teaches us about patience, and why the cities we love are never done.\", author: \"ines\", date: \"2026-09-28\", read: 14, art: 0, popular: 1 },\n  { id: \"a2\", section: \"Science\", kicker: \"Field notes\", title: \"Counting every bird in a parish of three hundred\", dek: \"A volunteer census, a very cold spring, and the surprising comeback of the corn bunting.\", author: \"tomas\", date: \"2026-09-26\", read: 11, art: 1, popular: 3 },\n  { id: \"a3\", section: \"Technology\", kicker: \"Slow tech\", title: \"The internet we left in the drawer\", dek: \"On forums, feeds and the small websites that quietly still work.\", author: \"amara\", date: \"2026-09-25\", read: 9, art: 2, popular: 2 },\n  { id: \"a4\", section: \"Culture\", kicker: \"Table talk\", title: \"A defence of the weeknight dinner\", dek: \"Nobody photographs it. That is exactly the point.\", author: \"elena\", date: \"2026-09-24\", read: 6, art: 3 },\n  { id: \"a5\", section: \"Interviews\", kicker: \"In conversation\", title: \"“I only write in the margins”\", dek: \"The novelist Dalia Costa on notebooks, naps and finishing nothing for eleven years.\", author: \"ines\", date: \"2026-09-22\", read: 18, art: 4, popular: 4 },\n  { id: \"a6\", section: \"Essays\", kicker: \"On work\", title: \"Against the optimised life\", dek: \"A year without metrics, and what was left when I stopped counting.\", author: \"amara\", date: \"2026-09-20\", read: 12, art: 5 },\n  { id: \"a7\", section: \"Science\", kicker: \"Deep time\", title: \"The rock that remembers the sea\", dek: \"A geologist reads a cliff face like a diary.\", author: \"tomas\", date: \"2026-09-18\", read: 8, art: 0 },\n  { id: \"a8\", section: \"Culture\", kicker: \"Listening\", title: \"What the radio knew\", dek: \"Why the old weather forecast still feels like poetry.\", author: \"elena\", date: \"2026-09-15\", read: 7, art: 2 },\n  { id: \"a9\", section: \"Technology\", kicker: \"Craft\", title: \"Write the manual first\", dek: \"How good software gets made backwards.\", author: \"amara\", date: \"2026-09-12\", read: 10, art: 1 },\n  { id: \"a10\", section: \"Essays\", kicker: \"Small things\", title: \"In praise of the folding chair\", dek: \"The most democratic object ever designed.\", author: \"ines\", date: \"2026-09-09\", read: 5, art: 3 },\n]\n\nexport type Issue = { n: number; season: string; theme: string; art: number; image?: MediaSource; imageAlt?: string }\n\nexport const issues: Issue[] = [\n  { n: 48, season: \"Autumn 2026\", theme: \"Patience\", art: 0 },\n  { n: 47, season: \"Summer 2026\", theme: \"Heat\", art: 1 },\n  { n: 46, season: \"Spring 2026\", theme: \"Return\", art: 2 },\n  { n: 45, season: \"Winter 2025\", theme: \"Quiet\", art: 3 },\n  { n: 44, season: \"Autumn 2025\", theme: \"Maps\", art: 4 },\n  { n: 43, season: \"Summer 2025\", theme: \"Water\", art: 5 },\n]\n\nexport const getAuthor = (id: string) => authors.find((a) => a.id === id) ?? authors[0]\nconst fmt = new Intl.DateTimeFormat(\"en-US\", { month: \"long\", day: \"numeric\", year: \"numeric\", timeZone: \"UTC\" })\nconst fmtShort = new Intl.DateTimeFormat(\"en-US\", { month: \"short\", day: \"numeric\", timeZone: \"UTC\" })\nexport const formatDate = (iso: string) => fmt.format(new Date(iso))\nexport const formatShort = (iso: string) => fmtShort.format(new Date(iso))\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/publication/publication-data.ts"
    },
    {
      "path": "registry/ballmac/components/templates/publication/publication-theme.tsx",
      "content": "// Ballmac UI: Publication template shell. https://ui.ballmac.com/templates/template-publication\n\"use client\"\n\nimport * as React from \"react\"\nimport { Menu, Search, X } from \"lucide-react\"\n\nimport { pubBody, pubDisplay, pubSans } from \"@/components/ballmac/templates/publication/publication-fonts\"\nimport { sections } from \"@/components/ballmac/templates/publication/publication-data\"\nimport { cn } from \"@/lib/utils\"\nimport { Media, type MediaSource } from \"@/components/ballmac/media\"\n\ntype PublicationPage = \"home\" | \"article\" | \"section\" | \"author\" | \"issues\"\ntype PublicationHrefs = Record<PublicationPage, string>\n\nconst defaultHrefs: PublicationHrefs = { home: \"/publication\", article: \"/publication/essays/the-unfinished-city\", section: \"/publication/essays\", author: \"/publication/authors/ines\", issues: \"/publication/issues\" }\n\n/** Publication's palette: newsprint and ink with a deep red. Dark mode is sepia night, for reading after dark. */\nconst publicationCss = `\n.publication-theme,body:not(:has([data-gallery])):has(.publication-theme){--publication-on-accent:oklch(0.18 0.01 60);--background:oklch(0.975 0.014 85);--foreground:oklch(0.18 0.01 60);--card:oklch(0.988 0.01 85);--card-foreground:oklch(0.18 0.01 60);--popover:oklch(0.988 0.01 85);--popover-foreground:oklch(0.18 0.01 60);--primary:oklch(0.18 0.01 60);--primary-foreground:oklch(0.975 0.014 85);--secondary:oklch(0.945 0.02 85);--secondary-foreground:oklch(0.18 0.01 60);--muted:oklch(0.945 0.02 85);--muted-foreground:oklch(0.45 0.02 70);--accent:oklch(0.93 0.025 80);--accent-foreground:oklch(0.18 0.01 60);--border:oklch(0.18 0.01 60 / 18%);--input:oklch(0.18 0.01 60 / 28%);--ring:oklch(0.45 0.17 28);--surface:oklch(0.958 0.016 85);--destructive:oklch(0.5 0.2 27);--chart-1:oklch(0.45 0.17 28);--chart-2:oklch(0.4 0.07 240);--chart-3:oklch(0.78 0.12 85);--chart-4:oklch(0.46 0.1 150);--chart-5:oklch(0.62 0.11 50);--radius:0.125rem}\n.dark .publication-theme,.dark body:not(:has([data-gallery])):has(.publication-theme){--publication-on-accent:oklch(0.18 0.01 60);--background:oklch(0.17 0.012 60);--foreground:oklch(0.94 0.02 85);--card:oklch(0.2 0.013 60);--card-foreground:oklch(0.94 0.02 85);--popover:oklch(0.22 0.014 60);--popover-foreground:oklch(0.94 0.02 85);--primary:oklch(0.94 0.02 85);--primary-foreground:oklch(0.18 0.01 60);--secondary:oklch(0.25 0.014 60);--secondary-foreground:oklch(0.94 0.02 85);--muted:oklch(0.24 0.014 60);--muted-foreground:oklch(0.72 0.025 80);--accent:oklch(0.28 0.016 60);--accent-foreground:oklch(0.94 0.02 85);--border:oklch(0.94 0.02 85 / 18%);--input:oklch(0.94 0.02 85 / 28%);--ring:oklch(0.74 0.14 30);--surface:oklch(0.19 0.012 60);--destructive:oklch(0.7 0.18 27);--chart-1:oklch(0.74 0.14 30);--chart-2:oklch(0.72 0.08 240);--chart-3:oklch(0.82 0.11 85);--chart-4:oklch(0.76 0.1 150);--chart-5:oklch(0.76 0.1 55)}\nbody:not(:has([data-gallery])):has(.publication-theme){font-family:var(--pub-sans),ui-sans-serif,system-ui,sans-serif}\n`\n\nconst serif = \"[font-family:var(--pub-display),ui-serif,Georgia,serif]\"\nconst text = \"[font-family:var(--pub-body),ui-serif,Georgia,serif]\"\n\n/** A duotone illustration in ink and red, standing in for photography. */\nfunction MagArt({ variant, className, image, imageAlt }: { variant: number; className?: string; image?: MediaSource; imageAlt?: string }) {\n  const v = ((variant % 6) + 6) % 6\n  if (image) return <Media media={image} alt={imageAlt} fill className={cn(\"aspect-[4/3] w-full\", className)} />\n  return (\n    <div aria-hidden=\"true\" className={cn(\"bg-secondary relative isolate aspect-[4/3] w-full overflow-hidden\", className)}>\n      {v === 0 && (<><div className=\"bg-chart-3 absolute inset-0\" />{[0, 1, 2, 3].map((i) => <div key={i} className=\"bg-foreground absolute bottom-0\" style={{ left: `${12 + i * 20}%`, width: \"14%\", height: `${28 + i * 10}%` }} />)}<div className=\"bg-chart-1 absolute top-[14%] end-[14%] w-[14%] aspect-square rounded-full\" /></>)}\n      {v === 1 && (<><div className=\"bg-chart-2 absolute inset-0\" /><div className=\"bg-card absolute top-[10%] start-[10%] w-[34%] aspect-square rounded-full\" />{[0, 1, 2, 3, 4, 5].map((i) => <div key={i} className=\"bg-chart-1 absolute w-[6%] aspect-square rounded-full\" style={{ left: `${44 + (i % 3) * 14}%`, top: `${42 + Math.floor(i / 3) * 18}%` }} />)}</>)}\n      {v === 2 && (<><div className=\"bg-card absolute inset-0\" /><div className=\"absolute inset-[10%] grid grid-cols-6 gap-[2%]\">{Array.from({ length: 24 }, (_, i) => <div key={i} className={cn(\"aspect-square\", i % 7 === 0 ? \"bg-chart-1\" : i % 3 === 0 ? \"bg-foreground\" : \"bg-secondary\")} />)}</div></>)}\n      {v === 3 && (<><div className=\"bg-chart-5 absolute inset-0\" /><div className=\"bg-card absolute top-1/2 left-1/2 w-[56%] aspect-square -translate-x-1/2 -translate-y-1/2 rounded-full\" /><div className=\"bg-foreground absolute top-1/2 left-1/2 w-[34%] aspect-square -translate-x-1/2 -translate-y-1/2 rounded-full\" /><div className=\"bg-chart-1 absolute top-1/2 left-1/2 w-[12%] aspect-square -translate-x-1/2 -translate-y-1/2 rounded-full\" /></>)}\n      {v === 4 && (<><div className=\"bg-foreground absolute inset-0\" />{[0, 1, 2, 3, 4, 5, 6].map((i) => <div key={i} className=\"bg-chart-3 absolute inset-x-[8%] h-[3px]\" style={{ top: `${14 + i * 11}%`, opacity: 1 - i * 0.1 }} />)}<div className=\"bg-chart-1 absolute top-[22%] start-[22%] h-[40%] w-[34%] rotate-6\" /></>)}\n      {v === 5 && (<><div className=\"bg-chart-4 absolute inset-0\" /><div className=\"bg-card absolute -end-[10%] -bottom-[30%] w-[90%] aspect-square rounded-full\" /><div className=\"bg-foreground absolute top-[16%] start-[12%] h-[8%] w-[50%]\" /><div className=\"bg-foreground absolute top-[30%] start-[12%] h-[8%] w-[34%]\" /></>)}\n    </div>\n  )\n}\n\nconst dateline = new Intl.DateTimeFormat(\"en-US\", { weekday: \"long\", month: \"long\", day: \"numeric\", year: \"numeric\", timeZone: \"UTC\" }).format(new Date(\"2026-10-01\"))\n\ntype PublicationShellProps = React.ComponentProps<\"div\"> & {\n  /** The page being shown. */\n  page: PublicationPage\n  /** The section to mark current in the nav. */\n  section?: string\n  /** Override where pages live (used by previews). */\n  hrefs?: Partial<PublicationHrefs>\n}\n\n/** Publication's frame: a dateline, a centred masthead between double rules, a section nav and a newsletter-first footer. */\nfunction PublicationShell({ page, section, hrefs: overrides, className, style, children, ...props }: PublicationShellProps) {\n  const hrefs = { ...defaultHrefs, ...overrides }\n  const [open, setOpen] = React.useState(false)\n  React.useEffect(() => {\n    const classes = [pubDisplay.variable, pubBody.variable, pubSans.variable].filter(Boolean)\n    document.body.classList.add(...classes)\n    return () => document.body.classList.remove(...classes)\n  }, [])\n  const current = section ?? (page === \"article\" ? \"Essays\" : undefined)\n  return (\n    <div\n      data-slot=\"publication\"\n      className={cn(\"publication-theme bg-background text-foreground relative min-h-dvh overflow-x-clip\", pubDisplay.variable, pubBody.variable, pubSans.variable, className)}\n      style={{ fontFamily: \"var(--pub-sans), ui-sans-serif, system-ui, sans-serif\", ...style }}\n      {...props}\n    >\n      <style>{publicationCss}</style>\n      <header className=\"bg-background\">\n        <div className=\"mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-2.5 text-xs sm:px-6\">\n          <p className=\"text-muted-foreground hidden sm:block\">{dateline}</p>\n          <p className=\"text-muted-foreground font-medium sm:hidden\">Issue 48</p>\n          <p className=\"flex items-center gap-4\"><a href={hrefs.issues} className=\"hover:text-chart-1 font-semibold underline-offset-4 hover:underline\">Issue 48 · Autumn 2026</a><a href=\"#\" className=\"hidden font-semibold underline-offset-4 hover:underline sm:inline\">Sign in</a><a href={hrefs.issues} className=\"bg-chart-1 text-primary-foreground focus-visible:ring-ring/50 rounded-sm px-3 py-1.5 font-bold tracking-wide uppercase outline-none focus-visible:ring-[3px]\">Subscribe</a></p>\n        </div>\n        <div className=\"mx-auto max-w-6xl px-4 sm:px-6\">\n          <div className=\"border-foreground border-y-4 border-double py-4 text-center\">\n            <a href={hrefs.home} className={cn(\"focus-visible:ring-ring/50 inline-block rounded text-[clamp(2.6rem,9vw,6.5rem)] leading-none tracking-[-0.02em] outline-none focus-visible:ring-[3px]\", serif)}>Marginalia</a>\n            <p className=\"text-muted-foreground mt-1 text-xs tracking-[0.2em] uppercase\">A magazine of ideas, written slowly</p>\n          </div>\n          <div className=\"flex items-center justify-between border-b py-1\">\n            <nav aria-label=\"Sections\" className=\"hidden md:block\">\n              <ul className=\"flex gap-1\">{sections.map((s) => <li key={s}><a href={hrefs.section} aria-current={current === s ? \"page\" : undefined} className=\"hover:text-chart-1 aria-[current=page]:text-chart-1 focus-visible:ring-ring/50 block rounded px-3.5 py-2.5 text-sm font-semibold tracking-wide uppercase outline-none transition-colors focus-visible:ring-[3px]\">{s}</a></li>)}</ul>\n            </nav>\n            <button type=\"button\" aria-label={open ? \"Close sections\" : \"Open sections\"} aria-expanded={open} aria-controls=\"pub-mobile-nav\" onClick={() => setOpen((v) => !v)} className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-10 items-center justify-center rounded outline-none focus-visible:ring-[3px] md:hidden\">{open ? <X className=\"size-5\" aria-hidden=\"true\" /> : <Menu className=\"size-5\" aria-hidden=\"true\" />}</button>\n            <button type=\"button\" aria-label=\"Search\" className=\"hover:bg-accent focus-visible:ring-ring/50 ms-auto inline-flex size-10 items-center justify-center rounded outline-none focus-visible:ring-[3px]\"><Search className=\"size-5\" aria-hidden=\"true\" /></button>\n          </div>\n          {open && <nav id=\"pub-mobile-nav\" aria-label=\"Sections, mobile\" className=\"border-b py-2 md:hidden\">{sections.map((s) => <a key={s} href={hrefs.section} className=\"hover:bg-accent block rounded px-2 py-2.5 text-sm font-semibold tracking-wide uppercase\">{s}</a>)}</nav>}\n        </div>\n      </header>\n      {children}\n      <footer className=\"bg-foreground text-background mt-20\">\n        <div className=\"mx-auto grid max-w-6xl gap-10 px-4 py-14 sm:px-6 md:grid-cols-[1.4fr_repeat(3,1fr)]\">\n          <div><p className={cn(\"text-3xl\", serif)}>Marginalia</p><p className={cn(\"mt-3 max-w-xs text-sm opacity-75\", text)}>Independent since 2014. Funded by readers, not by clicks.</p></div>\n          {[[\"Read\", [\"Essays\", \"Culture\", \"Science\", \"Technology\"]], [\"Magazine\", [\"Issues\", \"Subscribe\", \"Gift a year\", \"Back issues\"]], [\"About\", [\"Our writers\", \"Submissions\", \"Contact\", \"Privacy\"]]].map(([t, items]) => (\n            <div key={t as string}><h2 className=\"text-xs font-bold tracking-widest uppercase opacity-60\">{t as string}</h2><ul className=\"mt-4 space-y-2.5\">{(items as string[]).map((i) => <li key={i}><a href={i === \"Issues\" || i === \"Subscribe\" ? hrefs.issues : i === \"Our writers\" ? hrefs.author : hrefs.section} className=\"text-sm opacity-90 hover:underline\">{i}</a></li>)}</ul></div>\n          ))}\n        </div>\n        <p className=\"mx-auto max-w-6xl border-t border-current/20 px-4 py-5 text-xs opacity-60 sm:px-6\">© 2026 Marginalia Press. All rights reserved.</p>\n      </footer>\n    </div>\n  )\n}\n\nexport { MagArt, PublicationShell, defaultHrefs as publicationDefaultHrefs, serif as pubSerifClass, text as pubTextClass, type PublicationHrefs, type PublicationPage, type PublicationShellProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/publication/publication-theme.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/publication/publication-home.tsx",
      "content": "// Ballmac UI: Publication home page. https://ui.ballmac.com/templates/template-publication\n\"use client\"\n\nimport * as React from \"react\"\nimport { Check } from \"lucide-react\"\n\nimport { articles, formatShort, getAuthor, sections } from \"@/components/ballmac/templates/publication/publication-data\"\nimport { MagArt, PublicationShell, pubSerifClass, pubTextClass, type PublicationHrefs } from \"@/components/ballmac/templates/publication/publication-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst kicker = \"text-chart-1 text-xs font-bold tracking-[0.14em] uppercase\"\n\nfunction Newsletter({ tone = \"card\" }: { tone?: \"card\" | \"ink\" }) {\n  const [email, setEmail] = React.useState(\"\")\n  const [error, setError] = React.useState(\"\")\n  const [done, setDone] = React.useState(false)\n  function submit(e: React.FormEvent) {\n    e.preventDefault()\n    if (!/^\\S+@\\S+\\.\\S+$/.test(email.trim())) return setError(\"Enter an email like you@example.com.\")\n    setError(\"\")\n    setDone(true)\n  }\n  return (\n    <section aria-labelledby={`nl-${tone}`} className={cn(\"border-foreground border-y-4 border-double px-2 py-10 text-center sm:py-14\", tone === \"ink\" && \"bg-secondary\")}>\n      <p className={kicker}>The Sunday Margin</p>\n      <h2 id={`nl-${tone}`} className={cn(\"mx-auto mt-3 max-w-xl text-4xl text-balance sm:text-5xl\", pubSerifClass)}>One good essay, every Sunday morning.</h2>\n      <p className={cn(\"text-muted-foreground mx-auto mt-3 max-w-md text-pretty\", pubTextClass)}>Free, short and unhurried. Read by 41,000 people over breakfast.</p>\n      {done ? (\n        <p role=\"status\" className=\"mt-6 inline-flex items-center gap-2 font-semibold\"><Check className=\"text-chart-4 size-5\" aria-hidden=\"true\" />You’re on the list. See you Sunday.</p>\n      ) : (\n        <form onSubmit={submit} noValidate className=\"mx-auto mt-6 flex max-w-md flex-col gap-2 sm:flex-row\">\n          <label htmlFor={`nl-email-${tone}`} className=\"sr-only\">Email address</label>\n          <input id={`nl-email-${tone}`} type=\"email\" value={email} onChange={(e) => setEmail(e.target.value)} aria-invalid={!!error} aria-describedby={error ? `nl-err-${tone}` : undefined} placeholder=\"you@example.com\" className=\"bg-background focus-visible:ring-ring/50 placeholder:text-muted-foreground aria-[invalid=true]:border-destructive h-12 flex-1 rounded-sm border px-4 outline-none focus-visible:ring-[3px]\" />\n          <button type=\"submit\" className=\"bg-foreground text-background focus-visible:ring-ring/50 h-12 rounded-sm px-6 text-sm font-bold tracking-wide uppercase outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]\">Sign me up</button>\n        </form>\n      )}\n      {error && <p id={`nl-err-${tone}`} role=\"alert\" className=\"text-destructive mt-2 text-sm\">{error}</p>}\n    </section>\n  )\n}\n\ntype PublicationHomeProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<PublicationHrefs> }\n\n/** The Publication home page: a lead story with a dek, supporting stories, an opinion column, picks and a newsletter. */\nfunction PublicationHome({ hrefs, ...props }: PublicationHomeProps) {\n  const [lead, ...rest] = articles\n  const side = rest.slice(0, 3)\n  const grid = rest.slice(3, 6)\n  const popular = [...articles].filter((a) => a.popular).sort((a, b) => (a.popular ?? 0) - (b.popular ?? 0))\n  const a = hrefs?.article ?? \"/publication/essays/the-unfinished-city\"\n  return (\n    <PublicationShell page=\"home\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-6xl px-4 pt-8 sm:px-6\">\n        <div className=\"grid gap-8 lg:grid-cols-[1fr_20rem] lg:gap-10\">\n          <article>\n            <a href={a} className=\"group focus-visible:ring-ring/50 block rounded outline-none focus-visible:ring-[3px]\">\n              <MagArt variant={lead.art} image={lead.image} imageAlt={lead.imageAlt} className=\"aspect-[16/10]\" />\n              <p className={cn(\"mt-5\", kicker)}>{lead.kicker}</p>\n              <h1 className={cn(\"mt-2 text-[clamp(2.4rem,6vw,4.5rem)] leading-[1.02] tracking-[-0.01em] text-balance group-hover:underline decoration-1 underline-offset-4\", pubSerifClass)}>{lead.title}</h1>\n              <p className={cn(\"text-muted-foreground mt-4 max-w-2xl text-xl leading-relaxed text-pretty\", pubTextClass)}>{lead.dek}</p>\n              <p className=\"text-muted-foreground mt-4 text-sm\">By <span className=\"text-foreground font-semibold\">{getAuthor(lead.author).name}</span> · {lead.read} min read</p>\n            </a>\n          </article>\n          <aside aria-label=\"More stories\" className=\"lg:border-s lg:ps-10\">\n            <ul className=\"divide-y\">\n              {side.map((s) => (\n                <li key={s.id} className=\"py-5 first:pt-0 lg:last:pb-0\">\n                  <a href={a} className=\"group focus-visible:ring-ring/50 block rounded outline-none focus-visible:ring-[3px]\">\n                    <p className={kicker}>{s.section}</p>\n                    <h2 className={cn(\"mt-1.5 text-2xl leading-tight text-balance group-hover:underline underline-offset-4\", pubSerifClass)}>{s.title}</h2>\n                    <p className=\"text-muted-foreground mt-2 text-sm\">{getAuthor(s.author).name} · {s.read} min</p>\n                  </a>\n                </li>\n              ))}\n            </ul>\n          </aside>\n        </div>\n\n        <section aria-label=\"Latest\" className=\"mt-12 border-t-2 border-current pt-8\">\n          <ul className=\"grid gap-x-8 gap-y-10 sm:grid-cols-3\">\n            {grid.map((g) => (\n              <li key={g.id}>\n                <a href={a} className=\"group focus-visible:ring-ring/50 block rounded outline-none focus-visible:ring-[3px]\">\n                  <MagArt variant={g.art} image={g.image} imageAlt={g.imageAlt} />\n                  <p className={cn(\"mt-4\", kicker)}>{g.section}</p>\n                  <h3 className={cn(\"mt-1.5 text-2xl leading-tight text-balance group-hover:underline underline-offset-4\", pubSerifClass)}>{g.title}</h3>\n                  <p className={cn(\"text-muted-foreground mt-2 text-pretty\", pubTextClass)}>{g.dek}</p>\n                  <p className=\"text-muted-foreground mt-3 text-sm\">{getAuthor(g.author).name} · {formatShort(g.date)}</p>\n                </a>\n              </li>\n            ))}\n          </ul>\n        </section>\n\n        <div className=\"mt-16 grid gap-12 lg:grid-cols-[1fr_20rem]\">\n          <section aria-labelledby=\"pub-opinion\">\n            <h2 id=\"pub-opinion\" className=\"border-foreground border-b-2 pb-2 text-sm font-bold tracking-[0.14em] uppercase\">Opinion &amp; essays</h2>\n            <ul className=\"divide-y\">\n              {articles.filter((x) => x.section === \"Essays\" || x.section === \"Interviews\").slice(1, 5).map((o) => {\n                const au = getAuthor(o.author)\n                return (\n                  <li key={o.id} className=\"grid grid-cols-[3.5rem_1fr] gap-4 py-6\">\n                    <span className={cn(\"flex size-14 items-center justify-center rounded-full text-lg\", pubSerifClass, [\"bg-chart-3 text-[var(--publication-on-accent)]\", \"bg-chart-5 text-[var(--publication-on-accent)]\", \"bg-chart-4 text-background\", \"bg-secondary text-secondary-foreground\"][au.tone - 1])} aria-hidden=\"true\">{au.name.split(\" \").map((w) => w[0]).join(\"\")}</span>\n                    <a href={a} className=\"group focus-visible:ring-ring/50 rounded outline-none focus-visible:ring-[3px]\">\n                      <p className=\"text-muted-foreground text-xs font-semibold tracking-wide uppercase\">{au.name}</p>\n                      <h3 className={cn(\"mt-1 text-2xl leading-tight text-balance group-hover:underline underline-offset-4\", pubSerifClass)}>{o.title}</h3>\n                      <p className={cn(\"text-muted-foreground mt-1.5 text-pretty\", pubTextClass)}>{o.dek}</p>\n                    </a>\n                  </li>\n                )\n              })}\n            </ul>\n          </section>\n          <aside aria-labelledby=\"pub-popular\" className=\"bg-surface h-fit border p-6\">\n            <h2 id=\"pub-popular\" className=\"text-sm font-bold tracking-[0.14em] uppercase\">Most read</h2>\n            <ol className=\"mt-4 space-y-5\">\n              {popular.map((p, i) => (\n                <li key={p.id} className=\"grid grid-cols-[2rem_1fr] gap-2\">\n                  <span className={cn(\"text-chart-1 text-4xl leading-none\", pubSerifClass)} aria-hidden=\"true\">{i + 1}</span>\n                  <a href={a} className={cn(\"hover:underline text-lg leading-snug text-balance underline-offset-4\", pubSerifClass)}><span className=\"sr-only\">{i + 1}. </span>{p.title}</a>\n                </li>\n              ))}\n            </ol>\n          </aside>\n        </div>\n\n        <div className=\"mt-16\"><Newsletter /></div>\n        <ul aria-label=\"Browse by section\" className=\"mt-10 flex flex-wrap justify-center gap-2\">{sections.map((s) => <li key={s}><a href={hrefs?.section ?? \"/publication/essays\"} className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex h-10 items-center rounded-full border px-5 text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px]\">{s}</a></li>)}</ul>\n      </main>\n    </PublicationShell>\n  )\n}\n\nexport { Newsletter, PublicationHome, type PublicationHomeProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/publication/publication-home.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/publication/publication-article.tsx",
      "content": "// Ballmac UI: Publication article page. https://ui.ballmac.com/templates/template-publication\n\"use client\"\n\nimport * as React from \"react\"\nimport { Link2 } from \"lucide-react\"\n\nimport { CopyButton } from \"@/components/ballmac/copy-button\"\nimport { ScrollProgress } from \"@/components/ballmac/scroll-progress\"\nimport { articles, formatDate, getAuthor } from \"@/components/ballmac/templates/publication/publication-data\"\nimport { Newsletter } from \"@/components/ballmac/templates/publication/publication-home\"\nimport { MagArt, PublicationShell, pubSerifClass, pubTextClass, type PublicationHrefs } from \"@/components/ballmac/templates/publication/publication-theme\"\nimport { cn } from \"@/lib/utils\"\n\n/** A paragraph with an optional margin note: beside the text on wide screens, under it on small ones. */\nfunction Para({ children, note, n }: { children: React.ReactNode; note?: string; n?: number }) {\n  return (\n    <div className=\"relative\">\n      <p className={cn(\"text-[1.1875rem] leading-[1.8] text-pretty\", pubTextClass)}>\n        {children}\n        {n && <sup className=\"ms-0.5\"><a href={`#note-${n}`} id={`ref-${n}`} className=\"text-chart-1 focus-visible:ring-ring/50 rounded px-0.5 font-sans text-xs font-bold outline-none focus-visible:ring-[3px]\" aria-label={`Note ${n}`}>{n}</a></sup>}\n      </p>\n      {note && n && (\n        <aside id={`note-${n}`} aria-label={`Note ${n}`} className=\"text-muted-foreground border-chart-1 mt-3 border-s-2 ps-4 text-sm leading-relaxed xl:absolute xl:top-0 xl:start-full xl:mt-0 xl:ms-10 xl:w-56 xl:border-s-0 xl:ps-0\">\n          <span className=\"text-chart-1 me-1.5 font-bold\">{n}</span>{note}\n        </aside>\n      )}\n    </div>\n  )\n}\n\ntype PublicationArticleProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<PublicationHrefs> }\n\n/** The Publication article page: a reading-progress bar, drop cap, margin notes that sit beside the text on wide screens, a pull quote and an author card. */\nfunction PublicationArticle({ hrefs, ...props }: PublicationArticleProps) {\n  const a = articles[0]\n  const au = getAuthor(a.author)\n  const related = articles.slice(1, 4)\n  return (\n    <PublicationShell page=\"article\" section=\"Essays\" hrefs={hrefs} {...props}>\n      <ScrollProgress position=\"top\" className=\"[&>*]:bg-chart-1\" />\n      <main>\n        <header className=\"mx-auto max-w-3xl px-4 pt-12 text-center sm:px-6\">\n          <p className=\"text-chart-1 text-xs font-bold tracking-[0.14em] uppercase\">{a.section} · {a.kicker}</p>\n          <h1 className={cn(\"mt-4 text-[clamp(2.6rem,6.4vw,4.8rem)] leading-[1.02] tracking-[-0.01em] text-balance\", pubSerifClass)}>{a.title}</h1>\n          <p className={cn(\"text-muted-foreground mt-5 text-xl leading-relaxed text-pretty\", pubTextClass)}>{a.dek}</p>\n          <div className=\"mt-7 flex items-center justify-center gap-3 text-sm\">\n            <span className={cn(\"bg-chart-3 text-[var(--publication-on-accent)] flex size-11 items-center justify-center rounded-full text-base\", pubSerifClass)} aria-hidden=\"true\">IM</span>\n            <p className=\"text-start\"><span className=\"block font-semibold\">By {au.name}</span><span className=\"text-muted-foreground\">{formatDate(a.date)} · {a.read} min read</span></p>\n          </div>\n        </header>\n        <div className=\"mx-auto mt-10 max-w-5xl px-4 sm:px-6\"><MagArt variant={a.art} image={a.image} imageAlt={a.imageAlt} className=\"aspect-[16/8]\" /><p className=\"text-muted-foreground mt-2 text-xs\">Scaffolding on Rua da Madalena, Lisbon. Illustration.</p></div>\n\n        <article className=\"mx-auto mt-12 max-w-[40rem] space-y-7 px-4 sm:px-6\">\n          <div className=\"relative\">\n            <p className={cn(\"first-letter:float-start first-letter:me-3 first-letter:text-[5.2rem] first-letter:leading-[0.8] first-letter:font-bold first-letter:[font-family:var(--pub-display)] text-[1.1875rem] leading-[1.8] text-pretty\", pubTextClass)}>\n              There is a street near my flat that has been covered in scaffolding for as long as I have lived here. The poles have weathered, a pigeon has opinions about the third floor, and the building behind it appears to be neither getting worse nor better. I used to find it depressing.<sup className=\"ms-0.5\"><a href=\"#note-1\" id=\"ref-1\" className=\"text-chart-1 focus-visible:ring-ring/50 rounded px-0.5 font-sans text-xs font-bold outline-none focus-visible:ring-[3px]\" aria-label=\"Note 1\">1</a></sup>\n            </p>\n            <aside id=\"note-1\" aria-label=\"Note 1\" className=\"text-muted-foreground border-chart-1 mt-3 border-s-2 ps-4 text-sm leading-relaxed xl:absolute xl:top-0 xl:start-full xl:mt-0 xl:ms-10 xl:w-56 xl:border-s-0 xl:ps-0\"><span className=\"text-chart-1 me-1.5 font-bold\">1</span>The building is a 1920s tenement. The scaffold went up in 2019 after a cornice fell on a parked car.</aside>\n          </div>\n          <Para n={2} note=\"Jane Jacobs made this point in 1961. Cities, she wrote, need old buildings, and a great many of them, mixed in with the new.\">Then I started to notice how many of the places I love are unfinished in exactly this way: the cathedral with a crane, the square half dug up, the market that is always about to be renovated. A city that is finished is a museum.</Para>\n          <h2 className={cn(\"pt-4 text-3xl leading-tight text-balance\", pubSerifClass)}>What patience looks like in stone</h2>\n          <Para>Stonemasons talk about work they will never see completed. The medieval builders of a cathedral knew they would die before the spire went up, and they built the foundations for a roof they would never stand under. We have mostly lost that habit of mind.</Para>\n          <blockquote className=\"border-foreground my-10 border-y-4 border-double py-8 text-center\"><p className={cn(\"text-[clamp(1.7rem,3.6vw,2.4rem)] leading-tight text-balance\", pubSerifClass)}>“A city that is finished is a museum.”</p></blockquote>\n          <Para n={3} note=\"The scaffold on the Rua da Madalena has a name among neighbours: ‘o esqueleto’. The skeleton.\">I asked the neighbours what they thought. Nobody wanted it gone. They had named it, mapped their bicycles to its poles and learned which floor had the best light. The scaffold had become part of the street’s grammar.</Para>\n          <Para>None of this is an argument for neglect. It is an argument for noticing that the stage between the old and the new is not a failure, and for learning to live there gracefully, perhaps even with affection.</Para>\n        </article>\n\n        <div className=\"mx-auto mt-14 flex max-w-[40rem] flex-wrap items-center justify-between gap-4 border-y px-4 py-4 sm:px-6\">\n          <p className=\"text-muted-foreground text-sm\">Filed under <a href={hrefs?.section ?? \"/publication/essays\"} className=\"text-foreground font-semibold underline-offset-4 hover:underline\">Essays</a></p>\n          <div className=\"flex items-center gap-2\"><span className=\"text-sm font-semibold\">Share</span><CopyButton value=\"https://marginalia.example/essays/the-unfinished-city\" ariaLabel=\"Copy link to this article\" /></div>\n        </div>\n\n        <aside aria-labelledby=\"pub-author\" className=\"mx-auto mt-10 flex max-w-[40rem] gap-5 px-4 sm:px-6\">\n          <span className={cn(\"bg-chart-3 text-[var(--publication-on-accent)] flex size-16 shrink-0 items-center justify-center rounded-full text-2xl\", pubSerifClass)} aria-hidden=\"true\">IM</span>\n          <div><h2 id=\"pub-author\" className=\"font-semibold\">About {au.name}</h2><p className={cn(\"text-muted-foreground mt-1 text-pretty\", pubTextClass)}>{au.bio}</p><a href={hrefs?.author ?? \"/publication/authors/ines\"} className=\"text-chart-1 mt-2 inline-flex items-center gap-1.5 text-sm font-semibold underline-offset-4 hover:underline\"><Link2 className=\"size-4\" aria-hidden=\"true\" />More by {au.name.split(\" \")[0]}</a></div>\n        </aside>\n\n        <section aria-labelledby=\"pub-related\" className=\"mx-auto mt-20 max-w-6xl px-4 sm:px-6\">\n          <h2 id=\"pub-related\" className=\"border-foreground border-b-2 pb-2 text-sm font-bold tracking-[0.14em] uppercase\">Keep reading</h2>\n          <ul className=\"mt-8 grid gap-8 sm:grid-cols-3\">{related.map((r) => <li key={r.id}><a href={hrefs?.article ?? \"#\"} className=\"group focus-visible:ring-ring/50 block rounded outline-none focus-visible:ring-[3px]\"><MagArt variant={r.art} image={r.image} imageAlt={r.imageAlt} /><h3 className={cn(\"mt-4 text-2xl leading-tight text-balance group-hover:underline underline-offset-4\", pubSerifClass)}>{r.title}</h3><p className=\"text-muted-foreground mt-2 text-sm\">{getAuthor(r.author).name} · {r.read} min</p></a></li>)}</ul>\n        </section>\n        <div className=\"mx-auto mt-16 max-w-6xl px-4 sm:px-6\"><Newsletter tone=\"ink\" /></div>\n      </main>\n    </PublicationShell>\n  )\n}\n\nexport { PublicationArticle, type PublicationArticleProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/publication/publication-article.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/publication/publication-section.tsx",
      "content": "// Ballmac UI: Publication section page. https://ui.ballmac.com/templates/template-publication\n\"use client\"\n\nimport * as React from \"react\"\n\nimport { articles, formatShort, getAuthor, sections, type Section } from \"@/components/ballmac/templates/publication/publication-data\"\nimport { MagArt, PublicationShell, pubSerifClass, pubTextClass, type PublicationHrefs } from \"@/components/ballmac/templates/publication/publication-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst descriptions: Record<string, string> = { Essays: \"Long arguments, patiently made. New essays every week.\", Culture: \"Food, music, books and the small rituals of daily life.\", Science: \"Field work, deep time and the people who count things.\", Technology: \"Slow technology: tools, craft and the people who make them.\", Interviews: \"Unhurried conversations with writers and makers.\" }\n\ntype PublicationSectionProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<PublicationHrefs>; initial?: Section | \"All\" }\n\n/** A Publication section page: a topic switch, a sort, a story list with thumbnails and a \"load more\" that adds stories. */\nfunction PublicationSection({ hrefs, initial = \"Essays\", ...props }: PublicationSectionProps) {\n  const [section, setSection] = React.useState<Section | \"All\">(initial)\n  const [sort, setSort] = React.useState<\"new\" | \"long\">(\"new\")\n  const [count, setCount] = React.useState(4)\n  const list = articles.filter((a) => section === \"All\" || a.section === section).sort((a, b) => (sort === \"new\" ? b.date.localeCompare(a.date) : b.read - a.read))\n  const shown = list.slice(0, count)\n  const a = hrefs?.article ?? \"/publication/essays/the-unfinished-city\"\n  return (\n    <PublicationShell page=\"section\" section={section === \"All\" ? undefined : section} hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-4xl px-4 pt-10 sm:px-6\">\n        <p className=\"text-chart-1 text-xs font-bold tracking-[0.14em] uppercase\">Section</p>\n        <h1 className={cn(\"mt-2 text-[clamp(3rem,8vw,5.5rem)] leading-none\", pubSerifClass)}>{section === \"All\" ? \"Everything\" : section}</h1>\n        <p className={cn(\"text-muted-foreground mt-4 max-w-xl text-xl text-pretty\", pubTextClass)}>{section === \"All\" ? \"Every story, newest first.\" : descriptions[section]}</p>\n        <div className=\"mt-8 flex flex-wrap items-center gap-3 border-y py-3\">\n          <div role=\"group\" aria-label=\"Choose a section\" className=\"flex flex-wrap gap-1.5\">\n            {([\"All\", ...sections] as const).map((s) => <button key={s} type=\"button\" aria-pressed={section === s} onClick={() => { setSection(s); setCount(4) }} className={cn(\"focus-visible:ring-ring/50 h-9 rounded-full border px-4 text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px]\", section === s ? \"bg-foreground text-background border-transparent\" : \"hover:bg-accent\")}>{s}</button>)}\n          </div>\n          <label className=\"text-muted-foreground ms-auto flex items-center gap-2 text-sm\">Sort <select value={sort} onChange={(e) => setSort(e.target.value as \"new\" | \"long\")} className=\"bg-background focus-visible:ring-ring/50 text-foreground h-9 rounded border px-2 outline-none focus-visible:ring-[3px]\"><option value=\"new\">Newest</option><option value=\"long\">Longest</option></select></label>\n        </div>\n        <p className=\"sr-only\" role=\"status\">Showing {shown.length} of {list.length} stories</p>\n        {shown.length === 0 && <p className=\"text-muted-foreground mt-12 border border-dashed p-12 text-center\">Nothing in this section yet.</p>}\n        <ul className=\"divide-y\">\n          {shown.map((s) => (\n            <li key={s.id}>\n              <a href={a} className=\"group focus-visible:ring-ring/50 grid gap-5 rounded py-8 outline-none focus-visible:ring-[3px] sm:grid-cols-[1fr_14rem]\">\n                <div>\n                  <p className=\"text-chart-1 text-xs font-bold tracking-[0.14em] uppercase\">{s.section} · {s.kicker}</p>\n                  <h2 className={cn(\"mt-2 text-3xl leading-tight text-balance group-hover:underline underline-offset-4 sm:text-4xl\", pubSerifClass)}>{s.title}</h2>\n                  <p className={cn(\"text-muted-foreground mt-3 text-pretty\", pubTextClass)}>{s.dek}</p>\n                  <p className=\"text-muted-foreground mt-4 text-sm\">By <span className=\"text-foreground font-semibold\">{getAuthor(s.author).name}</span> · {formatShort(s.date)} · {s.read} min</p>\n                </div>\n                <MagArt variant={s.art} image={s.image} imageAlt={s.imageAlt} className=\"order-first sm:order-none\" />\n              </a>\n            </li>\n          ))}\n        </ul>\n        {count < list.length && <div className=\"mt-6 text-center\"><button type=\"button\" onClick={() => setCount((c) => c + 4)} className=\"hover:bg-accent focus-visible:ring-ring/50 h-12 rounded-sm border-2 border-current px-8 text-sm font-bold tracking-wide uppercase outline-none transition-colors focus-visible:ring-[3px]\">Load more stories</button></div>}\n      </main>\n    </PublicationShell>\n  )\n}\n\nexport { PublicationSection, type PublicationSectionProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/publication/publication-section.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/publication/publication-author.tsx",
      "content": "// Ballmac UI: Publication author page. https://ui.ballmac.com/templates/template-publication\n\"use client\"\n\nimport * as React from \"react\"\n\nimport { articles, authors, formatShort } from \"@/components/ballmac/templates/publication/publication-data\"\nimport { MagArt, PublicationShell, pubSerifClass, pubTextClass, type PublicationHrefs } from \"@/components/ballmac/templates/publication/publication-theme\"\nimport { cn } from \"@/lib/utils\"\n\ntype PublicationAuthorProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<PublicationHrefs>; authorId?: string }\n\n/** A Publication author page: portrait initials, a bio, the byline count and every story they wrote. */\nfunction PublicationAuthor({ hrefs, authorId = \"ines\", ...props }: PublicationAuthorProps) {\n  const au = authors.find((x) => x.id === authorId) ?? authors[0]\n  const mine = articles.filter((a) => a.author === au.id)\n  const total = mine.reduce((n, a) => n + a.read, 0)\n  const a = hrefs?.article ?? \"/publication/essays/the-unfinished-city\"\n  return (\n    <PublicationShell page=\"author\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-4xl px-4 pt-12 sm:px-6\">\n        <header className=\"grid gap-8 border-b-4 border-double border-foreground pb-10 sm:grid-cols-[10rem_1fr] sm:items-center\">\n          <span className={cn(\"flex size-36 items-center justify-center rounded-full text-6xl sm:size-40\", pubSerifClass, [\"bg-chart-3 text-[var(--publication-on-accent)]\", \"bg-chart-5 text-[var(--publication-on-accent)]\", \"bg-chart-4 text-background\", \"bg-secondary text-secondary-foreground\"][au.tone - 1])} aria-hidden=\"true\">{au.name.split(\" \").map((w) => w[0]).join(\"\")}</span>\n          <div>\n            <p className=\"text-chart-1 text-xs font-bold tracking-[0.14em] uppercase\">{au.role}</p>\n            <h1 className={cn(\"mt-2 text-[clamp(2.8rem,7vw,4.8rem)] leading-none\", pubSerifClass)}>{au.name}</h1>\n            <p className={cn(\"text-muted-foreground mt-4 max-w-xl text-xl leading-relaxed text-pretty\", pubTextClass)}>{au.bio}</p>\n            <dl className=\"mt-5 flex gap-8 text-sm\"><div><dd className=\"text-2xl font-bold tabular-nums\">{mine.length}</dd><dt className=\"text-muted-foreground\">stories</dt></div><div><dd className=\"text-2xl font-bold tabular-nums\">{total}</dd><dt className=\"text-muted-foreground\">minutes of reading</dt></div></dl>\n          </div>\n        </header>\n        <section aria-labelledby=\"pub-by\" className=\"mt-10\">\n          <h2 id=\"pub-by\" className=\"text-sm font-bold tracking-[0.14em] uppercase\">Stories by {au.name.split(\" \")[0]}</h2>\n          <ul className=\"divide-y\">\n            {mine.map((s) => (\n              <li key={s.id}><a href={a} className=\"group focus-visible:ring-ring/50 grid gap-5 rounded py-7 outline-none focus-visible:ring-[3px] sm:grid-cols-[1fr_12rem]\"><div><p className=\"text-chart-1 text-xs font-bold tracking-[0.14em] uppercase\">{s.section} · {formatShort(s.date)}</p><h3 className={cn(\"mt-2 text-3xl leading-tight text-balance group-hover:underline underline-offset-4\", pubSerifClass)}>{s.title}</h3><p className={cn(\"text-muted-foreground mt-2 text-pretty\", pubTextClass)}>{s.dek}</p></div><MagArt variant={s.art} image={s.image} imageAlt={s.imageAlt} className=\"order-first sm:order-none\" /></a></li>\n            ))}\n          </ul>\n        </section>\n      </main>\n    </PublicationShell>\n  )\n}\n\nexport { PublicationAuthor, type PublicationAuthorProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/publication/publication-author.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/publication/publication-issues.tsx",
      "content": "// Ballmac UI: Publication issues page. https://ui.ballmac.com/templates/template-publication\n\"use client\"\n\nimport * as React from \"react\"\nimport { Check } from \"lucide-react\"\n\nimport { issues } from \"@/components/ballmac/templates/publication/publication-data\"\nimport { Newsletter } from \"@/components/ballmac/templates/publication/publication-home\"\nimport { MagArt, PublicationShell, pubSerifClass, pubTextClass, type PublicationHrefs } from \"@/components/ballmac/templates/publication/publication-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst plans = [\n  { id: \"digital\", name: \"Digital\", price: 4, per: \"a month\", features: [\"Every story and archive\", \"Four issues a year as PDF and ePub\", \"The Sunday Margin newsletter\"] },\n  { id: \"print\", name: \"Print & digital\", price: 9, per: \"a month\", features: [\"Everything in Digital\", \"Four printed issues, posted free worldwide\", \"Back-issue discount of 30%\"], featured: true },\n]\n\ntype PublicationIssuesProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<PublicationHrefs> }\n\n/** The Publication issues page: a shelf of covers by season, two subscription plans with monthly or yearly billing, and the newsletter. */\nfunction PublicationIssues({ hrefs, ...props }: PublicationIssuesProps) {\n  const [yearly, setYearly] = React.useState(true)\n  return (\n    <PublicationShell page=\"issues\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-6xl px-4 pt-12 sm:px-6\">\n        <p className=\"text-chart-1 text-xs font-bold tracking-[0.14em] uppercase\">The magazine</p>\n        <h1 className={cn(\"mt-2 text-[clamp(3rem,8vw,6rem)] leading-none\", pubSerifClass)}>Issues</h1>\n        <p className={cn(\"text-muted-foreground mt-4 max-w-xl text-xl text-pretty\", pubTextClass)}>Four times a year, one theme, 160 pages. Printed on uncoated paper and meant to be kept.</p>\n\n        <section aria-label=\"Back issues\" className=\"mt-12\">\n          <ul className=\"grid grid-cols-2 gap-x-5 gap-y-10 sm:grid-cols-3 lg:grid-cols-6\">\n            {issues.map((i) => (\n              <li key={i.n}>\n                <a href=\"#\" className=\"group focus-visible:ring-ring/50 block rounded outline-none focus-visible:ring-[3px]\">\n                  <div className=\"relative shadow-[6px_6px_0_0_var(--border)] transition-transform duration-300 group-hover:-translate-y-1 motion-reduce:transition-none\">\n                    <MagArt variant={i.art} image={i.image} imageAlt={i.imageAlt} className=\"aspect-[3/4]\" />\n                    <div className=\"bg-card/95 absolute inset-x-0 bottom-0 border-t p-3\"><p className={cn(\"text-xl leading-none\", pubSerifClass)}>{i.theme}</p><p className=\"text-muted-foreground mt-1 text-[11px] tracking-wide uppercase\">No. {i.n}</p></div>\n                  </div>\n                  <p className=\"text-muted-foreground mt-3 text-sm\">{i.season}</p>\n                </a>\n              </li>\n            ))}\n          </ul>\n        </section>\n\n        <section aria-labelledby=\"pub-sub\" className=\"mt-24\">\n          <div className=\"text-center\">\n            <h2 id=\"pub-sub\" className={cn(\"text-4xl sm:text-5xl\", pubSerifClass)}>Read without the noise</h2>\n            <div role=\"group\" aria-label=\"Billing\" className=\"mt-6 inline-flex rounded-full border p-1\">\n              {([[true, \"Yearly · 2 months free\"], [false, \"Monthly\"]] as const).map(([v, l]) => <button key={l} type=\"button\" aria-pressed={yearly === v} onClick={() => setYearly(v)} className={cn(\"focus-visible:ring-ring/50 h-10 rounded-full px-5 text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px]\", yearly === v ? \"bg-foreground text-background\" : \"text-muted-foreground\")}>{l}</button>)}\n            </div>\n          </div>\n          <div className=\"mx-auto mt-10 grid max-w-3xl gap-5 md:grid-cols-2\">\n            {plans.map((p) => {\n              const price = yearly ? Math.round(p.price * 10) : p.price\n              return (\n                <article key={p.id} className={cn(\"flex flex-col border-2 p-8\", \"featured\" in p && p.featured ? \"border-chart-1 bg-card\" : \"border-foreground\")}>\n                  <h3 className={cn(\"text-3xl\", pubSerifClass)}>{p.name}</h3>\n                  <p className=\"mt-4 flex items-baseline gap-2\"><span className={cn(\"text-6xl tabular-nums\", pubSerifClass)}>${price}</span><span className=\"text-muted-foreground text-sm\">{yearly ? \"a year\" : p.per}</span></p>\n                  <ul className=\"mt-6 flex-1 space-y-3\">{p.features.map((f) => <li key={f} className={cn(\"flex items-start gap-3 text-pretty\", pubTextClass)}><Check className=\"text-chart-4 mt-1.5 size-4 shrink-0\" aria-hidden=\"true\" />{f}</li>)}</ul>\n                  <a href=\"#\" className={cn(\"focus-visible:ring-ring/50 mt-8 inline-flex h-12 items-center justify-center rounded-sm text-sm font-bold tracking-wide uppercase outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]\", \"featured\" in p && p.featured ? \"bg-chart-1 text-primary-foreground\" : \"bg-foreground text-background\")}>Subscribe</a>\n                </article>\n              )\n            })}\n          </div>\n        </section>\n        <div className=\"mt-20\"><Newsletter /></div>\n      </main>\n    </PublicationShell>\n  )\n}\n\nexport { PublicationIssues, type PublicationIssuesProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/publication/publication-issues.tsx"
    },
    {
      "path": "registry/ballmac/app/publication/page.tsx",
      "content": "// Ballmac UI: Publication template route. https://ui.ballmac.com/templates/template-publication\nimport type { Metadata } from \"next\"\n\nimport { PublicationHome } from \"@/components/ballmac/templates/publication/publication-home\"\n\nexport const metadata: Metadata = {\n  title: \"Marginalia: a magazine of ideas, written slowly\",\n  description: \"Essays, culture, science and technology, written slowly.\",\n}\n\nexport default function Page() {\n  return <PublicationHome />\n}\n",
      "type": "registry:page",
      "target": "app/publication/page.tsx"
    },
    {
      "path": "registry/ballmac/app/publication/essays/[slug]/page.tsx",
      "content": "// Ballmac UI: Publication template route. https://ui.ballmac.com/templates/template-publication\nimport type { Metadata } from \"next\"\n\nimport { PublicationArticle } from \"@/components/ballmac/templates/publication/publication-article\"\n\nexport const metadata: Metadata = {\n  title: \"The case for the unfinished city · Marginalia\",\n  description: \"What a scaffold-covered street teaches us about patience.\",\n}\n\n// One layout for every article. Load the story by params.slug from your data when you add more.\nexport default function Page() {\n  return <PublicationArticle />\n}\n",
      "type": "registry:page",
      "target": "app/publication/essays/[slug]/page.tsx"
    },
    {
      "path": "registry/ballmac/app/publication/essays/page.tsx",
      "content": "// Ballmac UI: Publication template route. https://ui.ballmac.com/templates/template-publication\nimport type { Metadata } from \"next\"\n\nimport { PublicationSection } from \"@/components/ballmac/templates/publication/publication-section\"\n\nexport const metadata: Metadata = {\n  title: \"Essays · Marginalia\",\n  description: \"Long arguments, patiently made.\",\n}\n\nexport default function Page() {\n  return <PublicationSection />\n}\n",
      "type": "registry:page",
      "target": "app/publication/essays/page.tsx"
    },
    {
      "path": "registry/ballmac/app/publication/authors/[id]/page.tsx",
      "content": "// Ballmac UI: Publication template route. https://ui.ballmac.com/templates/template-publication\nimport type { Metadata } from \"next\"\n\nimport { PublicationAuthor } from \"@/components/ballmac/templates/publication/publication-author\"\n\nexport const metadata: Metadata = {\n  title: \"Authors · Marginalia\",\n  description: \"Writers at Marginalia.\",\n}\n\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  return <PublicationAuthor authorId={id} />\n}\n",
      "type": "registry:page",
      "target": "app/publication/authors/[id]/page.tsx"
    },
    {
      "path": "registry/ballmac/app/publication/issues/page.tsx",
      "content": "// Ballmac UI: Publication template route. https://ui.ballmac.com/templates/template-publication\nimport type { Metadata } from \"next\"\n\nimport { PublicationIssues } from \"@/components/ballmac/templates/publication/publication-issues\"\n\nexport const metadata: Metadata = {\n  title: \"Issues · Marginalia\",\n  description: \"Four times a year, one theme, 160 pages.\",\n}\n\nexport default function Page() {\n  return <PublicationIssues />\n}\n",
      "type": "registry:page",
      "target": "app/publication/issues/page.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "template",
      "magazine",
      "blog",
      "editorial",
      "publication",
      "article",
      "newsletter",
      "serif"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "Installs a five-page magazine. Change stories and authors in publication-data.ts, the palette in publicationCss, and the duotone MagArt illustrations (or swap them for photographs).",
      "whenToUse": [
        "Magazines, long-form blogs and newsletters with an archive",
        "Reading-first sites with margin notes and a strong masthead"
      ],
      "whenNotToUse": [
        "A product blog inside a SaaS marketing site (use blog-1 and blog-post-1)"
      ],
      "composesWith": [
        "blog-1",
        "blog-post-1",
        "newsletter-1"
      ],
      "a11y": [
        {
          "keys": "Tab to a note number",
          "action": "Moves to the note; on wide screens it sits in the margin beside the paragraph"
        },
        {
          "keys": "Enter / Space on a section",
          "action": "Switches the list and announces how many stories are shown"
        }
      ],
      "customization": [
        "Replace MagArt with photographs",
        "Edit publicationCss for the light and dark palettes",
        "Change the masthead name in publication-theme.tsx"
      ]
    },
    "examples": [
      "template-publication-demo",
      "template-publication-article",
      "template-publication-section",
      "template-publication-author",
      "template-publication-issues"
    ],
    "url": "https://ui.ballmac.com/components/template-publication"
  },
  "docs": "Pages are at /publication, /publication/essays/[slug], /publication/essays, /publication/authors/[id] and /publication/issues. Edit stories, authors and issues in publication-data.ts; the palette is the publicationCss string in publication-theme.tsx.",
  "categories": [
    "templates"
  ],
  "type": "registry:block"
}