# Build a visit page with the Tisane theme

You are building one page in an existing project: the page that tells people how to find a tea room, with its address, opening hours with today marked, how to get there, a map drawn from plain elements, notes on access 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 Tisane variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/tisane.json` and save https://strictthemes.com/themes/tisane/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/tisane/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:** Young Serif for headings, Albert Sans for body text, Reddit Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 56px/400, `text-h1` 38px/400, `text-h2` 26px/400, `text-body` 17px/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`), 40px (`10`), 72px (`18`).
- **Corners:** `rounded-sm` 6px, `rounded-md` 12px, `rounded-xl` 20px (the `lg` corner in `DESIGN.md`). `rounded-lg` is 12px as well: it is shadcn's name for the base corner, the one its stock buttons and inputs use.
- **Colors (light mode):** primary #4C5B1A, on-primary #FFFFFF, secondary #26281C, on-secondary #FCFAF4, tertiary #F0B59A, on-tertiary #26281C, neutral #626451, background #F5F1E8, surface #FCFAF4, foreground #26281C, border #DAD5C3, error #A8321F.

### How Tisane pages are laid out

- **Container:** one `<div>` wraps the whole page and is the container: `mx-auto max-w-[1120px]` with `px-4`, and `px-6` from `md` up, 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:** where this brief puts things side by side, they are items of a grid: `grid gap-6 md:grid-cols-12`, a single column below `md`. "Columns 1–6" is `md:col-span-6`; "columns 8–12" is `md:col-span-5 md:col-start-8`, and so on. The grid's `gap-6` is the space between its items in both directions, also when they stack, so a grid item never has a top margin. Where this brief says a section is "a grid", or "in columns 1–8 of a grid", the `<section>` itself carries the grid classes and its `mt-18`; in the second case one `<div>` in it spans those columns and holds the content. Add `md:items-start` to a grid of two or more items, unless this brief says they line up; a grid of one item needs none.
- **Measure:** a paragraph of body text is at most `max-w-xl` wide (576px).
- **Vertical rhythm:** written as a top margin on each element, never as `space-y-*` and never as a gap on a stack of text. These are the only distances:
  - Between sections: every `<section>` that is not inside another `<section>` has `mt-18` (72px), except the first, which has `pt-10` and no top margin. A `<section>` nested inside one of them takes the margin this brief gives it, never `mt-18`. The footer has `mt-18`.
  - A heading under a label, and a label under the thing it labels (a caption under a picture): `mt-2`.
  - Text under a heading, and a paragraph after a paragraph: `mt-4`.
  - A block after text or after another block (a grid, a list, a table, a card, a form, a picture, a button on its own line): `mt-6`.
  - An `<h2>` or `<h3>` that opens a new part inside a section, after content: `mt-10`.
  - The first element inside any box (a section, a grid item, a card, a list item, a form) has no top margin.
  - A link or a button on a line of its own sits in a `<p>` or a `<div>`, which carries the margin.
  These margins rely on normal block flow. Inside a grid item or a card, do not make the content `flex flex-col` with a gap.
