# Build a home page with the Atelier theme

You are building one page in an existing project: the opening page of a small fashion house, with one very large heading, one large picture placed off-center, the current collection, three pieces from it and a link onward. 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 Atelier variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/atelier.json` and save https://strictthemes.com/themes/atelier/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/atelier/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:** Bodoni Moda for headings, Hanken Grotesk for body text, Fragment Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 72px/500, `text-h1` 44px/500, `text-h2` 28px/500, `text-body` 16px/400, `text-label` 12px/400.
- **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`), 16px (`4`), 32px (`8`), 64px (`16`), 128px (`32`).
- **Corners:** `rounded-none` 0px.
- **Colors (light mode):** primary #141414, on-primary #FAF8F5, secondary #E4DFD6, on-secondary #141414, tertiary #DDBBAA, on-tertiary #141414, neutral #68635C, background #FAF8F5, surface #F1EEE8, foreground #141414, border #D6D1C8, error #A32020.

### How Atelier pages are laid out

- **Container:** one `<div>` wraps the whole page and is the container: `mx-auto max-w-[1320px] px-4 md:px-8`, plus `text-body`. This width is the one arbitrary value allowed. Inside it sit the `<header>`, a `<main>` holding everything between the two, and the `<footer>`.
- **Grid:** a section's content sits in a grid: `grid grid-cols-1 md:grid-cols-12 md:gap-x-8`. The grid sets columns only, never `gap-y-*`: every vertical distance comes from the rhythm rule. Below `md` every grid item takes the full width. Leave the columns this brief does not fill empty; center nothing to fill them. From `md` up every grid item gets `md:col-span-*`, because an item without one takes a single column: "columns 1–12" is `md:col-span-12`. It also gets `md:col-start-*` when it starts after column 1, and `md:col-start-1` when it must start a new row under an item that leaves room beside it; or the grid puts it in that room. An item whose columns overlap those of the item before it always starts a new row. Where a brief puts "a section's content in columns 5–12", the `<section>` is the grid and the content is one `<div>` with those column classes. Where a list "is itself the grid", the `<section>` is not a grid: the heading above the list sits directly in it at the full width.
- **Vertical rhythm:** written as a top margin on the lower element (`mt-*`), never as `space-y-*`, `gap-y-*`, a bottom margin or padding. Between a label and the one thing it names (a form label and its field, the "Size" line and its buttons, the house's name and the address in the footer): `mt-2`. Between a heading and the text under it, and between paragraphs: `mt-4`. A label line above a heading, and a label heading above its text, its `<address>` or its `<dl>`, count as headings: `mt-4` to what follows, or `mt-8` where this brief says so. Between blocks inside a section (a paragraph and a button, two figures, a form and the text above it): `mt-8`. Between a picture and its caption: `mt-2`. Where two grid items share a row from `md` up, the second has its phone margin and `md:mt-0`, unless this brief gives a margin from `md` up. Inside a flex row (the header, a caption line, a group of buttons) the space is the row's `gap-*` as this brief states it.
- **Sections:** `<main>` holds a stack of `<section>` elements. The first has `mt-16`. Every other section has `mt-16 md:mt-32`: 128px between sections on a large screen, 64px on a phone. That top margin is the whole gap; sections have no padding and no borders unless this brief gives one.
- **Header:** the `<header>` is itself the row, with `py-8`: `flex flex-col items-start gap-4 md:flex-row md:items-baseline md:justify-between`. On the left the house's name, a link to `/` in `font-heading text-h2`, styled as a header link and never marked as the current page. It is not a heading element: upright, with no weight class. On the right a `<nav>` with the `aria-label` "Main" and no classes, holding a `<ul>` with `flex flex-wrap gap-x-8 gap-y-2` of three links as labels, in this order: "Collection", "House" and "Appointment", each to the page of that name. Each brief names the current one, if any. Below `md` the links sit under the name; there is no "Menu" button. The header has no border.
- **Footer:** a `<footer>` with `mt-16 md:mt-32`, a 1px `border-border` top border and `py-8`. Inside it a grid like the one above. Columns 1–4 (`md:col-span-4`): the house's name as plain text in `font-heading text-h2`, then the atelier's address: one `<div>` with `mt-2 font-mono text-label uppercase text-muted-foreground` holding two `<p>` lines with no margin between them, `7 rue Belvèze` and `69001 Lyon`. Columns 9–12 (`md:col-start-9 md:col-span-4`, with `mt-8 md:mt-0`): a `<nav>` with the `aria-label` "Footer" holding a `<ul>` of the header's three links, in the same order, as labels, `mt-2` between items. The current page is marked here too, with `aria-current="page"` and `underline`. Under the grid, with `mt-8`, a label line in `text-muted-foreground` with the copyright, e.g. `© 2026 Maison Ardelle`.
- **Headings:** the `<h1>` is `text-h1`, and `text-display` from `md` up where this brief says so (`text-h1 md:text-display`). An `<h2>` in the heading font is `text-h2`, and so is an `<h3>`. Nothing in the heading font is smaller than `text-h2`: a smaller heading is a label heading. Headings keep the weight their type class gives them (500); add no weight class. Sentence case, no full stop.
- **Italic:** the heading font's italic is the one flourish. Put it on one word of a heading, or on one title (a collection's name), as an `<em>` inside the heading; `<em>` is italic on its own. At most one italic per heading and at most two per page. Never set a paragraph, a label or body text in italics: a collection's name in a paragraph, a caption or a label line stays upright, even where the sample italicizes it.
- **Labels:** nav links, captions, credits, dates, sizes, look numbers, form labels, terms in a `<dl>`, and button and badge text are labels: `font-mono text-label uppercase`. The wide tracking comes with `text-label`; do not add a `tracking-*` class. Write a label as a short fragment, not a sentence. Error lines and helper text use the label's font and size but are sentences, and are not uppercased. Join the parts of a label line with ` · `. A label on its own line is a block element such as a `<p>`. Email addresses and what someone typed are never uppercased. A unit keeps its case: put `cm` in a `<span>` with `normal-case`.
- **Label headings:** a heading that is styled as a label (Details, Care, Hours) is an `<h2>` or `<h3>`, whichever keeps the heading order, with `font-mono text-label uppercase text-foreground`. Keep the `font-mono`: a heading element gets the heading font on its own.
- **Text color:** headings, label headings, body text, nav links and form labels are `text-foreground`. A button's or a badge's text color comes from its variant or its classes. Caption lines after the first, credits, dates, `<dt>` terms and helper text are `text-muted-foreground`. Paragraphs are never muted. No other text colors; blush (`tertiary`) is never a text color.
- **Measure:** a paragraph of body text is at most `max-w-md` wide (448px), however many columns it sits in. A heading may run wider.
- **Figures and prices:** a price, a size, a time or a count that stands on its own is `font-mono tabular-nums`, in `text-label` as part of a caption and in `text-body` where this brief says so. Prices are written with the currency sign before the number and a comma for thousands: `€1,480`. A date or a time that stands on its own is a `<time>` with a `dateTime`: the opening hours and the years of a timeline and of a "Since" line. A `<time>` inside a sentence keeps the sentence's font. A date is written `Thursday 19 November 2026` (`dateTime="2026-11-19"`), a time `11:30`, a year `2014`. A year inside a name or a credit (`Autumn–Winter 2026`, `Tomas Reyl, 2026`, the copyright) is plain text.
- **Links:** a link in running text or in a caption is `text-foreground underline decoration-1 underline-offset-4 hover:text-muted-foreground`, and inherits its size and font. A link in the header or the footer is `text-foreground decoration-1 underline-offset-4 hover:underline`; the link for the current page has `aria-current="page"` and `underline`. Mark one only where this brief names the current page. Links are never colored, and get `transition-colors duration-150`. Every link to a page of this site, a piece's page and the current page's own nav link included, points to `#`, unless the project has routes for these pages; the house's name always points to `/`. Any other link with no destination named is `#`.
- **Buttons:** the shadcn `Button` with `className="h-auto px-8 py-4 font-mono text-label font-normal uppercase"`: `font-normal` because the mono font has one weight. The default variant (black, ivory in dark mode) is for the page's main action. A second action is `variant="secondary"` (stone). In `components/ui/button.tsx`, replace that variant's stock `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80`, once, not beside it: the 5% mix cannot be told from the resting color. A quiet action is `variant="outline"`. A button that sends a form needs `type="submit"`: this `Button` is `type="button"` unless told otherwise. A call to action that goes to another page is an `<a>` with the same classes: write `cn(buttonVariants({ variant }), "…")`, because `buttonVariants` alone does not merge classes. Do not use the `xs`, `sm` or icon sizes.
- **Button groups:** a set of buttons where one is chosen (a size) is a `role="group"` with `flex flex-wrap gap-2`, named by `aria-labelledby` pointing at the visible label above it. Each button has `className="h-auto min-w-16 px-4 py-2 font-mono text-label font-normal uppercase tabular-nums"`. The chosen one has `aria-pressed="true"`, `variant="secondary"` and `border-foreground`; the rest have `aria-pressed="false"` and `variant="outline"`. The chosen value is also written out in words in the label line above the group, so the choice is never shown by fill alone.
- **Inputs:** the shadcn `Input` and `Textarea` (`npx shadcn@latest add input textarea`) with `className="h-auto border-muted-foreground px-4 py-2 text-body md:text-body"`. They keep the fill they ship with, in dark mode too, and the stock `aria-invalid:border-destructive`. Every field has a visible `<label>` above it with `block`, tied to it with `htmlFor` and styled as a label in `text-foreground`; the field has `mt-2`. Fields follow each other with `mt-8`, on the wrapper `<div>` that holds a label, its field and its error. Optional fields say so in the label: `Note (optional)`.
- **Selects:** a choice from a short list is a native `<select>` with `h-auto w-full border border-muted-foreground bg-transparent px-4 py-2 text-body text-foreground dark:bg-input/30 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring` and the four `focus-visible:outline-*` classes from the rules: the fill, border and error classes are the ones the stock `Input` ships, so the two look alike in both modes; these two `dark:` classes are the one exception to the rules' "no `dark:` color overrides". It takes the same label, margins and error line as any field, and shows an error with `aria-invalid` and its red border, like an `Input`. Its first option is a prompt with an empty value (`Choose a time`).
- **Date fields:** an `Input` of type `date`, with the Inputs classes. Its picker and the `<select>`'s list follow dark mode through the rules' `color-scheme` lines; add no class for it.
- **Errors:** an error is a line under its field with `mt-2`: `flex items-start gap-2 font-mono text-label text-destructive`, not uppercased, holding the `CircleAlert` icon (`shrink-0`, no margin) and a sentence that says what to do. The field gets `aria-invalid` and points to the line with `aria-describedby`. Fields are checked in code when the form is sent, with `noValidate` on the `<form>`, and focus moves to the first field with an error. An error line and its field's `aria-invalid` are removed when that field changes (on `input` or `change`); an error that depends on a second field (a Saturday and its time) is also removed when that second field changes. Every other error stays until the next send.
- **Forms:** a form sits on the deeper paper: the `<form>` is `bg-card p-8`, with no border. The form and its confirmation are the only filled boxes on a page. The send button sits under the last field with `mt-8`.
- **Confirmations:** a message that replaces a form goes into an element with `role="status"` that is on the page from the start, empty. The form and the status element share one wrapper, and the wrapper carries the top margin, so the message stands where the form stood. The status element has no classes and no margin, so empty it takes no space; never hide it. Inside it, rendered only once there is a message, a `<div>` with `bg-card p-8`, like the form: an `<h2>` or `<h3>` in `text-h2` (whichever keeps the heading order), then with `mt-4` one or two sentences in `text-body`. A button that follows the message sits after the status element in the same wrapper, not inside it, with `mt-8`. Give the heading `tabIndex={-1}` and move focus to it, because the button that had focus is gone.
- **Badges:** at most one badge on a page, and only where this brief asks for it: the shadcn `Badge` (`npx shadcn@latest add badge`) with `className="h-auto rounded-none border-transparent bg-tertiary px-2 py-1 font-mono text-label font-normal uppercase text-tertiary-foreground"`. One or two words, no icon.
- **Pictures:** there are no image files. A picture is a plain `<div>` with an aspect ratio (`aspect-3/4`, `aspect-4/5`, `aspect-2/3`, `aspect-4/3`: Tailwind's ratio classes, not arbitrary values), `w-full`, a fill of `bg-secondary` unless this brief says `bg-card`, `role="img"` and an `aria-label` that describes the photograph in a sentence. Nothing is drawn inside it: no icon, no text, no gradient, no border. Every picture sits in a `<figure>` with a `<figcaption>`.
- **Caption below:** the `<figcaption>` comes under the picture with `mt-2`. It holds label lines: the first in `text-foreground` (a look number, a piece's name), the others in `text-muted-foreground` (materials, credits), each line a `<p>`, with `mt-1` between lines.
- **Caption beside:** where this brief puts a caption beside a picture, the `<figure>` spans all twelve columns (`md:col-span-12`) and is itself a grid from `md` up: `md:grid md:grid-cols-12 md:gap-x-8`. The picture and the caption take the columns this brief gives them, and the caption has `md:self-end md:mt-0`, so it sits at the foot of the picture. Where the caption is to the left of the picture, give both `md:row-start-1`. Below `md` the caption sits under the picture with `mt-2`, as any caption.
- **Lists of entries:** a list where this brief gives each item a 1px top border (a timeline, a list of workshops) is a `<ul>` or `<ol>` with `list-none`, no padding, and no space between its items; each item has `border-t border-border py-4`, and the list a `border-b border-border` that closes it. Where this brief lays an entry out in columns, the `<li>` itself carries the grid classes beside these, and its parts are the grid items.
- **Description lists:** a `<dl>` is a list of rows. Each row is a `<div>` with `grid grid-cols-3 items-baseline gap-x-4 border-t border-border py-4`: the `<dt>` is a label in `text-muted-foreground`, the `<dd>` takes the other two columns (`col-span-2`) in `text-body`. The `<dl>` has `border-b border-border`, so the last row is closed too. This holds at 375px as well.
- **Rules:** every line is 1px and `border-border`. No shadows, no blur, no gradients, no colored backgrounds behind sections.
- **Corners:** square everywhere. The stock components are already square in this theme; `rounded-none` is the only corner class, and it is needed only where these notes give it.
- **Icons:** from the project's icon set, `lucide-react`, with `size-4`, `strokeWidth={1.5}` and `aria-hidden="true"`. Use them only where this brief or these notes name one.
- **Motion:** in the code you write, the only transition is color. Nothing moves on scroll.

## The page

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

Sample house, if the user gave none: **Maison Ardelle**, a small fashion house in Lyon founded in 2014 by Inès Ardelle. It makes two collections a year in small numbers, cut in its own atelier from wool, linen and silk woven in Europe. The current collection is Autumn–Winter 2026, called *Still Water*: seven looks in undyed wool, black linen and silk the color of skin. Its pieces include the Lune wrap coat (€1,480), the Ilse pleated skirt (€640) and the Mira silk shirt (€520). Pictures are by the photographer Tomas Reyl. Write the rest of the copy yourself, short and plain: this page says little.

Build these parts in order.

### 1. Header

The header from the layout notes. None of the links is the current page.

### 2. Opening

The first section, in the grid:

- The `<h1>` in `text-h1 md:text-display`, across columns 1–10 (`md:col-span-10`). Sample: "Clothes cut slowly, to be worn for *years*", with the one italic word in an `<em>`.
- With `mt-16`, the large picture, as a figure with its caption beside it (see the layout notes). The picture takes columns 5–12 (`md:col-start-5 md:col-span-8`), `aspect-4/5`, `bg-secondary`. The caption takes columns 2–4 (`md:col-start-2 md:col-span-3`), to the left of the picture, so both get `md:row-start-1`. Caption lines: `Look 01 · Still Water`, then `Lune wrap coat, undyed wool`, then `Photograph · Tomas Reyl`.

This picture is the page's one image that fills most of a screen. Nothing else stands beside it.

### 3. The collection now

A section in the grid, with its content in columns 5–12 (`md:col-start-5 md:col-span-8`):

- A label line in `text-muted-foreground`: `Autumn–Winter 2026`. Beside it on the same line, the page's one badge: "New season". Add it with `npx shadcn@latest add badge`. Put the two in a `<div>` with `flex flex-wrap items-center gap-x-4 gap-y-2`: 16px between them on one line, 8px when the badge wraps under the label.
- With `mt-4`, an `<h2>` in `text-h2`: the collection's name with the name itself in an `<em>`, "*Still Water*". This is the page's second and last italic.
- With `mt-4`, one or two sentences in `text-body`, at most `max-w-md`: what the collection is made of and how many looks it has.

### 4. Three pieces

A section that is not a grid, with a label heading "From the collection", an `<h2>` at the full width. Then with `mt-8` a `<ul>` that is itself the grid (`grid grid-cols-1 md:grid-cols-12 md:gap-x-8`, `list-none`), holding three `<li>`, each a figure with its caption below:

- **First:** `md:col-start-1 md:col-span-4`, picture `aspect-3/4`.
- **Second:** `md:col-start-6 md:col-span-3`, with `mt-16 md:mt-32`, picture `aspect-2/3`, `bg-card`.
- **Third:** `md:col-start-10 md:col-span-3`, with `mt-16` at every width, picture `aspect-3/4`.

The three start at different heights from `md` up; the row is meant to look uneven. Each caption's first line is a `<p>` with `flex items-baseline justify-between gap-4`: the piece's name as a link to its page (styled as a link in a caption, in `font-mono text-label uppercase`) and its price in `font-mono text-label tabular-nums text-foreground` with `shrink-0`. The link has `min-w-0`: in a three-column box (up to about 1000px wide screens) a long name such as "Ilse pleated skirt" wraps onto two lines beside the price, which stays on one line at the right. The second line, with `mt-1`, gives the material in `text-muted-foreground`. Write each picture's `aria-label` as a description of the photograph of that piece.

### 5. Onward

A section in the grid with one `<p>` in columns 5–12 (`md:col-start-5 md:col-span-8`), holding a link "See the whole collection" to the collection page, as a label (`font-mono text-label uppercase`) styled as a link in running text, followed by the `ArrowRight` icon, in `inline-flex items-center gap-2`. Not a button: the page has no button at all.

### 6. Footer

The footer from the layout notes.

### On a phone

Every part is one column. The `<h1>` is `text-h1` and wraps over three or four lines. The large picture takes the full width with its caption under it. The three pieces stack: the first has no margin (the list's `mt-8` is above it), the second and the third are `mt-16` under the one before. The badge wraps under the season label if the line is too short.

### Behavior

Nothing on this page has state. Links only.

## 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`, `4`, `8`, `16`, `32`) plus 0, 1px and 2px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-3` or `gap-6`. `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. This theme has no full rounds: do not use `rounded-full`, and give a small mark a corner under half its height or none, because a box whose corner is half its height or more is a full round however it is written.
- **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 give one image or one heading the whole screen.
- Do set captions and credits small, in the mono font, and let them be found.
- Do use the italic for one word or one title at a time.
- Don't round a corner, add a shadow or tint a background to make something "pop".
- Don't set long text in Bodoni: below 28px its hairlines break up.
- 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.

- [ ] The `<h1>` is the page's only `text-display` text, from `md` up, and exactly one word in it is in the heading font's italic.
- [ ] The large picture spans eight columns and does not start at the left edge of the grid, with its caption beside it from `md` up and under it on a phone.
- [ ] The three pieces sit at different columns and heights from `md` up, not in an even row, and each has a caption with its name and its price in the mono font.
- [ ] The page has exactly one badge, and it is the only blush on the page.
- [ ] Most of the screen is empty: no section has a background, a border box or a card.
- [ ] 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.
