{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "template-atlas",
  "title": "Atlas: store admin",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A dense, quiet six-page admin for an online store: a dashboard with a live date range, sortable orders with bulk actions, an order page with a refund flow, products, customers and settings.",
  "dependencies": [
    "lucide-react",
    "recharts@^3"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/badge.json",
    "https://ui.ballmac.com/r/checkbox.json",
    "https://ui.ballmac.com/r/chart.json",
    "https://ui.ballmac.com/r/command.json",
    "https://ui.ballmac.com/r/dialog.json",
    "https://ui.ballmac.com/r/dropdown-menu.json",
    "https://ui.ballmac.com/r/segmented-control.json",
    "https://ui.ballmac.com/r/sheet.json",
    "https://ui.ballmac.com/r/sparkline.json",
    "https://ui.ballmac.com/r/switch.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/templates/atlas/atlas-fonts.ts",
      "content": "// Ballmac UI: Atlas template fonts. https://ui.ballmac.com/templates/template-atlas\nimport { Geist_Mono, Manrope } from \"next/font/google\"\n\nexport const atlasSans = Manrope({ variable: \"--atlas-sans\", subsets: [\"latin\"], display: \"swap\", preload: false })\nexport const atlasMono = Geist_Mono({ variable: \"--atlas-mono\", subsets: [\"latin\"], display: \"swap\", preload: false })\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/atlas/atlas-fonts.ts"
    },
    {
      "path": "registry/ballmac/components/templates/atlas/atlas-data.ts",
      "content": "// Ballmac UI: Atlas sample data. https://ui.ballmac.com/templates/template-atlas\n\nexport type AtlasPayment = \"paid\" | \"pending\" | \"refunded\"\nexport type AtlasFulfillment = \"unfulfilled\" | \"shipped\" | \"delivered\"\nexport type AtlasChannel = \"Online store\" | \"Instagram\" | \"Wholesale\" | \"Pop-up\"\n\nexport type AtlasOrder = {\n  id: string\n  /** ISO date-time, shown in UTC. */\n  date: string\n  customerId: string\n  channel: AtlasChannel\n  payment: AtlasPayment\n  fulfillment: AtlasFulfillment\n  items: { productId: string; qty: number }[]\n}\n\nexport type AtlasProduct = { id: string; name: string; category: string; price: number; stock: number; status: \"active\" | \"draft\"; hue: number; sold: number }\nexport type AtlasCustomer = { id: string; name: string; email: string; city: string; segment: \"VIP\" | \"New\" | \"At risk\" | \"Regular\"; tone: number }\n\nexport const products: AtlasProduct[] = [\n  { id: \"p-01\", name: \"Field notebook, set of 3\", category: \"Paper\", price: 24, stock: 142, status: \"active\", hue: 85, sold: 812 },\n  { id: \"p-02\", name: \"Brass pen, matte\", category: \"Writing\", price: 58, stock: 7, status: \"active\", hue: 70, sold: 244 },\n  { id: \"p-03\", name: \"Waxed canvas tote\", category: \"Bags\", price: 96, stock: 38, status: \"active\", hue: 150, sold: 301 },\n  { id: \"p-04\", name: \"Linen desk mat\", category: \"Desk\", price: 68, stock: 55, status: \"active\", hue: 40, sold: 187 },\n  { id: \"p-05\", name: \"Graphite pencils, 12\", category: \"Writing\", price: 16, stock: 260, status: \"active\", hue: 260, sold: 1204 },\n  { id: \"p-06\", name: \"Ceramic ink well\", category: \"Desk\", price: 42, stock: 0, status: \"active\", hue: 200, sold: 98 },\n  { id: \"p-07\", name: \"Leather pen roll\", category: \"Bags\", price: 74, stock: 21, status: \"active\", hue: 30, sold: 156 },\n  { id: \"p-08\", name: \"Stitched journal, A5\", category: \"Paper\", price: 32, stock: 9, status: \"active\", hue: 120, sold: 530 },\n  { id: \"p-09\", name: \"Wooden letter tray\", category: \"Desk\", price: 54, stock: 33, status: \"draft\", hue: 55, sold: 0 },\n  { id: \"p-10\", name: \"Wax seal kit\", category: \"Paper\", price: 28, stock: 64, status: \"active\", hue: 15, sold: 219 },\n  { id: \"p-11\", name: \"Fountain pen, steel\", category: \"Writing\", price: 120, stock: 12, status: \"active\", hue: 230, sold: 143 },\n  { id: \"p-12\", name: \"Travel ink, three colours\", category: \"Writing\", price: 36, stock: 5, status: \"active\", hue: 300, sold: 267 },\n]\n\nexport const customers: AtlasCustomer[] = [\n  { id: \"c-01\", name: \"Amara Okafor\", email: \"amara@fernhill.co\", city: \"Lagos\", segment: \"VIP\", tone: 1 },\n  { id: \"c-02\", name: \"Lucas Meyer\", email: \"lucas.meyer@mail.de\", city: \"Berlin\", segment: \"Regular\", tone: 2 },\n  { id: \"c-03\", name: \"Sofia Alvarez\", email: \"sofia@alvarez.studio\", city: \"Mexico City\", segment: \"VIP\", tone: 3 },\n  { id: \"c-04\", name: \"Hana Kobayashi\", email: \"hana.k@tokyo.jp\", city: \"Tokyo\", segment: \"New\", tone: 4 },\n  { id: \"c-05\", name: \"Daniel Reyes\", email: \"dreyes@harbor.io\", city: \"Manila\", segment: \"At risk\", tone: 5 },\n  { id: \"c-06\", name: \"Priya Raman\", email: \"priya@raman.in\", city: \"Chennai\", segment: \"Regular\", tone: 1 },\n  { id: \"c-07\", name: \"Marcus Lindqvist\", email: \"marcus@fjord.se\", city: \"Stockholm\", segment: \"VIP\", tone: 2 },\n  { id: \"c-08\", name: \"Elena Rossi\", email: \"elena@rossi.it\", city: \"Milan\", segment: \"Regular\", tone: 3 },\n  { id: \"c-09\", name: \"Tomás Beltrán\", email: \"tomas@beltran.ar\", city: \"Buenos Aires\", segment: \"New\", tone: 4 },\n  { id: \"c-10\", name: \"Grace Mutasa\", email: \"grace@mutasa.zw\", city: \"Harare\", segment: \"At risk\", tone: 5 },\n  { id: \"c-11\", name: \"Noah Fischer\", email: \"noah.fischer@web.ch\", city: \"Zürich\", segment: \"Regular\", tone: 1 },\n  { id: \"c-12\", name: \"Yuki Tanaka\", email: \"yuki@tanaka.jp\", city: \"Osaka\", segment: \"VIP\", tone: 2 },\n]\n\nconst channels: AtlasChannel[] = [\"Online store\", \"Online store\", \"Instagram\", \"Online store\", \"Wholesale\", \"Pop-up\", \"Online store\", \"Instagram\"]\nconst payments: AtlasPayment[] = [\"paid\", \"paid\", \"paid\", \"pending\", \"paid\", \"paid\", \"refunded\", \"paid\", \"paid\", \"pending\"]\nconst fulfilments: AtlasFulfillment[] = [\"delivered\", \"shipped\", \"unfulfilled\", \"delivered\", \"unfulfilled\", \"shipped\", \"delivered\", \"unfulfilled\", \"delivered\", \"shipped\"]\n\n/** 36 orders over three weeks, newest first. Built from fixed arithmetic so the server and browser agree. */\nexport const orders: AtlasOrder[] = Array.from({ length: 36 }, (_, i) => {\n  const n = 10480 - i\n  const day = 29 - Math.floor(i / 2)\n  const hour = i % 2 === 0 ? 14 + (i % 5) : 8 + (i % 4)\n  const minute = (i * 13) % 60\n  const lines = 1 + (i % 3)\n  const age = i < 6 ? 0 : i < 14 ? 1 : 2\n  const payment = payments[i % payments.length]\n  const fulfillment: AtlasFulfillment = payment === \"pending\" ? \"unfulfilled\" : age === 0 ? (i % 2 ? \"unfulfilled\" : \"shipped\") : fulfilments[i % fulfilments.length]\n  return {\n    id: `ORD-${n}`,\n    date: `2026-09-${String(day).padStart(2, \"0\")}T${String(hour).padStart(2, \"0\")}:${String(minute).padStart(2, \"0\")}:00Z`,\n    customerId: customers[(i * 5 + 1) % customers.length].id,\n    channel: channels[i % channels.length],\n    payment,\n    fulfillment,\n    items: Array.from({ length: lines }, (_, k) => ({ productId: products[(i * 3 + k * 4) % products.length].id, qty: 1 + ((i + k) % 3) })),\n  }\n})\n\nexport const getCustomer = (id: string) => customers.find((c) => c.id === id) ?? customers[0]\nexport const getProduct = (id: string) => products.find((p) => p.id === id) ?? products[0]\nexport const orderTotal = (o: AtlasOrder) => o.items.reduce((n, l) => n + getProduct(l.productId).price * l.qty, 0)\nexport const orderShipping = (o: AtlasOrder) => (orderTotal(o) >= 100 ? 0 : 8)\nexport const orderGrand = (o: AtlasOrder) => orderTotal(o) + orderShipping(o)\n\nexport const money = new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency: \"USD\", maximumFractionDigits: 0 })\nexport const moneyExact = new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency: \"USD\", minimumFractionDigits: 2 })\nconst dateFmt = new Intl.DateTimeFormat(\"en-US\", { month: \"short\", day: \"numeric\", timeZone: \"UTC\" })\nconst timeFmt = new Intl.DateTimeFormat(\"en-US\", { hour: \"numeric\", minute: \"2-digit\", timeZone: \"UTC\" })\nexport const formatDate = (iso: string) => dateFmt.format(new Date(iso))\nexport const formatTime = (iso: string) => timeFmt.format(new Date(iso))\n\nexport const initials = (name: string) => name.split(\" \").map((w) => w[0]).join(\"\").slice(0, 2)\n\n/** Thirty daily revenue figures, the last one being today. */\nexport const revenueSeries = Array.from({ length: 30 }, (_, i) => {\n  const base = 3200 + i * 46 + Math.round(Math.sin(i * 0.9) * 520) + (i % 7 === 5 ? 700 : 0)\n  const previous = 2900 + i * 30 + Math.round(Math.cos(i * 0.8) * 430)\n  return { day: `Sep ${i + 1}`, revenue: base, previous }\n})\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/atlas/atlas-data.ts"
    },
    {
      "path": "registry/ballmac/components/templates/atlas/atlas-theme.tsx",
      "content": "// Ballmac UI: Atlas template shell. https://ui.ballmac.com/templates/template-atlas\n\"use client\"\n\nimport * as React from \"react\"\nimport { Bell, ChevronsUpDown, CircleHelp, LayoutDashboard, Menu, Package, Receipt, Search, Settings, Users } from \"lucide-react\"\n\nimport { Badge } from \"@/components/ballmac/badge\"\nimport { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandShortcut } from \"@/components/ballmac/command\"\nimport { Sheet, SheetContent, SheetDescription, SheetTitle } from \"@/components/ballmac/sheet\"\nimport { atlasMono, atlasSans } from \"@/components/ballmac/templates/atlas/atlas-fonts\"\nimport { cn } from \"@/lib/utils\"\n\ntype AtlasPage = \"dashboard\" | \"orders\" | \"products\" | \"customers\" | \"settings\"\n/** Where each page lives. `order` is the order detail page. */\ntype AtlasHrefs = Record<AtlasPage | \"order\", string>\n\nconst defaultHrefs: AtlasHrefs = {\n  dashboard: \"/atlas\",\n  orders: \"/atlas/orders\",\n  order: \"/atlas/orders/ORD-10479\",\n  products: \"/atlas/products\",\n  customers: \"/atlas/customers\",\n  settings: \"/atlas/settings\",\n}\n\n/** Atlas's palette: warm stone neutrals with one teal. Dense, quiet and easy to read all day. */\nconst atlasCss = `\n.atlas-theme,body:not(:has([data-gallery])):has(.atlas-theme){--background:oklch(0.985 0.004 90);--foreground:oklch(0.2 0.012 80);--card:oklch(1 0 0);--card-foreground:oklch(0.2 0.012 80);--popover:oklch(1 0 0);--popover-foreground:oklch(0.2 0.012 80);--primary:oklch(0.26 0.012 80);--primary-foreground:oklch(0.985 0.004 90);--secondary:oklch(0.955 0.006 90);--secondary-foreground:oklch(0.2 0.012 80);--muted:oklch(0.955 0.006 90);--muted-foreground:oklch(0.47 0.015 85);--accent:oklch(0.94 0.008 90);--accent-foreground:oklch(0.2 0.012 80);--border:oklch(0.2 0.012 80 / 11%);--input:oklch(0.2 0.012 80 / 16%);--ring:oklch(0.5 0.1 190);--surface:oklch(0.968 0.006 90);--destructive:oklch(0.55 0.21 27);--chart-1:oklch(0.5 0.1 190);--chart-2:oklch(0.5 0.13 150);--chart-3:oklch(0.62 0.14 70);--chart-4:oklch(0.5 0.14 320);--chart-5:oklch(0.52 0.12 250);--radius:0.625rem}\n.dark .atlas-theme,.dark body:not(:has([data-gallery])):has(.atlas-theme){--background:oklch(0.165 0.008 80);--foreground:oklch(0.95 0.006 90);--card:oklch(0.2 0.009 80);--card-foreground:oklch(0.95 0.006 90);--popover:oklch(0.22 0.01 80);--popover-foreground:oklch(0.95 0.006 90);--primary:oklch(0.95 0.006 90);--primary-foreground:oklch(0.2 0.012 80);--secondary:oklch(0.25 0.01 80);--secondary-foreground:oklch(0.95 0.006 90);--muted:oklch(0.24 0.01 80);--muted-foreground:oklch(0.72 0.015 85);--accent:oklch(0.27 0.012 80);--accent-foreground:oklch(0.95 0.006 90);--border:oklch(1 0 0 / 10%);--input:oklch(1 0 0 / 14%);--ring:oklch(0.74 0.11 190);--surface:oklch(0.185 0.009 80);--destructive:oklch(0.7 0.19 25);--chart-1:oklch(0.78 0.11 190);--chart-2:oklch(0.78 0.14 150);--chart-3:oklch(0.82 0.14 80);--chart-4:oklch(0.76 0.13 320);--chart-5:oklch(0.76 0.11 250)}\n`\n\n/** Dialogs, sheets and menus render in a portal on <body>, so the font is set there too while an Atlas page is mounted. */\nconst atlasBodyCss = `body:not(:has([data-gallery])):has(.atlas-theme){font-family:var(--atlas-sans),ui-sans-serif,system-ui,sans-serif}`\n\nconst nav: { key: AtlasPage; label: string; icon: typeof Receipt; badge?: string }[] = [\n  { key: \"dashboard\", label: \"Dashboard\", icon: LayoutDashboard },\n  { key: \"orders\", label: \"Orders\", icon: Receipt, badge: \"8\" },\n  { key: \"products\", label: \"Products\", icon: Package },\n  { key: \"customers\", label: \"Customers\", icon: Users },\n  { key: \"settings\", label: \"Settings\", icon: Settings },\n]\n\nfunction AtlasMark({ className }: { className?: string }) {\n  return (\n    <svg viewBox=\"0 0 28 28\" aria-hidden=\"true\" className={cn(\"size-7\", className)} fill=\"none\">\n      <rect width=\"28\" height=\"28\" rx=\"8\" fill=\"var(--chart-1)\" />\n      <path d=\"M8 19 14 8l6 11M10.5 15h7\" stroke=\"var(--card)\" strokeWidth=\"2.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n    </svg>\n  )\n}\n\nfunction SidebarNav({ page, hrefs, onNavigate }: { page: AtlasPage; hrefs: AtlasHrefs; onNavigate?: () => void }) {\n  return (\n    <div className=\"flex h-full flex-col\">\n      <div className=\"flex h-14 items-center gap-2.5 px-4\">\n        <AtlasMark />\n        <div className=\"leading-tight\">\n          <p className=\"text-sm font-bold tracking-tight\">Atlas</p>\n          <p className=\"text-muted-foreground text-[11px]\">Fieldnote Goods</p>\n        </div>\n      </div>\n      <nav aria-label=\"Main\" className=\"flex-1 space-y-0.5 px-2.5 py-3\">\n        {nav.map((n) => (\n          <a\n            key={n.key}\n            href={hrefs[n.key]}\n            onClick={onNavigate}\n            aria-current={page === n.key ? \"page\" : undefined}\n            className=\"text-muted-foreground hover:text-foreground hover:bg-accent aria-[current=page]:bg-accent aria-[current=page]:text-foreground focus-visible:ring-ring/50 flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium outline-none transition-colors focus-visible:ring-[3px]\"\n          >\n            <n.icon className=\"size-4\" aria-hidden=\"true\" />\n            {n.label}\n            {n.badge && <span className=\"bg-chart-1 text-primary-foreground ms-auto rounded-full px-1.5 text-[11px] font-semibold tabular-nums\">{n.badge}</span>}\n          </a>\n        ))}\n      </nav>\n      <div className=\"border-t p-2.5\">\n        <button type=\"button\" className=\"hover:bg-accent focus-visible:ring-ring/50 flex w-full items-center gap-2.5 rounded-lg p-2 text-start outline-none focus-visible:ring-[3px]\">\n          <span className=\"bg-chart-4/25 flex size-8 items-center justify-center rounded-full text-xs font-bold\" aria-hidden=\"true\">MK</span>\n          <span className=\"min-w-0 flex-1 leading-tight\">\n            <span className=\"block truncate text-sm font-semibold\">Mina Kovac</span>\n            <span className=\"text-muted-foreground block truncate text-xs\">Owner</span>\n          </span>\n          <ChevronsUpDown className=\"text-muted-foreground size-4\" aria-hidden=\"true\" />\n        </button>\n      </div>\n    </div>\n  )\n}\n\ntype AtlasShellProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n  /** The page being shown, so its nav link is marked current. */\n  page: AtlasPage\n  /** Page title shown in the top bar. */\n  title: string\n  /** Override where pages live (used by previews). */\n  hrefs?: Partial<AtlasHrefs>\n  /** Controls on the right of the top bar. */\n  actions?: React.ReactNode\n}\n\n/** Atlas's frame: sidebar, top bar with a command menu (⌘K), and a drawer on phones. Wrap every Atlas page in it. */\nfunction AtlasShell({ page, title, hrefs: overrides, actions, className, style, children, ...props }: AtlasShellProps) {\n  const hrefs = { ...defaultHrefs, ...overrides }\n  const [drawer, setDrawer] = React.useState(false)\n  const [palette, setPalette] = React.useState(false)\n  // Portaled UI (command menu, sheet) lives outside this element; give <body> the font variables too.\n  React.useEffect(() => {\n    const classes = [atlasSans.variable, atlasMono.variable].filter(Boolean)\n    document.body.classList.add(...classes)\n    return () => document.body.classList.remove(...classes)\n  }, [])\n  return (\n    <div\n      data-slot=\"atlas\"\n      className={cn(\"atlas-theme bg-background text-foreground relative min-h-dvh lg:grid lg:grid-cols-[15rem_minmax(0,1fr)]\", atlasSans.variable, atlasMono.variable, className)}\n      style={{ fontFamily: \"var(--atlas-sans), ui-sans-serif, system-ui, sans-serif\", ...style }}\n      {...props}\n    >\n      <style>{atlasCss + atlasBodyCss}</style>\n      <aside className=\"bg-surface sticky top-0 hidden h-dvh border-e lg:block\">\n        <SidebarNav page={page} hrefs={hrefs} />\n      </aside>\n      <Sheet open={drawer} onOpenChange={setDrawer}>\n        <SheetContent side=\"start\" className=\"w-72 p-0\" closeLabel=\"Close navigation\">\n          <SheetTitle className=\"sr-only\">Navigation</SheetTitle>\n          <SheetDescription className=\"sr-only\">Pages in the Fieldnote Goods store</SheetDescription>\n          <SidebarNav page={page} hrefs={hrefs} onNavigate={() => setDrawer(false)} />\n        </SheetContent>\n      </Sheet>\n\n      <div className=\"min-w-0\">\n        <header className=\"bg-background/85 sticky top-0 z-30 flex h-14 items-center gap-3 border-b px-4 backdrop-blur-lg sm:px-6\">\n          <button type=\"button\" aria-label=\"Open navigation\" onClick={() => setDrawer(true)} className=\"hover:bg-accent focus-visible:ring-ring/50 -ms-1 inline-flex size-9 items-center justify-center rounded-lg outline-none focus-visible:ring-[3px] lg:hidden\">\n            <Menu className=\"size-5\" aria-hidden=\"true\" />\n          </button>\n          <h1 className=\"min-w-0 truncate text-[15px] font-bold tracking-tight\">{title}</h1>\n          <div className=\"ms-auto flex items-center gap-2\">\n            <button\n              type=\"button\"\n              onClick={() => setPalette(true)}\n              className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 bg-card flex h-9 items-center gap-2 rounded-lg border px-2.5 text-sm outline-none transition-colors focus-visible:ring-[3px] sm:w-56\"\n            >\n              <Search className=\"size-4\" aria-hidden=\"true\" />\n              <span className=\"hidden flex-1 text-start sm:block\">Search</span>\n              <kbd className=\"bg-muted hidden rounded px-1.5 py-0.5 text-[11px] sm:block\" style={{ fontFamily: \"var(--atlas-mono)\" }}>⌘K</kbd>\n              <span className=\"sr-only sm:hidden\">Search the store</span>\n            </button>\n            {actions}\n            <button type=\"button\" aria-label=\"Notifications, 3 unread\" className=\"hover:bg-accent focus-visible:ring-ring/50 relative inline-flex size-9 items-center justify-center rounded-lg outline-none focus-visible:ring-[3px]\">\n              <Bell className=\"size-[18px]\" aria-hidden=\"true\" />\n              <span className=\"bg-chart-3 absolute top-2 end-2.5 size-1.5 rounded-full\" aria-hidden=\"true\" />\n            </button>\n          </div>\n        </header>\n        {children}\n      </div>\n\n      <CommandDialog open={palette} onOpenChange={setPalette} title=\"Search the store\">\n        <CommandInput placeholder=\"Search pages, orders, products…\" />\n        <CommandList>\n          <CommandEmpty>No results.</CommandEmpty>\n          <CommandGroup heading=\"Go to\">\n            {nav.map((n) => (\n              <CommandItem key={n.key} value={n.label} onSelect={() => { setPalette(false); window.location.assign(hrefs[n.key]) }}>\n                <n.icon aria-hidden=\"true\" /> {n.label}\n              </CommandItem>\n            ))}\n          </CommandGroup>\n          <CommandGroup heading=\"Recent orders\">\n            {[\"ORD-10479\", \"ORD-10479\", \"ORD-10476\"].map((id) => (\n              <CommandItem key={id} value={id} onSelect={() => { setPalette(false); window.location.assign(hrefs.order) }}>\n                <Receipt aria-hidden=\"true\" /> {id}\n                <CommandShortcut>Order</CommandShortcut>\n              </CommandItem>\n            ))}\n          </CommandGroup>\n          <CommandGroup heading=\"Help\">\n            <CommandItem value=\"Help center\"><CircleHelp aria-hidden=\"true\" /> Help center</CommandItem>\n          </CommandGroup>\n        </CommandList>\n      </CommandDialog>\n    </div>\n  )\n}\n\n/** The heading row of a page: title text, a description and actions. */\nfunction AtlasPageHeader({ title, description, children }: { title: string; description?: string; children?: React.ReactNode }) {\n  return (\n    <div className=\"flex flex-wrap items-end justify-between gap-4\">\n      <div className=\"min-w-0\">\n        <h2 className=\"text-2xl font-extrabold tracking-[-0.03em]\">{title}</h2>\n        {description && <p className=\"text-muted-foreground mt-1 text-sm text-pretty\">{description}</p>}\n      </div>\n      {children && <div className=\"flex flex-wrap items-center gap-2\">{children}</div>}\n    </div>\n  )\n}\n\nconst buttonBase = \"focus-visible:ring-ring/50 inline-flex h-9 shrink-0 items-center justify-center gap-2 rounded-lg px-3.5 text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4\"\n/** Class names for Atlas buttons, so links and buttons match. */\nconst atlasButton = {\n  primary: cn(buttonBase, \"bg-primary text-primary-foreground hover:opacity-90\"),\n  outline: cn(buttonBase, \"bg-card hover:bg-accent border\"),\n  ghost: cn(buttonBase, \"hover:bg-accent\"),\n  danger: cn(buttonBase, \"bg-card text-destructive border-destructive/40 hover:bg-destructive/10 border\"),\n}\n\nconst paymentTone = { paid: \"success\", pending: \"warning\", refunded: \"neutral\" } as const\nconst fulfilmentTone = { unfulfilled: \"warning\", shipped: \"neutral\", delivered: \"success\" } as const\nconst label = (s: string) => s[0].toUpperCase() + s.slice(1)\n\n/** Payment status as a badge. */\nfunction PaymentBadge({ value }: { value: keyof typeof paymentTone }) {\n  return <Badge status={paymentTone[value]} variant=\"outline\">{label(value)}</Badge>\n}\n/** Fulfilment status as a badge. */\nfunction FulfilmentBadge({ value }: { value: keyof typeof fulfilmentTone }) {\n  return <Badge status={fulfilmentTone[value]} variant=\"outline\">{label(value)}</Badge>\n}\n\n/** A small painted tile standing in for a product photo. */\nfunction ProductTile({ hue, className }: { hue: number; className?: string }) {\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={cn(\"relative block overflow-hidden rounded-lg\", className)}\n      style={{ background: `linear-gradient(135deg, oklch(0.9 0.06 ${hue}), oklch(0.78 0.1 ${hue + 30}))` }}\n    >\n      <span className=\"absolute inset-x-[22%] bottom-0 h-[58%] rounded-t-md\" style={{ background: `oklch(0.55 0.1 ${hue} / 0.55)` }} />\n    </span>\n  )\n}\n\nexport { AtlasMark, AtlasPageHeader, AtlasShell, FulfilmentBadge, PaymentBadge, ProductTile, atlasButton, defaultHrefs as atlasDefaultHrefs, type AtlasHrefs, type AtlasPage, type AtlasShellProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/atlas/atlas-theme.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/atlas/atlas-dashboard.tsx",
      "content": "// Ballmac UI: Atlas dashboard page. https://ui.ballmac.com/templates/template-atlas\n\"use client\"\n\nimport * as React from \"react\"\nimport { AlertTriangle, ArrowDownRight, ArrowRight, ArrowUpRight, Download, PackageX, Truck } from \"lucide-react\"\nimport { Area, AreaChart, CartesianGrid, XAxis, YAxis } from \"recharts\"\n\nimport { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, type ChartConfig } from \"@/components/ballmac/chart\"\nimport { SegmentedControl, SegmentedControlItem } from \"@/components/ballmac/segmented-control\"\nimport { Sparkline } from \"@/components/ballmac/sparkline\"\nimport { formatDate, getCustomer, initials, money, moneyExact, orderGrand, orders, products, revenueSeries } from \"@/components/ballmac/templates/atlas/atlas-data\"\nimport { AtlasPageHeader, AtlasShell, FulfilmentBadge, PaymentBadge, ProductTile, atlasButton, type AtlasHrefs } from \"@/components/ballmac/templates/atlas/atlas-theme\"\nimport { cn } from \"@/lib/utils\"\n\ntype Range = \"7\" | \"30\"\n\nconst config = { revenue: { label: \"This period\", color: \"var(--chart-1)\" }, previous: { label: \"Previous period\", color: \"var(--chart-3)\" } } satisfies ChartConfig\n\nconst sum = (xs: number[]) => xs.reduce((a, b) => a + b, 0)\nconst pct = (a: number, b: number) => ((a - b) / b) * 100\n\ntype AtlasDashboardProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<AtlasHrefs> }\n\n/** The Atlas dashboard: a date range that recomputes the KPIs and chart, work that needs attention, top products and recent orders. */\nfunction AtlasDashboard({ hrefs, ...props }: AtlasDashboardProps) {\n  const [range, setRange] = React.useState<Range>(\"30\")\n  const n = Number(range)\n  const data = revenueSeries.slice(-n)\n  const revenue = sum(data.map((d) => d.revenue))\n  const previous = sum(data.map((d) => d.previous))\n  const orderCount = Math.round(revenue / 71)\n  const prevOrders = Math.round(previous / 69)\n  const conversion = 2.1 + n * 0.004\n  const aov = revenue / orderCount\n\n  const kpis = [\n    { label: \"Revenue\", value: money.format(revenue), delta: pct(revenue, previous), trend: data.map((d) => d.revenue) },\n    { label: \"Orders\", value: orderCount.toLocaleString(\"en-US\"), delta: pct(orderCount, prevOrders), trend: data.map((d) => Math.round(d.revenue / 70)) },\n    { label: \"Conversion\", value: `${conversion.toFixed(1)}%`, delta: 4.2, trend: data.map((d, i) => 2 + Math.sin(i * 0.5) * 0.3 + i * 0.01) },\n    { label: \"Average order\", value: moneyExact.format(aov), delta: -1.8, trend: data.map((d, i) => 70 + Math.cos(i * 0.7) * 6 - i * 0.1) },\n  ]\n\n  const unfulfilled = orders.filter((o) => o.fulfillment === \"unfulfilled\" && o.payment !== \"pending\").length\n  const pending = orders.filter((o) => o.payment === \"pending\").length\n  const low = products.filter((p) => p.stock <= 10 && p.status === \"active\")\n  const top = [...products].filter((p) => p.status === \"active\").sort((a, b) => b.sold * b.price - a.sold * a.price).slice(0, 5)\n  const topMax = top[0].sold * top[0].price\n  const recent = orders.slice(0, 6)\n  const h = { orders: hrefs?.orders ?? \"/atlas/orders\", order: hrefs?.order ?? \"/atlas/orders/ORD-10479\", products: hrefs?.products ?? \"/atlas/products\" }\n\n  return (\n    <AtlasShell\n      page=\"dashboard\"\n      title=\"Dashboard\"\n      hrefs={hrefs}\n      actions={<a href={h.orders} className={cn(atlasButton.primary, \"hidden sm:inline-flex\")}>View orders</a>}\n      {...props}\n    >\n      <main className=\"mx-auto max-w-7xl space-y-6 px-4 py-6 sm:px-6\">\n        <AtlasPageHeader title=\"Good morning, Mina\" description=\"Here is how Fieldnote Goods is doing.\">\n          <SegmentedControl aria-label=\"Date range\" value={range} onValueChange={(v) => setRange(v as Range)} size=\"sm\">\n            <SegmentedControlItem value=\"7\">7 days</SegmentedControlItem>\n            <SegmentedControlItem value=\"30\">30 days</SegmentedControlItem>\n          </SegmentedControl>\n          <button type=\"button\" className={atlasButton.outline}><Download aria-hidden=\"true\" /> Export</button>\n        </AtlasPageHeader>\n\n        <section aria-label=\"Key figures\" className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-4\">\n          {kpis.map((k) => {\n            const up = k.delta >= 0\n            const Arrow = up ? ArrowUpRight : ArrowDownRight\n            return (\n              <div key={k.label} className=\"bg-card rounded-xl border p-4\">\n                <p className=\"text-muted-foreground text-sm font-medium\">{k.label}</p>\n                <div className=\"mt-2 flex items-end justify-between gap-3\">\n                  <p className=\"text-[1.65rem] leading-none font-extrabold tracking-[-0.03em] tabular-nums\">{k.value}</p>\n                  <Sparkline values={k.trend} label={`${k.label} trend over ${n} days`} height={32} showEnd={false} className=\"text-chart-1 w-20 shrink-0\" />\n                </div>\n                <p className=\"mt-3 flex items-center gap-1 text-xs\">\n                  <Arrow className={cn(\"size-3.5\", up ? \"text-chart-2\" : \"text-destructive\")} aria-hidden=\"true\" />\n                  <span className=\"font-semibold tabular-nums\">{Math.abs(k.delta).toFixed(1)}%</span>\n                  <span className=\"text-muted-foreground\">{up ? \"up\" : \"down\"} from the previous {n} days</span>\n                </p>\n              </div>\n            )\n          })}\n        </section>\n\n        <div className=\"grid gap-4 xl:grid-cols-[minmax(0,1fr)_21rem]\">\n          <section aria-labelledby=\"atlas-rev\" className=\"bg-card min-w-0 rounded-xl border p-4 sm:p-5\">\n            <div className=\"flex items-baseline justify-between gap-3\">\n              <h3 id=\"atlas-rev\" className=\"text-sm font-bold\">Revenue</h3>\n              <p className=\"text-muted-foreground text-xs\">Daily, last {n} days</p>\n            </div>\n            <ChartContainer\n              config={config}\n              label={`Daily revenue for the last ${n} days compared with the previous period`}\n              summary={`Revenue totalled ${money.format(revenue)}, ${Math.abs(pct(revenue, previous)).toFixed(1)} percent ${revenue >= previous ? \"above\" : \"below\"} the previous period.`}\n              className=\"mt-4 aspect-[16/7] w-full\"\n            >\n              <AreaChart data={data} margin={{ left: 0, right: 8, top: 8 }} accessibilityLayer>\n                <defs>\n                  <linearGradient id=\"atlas-fill\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                    <stop offset=\"5%\" stopColor=\"var(--color-revenue)\" stopOpacity={0.28} />\n                    <stop offset=\"95%\" stopColor=\"var(--color-revenue)\" stopOpacity={0.02} />\n                  </linearGradient>\n                </defs>\n                <CartesianGrid vertical={false} />\n                <XAxis dataKey=\"day\" tickLine={false} axisLine={false} tickMargin={8} interval={n === 7 ? 0 : 4} />\n                <YAxis tickLine={false} axisLine={false} width={44} tickFormatter={(v: number) => `$${(v / 1000).toFixed(1)}k`} />\n                <ChartTooltip cursor={false} content={<ChartTooltipContent indicator=\"line\" formatter={(v) => money.format(Number(v))} />} />\n                <Area dataKey=\"previous\" type=\"monotone\" fill=\"none\" stroke=\"var(--color-previous)\" strokeWidth={1.5} strokeDasharray=\"4 4\" dot={false} isAnimationActive={false} />\n                <Area dataKey=\"revenue\" type=\"monotone\" fill=\"url(#atlas-fill)\" stroke=\"var(--color-revenue)\" strokeWidth={2} dot={false} isAnimationActive={false} />\n                <ChartLegend content={<ChartLegendContent />} />\n              </AreaChart>\n            </ChartContainer>\n          </section>\n\n          <section aria-labelledby=\"atlas-attn\" className=\"bg-card rounded-xl border\">\n            <h3 id=\"atlas-attn\" className=\"px-4 pt-4 text-sm font-bold sm:px-5\">Needs attention</h3>\n            <ul className=\"mt-2 divide-y\">\n              {[\n                { icon: Truck, text: `${unfulfilled} orders to fulfil`, hint: \"Oldest from 2 days ago\", href: h.orders, tone: \"bg-chart-3/20\" },\n                { icon: AlertTriangle, text: `${pending} payments pending`, hint: \"Bank transfers, 1 to 3 days\", href: h.orders, tone: \"bg-chart-5/20\" },\n                { icon: PackageX, text: `${low.length} products low on stock`, hint: low.slice(0, 2).map((p) => p.name.split(\",\")[0]).join(\", \") + \" and more\", href: h.products, tone: \"bg-destructive/15\" },\n              ].map((a) => (\n                <li key={a.text}>\n                  <a href={a.href} className=\"hover:bg-accent focus-visible:ring-ring/50 flex items-center gap-3 px-4 py-3 outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-inset sm:px-5\">\n                    <span className={cn(\"flex size-9 shrink-0 items-center justify-center rounded-lg\", a.tone)}><a.icon className=\"size-4\" aria-hidden=\"true\" /></span>\n                    <span className=\"min-w-0 flex-1\"><span className=\"block text-sm font-semibold\">{a.text}</span><span className=\"text-muted-foreground block truncate text-xs\">{a.hint}</span></span>\n                    <ArrowRight className=\"text-muted-foreground size-4 rtl:rotate-180\" aria-hidden=\"true\" />\n                  </a>\n                </li>\n              ))}\n            </ul>\n            <div className=\"border-t p-4 sm:p-5\">\n              <h3 className=\"text-sm font-bold\">Top products</h3>\n              <ul className=\"mt-3 space-y-3\">\n                {top.map((p) => (\n                  <li key={p.id} className=\"flex items-center gap-3\">\n                    <ProductTile hue={p.hue} className=\"size-9 shrink-0\" />\n                    <div className=\"min-w-0 flex-1\">\n                      <p className=\"truncate text-sm font-medium\">{p.name}</p>\n                      <div className=\"bg-muted mt-1.5 h-1.5 rounded-full\"><div className=\"bg-chart-1 h-full rounded-full\" style={{ width: `${(p.sold * p.price / topMax) * 100}%` }} /></div>\n                    </div>\n                    <p className=\"text-sm font-semibold tabular-nums\">{money.format(p.sold * p.price)}</p>\n                  </li>\n                ))}\n              </ul>\n            </div>\n          </section>\n        </div>\n\n        <section aria-labelledby=\"atlas-recent\" className=\"bg-card overflow-hidden rounded-xl border\">\n          <div className=\"flex items-center justify-between px-4 py-3.5 sm:px-5\">\n            <h3 id=\"atlas-recent\" className=\"text-sm font-bold\">Recent orders</h3>\n            <a href={h.orders} className=\"text-chart-1 focus-visible:ring-ring/50 rounded text-sm font-semibold underline-offset-4 outline-none hover:underline focus-visible:ring-[3px]\">View all</a>\n          </div>\n          <div tabIndex={0} role=\"region\" aria-label=\"Recent orders\" className=\"focus-visible:ring-ring/50 overflow-x-auto outline-none focus-visible:ring-[3px] focus-visible:ring-inset\">\n            <table className=\"w-full min-w-[40rem] text-start text-sm\">\n              <caption className=\"sr-only\">Most recent orders</caption>\n              <thead>\n                <tr className=\"text-muted-foreground bg-surface border-y text-xs\">\n                  {[\"Order\", \"Date\", \"Customer\", \"Payment\", \"Fulfilment\", \"Total\"].map((c, i) => <th key={c} scope=\"col\" className={cn(\"px-4 py-2.5 font-semibold sm:px-5\", i === 5 && \"text-end\")}>{c}</th>)}\n                </tr>\n              </thead>\n              <tbody className=\"divide-y\">\n                {recent.map((o) => {\n                  const c = getCustomer(o.customerId)\n                  return (\n                    <tr key={o.id} className=\"hover:bg-accent/50\">\n                      <td className=\"px-4 py-3 sm:px-5\"><a href={h.order} className=\"text-chart-1 font-semibold underline-offset-4 hover:underline\" style={{ fontFamily: \"var(--atlas-mono)\" }}>{o.id}</a></td>\n                      <td className=\"text-muted-foreground px-4 py-3 whitespace-nowrap sm:px-5\">{formatDate(o.date)}</td>\n                      <td className=\"px-4 py-3 sm:px-5\"><span className=\"flex items-center gap-2.5\"><span className={cn(\"flex size-7 items-center justify-center rounded-full text-[11px] font-bold\", [\"bg-chart-1/20\", \"bg-chart-2/20\", \"bg-chart-3/25\", \"bg-chart-4/20\", \"bg-chart-5/20\"][c.tone - 1])} aria-hidden=\"true\">{initials(c.name)}</span>{c.name}</span></td>\n                      <td className=\"px-4 py-3 sm:px-5\"><PaymentBadge value={o.payment} /></td>\n                      <td className=\"px-4 py-3 sm:px-5\"><FulfilmentBadge value={o.fulfillment} /></td>\n                      <td className=\"px-4 py-3 text-end font-semibold tabular-nums sm:px-5\">{moneyExact.format(orderGrand(o))}</td>\n                    </tr>\n                  )\n                })}\n              </tbody>\n            </table>\n          </div>\n        </section>\n      </main>\n    </AtlasShell>\n  )\n}\n\nexport { AtlasDashboard, type AtlasDashboardProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/atlas/atlas-dashboard.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/atlas/atlas-orders.tsx",
      "content": "// Ballmac UI: Atlas orders page. https://ui.ballmac.com/templates/template-atlas\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, Download, Printer, Search, Truck, X } from \"lucide-react\"\n\nimport { Checkbox } from \"@/components/ballmac/checkbox\"\nimport { formatDate, formatTime, getCustomer, initials, moneyExact, orderGrand, orders as seed, type AtlasOrder } from \"@/components/ballmac/templates/atlas/atlas-data\"\nimport { AtlasPageHeader, AtlasShell, FulfilmentBadge, PaymentBadge, atlasButton, type AtlasHrefs } from \"@/components/ballmac/templates/atlas/atlas-theme\"\nimport { cn } from \"@/lib/utils\"\n\ntype Tab = \"all\" | \"unfulfilled\" | \"unpaid\" | \"shipped\" | \"delivered\"\ntype SortKey = \"date\" | \"customer\" | \"channel\" | \"total\"\n\nconst tabs: { id: Tab; label: string; test: (o: AtlasOrder) => boolean }[] = [\n  { id: \"all\", label: \"All\", test: () => true },\n  { id: \"unfulfilled\", label: \"Unfulfilled\", test: (o) => o.fulfillment === \"unfulfilled\" },\n  { id: \"unpaid\", label: \"Unpaid\", test: (o) => o.payment === \"pending\" },\n  { id: \"shipped\", label: \"Shipped\", test: (o) => o.fulfillment === \"shipped\" },\n  { id: \"delivered\", label: \"Delivered\", test: (o) => o.fulfillment === \"delivered\" },\n]\n\nconst PAGE = 10\nconst tones = [\"bg-chart-1/20\", \"bg-chart-2/20\", \"bg-chart-3/25\", \"bg-chart-4/20\", \"bg-chart-5/20\"]\n\ntype AtlasOrdersProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<AtlasHrefs> }\n\n/** The Atlas orders page: status tabs with counts, search, sortable columns, row selection with bulk actions, and pagination. */\nfunction AtlasOrders({ hrefs, ...props }: AtlasOrdersProps) {\n  const [rows, setRows] = React.useState<AtlasOrder[]>(seed)\n  const [tab, setTab] = React.useState<Tab>(\"all\")\n  const [query, setQuery] = React.useState(\"\")\n  const [sort, setSort] = React.useState<{ key: SortKey; dir: \"asc\" | \"desc\" }>({ key: \"date\", dir: \"desc\" })\n  const [page, setPage] = React.useState(0)\n  const [selected, setSelected] = React.useState<Set<string>>(new Set())\n  const orderHref = hrefs?.order ?? \"/atlas/orders/ORD-10479\"\n\n  const q = query.trim().toLowerCase()\n  const filtered = rows\n    .filter(tabs.find((t) => t.id === tab)!.test)\n    .filter((o) => !q || [o.id, getCustomer(o.customerId).name, o.channel].join(\" \").toLowerCase().includes(q))\n  const sorted = [...filtered].sort((a, b) => {\n    const dir = sort.dir === \"asc\" ? 1 : -1\n    const val = (o: AtlasOrder) => (sort.key === \"date\" ? o.date : sort.key === \"customer\" ? getCustomer(o.customerId).name : sort.key === \"channel\" ? o.channel : orderGrand(o))\n    const x = val(a)\n    const y = val(b)\n    return (x < y ? -1 : x > y ? 1 : 0) * dir\n  })\n  const pages = Math.max(1, Math.ceil(sorted.length / PAGE))\n  const safePage = Math.min(page, pages - 1)\n  const view = sorted.slice(safePage * PAGE, safePage * PAGE + PAGE)\n  const allOnPage = view.length > 0 && view.every((o) => selected.has(o.id))\n  const someOnPage = view.some((o) => selected.has(o.id))\n\n  function toggleSort(key: SortKey) {\n    setSort((s) => (s.key === key ? { key, dir: s.dir === \"asc\" ? \"desc\" : \"asc\" } : { key, dir: key === \"total\" || key === \"date\" ? \"desc\" : \"asc\" }))\n    setPage(0)\n  }\n  function toggleAll(on: boolean) {\n    setSelected((s) => {\n      const next = new Set(s)\n      for (const o of view) on ? next.add(o.id) : next.delete(o.id)\n      return next\n    })\n  }\n  function toggleOne(id: string, on: boolean) {\n    setSelected((s) => {\n      const next = new Set(s)\n      on ? next.add(id) : next.delete(id)\n      return next\n    })\n  }\n  function markShipped() {\n    setRows((all) => all.map((o) => (selected.has(o.id) && o.fulfillment === \"unfulfilled\" && o.payment !== \"pending\" ? { ...o, fulfillment: \"shipped\" } : o)))\n    setSelected(new Set())\n  }\n\n  const head = (k: SortKey, children: React.ReactNode, right?: boolean) => (\n    <th key={k} scope=\"col\" aria-sort={sort.key === k ? (sort.dir === \"asc\" ? \"ascending\" : \"descending\") : \"none\"} className={cn(\"px-3 py-2.5 font-semibold\", right && \"text-end\")}>\n      <button type=\"button\" onClick={() => toggleSort(k)} className={cn(\"hover:text-foreground focus-visible:ring-ring/50 -mx-1.5 inline-flex items-center gap-1 rounded px-1.5 py-0.5 outline-none focus-visible:ring-[3px]\", sort.key === k && \"text-foreground\")}>\n        {children}\n        {sort.key === k ? (sort.dir === \"asc\" ? <ArrowUp className=\"size-3\" aria-hidden=\"true\" /> : <ArrowDown className=\"size-3\" aria-hidden=\"true\" />) : <ArrowUpDown className=\"size-3 opacity-50\" aria-hidden=\"true\" />}\n      </button>\n    </th>\n  )\n\n  return (\n    <AtlasShell page=\"orders\" title=\"Orders\" hrefs={hrefs} actions={<button type=\"button\" className={cn(atlasButton.outline, \"hidden sm:inline-flex\")}><Download aria-hidden=\"true\" /> Export</button>} {...props}>\n      <main className=\"mx-auto max-w-7xl space-y-5 px-4 py-6 sm:px-6\">\n        <AtlasPageHeader title=\"Orders\" description=\"Everything customers have bought, across every channel.\" />\n\n        <div className=\"bg-card rounded-xl border\">\n          <div className=\"flex flex-col gap-3 border-b p-3 lg:flex-row lg:items-center lg:justify-between\">\n            <div role=\"group\" aria-label=\"Filter by status\" className=\"flex gap-1 overflow-x-auto [scrollbar-width:none]\">\n              {tabs.map((t) => {\n                const count = rows.filter(t.test).length\n                const on = tab === t.id\n                return (\n                  <button\n                    key={t.id}\n                    type=\"button\"\n                    aria-pressed={on}\n                    onClick={() => { setTab(t.id); setPage(0); setSelected(new Set()) }}\n                    className={cn(\"focus-visible:ring-ring/50 flex shrink-0 items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px]\", on ? \"bg-accent text-foreground\" : \"text-muted-foreground hover:text-foreground\")}\n                  >\n                    {t.label} <span className=\"text-muted-foreground text-xs font-medium tabular-nums\">{count}</span>\n                  </button>\n                )\n              })}\n            </div>\n            <div className=\"relative lg:w-72\">\n              <Search className=\"text-muted-foreground pointer-events-none absolute top-1/2 start-3 size-4 -translate-y-1/2\" aria-hidden=\"true\" />\n              <input\n                type=\"search\"\n                aria-label=\"Search orders\"\n                value={query}\n                onChange={(e) => { setQuery(e.target.value); setPage(0) }}\n                placeholder=\"Search order, customer or channel\"\n                className=\"bg-background focus-visible:ring-ring/50 placeholder:text-muted-foreground h-9 w-full rounded-lg border pe-3 ps-9 text-sm outline-none focus-visible:ring-[3px]\"\n              />\n            </div>\n          </div>\n\n          {selected.size > 0 && (\n            <div role=\"region\" aria-label=\"Bulk actions\" className=\"bg-accent flex flex-wrap items-center gap-2 border-b px-3 py-2\">\n              <p className=\"me-auto text-sm font-semibold\" aria-live=\"polite\">{selected.size} selected</p>\n              <button type=\"button\" onClick={markShipped} className={atlasButton.outline}><Truck aria-hidden=\"true\" /> Mark as shipped</button>\n              <button type=\"button\" className={atlasButton.outline}><Printer aria-hidden=\"true\" /> Print labels</button>\n              <button type=\"button\" onClick={() => setSelected(new Set())} aria-label=\"Clear selection\" className={cn(atlasButton.ghost, \"px-2.5\")}><X aria-hidden=\"true\" /></button>\n            </div>\n          )}\n\n          <div tabIndex={0} role=\"region\" aria-label=\"Orders table\" className=\"focus-visible:ring-ring/50 overflow-x-auto outline-none focus-visible:ring-[3px] focus-visible:ring-inset\">\n            <table className=\"w-full min-w-[52rem] text-start text-sm\">\n              <caption className=\"sr-only\">Orders, {sorted.length} shown</caption>\n              <thead>\n                <tr className=\"text-muted-foreground bg-surface border-b text-xs\">\n                  <th scope=\"col\" className=\"w-10 px-3 py-2.5\"><Checkbox aria-label=\"Select all orders on this page\" checked={allOnPage ? true : someOnPage ? \"indeterminate\" : false} onCheckedChange={(v) => toggleAll(v === true)} /></th>\n                  <th scope=\"col\" className=\"px-3 py-2.5 font-semibold\">Order</th>\n                  {head(\"date\", \"Date\")}\n                  {head(\"customer\", \"Customer\")}\n                  {head(\"channel\", \"Channel\")}\n                  <th scope=\"col\" className=\"px-3 py-2.5 font-semibold\">Payment</th>\n                  <th scope=\"col\" className=\"px-3 py-2.5 font-semibold\">Fulfilment</th>\n                  {head(\"total\", \"Total\", true)}\n                </tr>\n              </thead>\n              <tbody className=\"divide-y\">\n                {view.map((o) => {\n                  const c = getCustomer(o.customerId)\n                  const on = selected.has(o.id)\n                  return (\n                    <tr key={o.id} data-state={on ? \"selected\" : undefined} className=\"hover:bg-accent/40 data-[state=selected]:bg-accent/60\">\n                      <td className=\"px-3 py-3\"><Checkbox aria-label={`Select ${o.id}`} checked={on} onCheckedChange={(v) => toggleOne(o.id, v === true)} /></td>\n                      <td className=\"px-3 py-3\"><a href={orderHref} className=\"text-chart-1 font-semibold underline-offset-4 hover:underline\" style={{ fontFamily: \"var(--atlas-mono)\" }}>{o.id}</a></td>\n                      <td className=\"text-muted-foreground px-3 py-3 whitespace-nowrap\">{formatDate(o.date)}<span className=\"ms-1.5 text-xs\">{formatTime(o.date)}</span></td>\n                      <td className=\"px-3 py-3\"><span className=\"flex items-center gap-2.5 whitespace-nowrap\"><span className={cn(\"flex size-7 shrink-0 items-center justify-center rounded-full text-[11px] font-bold\", tones[c.tone - 1])} aria-hidden=\"true\">{initials(c.name)}</span>{c.name}</span></td>\n                      <td className=\"text-muted-foreground px-3 py-3 whitespace-nowrap\">{o.channel}</td>\n                      <td className=\"px-3 py-3\"><PaymentBadge value={o.payment} /></td>\n                      <td className=\"px-3 py-3\"><FulfilmentBadge value={o.fulfillment} /></td>\n                      <td className=\"px-3 py-3 text-end font-semibold tabular-nums\">{moneyExact.format(orderGrand(o))}</td>\n                    </tr>\n                  )\n                })}\n                {view.length === 0 && (\n                  <tr><td colSpan={8} className=\"text-muted-foreground px-3 py-16 text-center\">No orders match. Try another status or clear the search.</td></tr>\n                )}\n              </tbody>\n            </table>\n          </div>\n\n          <div className=\"flex items-center justify-between gap-3 border-t px-3 py-2.5 text-sm\">\n            <p className=\"text-muted-foreground\" role=\"status\">\n              {sorted.length === 0 ? \"No orders\" : `Showing ${safePage * PAGE + 1}–${safePage * PAGE + view.length} of ${sorted.length}`}\n            </p>\n            <div className=\"flex items-center gap-1\">\n              <button type=\"button\" aria-label=\"Previous page\" disabled={safePage === 0} onClick={() => setPage(safePage - 1)} className={cn(atlasButton.outline, \"px-2.5\")}><ChevronLeft aria-hidden=\"true\"  className=\"rtl:rotate-180\"/></button>\n              <span className=\"text-muted-foreground px-2 text-xs tabular-nums\">Page {safePage + 1} of {pages}</span>\n              <button type=\"button\" aria-label=\"Next page\" disabled={safePage >= pages - 1} onClick={() => setPage(safePage + 1)} className={cn(atlasButton.outline, \"px-2.5\")}><ChevronRight aria-hidden=\"true\"  className=\"rtl:rotate-180\"/></button>\n            </div>\n          </div>\n        </div>\n      </main>\n    </AtlasShell>\n  )\n}\n\nexport { AtlasOrders, type AtlasOrdersProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/atlas/atlas-orders.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/atlas/atlas-order.tsx",
      "content": "// Ballmac UI: Atlas order detail page. https://ui.ballmac.com/templates/template-atlas\n\"use client\"\n\nimport * as React from \"react\"\nimport { Check, ChevronRight, CreditCard, Mail, MapPin, MoreHorizontal, Printer, RotateCcw, Truck } from \"lucide-react\"\n\nimport { Checkbox } from \"@/components/ballmac/checkbox\"\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from \"@/components/ballmac/dialog\"\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from \"@/components/ballmac/dropdown-menu\"\nimport { formatDate, formatTime, getCustomer, getProduct, initials, moneyExact, orderShipping, orderTotal, orders } from \"@/components/ballmac/templates/atlas/atlas-data\"\nimport { AtlasShell, FulfilmentBadge, PaymentBadge, ProductTile, atlasButton, type AtlasHrefs } from \"@/components/ballmac/templates/atlas/atlas-theme\"\nimport { cn } from \"@/lib/utils\"\n\ntype Event = { id: string; title: string; detail?: string; at: string }\n\ntype AtlasOrderProps = React.ComponentProps<\"div\"> & {\n  /** Which sample order to show. */\n  orderId?: string\n  hrefs?: Partial<AtlasHrefs>\n}\n\n/** The Atlas order page: items and totals, a live timeline, fulfilment and a refund flow with validation, plus customer and notes. */\nfunction AtlasOrder({ orderId = \"ORD-10479\", hrefs, ...props }: AtlasOrderProps) {\n  const base = orders.find((o) => o.id === orderId) ?? orders[1]\n  const customer = getCustomer(base.customerId)\n  const [fulfillment, setFulfillment] = React.useState(base.fulfillment)\n  const [payment, setPayment] = React.useState(base.payment)\n  const [refunded, setRefunded] = React.useState(0)\n  const [events, setEvents] = React.useState<Event[]>([\n    { id: \"e3\", title: \"Payment captured\", detail: `${moneyExact.format(orderTotal(base) + orderShipping(base))} on Visa ending 4242`, at: base.date },\n    { id: \"e2\", title: \"Order confirmation sent\", detail: `Emailed to ${customer.email}`, at: base.date },\n    { id: \"e1\", title: \"Order placed\", detail: `via ${base.channel}`, at: base.date },\n  ])\n  const [note, setNote] = React.useState(\"Gift wrap please. Leave with the neighbour if nobody is home.\")\n  const [savedNote, setSavedNote] = React.useState(note)\n  const [refundOpen, setRefundOpen] = React.useState(false)\n  const [picked, setPicked] = React.useState<Record<string, boolean>>({})\n  const [reason, setReason] = React.useState(\"\")\n  const [error, setError] = React.useState(\"\")\n  const [announce, setAnnounce] = React.useState(\"\")\n\n  const subtotal = orderTotal(base)\n  const shipping = orderShipping(base)\n  const total = subtotal + shipping\n  const refundAmount = base.items.reduce((n, l) => n + (picked[l.productId] ? getProduct(l.productId).price * l.qty : 0), 0)\n  const stamp = \"2026-09-29T14:20:00Z\"\n\n  function addEvent(title: string, detail?: string) {\n    setEvents((all) => [{ id: `e${all.length + 1}-${title}`, title, detail, at: stamp }, ...all])\n  }\n  function ship() {\n    setFulfillment(\"shipped\")\n    addEvent(\"Marked as shipped\", \"Tracking sent to the customer\")\n    setAnnounce(`${base.id} marked as shipped`)\n  }\n  function refund() {\n    if (refundAmount === 0) return setError(\"Choose at least one item to refund.\")\n    if (!reason) return setError(\"Pick a reason so your team can follow up.\")\n    setRefunded(refundAmount)\n    if (refundAmount === subtotal) setPayment(\"refunded\")\n    addEvent(`Refunded ${moneyExact.format(refundAmount)}`, `Reason: ${reason}`)\n    setAnnounce(`Refunded ${moneyExact.format(refundAmount)}`)\n    setRefundOpen(false)\n    setPicked({})\n    setReason(\"\")\n    setError(\"\")\n  }\n\n  return (\n    <AtlasShell page=\"orders\" title=\"Orders\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-6xl space-y-5 px-4 py-6 sm:px-6\">\n        <nav aria-label=\"Breadcrumb\" className=\"text-muted-foreground flex items-center gap-1 text-sm\">\n          <a href={hrefs?.orders ?? \"/atlas/orders\"} className=\"hover:text-foreground focus-visible:ring-ring/50 rounded outline-none focus-visible:ring-[3px]\">Orders</a>\n          <ChevronRight className=\"size-3.5 rtl:rotate-180\" aria-hidden=\"true\" />\n          <span className=\"text-foreground font-medium\" aria-current=\"page\">{base.id}</span>\n        </nav>\n\n        <div className=\"flex flex-wrap items-start justify-between gap-4\">\n          <div>\n            <div className=\"flex flex-wrap items-center gap-2.5\">\n              <h2 className=\"text-2xl font-extrabold tracking-[-0.03em]\" style={{ fontFamily: \"var(--atlas-mono)\" }}>{base.id}</h2>\n              <PaymentBadge value={payment} />\n              <FulfilmentBadge value={fulfillment} />\n            </div>\n            <p className=\"text-muted-foreground mt-1.5 text-sm\">{formatDate(base.date)} at {formatTime(base.date)} · {base.channel}</p>\n          </div>\n          <div className=\"flex flex-wrap items-center gap-2\">\n            <button type=\"button\" className={atlasButton.outline} onClick={() => setRefundOpen(true)} disabled={payment === \"refunded\"}><RotateCcw aria-hidden=\"true\" /> Refund</button>\n            {fulfillment === \"unfulfilled\" && payment !== \"pending\" ? (\n              <button type=\"button\" className={atlasButton.primary} onClick={ship}><Truck aria-hidden=\"true\" /> Mark as shipped</button>\n            ) : (\n              <button type=\"button\" className={atlasButton.outline}><Printer aria-hidden=\"true\" /> Print packing slip</button>\n            )}\n            <DropdownMenu>\n              <DropdownMenuTrigger className={cn(atlasButton.outline, \"px-2.5\")} aria-label=\"More actions\"><MoreHorizontal aria-hidden=\"true\" /></DropdownMenuTrigger>\n              <DropdownMenuContent align=\"end\">\n                <DropdownMenuItem>Duplicate order</DropdownMenuItem>\n                <DropdownMenuItem>Download invoice</DropdownMenuItem>\n                <DropdownMenuSeparator />\n                <DropdownMenuItem destructive>Cancel order</DropdownMenuItem>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </div>\n        </div>\n        <p role=\"status\" className=\"sr-only\">{announce}</p>\n\n        <div className=\"grid gap-4 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n          <div className=\"min-w-0 space-y-4\">\n            <section aria-labelledby=\"ao-items\" className=\"bg-card rounded-xl border\">\n              <h3 id=\"ao-items\" className=\"px-5 pt-4 text-sm font-bold\">Items</h3>\n              <ul className=\"divide-y px-5\">\n                {base.items.map((l) => {\n                  const p = getProduct(l.productId)\n                  return (\n                    <li key={l.productId} className=\"flex items-center gap-4 py-4\">\n                      <ProductTile hue={p.hue} className=\"size-14 shrink-0\" />\n                      <div className=\"min-w-0 flex-1\">\n                        <p className=\"truncate font-semibold\">{p.name}</p>\n                        <p className=\"text-muted-foreground text-sm\">{p.category} · {moneyExact.format(p.price)} each</p>\n                      </div>\n                      <p className=\"text-muted-foreground text-sm tabular-nums\">× {l.qty}</p>\n                      <p className=\"w-20 text-end font-semibold tabular-nums\">{moneyExact.format(p.price * l.qty)}</p>\n                    </li>\n                  )\n                })}\n              </ul>\n              <dl className=\"bg-surface space-y-2 rounded-b-xl border-t px-5 py-4 text-sm\">\n                <div className=\"flex justify-between\"><dt className=\"text-muted-foreground\">Subtotal</dt><dd className=\"tabular-nums\">{moneyExact.format(subtotal)}</dd></div>\n                <div className=\"flex justify-between\"><dt className=\"text-muted-foreground\">Shipping</dt><dd className=\"tabular-nums\">{shipping === 0 ? \"Free\" : moneyExact.format(shipping)}</dd></div>\n                {refunded > 0 && <div className=\"flex justify-between\"><dt className=\"text-muted-foreground\">Refunded</dt><dd className=\"text-destructive tabular-nums\">−{moneyExact.format(refunded)}</dd></div>}\n                <div className=\"flex justify-between border-t pt-3 text-base font-bold\"><dt>Total</dt><dd className=\"tabular-nums\">{moneyExact.format(total - refunded)}</dd></div>\n              </dl>\n            </section>\n\n            <section aria-labelledby=\"ao-timeline\" className=\"bg-card rounded-xl border p-5\">\n              <h3 id=\"ao-timeline\" className=\"text-sm font-bold\">Timeline</h3>\n              <ol className=\"mt-4\">\n                {events.map((e, i) => (\n                  <li key={e.id} className=\"relative flex gap-3.5 pb-5 last:pb-0\">\n                    {i < events.length - 1 && <span className=\"bg-border absolute top-5 bottom-0 start-[7px] w-px\" aria-hidden=\"true\" />}\n                    <span className={cn(\"relative mt-1 size-3.5 shrink-0 rounded-full border-2\", i === 0 ? \"border-chart-1 bg-chart-1\" : \"bg-card border-muted-foreground/50\")} aria-hidden=\"true\" />\n                    <div className=\"min-w-0 flex-1\">\n                      <p className=\"text-sm font-semibold\">{e.title}</p>\n                      {e.detail && <p className=\"text-muted-foreground text-sm text-pretty\">{e.detail}</p>}\n                    </div>\n                    <p className=\"text-muted-foreground shrink-0 text-xs tabular-nums\">{formatDate(e.at)}, {formatTime(e.at)}</p>\n                  </li>\n                ))}\n              </ol>\n            </section>\n          </div>\n\n          <aside className=\"space-y-4\">\n            <section aria-labelledby=\"ao-customer\" className=\"bg-card rounded-xl border p-5\">\n              <h3 id=\"ao-customer\" className=\"text-sm font-bold\">Customer</h3>\n              <div className=\"mt-4 flex items-center gap-3\">\n                <span className=\"bg-chart-1/20 flex size-11 items-center justify-center rounded-full text-sm font-bold\" aria-hidden=\"true\">{initials(customer.name)}</span>\n                <div className=\"min-w-0\"><p className=\"truncate font-semibold\">{customer.name}</p><p className=\"text-muted-foreground text-sm\">{customer.segment === \"VIP\" ? \"VIP customer\" : \"Customer since 2025\"}</p></div>\n              </div>\n              <a href={`mailto:${customer.email}`} className=\"text-chart-1 mt-4 flex items-center gap-2 text-sm underline-offset-4 hover:underline\"><Mail className=\"size-4\" aria-hidden=\"true\" />{customer.email}</a>\n              <p className=\"text-muted-foreground mt-3 flex items-start gap-2 text-sm\"><MapPin className=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />{customer.city}<br />Shipping and billing address match</p>\n              <p className=\"text-muted-foreground mt-3 flex items-center gap-2 text-sm\"><CreditCard className=\"size-4\" aria-hidden=\"true\" />Visa ending 4242</p>\n            </section>\n            <section aria-labelledby=\"ao-notes\" className=\"bg-card rounded-xl border p-5\">\n              <h3 id=\"ao-notes\" className=\"text-sm font-bold\">Notes</h3>\n              <label htmlFor=\"ao-note\" className=\"sr-only\">Order notes</label>\n              <textarea id=\"ao-note\" rows={4} value={note} onChange={(e) => setNote(e.target.value)} className=\"bg-background focus-visible:ring-ring/50 mt-3 w-full resize-none rounded-lg border p-3 text-sm outline-none focus-visible:ring-[3px]\" />\n              <div className=\"mt-3 flex items-center justify-between\">\n                <p className=\"text-muted-foreground flex items-center gap-1.5 text-xs\" role=\"status\">{note === savedNote ? <><Check className=\"size-3.5\" aria-hidden=\"true\" />Saved</> : \"Unsaved changes\"}</p>\n                <button type=\"button\" disabled={note === savedNote} onClick={() => { setSavedNote(note); addEvent(\"Note updated\") }} className={atlasButton.outline}>Save note</button>\n              </div>\n            </section>\n          </aside>\n        </div>\n      </main>\n\n      <Dialog open={refundOpen} onOpenChange={(o) => { setRefundOpen(o); if (!o) setError(\"\") }}>\n        <DialogContent className=\"max-w-md\">\n          <DialogHeader>\n            <DialogTitle>Refund {base.id}</DialogTitle>\n            <DialogDescription>Choose what to send back to the customer. The money returns to their Visa in 5 to 10 days.</DialogDescription>\n          </DialogHeader>\n          <fieldset className=\"space-y-2\">\n            <legend className=\"sr-only\">Items to refund</legend>\n            {base.items.map((l) => {\n              const p = getProduct(l.productId)\n              return (\n                <label key={l.productId} className=\"hover:bg-accent/50 flex cursor-pointer items-center gap-3 rounded-lg border p-3 text-sm\">\n                  <Checkbox checked={!!picked[l.productId]} onCheckedChange={(v) => setPicked((s) => ({ ...s, [l.productId]: v === true }))} />\n                  <span className=\"min-w-0 flex-1 truncate font-medium\">{p.name} <span className=\"text-muted-foreground font-normal\">× {l.qty}</span></span>\n                  <span className=\"tabular-nums\">{moneyExact.format(p.price * l.qty)}</span>\n                </label>\n              )\n            })}\n          </fieldset>\n          <div>\n            <label htmlFor=\"ao-reason\" className=\"text-sm font-medium\">Reason</label>\n            <select id=\"ao-reason\" value={reason} onChange={(e) => setReason(e.target.value)} className=\"bg-background focus-visible:ring-ring/50 mt-1.5 h-10 w-full rounded-lg border px-3 text-sm outline-none focus-visible:ring-[3px]\">\n              <option value=\"\">Choose a reason</option>\n              <option>Damaged in transit</option>\n              <option>Changed their mind</option>\n              <option>Wrong item sent</option>\n            </select>\n          </div>\n          {error && <p role=\"alert\" className=\"text-destructive text-sm\">{error}</p>}\n          <DialogFooter>\n            <button type=\"button\" className={atlasButton.outline} onClick={() => setRefundOpen(false)}>Cancel</button>\n            <button type=\"button\" className={atlasButton.primary} onClick={refund}>Refund {refundAmount > 0 ? moneyExact.format(refundAmount) : \"\"}</button>\n          </DialogFooter>\n        </DialogContent>\n      </Dialog>\n    </AtlasShell>\n  )\n}\n\nexport { AtlasOrder, type AtlasOrderProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/atlas/atlas-order.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/atlas/atlas-products.tsx",
      "content": "// Ballmac UI: Atlas products page. https://ui.ballmac.com/templates/template-atlas\n\"use client\"\n\nimport * as React from \"react\"\nimport { LayoutGrid, List, Minus, Plus, Search } from \"lucide-react\"\n\nimport { Badge } from \"@/components/ballmac/badge\"\nimport { SegmentedControl, SegmentedControlItem } from \"@/components/ballmac/segmented-control\"\nimport { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from \"@/components/ballmac/sheet\"\nimport { moneyExact, products as seed, type AtlasProduct } from \"@/components/ballmac/templates/atlas/atlas-data\"\nimport { AtlasPageHeader, AtlasShell, ProductTile, atlasButton, type AtlasHrefs } from \"@/components/ballmac/templates/atlas/atlas-theme\"\nimport { cn } from \"@/lib/utils\"\n\ntype View = \"grid\" | \"list\"\n\nfunction Stock({ p }: { p: AtlasProduct }) {\n  if (p.stock === 0) return <Badge status=\"error\" variant=\"outline\">Out of stock</Badge>\n  if (p.stock <= 10) return <Badge status=\"warning\" variant=\"outline\">{p.stock} left</Badge>\n  return <span className=\"text-muted-foreground text-sm tabular-nums\">{p.stock} in stock</span>\n}\n\nconst categories = [\"Paper\", \"Writing\", \"Bags\", \"Desk\"]\nconst fieldClass = \"bg-background focus-visible:ring-ring/50 mt-1.5 h-10 w-full rounded-lg border px-3 text-sm outline-none focus-visible:ring-[3px] aria-[invalid=true]:border-destructive\"\n\ntype AtlasProductsProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<AtlasHrefs> }\n\n/** The Atlas products page: search, category and low-stock filters, grid or list, quick stock edits and an add-product drawer with validation. */\nfunction AtlasProducts({ hrefs, ...props }: AtlasProductsProps) {\n  const [items, setItems] = React.useState<AtlasProduct[]>(seed)\n  const [query, setQuery] = React.useState(\"\")\n  const [category, setCategory] = React.useState(\"all\")\n  const [lowOnly, setLowOnly] = React.useState(false)\n  const [view, setView] = React.useState<View>(\"grid\")\n  const [open, setOpen] = React.useState(false)\n  const [errors, setErrors] = React.useState<{ name?: string; price?: string }>({})\n\n  const q = query.trim().toLowerCase()\n  const shown = items.filter((p) => (category === \"all\" || p.category === category) && (!lowOnly || p.stock <= 10) && (!q || p.name.toLowerCase().includes(q)))\n  const lowCount = items.filter((p) => p.stock <= 10).length\n\n  function adjust(id: string, by: number) {\n    setItems((all) => all.map((p) => (p.id === id ? { ...p, stock: Math.max(0, p.stock + by) } : p)))\n  }\n  function add(e: React.FormEvent<HTMLFormElement>) {\n    e.preventDefault()\n    const f = new FormData(e.currentTarget)\n    const name = String(f.get(\"name\") ?? \"\").trim()\n    const price = Number(f.get(\"price\"))\n    const next: typeof errors = {}\n    if (!name) next.name = \"Give the product a name.\"\n    if (!(price > 0)) next.price = \"Enter a price above $0.\"\n    setErrors(next)\n    if (next.name || next.price) {\n      ;(e.currentTarget.elements.namedItem(next.name ? \"name\" : \"price\") as HTMLElement | null)?.focus()\n      return\n    }\n    setItems((all) => [{ id: `p-new-${all.length}`, name, category: String(f.get(\"category\")), price, stock: Number(f.get(\"stock\")) || 0, status: f.get(\"status\") === \"draft\" ? \"draft\" : \"active\", hue: 180 + all.length * 17, sold: 0 }, ...all])\n    setOpen(false)\n  }\n\n  return (\n    <AtlasShell page=\"products\" title=\"Products\" hrefs={hrefs} actions={<button type=\"button\" onClick={() => setOpen(true)} className={cn(atlasButton.primary, \"hidden sm:inline-flex\")}><Plus aria-hidden=\"true\" /> Add product</button>} {...props}>\n      <main className=\"mx-auto max-w-7xl space-y-5 px-4 py-6 sm:px-6\">\n        <AtlasPageHeader title=\"Products\" description={`${items.length} products · ${lowCount} need restocking`}>\n          <button type=\"button\" onClick={() => setOpen(true)} className={cn(atlasButton.primary, \"sm:hidden\")}><Plus aria-hidden=\"true\" /> Add product</button>\n        </AtlasPageHeader>\n\n        <div className=\"flex flex-wrap items-center gap-2\">\n          <div className=\"relative min-w-52 flex-1 sm:max-w-xs\">\n            <Search className=\"text-muted-foreground pointer-events-none absolute top-1/2 start-3 size-4 -translate-y-1/2\" aria-hidden=\"true\" />\n            <input type=\"search\" aria-label=\"Search products\" value={query} onChange={(e) => setQuery(e.target.value)} placeholder=\"Search products\" className=\"bg-card focus-visible:ring-ring/50 placeholder:text-muted-foreground h-9 w-full rounded-lg border pe-3 ps-9 text-sm outline-none focus-visible:ring-[3px]\" />\n          </div>\n          <select aria-label=\"Category\" value={category} onChange={(e) => setCategory(e.target.value)} className=\"bg-card focus-visible:ring-ring/50 h-9 rounded-lg border px-3 text-sm outline-none focus-visible:ring-[3px]\">\n            <option value=\"all\">All categories</option>\n            {categories.map((c) => <option key={c}>{c}</option>)}\n          </select>\n          <button type=\"button\" aria-pressed={lowOnly} onClick={() => setLowOnly((v) => !v)} className={cn(\"focus-visible:ring-ring/50 h-9 rounded-lg border px-3 text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px]\", lowOnly ? \"bg-accent border-foreground/30\" : \"bg-card hover:bg-accent\")}>\n            Low stock <span className=\"text-muted-foreground ms-1 tabular-nums\">{lowCount}</span>\n          </button>\n          <SegmentedControl aria-label=\"Layout\" value={view} onValueChange={(v) => setView(v as View)} size=\"sm\" className=\"ms-auto\">\n            <SegmentedControlItem value=\"grid\" aria-label=\"Grid view\"><LayoutGrid /></SegmentedControlItem>\n            <SegmentedControlItem value=\"list\" aria-label=\"List view\"><List /></SegmentedControlItem>\n          </SegmentedControl>\n        </div>\n        <p className=\"sr-only\" role=\"status\">{shown.length} products shown</p>\n\n        {shown.length === 0 ? (\n          <div className=\"bg-card text-muted-foreground rounded-xl border px-6 py-20 text-center text-sm\">No products match these filters.</div>\n        ) : view === \"grid\" ? (\n          <ul className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n            {shown.map((p) => (\n              <li key={p.id} className=\"bg-card group rounded-xl border p-3 transition-shadow hover:shadow-md\">\n                <ProductTile hue={p.hue} className=\"aspect-[4/3] w-full\" />\n                <div className=\"mt-3 px-1\">\n                  <div className=\"flex items-start justify-between gap-2\">\n                    <h3 className=\"text-sm leading-snug font-semibold text-pretty\">{p.name}</h3>\n                    {p.status === \"draft\" && <Badge variant=\"secondary\">Draft</Badge>}\n                  </div>\n                  <p className=\"text-muted-foreground mt-0.5 text-xs\">{p.category}</p>\n                  <div className=\"mt-3 flex items-center justify-between\"><p className=\"font-bold tabular-nums\">{moneyExact.format(p.price)}</p><Stock p={p} /></div>\n                </div>\n              </li>\n            ))}\n          </ul>\n        ) : (\n          <div className=\"bg-card overflow-hidden rounded-xl border\">\n            <div tabIndex={0} role=\"region\" aria-label=\"Products table\" className=\"focus-visible:ring-ring/50 overflow-x-auto outline-none focus-visible:ring-[3px] focus-visible:ring-inset\">\n              <table className=\"w-full min-w-[40rem] text-start text-sm\">\n                <caption className=\"sr-only\">Products</caption>\n                <thead><tr className=\"text-muted-foreground bg-surface border-b text-xs\">{[\"Product\", \"Category\", \"Status\", \"Price\", \"Stock\"].map((c, i) => <th key={c} scope=\"col\" className={cn(\"px-4 py-2.5 font-semibold\", i === 3 && \"text-end\", i === 4 && \"text-end\")}>{c}</th>)}</tr></thead>\n                <tbody className=\"divide-y\">\n                  {shown.map((p) => (\n                    <tr key={p.id} className=\"hover:bg-accent/40\">\n                      <td className=\"px-4 py-2.5\"><span className=\"flex items-center gap-3\"><ProductTile hue={p.hue} className=\"size-9 shrink-0\" /><span className=\"font-semibold\">{p.name}</span></span></td>\n                      <td className=\"text-muted-foreground px-4 py-2.5\">{p.category}</td>\n                      <td className=\"px-4 py-2.5\"><Badge status={p.status === \"active\" ? \"success\" : \"neutral\"} variant=\"outline\">{p.status === \"active\" ? \"Active\" : \"Draft\"}</Badge></td>\n                      <td className=\"px-4 py-2.5 text-end font-semibold tabular-nums\">{moneyExact.format(p.price)}</td>\n                      <td className=\"px-4 py-2.5\">\n                        <div className=\"flex items-center justify-end gap-1.5\">\n                          <button type=\"button\" aria-label={`Remove one ${p.name}`} onClick={() => adjust(p.id, -1)} className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-7 items-center justify-center rounded-md border outline-none focus-visible:ring-[3px]\"><Minus className=\"size-3.5\" aria-hidden=\"true\" /></button>\n                          <span className=\"w-10 text-center font-semibold tabular-nums\" aria-live=\"polite\">{p.stock}</span>\n                          <button type=\"button\" aria-label={`Add one ${p.name}`} onClick={() => adjust(p.id, 1)} className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-7 items-center justify-center rounded-md border outline-none focus-visible:ring-[3px]\"><Plus className=\"size-3.5\" aria-hidden=\"true\" /></button>\n                        </div>\n                      </td>\n                    </tr>\n                  ))}\n                </tbody>\n              </table>\n            </div>\n          </div>\n        )}\n      </main>\n\n      <Sheet open={open} onOpenChange={(o) => { setOpen(o); if (!o) setErrors({}) }}>\n        <SheetContent side=\"end\" className=\"w-full sm:max-w-md\" closeLabel=\"Close\">\n          <form onSubmit={add} noValidate className=\"flex h-full flex-col\">\n            <SheetHeader>\n              <SheetTitle>Add product</SheetTitle>\n              <SheetDescription>New products start as active and appear in your store right away.</SheetDescription>\n            </SheetHeader>\n            <div className=\"flex-1 space-y-4 overflow-y-auto px-4 pb-4\">\n              <div>\n                <label htmlFor=\"ap-name\" className=\"text-sm font-medium\">Name</label>\n                <input id=\"ap-name\" name=\"name\" aria-invalid={!!errors.name} aria-describedby={errors.name ? \"ap-name-err\" : undefined} className={fieldClass} />\n                {errors.name && <p id=\"ap-name-err\" className=\"text-destructive mt-1.5 text-sm\">{errors.name}</p>}\n              </div>\n              <div>\n                <label htmlFor=\"ap-cat\" className=\"text-sm font-medium\">Category</label>\n                <select id=\"ap-cat\" name=\"category\" className={fieldClass}>{categories.map((c) => <option key={c}>{c}</option>)}</select>\n              </div>\n              <div className=\"grid grid-cols-2 gap-3\">\n                <div>\n                  <label htmlFor=\"ap-price\" className=\"text-sm font-medium\">Price (USD)</label>\n                  <input id=\"ap-price\" name=\"price\" type=\"number\" step=\"0.01\" min=\"0\" inputMode=\"decimal\" aria-invalid={!!errors.price} aria-describedby={errors.price ? \"ap-price-err\" : undefined} className={fieldClass} />\n                  {errors.price && <p id=\"ap-price-err\" className=\"text-destructive mt-1.5 text-sm\">{errors.price}</p>}\n                </div>\n                <div>\n                  <label htmlFor=\"ap-stock\" className=\"text-sm font-medium\">Stock</label>\n                  <input id=\"ap-stock\" name=\"stock\" type=\"number\" min=\"0\" defaultValue=\"0\" className={fieldClass} />\n                </div>\n              </div>\n              <div>\n                <label htmlFor=\"ap-status\" className=\"text-sm font-medium\">Status</label>\n                <select id=\"ap-status\" name=\"status\" className={fieldClass}><option value=\"active\">Active</option><option value=\"draft\">Draft</option></select>\n              </div>\n            </div>\n            <SheetFooter>\n              <button type=\"button\" className={atlasButton.outline} onClick={() => setOpen(false)}>Cancel</button>\n              <button type=\"submit\" className={atlasButton.primary}>Add product</button>\n            </SheetFooter>\n          </form>\n        </SheetContent>\n      </Sheet>\n    </AtlasShell>\n  )\n}\n\nexport { AtlasProducts, type AtlasProductsProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/atlas/atlas-products.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/atlas/atlas-customers.tsx",
      "content": "// Ballmac UI: Atlas customers page. https://ui.ballmac.com/templates/template-atlas\n\"use client\"\n\nimport * as React from \"react\"\nimport { Mail, Search } from \"lucide-react\"\n\nimport { Badge } from \"@/components/ballmac/badge\"\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from \"@/components/ballmac/sheet\"\nimport { customers, formatDate, initials, money, moneyExact, orderGrand, orders, type AtlasCustomer } from \"@/components/ballmac/templates/atlas/atlas-data\"\nimport { AtlasPageHeader, AtlasShell, FulfilmentBadge, atlasButton, type AtlasHrefs } from \"@/components/ballmac/templates/atlas/atlas-theme\"\nimport { cn } from \"@/lib/utils\"\n\ntype Segment = \"All\" | AtlasCustomer[\"segment\"]\nconst segments: Segment[] = [\"All\", \"VIP\", \"Regular\", \"New\", \"At risk\"]\nconst tones = [\"bg-chart-1/20\", \"bg-chart-2/20\", \"bg-chart-3/25\", \"bg-chart-4/20\", \"bg-chart-5/20\"]\nconst segmentStatus = { VIP: \"success\", Regular: \"neutral\", New: \"neutral\", \"At risk\": \"warning\" } as const\n\nfunction stats(c: AtlasCustomer) {\n  const mine = orders.filter((o) => o.customerId === c.id)\n  const spent = mine.reduce((n, o) => n + (o.payment === \"refunded\" ? 0 : orderGrand(o)), 0)\n  return { mine, spent, last: mine[0]?.date }\n}\n\ntype AtlasCustomersProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<AtlasHrefs> }\n\n/** The Atlas customers page: segment filters, search, lifetime value bars and a profile drawer with recent orders. */\nfunction AtlasCustomers({ hrefs, ...props }: AtlasCustomersProps) {\n  const [segment, setSegment] = React.useState<Segment>(\"All\")\n  const [query, setQuery] = React.useState(\"\")\n  const [openId, setOpenId] = React.useState<string | null>(null)\n  const q = query.trim().toLowerCase()\n  const rows = customers\n    .map((c) => ({ c, ...stats(c) }))\n    .filter((r) => (segment === \"All\" || r.c.segment === segment) && (!q || [r.c.name, r.c.email, r.c.city].join(\" \").toLowerCase().includes(q)))\n    .sort((a, b) => b.spent - a.spent)\n  const max = Math.max(...customers.map((c) => stats(c).spent))\n  const open = customers.find((c) => c.id === openId)\n  const openStats = open ? stats(open) : null\n\n  return (\n    <AtlasShell page=\"customers\" title=\"Customers\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-7xl space-y-5 px-4 py-6 sm:px-6\">\n        <AtlasPageHeader title=\"Customers\" description={`${customers.length} people have bought from Fieldnote Goods.`} />\n        <div className=\"bg-card rounded-xl border\">\n          <div className=\"flex flex-col gap-3 border-b p-3 lg:flex-row lg:items-center lg:justify-between\">\n            <div role=\"group\" aria-label=\"Filter by segment\" className=\"flex gap-1 overflow-x-auto [scrollbar-width:none]\">\n              {segments.map((s) => {\n                const on = segment === s\n                const count = s === \"All\" ? customers.length : customers.filter((c) => c.segment === s).length\n                return (\n                  <button key={s} type=\"button\" aria-pressed={on} onClick={() => setSegment(s)} className={cn(\"focus-visible:ring-ring/50 flex shrink-0 items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px]\", on ? \"bg-accent text-foreground\" : \"text-muted-foreground hover:text-foreground\")}>\n                    {s} <span className=\"text-muted-foreground text-xs font-medium tabular-nums\">{count}</span>\n                  </button>\n                )\n              })}\n            </div>\n            <div className=\"relative lg:w-72\">\n              <Search className=\"text-muted-foreground pointer-events-none absolute top-1/2 start-3 size-4 -translate-y-1/2\" aria-hidden=\"true\" />\n              <input type=\"search\" aria-label=\"Search customers\" value={query} onChange={(e) => setQuery(e.target.value)} placeholder=\"Search name, email or city\" className=\"bg-background focus-visible:ring-ring/50 placeholder:text-muted-foreground h-9 w-full rounded-lg border pe-3 ps-9 text-sm outline-none focus-visible:ring-[3px]\" />\n            </div>\n          </div>\n          <div tabIndex={0} role=\"region\" aria-label=\"Customers table\" className=\"focus-visible:ring-ring/50 overflow-x-auto outline-none focus-visible:ring-[3px] focus-visible:ring-inset\">\n            <table className=\"w-full min-w-[44rem] text-start text-sm\">\n              <caption className=\"sr-only\">Customers by lifetime spend</caption>\n              <thead><tr className=\"text-muted-foreground bg-surface border-b text-xs\">{[\"Customer\", \"Segment\", \"Orders\", \"Last order\", \"Lifetime spend\"].map((c) => <th key={c} scope=\"col\" className=\"px-4 py-2.5 font-semibold\">{c}</th>)}</tr></thead>\n              <tbody className=\"divide-y\">\n                {rows.map(({ c, mine, spent, last }) => (\n                  <tr key={c.id} className=\"hover:bg-accent/40\">\n                    <td className=\"px-4 py-3\">\n                      <button type=\"button\" onClick={() => setOpenId(c.id)} className=\"focus-visible:ring-ring/50 -m-1 flex items-center gap-3 rounded-lg p-1 text-start outline-none focus-visible:ring-[3px]\">\n                        <span className={cn(\"flex size-9 shrink-0 items-center justify-center rounded-full text-xs font-bold\", tones[c.tone - 1])} aria-hidden=\"true\">{initials(c.name)}</span>\n                        <span><span className=\"block font-semibold\">{c.name}</span><span className=\"text-muted-foreground block text-xs\">{c.email} · {c.city}</span></span>\n                      </button>\n                    </td>\n                    <td className=\"px-4 py-3\"><Badge status={segmentStatus[c.segment]} variant=\"outline\">{c.segment}</Badge></td>\n                    <td className=\"px-4 py-3 tabular-nums\">{mine.length}</td>\n                    <td className=\"text-muted-foreground px-4 py-3 whitespace-nowrap\">{last ? formatDate(last) : \"—\"}</td>\n                    <td className=\"px-4 py-3\">\n                      <div className=\"flex items-center gap-3\">\n                        <div className=\"bg-muted hidden h-1.5 w-24 rounded-full sm:block\" aria-hidden=\"true\"><div className=\"bg-chart-1 h-full rounded-full\" style={{ width: `${(spent / max) * 100}%` }} /></div>\n                        <span className=\"w-20 text-end font-semibold tabular-nums\">{money.format(spent)}</span>\n                      </div>\n                    </td>\n                  </tr>\n                ))}\n                {rows.length === 0 && <tr><td colSpan={5} className=\"text-muted-foreground px-4 py-16 text-center\">No customers match.</td></tr>}\n              </tbody>\n            </table>\n          </div>\n          <p className=\"text-muted-foreground border-t px-4 py-2.5 text-sm\" role=\"status\">{rows.length} of {customers.length} customers</p>\n        </div>\n      </main>\n\n      <Sheet open={!!open} onOpenChange={(o) => !o && setOpenId(null)}>\n        <SheetContent side=\"end\" className=\"w-full sm:max-w-md\" closeLabel=\"Close profile\">\n          {open && openStats && (\n            <>\n              <SheetHeader>\n                <div className=\"flex items-center gap-3\">\n                  <span className={cn(\"flex size-12 items-center justify-center rounded-full text-sm font-bold\", tones[open.tone - 1])} aria-hidden=\"true\">{initials(open.name)}</span>\n                  <div><SheetTitle>{open.name}</SheetTitle><SheetDescription>{open.city} · {open.segment}</SheetDescription></div>\n                </div>\n              </SheetHeader>\n              <div className=\"space-y-5 overflow-y-auto px-4 pb-6\">\n                <dl className=\"grid grid-cols-3 gap-2 text-center\">\n                  {[[\"Spent\", money.format(openStats.spent)], [\"Orders\", String(openStats.mine.length)], [\"Average\", openStats.mine.length ? moneyExact.format(openStats.spent / openStats.mine.length) : \"—\"]].map(([k, v]) => (\n                    <div key={k} className=\"bg-surface rounded-lg border p-3\"><dt className=\"text-muted-foreground text-xs\">{k}</dt><dd className=\"mt-1 font-bold tabular-nums\">{v}</dd></div>\n                  ))}\n                </dl>\n                <a href={`mailto:${open.email}`} className={cn(atlasButton.outline, \"w-full\")}><Mail aria-hidden=\"true\" /> Email {open.name.split(\" \")[0]}</a>\n                <section aria-labelledby=\"ac-recent\">\n                  <h3 id=\"ac-recent\" className=\"text-sm font-bold\">Recent orders</h3>\n                  <ul className=\"mt-2 divide-y rounded-lg border\">\n                    {openStats.mine.slice(0, 4).map((o) => (\n                      <li key={o.id} className=\"flex items-center justify-between gap-3 px-3 py-2.5 text-sm\">\n                        <span><span className=\"font-semibold\" style={{ fontFamily: \"var(--atlas-mono)\" }}>{o.id}</span><span className=\"text-muted-foreground ms-2 text-xs\">{formatDate(o.date)}</span></span>\n                        <span className=\"flex items-center gap-2\"><FulfilmentBadge value={o.fulfillment} /><span className=\"tabular-nums\">{moneyExact.format(orderGrand(o))}</span></span>\n                      </li>\n                    ))}\n                  </ul>\n                </section>\n              </div>\n            </>\n          )}\n        </SheetContent>\n      </Sheet>\n    </AtlasShell>\n  )\n}\n\nexport { AtlasCustomers, type AtlasCustomersProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/atlas/atlas-customers.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/atlas/atlas-settings.tsx",
      "content": "// Ballmac UI: Atlas settings page. https://ui.ballmac.com/templates/template-atlas\n\"use client\"\n\nimport * as React from \"react\"\nimport { Check } from \"lucide-react\"\n\nimport { Switch } from \"@/components/ballmac/switch\"\nimport { AtlasPageHeader, AtlasShell, atlasButton, type AtlasHrefs } from \"@/components/ballmac/templates/atlas/atlas-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst field = \"bg-background focus-visible:ring-ring/50 mt-1.5 h-10 w-full rounded-lg border px-3 text-sm outline-none focus-visible:ring-[3px]\"\n\ntype State = {\n  name: string\n  email: string\n  currency: string\n  rates: Record<string, boolean>\n  alerts: Record<string, boolean>\n}\n\nconst initial: State = {\n  name: \"Fieldnote Goods\",\n  email: \"hello@fieldnote.example\",\n  currency: \"USD\",\n  rates: { standard: true, express: true, pickup: false },\n  alerts: { orders: true, stock: true, reviews: false, digest: true },\n}\n\nconst rateRows = [\n  [\"standard\", \"Standard shipping\", \"3 to 5 business days · $8, free over $100\"],\n  [\"express\", \"Express shipping\", \"1 to 2 business days · $19\"],\n  [\"pickup\", \"Local pickup\", \"Collect from the Portland studio · Free\"],\n]\nconst alertRows = [\n  [\"orders\", \"New orders\", \"Email me as soon as an order is placed.\"],\n  [\"stock\", \"Low stock\", \"Tell me when a product drops to 10 or fewer.\"],\n  [\"reviews\", \"Customer reviews\", \"Tell me about new reviews that need a reply.\"],\n  [\"digest\", \"Weekly digest\", \"A Monday summary of sales, orders and top products.\"],\n]\n\nconst sections = [[\"profile\", \"Store profile\"], [\"shipping\", \"Shipping\"], [\"alerts\", \"Notifications\"], [\"danger\", \"Danger zone\"]]\n\ntype AtlasSettingsProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<AtlasHrefs> }\n\n/** The Atlas settings page: store profile, shipping rates and notification switches, with a save bar that appears only when something changed. */\nfunction AtlasSettings({ hrefs, ...props }: AtlasSettingsProps) {\n  const [saved, setSaved] = React.useState<State>(initial)\n  const [draft, setDraft] = React.useState<State>(initial)\n  const [status, setStatus] = React.useState<\"idle\" | \"saved\">(\"idle\")\n  const dirty = JSON.stringify(saved) !== JSON.stringify(draft)\n\n  function set<K extends keyof State>(key: K, value: State[K]) {\n    setStatus(\"idle\")\n    setDraft((d) => ({ ...d, [key]: value }))\n  }\n\n  return (\n    <AtlasShell page=\"settings\" title=\"Settings\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto grid max-w-5xl gap-8 px-4 py-6 pb-28 sm:px-6 lg:grid-cols-[11rem_minmax(0,1fr)]\">\n        <nav aria-label=\"Settings sections\" className=\"hidden lg:block\">\n          <ul className=\"sticky top-20 space-y-0.5\">\n            {sections.map(([id, label]) => <li key={id}><a href={`#${id}`} className=\"text-muted-foreground hover:text-foreground hover:bg-accent focus-visible:ring-ring/50 block rounded-lg px-3 py-2 text-sm font-medium outline-none focus-visible:ring-[3px]\">{label}</a></li>)}\n          </ul>\n        </nav>\n\n        <div className=\"min-w-0 space-y-6\">\n          <AtlasPageHeader title=\"Settings\" description=\"Manage how your store looks, ships and talks to you.\" />\n\n          <section id=\"profile\" aria-labelledby=\"as-profile\" className=\"bg-card scroll-mt-20 rounded-xl border p-5\">\n            <h3 id=\"as-profile\" className=\"font-bold\">Store profile</h3>\n            <div className=\"mt-4 grid gap-4 sm:grid-cols-2\">\n              <div><label htmlFor=\"as-name\" className=\"text-sm font-medium\">Store name</label><input id=\"as-name\" value={draft.name} onChange={(e) => set(\"name\", e.target.value)} className={field} /></div>\n              <div><label htmlFor=\"as-email\" className=\"text-sm font-medium\">Contact email</label><input id=\"as-email\" type=\"email\" value={draft.email} onChange={(e) => set(\"email\", e.target.value)} className={field} /></div>\n              <div><label htmlFor=\"as-cur\" className=\"text-sm font-medium\">Currency</label>\n                <select id=\"as-cur\" value={draft.currency} onChange={(e) => set(\"currency\", e.target.value)} className={field}><option>USD</option><option>EUR</option><option>GBP</option><option>CAD</option></select>\n              </div>\n            </div>\n          </section>\n\n          <section id=\"shipping\" aria-labelledby=\"as-ship\" className=\"bg-card scroll-mt-20 rounded-xl border\">\n            <h3 id=\"as-ship\" className=\"p-5 pb-3 font-bold\">Shipping rates</h3>\n            <ul className=\"divide-y border-t\">\n              {rateRows.map(([id, title, text]) => (\n                <li key={id} className=\"flex items-center justify-between gap-4 px-5 py-4\">\n                  <div><p id={`as-rate-${id}`} className=\"text-sm font-semibold\">{title}</p><p className=\"text-muted-foreground text-sm\">{text}</p></div>\n                  <Switch aria-labelledby={`as-rate-${id}`} checked={draft.rates[id]} onCheckedChange={(v) => set(\"rates\", { ...draft.rates, [id]: v })} />\n                </li>\n              ))}\n            </ul>\n          </section>\n\n          <section id=\"alerts\" aria-labelledby=\"as-alerts\" className=\"bg-card scroll-mt-20 rounded-xl border\">\n            <h3 id=\"as-alerts\" className=\"p-5 pb-3 font-bold\">Notifications</h3>\n            <ul className=\"divide-y border-t\">\n              {alertRows.map(([id, title, text]) => (\n                <li key={id} className=\"flex items-center justify-between gap-4 px-5 py-4\">\n                  <div><p id={`as-alert-${id}`} className=\"text-sm font-semibold\">{title}</p><p className=\"text-muted-foreground text-sm\">{text}</p></div>\n                  <Switch aria-labelledby={`as-alert-${id}`} checked={draft.alerts[id]} onCheckedChange={(v) => set(\"alerts\", { ...draft.alerts, [id]: v })} />\n                </li>\n              ))}\n            </ul>\n          </section>\n\n          <section id=\"danger\" aria-labelledby=\"as-danger\" className=\"border-destructive/40 bg-card scroll-mt-20 rounded-xl border p-5\">\n            <h3 id=\"as-danger\" className=\"font-bold\">Danger zone</h3>\n            <p className=\"text-muted-foreground mt-1 text-sm text-pretty\">Closing the store hides it from customers. Orders and customer data are kept for 90 days.</p>\n            <button type=\"button\" className={cn(atlasButton.danger, \"mt-4\")}>Close store</button>\n          </section>\n        </div>\n      </main>\n\n      {(dirty || status === \"saved\") && (\n        <div role=\"region\" aria-label=\"Save changes\" className=\"bg-popover fixed inset-x-3 bottom-4 z-40 mx-auto flex max-w-xl items-center justify-between gap-3 rounded-2xl border p-3 ps-5 shadow-[0_20px_50px_-12px_rgb(0_0_0/0.35)]\">\n          <p className=\"text-sm font-semibold\" role=\"status\">{dirty ? \"You have unsaved changes\" : <span className=\"flex items-center gap-1.5\"><Check className=\"text-chart-2 size-4\" aria-hidden=\"true\" />Changes saved</span>}</p>\n          {dirty && (\n            <div className=\"flex gap-2\">\n              <button type=\"button\" className={atlasButton.outline} onClick={() => setDraft(saved)}>Discard</button>\n              <button type=\"button\" className={atlasButton.primary} onClick={() => { setSaved(draft); setStatus(\"saved\") }}>Save changes</button>\n            </div>\n          )}\n        </div>\n      )}\n    </AtlasShell>\n  )\n}\n\nexport { AtlasSettings, type AtlasSettingsProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/atlas/atlas-settings.tsx"
    },
    {
      "path": "registry/ballmac/app/atlas/page.tsx",
      "content": "// Ballmac UI: Atlas template route. https://ui.ballmac.com/templates/template-atlas\nimport type { Metadata } from \"next\"\n\nimport { AtlasDashboard } from \"@/components/ballmac/templates/atlas/atlas-dashboard\"\n\nexport const metadata: Metadata = {\n  title: \"Atlas dashboard · Atlas\",\n  description: \"Sales, orders and stock for Fieldnote Goods.\",\n}\n\nexport default function Page() {\n  return <AtlasDashboard />\n}\n",
      "type": "registry:page",
      "target": "app/atlas/page.tsx"
    },
    {
      "path": "registry/ballmac/app/atlas/orders/page.tsx",
      "content": "// Ballmac UI: Atlas template route. https://ui.ballmac.com/templates/template-atlas\nimport type { Metadata } from \"next\"\n\nimport { AtlasOrders } from \"@/components/ballmac/templates/atlas/atlas-orders\"\n\nexport const metadata: Metadata = {\n  title: \"Orders · Atlas\",\n  description: \"Search, sort and fulfil every order.\",\n}\n\nexport default function Page() {\n  return <AtlasOrders />\n}\n",
      "type": "registry:page",
      "target": "app/atlas/orders/page.tsx"
    },
    {
      "path": "registry/ballmac/app/atlas/orders/[id]/page.tsx",
      "content": "// Ballmac UI: Atlas template route. https://ui.ballmac.com/templates/template-atlas\nimport type { Metadata } from \"next\"\n\nimport { AtlasOrder } from \"@/components/ballmac/templates/atlas/atlas-order\"\n\nexport async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {\n  const { id } = await params\n  return { title: `Order ${id} · Atlas`, description: \"Items, timeline, fulfilment and refunds for one order.\" }\n}\n\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  return <AtlasOrder orderId={id} />\n}\n",
      "type": "registry:page",
      "target": "app/atlas/orders/[id]/page.tsx"
    },
    {
      "path": "registry/ballmac/app/atlas/products/page.tsx",
      "content": "// Ballmac UI: Atlas template route. https://ui.ballmac.com/templates/template-atlas\nimport type { Metadata } from \"next\"\n\nimport { AtlasProducts } from \"@/components/ballmac/templates/atlas/atlas-products\"\n\nexport const metadata: Metadata = {\n  title: \"Products · Atlas\",\n  description: \"Catalog, stock and pricing.\",\n}\n\nexport default function Page() {\n  return <AtlasProducts />\n}\n",
      "type": "registry:page",
      "target": "app/atlas/products/page.tsx"
    },
    {
      "path": "registry/ballmac/app/atlas/customers/page.tsx",
      "content": "// Ballmac UI: Atlas template route. https://ui.ballmac.com/templates/template-atlas\nimport type { Metadata } from \"next\"\n\nimport { AtlasCustomers } from \"@/components/ballmac/templates/atlas/atlas-customers\"\n\nexport const metadata: Metadata = {\n  title: \"Customers · Atlas\",\n  description: \"Segments, lifetime spend and recent orders.\",\n}\n\nexport default function Page() {\n  return <AtlasCustomers />\n}\n",
      "type": "registry:page",
      "target": "app/atlas/customers/page.tsx"
    },
    {
      "path": "registry/ballmac/app/atlas/settings/page.tsx",
      "content": "// Ballmac UI: Atlas template route. https://ui.ballmac.com/templates/template-atlas\nimport type { Metadata } from \"next\"\n\nimport { AtlasSettings } from \"@/components/ballmac/templates/atlas/atlas-settings\"\n\nexport const metadata: Metadata = {\n  title: \"Settings · Atlas\",\n  description: \"Store profile, shipping and notifications.\",\n}\n\nexport default function Page() {\n  return <AtlasSettings />\n}\n",
      "type": "registry:page",
      "target": "app/atlas/settings/page.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "template",
      "admin",
      "dashboard",
      "ecommerce",
      "orders",
      "products",
      "customers",
      "settings"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "Installs a six-page store admin with working client-side filtering, sorting, selection, drawers and forms over sample data. Replace atlas-data.ts with your API, and edit atlasCss for the palette.",
      "whenToUse": [
        "Back offices for shops, marketplaces and internal tools",
        "A starting point for dense admin tables with bulk actions"
      ],
      "whenNotToUse": [
        "Public marketing sites (use a marketing template)"
      ],
      "composesWith": [
        "data-table",
        "dashboard-1",
        "settings-1"
      ],
      "a11y": [
        {
          "keys": "⌘K / Ctrl+K",
          "action": "Opens the search and navigation menu"
        },
        {
          "keys": "Enter / Space on a column heading",
          "action": "Sorts the table by that column"
        }
      ],
      "customization": [
        "Replace atlas-data.ts with your data",
        "Edit atlasCss for the light and dark palettes",
        "Swap the sidebar items in the nav array of atlas-theme.tsx"
      ]
    },
    "examples": [
      "template-atlas-demo",
      "template-atlas-orders",
      "template-atlas-order",
      "template-atlas-products",
      "template-atlas-customers",
      "template-atlas-settings"
    ],
    "url": "https://ui.ballmac.com/components/template-atlas"
  },
  "docs": "Pages are at /atlas, /atlas/orders, /atlas/orders/[id], /atlas/products, /atlas/customers and /atlas/settings. The palette is the atlasCss string in atlas-theme.tsx and the sample data is atlas-data.ts: replace it with calls to your own API.",
  "categories": [
    "templates"
  ],
  "type": "registry:block"
}