# Build a wedding gift list with the Confetti theme

You are building one page in an existing project: the gift list of a summer wedding, with a short note, a grid of gifts with drawn pictures, prices and a way to reserve each one, and a shared contribution toward one larger gift with a meter and a form. Follow this brief closely. `DESIGN.md` decides the tokens: colors, type, spacing steps and corners. This brief decides the layout, its widths included, and which token goes where. If this brief names a color, a type size, a spacing step or a corner that `DESIGN.md` does not have, `DESIGN.md` wins.

## Before you start

1. **Check the theme is installed.** The project should have `DESIGN.md` in its root and the Confetti variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/confetti.json` and save https://strictthemes.com/themes/confetti/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/confetti/tailwind.css instead. If the global CSS still imports a font the theme does not use (a new shadcn project imports Geist), remove that import, and uninstall its package if nothing else uses it. Keep every font import the theme's install added, also one for a style no page uses yet.
2. **Read `DESIGN.md` from start to finish.** It is the source of truth for every visual decision.
3. **Look at the project before writing code.** Use its framework, router and file layout. For controls (buttons, inputs, tabs, menus, dialogs, accordions, data tables) use the components in `components/ui`, and add a missing one with `npx shadcn@latest add <name>` rather than writing your own, unless this brief names a plain element. Layout is plain elements with theme classes: sections, panels, figures and lists need no component.
4. **Teach `cn()` the theme's type classes.** shadcn's class merger does not know `text-display` or `text-h1`. It takes them for colors, so `cn("text-label text-muted-foreground")` silently drops one of the two, and a type class passed to a stock component does not replace its size. In a new shadcn project, `lib/utils.ts` only passes `cn` on from the `cn` package. Unless the file already holds the lines below, change it to:

   ```ts
   import { createCn } from "cn/config"

   // The theme's type classes are font sizes, not colors.
   export const cn = createCn({
     extend: {
       classGroups: {
         "font-size": [{ text: ["display", "h1", "h2", "body", "label"] }],
       },
     },
   })
   ```

   Then make every file in `components/ui` use it: where one has `import { cn } from "cn"`, change that line to import `cn` from the project's utils file (`@/lib/utils` in a default setup). Do the same for each component you add later. If the project uses `tailwind-merge` instead of the `cn` package, its components already import from the utils file: pass the same object to `extendTailwindMerge` and keep `clsx`.
5. **Write real content.** If the user gave you details about their product, use them. Otherwise use the sample product in this brief and write the rest of the copy yourself: specific and plain, the way the samples read. Invent the company and people names; do not use real ones. Never use lorem ipsum. A link with no destination yet points to `#`. Set the page's `<title>` and meta description.

## The theme in short

- **Fonts:** Caprasimo for headings, Nunito for body text, Ubuntu Sans Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 64px/400, `text-h1` 40px/400, `text-h2` 24px/400, `text-body` 17px/400, `text-label` 13px/500.
- **Font per size:** a type class does not set the font family. Headings (`<h1>` to `<h6>`) get the heading font on their own; on any other element add the font class: `font-heading` with `text-display`, `text-h1`, `text-h2`; `font-mono` with `text-label`.
- **Spacing scale**, with the Tailwind step in brackets: 4px (`1`), 8px (`2`), 12px (`3`), 24px (`6`), 40px (`10`), 88px (`22`).
- **Corners:** `rounded-sm` 8px, `rounded-md` 14px, `rounded-xl` 24px (the `lg` corner in `DESIGN.md`). `rounded-lg` is 14px as well: it is shadcn's name for the base corner, the one its stock buttons and inputs use.
- **Colors (light mode):** primary #4A2A63, on-primary #FFFFFF, secondary #CDB4DB, on-secondary #2E1A3F, tertiary #A2D2FF, on-tertiary #2E1A3F, neutral #6B5480, background #FBF7FD, surface #FFFFFF, foreground #2E1A3F, border #DCCBE6, error #B3123F, blush #FFC8DD, on-blush #2E1A3F, candy #FFAFCC, on-candy #2E1A3F, mist #BDE0FE, on-mist #2E1A3F.

### How Confetti pages are laid out

