# Build a visitor information page with the Nocturne theme

You are building one page in an existing project: the visitor page of an independent cinema, with its address, opening times, its screens with seats and access notes, the bar, how to get there and a contact 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 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, in a former rope works at 21 Tallow Row, Oldmere OM2 4LT. Phone 01632 960 418, email hello@vesper.example. Screen 1 has 212 seats, Screen 2 has 84, Screen 3 has 38. The bar is on the first floor and stays open until midnight on Fridays and Saturdays. The nearest tram stop is Tallow Row on the Harbour line, three minutes' walk. Opening times: Monday closed; Tuesday to Thursday doors 17:00, last film 21:00, bar closes 23:30; Friday and Saturday doors 16:00, last film 23:15, bar closes 00:00 (midnight); Sunday doors 13:00, last film 20:30, bar closes 22:30.

Build these parts in order.

### 1. Header

The header from the layout notes. "Visit" is the current page.

### 2. Introduction

The first `<section>`, a grid (`md:grid-cols-12`):

- Columns 1–7: a label "Visit"; with `mt-2`, the `<h1>` "Find the Vesper" in `text-h1 md:text-display`; with `mt-6`, one paragraph within `max-w-xl` about the building: a rope works from 1871, a cinema since 1934, three screens and a bar on the first floor.
- Columns 9–12, with `mt-12 md:mt-0`: a card from the layout notes. A label "Address"; with `mt-2`, an `<address>` in `text-body` with `not-italic`, in three lines, "21 Tallow Row", "Oldmere" and "OM2 4LT", broken with `<br />`; with `mt-4`, a `<p>` holding the phone number and, under it with no margin, a `<p>` holding the email address, each a text link (`tel:` and `mailto:`). Do not uppercase the email address.

### 3. Opening times

The second `<section>`, with `mt-24`. An `<h2>` "Opening times" in `text-h2 md:text-h1`, then with `mt-4` one sentence: the box office opens half an hour before the first film.

With `mt-6`, in columns 1–8 of a grid (`md:grid-cols-12`), the `Table` from the layout notes (add it with `npx shadcn@latest add table`), with the `label` "Opening times". Columns: Day, Doors open, Last film starts, Bar closes. One row per day, Monday to Sunday; the first cell of each row is a `TableHead` with `scope="row"` and, in place of the column headers' label classes, `py-2 font-sans text-body font-normal text-foreground`; like every cell it keeps `px-4` and `first:pl-0 last:pr-0`. The column headers are `<th scope="col">`. Every time is a `<time>` (`dateTime="17:00"`) in `font-mono tabular-nums`. Monday is closed: its row says "Closed" in words in the Doors open cell, and its other two cells hold "—" with a visually hidden "Closed". Today, Wednesday, is not marked.

The stock cells do not wrap, so the table is about 480px wide: at 375px it is wider than the screen and scrolls sideways inside its own box, and the page does not.

### 4. Screens

The third `<section>`, with `mt-24`. An `<h2>` "The screens" in `text-h2 md:text-h1`.

With `mt-6`, three cards from the layout notes in a grid `grid grid-cols-1 gap-6 md:grid-cols-3`. Each card is an `<article>`:

- The screen's name as an `<h3>` in `text-h2` ("Screen 1").
- With `mt-2`, a label in `text-muted-foreground`: the seats and the screen's format, e.g. `212 seats · 35mm and digital`.
- With `mt-4`, one sentence about the room.
- With `mt-6`, the access notes as a `<ul>` with `list-disc pl-4` in `text-body`, with `mt-2` on every item but the first. Write them as facts: step-free from the street, the number of wheelchair spaces and where they are, a hearing loop, which seats have extra legroom, captioned screenings on Tuesdays. Screen 3 is up a flight of stairs with no lift: say so plainly, as the first note.

Below `md` the cards stack.

### 5. The bar

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

- Columns 1–7: a still of the bar in a `<figure>`, with the `aria-label` "The first-floor bar at night, with its long zinc counter and the windows over Tallow Row" and a caption, a label: `The bar · An hour before the late show`.
- Columns 8–12, with `mt-6 md:mt-0`: an `<h2>` "The bar" in `text-h2 md:text-h1`; with `mt-4`, two paragraphs, the second with `mt-4`, on what it serves (local beer, coffee until late, small plates until 21:00) and that you can take a drink into Screens 1 and 2; with `mt-4`, a label in `text-muted-foreground`: `Opens with the doors · Until midnight Fri and Sat`.

On a phone the still comes first.

### 6. Getting here

The fifth `<section>`, with `mt-24`. An `<h2>` "Getting here" in `text-h2 md:text-h1`. With `mt-6`, a `<dl>` laid out as a grid `grid grid-cols-1 gap-6 md:grid-cols-2`, four entries, each a `<div>` with `border-t border-border pt-4`: the `<dt>` a label in `text-muted-foreground` (`By tram`, `By bus`, `By bike`, `By car`), the `<dd>` with `mt-2` one or two sentences in `text-body`, within `max-w-xl`. No map.

### 7. Contact

The sixth `<section>`, with `mt-24` and the `id` "contact", a grid (`md:grid-cols-12`):

- Columns 1–5: an `<h2>` "Write to us" in `text-h2 md:text-h1`, then with `mt-4` one sentence: questions about access, private hire or lost property, answered within two working days.
- Columns 7–12, with `mt-12 md:mt-0`: a wrapper holding the form and the status element from the layout notes.

The form, with `noValidate`:

1. "Your name", an `Input` with `autoComplete="name"` (add `input` and `textarea` with `npx shadcn@latest add` if the project lacks them).
2. With `mt-6`, "Email address", an `Input` with `type="email"` and `autoComplete="email"`.
3. With `mt-6`, "Topic", a native `<select>`: General question, Access, Private hire, Lost property. General question is chosen at first.
4. With `mt-6`, "Message", a `Textarea` with `rows={5}`; in `components/ui/textarea.tsx`, replace `field-sizing-content` with `field-sizing-fixed` (the stock one sizes to its content and ignores `rows`). Helper text: "If you lost something, say which screening you were at."
5. With `mt-6`, the default `Button` "Send message", with `type="submit"`. It is the page's one gold action.

### 8. Footer

The footer from the layout notes.

### Behavior

- When the form is sent, check it in code: the name must not be empty ("Enter your name."), the email must not be empty and must look like an address, with one `@` and a dot after it ("Enter an email address such as name@example.com."), and the message must be at least 20 characters ("Write a few words more, so we can help: at least 20 characters."). Show every error at once and move focus to the first field with an error.
- A good form is replaced by the confirmation. Its `<h3>`: "Thank you, your message is with us", with no full stop. Under it, with `mt-4`, one sentence that names the topic and the email address the reply goes to: "We will answer your question about access at mara@example.com within two working days." The topic reads "your question" for General question, "your question about access", "your question about private hire" and "your message about lost property". Nothing is sent anywhere: this is a demo.
- The phone and email links use `tel:01632960418` and `mailto:hello@vesper.example`. No link needs 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 opening times are a table with real `<th>` cells, its times are `<time>` elements in `font-mono tabular-nums`, and at 375px it fits or scrolls inside its own keyboard-reachable box.
- [ ] Each screen states its seat count and its access notes in words; no access feature is shown by an icon alone.
- [ ] The address is an `<address>` element, and the still of the bar has `role="img"`, an `aria-label` and a caption.
- [ ] Sending the contact form with an empty name, a malformed email or an empty message shows a sentence with an icon under each such field, and a good form is replaced by a confirmation in a `role="status"` element.
- [ ] The "Send message" button is the page's only default (gold) button.
- [ ] 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.
