# Build a therapy fees and questions page with the Solace theme

You are building one page in an existing project: a therapist's page of fees and questions, with a table of session types with their length and fee, how to cancel and how to pay as two cards, eight questions that open and close, and one action. 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 Solace variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/solace.json` and save https://strictthemes.com/themes/solace/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/solace/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:** Literata for headings, Mulish for body text, Xanh 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` 24px/500, `text-body` 17px/400, `text-label` 14px/400.
- **Font per size:** a type class does not set the font family. Headings (`<h1>` to `<h6>`) get the heading font on their own; on any other element add the font class: `font-heading` with `text-display`, `text-h1`, `text-h2`; `font-mono` with `text-label`.
- **Spacing scale**, with the Tailwind step in brackets: 4px (`1`), 8px (`2`), 16px (`4`), 28px (`7`), 48px (`12`), 112px (`28`).
- **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 #22223B, on-primary #F2E9E4, secondary #C9ADA7, on-secondary #22223B, tertiary #9A8C98, on-tertiary #22223B, neutral #4A4E69, background #F2E9E4, surface #FAF5F2, foreground #22223B, border #DFD0C9, error #9B2C2C.

### How Solace pages are laid out

- **Container:** one `<div>` wraps the whole page and is the container: `mx-auto max-w-[1136px] px-4 md:px-7`, plus `text-body text-foreground`. From `md` up the content is at most 1080px wide, the grid in `DESIGN.md`, with 28px at each side. This width is the one arbitrary value allowed. Inside it sit the `<header>`, a `<main>` holding everything between the two, and the `<footer>`.
- **Grid:** a section's content sits in a grid: `grid grid-cols-1 md:grid-cols-12 md:gap-x-7`. The grid sets columns only, never `gap-y-*`: every vertical distance comes from the rhythm rule. Below `md` every grid item takes the full width. From `md` up every grid item gets `md:col-span-*`, because an item without one takes a single column: "columns 1–12" is `md:col-span-12`. It also gets `md:col-start-*` when it starts after column 1, and `md:col-start-1` when it must start a new row under an item that leaves room beside it; or the grid puts it in that room. Where a brief puts "a section's content in columns 1–7", the `<section>` is the grid and the content is one `<div>` with those column classes. Where a list "is itself the grid", the `<section>` is not a grid: the heading above the list sits directly in it.
- **Reading column and note:** running text, lists and forms sit in seven of the twelve columns (`md:col-span-7`), or the columns this brief gives. Beside them, where this brief asks for one, sits one short note in columns 9–12 (`md:col-start-9 md:col-span-4`), with `mt-12 md:mt-0`, so on a phone it comes after the text. Leave the columns this brief does not fill empty, and center nothing to fill them: the empty columns are the calm of the page.
- **Vertical rhythm:** written as a top margin on the lower element (`mt-*`), never as `space-y-*`, `gap-y-*`, a bottom margin or padding, except the padding of a card, a list entry or a table cell, which these notes give. Between a label and the one thing it names (a field's label and its field, a name and the line under it, a term and its value when they stack): `mt-2`. Between a heading and the text under it, between paragraphs, and from a label line to the heading under it: `mt-4`. Between blocks inside a section (a paragraph and a button, a heading and a list or a table, a card and the text after it, two fields of a form): `mt-7`. Between two parts of a section that each have their own heading: `mt-12`. Where two grid items share a row from `md` up, the second has its phone margin and `md:mt-0`. Inside a flex row (the header, a portrait and a name, a row of badges) the space is the row's `gap-*` as this brief states it. Where this brief gives an element its own margin, it replaces the one from this list.
- **Sections:** `<main>` holds a stack of `<section>` elements, each with `aria-labelledby` naming its heading. The first has `mt-12 md:mt-28`. Every other section has `mt-28`: 112px, at every width. Sections are divided by that space alone: no hairline, no fill and no border between them. A section's first element takes no top margin of its own.
- **Header:** the `<header>` is itself the row, with no border: `flex flex-wrap items-center gap-x-7 py-4`. On the left the therapist's name, `Nora Ellingham`, a link to `/` with `inline-flex h-11 items-center font-heading text-h2 text-foreground`, never marked as the current page. It is not a heading element. Then a `<nav>` with the `aria-label` "Main" and `w-full md:ml-auto md:w-auto`, so from `md` up it sits at the row's right end and below `md` it takes a line of its own under the name. It holds a `<ul>` with `flex flex-wrap gap-x-4 md:gap-x-7` and four links, in this order: "Approach" (`/approach`), "About" (`/about`), "Fees" (`/fees`) and "First session" (`/first-session`). Each is `inline-flex h-11 items-center text-body text-foreground decoration-1 underline-offset-4 hover:underline`. Each brief names the current one, if any: it keeps its address and adds `aria-current="page"` and `underline`. The four fit on one line at 375px, so there is no "Menu" button. The header holds no button: the page's one action is in the page.
- **Footer:** a `<footer>` with `mt-28 border-t border-border py-12`, the one hairline that runs the width of a page. Inside it a grid like the one above, with two parts:
  - Columns 1–7 (`md:col-span-7`): the name as a `<p>` in `font-heading text-h2`, `Nora Ellingham` (not a heading and not a link); with `mt-2` a label line in `text-muted-foreground`, `Registered psychotherapist · Larkmere and online`; with `mt-7` an `<address>` with `not-italic` in `text-body`, one line, `The Garden Room, 14 Orchard Row, Larkmere LK2 4DN`; with `mt-2` a `<p>` holding the email address `hello@ellingham.example` as a `mailto:` link, styled as a link in running text.
  - Columns 9–12 (`md:col-start-9 md:col-span-4`, with `mt-7 md:mt-0`): a `<nav>` with the `aria-label` "Footer" holding a `<ul>` of the header's four links, one under the other, in the same order and with the same classes. The current page is marked here too.
  - Under the grid, with `mt-7`, a `<p>` in `text-body text-foreground`, at most `max-w-xl`: "This practice is not an emergency service. In an emergency, call your local emergency number or go to the nearest emergency department." Then with `mt-4` a label line in `text-muted-foreground`: `© 2027 Nora Ellingham`.
- **The practice:** Nora Ellingham is an invented registered psychotherapist who works alone, with adults, one to one, in private practice since 2014. She sees people in The Garden Room, 14 Orchard Row, Larkmere LK2 4DN (an invented town) and online by video. Sessions are held on Tuesday 09:00–17:00 (in person), Wednesday 12:00–20:00 (in person or online), Thursday 09:00–17:00 (online) and Friday 09:00–13:00 (online). A session is 50 minutes and costs £68. It starts with an introductory call of 20 minutes, by phone or video, which is free. A longer session of 80 minutes is £102, and a reduced-fee session is £42. She replies to a request within two working days, by email from `hello@ellingham.example`; the practice has no public phone number. Cancelling is free with 48 hours' notice. She sends one invoice on the last Friday of each month, paid by bank transfer within 14 days. She speaks English and Dutch. Every page uses these names, times and fees as they are here. Name no real person, place, register, insurer, school or helpline.
- **Writing:** the words are part of the design. Nora writes as one person to another: "I", never "we" for the practice, and "you". Short sentences and plain words. No exclamation marks, no urgency ("book now", "today only", "limited places"), no promise of a result ("heal", "fix", "overcome", "transform"), no praise of herself. Copy that this brief gives in quotation marks is used word for word. Where you write a sentence yourself, write it the same way, and say what happens rather than how it will feel.
- **Headings:** the `<h1>` is `text-h1`, and `text-display` from `md` up where this brief says so (`text-h1 md:text-display`). An `<h2>` is `text-h2`, and so is an `<h3>`, unless this brief gives another size. Headings get the heading font on their own; any other element in `text-display`, `text-h1` or `text-h2` adds `font-heading`. Sentence case, no full stop. Nothing on these pages is bold: the theme has two weights, 400 and the 500 that `text-h2` and `font-medium` give. Never `font-semibold` or `font-bold`, no `<strong>` or `<b>`, and a `<th>`, which is bold by default, gets `font-normal`.
- **The italic:** both the heading font and the body font have a true italic. It is the page's one emphasis, and it is used in two places only. A phrase inside a heading, where this brief marks it, is an `<em>` with `italic`. Words a client said are a `<blockquote>` whose `<p>` is `font-heading text-h2 italic`. Nothing else is italic: an `<address>` is italic by default, so every `<address>` gets `not-italic`.
- **Labels:** times, lengths of sessions, fees, days of the week, dates, step numbers and places in a short line are labels: `font-mono text-label`, in sentence case. Nothing is uppercased, in the text or with a class, and no `tracking-*` class is added. A label is a short fragment. Join the parts of a label line with ` · `. A label on its own line is a block element such as a `<p>`. Nav links, button text, field labels and legends are not labels: they are in the body font.
- **Captions:** a caption under a quotation or a table and the helper text of a field use the label style in `text-muted-foreground` (`font-mono text-label text-muted-foreground`) and may be a short sentence or two.
- **Text color:** headings, body text, nav links, names, field labels, fees and times in a table are `text-foreground`. Label lines above or under a heading, terms in a `<dl>`, column headers of a table, step numbers and captions are `text-muted-foreground`. Muted text sits only on `bg-background` or `bg-card`. Paragraphs are never muted. No other text colors, except `text-destructive` on an error line and the colors a button, a badge and a portrait bring.
- **Measure:** a paragraph of body text is at most `max-w-xl` wide, however many columns it sits in. A heading may run wider.
- **Times, dates and fees:** a clock time is 24-hour with two-digit hours, `09:00`, and a span of hours has an en dash and no spaces, `09:00–17:00`. A time that stands in a table or a label line is a `<time>` with a `dateTime` (`<time dateTime="09:00">09:00</time>`), with `tabular-nums`. A time or a length inside a sentence, a field's label or a message is plain text. A length is written out: `50 minutes`. A fee is whole pounds with the pound sign, `£68`, or the word `Free`. A date is written `1 January 2027`, and in a label line it is a `<time>` too (`dateTime="2027-01-01"`). A year is a `<time>` only where this brief says so; the copyright year is plain text.
- **Links:** a link in running text is `text-foreground underline decoration-1 underline-offset-4 hover:text-muted-foreground`, and inherits its size and font. Links are the text color; nothing is colored to mark it as a link. Every link gets `transition-colors duration-150`. A link that stands alone at the end of a part is a `<p>` holding such a link, with no icon. A link whose destination this brief does not give points to `#`. The class lists in these notes and briefs leave out the focus outline: add the outline classes from the rules to every link, every native radio and every plain `<button>`.
- **Buttons:** the shadcn `Button` with `className="h-auto px-7 py-2 text-body"`: the body font, sentence case, two to four words, 12px corners (its stock `rounded-lg`) and about 46px tall. It keeps its stock `font-medium`: weight 500 is the heaviest weight the theme's type scale has, so no button is `font-semibold`. The default variant (indigo, rose taupe in dark mode) is for the page's one main action: one on a page, and this brief names it. Any other button is `variant="secondary"` (rose taupe, slate in dark mode). In `components/ui/button.tsx`, replace that variant's stock `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80`, once, not beside it: the 5% mix cannot be told from the resting color. Make this change on every page, also one with no secondary button, so every page's `button.tsx` is the same. Do not use the `outline`, `ghost`, `link` or `destructive` variants, or the `xs`, `sm` or icon sizes. A button that sends a form needs `type="submit"`: this `Button` is `type="button"` unless told otherwise. A call to action that goes to another page is an `<a>` with the same classes: write `cn(buttonVariants({ variant }), "h-auto px-7 py-2 text-body")`, because `buttonVariants` alone does not merge classes. No icon inside a button.
- **The one action:** a page asks for one thing. Where this brief ends a page with the action, it is a card from these notes holding an `<h2>`, with `mt-4` one or two sentences that say what happens after it, and with `mt-7` a `<div>` holding the default button "Request a first session", an `<a>` to `/first-session`. Nothing on a page hurries: no countdown, no "limited", no banner, no pop-up.
- **Added components:** a component you add with `npx shadcn@latest add` during the build (an `Input`, a `Textarea`, a `Checkbox`, a `Badge`) gets the `cn` import from the setup steps, the focus outline only if it takes focus on the page, and the edits these notes give its file.
- **Inputs:** the shadcn `Input` and `Textarea` with `className="h-auto border-muted-foreground px-4 py-2 text-body md:text-body"`; a `Textarea` adds `min-h-32`. The stock `border-input` is too faint on blush; `border-muted-foreground` reaches 3:1 in both modes. They keep their stock 12px corner (`rounded-lg`), the fill they ship with (in dark mode too), the stock `focus-visible:border-ring` and the stock `aria-invalid:border-destructive`. Every field has a visible `<label>` above it with `block text-body font-medium text-foreground`, tied to it with `htmlFor`. Under the label, where this brief gives one, a helper line with `mt-2`: a caption in a `<p>` with an `id`, which the field names in `aria-describedby` at all times. The field has `mt-2` under the label or the helper line. Fields follow each other with `mt-7`, on the wrapper `<div>` that holds a label, its helper line, its field and its error. A field that may be left empty says so in its label: `Phone number (optional)`. No placeholder text.
- **Groups of choices:** a set of radios or checkboxes that answers one question is a `<fieldset>` with `min-w-0`, inside its wrapper `<div>`, with a `<legend>` as its first child in `text-body font-medium text-foreground`. A helper line, where this brief gives one, follows with `mt-2`, then the options, the first with `mt-2`, then the error line while there is one. The fieldset's `aria-describedby` names the helper line and, while it shows, the error line.
- **Radios:** native `<input type="radio">` elements, drawn round with classes, not the shadcn `RadioGroup`: `size-5 shrink-0 appearance-none rounded-full border border-muted-foreground checked:border-6 checked:border-primary`, so the chosen one is a thick indigo ring (rose taupe in dark mode) around a hole. Each option is a row, `flex min-h-11 items-center gap-4`, holding the input with an `id` and its `<label>` with `htmlFor` in `text-body text-foreground`. All options of a group share a `name`. No option is chosen when the page opens.
- **Checkboxes:** the shadcn `Checkbox` with `className="size-5"`. In `components/ui/checkbox.tsx`, change its `rounded-[4px]` to `rounded-sm` and its `border-input` to `border-muted-foreground`, once, so it has the theme's 6px corner and a border that can be seen (where the file ships another corner or border class, change that one). Its stock check icon and checked fill (`bg-primary`) stay. In a group, each option is a row, `flex min-h-11 items-center gap-4`. A checkbox whose label is a sentence that may wrap is a row with `flex items-start gap-4`, and `mt-1` on the checkbox so it lines up with the first line. The `<label>` is `text-body text-foreground`. Give the `Checkbox` an `id` and the `<label>` `htmlFor` with it, so a click on the words ticks the box; give the `<label>` an `id` too, and the `Checkbox` `aria-labelledby` naming it, because in a Base UI project the `Checkbox`'s `id` lands on a hidden input and the visible box would otherwise have no name.
- **Errors:** an error is a line under its field or under the last option of its group, with `mt-2`: a `<p>` with an `id` and `flex items-start gap-2 text-body text-destructive`, holding the `CircleAlert` icon (`mt-1 shrink-0`) and one or two short sentences that say how to put it right, in the same calm voice as the rest. A field gets `aria-invalid` and adds the line's `id` to its `aria-describedby` (after the helper line's); a group names the line in its fieldset's `aria-describedby`. Fields are checked in code when the form is sent, with `noValidate` on the `<form>`; before the first send no error shows. Focus moves to the first field with an error, or to the first option of the first group with one. Render the error lines first and move focus after that render, in an effect or a `requestAnimationFrame`, so the field's description is there when it is read. An error line and its field's `aria-invalid` are removed when that field changes (on `input` or `change`; for a `Checkbox`, on `onCheckedChange`; for a group, when any of its options changes). Every other error stays until the next send. An email is well formed when, trimmed, it matches `/^[^\s@]+@[^\s@]+\.[^\s@]+$/`.
- **Forms:** a form sits in a card, and the card is the wrapper for the form and the confirmation that replaces it: a card from these notes holding the `<form>` and, after it, the status element. The `<form>` has `noValidate` and an `aria-labelledby` naming the heading inside it. Its send button sits under the last field with `mt-7`, full width below `md` (`w-full md:w-auto`).
- **Confirmations:** a message that replaces a form goes into an element with `role="status"` that is in the card from the start, empty, so that its message is announced. It is never hidden (no `hidden` attribute, no `empty:hidden`) and has no classes and no margin; empty, it takes no space. The form is removed when the message appears, so the message stands where the form stood. The message is a `<div>` rendered only once there is one: an `<h2>` in `text-h2`, then with `mt-4` the paragraphs this brief gives, in `text-body`, each a `<p>`, `mt-4` apart. Give the heading `tabIndex={-1}` and move focus to it, because the button that had focus is gone. No colored box, no tick icon.
- **Cards:** a card is a plain element with `rounded-xl border border-border bg-card p-7`: paler blush, 20px corners, 28px padding, a 1px border, no shadow. Not the shadcn `Card`, which ships its own padding and a ring. Inside it the first element takes no top margin. Use one only where this brief asks for it. Never a card inside a card, and nothing with a border and corners inside a card except the fields of a form: divide a card's parts with space.
- **Portraits:** there are no image files. A portrait is a round fill with the person's initials: a `<div>` with `flex size-18 shrink-0 items-center justify-center rounded-full bg-secondary font-heading text-h2 text-secondary-foreground`, `role="img"` and the `aria-label` "Portrait of Nora Ellingham", holding one `<span>` with `aria-hidden="true"`, `NE`. It is 72px across, the largest full round the theme allows, so it is never made larger. Nothing else is drawn in it and nothing is drawn around it: no icon, no ring, no leaf, no wave. It stands beside the name and a line of training, in a row with `flex items-center gap-4`, and never inside a button or a link.
- **Badges:** the shadcn `Badge` (`npx shadcn@latest add badge`) with `className="h-auto rounded-sm border-transparent bg-tertiary px-2 py-1 font-mono text-label font-normal text-tertiary-foreground"`: mauve grey with ink, in both modes. One or two words in sentence case, no icon: "Online", "In person", "Evenings". Only where this brief puts one. Nothing else on a page is `bg-tertiary`.
- **Lists with hairlines:** a list where this brief gives each entry a hairline (what people bring, training, questions) is a `<ul>`, `<ol>` or `<dl>` with `list-none`, no padding and no space between its entries; each entry has `border-t border-border py-4`, and the list a `border-b border-border` that closes it. Where this brief lays an entry out in columns, the entry itself carries the grid classes beside these.
- **Description lists:** a `<dl>` where a term stands beside its value is a list with hairlines whose rows are `<div>` elements with `grid grid-cols-3 items-baseline gap-x-4` beside the entry's classes: the `<dt>` is a label in `text-muted-foreground`, the `<dd>` takes the other two columns (`col-span-2`) in `text-body`. This holds at 375px too.
- **Tables:** a plain `<table>` with `w-full border-collapse border-b border-border text-left`, not the shadcn `Table`. Its first child is a `<caption>` with `sr-only` that names it; the heading above it names it on screen. Header cells are `<th scope="col">` with `py-2 pr-4 font-mono text-label font-normal text-muted-foreground`. Each row of the `<tbody>` has `border-t border-border`. Its first cell is a `<th scope="row">` with `py-4 pr-4 align-baseline text-body font-normal text-foreground`; the other cells are `<td>` with `py-4 pr-4 align-baseline`. The last cell of every row, the header row included, has `pr-0` in place of `pr-4`. No stripes, no hover fill, no outer box. The table is not in a scroll box: the cells this brief names get `whitespace-nowrap` and the others wrap, so it fits at 375px.
- **Icons:** from the project's icon set, `lucide-react`, with `size-4`, `strokeWidth={1.5}` and `aria-hidden="true"`. Use them only where this brief or these notes name one: `CircleAlert` in an error line, `Plus` and `Minus` on a question. Never emoji, and no drawn decoration.
- **Corners:** `rounded-sm` (6px) for badges and checkboxes; the stock 12px of the `Button`, `Input` and `Textarea` (their `rounded-lg`, which this theme sets to 12px); `rounded-xl` (20px, the `lg` corner in `DESIGN.md`) for cards. `rounded-full` only for a portrait and a radio.
- **Surfaces:** flat and soft. A card is the only surface, and it has a border and no shadow. No shadows, blur, gradients or glows in the code you write, and no colored band behind a section. A stock component that floats over the page keeps the one soft shadow it ships with; these pages use none.
- **Motion:** in the code you write, the only transition is color, and nothing moves on scroll or on load. Stock components keep the motion they ship with, the `Button`'s included.

