# Build a film page with the Nocturne theme

You are building one page in an existing project: the page of one film at an independent cinema, with its title, a still, its details, a synopsis, the credits, showtimes by day that can be chosen, and a quote from a review. 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 Nocturne variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/nocturne.json` and save https://strictthemes.com/themes/nocturne/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/nocturne/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:** Syne for headings, Onest for body text, Azeret Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 64px/800, `text-h1` 40px/700, `text-h2` 24px/700, `text-body` 16px/400, `text-label` 12px/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`), 16px (`4`), 24px (`6`), 48px (`12`), 96px (`24`).
- **Corners:** `rounded-sm` 3px, `rounded-md` 8px, `rounded-xl` 16px (the `lg` corner in `DESIGN.md`). `rounded-lg` is 8px as well: it is shadcn's name for the base corner, the one its stock buttons and inputs use.
- **Colors (light mode):** primary #6B4A00, on-primary #FFF8E6, secondary #17131F, on-secondary #F2F0F5, tertiary #C9B6FF, on-tertiary #17131F, neutral #625B6E, background #F2F0F5, surface #FFFFFF, foreground #17131F, border #DAD5E2, error #B3261E.

### How Nocturne pages are laid out

- **Page:** one `<div>` wraps the whole page: `min-h-screen bg-background text-body text-foreground`. Inside it sit the `<header>`, the `<main>` holding everything between the two, and the `<footer>`.
- **Container:** `mx-auto max-w-[1280px] px-4 md:px-6`. This width is the one arbitrary value allowed. The `<main>` element is itself a container and also carries `pt-12`. The header and the footer run the full width of the window, with their lines; the container is a `<div>` inside each. Every other line stays inside the container.
- **Grid:** where this brief gives columns, the element is `grid grid-cols-1 md:grid-cols-12 md:gap-x-6` (24px gutters) and each item says its span, such as `md:col-span-7`. The grid sets columns only, never vertical space. Below `md` items stack in source order.
- **Vertical rhythm:** written as a top margin on the later element, never as `space-y-*` and never as a gap on a column of mixed content. A heading under a label has `mt-2`. A paragraph under a heading, and a paragraph under a paragraph, has `mt-4`. A block under text (a row of buttons, a list, a form, a figure, a table, a row of cards) has `mt-6`. A new group inside a section has `mt-12`. A `<section>` inside `<main>` has `mt-24`, except the first, which has none: the `pt-12` on `<main>` is the space above it. Where two grid items share a row from `md` up and stack below it, the second gets `mt-6 md:mt-0` when it follows on from the first (text under a still) and `mt-12 md:mt-0` when it is a separate group (a card, a form, a column with its own heading); each brief writes which. Gaps are only for items side by side in a row (`flex`, `flex-wrap`, a grid of cards), where this brief or these notes name one. Inside a list of rows, a row has its own top border and padding and no margin.
- **Headings:** headings are Syne, which `<h1>` to `<h6>` get on their own, in sentence case with no full stop. Titles wrap; never truncate one. The page's big title is `text-h1 md:text-display`, the only `text-display` text on the page. A section's `<h2>` is `text-h2 md:text-h1`. A film title in a row or a card is an `<h3>` in `text-h2`. Syne has no italic: never set a heading or a quote in italics.
- **Labels:** dates, days, times, running times, ratings, seat counts, screen names, eyebrows, captions, nav links, field labels, table headers and button and badge text are labels: `font-mono text-label uppercase`, showtime buttons included. Write their text in sentence case ("22:45 · Late"); the `uppercase` class sets the capitals. The class `text-label` already carries the wide tracking; add no `tracking-*` class. A label is a short fragment; helper text and errors are sentences in body text. Parts of a label line are joined with ` · `. A label on its own line is a block element such as a `<p>`.
- **Times and figures:** a showtime, a date, a price or a seat count that stands on its own (in a label, a button, a table cell, or a price set large) gets `tabular-nums`, so times line up in a column. A date or a time that stands on its own is a `<time>` with a `dateTime` (`2026-11-13`, or `2026-11-13T20:30` for a showtime). Times are 24-hour (`20:30`). In a label a date is short, `Fri 13 Nov` (`11 Nov` without the day, a year only where a brief writes one); in a sentence it is written in full, `Friday 13 November`, and with `2026` only where the brief's own example sentence writes the year. A year alone (a film's year) is plain text, not a `<time>`. A date range in a label (`11–15 Nov`, `20 Nov – 18 Dec`) is two `<time>` elements joined by an en dash in plain text, each with its full `dateTime`. A number or a date inside a sentence is plain text. "Today" is always the fixed sample date, Wednesday 11 November 2026, never the clock.
- **Credits:** the line of credits under a film title (director, country, year) is a `<p>` in `text-body text-muted-foreground`, with its parts joined by ` · `. Names in it are never uppercased. The running time and the rating go on a label line instead. Running text (a synopsis, a note on a film, an introduction, the sentences under a section heading) is never muted. Helper text is a short line apart from running text that a brief sets in `text-muted-foreground`, such as a field's hint or a demo note.
- **Text color:** headings, body text, field labels, nav links and table body cells are `text-foreground`. Other labels, credits, captions and helper text are `text-muted-foreground`. A button's text color comes from its variant.
- **Gold:** `text-primary` and `bg-primary` are bronze in light mode and gold in dark mode. Each screen has one gold thing, besides a link under the pointer and the focus outline (`outline-ring` is gold too, and stays): the page's one main action, a default `Button` or a link styled as one, as this brief names it. `DESIGN.md` allows one gold title in place of an action; these pages spend it on the action, so headings are never gold. Primary is never a fill, a border or a band.
- **Lavender:** `bg-tertiary` is for one badge on a page, always with `text-tertiary-foreground`. It is never a text color or a border.
- **Measure:** a paragraph of running text is at most `max-w-xl` wide (about 70 characters a line in Onest).
- **Links:** a text link is `text-foreground underline decoration-1 underline-offset-4 hover:text-primary`, so gold shows only under the pointer. A link that holds a title (inside a heading, and the cinema's name in the header) is never gold: `text-foreground decoration-1 underline-offset-4 hover:underline`. A nav link is a label in `text-foreground` with `hover:underline underline-offset-4`; the link for the current page has `aria-current="page"` and `underline`. Links get `transition-colors duration-150`.
- **Link destinations:** the five pages are `/` (Programme), `/film`, `/season`, `/membership` and `/visit`; where the project has another router layout, use its paths. A link whose destination this brief does not name points to `#`.
- **Header:** a `<header>` with `border-b border-border`. Its container `<div>` is also the row: `flex flex-wrap items-center justify-between gap-4 py-4`. On the left the cinema's name, a link to `/` in `font-heading text-h2`, never marked as the current page. On the right, a `<nav>` with the `aria-label` "Main" and `hidden md:block`, holding a `<ul>` with `flex gap-6` of four label links, in this order: Programme (`/`), Seasons (`/season`), Membership (`/membership`), Visit (`/visit`). The brief names the current one, or says that none is.
- **Menu on small screens:** below `md` the `<nav>` is hidden and a "Menu" button (`variant="outline"`, with the button classes below) stands at the right end of the row, with `aria-expanded` and `aria-controls`. It shows the links as a list inside the `<header>`, under its row: a second `<nav>` with the same `aria-label` and `md:hidden`, which carries the `id` that `aria-controls` names and has the `hidden` attribute while closed. It has a 1px top border across the full width and, inside the container, `py-2`; each link is a block with `py-2`. It closes on Escape, pressed while focus is on the Menu button or in the menu (one `onKeyDown` handler on the `<header>`, acting only while the menu is open), and focus then goes to the Menu button; it also closes when a link in it is chosen. Do not use a `Sheet` or a `Dialog`.
- **Footer:** a `<footer>` with `mt-24`, `border-t border-border` and `py-12`. Inside it, the container `<div>` holds a grid (`md:grid-cols-12`): columns 1–6 hold the cinema's name as plain text in `font-heading text-h2` and, with `mt-2`, the address as a `<p>` in `text-body text-muted-foreground` ("21 Tallow Row, Oldmere OM2 4LT"); columns 7–12 hold a `<ul>` (not a `<nav>`) of four text links (Programme, Seasons, Membership, Visit) in `text-body` with `flex flex-wrap gap-x-6 gap-y-2`, with `mt-6 md:mt-0`. Under the grid, with `mt-12`, a label line in `text-muted-foreground`: `© 2026 The Vesper`.
- **Buttons:** the shadcn `Button` with `className="h-auto px-6 py-2 font-mono text-label uppercase"`. It keeps its stock `rounded-lg` corner. The default variant (gold) is the screen's one main action. Every other action is `variant="secondary"`, or `variant="outline"` for quiet ones. In this theme the `secondary` variant's stock hover is the same color as its fill: in `components/ui/button.tsx`, replace its `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80`, once, and add no hover class where you use it. A button that sends a form needs `type="submit"`: this `Button` is `type="button"` unless told otherwise. A link that looks like a button (to another page, or to a part of this one) is an `<a>` with the same classes: write `cn(buttonVariants({ variant }), "…")`, because `buttonVariants` alone does not merge classes. Such a link is `inline-flex`, so a top margin on it works: it needs no wrapper.
- **Button groups:** a set of buttons where one is chosen (a day, a billing period, a showtime) is a `role="group"` with an `aria-label` and `flex flex-wrap gap-2`. 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.
- **Badges:** the shadcn `Badge` with `variant="secondary"` and `className="h-auto rounded-sm border-transparent bg-tertiary px-2 py-1 font-mono text-label uppercase text-tertiary-foreground"`: the variant is named only because its classes hold no gold, and these classes replace its fill and text color. One or two words, and at most one badge on a page.
- **Inputs:** the shadcn `Input` and `Textarea` with `className="h-auto px-4 py-2 text-body md:text-body"`. They keep their stock `rounded-lg` corner. So that fields and selects share one fill in both modes, edit `components/ui/input.tsx` and `components/ui/textarea.tsx`: replace the class `bg-transparent` and the class `dark:bg-input/30` with `bg-card`; `file:bg-transparent` and `dark:disabled:bg-input/80` stay. Every field has a visible `<label>` above it: `block font-mono text-label uppercase text-foreground`, tied to the field with `htmlFor`; the field has `mt-2`. Helper text under a field is a `<p>` in `text-body text-muted-foreground` with `mt-2`.
- **Selects:** a choice from a short list is a native `<select>` with `w-full rounded-lg border border-input bg-card px-4 py-2 text-body text-foreground`, under a `<label>` like any field, with the same focus outline. These selects always have a value, so they never show an error.
- **Errors:** an error is a line under its field with `mt-2`: `flex items-start gap-2 text-body text-destructive`, holding the `CircleAlert` icon (`shrink-0`, `mt-1`) and a sentence that says what to do, not uppercased. The field gets `aria-invalid` and points to the line with `aria-describedby`, next to any helper text it already names. 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 is removed when its own field changes (on `change`); every other error stays until the next submit.
- **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 itself has no classes and no margin, and is never hidden (no `hidden`, no `empty:hidden`). The message inside it is rendered only once there is one, and has no fill or border: its first element is an `<h3>` in `text-h2` with `tabIndex={-1}`, then the sentences follow with `mt-4`. Move focus to the `<h3>`. A link that follows a confirmation sits after the status element, in the shared wrapper, rendered only with the message, inside a `<p>` with `mt-6` (a margin on a plain inline text link does nothing).
- **Cards:** a card is a plain element with `rounded-xl border border-border bg-card p-6`. Inside it, a label comes first where it has one, and the rest follows the rhythm above. Never put a card inside a card, and do not use the shadcn `Card`.
- **Pictures:** there are no image files. A picture is a plain element with a 1px border and a fill, `border border-border bg-muted`, with `role="img"` and an `aria-label` that says what it shows. A poster is `aspect-2/3 rounded-xl`; a still is `aspect-video rounded-xl` (16:9). Show them whole, in those proportions: never crop one to another shape. A poster in a row is a thumbnail, `w-24 shrink-0 self-start` and `md:w-32` (`self-start`, or the row stretches it to the height of the text beside it and it loses its proportions), with the film's year inside it in its lower left: the element is `flex items-end p-2`, the year a `<span>` in `font-mono text-label text-muted-foreground`. A screen reader does not read it (`role="img"` hides what it holds), which is right: the credits line gives the year. A still is wide, in a `<figure>`, with a `<figcaption>` under it with `mt-2`: a label in `text-muted-foreground`, as `DESIGN.md`'s caption is, naming the film and what is in the frame in a short fragment (`Low Tide at Merrow · Maren on the harbour wall`). Pictures sit on the page, never in a card: in dark mode `bg-muted` and `bg-card` are nearly the same color.
- **Lists of rows:** a list of films or dates is an `<ol>` or `<ul>` with no markers and no padding. Each row has `border-t border-border py-6`, and the list has `border-b border-border` so the last row closes. A row with a poster is `flex gap-4 md:gap-6`: the poster, then a `min-w-0 flex-1` column holding the text.
- **Tables:** the shadcn `Table` with `text-body`. Edit `components/ui/table.tsx`: its scroll box, the `<div>` around the `<table>`, gets `tabIndex={0}`, `role="region"`, an `aria-label` from a new required `label` prop, and the focus outline. Header cells are labels in `text-muted-foreground` with `px-4`; body cells have `px-4 py-2`. Add `first:pl-0 last:pr-0` to every cell. Times in cells are `font-mono tabular-nums`. Rows are divided by 1px borders, with no zebra stripes. The stock `TableBody` drops its last row's border: take `[&_tr:last-child]:border-0` out of it. The rows do nothing, so take `hover:bg-muted/50` out of the stock `TableRow`. Header cells keep the stock `h-10` and, like all cells, `whitespace-nowrap`. The table's name is the heading above it and the `label`; it has no `<caption>`.
- **Block quotes:** a `<figure>` with a `<blockquote>` holding a `<p>` in `font-heading text-h2`, upright, with no quotation-mark graphic, and a `border-l-2 border-foreground pl-6` on the `<blockquote>`. Who said it is a `<figcaption>` with `mt-4`: a label in `text-muted-foreground`.
- **Icons:** from `lucide-react`, with `size-4`, `strokeWidth={1.5}` and `aria-hidden="true"`, and only where this brief or these notes name one. Never emoji.
- **Corners:** `rounded-sm` (3px) for badges, `rounded-lg` (8px, the stock corner) for buttons, inputs and selects, `rounded-xl` (16px) for cards, posters and stills. `rounded-full` is for avatars only, and these pages have none.
- **Surfaces:** flat: no shadow, blur or gradient in the code you write. A stock component that floats keeps its one short shadow.
- **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 content, if the user gave none: **The Vesper**, an independent cinema with three screens and a late bar at 21 Tallow Row, Oldmere. The film is "Low Tide at Merrow", directed by Ada Kessler, Norway, 2026, 118 minutes, rated 15, in Norwegian with English subtitles. Maren, a ferry pilot, comes back to the island she left at nineteen to sell her late father's house, and finds the harbour town waiting for a storm that the radio keeps moving to the next night. It opens at the Vesper on Wednesday 11 November 2026. Credits: directed by Ada Kessler; written by Ada Kessler and Sindre Vold; Ingrid Solberg as Maren; cinematography by Hanne Kvist; music by Oskar Lunde; editing by Tora Brekke. The review is by Tomas Brecht in "The Oldmere Review": "Kessler keeps the storm offshore for two hours, and you feel every minute of the waiting."

Build these parts in order.

### 1. Header

The header from the layout notes. The film page is not in the nav, so no nav link is the current page and none has `aria-current`; Programme still points to `/`.

### 2. Title

The first `<section>`:

- A label line in `text-muted-foreground`: a text link "Programme" back to `/`, then ` · Now showing`. The link is `text-foreground` with the text link's underline, hover and transition from the layout notes, inside the muted `<p>`, which gives it the label classes; ` · Now showing` stays muted.
- With `mt-2`, the `<h1>` "Low Tide at Merrow" in `text-h1 md:text-display`. It wraps on a phone; it is never cut.
- With `mt-4`, the film's details as labels: a `<ul>` with `flex flex-wrap gap-x-6 gap-y-2`, each `<li>` a label in `text-muted-foreground`: `2026`, `Norway`, `118 min`, `Rated 15`, `Subtitled`. Give the year a visually hidden "Year: " in front of it, so it is not read as a bare number.

### 3. Still

Still inside the first `<section>`, after the details, with `mt-12` (a new group), a `<figure>` across the container's full width: the still from the layout notes, with the `aria-label` "Still from Low Tide at Merrow: Maren on the ferry's open deck, the island behind her" and a caption, a label: `Ingrid Solberg as Maren · The ferry's open deck`. Large and uncropped: keep `aspect-video` at every width.

### 4. Synopsis and credits

The second `<section>`, with `mt-24`, is a grid (`md:grid-cols-12`):

- Columns 1–7: an `<h2>` "Synopsis" in `text-h2 md:text-h1`, then three paragraphs, the first with `mt-4` under the heading and each next one with `mt-4`. Keep them to the measure.
- Columns 9–12, with `mt-12 md:mt-0`: an `<h2>` "Credits" in `text-h2 md:text-h1`, then with `mt-6` a `<dl>`. Each role is a `<div>` with `border-t border-border py-4`, and the `<dl>` has `border-b border-border`. In each `<div>`: the `<dt>` is a label in `text-muted-foreground`, the `<dd>` follows with `mt-1` in `text-body`. Roles: Director, Written by, Maren (the lead role, so the actor's name is the `<dd>`), Cinematography, Music, Editing. Two names in one role are joined with a comma.

### 5. Showtimes

The third `<section>`, with `mt-24`, and its own `id` ("showtimes"):

- An `<h2>` "Showtimes" in `text-h2 md:text-h1`, then with `mt-4` one sentence: which screen it is on and that every screening has subtitles.
- With `mt-6`, a `<ul>` of five days (Wednesday 11 to Sunday 15 November) as a list of rows, with `py-4` in place of `py-6` on each row. A row is `flex flex-col gap-2 md:flex-row md:items-center md:gap-6`: first the day, a `<p>` label holding a `<time>` (`Fri 13 Nov`), with `md:w-32 md:shrink-0`; then a button group with the `aria-label` "Times on Friday 13 November" (the day in full), holding that day's two or three times, each within that day's opening hours: 17:30 to 21:00 on Wednesday and Thursday, 16:30 to 23:15 on Friday and Saturday, 13:30 to 20:30 on Sunday. Each button holds a `<time>` with the full `dateTime`, and has the button classes from the layout notes plus `tabular-nums`. On Friday the 20:30 time is the premiere with a talk: its button reads `20:30 · Q&A`, the `<time>` holding only `20:30` and ` · Q&A` following it, outside the `<time>`, inside the button.
- The chosen time is one choice across all days, so the five groups share one piece of state. Nothing is chosen at first, so every button starts as `variant="outline"` with `aria-pressed="false"`.
- With `mt-6`, a wrapper `flex flex-wrap items-center gap-4` holding the choice line and the booking link. The choice line is a `<p>` with `aria-live="polite"` in `text-body`, on the page from the start: "Choose a time to book seats." at first, then the choice in words, e.g. "You have chosen Friday 13 November at 20:30, Screen 1. This screening is followed by a talk with Ada Kessler." Once a time is chosen, the default-variant link "Book seats" (to `#`) appears after the line. It is the page's one gold action.

On a phone the day sits above its times, and the times wrap.

### 6. Review

The fourth `<section>`, with `mt-24`: the block quote from the layout notes, in columns 1–8 of a grid (`md:grid-cols-12`). The sentence from the review in the sample, then the caption `Tomas Brecht · The Oldmere Review`. No stars, no score graphic.

### 7. Footer

The footer from the layout notes.

### Behavior

- The chosen showtime is component state: the day, the time and the screen. Choosing a time sets it; choosing the chosen time again clears it, and the line goes back to "Choose a time to book seats." and the "Book seats" link goes away. Focus stays on the button that was pressed.
- The choice line reads from the state. Write the day and the date in full in it, not as a label.
- "Book seats" and 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`, `4`, `6`, `12`, `24`) plus 0, 1px and 2px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-3` or `gap-5`. `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 design the dark mode first: it is where this theme lives.
- Do keep gold to one action or one title per screen.
- Do let images be large and uncropped.
- Don't add glows, neon or blurred glass, however much the subject invites it.
- Don't mix gold with other warm accents: lavender is the only second color.
- 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 film's title is the page's `<h1>` and its only `text-display` text, and it is `text-h1` below `md`.
- [ ] The credits are a `<dl>` with a `<dt>` and a `<dd>` for each role.
- [ ] Choosing a showtime marks only that time with `aria-pressed="true"` across all days, and a sentence in an `aria-live` element states the chosen day, time and screen in words.
- [ ] The "Book seats" link, the page's only default (gold) action, is on the page only once a time is chosen.
- [ ] The still keeps its 16:9 shape at every width, has `role="img"` and an `aria-label`, and has a caption.
- [ ] 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.
