diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md
new file mode 100644
index 0000000..76740ad
--- /dev/null
+++ b/.design-sync/NOTES.md
@@ -0,0 +1,157 @@
+# design-sync notes — Cloudrite
+
+Repo-specific gotchas for future syncs. Read this before re-running.
+
+## What this repo is
+
+Not a component library — a Next.js 16 marketing site (`private: true`, no `dist/`,
+no `exports`, no `.d.ts` tree). The DS surface is `components/ui/*` (54 shadcn/ui
+primitives, new-york style) plus 7 hand-built Cloudrite page sections.
+
+## The staging package (`.ds-pkg/`)
+
+The converter needs a package with a built entry and a `.d.ts` tree. Three scripts
+under `.design-sync/` build one from source; run them **in this order** (this is
+`cfg.buildCmd`):
+
+1. `node_modules/.bin/tsc -p .design-sync/tsconfig.dts.json` — declaration emit,
+ 69 `.d.ts` files. Exits 0; all three steps are chained with `&&`, so a real
+ failure stops the run.
+ **`../next-env.d.ts` must stay in that config's `include`.** It pulls in Next's
+ JSX augmentation for styled-jsx; without it the `
+
+
+);
diff --git a/.design-sync/previews/Dialog.tsx b/.design-sync/previews/Dialog.tsx
new file mode 100644
index 0000000..5fe4263
--- /dev/null
+++ b/.design-sync/previews/Dialog.tsx
@@ -0,0 +1,46 @@
+import type { ReactNode } from 'react';
+import {
+ Button,
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ Input,
+ Label,
+} from 'cloudrite';
+
+// Cloudrite is dark-only — the overlay surfaces read correctly only against the
+// brand background, not the harness's white card body.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+// `defaultOpen` is what makes the card worth looking at — a bare trigger shows
+// none of Dialog's parts. Paired with cardMode:single in .design-sync/config.json.
+export const Open = () => (
+
+
+
+);
diff --git a/.design-sync/previews/DropdownMenu.tsx b/.design-sync/previews/DropdownMenu.tsx
new file mode 100644
index 0000000..061c0c9
--- /dev/null
+++ b/.design-sync/previews/DropdownMenu.tsx
@@ -0,0 +1,58 @@
+import type { ReactNode } from 'react';
+import {
+ Button,
+ DropdownMenu,
+ DropdownMenuCheckboxItem,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuTrigger,
+} from 'cloudrite';
+import { Download, RefreshCw, Settings, Trash2 } from 'lucide-react';
+
+// Cloudrite is dark-only — the overlay surfaces read correctly only against the
+// brand background, not the harness's white card body.
+const Surface = ({ children }: { children: ReactNode }) => (
+
+
+);
diff --git a/.design-sync/previews/Empty.tsx b/.design-sync/previews/Empty.tsx
new file mode 100644
index 0000000..16335b3
--- /dev/null
+++ b/.design-sync/previews/Empty.tsx
@@ -0,0 +1,40 @@
+import type { ReactNode } from 'react';
+import {
+ Button,
+ Empty,
+ EmptyContent,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyMedia,
+ EmptyTitle,
+} from 'cloudrite';
+import { CloudOff, Plus } from 'lucide-react';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const Default = () => (
+
+
+
+
+
+
+ No sites yet
+
+ Add your first site and we'll handle DNS, SSL and backups for you.
+
+
+
+
+
+
+
+);
diff --git a/.design-sync/previews/Features.tsx b/.design-sync/previews/Features.tsx
new file mode 100644
index 0000000..edf35da
--- /dev/null
+++ b/.design-sync/previews/Features.tsx
@@ -0,0 +1,19 @@
+import { Features } from 'cloudrite';
+
+// The "why Cloudrite" feature showcase. Rotates its active feature on a 4s interval; the still captures whichever is active on first paint.
+//
+// The section draws light-on-dark against the page background, which the preview
+// card harness paints white — hence the surface wrapper. Transitions and
+// animation delays are zeroed because the capture harness only waits on fonts
+// and images, so an entrance animation would otherwise be screenshotted mid-flight.
+const still = `*,*::before,*::after{
+ transition-duration:0s !important;transition-delay:0s !important;
+ animation-duration:0s !important;animation-delay:0s !important;
+}`;
+
+export const Default = () => (
+
+
+
+
+);
diff --git a/.design-sync/previews/Field.tsx b/.design-sync/previews/Field.tsx
new file mode 100644
index 0000000..ed66c7b
--- /dev/null
+++ b/.design-sync/previews/Field.tsx
@@ -0,0 +1,78 @@
+import type { ReactNode } from 'react';
+import {
+ Checkbox,
+ Field,
+ FieldContent,
+ FieldDescription,
+ FieldError,
+ FieldGroup,
+ FieldLabel,
+ FieldLegend,
+ FieldSeparator,
+ FieldSet,
+ Input,
+} from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const Vertical = () => (
+
+
+
+ Domain
+
+ We'll point the DNS for you at no charge.
+
+
+
+);
+
+export const Horizontal = () => (
+
+
+
+
+
+ Managed backups
+ Nightly snapshots kept for 30 days.
+
+
+
+
+);
+
+export const WithError = () => (
+
+
+
+ Email
+
+ Enter a valid email address.
+
+
+
+);
+
+export const Fieldset = () => (
+
+
+
+);
diff --git a/.design-sync/previews/Footer.tsx b/.design-sync/previews/Footer.tsx
new file mode 100644
index 0000000..4b0f9eb
--- /dev/null
+++ b/.design-sync/previews/Footer.tsx
@@ -0,0 +1,19 @@
+import { Footer } from 'cloudrite';
+
+// The site footer: service/company link columns, contact details and the Cloudrite mark.
+//
+// The section draws light-on-dark against the page background, which the preview
+// card harness paints white — hence the surface wrapper. Transitions and
+// animation delays are zeroed because the capture harness only waits on fonts
+// and images, so an entrance animation would otherwise be screenshotted mid-flight.
+const still = `*,*::before,*::after{
+ transition-duration:0s !important;transition-delay:0s !important;
+ animation-duration:0s !important;animation-delay:0s !important;
+}`;
+
+export const Default = () => (
+
+
+
+
+);
diff --git a/.design-sync/previews/Form.tsx b/.design-sync/previews/Form.tsx
new file mode 100644
index 0000000..55efc56
--- /dev/null
+++ b/.design-sync/previews/Form.tsx
@@ -0,0 +1,79 @@
+import type { ReactNode } from 'react';
+import { useForm } from 'react-hook-form';
+import {
+ Button,
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Input,
+ Textarea,
+} from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+type Values = { name: string; email: string; message: string };
+
+// `Form` is react-hook-form's FormProvider — it needs a useForm() instance, and
+// FormField wires each control through Controller. This is the enquiry form on
+// the Cloudrite contact section.
+export const EnquiryForm = () => {
+ const form = useForm({
+ defaultValues: { name: 'Jamie Lambert', email: 'jamie@cafe.co.nz', message: '' },
+ });
+ return (
+
+
+
+
+ );
+};
diff --git a/.design-sync/previews/Header.tsx b/.design-sync/previews/Header.tsx
new file mode 100644
index 0000000..c6496da
--- /dev/null
+++ b/.design-sync/previews/Header.tsx
@@ -0,0 +1,20 @@
+import { Header } from 'cloudrite';
+
+// Header is `position: fixed`, so it contributes no height to its container — an
+// unwrapped render collapses the preview root to zero and the card comes up
+// empty. The sized, `relative` shell below gives it something to pin to and
+// shows it over page content the way it actually appears.
+const still = `*,*::before,*::after{
+ transition-duration:0s !important;transition-delay:0s !important;
+ animation-duration:0s !important;animation-delay:0s !important;
+}`;
+
+export const Default = () => (
+
+
+
+
+ Page content scrolls beneath the fixed header.
+
+
+);
diff --git a/.design-sync/previews/Hero.tsx b/.design-sync/previews/Hero.tsx
new file mode 100644
index 0000000..41538a6
--- /dev/null
+++ b/.design-sync/previews/Hero.tsx
@@ -0,0 +1,23 @@
+import { Hero } from 'cloudrite';
+
+// Hero takes no props — it is the cloudrite.co.nz landing hero, rendered exactly
+// as app/page.tsx does.
+//
+// Two framing details, both needed for a truthful still:
+// · Hero fades in (setTimeout 300ms, then `transition-all duration-1000`) and
+// the capture harness only waits on fonts and images, so an unmodified
+// render screenshots mid-fade at ~10% opacity. Zeroing transition/animation
+// time lands every element on its final state instead.
+// · It draws light-on-dark against the page background, which the card
+// harness paints white.
+const still = `*,*::before,*::after{
+ transition-duration:0s !important;transition-delay:0s !important;
+ animation-duration:0s !important;animation-delay:0s !important;
+}`;
+
+export const Default = () => (
+
+
+
+
+);
diff --git a/.design-sync/previews/Input.tsx b/.design-sync/previews/Input.tsx
new file mode 100644
index 0000000..8ac2c5c
--- /dev/null
+++ b/.design-sync/previews/Input.tsx
@@ -0,0 +1,45 @@
+import type { ReactNode } from 'react';
+import { Input, Label } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
+
+);
diff --git a/.design-sync/previews/Label.tsx b/.design-sync/previews/Label.tsx
new file mode 100644
index 0000000..0231752
--- /dev/null
+++ b/.design-sync/previews/Label.tsx
@@ -0,0 +1,33 @@
+import type { ReactNode } from 'react';
+import { Checkbox, Input, Label } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
+
+);
+
+export const WithCheckbox = () => (
+
+
+
+);
diff --git a/.design-sync/previews/Pagination.tsx b/.design-sync/previews/Pagination.tsx
new file mode 100644
index 0000000..2415385
--- /dev/null
+++ b/.design-sync/previews/Pagination.tsx
@@ -0,0 +1,46 @@
+import type { ReactNode } from 'react';
+import {
+ Pagination,
+ PaginationContent,
+ PaginationEllipsis,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const Default = () => (
+
+
+
+
+
+
+
+ 1
+
+
+
+ 2
+
+
+
+ 3
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/.design-sync/previews/Popover.tsx b/.design-sync/previews/Popover.tsx
new file mode 100644
index 0000000..1595948
--- /dev/null
+++ b/.design-sync/previews/Popover.tsx
@@ -0,0 +1,30 @@
+import type { ReactNode } from 'react';
+import { Button, Input, Label, Popover, PopoverContent, PopoverTrigger } from 'cloudrite';
+
+// Cloudrite is dark-only — the overlay surfaces read correctly only against the
+// brand background, not the harness's white card body.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const Open = () => (
+
+
+
+
+
+
+ e.preventDefault()}>
+
+
Rename site
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/.design-sync/previews/Process.tsx b/.design-sync/previews/Process.tsx
new file mode 100644
index 0000000..b881d65
--- /dev/null
+++ b/.design-sync/previews/Process.tsx
@@ -0,0 +1,19 @@
+import { Process } from 'cloudrite';
+
+// The four-step engagement timeline. Reveals on IntersectionObserver, which fires normally in the capture viewport.
+//
+// The section draws light-on-dark against the page background, which the preview
+// card harness paints white — hence the surface wrapper. Transitions and
+// animation delays are zeroed because the capture harness only waits on fonts
+// and images, so an entrance animation would otherwise be screenshotted mid-flight.
+const still = `*,*::before,*::after{
+ transition-duration:0s !important;transition-delay:0s !important;
+ animation-duration:0s !important;animation-delay:0s !important;
+}`;
+
+export const Default = () => (
+
+
+
+
+);
diff --git a/.design-sync/previews/Progress.tsx b/.design-sync/previews/Progress.tsx
new file mode 100644
index 0000000..cfa2a52
--- /dev/null
+++ b/.design-sync/previews/Progress.tsx
@@ -0,0 +1,32 @@
+import type { ReactNode } from 'react';
+import { Progress } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const Values = () => (
+
+
+
+
+
+
+
+
+);
+
+export const Labelled = () => (
+
+
+
+ Migrating site files
+ 72%
+
+
+
+
+);
diff --git a/.design-sync/previews/RadioGroup.tsx b/.design-sync/previews/RadioGroup.tsx
new file mode 100644
index 0000000..7eecf8a
--- /dev/null
+++ b/.design-sync/previews/RadioGroup.tsx
@@ -0,0 +1,41 @@
+import type { ReactNode } from 'react';
+import { Label, RadioGroup, RadioGroupItem } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const Default = () => (
+
+
+ {[
+ ['starter', 'Starter — 1 site'],
+ ['business', 'Business — 10 sites'],
+ ['vps', 'Dedicated VPS'],
+ ].map(([value, label]) => (
+
+ ))}
+
+
+);
+
+export const Disabled = () => (
+
+
+
+
+
+
+);
diff --git a/.design-sync/previews/Select.tsx b/.design-sync/previews/Select.tsx
new file mode 100644
index 0000000..da132dc
--- /dev/null
+++ b/.design-sync/previews/Select.tsx
@@ -0,0 +1,93 @@
+import type { ReactNode } from 'react';
+import {
+ Label,
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+} from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+// The open state is the primary story: a closed trigger shows none of the
+// compound parts, and SelectContent/SelectItem/SelectGroup/SelectLabel are the
+// half of this API worth seeing.
+export const Open = () => (
+
+
+
+);
+
+export const Default = () => (
+
+
+
+);
+
+export const WithLabelAndGroups = () => (
+
+
+
+
+
+
+);
+
+export const Disabled = () => (
+
+
+
+);
diff --git a/.design-sync/previews/Separator.tsx b/.design-sync/previews/Separator.tsx
new file mode 100644
index 0000000..b474577
--- /dev/null
+++ b/.design-sync/previews/Separator.tsx
@@ -0,0 +1,32 @@
+import type { ReactNode } from 'react';
+import { Separator } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const Horizontal = () => (
+
+
+
Cloud Hosting
+
Proudly hosted in New Zealand.
+
+
From $29/month
+
+
+);
+
+export const Vertical = () => (
+
+
+ Services
+
+ Hosting
+
+ Support
+
+
+);
diff --git a/.design-sync/previews/Services.tsx b/.design-sync/previews/Services.tsx
new file mode 100644
index 0000000..2d31110
--- /dev/null
+++ b/.design-sync/previews/Services.tsx
@@ -0,0 +1,19 @@
+import { Services } from 'cloudrite';
+
+// The four Cloudrite service cards — Web Development, Cloud Hosting, I.T Support & Repairs, AI Solutions.
+//
+// The section draws light-on-dark against the page background, which the preview
+// card harness paints white — hence the surface wrapper. Transitions and
+// animation delays are zeroed because the capture harness only waits on fonts
+// and images, so an entrance animation would otherwise be screenshotted mid-flight.
+const still = `*,*::before,*::after{
+ transition-duration:0s !important;transition-delay:0s !important;
+ animation-duration:0s !important;animation-delay:0s !important;
+}`;
+
+export const Default = () => (
+
+
+
+
+);
diff --git a/.design-sync/previews/Sheet.tsx b/.design-sync/previews/Sheet.tsx
new file mode 100644
index 0000000..c0a5232
--- /dev/null
+++ b/.design-sync/previews/Sheet.tsx
@@ -0,0 +1,42 @@
+import type { ReactNode } from 'react';
+import {
+ Button,
+ Input,
+ Label,
+ Sheet,
+ SheetClose,
+ SheetContent,
+ SheetDescription,
+ SheetFooter,
+ SheetHeader,
+ SheetTitle,
+} from 'cloudrite';
+
+// Cloudrite is dark-only — the overlay surfaces read correctly only against the
+// brand background, not the harness's white card body.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const Open = () => (
+
+
+ e.preventDefault()}>
+
+ Site settings
+ cloudrite.co.nz — Business plan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/.design-sync/previews/Skeleton.tsx b/.design-sync/previews/Skeleton.tsx
new file mode 100644
index 0000000..a6aea29
--- /dev/null
+++ b/.design-sync/previews/Skeleton.tsx
@@ -0,0 +1,31 @@
+import type { ReactNode } from 'react';
+import { Skeleton } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
+
+);
diff --git a/.design-sync/previews/Slider.tsx b/.design-sync/previews/Slider.tsx
new file mode 100644
index 0000000..ac8ce14
--- /dev/null
+++ b/.design-sync/previews/Slider.tsx
@@ -0,0 +1,33 @@
+import type { ReactNode } from 'react';
+import { Label, Slider } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
+
+);
+
+export const Range = () => (
+
+
+
+);
diff --git a/.design-sync/previews/Spinner.tsx b/.design-sync/previews/Spinner.tsx
new file mode 100644
index 0000000..6813960
--- /dev/null
+++ b/.design-sync/previews/Spinner.tsx
@@ -0,0 +1,29 @@
+import type { ReactNode } from 'react';
+import { Button, Spinner } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const Sizes = () => (
+
+
+
+
+
+
+
+
+);
+
+export const InButton = () => (
+
+
+
+);
diff --git a/.design-sync/previews/Switch.tsx b/.design-sync/previews/Switch.tsx
new file mode 100644
index 0000000..b2f34b2
--- /dev/null
+++ b/.design-sync/previews/Switch.tsx
@@ -0,0 +1,39 @@
+import type { ReactNode } from 'react';
+import { Label, Switch } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
+
+);
diff --git a/.design-sync/previews/Table.tsx b/.design-sync/previews/Table.tsx
new file mode 100644
index 0000000..70331dd
--- /dev/null
+++ b/.design-sync/previews/Table.tsx
@@ -0,0 +1,60 @@
+import type { ReactNode } from 'react';
+import {
+ Badge,
+ Table,
+ TableBody,
+ TableCaption,
+ TableCell,
+ TableFooter,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
+
+);
diff --git a/.design-sync/previews/Tabs.tsx b/.design-sync/previews/Tabs.tsx
new file mode 100644
index 0000000..4c4efed
--- /dev/null
+++ b/.design-sync/previews/Tabs.tsx
@@ -0,0 +1,30 @@
+import type { ReactNode } from 'react';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const Default = () => (
+
+
+
+ Overview
+ Backups
+ Domains
+
+
+ cloudrite.co.nz — Business plan, Auckland region, 99.98% uptime this month.
+
+
+ Last snapshot 03:00 NZST. 30 restore points retained.
+
+
+ 1 domain, auto-renewing 12 March 2027.
+
+
+
+);
diff --git a/.design-sync/previews/Textarea.tsx b/.design-sync/previews/Textarea.tsx
new file mode 100644
index 0000000..cf30854
--- /dev/null
+++ b/.design-sync/previews/Textarea.tsx
@@ -0,0 +1,36 @@
+import type { ReactNode } from 'react';
+import { Label, Textarea } from 'cloudrite';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
+
+);
+
+export const Disabled = () => (
+
+
+
+);
diff --git a/.design-sync/previews/Toggle.tsx b/.design-sync/previews/Toggle.tsx
new file mode 100644
index 0000000..519fbd6
--- /dev/null
+++ b/.design-sync/previews/Toggle.tsx
@@ -0,0 +1,53 @@
+import type { ReactNode } from 'react';
+import { Toggle } from 'cloudrite';
+import { Bold, Italic, Underline } from 'lucide-react';
+
+// Cloudrite is dark-only — foreground-coloured text and `border-border` are
+// invisible on the preview harness's white card body. Stories render on the
+// brand surface, which is also how a real screen is built.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+export const States = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export const Outline = () => (
+
+
+ Auto-renew
+
+ Maintenance mode
+
+
+
+);
+
+export const Sizes = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/.design-sync/previews/Tooltip.tsx b/.design-sync/previews/Tooltip.tsx
new file mode 100644
index 0000000..aac5d9b
--- /dev/null
+++ b/.design-sync/previews/Tooltip.tsx
@@ -0,0 +1,28 @@
+import type { ReactNode } from 'react';
+import { Button, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'cloudrite';
+import { Info } from 'lucide-react';
+
+// Cloudrite is dark-only — the overlay surfaces read correctly only against the
+// brand background, not the harness's white card body.
+const Surface = ({ children }: { children: ReactNode }) => (
+
{children}
+);
+
+// Tooltip must be inside TooltipProvider — outside one it throws rather than
+// rendering, so the provider is part of every composition.
+export const Open = () => (
+
+
+
+
+
+
+
+ Measured across all Auckland edge nodes
+
+
+
+
+);
diff --git a/.design-sync/shims/next-link.tsx b/.design-sync/shims/next-link.tsx
new file mode 100644
index 0000000..76cb763
--- /dev/null
+++ b/.design-sync/shims/next-link.tsx
@@ -0,0 +1,37 @@
+// `next/link` stand-in for the design-system bundle.
+//
+// Header, Hero and Footer navigate with next/link. The real one reads Next's
+// AppRouterContext, which doesn't exist outside a Next app — pulling it into the
+// bundle would drag the router runtime in and throw on mount in every preview
+// and every design the agent builds.
+//
+// Routed here by compilerOptions.paths in .design-sync/tsconfig.ds.json, which
+// the converter feeds to esbuild. Renders the plain anchor Next renders anyway,
+// and drops the router-only props so React doesn't warn about unknown DOM
+// attributes.
+
+import * as React from 'react';
+
+type UrlObject = { pathname?: string; query?: unknown; hash?: string };
+
+export type LinkProps = Omit, 'href'> & {
+ href: string | UrlObject;
+ prefetch?: boolean | null;
+ replace?: boolean;
+ scroll?: boolean;
+ shallow?: boolean;
+ passHref?: boolean;
+ legacyBehavior?: boolean;
+ locale?: string | false;
+};
+
+const Link = React.forwardRef(function Link(
+ { href, prefetch, replace, scroll, shallow, passHref, legacyBehavior, locale, ...rest },
+ ref,
+) {
+ const resolved =
+ typeof href === 'string' ? href : `${href?.pathname ?? ''}${href?.hash ?? ''}` || '#';
+ return ;
+});
+
+export default Link;
diff --git a/.design-sync/tailwind-entry.css b/.design-sync/tailwind-entry.css
new file mode 100644
index 0000000..8e433af
--- /dev/null
+++ b/.design-sync/tailwind-entry.css
@@ -0,0 +1,49 @@
+/* Design-sync Tailwind entry.
+ *
+ * The app's own app/globals.css is a Tailwind v4 *source* file (`@import
+ * 'tailwindcss'`), not a shippable stylesheet, and it leans on Next to supply
+ * two things the DS bundle has to supply itself:
+ *
+ * 1. Utilities. Compiled here via @tailwindcss/cli into .design-sync/compiled.css.
+ * 2. Font variables. next/font/google assigns --font-sans / --font-body on
+ * at runtime, so globals.css can write the self-referential
+ * `--font-sans: var(--font-sans), 'Space Grotesk', …`. Outside Next that
+ * cycle is invalid at computed-value time and the whole declaration drops,
+ * leaving every font-sans/font-body utility unset. The @theme block below
+ * re-declares both with real family stacks.
+ *
+ * Regenerate whenever component sources or authored previews change:
+ * pnpm exec @tailwindcss/cli -i .design-sync/tailwind-entry.css -o .design-sync/compiled.css --minify
+ */
+
+@import '../app/globals.css';
+@import './fonts/fonts.css';
+
+/* Tailwind v4 auto-detects sources from this file's directory, which would miss
+ * the repo's component tree. Name both scan roots explicitly. */
+@source '../components';
+@source './previews';
+
+@theme inline {
+ --font-sans: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
+ --font-body: 'Inter', ui-sans-serif, system-ui, sans-serif;
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
+}
+
+/* app/globals.css puts the dark palette on :root, so the DS is dark by default
+ * (matching cloudrite.co.nz). Tell the UA so form controls and scrollbars match. */
+:root {
+ color-scheme: dark;
+}
+
+/* Designs built with this DS get the dark page by default. Deliberately `body`
+ * only, at plain specificity: the preview-card harness sets its own
+ * `body{background:#fff}` in a later inline