## The page

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

Sample content, if the user gave none: the fees of **Nora Ellingham**, an invented registered psychotherapist who works alone, with adults, in a garden room in Larkmere, an invented town, and online by video. An introductory call of 20 minutes is free; a session of 50 minutes is £68, the first one too; a longer session of 80 minutes is £102; a reduced-fee session of 50 minutes is £42. Cancelling is free with 48 hours' notice, and inside 48 hours the full fee is charged. One invoice goes out by email on the last Friday of each month and is paid by bank transfer within 14 days. Fees are from 1 January 2027 and are reviewed each January. She does not bill insurers; name none. The copy below is given word for word: use it as it is.

Build these parts in order.

### 1. Header

The header from the layout notes. "Fees" is the current page, in the header and in the footer.

### 2. Opening

The first section, in the grid, with its `aria-labelledby` naming the `<h1>`:

- A label line in `text-muted-foreground`, columns 1–12 (`md:col-span-12`): `Fees from 1 January 2027`.
- With `mt-4`, the `<h1>` in `text-h1 md:text-display`, columns 1–9 (`md:col-span-9`): "Fees, said plainly". The words "said plainly" are an `<em>` with `italic`.
- With `mt-7`, a `<p>` in columns 1–7 (`md:col-start-1 md:col-span-7`, which overlap the `<h1>`'s, so it starts a row under it), at most `max-w-xl`, in `text-body`: "Here is what a session costs, how to pay and how to cancel. If something is not answered here, ask me. It is an ordinary question."

### 3. What sessions cost

A section in the grid, with two parts:

**The table**, in columns 1–7 (`md:col-span-7`):

- An `<h2>`: "What sessions cost".
- With `mt-7`, a table from the layout notes. Its `<caption>`: "Session types, with their length and fee". Three columns, with the headers `Session`, `Length` and `Fee`; the `Fee` header and every fee cell add `text-right`. In each row:
  - The row header, as the Tables note gives it, holding the session's name and under it a `<span>` with `mt-2 block font-mono text-label text-muted-foreground`, a short line on how it is held.
  - The length, a `<td>` that adds `whitespace-nowrap font-mono text-label text-foreground`.
  - The fee, a `<td>` that adds `whitespace-nowrap font-mono text-label tabular-nums text-foreground`.

  The rows, in this order:

  1. "Introductory call" / `By phone or video`, `20 minutes`, `Free`
  2. "First session" / `In person or online`, `50 minutes`, `£68`
  3. "Weekly session" / `In person or online`, `50 minutes`, `£68`
  4. "Longer session" / `By arrangement`, `80 minutes`, `£102`
  5. "Reduced-fee session" / `Ask me when you write`, `50 minutes`, `£42`
- With `mt-4`, a caption from the layout notes, a `<p>`: "Fees are reviewed each January. I tell you two months before any change."

**The note**, in columns 9–12 (`md:col-start-9 md:col-span-4 md:self-start`), with `mt-12 md:mt-0`: a card from the layout notes. `md:self-start` keeps the card as tall as its own text, not as tall as the column beside it.

- An `<h3>` in `text-h2`: "If the fee is a worry".
- With `mt-4`, a `<p>` in `text-body`: "Say so when you write. I keep some sessions at £42 for people on a low income, and I will tell you plainly whether I can offer one."

### 4. Cancelling and paying

Here, in place of one section, a `<div>` with the section's `mt-28` and the grid classes holds two `<section>` elements, each a card from the layout notes and each with `aria-labelledby` naming its own `<h2>`: the first in columns 1–6 (`md:col-span-6`), the second in columns 7–12 (`md:col-span-6`) with `mt-4 md:mt-0`. From `md` up the two cards are the same height. In each card:

- A label line in `text-muted-foreground`.
- With `mt-4`, an `<h2>`.
- With `mt-4`, three paragraphs in `text-body`, the second and third with `mt-4`.

The first card, word for word: label `48 hours' notice`; "How to cancel"; "Write to me at least 48 hours before your session, and there is no charge."; "Inside 48 hours I charge the full fee. I hold that time for you, and I cannot offer it to someone else at short notice."; "If I have to cancel, I tell you as early as I can, and you pay nothing."

The second card, word for word: label `One invoice a month`; "How to pay"; "I send one invoice by email on the last Friday of each month, for the sessions we had."; "You pay by bank transfer within 14 days. The details are on the invoice."; "I do not take cash or cards in the room. That keeps money out of the session."

### 5. Questions people ask

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

- An `<h2>`: "Questions people ask".
- With `mt-4`, a `<p>` in `text-body`: "Eight of them. Open the ones you need."
- With `mt-7`, a `<ul>` as a list with hairlines (see the layout notes), eight entries. Here an entry has no padding of its own (in place of the notes' `py-4`): the button carries it, so the whole row can be pressed. Each `<li>` holds:
  - An `<h3>` holding one plain `<button type="button">`, not a component, with an `id`, `aria-expanded` and `aria-controls` naming its answer, and `flex w-full cursor-pointer items-center justify-between gap-4 py-4 text-left font-heading text-h2 text-foreground hover:text-muted-foreground transition-colors duration-150`, plus the focus outline from the rules. It holds the question in a `<span>` and, after it, an icon with `shrink-0`: `Plus` while the answer is closed, `Minus` while it is open.
  - The answer: a `<div>` with an `id`, the `hidden` attribute while it is closed, and `pb-4`, holding one `<p>` in `text-body`, at most `max-w-xl`.

  The questions and answers, in this order, word for word:

  1. "How long would I come for?": "There is no set length. Some people come for eight or ten sessions, some for a year or longer. Every sixth session we look at whether it is still useful to you."
  2. "Do I need a referral from my doctor?": "No. You can write to me yourself. I do not contact your doctor unless you ask me to."
  3. "Can I use health insurance?": "I do not bill insurers. I can give you a receipt each month, and some policies pay you back. Your insurer can tell you whether yours does."
  4. "What if I am late?": "The session still ends at its usual time, so that the next person is not kept waiting. The fee is the same."
  5. "What if I am ill, or away?": "Tell me 48 hours before, and there is no charge. If you are too unwell to travel but well enough to talk, we can meet online instead."
  6. "Do you take holidays?": "Yes, about six weeks a year. I give you the dates at least a month ahead. You do not pay for sessions I cancel."
  7. "Is what I say confidential?": "Yes. What you say stays between us. There are a few limits, which are about safety and the law, and I explain them before we begin."
  8. "Can we change from in person to online?": "Yes. Tell me the day before, and I send you a video link. The time and the fee stay the same."

No `Accordion` component here: the plain buttons above are the whole mechanism.

### 6. The one action

A section in the grid, with one card in columns 1–7 (`md:col-span-7`), built as the note The one action says. The section's `aria-labelledby` names the card's `<h2>`.

- The `<h2>`: "If you would like to start".
- With `mt-4`, a `<p>` in `text-body`, at most `max-w-xl`: "Write to me with the form. The first call is free, and nothing is charged until we have met for a first session."
- With `mt-7`, a `<div>` holding the default button "Request a first session", an `<a>` to `/first-session`. The page's one default button.

### 7. Footer

The footer from the layout notes.

### On a phone

Every part is one column. The `<h1>` is `text-h1`. The table keeps its three columns: a session's name and the line under it wrap, the length and the fee stay on one line. The note comes 48px under the table's caption. The two cards stack, 16px apart, each as tall as its own text. A question's words wrap over two or three lines beside its icon, which stays at the right, centered on them.

### Behavior

- Keep the open questions in state as a set of their numbers; none is open when the page opens.
- Pressing a question's button, with the mouse, Enter or Space, opens its answer if it is closed and closes it if it is open: `aria-expanded` follows, the icon changes, and the answer loses or gets the `hidden` attribute. The others stay as they are, so any number can be open. Focus stays on the button. Nothing slides or fades.
- Nothing else has state. Links only.

## Rules

- **Colors:** only the theme's colors, through the semantic classes (`bg-background`, `text-foreground`, `bg-card`, `bg-primary`, `text-primary-foreground`, `text-muted-foreground`, `border-border` and so on). No hex, rgb or named colors in components, and no Tailwind palette classes such as `bg-blue-500` or `text-gray-600`.
- **Spacing:** padding, margin and gap use only the scale's steps (`1`, `2`, `4`, `7`, `12`, `28`) plus 0, 1px and 2px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-3` or `gap-6`. `auto` margins are fine. Widths and heights such as `w-64` or `size-4` are sizes, not spacing. This applies to the code you write: a stock component keeps the spacing inside it, unless this brief changes it.
- **Type:** every piece of text you write gets its size from one of `text-display`, `text-h1`, `text-h2`, `text-body`, `text-label`, set on the element or on a parent it inherits from. Put `text-body` on the outermost element of the page you write. No other sizes. A stock shadcn component keeps the size it ships with, unless `DESIGN.md` or this brief gives that component its own style. A type class does not replace a weight class: where a stock component ships `font-medium` and the text should be regular, add `font-normal`.
- **Fonts:** only the theme's fonts, through `font-sans`, `font-heading` and `font-mono`. Do not add a font.
- **Corners:** only the `rounded-*` classes, which the theme already sets. No arbitrary radius, 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 72px 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 write as one person speaks to another: short sentences, plain words, "you" and "I".
- Do give one action on a page and say what happens after it.
- Do say fees, lengths and how to cancel plainly.
- Don't use urgency: no countdowns, no "limited places", no pop-ups.
- Don't promise results, and don't decorate with leaves, waves or sunrises drawn in code.
- Don't use gradients, glows or a card inside a card.
- Don't introduce a color, size or radius that is not a token above.

## Check before you finish

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

- [ ] The fees are a `<table>` with a visually hidden caption and five rows, each with its length and its fee in the mono font: Free, £68, £68, £102 and £42; the fee column is right-aligned and no cell is bold.
- [ ] How to cancel and How to pay are two cards side by side from `md` up, each led by a label line, and they say 48 hours, the last Friday of each month and 14 days.
- [ ] Each of the eight questions is a `<button>` inside an `<h3>`, with aria-expanded and with aria-controls naming its answer; all are closed when the page opens, and any number can be open at once.
- [ ] A question opens and closes with Enter and with Space, its icon changes from Plus to Minus, and a closed answer has the hidden attribute.
- [ ] The page has exactly one default (indigo) button, Request a first session; the only italic is the phrase in the `<h1>`, and nothing is bold.
- [ ] At 375px the fees table fits its column without scrolling sideways, and each question's words wrap beside its icon.
- [ ] 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.