- **Pastel blocks:** a large block of color is `bg-blush text-blush-foreground`, `bg-candy text-candy-foreground` or `bg-mist text-mist-foreground`, the pair always together on the block itself. The three pastels and their ink are the same in both modes, so the ink class keeps text on a pastel plum (`#2E1A3F`) in dark mode too. A block is `rounded-xl p-6 md:p-10` with the fill and the ink, `flex flex-col gap-6` inside, no border and no shadow. Everything inside it inherits the ink: no element in a block gets a text color class of its own, so never `text-foreground`, `text-muted-foreground` or `text-primary` there, also not on the labels and small figures that the notes below make muted elsewhere. A block holds a heading and a few facts as text, and nothing that takes focus or carries its own color: no link, button, field, error line, badge or card. In dark mode the main button and the focus outline are pink, the candy color, so they would vanish on a block; the action that goes with a block sits on the paper right after it. A mark drawn on a block (a meter) uses the ink as a fill or a border: `bg-mist-foreground`, `border-mist-foreground`, or the same with `blush` or `candy`. One pastel per block, white paper between blocks: two blocks never touch, and a block is never inside a card. Where a block is a section, the `<section>` itself carries the block's classes.
- **Page:** one `<div>` wraps the whole page with `min-h-screen bg-background text-body text-foreground`. Inside it sit the `<header>`, a `<main>` and the `<footer>`.
- **Container:** the header's row, the `<main>` and the footer's row each sit in a centered container, `mx-auto max-w-[1088px] px-6`, so the content is at most 1040px wide, the grid in `DESIGN.md`, with 24px at each side at every width. This width is the one arbitrary value allowed.
- **Main:** the `<main>` element itself carries the container classes, `flex flex-col gap-22` (88px between sections) and `pt-10 pb-22`. Its items are the page's sections, in the order the brief gives.
- **Sections:** where this brief calls something a section, it is a `<section>` with `aria-labelledby` pointing at its heading; without a visible heading it gets an `aria-label`. There are three kinds, and the brief says which:
  - A full-width section is `flex flex-col gap-6`: the heading (or a heading group) first, the content after it.
  - A section in the centered column, the way a card is read, is `grid grid-cols-1 md:grid-cols-12` holding one `<div>` with `flex flex-col gap-6 md:col-span-8 md:col-start-3`, which holds the heading and the content. A pastel block in the centered column is that `<div>`, with the block's classes added.
  - A section of parts side by side is itself the grid: `grid grid-cols-1 gap-10 md:grid-cols-12 md:gap-x-6 md:items-start`, and each part gets the `md:col-span-*` (and `md:col-start-*`) the brief gives it. Below `md` the parts stack in their order, 40px apart.