- **Header:** the `<header>` is itself the row, with `py-6` and no border: `flex flex-col items-start gap-2 md:flex-row md:items-baseline md:justify-between`. On the left the tea room's name, a link to `/` in `font-heading text-h2`, styled as a header link and never marked as the current page. On the right a `<nav>` with the `aria-label` "Main", the page's only `<nav>`, holding a `<ul>` with `flex flex-wrap gap-x-6 gap-y-2` and four `<li>`s, each one link in `text-body`: Menu (`/menu`), Book a table (`/booking`), Journal (`/journal`), Visit (`/visit`). Every link to these places uses these addresses. Below `md` the links wrap onto a second line; they fit at 375px, so there is no "Menu" button. Mark one link as current only where this brief names it: it keeps its address and gets `aria-current="page"`.
- **Footer:** a `<footer>` with `mt-18`. Inside it one `<div>` with `border-t py-10`, which is not the grid but holds two things. First a grid `<div>` (`grid gap-6 md:grid-cols-12 md:items-start`): in columns 1–6 a `<div>` with the tea room's name as a `<p>` in `font-heading text-h2` (not a heading) and, with `mt-2`, one sentence about it; in columns 8–12 a `<div>` with an `<address className="not-italic">` holding three block `<span>`s, `14 Mill Lane`, `Ashby Wold`, `AW3 7LN`, then with `mt-4` a `<p>` label, `Tuesday to Sunday · Closed Mondays`. Then, under the grid with `mt-6`, a `<p>` label: `© 2026 The Linden Room`.
- **Headings:** the `<h1>` is `text-h1`, and `text-display` from `md` up only where this brief says so (`text-h1 md:text-display`). An `<h2>` is `text-h2`. An `<h3>` is `text-body`, in the heading font it gets on its own, unless this brief gives it `text-h2`. Young Serif has one weight and no italic: never put `font-bold`, `font-semibold` or `font-medium` on a heading or on any text in `font-heading`, and never slant it. Sentence case, no full stop.
- **Labels:** prices, times, dates, weights and measures, dietary marks, captions, table headers, form labels, and button and badge text are labels: `font-mono text-label`, in sentence case. A label is a short fragment, except helper text under a field and any line this brief writes as a sentence. Nothing is uppercased. A label on its own line is a block element such as a `<p>`. Parts of a label line are joined with ` · `.
- **Dates and times:** a date or a clock time is a `<time>` with a `dateTime` wherever it appears, in a sentence or a summary too: `<time dateTime="2026-11-14">14 November 2026</time>`, `<time dateTime="11:30">11:30</time>`. A `dateTime` always has two-digit hours, while the text has none: `<time dateTime="09:00">9:00</time>`. A date is written day, month in words, year (`14 November 2026`), with the weekday in front (`Saturday 14 November 2026`) only where this brief shows one. In a label or a table, a span of hours is two `<time>` elements joined by an en dash: `9:00–17:00`; in a sentence it reads "from 11:30 to 14:30", each time a `<time>`. A copyright year is plain text. "Today" is always the visitor's own clock, never a sample date.
- **Prices and amounts:** a price, an amount in a recipe or a time in a list is a label in `text-foreground` with `tabular-nums`; in a list of rows it stands at the right end of its row. Prices have the pound sign and two decimals: `£4.20`.
- **Text color:** headings, body text, prices, amounts and form labels are `text-foreground`, and so is any label this brief gives `text-foreground`. Every other label is `text-muted-foreground`: captions, dates, dietary marks, helper text, the label above a heading or at the top of a card, and the footer's hours and © lines. A button's text color comes from its variant. Paragraphs are never muted. `text-primary` (olive) is only for links and for the word that marks today in a table of hours.
- **Links:** a link with no destination named in this brief or these notes points to `#`. A link in running text is `text-primary underline decoration-1 underline-offset-4 hover:text-foreground` and inherits its size. A link in the header or the footer is `text-foreground decoration-1 underline-offset-4 hover:underline`; the current page's link also has `underline`. Links get `transition-colors duration-150`.
- **Added components:** a component you add with `npx shadcn@latest add` during the build (an `Input`, a `Table`, a `Badge`) gets the `cn` import, the focus outline only if it takes focus on the page, and the edits these notes give its file.
- **Buttons:** the shadcn `Button` with `className="h-auto rounded-md px-6 py-2 font-mono text-label"`. The default variant (olive) is for the page's one main action. Any other button is `variant="secondary"` (olive-black, cream in dark mode). Its stock hover mixes in the foreground, the same color here: in `components/ui/button.tsx` replace the `secondary` variant's `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80`, once, and add nothing to the button's `className`. Quiet buttons (the unchosen ones in a group, a stepper's plus and minus) are `variant="outline"`. A button that sends a form needs `type="submit"`: this `Button` is `type="button"` unless told otherwise. A button that its own click disables gets `focusableWhenDisabled`, so focus stays on it: only a stepper's minus and plus. Base UI then sets `aria-disabled="true"` and `data-disabled` instead of `disabled`, so the stock `disabled:` classes do nothing: on a page that uses the prop, and only there, add `data-disabled:pointer-events-none data-disabled:opacity-50` beside them in `components/ui/button.tsx`. A call to action that goes to another page is an `<a>` with the same classes: write `cn(buttonVariants({ variant }), "…")`. Button text is two or three words; a filter's choices are one or two.
- **Button groups:** a set of buttons where one is chosen (a filter) 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 rest have `aria-pressed="false"` and `variant="outline"`.
- **Steppers:** a number set with minus and plus buttons is a `role="group"` named by its visible label (`aria-labelledby` pointing at the label's `id`), with `flex items-center gap-2`: a minus `Button`, the value, a plus `Button`. Each button is `variant="outline" size="icon"` (32px, as it ships: above the 24px WCAG target) with only `rounded-md` added (not the text button's classes) and an `aria-label` that says what it does ("Fewer people"), holding the `Minus` or `Plus` icon. The value is a `<span>` in `inline-flex min-w-24 justify-center font-mono text-label text-foreground tabular-nums` with `aria-live="polite"`, written with its unit (`3 people`), centered between the buttons. At the lowest and highest value the matching button is `disabled` with `focusableWhenDisabled`.
- **Inputs:** the shadcn `Input` and `Textarea` with `className="h-auto rounded-md px-4 py-2 text-body md:text-body"`. Their fill is `bg-card`, the select's: in `components/ui/input.tsx` and `components/ui/textarea.tsx`, replace the stock `bg-transparent` and `dark:bg-input/30` with `bg-card` (in `input.tsx`, `file:bg-transparent` stays), and delete `dark:aria-invalid:border-destructive/50`, so a field's error border matches the select's in dark mode. Every field has a visible `<label>` above it with `block`, a label in `text-foreground`, tied to it with `htmlFor`, and the field has `mt-2`. A field that may be left empty says so: `Note (optional)`. A date field is an `Input` of type `date`.
- **Selects:** a choice from a short list is a native `<select>` with `h-auto w-full rounded-md border border-input bg-card px-4 py-2 text-body text-foreground aria-invalid:border-destructive`. Its first option is an empty choice in words ("Choose a time"). It gets the same label, `mt-2`, focus outline and error handling as any field: with an error, `aria-invalid` turns its border brick red, as the stock `Input` does.
- **Forms:** a form sits in a card, so its fields and select stand on cream. Each field sits in its own `<div>` with its label and the lines under it; these `<div>`s are `mt-6` apart (the first has none), and the button that sends the form sits in a `<div>` with `mt-6`.
- **Errors:** an error is a line under its field with `mt-2`: `flex items-start gap-2 font-mono text-label text-destructive`, holding the `CircleAlert` icon (`shrink-0`; `items-start` sets it at the top of the first line) and a sentence that says what to do. Where a field and a button share a row, it sits under the row. 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. Before the first send no error shows. An error line is removed when its own field changes (on `input` or `change`); every other error stays until the next send.
- **Confirmations:** a confirmation is plain text in `text-foreground`: a heading at the level this brief gives and a sentence or two, no colored box. It goes into an element with `role="status"` that is on the page from the start, empty, so that its message is announced. The form and the status element share one wrapper, the card, so the message stands where the form stood. The status element has no classes and no margin, sits after the form in the card's block flow, and is never hidden (no `hidden`, no `empty:hidden`), so empty it takes no space. The message in it is a `<div>` rendered only when there is a message. Where text stays above it in the card, the message `<div>` has `mt-6`, the margin the form had; where it is the card's only content, it has none. A link or a button that follows a confirmation sits beside the status element in the card, not inside it. Give the message's heading `tabIndex={-1}` and move focus to it.
- **Badges:** the shadcn `Badge` with `className="h-auto rounded-sm border-transparent bg-tertiary px-2 py-1 font-mono text-label text-tertiary-foreground"`. Peach is a fill only: never `text-tertiary` or a peach border. One or two words, no icon. At most one badge on a page.
- **Cards:** a card is a plain element with `rounded-xl border bg-card p-6`, not the shadcn `Card`, which ships its own padding and ring. Inside a card, a label comes first where there is one, and the text follows with `mt-2`. Never nest cards. A card with a picture at its top is `overflow-hidden rounded-xl border bg-card` with no padding: the picture is the plain `<div>` alone, with no `<figure>`, no caption and no corners or border of its own, and fills the card's top edge. The text under it sits in a `<div>` with `p-6`.
- **Pictures:** there are no image files. A picture is a `<figure>` holding a plain `<div>` with an aspect ratio (`aspect-4/3`, `aspect-3/2` or `aspect-square`, as this brief says), `rounded-xl bg-muted`, `role="img"` and an `aria-label` that says what it shows, and a `<figcaption>` under it, a label in `text-muted-foreground` with `mt-2`. The `<figcaption>` is the figure's last child: anything else about the picture goes after the `<figure>`. Draw nothing inside unless this brief asks. A picture in a card is the exception given under Cards.
- **Rows:** a list where each row has a name on the left and a mono value on the right (a menu, ingredients, hours) is a `<ul>` with no markers and no padding, and `divide-y`, so a 1px rule falls between rows and none above the first or below the last. Each `<li>` is `flex items-baseline justify-between gap-4 py-4`, unless this brief gives `py-2`; the value has `shrink-0`. A list in running text is `list-disc` or `list-decimal` with `pl-6`, and `mt-2` between its items.
- **Tables:** the shadcn `Table` with `className="text-body"`. Header cells are labels in `text-muted-foreground` with `h-auto px-4 py-2`; body cells have `px-4 py-2 whitespace-normal`; the first and last cell of a row add `first:pl-0 last:pr-0`. Rows do not change on hover: `hover:bg-transparent` on every row. The stock `Table` wraps the `<table>` in a box that scrolls sideways; make it reachable with the keyboard even where the table fits: in `components/ui/table.tsx`, give that box `tabIndex={0}`, `role="region"` and an `aria-label`, and add a required `label` prop for the name. `label` and a visually hidden `<caption>` (`sr-only`) repeat the words of the heading above the table.
- **Rules:** every line is 1px and `border-border`.
- **Corners:** `rounded-sm` (6px) for badges and small tags, `rounded-md` (12px) for buttons, inputs and selects, `rounded-xl` (20px, the spec's `lg`) for cards and pictures. `rounded-lg` is 12px too; write `rounded-md`. No `rounded-full`.
- **Surfaces:** flat and matte. No shadows, blur, gradients or glows in the code you write. A stock component that floats over the page keeps the one soft shadow it ships with.
- **Icons:** from `lucide-react`, with `size-4`, `strokeWidth={1.5}` and `aria-hidden="true"`, only where this brief or these notes name one. Never emoji.
- **Motion:** in the code you write, the only transition is color. Nothing moves on scroll. Stock components keep the motion they ship with.

## The page

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

Sample content, if the user gave none: "The Linden Room", a tea room and shop at 14 Mill Lane, Ashby Wold, AW3 7LN, on the corner of Mill Lane and Station Road, opposite Orchard Park. Phone 01632 960 418, email hello@lindenroom.example. Open Tuesday to Friday 9:00–17:00, Saturday 9:00–18:00, Sunday 10:00–16:00, closed on Mondays; the kitchen serves plates from 11:30 to 14:30. Ashby Wold station is a four-minute walk; the 12 and 31 buses stop outside the park.

Build these parts in order.

### 1. Header

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

### 2. Opening

The first section, with `pt-10`, in columns 1–8 of a grid (`grid gap-6 md:grid-cols-12`): a label, `Mill Lane, Ashby Wold`, then with `mt-2` the `<h1>` "Visit us" in `text-h1`, then with `mt-4` one or two sentences: where the room is, and that there is no need to book unless you are four or more.

### 3. Address and hours

A section with `mt-18`, as a grid (`grid gap-6 md:grid-cols-12 md:items-start`):

- In columns 1–4: an `<h2>` "Address", then with `mt-4` an `<address className="not-italic">` with three block lines in `text-body`: `14 Mill Lane`, `Ashby Wold`, `AW3 7LN`. With `mt-4` the phone number and with `mt-2` the email address, each a link in running text in its own `<p>` inside the `<address>`, the `<p>` carrying the margin: `tel:+441632960418` and `mailto:hello@lindenroom.example`.
- In columns 6–12: an `<h2>` "Opening hours", then with `mt-6` the hours table.

**Hours table.** The shadcn `Table` as the layout notes describe (add it, with the form's fields, by `npx shadcn@latest add table input textarea` if missing), with `label="Opening hours"`, seven rows from Monday to Sunday and three columns: Day, Room, Kitchen. Header cells have `scope="col"`; the day in each row is a `TableHead` with `scope="row"`, given `h-auto`, the body cells' classes and `font-sans text-body font-normal text-foreground text-left` in place of the header look. The Room and Kitchen cells are labels in `text-foreground` with `tabular-nums`, each time in a `<time>`: `9:00–17:00`, `11:30–14:30`. Monday reads `Closed` in both, as a word.

Today's row gets `aria-current="date"`, and its day cell holds, under the day's name, the word `Today` as a `<span>` with `block font-mono text-label text-primary` and no margin. No fill, no bold. Take today from the visitor's clock.

At 375px the table fits: `whitespace-nowrap` on every Room and Kitchen cell, Monday's `Closed` included; the day cell wraps.

### 4. Getting here

A section with `mt-18`, as a grid (`grid gap-6 md:grid-cols-12 md:items-start`):

- In columns 1–5: an `<h2>` "Getting here", then with `mt-6` four entries as a `<ul>` with no markers and no padding. Each entry after the first has `mt-6`. Each is an `<h3>` in `text-body` (By train, By bus, By bike, By car), and with `mt-2` one or two sentences in `text-body`. By car: there is no parking on Mill Lane; the car park on Station Road is three minutes away.
- In columns 7–12: a `<div>` holding the map's `<figure>` and, after the figure, the directions paragraph.

**The map.** A `<div>` with `relative aspect-4/3 overflow-hidden rounded-xl border bg-muted`, `role="img"`, the `aria-label` "Map of the streets around The Linden Room", and `aria-describedby` pointing at the directions paragraph. Draw it with absolutely placed plain elements, using only the classes below: fractions of the box and `inset-*-0`, no translate and no arbitrary values. No shape is round:

- Two streets, each a strip in `bg-background`: Station Road across the box (`absolute inset-x-0 top-1/2 h-4`) and Mill Lane down it (`absolute inset-y-0 left-1/4 w-4`).
- The railway, a thin strip in `bg-secondary` along the top (`absolute inset-x-0 top-1/6 h-1`).
- Orchard Park, a block in `bg-card` with a 1px border and square corners, in the lower right (`absolute right-1/12 bottom-1/12 flex h-1/5 w-2/5 items-center justify-center`), holding its name tag.
- The tea room, just below the corner of the two streets: a `<div>` with `absolute top-7/12 left-1/4 flex items-center gap-1` holding the `MapPin` icon in `text-primary` with `size-6` (here, instead of the notes' `size-4`), `strokeWidth={1.5}` and `aria-hidden="true"`, and a tag reading `The Linden Room`.
- A tag is `rounded-sm bg-card px-2 py-1 font-mono text-label text-foreground whitespace-nowrap`; the tea room's alone adds `border`. Besides that one and the park's, two more: `Station Road` above its street at the right (`absolute right-1/12 bottom-1/2`), and `Mill Lane` on its street near the top (`absolute top-1/4 left-1/4`).

At 375px the box is about 343 by 257 pixels, and these places keep every tag inside it and clear of the others: the closest pair, the tea room's tag and the park, are about 8px apart.

Under the map, with `mt-2`, the `<figcaption>`: a label in `text-muted-foreground`, `Not to scale`. After the `<figure>`, with `mt-4`, the directions paragraph in `text-body` with an `id`: "From the station, walk down Station Road for four minutes. We are on the corner with Mill Lane, opposite Orchard Park."

### 5. Access

A section with `mt-18`, in columns 1–8 of a grid: an `<h2>` "Access", then with `mt-4` a bulleted list in running text (`list-disc pl-6`, `mt-2` between items) of six notes: a step-free entrance on Mill Lane, an accessible toilet on the ground floor, two tables at wheelchair height by the window, a printed large-type menu at the counter, assistance dogs welcome, and a quiet first hour on Tuesdays with the music off.

### 6. Write to us

A section with `mt-18` and the `id` `contact`, in columns 1–7 of a grid: an `<h2>` "Write to us", then with `mt-4` one sentence saying who reads the messages and that they answer within two days, then with `mt-6` a card that is the wrapper for the form and the confirmation. The `<form>` has `noValidate` and the `aria-label` "Write to us"; after it comes the empty `role="status"` element. Fields, `mt-6` apart:

- "Your name": an `Input` with `autoComplete="name"`.
- "Email address": an `Input` of type `email` with `autoComplete="email"`.
- "About": a native `<select>` styled as the layout notes describe, with the empty choice "Choose a subject", then "A booking", "Buying tea", "Access", "Something else".
- "Message": a `Textarea` with `min-h-48`.

Then, in the notes' `<div>` with `mt-6`, the page's one default button, `type="submit"`: "Send message".

### 7. Footer

The footer from the layout notes.

### Behavior

- Each empty field gets its own error line: "Write your name.", "Write your email address.", "Choose what your message is about.", "Write a message." A malformed address gets "Check the email address: it needs an @ and a domain." Focus moves to the first field with an error.
- When all four are good, the form is replaced in its card by the confirmation in the status element: an `<h3>` in `text-h2`, "Thank you", with `tabIndex={-1}`, and with `mt-4` one sentence saying an answer comes within two days, to the address they gave. The heading and sentence sit in the message `<div>`, the `mt-4` on the sentence; no button follows. Move focus to the heading.
- Today's mark is set when the page loads.
- 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`, `10`, `18`) 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 keep the palette to what could grow in a garden.
- Do give each section room: one idea, then space.
- Do set prices and times in the mono font, quietly.
- Don't use bright or pure colors, gradients or glows.
- Don't crowd the page with badges or buttons: one of each is usually enough.
- 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 hours are a table with real `<th>` cells, and today's row says "Today" in words, not only with a color.
- [ ] The map is drawn from plain elements with `role="img"` and an `aria-label`, and a paragraph next to it gives the same directions in words.
- [ ] The address is an `<address>` element, set upright, with the phone number and email address as links.
- [ ] Every field of the contact form has a visible label, each empty or wrong field gets its own message in words with an icon, and a good message replaces the form with a confirmation that a screen reader announces.
- [ ] At 375px the hours table and the map fit the screen without the page scrolling sideways.
- [ ] 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.
