# Build an artist page with the Voltage theme

You are building one page in an existing project: the page of one act at the festival, with its name, where it is from, a picture, a short text, its sets and the other acts on the same day. 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 Voltage variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/voltage.json` and save https://strictthemes.com/themes/voltage/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/voltage/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:** Unbounded for headings, Familjen Grotesk for body text, Space Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 64px/900, `text-h1` 38px/800, `text-h2` 22px/700, `text-body` 17px/400, `text-label` 12px/700.
- **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`), 12px (`3`), 20px (`5`), 32px (`8`), 64px (`16`).
- **Corners:** `rounded-sm` 6px, `rounded-md` 14px, `rounded-xl` 22px (the `lg` corner in `DESIGN.md`). `rounded-lg` is 14px as well: it is shadcn's name for the base corner, the one its stock buttons and inputs use.
- **Colors (light mode):** primary #0A0A0A, on-primary #FFE500, secondary #2B2BF0, on-secondary #FFFFFF, tertiary #FF4F9A, on-tertiary #0A0A0A, neutral #4A4838, background #FFE500, surface #FFFFFF, foreground #0A0A0A, border #0A0A0A, error #B00020.

### How Voltage pages are laid out

- **Page:** the outermost element of the page is a `<div>` with `min-h-screen bg-background font-sans text-body text-foreground`. The page carries the color: paint nothing else yellow, hover colors included.
- **Container:** `mx-auto max-w-[1200px] px-3 md:px-5`. This width is the one arbitrary value allowed. Sections fill its full width.
- **Grid:** where a section has panels side by side, it is a grid with `grid grid-cols-1 gap-5 md:grid-cols-12`, and each item spans the columns this brief gives it (`md:col-span-4` and so on). Below `md` every item takes the full width, in source order. Panels in one row stretch to the same height.
- **Vertical rhythm:** every vertical distance is a `gap-*` on a `flex flex-col` stack or on a grid, or a padding this brief names. No `mt-*`, `mb-*` or `space-y-*`, except the icon margin in the Icons note. The steps: `gap-1` between a value and the small line under it; `gap-2` between a label and the thing it labels, between a field and its error line, and between the buttons of a group; `gap-3` between a heading and the text under it, between paragraphs, between the items of a list and between buttons side by side; `gap-5` between blocks inside a panel, between form fields and between panels; `gap-8` between a section's heading row and its content; `gap-16` between sections.
- **Main:** the `<main>` element carries the container and the stack: `mx-auto max-w-[1200px] px-3 md:px-5 flex flex-col gap-16 py-8 md:py-16`. Each section is a `<section>` with `flex flex-col gap-8` and no padding, whatever its children. No background bands and no borders around sections.
- **Links and addresses:** the pages live at `/` (the event page), `/line-up`, `/tickets`, `/info` and `/artists/<slug>` for an act (`/artists/ossa-bloom`). A link this brief names without an address goes to the page it names; any other link with no destination is `#`.
- **Header:** a `<header>` that runs the full width of the window with `border-b border-border`. Inside it, a `<div>` carries the container and the row: `flex flex-wrap items-center gap-x-8 gap-y-3 py-3`. On the left the festival's name, "Tremor Days", a link to `/` in `font-heading text-h2 text-foreground` with no underline at rest and `hover:underline underline-offset-4`; then a `<nav>` with the `aria-label` "Main" and `hidden md:flex gap-5`, holding three links as labels, in this order and with no `<ul>`: Line-up (`/line-up`), Tickets (`/tickets`) and Info (`/info`). Then at the right end (`ml-auto`) the festival's dates as a label, `4–5 Dec 2026`, `hidden md:block`. A nav link is `text-foreground hover:underline underline-offset-4`. The current page's link has `aria-current="page"` and `underline decoration-2 underline-offset-4`; this brief says which one it is, if any. The header holds no button besides "Menu".
- **Menus on small screens:** below `md` a "Menu" button with `aria-expanded` and `aria-controls` sits at the right end of the row, with `ml-auto md:hidden`: the dates that carry the `ml-auto` from `md` up are hidden below it. It is a quiet button from the Buttons note. It shows the same three links, stacked, inside the `<header>`, under its row: a second `<nav>` with the same `aria-label` "Main", `md:hidden`, which carries the `id` that `aria-controls` names and has the `hidden` attribute while closed. The second nav has a 1px `border-t border-border` across the full width, and inside it a `<div>` with the container and `py-2` that holds the links directly, with no `<ul>`; each link is `block py-2`. It closes on Escape, which returns focus to the button. Do not use a `Sheet` or a `Dialog`.
- **Footer:** a `<footer>` that runs the full width with `border-t border-border`. Inside it, a `<div>` carries the container and the stack, `flex flex-col gap-3 py-8`: the festival's name as plain text in `font-heading text-h2`, one sentence in `text-body` saying what it is, and a label line with the place, the dates and the copyright (`Varsby Docks · 4–5 Dec 2026 · © 2026 Tremor Days`).
- **Headings:** Headings stay short: five words or fewer, sentence case, no full stop, and no single word longer than nine letters in an `<h1>`. The page's `<h1>` is `text-h1 md:text-display`: 38px on a phone, 64px from `md` up. It is the only `text-display` text on the page, unless this brief names another element that uses it. A section's `<h2>` is `text-h2 md:text-h1`. A heading inside a panel is an `<h2>` or `<h3>`, whichever keeps the order, in `text-h2`.
- **Section heading row:** a section opens with a row, `flex flex-wrap items-end justify-between gap-3`, holding its `<h2>` and, where this brief gives one, a link at the right end.
- **Labels:** dates, times, prices, stage names, field labels, nav links, captions, table headers, and button and badge text are labels: `font-mono text-label font-bold uppercase`, in `text-foreground`. The `font-bold` holds the weight inside stock components. A label is a short fragment. Inside a label line, stage names (Crane Stage) and the festival's name are uppercased with the rest; in the header's and the footer's name, a heading and a sentence they keep their case. Only the name of a person or an act keeps its case in a label line: put it in a `<span>` with `normal-case`.
- **Small print:** helper text, the note under a total, a count such as `3 of 10 sets`: `font-mono text-label text-muted-foreground`, never uppercased, fragment or sentence. A count in small print adds `tabular-nums`. A caption is a label in `text-muted-foreground`. Paragraphs are never muted, and nothing inside a button or a field is muted.
- **Numbers and dates:** a date, a time, a price or a count that stands on its own is `font-mono tabular-nums`. A large number, the one figure a panel or a block is about, is `font-heading text-h1 tabular-nums`. A range is two `<time>` elements joined by an en dash: `<time dateTime="2026-12-04">4</time>–<time dateTime="2026-12-05">5 Dec 2026</time>`, and `17:30–18:15` the same way with full date-times. Every date and clock time that stands on its own or sits in a label, a heading or a table is a `<time>` with a `dateTime` (`2026-12-04T16:00+01:00`, or `2026-12-04` for a day), the dates in the header and the footer included; a year alone (the copyright) and a button's text stay plain. A number in running text (a sentence, or a line of a list in `text-body`) is plain text in that text's font; a date or a time there is a `<time>` only where this brief says so. Dates come day first, with no leading zero: in a label the short form (`Fri 4 Dec`, `4–5 Dec 2026`), in a heading, a sentence or a table cell the long form (`Friday 4 December`). Times are 24-hour (`16:00`). Nothing on these pages depends on today's date except the countdown, which reads the clock.
- **Text on the page and on panels:** short text may sit straight on the yellow page: headings, labels, one line of facts, and at most two sentences of body text. Anything longer goes on a panel. A paragraph is at most `max-w-xl` wide.
- **Panels:** a panel is a plain element with `flex flex-col gap-3 rounded-xl border border-border bg-card p-5`. Where this brief gives a panel another gap, it replaces `gap-3`. Do not use the shadcn `Card`. Never put a panel inside a panel: divide a panel's parts with a 1px `border-t border-border` and the panel's gap.
- **Panels with rows:** a panel that holds a list of rows edge to edge has no padding of its own, `gap-0` and `overflow-hidden`. Its heading row sits in a wrapper with `px-5 pt-5 pb-3` and a 1px `border-b border-border`. Each row has `px-5 py-3` and a 1px `border-t border-border`, except the first.
- **Links:** a text link is `text-foreground underline underline-offset-4 hover:decoration-2` and inherits its size. No link changes color, so a link has no transition. Links are never blue or pink.
- **Focus:** a link or a `<select>` you write also gets the outline from the Focus rule, which the class lists here leave out; a stock component gets it in its own file.
- **Buttons:** the shadcn `Button` with `className="h-auto px-5 py-2 font-mono text-label font-bold uppercase"`, keeping its stock `rounded-lg` (14px). The default variant (black with yellow text) is for the one main action, and a screen shows at most one. The second action is `variant="secondary"` (electric blue, white text). In `components/ui/button.tsx`, in the secondary variant, replace the class `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80`, since the stock hover cannot be told from the resting color; add no hover class where you use the variant. Quiet actions, the "Menu" button included, are `variant="outline"`: white with a black outline on the page and on a panel. In the same file, in the outline variant, replace `bg-background` (the page's yellow) with `bg-card`; replace `hover:bg-muted`, `aria-expanded:bg-muted` and `dark:hover:bg-input/50` with `hover:bg-accent`, `aria-expanded:bg-accent` and `dark:hover:bg-accent` (`bg-muted` is yellow too); and delete `dark:bg-input/30`, which would beat `bg-card` in dark mode and leave the button see-through. Keep `dark:border-input`. A button that holds only an icon is `size="icon"`, quiet, with an `aria-label`. A button that sends a form needs `type="submit"`: this `Button` is `type="button"` unless told otherwise. A button that keeps the focus while its own click disables it gets `focusableWhenDisabled`; this brief names each one. Base UI then sets `data-disabled` and not `disabled`, so on a page that uses the prop, and only there, add `data-disabled:pointer-events-none data-disabled:opacity-50` beside the stock `disabled:` classes 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 }), "…")`, because `buttonVariants` alone does not merge classes. A button or a link with `inline-flex` that is a direct child of a `flex-col` stack stretches to the stack's width: give it `self-start` unless this brief makes it full width.
- **Button groups:** a set of buttons where one is chosen (a day) is a `role="group"` with `flex flex-wrap gap-2`, named by an `aria-label`, or `aria-labelledby` where this brief gives it a visible label. The chosen button has `aria-pressed="true"`, `variant="secondary"` and the `Check` icon before its text, so the fill is not the only sign; the rest have `aria-pressed="false"` and are quiet buttons. Not `Tabs`: there are no tab panels.
- **Badges:** the shadcn `Badge` with `className="h-auto rounded-sm border-transparent bg-tertiary px-2 py-1 font-mono text-label font-bold uppercase text-tertiary-foreground"`: hot pink with black text, one or two words, no icon. At most one badge on a page. Any other state is a word.
- **Inputs:** the shadcn `Input` with `className="h-auto px-3 py-2 text-body md:text-body"`. It keeps its stock `rounded-lg` and black `border-input` outline. Give it the same fill as a select: in `components/ui/input.tsx`, replace the class `bg-transparent` with `bg-card` and delete `dark:bg-input/30`; `file:bg-transparent`, `aria-invalid:border-destructive` and `dark:aria-invalid:border-destructive/50` stay. Fields sit on panels. Every field has a visible `<label>` above it with `block`, tied to it with `htmlFor`, styled as a label; a field and its label are a stack with `gap-2`. No placeholder text.
- **Selects:** a native `<select>` with `h-auto w-full rounded-lg border border-input bg-card px-3 py-2 text-body text-foreground aria-invalid:border-destructive`. Set a width on an element around it, not on the `<select>`. It gets a label and an error line like any field.
- **Errors:** an error is a line under its field, in the field's stack: `flex items-start gap-2 font-mono text-label text-destructive`, not uppercased, holding the `CircleAlert` icon (`shrink-0`) and a sentence that says what to do. The field gets `aria-invalid` and points to the line with `aria-describedby`. Fields are checked in code when the form is sent, with `noValidate` on the `<form>`, and focus moves to the first field with an error. An error line is removed when its own field changes (on `input` or `change`); an error that depends on a second field is also removed when that field changes. 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; one that arrives with its text is often not announced. The status element has no classes that draw or take space, and is never hidden. The form, the status element and any button that follows the message share one wrapper, a plain `<div>` with no gap, so the empty status element adds no space. Inside the status element, rendered only with the message, an element carries the message's layout; its first element has `tabIndex={-1}` and gets focus, because the button that had focus is gone. A button after the message sits in the wrapper, outside the status element, in a `<div>` with `pt-3` rendered only with the message. The theme has no success color: a confirmation is `text-foreground`, with the `Check` icon and words that say what happened.
- **Tables:** the shadcn `Table` with `text-body` on it. Header cells are labels in `text-muted-foreground`, with `px-3` and their stock height. Body cells get `px-3 py-2`. Add `first:pl-0 last:pr-0` to both, since a table here sits in a padded panel. Rows are divided by 1px borders; in `components/ui/table.tsx` remove the row's yellow fills: `hover:bg-muted/50`, `has-aria-expanded:bg-muted/50` and `data-[state=selected]:bg-muted`. The table scrolls sideways in its own box: in the same file give the scroll box `tabIndex={0}`, `role="region"` and an `aria-label`, and add a required `label` prop for the name. The visible name is the heading above the table's panel; no `<caption>`.
- **Pictures:** there are no image files. A picture is a plain element with an aspect ratio (`aspect-square` or `aspect-video`), `rounded-xl border border-border bg-foreground`, `role="img"` and an `aria-label` that says what it shows. Anything drawn inside it is `bg-background`, with no corners, or text in `text-background`. A picture with a caption is a `<figure>` with `flex flex-col gap-2`, its `<figcaption>` a caption.
- **Avatars:** a circle, `size-12 shrink-0 rounded-full bg-foreground`, holding the act's initials in `font-mono text-label font-bold text-background`, centered with `flex items-center justify-center`, with `aria-hidden="true"`, since the name stands beside it.
- **Icons:** from `lucide-react`, with `size-4` and the default 2px stroke. Use them only where this brief or these notes name one. An icon beside a word has `aria-hidden="true"`. In an `items-start` row beside `text-body`, an icon gets `mt-1` so it lines up with the first line; beside a label it needs none.
- **Corners:** `rounded-sm` (6px) for badges and checkboxes, `rounded-lg` (14px) for buttons, inputs and selects, `rounded-xl` (22px, the `lg` corner in `DESIGN.md`) for panels and pictures, `rounded-full` only for avatars and countdown dots.
- **Colors that are not text:** `bg-secondary` (blue) fills buttons only. `bg-tertiary` (pink) fills the one badge only. Never use `text-secondary`, `text-tertiary` or `text-primary`: text is `text-foreground`, `text-muted-foreground`, `text-destructive`, or the foreground class of the fill it sits on.
- **Surfaces:** flat. No shadows in the code you write, offset "sticker" shadows included, no glow, no blur and no gradients. Every line is 1px `border-border`. A stock menu or dialog keeps the short shadow it ships with.
- **Motion:** in the code you write, the only transition is color. Nothing moves on scroll, nothing pulses or blinks, and a number that changes just shows its new value. Stock components keep the motion they ship with, such as the `Button`'s 1px nudge while pressed.

## The page

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

Sample content, if the user gave none: **Tremor Days**, a two-day music and design festival at Varsby Docks, on Friday 4 and Saturday 5 December 2026. The act is **Ossa Bloom**, an invented electronic duo from Gothenburg, Sweden: Edda Lind builds the music, Sami Rahko the light. They play the Crane Stage on Friday, 22:40–23:50, and give a talk in the Drawing Office on Saturday, 15:00–15:45, "Light shows from scrap". Also on Friday: Gravel & Gold (Crane Stage, 19:00), The Night Ferries (Crane Stage, 20:40), Sister Antenna (Pump House, 21:00) and Basement Choir (Pump House, 23:00). All names are invented.

Build these parts in order, under the header from the layout notes, in which Line-up is the current page, since an act's page belongs to it.

### 1. Name

A section with no heading row. Its stack has `gap-3`:

- A text link back to the line-up, "All acts" (`/line-up`), with the `ArrowLeft` icon before it, in a row with `inline-flex self-start items-center gap-2`, so it does not stretch across the stack. It is a label, so it has `font-mono text-label font-bold uppercase` and keeps the link's underline.
- A stack with `gap-2`: the label `Fri 4 Dec · Crane Stage`, the date a `<time dateTime="2026-12-04">`, then the `<h1>` "Ossa Bloom". The label gives the live set only; the Saturday talk is listed under Sets.
- Where they are from, in `font-heading text-h2`: "From Gothenburg, Sweden".

### 2. About

A section with a visually hidden `<h2>` "About Ossa Bloom" and the grid from the layout notes; the two items stack below `md`, picture first.

**Picture, `md:col-span-5`:** a `<figure>` from the Pictures note. The picture is `aspect-square` and is drawn from plain elements: inside it, a stack with `flex h-full flex-col justify-end gap-3 p-5` holds three bars of light, each `h-3 bg-background` with no corners, with the widths `w-1/2`, `w-3/4` and `w-full`, and under them the duo's initials, "OB", in `font-heading text-display text-background`, the one other `text-display` text on the page. The picture has `role="img"` and the `aria-label` "Drawing of three bars of light over the letters OB, on black". The bars and the letters inside it get `aria-hidden="true"`. The `<figcaption>`: `Edda Lind · Sami Rahko`, each name in its own `<span>` with `normal-case`.

**Text, `md:col-span-7`:** a panel with `gap-5`:

1. An `<h3>` in `text-h2`: "Music you can see".
2. Three paragraphs in `text-body`, in a stack with `gap-3`, each at most `max-w-xl`: how the two met, what a set sounds like, and how the light rig is built from parts found at scrap yards. Write them yourself; keep them plain and specific, about 120 words in all.
3. A row of facts with `border-t border-border pt-5` and `flex flex-wrap gap-x-8 gap-y-3`, three pairs, each a stack with `gap-1`: a label, here in `text-muted-foreground` in place of the Labels note's `text-foreground`, and its value in `text-body`. `Plays` "Live electronics and light", `First album` "Static Harbour, 2024", `Members` "Edda Lind, Sami Rahko".

### 3. Sets

A section. Its heading row holds the `<h2>` "Sets at Tremor Days".

One panel with rows across the full width, with no heading row of its own: the section's `<h2>` names it. It holds a `<ul>` with no markers and no padding, one `<li>` per set as a row from the Panels with rows note, laid out as `flex flex-col gap-1 md:flex-row md:items-center md:gap-5`:

1. The day and the times, `md:w-56 md:shrink-0`, in `font-mono text-label font-bold uppercase tabular-nums`: `Fri 4 Dec · 22:40–23:50`. The day is a `<time dateTime="2026-12-04">`; each time is a `<time>` with a full `dateTime` and the offset (`2026-12-04T22:40+01:00`).
2. What the set is, `md:flex-1`, an `<h3>` in `text-h2`: "Live set" and "Talk: Light shows from scrap".
3. The stage, `md:w-40 md:shrink-0`, as a label: `Crane Stage` and `Drawing Office`.

On a phone the three parts stack in that order inside the row.

### 4. Also on Friday

A section. Its heading row holds the `<h2>` "Also on Friday" and, at the right end, a text link "Full line-up" (`/line-up`).

Then the grid from the layout notes with four panels, each `md:col-span-6 lg:col-span-3`: two by two on a tablet, four in a row on a laptop, one under another on a phone. Each panel is the stock panel, laid out as a row: `flex-row items-center gap-3`, in place of the panel's `flex-col`. It holds:

- An avatar from the Avatars note with the act's initials (`GG`, `NF`, `SA`, `BC`).
- A stack with `min-w-0 flex-1 gap-1` (`flex flex-col`): the act's name as an `<h3>` in `text-h2 break-words` holding a link to its page (`/artists/<slug>`; no underline at rest, `underline underline-offset-4` on hover), and under it a label line with the start as a `<time>` and the stage: `19:00 · Crane Stage`. A long name wraps between words inside the stack, and `break-words` breaks a single word too long for it, which can happen four to a row at `lg`.

### 5. Footer

The footer from the layout notes.

### Behavior

- The page has no state of its own besides the menu button. "All acts", "Full line-up" and the act names are links and need no behavior.
- Keep the act's details, its sets and the other acts as data in the component, so the page could be filled for any act from the same shape.

## 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`, `3`, `5`, `8`, `16`) plus 0, 1px and 2px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-6` or `gap-10`. `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 page yellow and put content on white panels.
- Do set one short headline very large.
- Do outline every panel and field in black.
- Don't put small body text straight on yellow for long passages: use a panel.
- Don't add gradients, glows or hard offset shadows.
- 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 act's name is the page's `<h1>`, `text-display` from `md` up and `text-h1` below, and where the act is from stands right under it.
- [ ] The picture is a plain element with `role="img"` and an `aria-label`, with no image file, and its caption sits under it.
- [ ] The text about the act is on a white panel, not straight on the yellow page.
- [ ] Each set gives its day and its times as `<time>` elements with a `dateTime`, and its stage in words.
- [ ] The other acts on the same day are panels with a round avatar of initials, the act's name as a link, and its time and stage.
- [ ] 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.