- **Grid of cards:** a row of cards of the same kind is `grid grid-cols-1 gap-6 md:grid-cols-12`, `gap-6` in both directions, each card with the column span the brief gives. Cards in one row stretch to the same height.
- **Vertical rhythm:** written as gaps on stacks, never as top margins and never as `space-y-*`. Every element that holds more than one thing in a column is `flex flex-col` with one of these gaps: `gap-2` between a label and what it labels, between a heading and a label or small figure right above or under it, and between a field's label, the field, its helper text and its error; `gap-3` between a heading and its paragraph, between paragraphs, and between the parts inside a card; `gap-6` between the fields of a form, between a section's heading and its content, between cards of the same kind, and between the groups of a pastel block; `gap-22` between sections, on the `<main>`. A row of things side by side is `flex flex-wrap items-center gap-3`, or `gap-2` for things that belong together (an icon and its word). Where this brief gives no gap, use the one of these that fits.
- **Header:** the first thing on the page is a `<header>` with `border-b border-border`, running the full width. Inside it, one `<div>` carries both the container classes and the row's classes, `flex flex-wrap items-center gap-x-6 gap-y-2 py-3`. On the left the couple's names, "Clem & Rafe", a link to `/` in `inline-flex h-11 items-center font-heading text-h2 text-foreground`, never marked as the current page. Then the `<nav>`, the page's only one, with the `aria-label` "Main" and `w-full md:ml-auto md:w-auto`, so from `md` up it sits at the row's right end and below `md` it takes a line of its own under the names. It holds a `<ul>` with `flex flex-wrap gap-x-6` and four links, in this order: "Reply" to `/rsvp`, "Gifts" to `/gifts`, "The day" to `/day`, "Guestbook" to `/guestbook`. Each link is `inline-flex h-11 items-center font-mono text-label text-foreground underline-offset-4 hover:underline transition-colors duration-150`. The current page's link, where the brief names one, keeps its address and adds `aria-current="page"` and `underline`. The four fit on one line at 375px, so there is no "Menu" button.
- **Footer:** a `<footer>` with `border-t border-border`, running the full width. Inside it, one `<div>` carries both the container classes and `flex flex-col gap-3 py-10 md:flex-row md:items-center md:justify-between`: the names as a `<p>` in `font-heading text-h2`, "Clem & Rafe" (not a heading and not a link), then a `flex flex-col gap-2 md:items-end` with two labels in `text-muted-foreground`: `Saturday 17 July 2027 · Quillbrook Barn, Little Saltry`, the date a `<time dateTime="2027-07-17">`, and `Questions: hello@clemandrafe.example`, the address a text link to `mailto:hello@clemandrafe.example`.
- **The occasion:** the wedding of Clem Ashdown and Rafe Linnell on Saturday 17 July 2027 at Quillbrook Barn, Saltry Lane, Little Saltry, ZQ4 7HB, a village in the English countryside near the market town of Market Fenley. Replies by Friday 30 April 2027. Every page uses these names, this date and this place, word for word.
- **Headings:** the `<h1>` is `text-h1`, and `text-display` from `md` up only where this brief says so (`text-h1 md:text-display`). An `<h2>` is `text-h2`. An `<h3>` is `text-h2` too, unless this brief gives it `text-body`. Headings get the heading font on their own; any other element in `text-display`, `text-h1` or `text-h2` adds `font-heading`. Caprasimo has one weight, already bold, and no italic: never put `font-bold`, `font-semibold` or `font-medium` on a heading or on any text in `font-heading`, and never slant it. Sentence case, no full stop.
- **Labels:** field labels, nav links, button and badge text, captions, dates, times, addresses in a short line, counts of guests and prices are labels: `font-mono text-label`, in sentence case. Nothing is uppercased, in the text or with a class. A label is a short fragment; helper text and error lines use the label style too and may be one sentence. Parts of a label line are joined with ` · `. A label on its own line is a block element such as a `<p>`.
- **Small figures:** a date, a time, a count or a price note that this brief calls a small figure is `font-mono text-label tabular-nums text-muted-foreground`, on the element that holds the whole figure.
- **Text color:** on paper and on cards, headings, body text, prices, field labels and the names of things are `text-foreground`. Other labels, small figures, captions and helper text are `text-muted-foreground`. Paragraphs are never muted. `text-primary` is only for links in running text. On a pastel block every text inherits the block's ink, as the first note says.
- **Writing:** warm and plain, in short sentences, addressing the guest as "you" and written by the couple as "we". No exclamation marks in headings. An error says what to do.
- **Measure:** a paragraph of body text is at most `max-w-xl` wide.
- **Dates and times:** a date or a clock time is a `<time>` with a `dateTime` wherever it appears, in a sentence, a label or a row too, except inside an `<option>` or an `aria-hidden` drawing: `<time dateTime="2027-07-17">Saturday 17 July 2027</time>`, `<time dateTime="14:00">14:00</time>`. A date is written with the weekday, the day, the month in words and the year, with no comma (`Saturday 17 July 2027`), or shorter only where this brief shows it (`30 April`). A clock time is 24-hour with two-digit hours, `09:30`, `14:00`. A time in a row takes `tabular-nums`. A copyright year is plain text.
- **Prices:** whole pounds with the pound sign and no pence: `£85`, `£900`. A price is a label in `text-foreground` with `tabular-nums`.
- **Links:** a link in running text is `text-primary underline underline-offset-4 hover:text-foreground` with `transition-colors duration-150`, and inherits its size. A link whose destination this brief does not name points to `#`.
- **Added components:** a component you add with `npx shadcn@latest add` during the build (an `Input`, a `Textarea`, a `Badge`, an `Accordion`) gets the `cn` import, the focus outline only if it takes focus on the page, and the edits these notes give its file.
- **Buttons:** the shadcn `Button` with `className="h-11 rounded-md px-6 font-mono text-label"`: 44px tall, 14px corners, sentence case, two to four words. The default variant (plum, pink in dark mode) is for the page's one main action; the brief names it, and no page has two. Every other button is `variant="secondary"`, lilac with ink in both modes. Its stock hover mixes in the foreground and can hardly be told from its resting color: in `components/ui/button.tsx`, replace the `secondary` variant's `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80`, once, on every page. Quiet buttons (the unchosen ones in a group, a stepper's minus and plus) are `variant="outline"`; on a page that uses one, in the `outline` variant replace `bg-background` with `bg-card` and take out `dark:bg-input/30`, so it is the card's white in both modes, its hover classes unchanged. A button that sends a form needs `type="submit"`: this `Button` is `type="button"` unless told otherwise. A button that its own click disables gets `focusableWhenDisabled`, so keyboard focus stays on it: only a stepper's minus and plus. Base UI then sets `aria-disabled="true"` and `data-disabled` in place of the `disabled` attribute, so the stock `disabled:` classes do nothing: on a page that uses the prop, and only there, add `data-disabled:pointer-events-none data-disabled:opacity-50` beside them in `components/ui/button.tsx`. A call to action that goes to another page is an `<a>` with the same classes: write `cn(buttonVariants({ variant }), "h-11 rounded-md px-6 font-mono text-label")`, because `buttonVariants` alone does not merge classes. An icon inside a button comes before the text.
- **Button groups:** a set of buttons where one is chosen is a `role="group"` with `flex flex-wrap gap-2`, named by its visible label (`aria-labelledby`). The chosen button has `aria-pressed="true"` and `variant="secondary"`; the others have `aria-pressed="false"` and `variant="outline"`. Not `Tabs`: there are no tab panels.
- **Steppers:** a number set with minus and plus buttons is a `role="group"` named by its visible label (`aria-labelledby`), with `flex items-center gap-2`: a minus `Button`, the value, a plus `Button`. Each button is `variant="outline" size="icon"` with `className="size-11 rounded-md"` (not the text button's classes) and an `aria-label` that says what it does, holding the `Minus` or `Plus` icon. The value is a `<span>` in `inline-flex min-w-24 justify-center font-mono text-label text-foreground tabular-nums` with `aria-live="polite"`, written with its unit (`2 guests`). At the lowest and highest value the matching button is `disabled` with `focusableWhenDisabled`.
- **Inputs:** the shadcn `Input` and `Textarea` with `className="h-11 rounded-md px-3 text-body md:text-body"` (a `Textarea` takes `min-h-32 rounded-md px-3 py-2 text-body md:text-body` instead, with no `h-11`). Fields only ever sit in cards, and their fill is the card's, like the select's: in `components/ui/input.tsx` and `components/ui/textarea.tsx`, replace the stock `bg-transparent` and `dark:bg-input/30` with `bg-card` (in `input.tsx`, `file:bg-transparent` stays), and delete `dark:aria-invalid:border-destructive/50`, so an error border is the same red as the select's in dark mode. Every field has a visible `<label>` above it with `block`, a label in `text-foreground`, tied to it with `htmlFor`. The label, the field, helper text and the error sit in a `flex flex-col gap-2`. Helper text is a label in `text-muted-foreground`, and the field names it in `aria-describedby`. A field that may be left empty says so in its label: `A note (optional)`.
- **Selects:** a choice from a short list is a native `<select>` with `h-11 w-full rounded-md border border-input bg-card px-3 text-body text-foreground aria-invalid:border-destructive` and the focus outline from the rules (`focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring`). Its first option is an empty choice in words ("Choose a dinner"). It gets the same label, stack and error handling as any field.
- **Forms:** a form sits in a card, the card is the wrapper for the form and the confirmation that replaces it, and the form's heading sits above the card, in the section's stack, not inside it. In place of the card's column stack, such a card is a plain block (`rounded-xl border border-border bg-card p-6` with no `flex`), holding the `<form>` and, after it, the status element. The `<form>` has `noValidate`, an `aria-label` and `flex flex-col gap-6`; its last item is the submit button, full width below `md` (`w-full md:w-auto md:self-start`).
- **Errors:** an error is a line under its field, inside the field's stack: `flex items-start gap-2 font-mono text-label text-destructive`, holding the `CircleAlert` icon (`size-4 shrink-0`, `aria-hidden="true"`; `items-start` sets it on the first line) and one sentence that says what to do. The field gets `aria-invalid` and adds the line's `id` to its `aria-describedby`. Fields are checked in code when the form is sent, and focus moves to the first field with an error; before the first send no error shows. To focus a field in the same step that adds its error line, set the errors in state and move the focus in an effect that runs after that render (or wrap the state change in `flushSync`), so the line is in the page when focus lands. An email is well formed when, trimmed, it matches `/^[^\s@]+@[^\s@]+\.[^\s@]+$/`. An error line is removed when its own field changes (on `input` or `change`); every other error stays until the next send.
- **Confirmations:** a confirmation is plain text in `text-foreground`, no colored box: a heading at the level this brief gives and a sentence or two. It goes into an element with `role="status"` that is in the card from the start, empty, so that its message is announced. It is never hidden (no `hidden` attribute, no `empty:hidden`) and has no classes and no margin; empty, it takes no space. The message in it is a `<div>` with `flex flex-col gap-3`, rendered only once there is a message, and the form is removed when it appears, so the message stands where the form stood. Give the message's heading `tabIndex={-1}` and move focus to it, because the button that had focus is gone.
- **Cards:** a card is a plain element with `rounded-xl border border-border bg-card p-6` (24px corners, 24px padding, a 1px lilac border, no shadow) and `flex flex-col gap-3` inside, unless this brief says otherwise. Not the shadcn `Card`, which ships its own padding and a ring. Never a card inside a card, never a bordered box inside a card, and never a pastel block inside a card: divide a card's parts with a 1px `border-t border-border` and `pt-3`.
- **Cards with a picture:** a card whose picture fills its top edge is `flex flex-col overflow-hidden rounded-xl border border-border bg-card` with no padding: the picture is a plain `<div>` first in it, with no corners, border or caption of its own, and the text follows in a `<div>` with `flex flex-1 flex-col gap-3 p-6`.
- **Pictures:** there are no image files. A picture is a plain `<div>` with the aspect ratio this brief gives (`aspect-3/2`), `bg-muted`, `role="img"` and an `aria-label` that says what it shows, drawn from plain elements inside it with only the classes this brief gives. A drawn shape is a flat fill with no border and no shadow, in one pastel (`bg-blush`, `bg-candy` or `bg-mist`, one per picture) and, where the brief says, `bg-secondary`. A shape less than 24px on its shorter side has square corners (no `rounded-*`); a larger one takes `rounded-sm`, or `rounded-md` where the brief says. No shape is round.
- **Rows:** a list where each row has a name or a time on one side and its value on the other is a `<ul>`, `<ol>` or `<dl>` with no markers and no padding, and `divide-y divide-border`, so a 1px rule falls between rows and none above the first or below the last. Each row is `flex items-baseline gap-6 py-3 first:pt-0 last:pb-0`, with `justify-between` where the value stands at the right end. On a pastel block rows have no rules: they are a `flex flex-col gap-3`.
- **Badges:** the shadcn `Badge` with `className="h-auto rounded-sm border-transparent bg-tertiary px-2 py-1 font-mono text-label text-tertiary-foreground"`: sky blue with ink, in both modes. One or two words, no icon. Sky blue is a fill only: never `text-tertiary` or a sky-blue border. Badges sit on paper or on a card, never on a pastel block.
- **Avatars:** a circle with the person's initials: `flex size-11 shrink-0 items-center justify-center rounded-full bg-secondary font-mono text-label text-secondary-foreground`, with `aria-hidden="true"` beside the written name. 44px, under the theme's 64px limit for full rounds.
- **Meters:** a bar that shows a share sits on a pastel block and is drawn in its ink: a track `h-6 w-full overflow-hidden rounded-sm border` with the block's ink border (`border-mist-foreground` on mist), holding a fill `h-full` with the block's ink fill (`bg-mist-foreground`), whose width is set with an inline `style`. The track has `aria-hidden="true"`; the numbers beside it say the share in words.
- **Icons:** from `lucide-react`, with `size-4`, `strokeWidth={1.5}` and `aria-hidden="true"`, only where this brief or these notes name one. Beside a line of body text in an `items-start` row, the icon gets `mt-1` to sit on the first line. Never an emoji, however festive the page.
- **Corners:** `rounded-sm` (8px) for badges, meters and drawn shapes; `rounded-md` (14px) for buttons, inputs and selects; `rounded-xl` (24px, the spec's `lg`) for cards and pastel blocks. `rounded-lg` is 14px too, shadcn's name for the base corner; write `rounded-md`. `rounded-full` only for avatars.
- **Surfaces:** flat, like a printed card. No shadows, blur, gradients or glows in the code you write, and no decoration drawn in code: no confetti, dots, streamers, hearts or flourishes. A stock component that floats over the page keeps the one soft shadow it ships with.
- **Motion:** in the code you write, the only transition is color, `transition-colors duration-150`. Nothing moves on scroll. Stock components keep the motion they ship with.

## The page

Where this section and the layout notes above differ, this section wins.

Sample content, if the user gave none: the gift list of Clem Ashdown and Rafe Linnell, who marry on Saturday 17 July 2027 at Quillbrook Barn, Little Saltry, and are setting up their first home in the village. Six things for the house, two of them already reserved by other guests, and one bigger gift that guests share: two bicycles with baskets, £900 in all, of which 14 guests have given £540 so far. Write any other copy yourself, warm and plain.

The six gifts, in this order:

1. Linen tablecloth, £85: "Long enough for the table Rafe is building: eight places, and room for the jam." Free.
2. Four stoneware bowls, £60: "Deep enough for soup, wide enough for pasta." Reserved.
3. A picnic hamper for two, £120: "For Sunday afternoons on the hill behind the village." Free.
4. Six tumblers, £48: "Thick glass that survives the dishwasher and the garden." Reserved.
5. A bookshelf for the hall, £210: "Oak, five shelves, and room at last for Clem's cookbooks." Free.
6. A window box of herbs, £40: "For the kitchen sill: thyme, mint, sage and chives." Free.

Build these parts in order.

### 1. Header

The header from the layout notes, with "Gifts" as the current page; its link keeps `/gifts`.

### 2. Opening

A full-width section that is a pastel block in candy, as the layout notes describe it, with its `aria-labelledby` pointing at the `<h1>`. The block element itself carries `bg-candy text-candy-foreground`, the pair from the first layout note, so its text stays plum in dark mode; nothing inside it gets a text color class of its own. It holds one `flex flex-col gap-3`: a `flex flex-col gap-2` with a label, `Gift list`, and the `<h1>` "A few things for our home" in `text-h1 md:text-display`; then one paragraph in `text-body` with `max-w-xl`: "Coming is the gift. If you would like to bring something too, here are a few things for our first home in Little Saltry. Reserve one, and it shows as taken for everyone else."

### 3. Things for the house

A full-width section, with its `aria-labelledby` pointing at the `<h2>`. It opens with a `flex flex-col gap-2` holding the `<h2>` "Things for the house" and, under it, a `<p>` with `role="status"` as a small figure: `4 of 6 gifts still free`. Unlike the confirmations in the layout notes, it holds its text from the start: it is a count that changes, not a message that replaces a form. At one it reads `1 of 6 gifts still free`.

Then, in the section's `gap-6`, the six gifts as a grid of cards from the layout notes, each card `md:col-span-6 lg:col-span-4`: two to a row from `md`, three from `lg`, one below `md`. Add the badge with `npx shadcn@latest add badge` if the project has none. Each card is a `<li>` of a `<ul>` that carries the grid classes, and is a card with a picture, as the layout notes describe it:

- **The picture,** `flex aspect-3/2 items-center justify-center bg-muted`, with `role="img"` and the `aria-label` given below. Inside it one drawing, built with exactly these classes:
  1. Tablecloth, "A table laid with a pink linen cloth": a `flex flex-col items-center` holding the cloth, `h-16 w-40 rounded-sm bg-blush`, and under it a `flex w-36 justify-between` with two table legs, each `h-10 w-2 bg-secondary`.
  2. Bowls, "A stack of four pale blue bowls": a `flex flex-col items-center gap-1` holding four bowls, each `h-6 rounded-sm bg-mist`, from the top `w-20`, `w-24`, `w-28`, `w-32`.
  3. Hamper, "A pink picnic hamper with a handle": a `flex flex-col items-center gap-1` holding the handle, `h-2 w-16 bg-secondary`, and the basket, `h-16 w-32 rounded-md bg-candy`.
  4. Tumblers, "Six pale blue tumblers in a row": a `flex items-end gap-2` holding six tumblers, each `h-12 w-6 rounded-sm bg-mist`.
  5. Bookshelf, "Five pink books standing on a shelf": a `flex flex-col items-center` holding a `flex items-end gap-1` of five books, each `w-4 bg-blush`, with the heights `h-16`, `h-12`, `h-14`, `h-10`, `h-16`, and under them the shelf, `h-2 w-36 bg-secondary`.
  6. Window box, "A pink window box with five herbs growing in it": a `flex flex-col items-center gap-1` holding a `flex items-end gap-2` of five herbs, each `w-4 bg-secondary`, with the heights `h-8`, `h-12`, `h-10`, `h-12`, `h-8`, and under them the box, `h-8 w-36 rounded-sm bg-candy`.

  The shapes have no border, no shadow and no other classes; the ones under 24px across have square corners, as the Pictures note says.
- **The text,** in the card's `<div>` with `flex flex-1 flex-col gap-3 p-6`: the gift's name as an `<h3>` in `text-h2`; its sentence in `text-body`; a row, `flex flex-wrap items-center gap-3`, with the price as a label in `text-foreground` with `tabular-nums` (`£85`) and, for a reserved gift, the badge from the layout notes, "Reserved"; and last, with `mt-auto`, the action, so the actions of a row line up:
  - A free gift: a `Button` with `variant="secondary"` and `w-full`, "Reserve this gift", with the `aria-label` "Reserve this gift: Linen tablecloth" (the visible words, a colon and the gift's name). These are not the page's main action: there are four of them on screen, so they are lilac.
  - A gift reserved by another guest: no button. In its place a `<p>` with `mt-auto flex h-11 items-center font-mono text-label text-muted-foreground`: "Another guest has this one".

### 4. One bigger gift

A section of parts side by side, from the layout notes, with its `aria-labelledby` pointing at the `<h2>` in the block.

**First part,** `md:col-span-7`: a pastel block in mist, as the layout notes describe it. The block element itself carries `bg-mist text-mist-foreground`, the pair from the first layout note, so its text stays plum in dark mode; nothing inside it gets a text color class of its own. In the block's `gap-6`, two groups:

- A `flex flex-col gap-3` holding a `flex flex-col gap-2` with a label, `One bigger gift`, and the `<h2>` "Two bicycles for the lanes"; then one paragraph in `text-body`: "We would like to ride the lanes around Little Saltry on two bicycles with baskets. Any share helps, and every pound goes to the bicycles."
- A `flex flex-col gap-3` holding the figure's row, `flex flex-wrap items-baseline gap-2`, with the amount given so far as a `<p>` in `font-heading text-h1 tabular-nums`, `£540`, and beside it a label, `of £900`; then the meter from the layout notes, in the mist block's ink (`border-mist-foreground` on the track, `bg-mist-foreground` on the fill), its fill at 60%; then a label: `14 guests so far · 60%`. At one guest it reads `1 guest so far`.

**Second part,** `md:col-span-5`: the form card, as the Forms note describes it. Here, instead of a heading above the card, the block's `<h2>` heads the section, and the card is the part itself. Add the field with `npx shadcn@latest add input` if the project has none. The `<form>` has the `aria-label` "Add to the bicycles". Its fields, each in its own `flex flex-col gap-2`:

1. **Name**: the label "Your name", an `Input` with `autoComplete="name"`.
2. **Email**: the label "Email address", an `Input` of type `email` with `autoComplete="email"`, and the helper text `We write once, with how to send your share.`
3. **Amount**: the label "Your share in pounds", an `Input` of type `text` with `inputMode="numeric"` and `autoComplete="off"`, and the helper text `From £5 to £360, what is still needed`. The `£360` is the amount still needed, read from state.

The last item of the form is the page's one main action: the default `Button`, `type="submit"`, "Add my share". After the form, in the card, the empty `role="status"` element.

### 5. Something else in mind?

A section in the centered column from the layout notes, with an `<h2>` "Something else in mind?" and, in the column's `gap-6`, one paragraph in `text-body`: "Anything handmade, grown or written makes us just as happy. There is a table for cards and parcels by the barn door."

### 6. Footer

The footer from the layout notes.

### Behavior

- Keep the gifts in component state, each with its name, price and state: free, reserved by another guest, or reserved by you. The count in the status line reads from the same state.
- Clicking "Reserve this gift" reserves that gift for you: its card shows the "Reserved" badge beside the price, and the same `Button` element (do not render a new one) now reads "Cancel my reservation", with the `aria-label` "Cancel my reservation: Linen tablecloth", still `variant="secondary"`. Focus stays on it. Clicking it again gives the gift back: the badge goes and the button reads "Reserve this gift" again. The count follows each click: `3 of 6 gifts still free`. The two gifts reserved by other guests cannot be changed.
- Keep the amount given (starting at £540) and the number of guests (starting at 14) in state. The amount still needed is £900 minus the amount given. The percentage is the amount given over £900, rounded to a whole number.
- When the form is sent, check it in code:
  - Name: empty, or only spaces, gives "Write your name, so we know who to thank."
  - Email: empty gives "Write your email address."; a malformed one gives "Check the email address: it needs an @ and a domain."
  - Amount: trim it and allow one `£` in front. Empty gives "Write an amount in pounds."; anything but a whole number gives "Write a whole number of pounds, like 50."; under 5 gives "The smallest share is £5."; more than what is still needed gives "We only need £360 more. Write £360 or less.", with the amount still needed.
- Each error line follows the layout notes, which say when it shows, when it goes and how focus reaches it.
- When everything is good, add the share to the amount given and one to the number of guests, so the block reads, after £50, `£590`, `15 guests so far · 66%` and a fill at 66%. The form is replaced in its card by the confirmation in the status element: an `<h3>` in `text-h2`, "Thank you, Dev Raman" (the name as typed, trimmed), with `tabIndex={-1}`, and one sentence: "We have noted £50 toward the bicycles, and we will write to dev@example.com with how to send it.", with the amount and the address they typed. The heading and the sentence sit in the message `<div>`; no button follows. Move focus to the heading. The block is not a live region: the confirmation says the amount.
- The other links need no behavior.

## Rules

- **Colors:** only the theme's colors, through the semantic classes (`bg-background`, `text-foreground`, `bg-card`, `bg-primary`, `text-primary-foreground`, `text-muted-foreground`, `border-border` and so on). No hex, rgb or named colors in components, and no Tailwind palette classes such as `bg-blue-500` or `text-gray-600`.
- **Spacing:** padding, margin and gap use only the scale's steps (`1`, `2`, `3`, `6`, `10`, `22`) plus 0, 1px and 2px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-5` or `gap-9`. `auto` margins are fine. Widths and heights such as `w-64` or `size-4` are sizes, not spacing. This applies to the code you write: a stock component keeps the spacing inside it, unless this brief changes it.
- **Type:** every piece of text you write gets its size from one of `text-display`, `text-h1`, `text-h2`, `text-body`, `text-label`, set on the element or on a parent it inherits from. Put `text-body` on the outermost element of the page you write. No other sizes. A stock shadcn component keeps the size it ships with, unless `DESIGN.md` or this brief gives that component its own style. A type class does not replace a weight class: where a stock component ships `font-medium` and the text should be regular, add `font-normal`.
- **Fonts:** only the theme's fonts, through `font-sans`, `font-heading` and `font-mono`. Do not add a font.
- **Corners:** only the `rounded-*` classes, which the theme already sets. No arbitrary radius, and `rounded-full` only where `DESIGN.md` or this brief names a full round, such as an avatar or a status dot. The theme allows a full round on a box up to 64px across; Strict Check reports a larger one, and a pill counts by its height.
- **Contrast:** all text meets WCAG AA against its background.
- **Stock components:** a component from `components/ui`, and an `<a>` styled with `buttonVariants`, keeps the hover, pressed and disabled colors and the motion it ships with, unless this brief changes them. They are the theme's colors under other names.
- **Counts:** a number with a noun reads right at one: "1 set", not "1 sets". Where this brief gives the words for only one of the two, write the other the same way.
- **Sizes from data:** a height or a width that comes from data (a chart bar, a meter) is set with an inline `style`. That is the one use for an inline style.
- **Focus:** every link and control shows a 2px outline on keyboard focus: `focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring`. Keep `focus-visible:outline-solid` in it: in Tailwind 4 an `outline-none` on the same element, which stock components carry, otherwise hides the outline. An element that only gets focus from code (a heading or a message with `tabIndex={-1}`) gets the same outline written with `focus:` in place of `focus-visible:` (`focus:outline-solid focus:outline-2 focus:outline-offset-2 focus:outline-ring`): after a mouse click, a browser does not match `:focus-visible` on an element that code focuses. A link, a button or a field that code focuses keeps the `focus-visible:` classes it has: after a mouse click it shows no outline, which is how a browser treats every control, and is right. A stock component that ships its own focus ring (`Button`, `buttonVariants`, `Input`, `Textarea`, `Checkbox`, `Switch`, `AccordionTrigger`, the close button of a `Sheet`) must lose that ring. Do it once, in the component's file in `components/ui`, which is yours to change: replace its `focus-visible:ring-*` classes with the outline classes, and in a field replace its `aria-invalid:ring-*` classes, the `dark:` ones included, with `aria-invalid:ring-0`, or a field with an error shows a tinted ring. `Button` has two more sets. Its base classes also carry `aria-invalid:ring-*` classes: replace them with `aria-invalid:ring-0`, as in a field. Its `destructive` variant carries `focus-visible:ring-*` classes of its own, the `dark:` one included: delete them and put nothing in their place, because the base classes already give every variant the outline. Wherever this rule says replace, change the classes in the file and do not add `ring-0` beside them: an added class only wins where it comes later and passes through `cn`. The stock `focus-visible:border-ring` can stay. These ring changes go only into the files of components that this page uses and that can take focus on it: a `Badge` that is not a link keeps its ring classes. The `cn` import from the setup steps is another matter and goes into every file. A box that scrolls sideways (a code block, a wide table) gets `tabIndex={0}`, `role="region"`, an `aria-label` and the same outline, also at widths where it happens to fit. Where a focusable box touches the edges of a parent that has `overflow-hidden`, draw its outline inside it: `focus-visible:-outline-offset-2` in place of `focus-visible:outline-offset-2`.
- **Not allowed:** purple or multi-color gradients; emoji used as icons or bullets; glows and colored shadows.
- **Avoid:** a card inside a card.
- **Dark mode:** the page must work with the `dark` class on `<html>`. Because every color comes from a semantic class, this needs no extra code; do not write `dark:` color overrides. A new shadcn project follows the system's mode and switches on the `d` key: leave that as it is, and add no mode switch of your own. If the page has a native `<select>` or a date or time field, tell the browser the mode, or its list and its picker stay light in dark mode: in the global CSS, give `:root` `color-scheme: light` and `.dark` `color-scheme: dark`.

From the theme's own do's and don'ts:

- Do keep text in plum ink: a pastel is a fill, never a text color.
- Do keep buttons, links and fields on the paper: a pastel block holds text only.
- Do use one pastel per block, and white paper between blocks.
- Do say the date, the time and the place plainly, near the top.
- Don't scatter decoration: no confetti drawn in code, no gradients, no glows.
- Don't put a pastel block inside a card or a card inside a card.
- Don't introduce a color, size or radius that is not a token above.

## Check before you finish

Go through this list against the code you wrote, and fix anything that fails.

- [ ] Each gift card opens with a picture drawn from plain elements with role img and an aria-label, no image file is loaded, and no drawn shape has a border.
- [ ] Every price is a mono label in whole pounds, and the two gifts reserved by other guests carry a sky-blue Reserved badge with ink text and no button.
- [ ] The Reserve this gift buttons are lilac secondary buttons, each with an aria-label that names its gift, and Add my share is the only plum (default) button on the page.
- [ ] Reserving a gift adds its Reserved badge and turns its button into Cancel my reservation without moving focus, and the count of free gifts in its status line follows.
- [ ] The meter sits on the mist block and is drawn in its plum ink in both modes, the amount and the percentage beside it say the same in words, and a good share updates all three.
- [ ] The opening block element carries bg-candy text-candy-foreground and the bicycles block element bg-mist text-mist-foreground, so their text stays plum ink in dark mode, and nothing inside them has a text color class of its own.
- [ ] No color is written as a hex, rgb or Tailwind palette value in the page's code.
- [ ] Every padding, margin and gap in the code you wrote is a step of the spacing scale.
- [ ] Every text element you wrote gets its size from a type-scale class, its own or an inherited one. Visually hidden text is exempt.
- [ ] No file in `components/ui` imports `cn` from `"cn"` directly.
- [ ] Text contrast meets WCAG AA in light and dark mode.
- [ ] The page has one `<h1>`, headings are in order, and every image has alt text.
- [ ] Every interactive element can be reached with the keyboard and shows the focus outline from the rules.
- [ ] At 375px wide the page does not scroll sideways. Wide tables and code blocks scroll inside their own box.
- [ ] The project builds without errors or warnings.

Then tell the user which checks pass, which you could not verify, and anything in this brief you did not do and why.
