{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "changelog-page",
  "title": "Changelog page",
  "description": "A timeline of releases with a filter for new, improved and fixed, a version index and a subscribe form.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/manniche/changelog-page/changelog-page.tsx",
      "content": "// A changelog page with a timeline. The filter is a row of radio buttons; :has() hides the changes that do not match,\n// so it works without JavaScript and the markup can ship as a static page. Colours come from the theme tokens.\nimport { ArrowRight, Rss, Sparkles, Wrench, Zap } from 'lucide-react'\nimport { cn } from '@/lib/utils'\n\ntype Kind = 'new' | 'improved' | 'fixed'\n\nconst kinds: Record<Kind, { label: string; icon: typeof Sparkles; style: string }> = {\n  new: { label: 'New', icon: Sparkles, style: 'bg-primary/12 text-primary' },\n  improved: { label: 'Improved', icon: Zap, style: 'bg-success/12 text-success' },\n  fixed: { label: 'Fixed', icon: Wrench, style: 'bg-muted text-muted-foreground' },\n}\n\n// Written out in full so Tailwind can find the classes. A change is hidden when another filter is chosen.\nconst hideUnless: Record<Kind, string> = {\n  new: 'group-has-[[value=improved]:checked]/log:hidden group-has-[[value=fixed]:checked]/log:hidden',\n  improved: 'group-has-[[value=new]:checked]/log:hidden group-has-[[value=fixed]:checked]/log:hidden',\n  fixed: 'group-has-[[value=new]:checked]/log:hidden group-has-[[value=improved]:checked]/log:hidden',\n}\nconst hideIfNone: Record<Kind, string> = {\n  new: 'group-has-[[value=new]:checked]/log:hidden',\n  improved: 'group-has-[[value=improved]:checked]/log:hidden',\n  fixed: 'group-has-[[value=fixed]:checked]/log:hidden',\n}\n\ntype Release = { version: string; date: string; iso: string; title: string; summary: string; highlight?: string[]; changes: [Kind, string][] }\n\nconst releases: Release[] = [\n  {\n    version: '4.2',\n    date: '1 October 2026',\n    iso: '2026-10-01',\n    title: 'Gift cards and a faster checkout',\n    summary: 'Sell gift cards in any amount, and checkout now loads in half the time on phones.',\n    highlight: ['Gift card', '€50', 'For Ana, with love'],\n    changes: [\n      ['new', 'Gift cards in fixed or custom amounts, with an optional message.'],\n      ['new', 'Customers can check a gift card balance from the order page.'],\n      ['improved', 'Checkout loads 48% faster on mobile networks.'],\n      ['fixed', 'Discount codes with lowercase letters were sometimes rejected.'],\n    ],\n  },\n  {\n    version: '4.1',\n    date: '12 September 2026',\n    iso: '2026-09-12',\n    title: 'Stock across warehouses',\n    summary: 'Keep stock in more than one place and let Quayside pick the nearest warehouse for each order.',\n    changes: [\n      ['new', 'Warehouses with their own stock levels and opening hours.'],\n      ['improved', 'Low stock alerts now say which warehouse is running out.'],\n      ['improved', 'The product list remembers your filters between visits.'],\n      ['fixed', 'CSV exports used the wrong decimal separator for some languages.'],\n    ],\n  },\n  {\n    version: '4.0',\n    date: '20 August 2026',\n    iso: '2026-08-20',\n    title: 'A calmer admin',\n    summary: 'A new navigation, a dark mode and keyboard shortcuts for everything you do every day.',\n    changes: [\n      ['new', 'Dark mode that follows your system setting.'],\n      ['new', 'Keyboard shortcuts. Press ? anywhere to see them.'],\n      ['improved', 'The sidebar is grouped by task instead of by feature.'],\n    ],\n  },\n  {\n    version: '3.9',\n    date: '29 July 2026',\n    iso: '2026-07-29',\n    title: 'Small fixes before the summer sale',\n    summary: 'A round of fixes reported by shops getting ready for August.',\n    changes: [\n      ['fixed', 'Shipping labels for parcels over 20 kg printed on two pages.'],\n      ['fixed', 'Order emails showed the wrong time zone for some shops.'],\n      ['improved', 'Search finds orders by the last four digits of a phone number.'],\n    ],\n  },\n]\n\nfunction Logo() {\n  return (\n    <a href=\"#top\" className=\"flex items-center gap-2 font-semibold tracking-tight\">\n      <svg viewBox=\"0 0 24 24\" className=\"size-7\" aria-hidden>\n        <rect width=\"24\" height=\"24\" rx=\"7\" className=\"fill-primary\" />\n        <path d=\"M6 15c3-2 9-2 12 0M6 10.5c3-2 9-2 12 0\" fill=\"none\" strokeWidth=\"2\" strokeLinecap=\"round\" className=\"stroke-primary-foreground\" />\n      </svg>\n      Quayside\n    </a>\n  )\n}\n\nfunction KindBadge({ kind }: { kind: Kind }) {\n  const { label, icon: Icon, style } = kinds[kind]\n  return (\n    <span className={cn('inline-flex w-24 shrink-0 items-center gap-1.5 self-start rounded-full px-2 py-0.5 text-xs font-medium', style)}>\n      <Icon className=\"size-3\" aria-hidden />\n      {label}\n    </span>\n  )\n}\n\n/** Changelog page: a filterable timeline of releases with a highlight card, a version index and a subscribe form. */\nexport function ChangelogPage() {\n  return (\n    <div id=\"top\" className=\"group/log min-h-dvh bg-background font-sans text-foreground antialiased\">\n      <header className=\"border-b\">\n        <div className=\"mx-auto flex h-16 max-w-5xl items-center gap-6 px-4 sm:px-6\">\n          <Logo />\n          <span className=\"text-sm text-muted-foreground\">/ Changelog</span>\n          <a\n            href=\"#subscribe\"\n            className=\"ml-auto inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm transition-colors duration-150 hover:bg-muted\"\n          >\n            <Rss className=\"size-4 text-primary\" aria-hidden />\n            Subscribe\n          </a>\n        </div>\n      </header>\n\n      <main className=\"mx-auto max-w-5xl px-4 sm:px-6\">\n        <div className=\"py-14 sm:py-20\">\n          <h1 className=\"text-4xl font-semibold tracking-tight sm:text-5xl\">What’s new in Quayside</h1>\n          <p className=\"mt-3 max-w-xl text-lg text-muted-foreground\">\n            New features, improvements and fixes. We ship small changes every week and bigger ones about once a month.\n          </p>\n\n          <fieldset className=\"mt-8 flex flex-wrap gap-2 text-sm\">\n            <legend className=\"sr-only\">Show</legend>\n            {(\n              [\n                ['all', 'Everything'],\n                ['new', 'New'],\n                ['improved', 'Improved'],\n                ['fixed', 'Fixed'],\n              ] as const\n            ).map(([value, label]) => (\n              <label\n                key={value}\n                className=\"cursor-pointer rounded-full border px-3.5 py-1.5 text-muted-foreground transition-[background-color,color,border-color] duration-150 hover:text-foreground has-checked:border-foreground has-checked:bg-foreground has-checked:text-background has-focus-visible:ring-2 has-focus-visible:ring-ring has-focus-visible:ring-offset-2 has-focus-visible:ring-offset-background\"\n              >\n                <input type=\"radio\" name=\"changelog-filter\" value={value} defaultChecked={value === 'all'} className=\"sr-only\" />\n                {label}\n              </label>\n            ))}\n          </fieldset>\n        </div>\n\n        <div className=\"grid gap-12 pb-20 lg:grid-cols-[1fr_12rem]\">\n          <ol className=\"relative\">\n            {releases.map((r, i) => {\n              const present = new Set(r.changes.map(([k]) => k))\n              const missing = (Object.keys(kinds) as Kind[]).filter((k) => !present.has(k))\n              return (\n                <li\n                  key={r.version}\n                  id={`v${r.version}`}\n                  className={cn(\n                    'relative grid scroll-mt-6 gap-4 pb-14 pl-8 sm:grid-cols-[9rem_1fr] sm:gap-12 sm:pl-0',\n                    missing.map((k) => hideIfNone[k]),\n                  )}\n                >\n                  <span aria-hidden className=\"absolute top-2 bottom-0 left-[5px] w-px bg-border sm:left-[10.5rem]\" />\n                  <span\n                    aria-hidden\n                    className={cn(\n                      'absolute top-1.5 left-0 size-[11px] rounded-full border-2 border-background ring-1 sm:left-[calc(10.5rem-5px)]',\n                      i === 0 ? 'bg-primary ring-primary' : 'bg-muted-foreground/40 ring-border',\n                    )}\n                  />\n                  <div className=\"sm:pt-0.5\">\n                    <p className=\"font-semibold tabular-nums\">v{r.version}</p>\n                    <time dateTime={r.iso} className=\"text-sm text-muted-foreground\">\n                      {r.date}\n                    </time>\n                  </div>\n                  <article className=\"min-w-0\">\n                    {i === 0 && (\n                      <span className=\"mb-3 inline-block rounded-full bg-primary px-2.5 py-0.5 text-xs font-medium text-primary-foreground\">Latest</span>\n                    )}\n                    <h2 className=\"text-2xl font-semibold tracking-tight\">{r.title}</h2>\n                    <p className=\"mt-2 text-muted-foreground\">{r.summary}</p>\n\n                    {r.highlight && (\n                      <div className=\"mt-6 overflow-hidden rounded-2xl border bg-muted/50 p-6 sm:p-8\">\n                        <div className=\"mx-auto max-w-xs -rotate-2 rounded-2xl bg-primary p-5 text-primary-foreground shadow-xl shadow-primary/25 transition-transform duration-500 ease-out-quint hover:rotate-0 starting:rotate-6 starting:opacity-0\">\n                          <div className=\"flex items-center justify-between text-sm\">\n                            <span className=\"font-medium\">{r.highlight[0]}</span>\n                            <span className=\"opacity-75\">Quayside</span>\n                          </div>\n                          <p className=\"mt-8 text-4xl font-semibold tracking-tight\">{r.highlight[1]}</p>\n                          <p className=\"mt-1 text-sm opacity-80\">{r.highlight[2]}</p>\n                        </div>\n                      </div>\n                    )}\n\n                    <ul className=\"mt-6 space-y-3\">\n                      {r.changes.map(([kind, text]) => (\n                        <li key={text} className={cn('flex gap-3 text-sm', hideUnless[kind])}>\n                          <KindBadge kind={kind} />\n                          <span className=\"pt-px\">{text}</span>\n                        </li>\n                      ))}\n                    </ul>\n                  </article>\n                </li>\n              )\n            })}\n          </ol>\n\n          <aside className=\"hidden lg:block\">\n            <nav aria-label=\"Versions\" className=\"sticky top-6\">\n              <p className=\"text-xs font-semibold tracking-wide text-muted-foreground uppercase\">Versions</p>\n              <ul className=\"mt-3 space-y-1 border-l text-sm\">\n                {releases.map((r) => (\n                  <li key={r.version}>\n                    <a\n                      href={`#v${r.version}`}\n                      className=\"-ml-px block border-l border-transparent py-1 pl-4 text-muted-foreground transition-colors duration-150 hover:border-foreground hover:text-foreground\"\n                    >\n                      v{r.version}\n                    </a>\n                  </li>\n                ))}\n              </ul>\n            </nav>\n          </aside>\n        </div>\n\n        <section id=\"subscribe\" className=\"mb-20 rounded-3xl border bg-card p-6 sm:p-10\">\n          <div className=\"grid gap-6 md:grid-cols-[1fr_auto] md:items-center\">\n            <div>\n              <h2 className=\"text-xl font-semibold tracking-tight\">Get the changelog by email</h2>\n              <p className=\"mt-1 text-sm text-muted-foreground\">One short email when we ship something worth knowing. Unsubscribe in one click.</p>\n            </div>\n            <form action=\"#\" className=\"flex flex-col gap-2 sm:flex-row\">\n              <label htmlFor=\"changelog-email\" className=\"sr-only\">\n                Email\n              </label>\n              <input\n                id=\"changelog-email\"\n                type=\"email\"\n                required\n                placeholder=\"you@shop.com\"\n                className=\"h-11 rounded-full border bg-background px-4 text-sm outline-none transition-[border-color,box-shadow] focus:border-ring focus:ring-4 focus:ring-ring/15 sm:w-64\"\n              />\n              <button\n                type=\"submit\"\n                className=\"group/sub inline-flex h-11 items-center justify-center gap-2 rounded-full bg-primary px-5 text-sm font-medium text-primary-foreground transition-transform duration-150 ease-out active:scale-[0.97]\"\n              >\n                Subscribe\n                <ArrowRight className=\"size-4 transition-transform duration-200 group-hover/sub:translate-x-0.5\" aria-hidden />\n              </button>\n            </form>\n          </div>\n        </section>\n      </main>\n\n      <footer className=\"border-t\">\n        <p className=\"mx-auto max-w-5xl px-4 py-8 text-sm text-muted-foreground sm:px-6\">© 2026 Quayside. A template from Manniche UI.</p>\n      </footer>\n    </div>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "cssVars": {
    "theme": {
      "color-success": "var(--success)",
      "ease-out-quint": "cubic-bezier(0.23, 1, 0.32, 1)"
    },
    "light": {
      "success": "oklch(0.55 0.13 155)"
    },
    "dark": {
      "success": "oklch(0.72 0.13 155)"
    }
  },
  "categories": [
    "templates"
  ],
  "type": "registry:block"
}