{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "template-summit",
  "title": "Northlight Summit: conference",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A five-page conference site under a sunrise ridgeline: a hero with a live countdown, a schedule you can star into an agenda with clash warnings, speakers with bios, tickets with a live total and promo code, and a venue guide.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/dialog.json",
    "https://ui.ballmac.com/r/accordion.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/templates/summit/summit-fonts.ts",
      "content": "// Ballmac UI: Summit template fonts. https://ui.ballmac.com/templates/template-summit\nimport { Figtree, Unbounded } from \"next/font/google\"\n\nexport const summitDisplay = Unbounded({ variable: \"--summit-display\", subsets: [\"latin\"], display: \"swap\", preload: false })\nexport const summitSans = Figtree({ variable: \"--summit-sans\", subsets: [\"latin\"], display: \"swap\", preload: false })\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/summit/summit-fonts.ts"
    },
    {
      "path": "registry/ballmac/components/templates/summit/summit-data.ts",
      "content": "// Ballmac UI: Summit template data. https://ui.ballmac.com/templates/template-summit\nexport type Track = \"Main stage\" | \"Craft\" | \"Systems\"\nexport const tracks: Track[] = [\"Main stage\", \"Craft\", \"Systems\"]\n\nexport type Speaker = { id: string; name: string; role: string; company: string; bio: string; tone: number; track: Track; talk: string }\n\nexport const speakers: Speaker[] = [\n  { id: \"ingrid\", name: \"Ingrid Solheim\", role: \"Design director\", company: \"Fjord & Co\", bio: \"Ingrid leads a forty-person studio that designs ferries, hospitals and the signs between them. She believes the best interface is a good sign.\", tone: 0, track: \"Main stage\", talk: \"Wayfinding for the weary\" },\n  { id: \"kofi\", name: \"Kofi Mensah\", role: \"Principal engineer\", company: \"Lattice\", bio: \"Kofi has run the on-call rota for a payments network for eight years and has opinions about alert fatigue that he is happy to share.\", tone: 1, track: \"Systems\", talk: \"Pagers are a design problem\" },\n  { id: \"mira\", name: \"Mira Castellanos\", role: \"Type designer\", company: \"Letterform Foundry\", bio: \"Mira draws typefaces for screens that nobody has built yet. Her latest family ships with a hundred and forty weights.\", tone: 2, track: \"Craft\", talk: \"The weight between regular and bold\" },\n  { id: \"jun\", name: \"Jun Watanabe\", role: \"Founder\", company: \"Tidepool\", bio: \"Jun built three companies in three countries and is writing a book about the quiet years between them.\", tone: 3, track: \"Main stage\", talk: \"The quiet years\" },\n  { id: \"amara\", name: \"Amara Nwosu\", role: \"Research lead\", company: \"Open Lab\", bio: \"Amara studies how people actually use software when nobody is watching, and what they build around it.\", tone: 4, track: \"Craft\", talk: \"What users do with your product on Sunday\" },\n  { id: \"leo\", name: \"Leo Bergstrom\", role: \"Staff engineer\", company: \"Northwind\", bio: \"Leo ripped out a two-million-line monolith one route at a time and kept a diary of every week.\", tone: 0, track: \"Systems\", talk: \"Deleting the monolith, a diary\" },\n  { id: \"sana\", name: \"Sana Iqbal\", role: \"Head of product\", company: \"Parcel\", bio: \"Sana ships features that remove other features. Her team once shrank the settings page by sixty percent.\", tone: 2, track: \"Main stage\", talk: \"Subtraction as a roadmap\" },\n  { id: \"tomas\", name: \"Tomáš Havel\", role: \"Motion designer\", company: \"Frame Rate\", bio: \"Tomáš animates interfaces that feel like physical objects, and has strong views on easing curves.\", tone: 1, track: \"Craft\", talk: \"Easing is a feeling\" },\n  { id: \"freya\", name: \"Freya Lindqvist\", role: \"Security architect\", company: \"Vaultline\", bio: \"Freya designs the unglamorous parts: key rotation, audit trails and the recovery flow nobody wants to test.\", tone: 3, track: \"Systems\", talk: \"Design for the worst day\" },\n  { id: \"dev\", name: \"Dev Raman\", role: \"Creative technologist\", company: \"Studio Nine\", bio: \"Dev builds installations out of sensors, shaders and a worrying amount of tape.\", tone: 4, track: \"Craft\", talk: \"Making light listen\" },\n  { id: \"elena\", name: \"Elena Rossi\", role: \"Editor in chief\", company: \"Margin Magazine\", bio: \"Elena edits a magazine about slowness, which is published, appropriately, quarterly.\", tone: 2, track: \"Main stage\", talk: \"In praise of the long read\" },\n  { id: \"noah\", name: \"Noah Fischer\", role: \"Platform lead\", company: \"Relay\", bio: \"Noah makes developer platforms people actually enjoy, mostly by deleting steps from the docs.\", tone: 0, track: \"Systems\", talk: \"The ten-minute quickstart\" },\n]\n\nexport type Session = { id: string; day: 1 | 2; start: string; end: string; title: string; track: Track; speaker: string; room: string; level: \"All levels\" | \"Intermediate\" | \"Advanced\"; blurb: string }\n\nexport const sessions: Session[] = [\n  { id: \"s1\", day: 1, start: \"09:30\", end: \"10:15\", title: \"Wayfinding for the weary\", track: \"Main stage\", speaker: \"ingrid\", room: \"Aurora Hall\", level: \"All levels\", blurb: \"What a hospital corridor teaches us about onboarding, and why the best signs say less.\" },\n  { id: \"s2\", day: 1, start: \"10:45\", end: \"11:30\", title: \"Pagers are a design problem\", track: \"Systems\", speaker: \"kofi\", room: \"Basalt Room\", level: \"Intermediate\", blurb: \"Eight years of on-call, distilled into six rules for alerts a human can act on at 3 a.m.\" },\n  { id: \"s3\", day: 1, start: \"10:45\", end: \"11:30\", title: \"The weight between regular and bold\", track: \"Craft\", speaker: \"mira\", room: \"Studio One\", level: \"Advanced\", blurb: \"A walk through variable fonts, optical sizes and the weights nobody ships but everybody feels.\" },\n  { id: \"s4\", day: 1, start: \"13:00\", end: \"13:45\", title: \"The quiet years\", track: \"Main stage\", speaker: \"jun\", room: \"Aurora Hall\", level: \"All levels\", blurb: \"Companies are not built in the launch week. A founder on the long, unglamorous middle.\" },\n  { id: \"s5\", day: 1, start: \"14:15\", end: \"15:00\", title: \"What users do with your product on Sunday\", track: \"Craft\", speaker: \"amara\", room: \"Studio One\", level: \"Intermediate\", blurb: \"Field research from forty living rooms, and the workarounds that became features.\" },\n  { id: \"s6\", day: 1, start: \"14:15\", end: \"15:00\", title: \"Deleting the monolith, a diary\", track: \"Systems\", speaker: \"leo\", room: \"Basalt Room\", level: \"Advanced\", blurb: \"Two million lines, one route at a time. What worked, what burned and what we would not do again.\" },\n  { id: \"s7\", day: 1, start: \"16:00\", end: \"16:45\", title: \"Subtraction as a roadmap\", track: \"Main stage\", speaker: \"sana\", room: \"Aurora Hall\", level: \"All levels\", blurb: \"How removing settings, steps and screens made a product feel twice as fast.\" },\n  { id: \"s8\", day: 2, start: \"09:30\", end: \"10:15\", title: \"In praise of the long read\", track: \"Main stage\", speaker: \"elena\", room: \"Aurora Hall\", level: \"All levels\", blurb: \"Why a slower publication found a faster audience.\" },\n  { id: \"s9\", day: 2, start: \"10:45\", end: \"11:30\", title: \"Easing is a feeling\", track: \"Craft\", speaker: \"tomas\", room: \"Studio One\", level: \"Intermediate\", blurb: \"Curves, springs and the physics of things that should feel heavy or light.\" },\n  { id: \"s10\", day: 2, start: \"10:45\", end: \"11:30\", title: \"Design for the worst day\", track: \"Systems\", speaker: \"freya\", room: \"Basalt Room\", level: \"Advanced\", blurb: \"Recovery flows, audit trails and the screens people only see when something has gone wrong.\" },\n  { id: \"s11\", day: 2, start: \"13:00\", end: \"13:45\", title: \"Making light listen\", track: \"Craft\", speaker: \"dev\", room: \"Studio One\", level: \"Advanced\", blurb: \"A live build of an installation that responds to a room full of people.\" },\n  { id: \"s12\", day: 2, start: \"13:00\", end: \"13:45\", title: \"The ten-minute quickstart\", track: \"Systems\", speaker: \"noah\", room: \"Basalt Room\", level: \"Intermediate\", blurb: \"A teardown of five onboarding docs and the step each one should lose.\" },\n  { id: \"s13\", day: 2, start: \"15:00\", end: \"16:00\", title: \"Closing conversation: what we will keep\", track: \"Main stage\", speaker: \"ingrid\", room: \"Aurora Hall\", level: \"All levels\", blurb: \"Four speakers, one table and the questions you send in during the two days.\" },\n]\n\nexport type Tier = { id: string; name: string; price: number; blurb: string; perks: string[]; badge?: string }\nexport const tiers: Tier[] = [\n  { id: \"student\", name: \"Student\", price: 190, blurb: \"With a valid student ID at the door.\", perks: [\"Both days, all tracks\", \"Lunch and coffee\", \"Recordings next week\"] },\n  { id: \"early\", name: \"Early bird\", price: 490, blurb: \"Until 15 January, or 200 tickets.\", perks: [\"Both days, all tracks\", \"Lunch, coffee and the Thursday dinner\", \"Recordings next week\", \"Speaker Q&A lounge\"], badge: \"Best value\" },\n  { id: \"standard\", name: \"Standard\", price: 690, blurb: \"The full two days.\", perks: [\"Both days, all tracks\", \"Lunch, coffee and the Thursday dinner\", \"Recordings next week\", \"Speaker Q&A lounge\"] },\n  { id: \"patron\", name: \"Patron\", price: 1290, blurb: \"Support the event and sit near the front.\", perks: [\"Everything in Standard\", \"Reserved seats in Aurora Hall\", \"Patrons’ breakfast with the speakers\", \"Your name on the wall\"] },\n]\n\nexport const faqs = [\n  { q: \"Will talks be recorded?\", a: \"Yes. Every main stage and track talk is recorded and sent to ticket holders within a week, with captions.\" },\n  { q: \"Is the venue accessible?\", a: \"All three rooms are step-free, with hearing loops and a quiet room on every floor. Tell us what you need when you buy and we will arrange it.\" },\n  { q: \"Can I get a refund?\", a: \"Full refund until 30 days before the event, then a transfer to another person for free. No questions.\" },\n  { q: \"Do you offer childcare?\", a: \"There is a free, staffed room for children aged three to ten on both days. Add it to your order.\" },\n  { q: \"What is the code of conduct?\", a: \"Be kind, ask before you photograph people and leave a seat for someone standing. Violations are handled by a named, trained team.\" },\n  { q: \"Where should I stay?\", a: \"We hold rooms at three hotels within ten minutes’ walk. Prices are on the venue page and the code is in your ticket email.\" },\n]\n\nexport const sponsors = [\"Northwind\", \"Relay\", \"Parcel\", \"Lattice\", \"Tidepool\", \"Vaultline\", \"Studio Nine\", \"Margin\"]\nexport const EVENT_DATE = \"2027-05-14T09:00:00Z\"\nexport const speakerById = (id: string) => speakers.find((s) => s.id === id) ?? speakers[0]!\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/summit/summit-data.ts"
    },
    {
      "path": "registry/ballmac/components/templates/summit/summit-theme.tsx",
      "content": "// Ballmac UI: Summit template shell. https://ui.ballmac.com/templates/template-summit\n\"use client\"\n\nimport * as React from \"react\"\nimport { Menu, X } from \"lucide-react\"\n\nimport { EVENT_DATE, type Speaker } from \"@/components/ballmac/templates/summit/summit-data\"\nimport { summitDisplay, summitSans } from \"@/components/ballmac/templates/summit/summit-fonts\"\nimport { cn } from \"@/lib/utils\"\n\ntype SummitPage = \"home\" | \"schedule\" | \"speakers\" | \"tickets\" | \"venue\"\ntype SummitHrefs = Record<SummitPage, string>\n\nconst defaultHrefs: SummitHrefs = { home: \"/summit\", schedule: \"/summit/schedule\", speakers: \"/summit/speakers\", tickets: \"/summit/tickets\", venue: \"/summit/venue\" }\n\n/** Northlight's palette: a cream morning page with indigo ink; dark mode is the same mountain at night. The hero sky is the same in both. */\nconst summitCss = `\n.summit-theme,body:not(:has([data-gallery])):has(.summit-theme){--background:oklch(0.975 0.015 80);--foreground:oklch(0.22 0.08 275);--card:oklch(0.99 0.01 80);--card-foreground:oklch(0.22 0.08 275);--popover:oklch(0.99 0.01 80);--popover-foreground:oklch(0.22 0.08 275);--primary:oklch(0.3 0.12 275);--primary-foreground:oklch(0.975 0.015 80);--secondary:oklch(0.94 0.025 80);--secondary-foreground:oklch(0.22 0.08 275);--muted:oklch(0.94 0.025 80);--muted-foreground:oklch(0.44 0.07 275);--accent:oklch(0.92 0.04 75);--accent-foreground:oklch(0.22 0.08 275);--border:oklch(0.22 0.08 275 / 14%);--input:oklch(0.22 0.08 275 / 22%);--ring:oklch(0.5 0.2 285);--surface:oklch(0.955 0.02 80);--destructive:oklch(0.52 0.21 27);--chart-1:oklch(0.7 0.18 38);--chart-2:oklch(0.84 0.15 85);--chart-3:oklch(0.62 0.11 190);--chart-4:oklch(0.52 0.2 295);--chart-5:oklch(0.7 0.16 350);--summit-on-sun:oklch(0.2 0.08 275);--summit-hero-fg:oklch(0.98 0.012 80);--summit-sky-1:oklch(0.2 0.09 280);--summit-sky-2:oklch(0.36 0.15 300);--summit-sky-3:oklch(0.62 0.19 20);--summit-sky-4:oklch(0.82 0.15 75);--summit-ridge-1:oklch(0.3 0.1 290);--summit-ridge-2:oklch(0.23 0.09 282);--summit-ridge-3:oklch(0.17 0.07 278);--radius:1.25rem}\n.dark .summit-theme,.dark body:not(:has([data-gallery])):has(.summit-theme){--background:oklch(0.17 0.06 277);--foreground:oklch(0.96 0.015 80);--card:oklch(0.21 0.07 277);--card-foreground:oklch(0.96 0.015 80);--popover:oklch(0.23 0.07 277);--popover-foreground:oklch(0.96 0.015 80);--primary:oklch(0.85 0.14 85);--primary-foreground:oklch(0.2 0.07 277);--secondary:oklch(0.26 0.07 277);--secondary-foreground:oklch(0.96 0.015 80);--muted:oklch(0.25 0.07 277);--muted-foreground:oklch(0.75 0.05 280);--accent:oklch(0.3 0.08 280);--accent-foreground:oklch(0.96 0.015 80);--border:oklch(1 0 0 / 11%);--input:oklch(1 0 0 / 16%);--ring:oklch(0.85 0.14 85);--surface:oklch(0.195 0.065 277);--destructive:oklch(0.7 0.19 27);--chart-1:oklch(0.75 0.17 40);--chart-2:oklch(0.86 0.14 88);--chart-3:oklch(0.76 0.11 190);--chart-4:oklch(0.72 0.17 300);--chart-5:oklch(0.78 0.14 350)}\nbody:not(:has([data-gallery])):has(.summit-theme){font-family:var(--summit-sans),ui-sans-serif,system-ui,sans-serif}\n@keyframes summit-float{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}\n.summit-float{animation:summit-float 6s ease-in-out infinite}\n@media (prefers-reduced-motion:reduce){.summit-float{animation:none}}\n`\n\nconst display = \"[font-family:var(--summit-display),ui-sans-serif,system-ui,sans-serif] font-semibold tracking-[-0.03em]\"\nconst onSun = \"text-[var(--summit-on-sun)]\"\nconst tones = [\"bg-chart-1\", \"bg-chart-2\", \"bg-chart-3\", \"bg-chart-4\", \"bg-chart-5\"]\n\n/** A portrait without a photo: a colour field, shoulders and a head. Replace it with an image when you have one. */\nfunction Portrait({ speaker, className }: { speaker: Pick<Speaker, \"tone\" | \"name\">; className?: string }) {\n  return (\n    <div aria-hidden=\"true\" className={cn(\"relative isolate aspect-[4/5] w-full overflow-hidden\", tones[speaker.tone % tones.length], className)}>\n      <div className=\"bg-[var(--summit-ridge-3)] absolute bottom-[-30%] left-1/2 aspect-square w-[88%] -translate-x-1/2 rounded-full\" />\n      <div className=\"bg-[var(--summit-hero-fg)] absolute top-[22%] left-1/2 aspect-square w-[34%] -translate-x-1/2 rounded-full\" />\n    </div>\n  )\n}\n\n/** The mountain scene: a night-to-sunrise sky, a sun and three ridgelines. Decorative. */\nfunction Ridge({ className }: { className?: string }) {\n  return (\n    <div aria-hidden=\"true\" className={cn(\"pointer-events-none absolute inset-0 -z-10 overflow-hidden\", className)} style={{ backgroundImage: \"linear-gradient(to bottom, var(--summit-sky-1) 0%, var(--summit-sky-2) 38%, var(--summit-sky-3) 74%, var(--summit-sky-4) 100%)\" }}>\n      <div className=\"bg-[var(--summit-sky-4)] summit-float absolute end-[12%] bottom-[24%] aspect-square w-[min(22vw,15rem)] rounded-full opacity-95 shadow-[0_0_120px_40px_var(--summit-sky-3)]\" />\n      <svg viewBox=\"0 0 1440 420\" preserveAspectRatio=\"none\" className=\"absolute inset-x-0 bottom-0 h-[46%] w-full\">\n        <path className=\"fill-[var(--summit-ridge-1)]\" d=\"M0 250 L120 190 L210 230 L340 130 L470 210 L560 170 L700 250 L820 150 L930 220 L1060 120 L1190 210 L1300 160 L1440 230 L1440 420 L0 420 Z\" />\n        <path className=\"fill-[var(--summit-ridge-2)]\" d=\"M0 310 L90 270 L200 320 L330 240 L450 300 L590 230 L720 310 L860 250 L980 320 L1100 260 L1240 320 L1340 280 L1440 320 L1440 420 L0 420 Z\" />\n        <path className=\"fill-[var(--summit-ridge-3)]\" d=\"M0 370 L140 340 L260 375 L400 330 L540 372 L690 335 L820 378 L960 340 L1100 376 L1240 345 L1440 380 L1440 420 L0 420 Z\" />\n      </svg>\n    </div>\n  )\n}\n\n/** Days, hours, minutes and seconds to the opening keynote. Computed after mount so server and client markup match. */\nfunction useCountdown(target: string) {\n  const [left, setLeft] = React.useState<number | null>(null)\n  React.useEffect(() => {\n    const tick = () => setLeft(Math.max(0, Math.floor((new Date(target).getTime() - Date.now()) / 1000)))\n    tick()\n    const id = window.setInterval(tick, 1000)\n    return () => window.clearInterval(id)\n  }, [target])\n  if (left === null) return null\n  return { days: Math.floor(left / 86400), hours: Math.floor((left % 86400) / 3600), minutes: Math.floor((left % 3600) / 60), seconds: left % 60 }\n}\n\nconst links: { key: SummitPage; label: string }[] = [\n  { key: \"schedule\", label: \"Schedule\" },\n  { key: \"speakers\", label: \"Speakers\" },\n  { key: \"venue\", label: \"Venue\" },\n]\n\ntype SummitShellProps = React.ComponentProps<\"div\"> & {\n  /** The page being shown, so its nav link is marked current. */\n  page: SummitPage\n  /** Override where pages live (used by previews). */\n  hrefs?: Partial<SummitHrefs>\n}\n\n/** Northlight's frame: a transparent header that settles on scroll, a ticket button that is always in reach and a mountain footer. */\nfunction SummitShell({ page, hrefs: overrides, className, style, children, ...props }: SummitShellProps) {\n  const hrefs = { ...defaultHrefs, ...overrides }\n  const [open, setOpen] = React.useState(false)\n  React.useEffect(() => {\n    const classes = [summitDisplay.variable, summitSans.variable].filter(Boolean)\n    document.body.classList.add(...classes)\n    return () => document.body.classList.remove(...classes)\n  }, [])\n  return (\n    <div\n      data-slot=\"summit\"\n      className={cn(\"summit-theme bg-background text-foreground relative min-h-dvh overflow-x-clip\", summitDisplay.variable, summitSans.variable, className)}\n      style={{ fontFamily: \"var(--summit-sans), ui-sans-serif, system-ui, sans-serif\", ...style }}\n      {...props}\n    >\n      <style>{summitCss}</style>\n      <header className=\"bg-background/85 sticky top-0 z-40 border-b backdrop-blur-xl\">\n        <div className=\"mx-auto flex h-16 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6\">\n          <a href={hrefs.home} className=\"focus-visible:ring-ring/50 flex items-center gap-2.5 rounded-lg outline-none focus-visible:ring-[3px]\">\n            <span aria-hidden=\"true\" className=\"bg-chart-1 relative size-8 overflow-hidden rounded-full\"><span className=\"bg-[var(--summit-ridge-2)] absolute -bottom-3 left-1/2 aspect-square w-[130%] -translate-x-1/2 rounded-[40%]\" /></span>\n            <span className={cn(\"text-base\", display)}>Northlight <span className=\"text-muted-foreground\">’27</span></span>\n          </a>\n          <nav aria-label=\"Main\" className=\"hidden items-center gap-1 md:flex\">\n            {links.map((l) => <a key={l.key} href={hrefs[l.key]} aria-current={page === l.key ? \"page\" : undefined} className=\"hover:bg-accent aria-[current=page]:bg-accent focus-visible:ring-ring/50 rounded-full px-4 py-2 text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px] motion-reduce:transition-none\">{l.label}</a>)}\n          </nav>\n          <div className=\"flex items-center gap-2\">\n            <a href={hrefs.tickets} aria-current={page === \"tickets\" ? \"page\" : undefined} className={cn(\"bg-chart-1 focus-visible:ring-ring/50 inline-flex h-10 items-center rounded-full px-5 text-sm font-bold outline-none transition-transform hover:-translate-y-0.5 focus-visible:ring-[3px] motion-reduce:transition-none motion-reduce:hover:translate-y-0\", onSun)}>Get tickets</a>\n            <button type=\"button\" aria-label={open ? \"Close menu\" : \"Open menu\"} aria-expanded={open} aria-controls=\"summit-mobile-menu\" onClick={() => setOpen((v) => !v)} className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-10 items-center justify-center rounded-full 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          </div>\n        </div>\n        {open && <nav id=\"summit-mobile-menu\" aria-label=\"Mobile\" className=\"px-4 pb-4 md:hidden\">{links.map((l) => <a key={l.key} href={hrefs[l.key]} className=\"hover:bg-accent block rounded-2xl px-4 py-3 text-lg font-semibold\">{l.label}</a>)}</nav>}\n      </header>\n      {children}\n      <footer className=\"relative isolate mt-24 overflow-hidden text-[var(--summit-hero-fg)]\">\n        <Ridge />\n        <div aria-hidden=\"true\" className=\"bg-[var(--summit-ridge-3)]/75 absolute inset-0 -z-10\" />\n        <div className=\"mx-auto grid max-w-7xl gap-10 px-4 pt-24 pb-14 sm:px-6 md:grid-cols-[1.5fr_1fr_1fr]\">\n          <div><p className={cn(\"text-[clamp(2rem,5vw,3.5rem)] leading-none\", display)}>See you in the mountains.</p><p className=\"mt-4 max-w-sm text-pretty\">14 and 15 May 2027 · Harpa, Reykjavik. Two days of talks about design, engineering and the long view.</p></div>\n          <div><h2 className=\"text-xs font-bold tracking-[0.14em] uppercase\">The event</h2><ul className=\"mt-4 grid gap-2.5\">{[[\"Schedule\", hrefs.schedule], [\"Speakers\", hrefs.speakers], [\"Venue and travel\", hrefs.venue], [\"Tickets\", hrefs.tickets]].map(([l, h]) => <li key={l}><a href={h} className=\"hover:underline\">{l}</a></li>)}</ul></div>\n          <div><h2 className=\"text-xs font-bold tracking-[0.14em] uppercase\">Contact</h2><ul className=\"mt-4 grid gap-2.5\"><li>hello@northlight.example</li><li>Press and partnerships</li><li>Code of conduct</li></ul></div>\n        </div>\n        <p className=\"mx-auto max-w-7xl px-4 pb-6 text-xs sm:px-6\">© 2027 Northlight Summit. Printed on a very small number of trees.</p>\n      </footer>\n    </div>\n  )\n}\n\nexport { Portrait, Ridge, SummitShell, defaultHrefs as summitDefaultHrefs, display as summitDisplayClass, onSun as summitOnSun, tones as summitTones, useCountdown, type SummitHrefs, type SummitPage, type SummitShellProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/summit/summit-theme.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/summit/summit-home.tsx",
      "content": "// Ballmac UI: Summit home page. https://ui.ballmac.com/templates/template-summit\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowRight, CalendarDays, MapPin } from \"lucide-react\"\n\nimport { EVENT_DATE, sessions, speakerById, speakers, sponsors, tiers } from \"@/components/ballmac/templates/summit/summit-data\"\nimport { Portrait, Ridge, SummitShell, summitDisplayClass, summitOnSun, useCountdown, type SummitHrefs } from \"@/components/ballmac/templates/summit/summit-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst stats = [[\"2\", \"days\"], [\"24\", \"talks\"], [\"3\", \"stages\"], [\"900\", \"people\"]]\nconst reasons = [\n  { n: \"01\", title: \"Talks with a point of view\", body: \"Every speaker is asked one question before they are booked: what do you believe that most of your industry does not?\" },\n  { n: \"02\", title: \"Rooms that are not crowded\", body: \"Nine hundred seats in a hall built for three thousand. You will always find a place to sit and someone to talk to.\" },\n  { n: \"03\", title: \"Time to talk, on purpose\", body: \"Ninety minutes for lunch, a long Thursday dinner and a lounge where speakers sit down and answer the second question.\" },\n]\n\nfunction Tile({ value, unit }: { value: string; unit: string }) {\n  return (\n    <div className=\"bg-[var(--summit-ridge-3)]/55 min-w-[4.5rem] rounded-2xl border border-white/20 px-3 py-3 text-center backdrop-blur-sm sm:min-w-24 sm:px-5 sm:py-4\">\n      <div className={cn(\"text-3xl tabular-nums sm:text-5xl\", summitDisplayClass)}>{value}</div>\n      <div className=\"mt-1 text-xs font-semibold tracking-widest uppercase\">{unit}</div>\n    </div>\n  )\n}\n\ntype SummitHomeProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<SummitHrefs> }\n\n/** The Summit home page: a sunrise hero with a live countdown, numbers, speakers, a schedule teaser and tickets. */\nfunction SummitHome({ hrefs, ...props }: SummitHomeProps) {\n  const link = { schedule: \"/summit/schedule\", speakers: \"/summit/speakers\", tickets: \"/summit/tickets\", venue: \"/summit/venue\", ...hrefs }\n  const left = useCountdown(EVENT_DATE)\n  const pad = (n: number) => String(n).padStart(2, \"0\")\n  return (\n    <SummitShell page=\"home\" hrefs={hrefs} {...props}>\n      <main>\n        <section aria-labelledby=\"sh-title\" className=\"relative isolate overflow-hidden text-[var(--summit-hero-fg)]\">\n          <Ridge />\n          <div className=\"mx-auto max-w-7xl px-4 pt-16 pb-72 sm:px-6 sm:pt-24 sm:pb-80\">\n            <p className=\"inline-flex flex-wrap items-center gap-x-4 gap-y-1 rounded-full border border-white/25 px-4 py-1.5 text-sm font-semibold\"><span className=\"inline-flex items-center gap-1.5\"><CalendarDays className=\"size-4\" aria-hidden=\"true\" />14–15 May 2027</span><span className=\"inline-flex items-center gap-1.5\"><MapPin className=\"size-4\" aria-hidden=\"true\" />Harpa, Reykjavik</span></p>\n            <h1 id=\"sh-title\" className={cn(\"mt-8 max-w-4xl text-[clamp(3.2rem,11vw,9.5rem)] leading-[0.92]\", summitDisplayClass)}>Look further.</h1>\n            <p className=\"mt-6 max-w-xl text-xl text-pretty\">Two days of talks about design, engineering and the long view, held where the sun barely sets. Twelve speakers, nine hundred seats.</p>\n            <div className=\"mt-9 flex flex-wrap gap-3\">\n              <a href={link.tickets} className={cn(\"bg-chart-2 focus-visible:ring-ring inline-flex h-14 items-center gap-2 rounded-full px-8 text-lg font-bold outline-none transition-transform hover:-translate-y-0.5 focus-visible:ring-[3px] motion-reduce:transition-none motion-reduce:hover:translate-y-0\", summitOnSun)}>Get tickets from $190 <ArrowRight className=\"size-5 rtl:rotate-180\" aria-hidden=\"true\" /></a>\n              <a href={link.schedule} className=\"focus-visible:ring-ring inline-flex h-14 items-center rounded-full border-2 border-white/60 px-8 text-lg font-bold outline-none transition-colors hover:bg-white/10 focus-visible:ring-[3px] motion-reduce:transition-none\">See the schedule</a>\n            </div>\n            <div className=\"mt-12\" role=\"group\" aria-label=\"Time until the first talk\">\n              <p className=\"mb-3 text-sm font-semibold tracking-widest uppercase\">Doors open in</p>\n              <div className=\"flex gap-2 sm:gap-3\" aria-hidden=\"true\">\n                <Tile value={left ? String(left.days) : \"—\"} unit=\"days\" /><Tile value={left ? pad(left.hours) : \"—\"} unit=\"hours\" /><Tile value={left ? pad(left.minutes) : \"—\"} unit=\"min\" /><Tile value={left ? pad(left.seconds) : \"—\"} unit=\"sec\" />\n              </div>\n              <p className=\"sr-only\">{left ? `${left.days} days until the doors open on 14 May 2027.` : \"Doors open on 14 May 2027.\"}</p>\n            </div>\n          </div>\n        </section>\n\n        <section aria-label=\"Supporters\" className=\"border-b\">\n          <div className=\"mx-auto flex max-w-7xl flex-wrap items-center justify-center gap-x-10 gap-y-3 px-4 py-6 sm:px-6\"><span className=\"text-muted-foreground text-xs font-bold tracking-[0.14em] uppercase\">Supported by</span>{sponsors.map((s) => <span key={s} className={cn(\"text-muted-foreground text-lg\", summitDisplayClass)}>{s}</span>)}</div>\n        </section>\n\n        <section aria-label=\"The event in numbers\" className=\"mx-auto max-w-7xl px-4 py-16 sm:px-6\">\n          <ul className=\"grid grid-cols-2 gap-4 md:grid-cols-4\">{stats.map(([n, l], i) => <li key={l} className={cn(\"rounded-3xl border p-6 sm:p-8\", [\"bg-chart-1/15\", \"bg-chart-2/25\", \"bg-chart-3/15\", \"bg-chart-4/15\"][i])}><span className={cn(\"block text-[clamp(2.8rem,7vw,5rem)] leading-none\", summitDisplayClass)}>{n}</span><span className=\"text-muted-foreground mt-2 block text-sm font-semibold tracking-widest uppercase\">{l}</span></li>)}</ul>\n        </section>\n\n        <section aria-labelledby=\"sh-speakers\" className=\"mx-auto max-w-7xl px-4 pb-16 sm:px-6\">\n          <div className=\"flex items-end justify-between gap-4\"><h2 id=\"sh-speakers\" className={cn(\"text-[clamp(2rem,5vw,3.5rem)] leading-none\", summitDisplayClass)}>On stage</h2><a href={link.speakers} className=\"text-primary text-sm font-bold underline-offset-4 hover:underline\">All 12 speakers</a></div>\n          <ul className=\"mt-8 grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6\">\n            {speakers.slice(0, 6).map((s) => (\n              <li key={s.id}><a href={link.speakers} className=\"focus-visible:ring-ring/50 group block rounded-3xl outline-none focus-visible:ring-[3px]\"><Portrait speaker={s} className=\"rounded-3xl transition-transform group-hover:-translate-y-1 motion-reduce:transition-none motion-reduce:group-hover:translate-y-0\" /><span className=\"mt-3 block font-bold\">{s.name}</span><span className=\"text-muted-foreground block text-sm\">{s.role}, {s.company}</span></a></li>\n            ))}\n          </ul>\n        </section>\n\n        <section aria-labelledby=\"sh-day\" className=\"bg-surface border-y\">\n          <div className=\"mx-auto grid max-w-7xl gap-10 px-4 py-16 sm:px-6 lg:grid-cols-[1fr_1.4fr]\">\n            <div><h2 id=\"sh-day\" className={cn(\"text-[clamp(2rem,5vw,3.5rem)] leading-none\", summitDisplayClass)}>Day one, at a glance</h2><p className=\"text-muted-foreground mt-4 max-w-sm text-lg text-pretty\">Three rooms, no overlap you will regret. Star the talks you want and we will build your day.</p><a href={link.schedule} className=\"bg-primary text-primary-foreground focus-visible:ring-ring/50 mt-6 inline-flex h-12 items-center gap-2 rounded-full px-6 font-bold outline-none focus-visible:ring-[3px]\">Build my agenda <ArrowRight className=\"size-4 rtl:rotate-180\" aria-hidden=\"true\" /></a></div>\n            <ol className=\"divide-y rounded-3xl border bg-card\">\n              {sessions.filter((s) => s.day === 1).slice(0, 5).map((s) => (\n                <li key={s.id} className=\"grid grid-cols-[4.5rem_minmax(0,1fr)] gap-4 p-4 sm:grid-cols-[5.5rem_minmax(0,1fr)_auto] sm:p-5\">\n                  <span className={cn(\"text-lg tabular-nums\", summitDisplayClass)}>{s.start}</span>\n                  <span className=\"min-w-0\"><span className=\"block font-bold text-pretty\">{s.title}</span><span className=\"text-muted-foreground block text-sm\">{speakerById(s.speaker).name} · {s.room}</span></span>\n                  <span className=\"bg-secondary hidden h-fit rounded-full px-3 py-1 text-xs font-bold sm:inline\">{s.track}</span>\n                </li>\n              ))}\n            </ol>\n          </div>\n        </section>\n\n        <section aria-labelledby=\"sh-why\" className=\"mx-auto max-w-7xl px-4 py-16 sm:px-6\">\n          <h2 id=\"sh-why\" className={cn(\"max-w-2xl text-[clamp(2rem,5vw,3.5rem)] leading-none text-balance\", summitDisplayClass)}>Why people come back</h2>\n          <ul className=\"mt-10 grid gap-4 md:grid-cols-3\">{reasons.map((r) => <li key={r.n} className=\"bg-card rounded-3xl border p-8\"><span className={cn(\"text-primary text-sm\", summitDisplayClass)}>{r.n}</span><h3 className=\"mt-6 text-2xl font-bold text-balance\">{r.title}</h3><p className=\"text-muted-foreground mt-3 text-pretty\">{r.body}</p></li>)}</ul>\n        </section>\n\n        <section aria-labelledby=\"sh-tix\" className=\"mx-auto max-w-7xl px-4 pb-8 sm:px-6\">\n          <div className={cn(\"bg-chart-1 relative overflow-hidden rounded-[2rem] p-8 sm:p-14\", summitOnSun)}>\n            <div aria-hidden=\"true\" className=\"bg-chart-2 absolute -top-24 -end-16 aspect-square w-80 rounded-full\" />\n            <div className=\"relative grid items-end gap-8 lg:grid-cols-[1.4fr_1fr]\">\n              <div><h2 id=\"sh-tix\" className={cn(\"text-[clamp(2rem,5vw,3.8rem)] leading-none text-balance\", summitDisplayClass)}>The early bird is $200 cheaper.</h2><p className=\"mt-4 max-w-lg text-lg text-pretty\">Early bird ends on 15 January or at 200 tickets, whichever comes first. Refundable until 30 days before.</p></div>\n              <div className=\"flex flex-wrap items-center gap-4 lg:justify-end\"><span className={cn(\"text-6xl\", summitDisplayClass)}>${tiers[1]!.price}</span><a href={link.tickets} className=\"bg-[var(--summit-ridge-3)] focus-visible:ring-ring inline-flex h-14 items-center gap-2 rounded-full px-8 text-lg font-bold text-[var(--summit-hero-fg)] outline-none focus-visible:ring-[3px]\">Get tickets <ArrowRight className=\"size-5 rtl:rotate-180\" aria-hidden=\"true\" /></a></div>\n            </div>\n          </div>\n        </section>\n      </main>\n    </SummitShell>\n  )\n}\n\nexport { SummitHome, type SummitHomeProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/summit/summit-home.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/summit/summit-schedule.tsx",
      "content": "// Ballmac UI: Summit schedule page. https://ui.ballmac.com/templates/template-summit\n\"use client\"\n\nimport * as React from \"react\"\nimport { Clock, MapPin, Star, TriangleAlert } from \"lucide-react\"\n\nimport { sessions, speakerById, tracks, type Session, type Track } from \"@/components/ballmac/templates/summit/summit-data\"\nimport { Portrait, SummitShell, summitDisplayClass, type SummitHrefs } from \"@/components/ballmac/templates/summit/summit-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst trackDot: Record<Track, string> = { \"Main stage\": \"bg-chart-1\", Craft: \"bg-chart-4\", Systems: \"bg-chart-3\" }\nconst days = [{ n: 1 as const, label: \"Day 1\", date: \"Friday 14 May\" }, { n: 2 as const, label: \"Day 2\", date: \"Saturday 15 May\" }]\nconst minutes = (t: string) => Number(t.slice(0, 2)) * 60 + Number(t.slice(3))\nconst overlaps = (a: Session, b: Session) => a.id !== b.id && a.day === b.day && minutes(a.start) < minutes(b.end) && minutes(b.start) < minutes(a.end)\n\ntype SummitScheduleProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<SummitHrefs>; defaultSaved?: string[] }\n\n/** The schedule: day switch, track filters, one-tap agenda saving with clash warnings and a live summary. */\nfunction SummitSchedule({ hrefs, defaultSaved = [], ...props }: SummitScheduleProps) {\n  const [day, setDay] = React.useState<1 | 2>(1)\n  const [active, setActive] = React.useState<Track[]>([...tracks])\n  const [saved, setSaved] = React.useState<string[]>(defaultSaved)\n  const toggleTrack = (t: Track) => setActive((a) => (a.includes(t) ? (a.length > 1 ? a.filter((x) => x !== t) : a) : [...a, t]))\n  const toggleSaved = (id: string) => setSaved((s) => (s.includes(id) ? s.filter((x) => x !== id) : [...s, id]))\n  const list = sessions.filter((s) => s.day === day && active.includes(s.track))\n  const slots = [...new Set(list.map((s) => s.start))].sort()\n  const mine = sessions.filter((s) => saved.includes(s.id)).sort((a, b) => a.day - b.day || minutes(a.start) - minutes(b.start))\n  const clashes = mine.filter((s) => mine.some((o) => overlaps(s, o)))\n  return (\n    <SummitShell page=\"schedule\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-7xl px-4 pt-12 pb-8 sm:px-6 sm:pt-16\">\n        <h1 className={cn(\"text-[clamp(2.8rem,9vw,7rem)] leading-[0.95]\", summitDisplayClass)}>Schedule</h1>\n        <p className=\"text-muted-foreground mt-4 max-w-xl text-xl text-pretty\">Three rooms run side by side. Star what you want to see and we will keep an eye out for clashes.</p>\n\n        <div className=\"mt-10 flex flex-wrap items-center justify-between gap-4 border-y py-4\">\n          <div role=\"group\" aria-label=\"Day\" className=\"flex gap-2\">\n            {days.map((d) => <button key={d.n} type=\"button\" aria-pressed={day === d.n} onClick={() => setDay(d.n)} className=\"hover:bg-accent focus-visible:ring-ring/50 aria-pressed:bg-primary aria-pressed:text-primary-foreground rounded-2xl border px-5 py-2 text-start outline-none focus-visible:ring-[3px]\"><span className=\"block text-sm font-bold\">{d.label}</span><span className=\"block text-xs\">{d.date}</span></button>)}\n          </div>\n          <div role=\"group\" aria-label=\"Tracks\" className=\"flex flex-wrap gap-2\">\n            {tracks.map((t) => <button key={t} type=\"button\" aria-pressed={active.includes(t)} onClick={() => toggleTrack(t)} className=\"hover:bg-accent focus-visible:ring-ring/50 aria-pressed:bg-secondary inline-flex h-10 items-center gap-2 rounded-full border px-4 text-sm font-semibold outline-none focus-visible:ring-[3px]\"><span className={cn(\"size-2.5 rounded-full\", trackDot[t])} aria-hidden=\"true\" />{t}</button>)}\n          </div>\n        </div>\n\n        <p role=\"status\" className=\"text-muted-foreground mt-4 text-sm\">{list.length} {list.length === 1 ? \"talk\" : \"talks\"} on {days[day - 1]!.date}. {saved.length > 0 ? `${saved.length} saved to your agenda.` : \"Nothing saved yet.\"}</p>\n\n        <div className=\"mt-6 grid gap-10\">\n          {slots.map((slot) => {\n            const here = list.filter((s) => s.start === slot)\n            return (\n              <section key={slot} aria-label={`${slot} sessions`} className=\"grid gap-4 md:grid-cols-[6rem_minmax(0,1fr)]\">\n                <h2 className={cn(\"flex items-center gap-2 text-2xl tabular-nums md:block md:pt-4\", summitDisplayClass)}>{slot}<span className=\"text-muted-foreground text-xs font-medium md:mt-1 md:block\">until {here[0]!.end}</span></h2>\n                <ul className={cn(\"grid gap-4\", here.length > 1 && \"lg:grid-cols-2\", here.length > 2 && \"xl:grid-cols-3\")}>\n                  {here.map((s) => {\n                    const sp = speakerById(s.speaker)\n                    const on = saved.includes(s.id)\n                    return (\n                      <li key={s.id} className={cn(\"bg-card grid grid-cols-[3.5rem_minmax(0,1fr)_auto] gap-4 rounded-3xl border p-5 transition-colors motion-reduce:transition-none\", on && \"border-primary bg-accent/50\")}>\n                        <Portrait speaker={sp} className=\"aspect-square h-14 w-14 rounded-2xl\" />\n                        <div className=\"min-w-0\">\n                          <p className=\"flex items-center gap-2 text-xs font-bold\"><span className={cn(\"size-2.5 rounded-full\", trackDot[s.track])} aria-hidden=\"true\" />{s.track}</p>\n                          <h3 className=\"mt-1 text-lg leading-snug font-bold text-balance\">{s.title}</h3>\n                          <p className=\"text-muted-foreground mt-1 text-sm\">{sp.name}, {sp.company}</p>\n                          <p className=\"text-muted-foreground mt-3 text-sm text-pretty\">{s.blurb}</p>\n                          <p className=\"text-muted-foreground mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs font-medium\"><span className=\"inline-flex items-center gap-1\"><MapPin className=\"size-3.5\" aria-hidden=\"true\" />{s.room}</span><span className=\"inline-flex items-center gap-1\"><Clock className=\"size-3.5\" aria-hidden=\"true\" />{s.level}</span></p>\n                        </div>\n                        <button type=\"button\" aria-pressed={on} aria-label={on ? `Remove ${s.title} from my agenda` : `Add ${s.title} to my agenda`} onClick={() => toggleSaved(s.id)} className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-10 items-center justify-center rounded-full border outline-none focus-visible:ring-[3px]\"><Star className={cn(\"size-5\", on && \"fill-current\")} aria-hidden=\"true\" /></button>\n                      </li>\n                    )\n                  })}\n                </ul>\n              </section>\n            )\n          })}\n          {slots.length === 0 && <p className=\"text-muted-foreground py-10 text-center\">No talks match these tracks on this day.</p>}\n        </div>\n\n        <section aria-labelledby=\"ss-agenda\" className=\"bg-surface mt-16 rounded-3xl border p-6 sm:p-10\">\n          <div className=\"flex flex-wrap items-end justify-between gap-4\"><h2 id=\"ss-agenda\" className={cn(\"text-3xl\", summitDisplayClass)}>My agenda</h2>{saved.length > 0 && <button type=\"button\" onClick={() => setSaved([])} className=\"hover:bg-accent focus-visible:ring-ring/50 h-10 rounded-full border px-5 text-sm font-bold outline-none focus-visible:ring-[3px]\">Clear all</button>}</div>\n          {mine.length === 0 ? (\n            <p className=\"text-muted-foreground mt-4 max-w-md text-pretty\">Press the star on any talk and it lands here, sorted by day and time.</p>\n          ) : (\n            <>\n              {clashes.length > 0 && <p className=\"bg-chart-2/25 mt-4 flex items-start gap-2 rounded-xl p-3 text-sm font-medium\"><TriangleAlert className=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />{clashes.length} of your talks run at the same time. Pick one for each slot marked below.</p>}\n              <ol className=\"mt-5 divide-y\">\n                {mine.map((s) => (\n                  <li key={s.id} className=\"flex flex-wrap items-center gap-x-5 gap-y-1 py-3\">\n                    <span className={cn(\"w-28 text-sm tabular-nums\", summitDisplayClass)}>Day {s.day} · {s.start}</span>\n                    <span className=\"min-w-0 flex-1 font-semibold\">{s.title}</span>\n                    <span className=\"text-muted-foreground text-sm\">{s.room}</span>\n                    {clashes.includes(s) && <span className=\"bg-chart-2/40 rounded-full px-2.5 py-0.5 text-xs font-bold\">Clash</span>}\n                  </li>\n                ))}\n              </ol>\n            </>\n          )}\n        </section>\n      </main>\n    </SummitShell>\n  )\n}\n\nexport { SummitSchedule, type SummitScheduleProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/summit/summit-schedule.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/summit/summit-speakers.tsx",
      "content": "// Ballmac UI: Summit speakers page. https://ui.ballmac.com/templates/template-summit\n\"use client\"\n\nimport * as React from \"react\"\nimport { Search } from \"lucide-react\"\n\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from \"@/components/ballmac/dialog\"\nimport { sessions, speakers, tracks, type Speaker } from \"@/components/ballmac/templates/summit/summit-data\"\nimport { Portrait, SummitShell, summitDisplayClass, type SummitHrefs } from \"@/components/ballmac/templates/summit/summit-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst filters = [\"All\", ...tracks] as const\n\ntype SummitSpeakersProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<SummitHrefs> }\n\n/** The speakers page: filter by track or search by name, open a bio with the talk and the time it happens. */\nfunction SummitSpeakers({ hrefs, ...props }: SummitSpeakersProps) {\n  const [filter, setFilter] = React.useState<(typeof filters)[number]>(\"All\")\n  const [query, setQuery] = React.useState(\"\")\n  const [open, setOpen] = React.useState<Speaker | null>(null)\n  const q = query.trim().toLowerCase()\n  const list = speakers.filter((s) => (filter === \"All\" || s.track === filter) && (!q || `${s.name} ${s.company} ${s.talk} ${s.role}`.toLowerCase().includes(q)))\n  const session = open ? sessions.find((s) => s.speaker === open.id) : null\n  return (\n    <SummitShell page=\"speakers\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-7xl px-4 pt-12 pb-8 sm:px-6 sm:pt-16\">\n        <h1 className={cn(\"text-[clamp(2.8rem,9vw,7rem)] leading-[0.95]\", summitDisplayClass)}>Speakers</h1>\n        <p className=\"text-muted-foreground mt-4 max-w-xl text-xl text-pretty\">Twelve people who have spent years on one hard question. Each gets forty-five minutes and no slides they did not make.</p>\n        <div className=\"mt-10 flex flex-wrap items-center gap-3 border-y py-4\">\n          <div className=\"relative min-w-52 flex-1 sm:max-w-xs\">\n            <label htmlFor=\"sp-q\" className=\"sr-only\">Search speakers</label>\n            <Search className=\"text-muted-foreground pointer-events-none absolute top-1/2 start-4 size-4 -translate-y-1/2\" aria-hidden=\"true\" />\n            <input id=\"sp-q\" type=\"search\" value={query} onChange={(e) => setQuery(e.target.value)} placeholder=\"Search speakers or talks\" className=\"bg-card focus-visible:ring-ring/50 h-11 w-full rounded-full border pe-4 ps-10 text-sm outline-none focus-visible:ring-[3px]\" />\n          </div>\n          <div role=\"group\" aria-label=\"Filter by track\" className=\"flex flex-wrap gap-2\">\n            {filters.map((f) => <button key={f} type=\"button\" aria-pressed={filter === f} onClick={() => setFilter(f)} className=\"hover:bg-accent focus-visible:ring-ring/50 aria-pressed:bg-primary aria-pressed:text-primary-foreground h-11 rounded-full border px-5 text-sm font-semibold outline-none focus-visible:ring-[3px]\">{f}</button>)}\n          </div>\n        </div>\n        <p role=\"status\" className=\"text-muted-foreground mt-4 text-sm\">{list.length} {list.length === 1 ? \"speaker\" : \"speakers\"}</p>\n        <ul className=\"mt-6 grid grid-cols-2 gap-x-4 gap-y-10 md:grid-cols-3 lg:grid-cols-4\">\n          {list.map((s) => (\n            <li key={s.id}>\n              <button type=\"button\" onClick={() => setOpen(s)} aria-haspopup=\"dialog\" className=\"focus-visible:ring-ring/50 group block w-full rounded-3xl text-start outline-none focus-visible:ring-[3px]\">\n                <Portrait speaker={s} className=\"rounded-3xl transition-transform group-hover:-translate-y-1 motion-reduce:transition-none motion-reduce:group-hover:translate-y-0\" />\n                <span className=\"mt-4 block text-lg font-bold\">{s.name}</span>\n                <span className=\"text-muted-foreground block text-sm\">{s.role}, {s.company}</span>\n                <span className=\"mt-2 block text-sm font-semibold text-pretty\">“{s.talk}”</span>\n              </button>\n            </li>\n          ))}\n        </ul>\n        {list.length === 0 && <div className=\"bg-surface mt-4 rounded-3xl border p-12 text-center\"><p className={cn(\"text-2xl\", summitDisplayClass)}>No one by that name.</p><button type=\"button\" onClick={() => { setQuery(\"\"); setFilter(\"All\") }} className=\"bg-primary text-primary-foreground focus-visible:ring-ring/50 mt-5 h-11 rounded-full px-6 font-bold outline-none focus-visible:ring-[3px]\">Show everyone</button></div>}\n      </main>\n\n      <Dialog open={!!open} onOpenChange={(v) => !v && setOpen(null)}>\n        <DialogContent className=\"summit-theme bg-background max-w-2xl gap-0 overflow-hidden p-0\">\n          {open && (\n            <div className=\"grid sm:grid-cols-[14rem_minmax(0,1fr)]\">\n              <Portrait speaker={open} className=\"aspect-[4/3] sm:aspect-auto sm:h-full\" />\n              <div className=\"p-6 sm:p-8\">\n                <DialogHeader className=\"text-start\"><DialogTitle className={cn(\"text-2xl\", summitDisplayClass)}>{open.name}</DialogTitle><DialogDescription>{open.role}, {open.company}</DialogDescription></DialogHeader>\n                <p className=\"mt-4 text-pretty\">{open.bio}</p>\n                {session && (\n                  <div className=\"bg-surface mt-6 rounded-2xl border p-4\">\n                    <p className=\"text-muted-foreground text-xs font-bold tracking-[0.14em] uppercase\">Talk</p>\n                    <p className=\"mt-1 font-bold\">{session.title}</p>\n                    <p className=\"text-muted-foreground mt-1 text-sm\">Day {session.day} · {session.start} · {session.room}</p>\n                  </div>\n                )}\n              </div>\n            </div>\n          )}\n        </DialogContent>\n      </Dialog>\n    </SummitShell>\n  )\n}\n\nexport { SummitSpeakers, type SummitSpeakersProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/summit/summit-speakers.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/summit/summit-tickets.tsx",
      "content": "// Ballmac UI: Summit tickets page. https://ui.ballmac.com/templates/template-summit\n\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Minus, PartyPopper, Plus } from \"lucide-react\"\n\nimport { faqs, tiers } from \"@/components/ballmac/templates/summit/summit-data\"\nimport { SummitShell, summitDisplayClass, summitOnSun, type SummitHrefs } from \"@/components/ballmac/templates/summit/summit-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst money = (n: number) => `$${n.toLocaleString(\"en-US\", { minimumFractionDigits: n % 1 ? 2 : 0, maximumFractionDigits: 2 })}`\nconst field = \"bg-background focus-visible:ring-ring/50 aria-invalid:border-destructive h-12 w-full rounded-xl border px-4 outline-none focus-visible:ring-[3px]\"\n\ntype SummitTicketsProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<SummitHrefs> }\n\n/** Tickets: pick a tier and a quantity, see a live total with group and promo discounts, then a validated checkout form. */\nfunction SummitTickets({ hrefs, ...props }: SummitTicketsProps) {\n  const [tier, setTier] = React.useState(\"early\")\n  const [qty, setQty] = React.useState(1)\n  const [promo, setPromo] = React.useState(\"\")\n  const [promoState, setPromoState] = React.useState<\"idle\" | \"ok\" | \"bad\">(\"idle\")\n  const [form, setForm] = React.useState({ name: \"\", email: \"\", company: \"\" })\n  const [errors, setErrors] = React.useState<Record<string, string>>({})\n  const [done, setDone] = React.useState(false)\n  const errorRef = React.useRef<HTMLFormElement>(null)\n\n  const t = tiers.find((x) => x.id === tier)!\n  const subtotal = t.price * qty\n  const group = qty >= 5 ? subtotal * 0.15 : 0\n  const code = promoState === \"ok\" ? (subtotal - group) * 0.1 : 0\n  const total = subtotal - group - code\n\n  function applyPromo(e: React.FormEvent) {\n    e.preventDefault()\n    setPromoState(promo.trim().toUpperCase() === \"NORTH10\" ? \"ok\" : \"bad\")\n  }\n  function submit(e: React.FormEvent) {\n    e.preventDefault()\n    const next: Record<string, string> = {}\n    if (!form.name.trim()) next.name = \"Tell us who the ticket is for.\"\n    if (!/^\\S+@\\S+\\.\\S+$/.test(form.email.trim())) next.email = \"Enter an email like you@example.com.\"\n    setErrors(next)\n    const first = Object.keys(next)[0]\n    if (first) return void errorRef.current?.querySelector<HTMLInputElement>(`[name=\"${first}\"]`)?.focus()\n    setDone(true)\n  }\n  const bind = (k: keyof typeof form) => ({ name: k, value: form[k], onChange: (e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, [k]: e.target.value })), \"aria-invalid\": !!errors[k], \"aria-describedby\": errors[k] ? `st-err-${k}` : undefined })\n\n  return (\n    <SummitShell page=\"tickets\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-7xl px-4 pt-12 pb-8 sm:px-6 sm:pt-16\">\n        <h1 className={cn(\"text-[clamp(2.8rem,9vw,7rem)] leading-[0.95]\", summitDisplayClass)}>Tickets</h1>\n        <p className=\"text-muted-foreground mt-4 max-w-xl text-xl text-pretty\">One ticket covers both days and every room. Buy five or more and take 15% off automatically.</p>\n\n        {done ? (\n          <section aria-labelledby=\"st-done\" className=\"bg-chart-2/25 mt-10 rounded-[2rem] border p-8 text-center sm:p-14\">\n            <PartyPopper className=\"mx-auto size-10\" aria-hidden=\"true\" />\n            <h2 id=\"st-done\" className={cn(\"mt-4 text-3xl sm:text-5xl\", summitDisplayClass)}>You are going, {form.name.split(\" \")[0]}.</h2>\n            <p role=\"status\" className=\"mx-auto mt-4 max-w-md text-lg text-pretty\">{qty} × {t.name} for {money(total)}. A receipt and your ticket{qty > 1 ? \"s are\" : \" is\"} on the way to {form.email}.</p>\n            <button type=\"button\" onClick={() => { setDone(false); setForm({ name: \"\", email: \"\", company: \"\" }) }} className=\"hover:bg-accent focus-visible:ring-ring/50 mt-8 h-12 rounded-full border-2 px-6 font-bold outline-none focus-visible:ring-[3px]\">Buy more tickets</button>\n          </section>\n        ) : (\n          <div className=\"mt-10 grid grid-cols-[minmax(0,1fr)] items-start gap-8 lg:grid-cols-[minmax(0,1fr)_24rem]\">\n            <div className=\"grid gap-8\">\n              <fieldset>\n                <legend className={cn(\"mb-4 text-2xl\", summitDisplayClass)}>1. Choose a ticket</legend>\n                <div className=\"grid gap-3 sm:grid-cols-2\">\n                  {tiers.map((x) => (\n                    <label key={x.id} className={cn(\"bg-card focus-within:ring-ring/50 relative flex cursor-pointer flex-col rounded-3xl border-2 p-6 transition-colors focus-within:ring-[3px] motion-reduce:transition-none\", tier === x.id ? \"border-primary bg-accent/40\" : \"hover:border-primary/40\")}>\n                      <input type=\"radio\" name=\"tier\" value={x.id} checked={tier === x.id} onChange={() => setTier(x.id)} className=\"sr-only\" />\n                      {x.badge && <span className={cn(\"bg-chart-2 absolute -top-3 start-6 rounded-full px-3 py-0.5 text-xs font-bold\", summitOnSun)}>{x.badge}</span>}\n                      <span className=\"flex items-baseline justify-between gap-2\"><span className=\"text-lg font-bold\">{x.name}</span><span className={cn(\"text-3xl tabular-nums\", summitDisplayClass)}>${x.price}</span></span>\n                      <span className=\"text-muted-foreground mt-1 text-sm\">{x.blurb}</span>\n                      <ul className=\"mt-4 grid gap-1.5 text-sm\">{x.perks.map((p) => <li key={p} className=\"flex gap-2\"><Check className=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />{p}</li>)}</ul>\n                    </label>\n                  ))}\n                </div>\n              </fieldset>\n\n              <form ref={errorRef} onSubmit={submit} noValidate aria-labelledby=\"st-details\" className=\"bg-card grid gap-5 rounded-3xl border p-6 sm:p-8\">\n                <h2 id=\"st-details\" className={cn(\"text-2xl\", summitDisplayClass)}>2. Your details</h2>\n                <div className=\"grid gap-2\"><label htmlFor=\"st-name\" className=\"text-sm font-semibold\">Full name</label><input id=\"st-name\" autoComplete=\"name\" className={field} {...bind(\"name\")} />{errors.name && <p id=\"st-err-name\" role=\"alert\" className=\"text-destructive text-sm\">{errors.name}</p>}</div>\n                <div className=\"grid gap-2\"><label htmlFor=\"st-email\" className=\"text-sm font-semibold\">Email for the receipt</label><input id=\"st-email\" type=\"email\" autoComplete=\"email\" className={field} {...bind(\"email\")} />{errors.email && <p id=\"st-err-email\" role=\"alert\" className=\"text-destructive text-sm\">{errors.email}</p>}</div>\n                <div className=\"grid gap-2\"><label htmlFor=\"st-company\" className=\"text-sm font-semibold\">Company <span className=\"text-muted-foreground font-normal\">(optional)</span></label><input id=\"st-company\" autoComplete=\"organization\" className={field} {...bind(\"company\")} /></div>\n                <button type=\"submit\" className=\"bg-primary text-primary-foreground focus-visible:ring-ring/50 h-14 rounded-full text-lg font-bold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]\">Pay {money(total)}</button>\n                <p className=\"text-muted-foreground text-center text-xs\">Demo checkout: no card is taken. Refundable until 30 days before.</p>\n              </form>\n            </div>\n\n            <aside aria-labelledby=\"st-sum\" className=\"bg-surface rounded-3xl border p-6 lg:sticky lg:top-24\">\n              <h2 id=\"st-sum\" className={cn(\"text-2xl\", summitDisplayClass)}>Your order</h2>\n              <div className=\"mt-5 flex items-center justify-between gap-3\">\n                <span className=\"font-semibold\">{t.name}</span>\n                <div className=\"flex items-center gap-1 rounded-full border bg-card\" role=\"group\" aria-label=\"Number of tickets\">\n                  <button type=\"button\" aria-label=\"Fewer tickets\" disabled={qty <= 1} onClick={() => setQty((q) => Math.max(1, q - 1))} className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-10 items-center justify-center rounded-full outline-none focus-visible:ring-[3px] disabled:opacity-50\"><Minus className=\"size-4\" aria-hidden=\"true\" /></button>\n                  <output aria-live=\"polite\" className=\"w-8 text-center font-bold tabular-nums\">{qty}</output>\n                  <button type=\"button\" aria-label=\"More tickets\" disabled={qty >= 20} onClick={() => setQty((q) => Math.min(20, q + 1))} className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-10 items-center justify-center rounded-full outline-none focus-visible:ring-[3px] disabled:opacity-50\"><Plus className=\"size-4\" aria-hidden=\"true\" /></button>\n                </div>\n              </div>\n              <form onSubmit={applyPromo} className=\"mt-5 flex gap-2\">\n                <label htmlFor=\"st-promo\" className=\"sr-only\">Promo code</label>\n                <input id=\"st-promo\" value={promo} onChange={(e) => { setPromo(e.target.value); setPromoState(\"idle\") }} placeholder=\"Promo code\" aria-describedby=\"st-promo-msg\" className=\"bg-background focus-visible:ring-ring/50 h-11 min-w-0 flex-1 rounded-xl border px-3 text-sm outline-none focus-visible:ring-[3px]\" />\n                <button type=\"submit\" className=\"hover:bg-accent focus-visible:ring-ring/50 h-11 rounded-xl border px-4 text-sm font-bold outline-none focus-visible:ring-[3px]\">Apply</button>\n              </form>\n              <p id=\"st-promo-msg\" role=\"status\" className={cn(\"mt-2 min-h-5 text-sm\", promoState === \"bad\" && \"text-destructive\")}>{promoState === \"ok\" ? \"NORTH10 applied: 10% off.\" : promoState === \"bad\" ? \"That code is not valid. Try NORTH10.\" : \"\"}</p>\n              <dl className=\"mt-3 grid gap-2 border-t pt-4 text-sm\">\n                <div className=\"flex justify-between\"><dt>{qty} × {t.name}</dt><dd className=\"tabular-nums\">{money(subtotal)}</dd></div>\n                {group > 0 && <div className=\"flex justify-between\"><dt>Group discount (15%)</dt><dd className=\"tabular-nums\">−{money(group)}</dd></div>}\n                {code > 0 && <div className=\"flex justify-between\"><dt>Promo NORTH10</dt><dd className=\"tabular-nums\">−{money(code)}</dd></div>}\n                <div className=\"flex justify-between border-t pt-3 text-lg font-bold\"><dt>Total</dt><dd className=\"tabular-nums\">{money(total)}</dd></div>\n              </dl>\n              {qty < 5 && <p className=\"text-muted-foreground mt-4 text-xs\">Add {5 - qty} more {5 - qty === 1 ? \"ticket\" : \"tickets\"} for 15% off.</p>}\n            </aside>\n          </div>\n        )}\n\n        <section aria-labelledby=\"st-faq\" className=\"mt-20 grid gap-8 lg:grid-cols-[1fr_1.6fr]\">\n          <h2 id=\"st-faq\" className={cn(\"text-[clamp(2rem,5vw,3rem)] leading-none\", summitDisplayClass)}>Before you buy</h2>\n          <div className=\"divide-y border-y\">{faqs.slice(2, 5).map((f) => <details key={f.q} className=\"group py-4\"><summary className=\"focus-visible:ring-ring/50 cursor-pointer list-none rounded-md text-lg font-bold outline-none focus-visible:ring-[3px]\">{f.q}</summary><p className=\"text-muted-foreground mt-2 text-pretty\">{f.a}</p></details>)}</div>\n        </section>\n      </main>\n    </SummitShell>\n  )\n}\n\nexport { SummitTickets, type SummitTicketsProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/summit/summit-tickets.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/summit/summit-venue.tsx",
      "content": "// Ballmac UI: Summit venue page. https://ui.ballmac.com/templates/template-summit\n\"use client\"\n\nimport * as React from \"react\"\nimport { Accessibility, Bed, Plane, Train } from \"lucide-react\"\n\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from \"@/components/ballmac/accordion\"\nimport { faqs } from \"@/components/ballmac/templates/summit/summit-data\"\nimport { SummitShell, summitDisplayClass, type SummitHrefs } from \"@/components/ballmac/templates/summit/summit-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst ways = [\n  { id: \"fly\", label: \"Fly\", icon: Plane, title: \"Keflavík airport, 50 minutes away\", body: \"Flybus meets every arrival and drops you at the hotel row. We run a free shuttle from the airport at 8:00 and 12:00 on Thursday and Friday.\" },\n  { id: \"bus\", label: \"City bus\", icon: Train, title: \"Lines 1, 3 and 11 stop at the door\", body: \"Buses run every ten minutes until midnight. Your ticket doubles as a three-day city pass. Show the QR code to the driver.\" },\n  { id: \"walk\", label: \"On foot\", icon: Accessibility, title: \"Twelve minutes from the old harbour\", body: \"The route along the water is flat and lit. Taxis and wheelchair-accessible vehicles wait at the north entrance.\" },\n]\nconst hotels = [[\"Hótel Esja\", \"3 min walk\", 164], [\"The Harbour House\", \"6 min walk\", 142], [\"Sól Apartments\", \"9 min walk\", 118]] as const\n\n/** A flat map of the harbour: water, land, a route and three pins. Decorative; the list beside it says the same in words. */\nfunction VenueMap() {\n  return (\n    <svg viewBox=\"0 0 600 420\" role=\"img\" aria-label=\"Map: the airport is to the south east, the hotels sit along the harbour and Harpa is at the north end of the water\" className=\"bg-chart-3/20 h-full w-full rounded-3xl\">\n      <path className=\"fill-secondary\" d=\"M0 0H600V420H0Z\" />\n      <path className=\"fill-chart-3/35\" d=\"M-10 150C80 110 140 190 240 160S400 80 470 120 590 150 610 130V-10H-10Z\" />\n      <path className=\"fill-card\" d=\"M0 200C90 170 170 240 270 215S420 150 500 190 580 220 600 210V420H0Z\" />\n      <path className=\"stroke-primary fill-none\" strokeWidth=\"3\" strokeDasharray=\"2 10\" strokeLinecap=\"round\" d=\"M90 360C150 320 200 330 260 280S360 230 430 215\" />\n      {[[90, 360, \"Airport bus\"], [260, 280, \"Hotels\"], [430, 215, \"Harpa\"]].map(([x, y, l]) => (\n        <g key={l as string}><circle cx={x as number} cy={y as number} r=\"13\" className={l === \"Harpa\" ? \"fill-chart-1\" : \"fill-primary\"} /><circle cx={x as number} cy={y as number} r=\"5\" className=\"fill-card\" /><text x={(x as number) + 20} y={(y as number) + 5} className=\"fill-foreground text-[15px] font-bold\">{l as string}</text></g>\n      ))}\n    </svg>\n  )\n}\n\ntype SummitVenueProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<SummitHrefs> }\n\n/** The venue page: map, three ways to arrive, nearby hotels with a held rate, access details and the full FAQ. */\nfunction SummitVenue({ hrefs, ...props }: SummitVenueProps) {\n  const [way, setWay] = React.useState(\"fly\")\n  const current = ways.find((w) => w.id === way)!\n  return (\n    <SummitShell page=\"venue\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-7xl px-4 pt-12 pb-8 sm:px-6 sm:pt-16\">\n        <h1 className={cn(\"text-[clamp(2.8rem,9vw,7rem)] leading-[0.95]\", summitDisplayClass)}>Venue</h1>\n        <p className=\"text-muted-foreground mt-4 max-w-xl text-xl text-pretty\">Harpa sits on the water at the edge of the old harbour. Three rooms, one long lunch hall and a lot of glass.</p>\n\n        <div className=\"mt-10 grid items-stretch gap-6 lg:grid-cols-[1.3fr_1fr]\">\n          <div className=\"min-h-72\"><VenueMap /></div>\n          <section aria-labelledby=\"sv-way\" className=\"bg-card rounded-3xl border p-6 sm:p-8\">\n            <h2 id=\"sv-way\" className={cn(\"text-2xl\", summitDisplayClass)}>Getting there</h2>\n            <div role=\"group\" aria-label=\"How you travel\" className=\"mt-4 flex flex-wrap gap-2\">{ways.map((w) => <button key={w.id} type=\"button\" aria-pressed={way === w.id} onClick={() => setWay(w.id)} className=\"hover:bg-accent focus-visible:ring-ring/50 aria-pressed:bg-primary aria-pressed:text-primary-foreground inline-flex h-10 items-center gap-2 rounded-full border px-4 text-sm font-semibold outline-none focus-visible:ring-[3px]\"><w.icon className=\"size-4\" aria-hidden=\"true\" />{w.label}</button>)}</div>\n            <div aria-live=\"polite\" className=\"mt-6\"><h3 className=\"text-xl font-bold text-balance\">{current.title}</h3><p className=\"text-muted-foreground mt-2 text-pretty\">{current.body}</p></div>\n            <address className=\"text-muted-foreground mt-6 border-t pt-4 text-sm not-italic\">Harpa Concert Hall<br />Austurbakki 2, 101 Reykjavik</address>\n          </section>\n        </div>\n\n        <section aria-labelledby=\"sv-hotels\" className=\"mt-16\">\n          <h2 id=\"sv-hotels\" className={cn(\"text-[clamp(2rem,5vw,3rem)] leading-none\", summitDisplayClass)}>Where to stay</h2>\n          <p className=\"text-muted-foreground mt-3 max-w-xl text-pretty\">We hold rooms at three hotels until 1 April. Use the code NORTHLIGHT when you book.</p>\n          <ul className=\"mt-6 grid gap-4 md:grid-cols-3\">{hotels.map(([n, d, p]) => <li key={n} className=\"bg-card rounded-3xl border p-6\"><Bed className=\"text-primary size-6\" aria-hidden=\"true\" /><h3 className=\"mt-4 text-xl font-bold\">{n}</h3><p className=\"text-muted-foreground text-sm\">{d}</p><p className=\"mt-4\"><span className={cn(\"text-3xl tabular-nums\", summitDisplayClass)}>${p}</span><span className=\"text-muted-foreground text-sm\"> a night</span></p></li>)}</ul>\n        </section>\n\n        <section aria-labelledby=\"sv-access\" className=\"bg-surface mt-16 grid gap-6 rounded-3xl border p-6 sm:p-10 md:grid-cols-3\">\n          <h2 id=\"sv-access\" className={cn(\"text-[clamp(1.8rem,4vw,2.5rem)] leading-none md:col-span-3\", summitDisplayClass)}>Access, for everyone</h2>\n          {[[\"Step-free\", \"Every room, restroom and the lunch hall. Lifts at both ends of the building.\"], [\"Hearing and sight\", \"Hearing loops in all three rooms, live captions on every screen and large-print programmes.\"], [\"Quiet and care\", \"A quiet room on each floor and a staffed children’s room, both open all day.\"]].map(([t, b]) => <div key={t}><h3 className=\"text-lg font-bold\">{t}</h3><p className=\"text-muted-foreground mt-2 text-pretty\">{b}</p></div>)}\n        </section>\n\n        <section aria-labelledby=\"sv-faq\" className=\"mt-16 grid gap-8 lg:grid-cols-[1fr_1.6fr]\">\n          <h2 id=\"sv-faq\" className={cn(\"text-[clamp(2rem,5vw,3rem)] leading-none\", summitDisplayClass)}>Questions</h2>\n          <Accordion type=\"single\" collapsible className=\"border-y\">{faqs.map((f) => <AccordionItem key={f.q} value={f.q}><AccordionTrigger className=\"text-lg font-bold\">{f.q}</AccordionTrigger><AccordionContent className=\"text-muted-foreground text-pretty\">{f.a}</AccordionContent></AccordionItem>)}</Accordion>\n        </section>\n      </main>\n    </SummitShell>\n  )\n}\n\nexport { SummitVenue, type SummitVenueProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/summit/summit-venue.tsx"
    },
    {
      "path": "registry/ballmac/app/summit/page.tsx",
      "content": "// Ballmac UI: Summit template route. https://ui.ballmac.com/templates/template-summit\nimport type { Metadata } from \"next\"\n\nimport { SummitHome } from \"@/components/ballmac/templates/summit/summit-home\"\n\nexport const metadata: Metadata = {\n  title: \"Northlight Summit 2027: look further\",\n  description: \"Two days of talks about design, engineering and the long view. Reykjavik, 14 and 15 May 2027.\",\n}\n\nexport default function Page() {\n  return <SummitHome />\n}\n",
      "type": "registry:page",
      "target": "app/summit/page.tsx"
    },
    {
      "path": "registry/ballmac/app/summit/schedule/page.tsx",
      "content": "// Ballmac UI: Summit template route. https://ui.ballmac.com/templates/template-summit\nimport type { Metadata } from \"next\"\n\nimport { SummitSchedule } from \"@/components/ballmac/templates/summit/summit-schedule\"\n\nexport const metadata: Metadata = {\n  title: \"Schedule · Northlight Summit\",\n  description: \"Three rooms, two days. Star talks to build your agenda.\",\n}\n\nexport default function Page() {\n  return <SummitSchedule />\n}\n",
      "type": "registry:page",
      "target": "app/summit/schedule/page.tsx"
    },
    {
      "path": "registry/ballmac/app/summit/speakers/page.tsx",
      "content": "// Ballmac UI: Summit template route. https://ui.ballmac.com/templates/template-summit\nimport type { Metadata } from \"next\"\n\nimport { SummitSpeakers } from \"@/components/ballmac/templates/summit/summit-speakers\"\n\nexport const metadata: Metadata = {\n  title: \"Speakers · Northlight Summit\",\n  description: \"Twelve speakers, forty-five minutes each.\",\n}\n\nexport default function Page() {\n  return <SummitSpeakers />\n}\n",
      "type": "registry:page",
      "target": "app/summit/speakers/page.tsx"
    },
    {
      "path": "registry/ballmac/app/summit/tickets/page.tsx",
      "content": "// Ballmac UI: Summit template route. https://ui.ballmac.com/templates/template-summit\nimport type { Metadata } from \"next\"\n\nimport { SummitTickets } from \"@/components/ballmac/templates/summit/summit-tickets\"\n\nexport const metadata: Metadata = {\n  title: \"Tickets · Northlight Summit\",\n  description: \"Early bird $490. Group discounts from five tickets.\",\n}\n\nexport default function Page() {\n  return <SummitTickets />\n}\n",
      "type": "registry:page",
      "target": "app/summit/tickets/page.tsx"
    },
    {
      "path": "registry/ballmac/app/summit/venue/page.tsx",
      "content": "// Ballmac UI: Summit template route. https://ui.ballmac.com/templates/template-summit\nimport type { Metadata } from \"next\"\n\nimport { SummitVenue } from \"@/components/ballmac/templates/summit/summit-venue\"\n\nexport const metadata: Metadata = {\n  title: \"Venue · Northlight Summit\",\n  description: \"Harpa, Reykjavik. How to get there, where to stay and access details.\",\n}\n\nexport default function Page() {\n  return <SummitVenue />\n}\n",
      "type": "registry:page",
      "target": "app/summit/venue/page.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "template",
      "event",
      "conference",
      "summit",
      "schedule",
      "speakers",
      "tickets",
      "countdown"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "Installs a five-page conference site with a countdown, a starrable schedule that warns about clashes, speaker dialogs, a ticket calculator with group and promo discounts, and a venue guide. Edit summit-data.ts and summitCss.",
      "whenToUse": [
        "Conferences, meetups and festivals",
        "Any event site with a schedule, speakers and ticket tiers"
      ],
      "whenNotToUse": [
        "Ticketing with real payments (wire the form to your processor first)"
      ],
      "composesWith": [
        "dialog",
        "accordion",
        "countdown"
      ],
      "a11y": [
        {
          "keys": "Star button on a session",
          "action": "Adds or removes it from your agenda; the summary updates in a live region"
        },
        {
          "keys": "Enter on a speaker",
          "action": "Opens their bio in a dialog; Escape closes it"
        },
        {
          "keys": "Arrow buttons beside the ticket count",
          "action": "Change the quantity; the total is announced"
        }
      ],
      "customization": [
        "Edit summit-data.ts for the event, speakers, sessions and tiers",
        "Change summitCss for the sky and ridge colours",
        "Swap Portrait for real photos",
        "Connect the checkout form to your payment provider"
      ]
    },
    "examples": [
      "template-summit-demo",
      "template-summit-schedule",
      "template-summit-speakers",
      "template-summit-tickets",
      "template-summit-venue"
    ],
    "url": "https://ui.ballmac.com/components/template-summit"
  },
  "docs": "Pages are at /summit, /summit/schedule, /summit/speakers, /summit/tickets and /summit/venue. Speakers, sessions, tiers and FAQ live in summit-data.ts; set EVENT_DATE for the countdown. The promo code in the demo is NORTH10. Replace Portrait with photos when you have them.",
  "categories": [
    "templates"
  ],
  "type": "registry:block"
}