# Build a documentation page with the Cipher theme

You are building one page in an existing project: one reference page of a command-line tool's docs, with a sidebar of pages, code blocks that copy, a table of flags, a note, an "on this page" list and links to the previous and next page. 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 Cipher variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/cipher.json` and save https://strictthemes.com/themes/cipher/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/cipher/tailwind.css instead. One of the theme's fonts is Geist, which a new shadcn project already imports: keep that import. If the global CSS imports any other font the theme does not use, 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:** JetBrains Mono for headings, Geist for body text, JetBrains Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 44px/800, `text-h1` 30px/700, `text-h2` 20px/700, `text-body` 15px/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`; the two classes are the same family in this theme.
- **Spacing scale**, with the Tailwind step in brackets: 4px (`1`), 8px (`2`), 12px (`3`), 16px (`4`), 24px (`6`), 48px (`12`).
- **Corners:** `rounded-sm` 3px, `rounded-md` 6px, `rounded-xl` 10px (the `lg` corner in `DESIGN.md`). `rounded-lg` is 6px as well: it is shadcn's name for the base corner, the one its stock buttons and inputs use.
- **Colors (light mode):** primary #5B32E6, on-primary #FFFFFF, secondary #0E1116, on-secondary #FFFFFF, tertiary #C6F135, on-tertiary #0E1116, neutral #565E6E, background #F1F3F1, surface #FFFFFF, foreground #0E1116, border #D3D8DA, error #C4281C, success #177A45.

### How Cipher pages are laid out

- **Fonts:** headings, labels, code and figures are JetBrains Mono; body text is Geist. `font-heading` and `font-mono` are the same family here: write `font-mono` on labels, code and figures, and `font-heading` only on heading-sized text that is not a heading element.
- **Page:** left-aligned, nothing centered or justified; the outermost element is `min-h-svh bg-background text-body text-foreground`.
- **Container:** the header's row, the main content and the footer's row sit in a centered container, `mx-auto max-w-[1200px]`, with `px-4`, and `px-6` from `md` up. This width is the one arbitrary value.
- **Grid:** where this brief gives columns, the element is a 12-column grid with `gap-6` (24px gutters), and a single column below the breakpoint the brief names (`md` unless it says `lg`). The grid's `gap-6` is also the vertical distance between its items when they stack: its items get no top margin. A grid item that holds code or a table gets `min-w-0`, or a long line widens it past the screen.
- **Vertical rhythm:** inside a column, a panel or a section, the distance between two blocks is a top margin on the lower one, never `space-y-*` or a vertical `gap` on a stack. The first element of a container has no top margin. The steps: `mt-2` between a label and the thing it labels, between a field's label and the field, and between the items of a list of text (links that are blocks with their own `py-*`, as in a menu or a sidebar, get no margin); `mt-3` between a heading and the text under it and between paragraphs; `mt-4` between form fields; `mt-6` between blocks (a code block, a table, a note, a form, a row of buttons) and between a block and the paragraph after it; `mt-12` between the sections of a page. Inside a row (`flex` on one line), the distance is a `gap-*`: `gap-2` between a word and its icon or dot and between buttons that belong together, `gap-3` between the items of a row of figures. Where this brief gives a margin, it replaces the step.
- **Headings:** the `<h1>` is `text-h1`; `text-display` only where this brief says so. An `<h2>` is `text-h2`. An `<h3>` is `text-body font-bold`, in the mono font it gets as a heading. Sentence case, no full stop; a command in a heading keeps its case (`plinth deploy`).
- **Labels:** eyebrows, field labels, table headers, captions, nav links, the name strip of a code block, and button and badge text are labels: `font-mono text-label`. Write them in sentence case, or lowercase where this brief writes them so. Nothing is uppercased, with a class or in the text. A label is a short fragment; only helper text and error lines, set in the label style, are sentences. A label on its own line is a `<p>` or other block. Values from the tool (a branch, an environment, a scope, a kind of change, a state) are lowercase, as the CLI prints them: `main`, `production`, `deploy:write`, `fixed`, `ready`.
- **Figures:** commands, paths, flags, versions, commit hashes, tokens, durations, dates and times are always `font-mono`, in running text too, at the size of the text around them; standing on their own (a table cell, a row of figures) they also get `tabular-nums`. A "small figure" is one in `font-mono text-label tabular-nums text-muted-foreground`. Every date and time shown is a `<time>` with a `dateTime`, written one way: a date `10 Nov 2026` (no leading zero, the month's first three letters; `dateTime="2026-11-10"`), a date with a time `11 Nov 2026, 14:05` (24-hour; `dateTime="2026-11-11T14:05"`), a log line's time `14:05:12` (`dateTime="2026-11-11T14:05:12"`). In a table, durations and times are right-aligned, and so is their header (`text-right` on the `TableHead`). A cell with no value holds an em dash, `—`.
- **Text color:** headings, body text, field labels, values and code are `text-foreground`. Other labels, small figures, helper text, code comments and a terminal's prompt sign are `text-muted-foreground`. Paragraphs are never muted. `text-primary` is for links and, where this brief says so, a config file's keys. `text-tertiary` is never used: lime is a fill only.
- **Measure:** a paragraph of body text is at most `max-w-lg` wide (512px).
- **Links:** a link this brief gives no address goes to `#`. The product's name goes to `/`, Docs to `/docs`, Changelog to `/changelog`, Open console to `/deploys`, and a link to the page itself to its own address. A text link is `text-primary underline underline-offset-4 hover:text-foreground transition-colors duration-150` and inherits its size. The product's name in a header is `text-foreground` and never current. Other links in a header, a footer or a sidebar are `text-muted-foreground hover:text-foreground transition-colors duration-150`, without an underline; the link for the current page has `aria-current="page"` and is `text-foreground underline underline-offset-4`, so the mark is not color alone.
- **Site header:** on the landing, docs and changelog pages, the first thing is a `<header>` with `border-b border-border`, running the full width of the window. Inside the container, one row, `flex flex-wrap items-center gap-x-6 gap-y-2 py-3`: on the left the product's name, a link to `/` in `font-heading text-h2`, lowercase (`plinth`); then a `<nav>` with the `aria-label` "Main" holding the nav links as labels with `gap-6`; then the header's actions in one `flex items-center gap-3` element at the right end (`ml-auto`).
- **Console bar:** on the deploys and tokens pages, the site header's place is taken by a full-width `<header>` with `border-b border-border bg-card` and the same row: the product's name, then the project as a small figure (`orrin / orrin-web`), then the `<nav>` "Main", then at the right end the signed-in person's email as a small figure, hidden below `md`. Below `md` its nav links go behind the "Menu" button.
- **Footer:** site pages only: a full-width `<footer>` with `mt-12` and `border-t border-border`. Inside the container, one row, `flex flex-wrap items-center gap-x-6 gap-y-2 py-6`: the copyright as a label in `text-muted-foreground` (`© 2026 Plinth Labs`), then at the right end (`ml-auto`) a `<nav>` with the `aria-label` "Footer" holding three footer links as labels, `flex flex-wrap gap-x-6 gap-y-2`.
- **Main:** `<main>` holds the container. On the site pages `<main>` has `pt-12` and no bottom padding (the footer's `mt-12` gives the space). On the console pages `<main>` has `py-6`, and its container is a 12-column grid whose first item, spanning all twelve columns, is the page header: a row that is not a panel, `flex flex-wrap items-center justify-between gap-3`, with the `<h1>` on the left and the page's actions on the right, `gap-2` apart.
- **Panels:** a panel is a plain element with `rounded-xl border border-border bg-card p-6`, no shadow. Not the shadcn `Card`, which ships its own padding and a ring. Never put a panel or a bordered code block inside a panel: divide a panel's parts with 1px borders.
- **Panel heading:** a panel opens with a heading row, unless this brief says it has none: `flex min-h-8 flex-wrap items-center justify-between gap-3`, holding the panel's heading, an `<h2>` or the `<h3>` this brief names, as a label heading: `font-mono text-label text-muted-foreground` in place of the heading style of its level, and, at its right end, anything that belongs to the whole panel. Content follows with `mt-4`.
- **Panels with rows:** a panel that holds a table, a list of rows or a log has no padding of its own and `overflow-hidden`. Its heading row sits in a wrapper with `px-6 py-3` and a 1px bottom border. A row that closes such a panel (paging, a form) has a 1px top border and `px-6 py-4`.
- **Buttons:** the shadcn `Button` with `className="px-4 font-mono text-label"`. It keeps its stock height and `rounded-lg` corners. The default variant (violet) is for the one main action of a screen: at most one enabled default button per screen. Other actions are `variant="secondary"` (near-black, light in dark mode). Its stock hover changes nothing in this theme: 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`, once, and add no hover class to a secondary button you write. Quiet actions are `variant="outline"`. 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 }), "…")`, because `buttonVariants` alone does not merge classes. Button text: sentence case, one to three words. An icon in a `Button` keeps the button's stock gap; the rhythm's `gap-2` is for rows you write. No button here keeps the focus while its own click disables it ("Clear filters" moves focus to the search field), so none gets `focusableWhenDisabled` and `components/ui/button.tsx` needs no `data-disabled:` classes.
- **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"`.
- **Badges:** the shadcn `Badge` with `className="h-auto rounded-sm px-2 py-1 font-mono text-label"`, in the `outline` variant. The signal badge adds `border-transparent bg-tertiary text-tertiary-foreground`. A page has at most one lime mark, where this brief puts it.
- **Inputs:** the shadcn `Input` with `className="text-body md:text-body"`, keeping its stock height, padding and corners. In `components/ui/input.tsx` remove the class `dark:bg-input/30` (`disabled:bg-input/50` and `dark:disabled:bg-input/80` stay), so a field shows the surface behind it in both modes, as a select does. Every field has a visible `<label>` above it with `block` and `htmlFor`, in `font-mono text-label text-foreground`; the field has `mt-2`. Helper text under a field is a small figure with `mt-2`, named in the field's `aria-describedby`.
- **Selects:** a native `<select>` with `h-8 w-full rounded-lg border border-input bg-transparent px-2 text-body aria-invalid:border-destructive`, its width set on a wrapper, with a label, errors and the focus outline like any field. A select of tool values (a scope) adds `font-mono`.
- **Errors:** an error is a line with `mt-2` under its field: `flex items-start gap-2 font-mono text-label text-destructive`, holding the `CircleAlert` icon (`size-4 shrink-0`, `aria-hidden="true"`, no margin: `items-start` lines its top up with the 15.6px line) and a sentence that says what to do. The field gets `aria-invalid`, and its `aria-describedby` lists the line's id after the helper text's. 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`); every other error stays until the next submit.
- **Confirmations:** a message that appears after an action goes into an element with `role="status"` that is on the page from the start, empty, and never hidden. The status element itself has no classes that draw or take space: the message inside it, rendered only when there is one, carries the margin and any fill. A message that replaces a form shares one wrapper with it, which carries the top margin; buttons that follow the message ("Copy", "Done") sit beside the status element in that wrapper, not inside it. Where the message replaces the control that had focus, give it `tabIndex={-1}` and move focus to it.
- **State:** a state is a dot and a word, side by side: `inline-flex items-center gap-2`. The dot is `size-2 shrink-0 rounded-full` with `aria-hidden="true"`: `bg-success` for passed or ready, `bg-destructive` for failed, `bg-primary` for under way, `bg-muted-foreground` for waiting or stopped. The word is `text-foreground`, lowercase. Never show a state by color alone. `text-success` and `text-destructive` are only for a short message or log line that also says in words what it is, and only on `bg-card` or `bg-background`, never on `bg-muted`.
- **Code blocks:** a `<figure>` with `rounded-xl border border-border bg-card overflow-hidden`. First a strip, `flex items-center justify-between gap-3 border-b border-border px-4 py-2`: the file name or the language as a label in `text-muted-foreground` (a `<figcaption>`), and at the right a "Copy" `Button` in `variant="outline"` and `size="sm"` with the button classes. Then the `<pre>`, `overflow-x-auto p-4 font-mono text-body`, scrolling sideways as the rules say, with an `aria-label` such as "Code: plinth.toml" and its outline drawn inside. Lines never wrap; comments are `text-muted-foreground`. After a copy, the button reads "Copied" with a `Check` icon for two seconds; if the copy fails, "Copy failed" for as long. Beside it, a visually hidden `role="status"` element, there from the start, says the same; it is the one announcement, since the button's changed text is not a live region. It copies the code alone, with no prompt sign or output, through `navigator.clipboard.writeText`.
- **Inline code:** a `<code>` in running text is `rounded-sm bg-muted px-1 py-0.5 font-mono`, at the size of the text around it.
- **Tables:** the shadcn `Table` with `text-body`. Header cells are labels in `text-muted-foreground`; they keep their stock height. Every cell gets `px-3`, the first `first:pl-6` and the last `last:pr-6`; body cells also get `py-2`. Stock cells do not wrap: a cell that holds a sentence gets `whitespace-normal`. Rows keep their 1px borders and stock hover, also where a row does nothing: no stripes. No `<caption>`: the heading above the table names it on screen, and the `label` names its scroll box. A table here fills a panel with rows and scrolls sideways in its own box: edit `components/ui/table.tsx` to give its scroll box `tabIndex={0}`, `role="region"`, an `aria-label` from a required `label` prop, and the focus outline drawn inside it (`focus-visible:-outline-offset-2`).
- **Icons:** from `lucide-react`, with `size-4`, `strokeWidth={1.5}` and `aria-hidden="true"`, beside a word or above a heading, and only where this brief or these notes name one. No emoji, and no check or cross typed as a character.
- **Corners:** `rounded-sm` (3px) for badges and inline code, `rounded-lg` (6px, as shipped) for buttons, inputs and selects, `rounded-xl` (10px) for panels, code blocks and the terminal, `rounded-full` only for status dots.
- **Surfaces:** flat: no shadows, glows, blur, gradients or scanlines in your code.
- **Menus on small screens:** below `md` (below `lg` on the docs page) the nav links, and what this brief names, go behind a "Menu" button (`variant="outline"`) with `aria-expanded` and `aria-controls`, at the right end of the row. It shows them inside the `<header>`, under its row: a second `<nav>` with the same `aria-label`, shown only below the breakpoint, with the `id` that `aria-controls` names and the `hidden` attribute while closed; at any width one of the two is hidden, so they may share a name. It has a 1px top border across the full width and, inside the container, `py-2`; each link, "Open console" and "Sign in" too, is a block with `py-2` in the header-link style, not a button. The "Menu" button has `ml-auto` and is shown only below the breakpoint (`md:hidden`, `lg:hidden` on docs); the row's `<nav>` and actions element are hidden below it (`hidden md:flex`, `hidden lg:flex` on docs). Escape closes it and refocuses the button. No `Sheet`, no `Dialog`, and one "Menu" button per page.
- **Motion:** in the code you write, the only transition is color. Nothing blinks, types itself out or moves on scroll.

## The page

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

Sample content, if the user gave none: the reference page for `plinth deploy` in the docs of **Plinth**, a command-line tool by Plinth Labs that deploys a web project from the terminal (version `2.4.0`). The page says what the command does, shows its usage, lists its flags, gives two examples and lists its exit codes. The previous page is `plinth init`, the next `plinth logs`. Write the text yourself, short and exact.

Build these parts in order.

### 1. Header

The site header from the layout notes. The product's name and, after it, the label `docs` in `text-muted-foreground` share one element with `flex items-baseline gap-2`, which is the row's first item. Nav links, in order: Docs (`/docs`, the current part of the site: here `aria-current="true"` in place of `"page"`, with the same underline), Changelog (`/changelog`), Pricing (`#`). Action at the right end: an `outline` button-link "Open console" (`/deploys`). Below `lg`, not `md`, the row holds the name, the `docs` label and the "Menu" button. The menu holds the three nav links, then "Open console", then the sidebar's groups in the sidebar's order. The nav links and "Open console" are labels; the sidebar's links keep the sidebar's `text-body`, commands in `font-mono`, with the menu's `py-2`. Each group opens with its label, a `<p>` with `mt-4` in `font-mono text-label text-muted-foreground`, and its links follow with no margin; `plinth deploy` has `aria-current="page"` there too. It is the page's only menu.

### 2. Page layout

Here `<main>` has no top padding, in place of the notes' `pt-12`. Its container is, from `lg` up, a 12-column grid; below `lg` only the content column shows.

- **Columns 1–3, the sidebar.** A `<nav>` with the `aria-label` "Docs", hidden below `lg`. It stays in view and scrolls on its own: `sticky top-0 max-h-svh self-start overflow-y-auto pt-6 pb-6`, with `-mx-1 px-1` so the box does not clip the links' focus outline.
- **Columns 4–12 (4–10 from `xl` up), the content.** It has `min-w-0` and `pt-6`.
- **Columns 11–12, on this page.** Shown from `xl` up only: `sticky top-0 self-start pt-6`.

### 3. Sidebar

Three groups, each a label in a `<p>` (not a heading) in `text-muted-foreground`, and under it with `mt-2` a list of links; the groups are `mt-6` apart. Each link is a block with `py-1` in `text-body`, styled as a sidebar link from the layout notes, and the list's items have no margin; links that are commands are in `font-mono`. A docs page's address is `/docs/` plus its group and name, lowercase with hyphens: this page is `/docs/commands/deploy`, the previous `/docs/commands/init`, `plinth.toml` `/docs/reference/plinth-toml`.

- **Get started:** Install, Quick start, Configuration.
- **Commands:** `plinth init`, `plinth deploy` (the current page), `plinth logs`, `plinth rollback`, `plinth env`.
- **Reference:** `plinth.toml`, Environment variables, Exit codes.

### 4. Content

In this order:

1. A breadcrumb as a label in a `<p>`, `text-muted-foreground`: `Commands / plinth deploy`.
2. With `mt-2`, the `<h1>`: `plinth deploy`.
3. With `mt-3`, one paragraph: what the command does.
4. Four sections, each opened by an `<h2>` with `mt-12`, an `id` and `scroll-mt-6`: Usage, Flags, Examples, Exit codes. Inside a section the rhythm of the layout notes holds: `mt-3` from the heading to the text and between paragraphs, `mt-6` before and after a code block, the table and the note.

**Usage.** A paragraph, then a code block with the strip label `shell` holding `plinth deploy [path] [flags]`, then a paragraph that explains `path` with inline code.

**Flags.** One sentence, then the table, then the note.

- Add the component with `npx shadcn@latest add table`. The table fills a panel with rows that has no heading row of its own: the section's `<h2>` names it, and the `Table`'s `label` is "Flags of plinth deploy". Columns: Flag, Short, Default and Description. Flag, Short and Default are `font-mono`; an em dash, as the notes say, marks no value. The Description cells hold a sentence and get `whitespace-normal`. Give the `<table>` `min-w-xl`, so on a phone it scrolls rather than squeezes. Seven rows: `--env <name>` (`-e`, `preview`), `--prebuilt` (no short, `false`), `--message <text>` (`-m`, no default: the description says the commit's message is used), `--wait` (no short, `false`), `--yes` (`-y`, `false`), `--json` (no short, `false`), `--token <token>` (no short, `$PLINTH_TOKEN`).
- The note is an `<aside>`, a panel with `p-4` in place of `p-6`. Its first line is `flex items-center gap-2`, the `Info` icon and the label `note` in `text-foreground`. Under it, with `mt-2`, one or two sentences: `--yes` skips the confirmation before a production deploy, and a deploy run with no terminal attached, as in CI, needs it or stops with exit code 2.

**Examples.** A sentence, then a code block with the strip label `deploy a preview`, then with `mt-6` a sentence and a code block with the strip label `deploy to production and wait`. Each holds one command: `plinth deploy` and `plinth deploy --env production --wait --yes`. Lines that start with `#` are comments in `text-muted-foreground`; give the second block one comment line above the command.

**Exit codes.** With `mt-3` under the heading, a list without markers, its items `mt-2` apart. Each item is a line: the code as inline code, then the meaning in a few words of plain text, not `font-mono`. `0` the deploy is ready, `1` the build failed, `2` the deploy needs a confirmation, `3` the configuration is wrong, `4` it timed out while waiting.

**Previous and next.** At the end of the content, with `mt-12`, a `<nav>` with the `aria-label` "Previous and next" and a 1px top border, `pt-6`, holding two links in a row with `flex flex-wrap justify-between gap-6`. Each link is a block: a line with `flex items-center gap-2`, the label "Previous" or "Next" in `text-muted-foreground` beside the `ArrowLeft` or `ArrowRight` icon (the arrow before "Previous" and after "Next"), and under it, in a `<p>` with `mt-2`, the page's name in `font-heading text-h2 text-foreground`. The link has `group`, the name `underline-offset-4 group-hover:underline`. "Next" has `ml-auto text-right`, so it stays at the right end when the row wraps, and its icon line `justify-end`.

### 5. On this page

A `<nav>` with the `aria-label` "On this page". At its top, a label in a `<p>`, "On this page", in `text-muted-foreground`. Under it with `mt-2`, a list with one link per `<h2>` of the content (`#usage` and so on), the items `mt-2` apart. Each link is a label, `font-mono text-label`, `text-muted-foreground hover:text-foreground` with `transition-colors duration-150`.

### 6. Footer

The footer from the layout notes, with the links "Edit this page" (`#`), Changelog (`/changelog`) and Status (`#`).

### Behavior

- Mark the section in view in "On this page". Work it out from the headings' positions on scroll: the section in view is the last `<h2>` whose top is above a line one third of the way down the window, the first one before any is, and the last one when the page is scrolled to its end. The marked link is `text-foreground underline underline-offset-4` with `aria-current="location"`; the others keep their muted color and no underline.
- The "Copy" buttons work as the layout notes say, and copy the command without the comment lines.
- The "Menu" button works as the layout notes say, and the "On this page" links jump to their headings.

## 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`, `4`, `6`, `12`) plus 0, 1px and 2px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-5` 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 12px 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 set every command, path, flag and version in the mono font.
- Do keep lime to one mark per screen.
- Do left-align, and let lines be long before you center anything.
- Don't use violet as a fill for large areas, and never as a gradient.
- Don't use glows, neon outlines or scanline effects. This is a tool, not a poster of one.
- 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 sidebar marks the current page with `aria-current="page"` and an underline, not by color alone.
- [ ] Every code block has a working "Copy" button that confirms the copy in words.
- [ ] The "On this page" links jump to their headings and mark the one in view with `aria-current="location"`.
- [ ] Below `lg` the page has one "Menu" button, and it opens the nav links and the sidebar's pages inside the header.
- [ ] Code blocks and the flags table scroll sideways inside their own box at 375px, and the page does not.
- [ ] 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.
