# Build a front page with the Dispatch theme

You are building one page in an existing project: a city newspaper's front page, with a masthead, one lead story, a column of other stories, a live strip and three sections of three stories each. 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 Dispatch variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/dispatch.json` and save https://strictthemes.com/themes/dispatch/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/dispatch/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:** Big Shoulders for headings, Newsreader for body text, Chivo Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 88px/800, `text-h1` 56px/800, `text-h2` 32px/700, `text-body` 18px/400, `text-label` 12px/500.
- **Font per size:** a type class does not set the font family. Headings (`<h1>` to `<h6>`) get the heading font on their own; on any other element add the font class: `font-heading` with `text-display`, `text-h1`, `text-h2`; `font-mono` with `text-label`.
- **Spacing scale**, with the Tailwind step in brackets: 4px (`1`), 8px (`2`), 16px (`4`), 24px (`6`), 48px (`12`), 96px (`24`).
- **Corners:** `rounded-none` 0px, `rounded-sm` 1px, `rounded-md` 2px.
- **Colors (light mode):** primary #BF0D2A, on-primary #FFFFFF, secondary #111111, on-secondary #FFFFFF, tertiary #FFE14D, on-tertiary #111111, neutral #5C5850, background #F4F1EA, surface #FFFFFF, foreground #111111, border #1C1C1C, error #9E0019.

### How Dispatch pages are laid out

- **Container:** the `<header>`, the `<main>` and the `<footer>` each hold one `<div>` that is the container: `mx-auto max-w-[1280px]`, with `px-4`, and `px-6` from `md` up. The three landmarks carry no classes of their own; every class these notes give the header, main or footer goes on its container `<div>`. This width is the one arbitrary value allowed. Put `text-body` on the element that wraps the whole page.
- **Fonts:** headings (`<h1>` to `<h6>`) get Big Shoulders, the headline font, on their own. `font-sans` is Newsreader, the body font: in this project it is a serif, and it is the only serif. Do not write `font-serif`. `font-mono` is Chivo Mono, for labels and figures. The theme declares the weights 400, 500, 700 and 800: use only `font-normal`, `font-medium`, `font-bold` and `font-extrabold`.
- **Headlines:** every piece of text in the headline font is `uppercase`: headlines, the masthead name, a pull quote set in it. Never sentence case. A headline is at most two lines at 1280px (three are fine between `md` and `lg` in a row of three), with no word over ten letters so nothing overflows at 375px. Do not rely on word-breaking utilities.
- **Headline sizes:** the page's one main headline is the largest text on the screen, and nothing else comes near it. It is `text-h1` below `md` and `text-display` from `md` up; this brief names which headline that is. Other story headlines are `text-h2`. A short headline in a dense list (a side column, "Most read", the "Live" strip, a related story) is a **small headline**: `font-heading text-body font-bold uppercase`. A headline that is a link holds an `<a>` in `text-foreground hover:underline decoration-2 underline-offset-4`.
- **Masthead:** the paper's name is a link to `/` in `font-heading uppercase text-foreground hover:underline decoration-2 underline-offset-4`, in the header and the footer, never a heading element. It is `text-h1` from `md` up on the front page, and `text-h2` on every other page and below `md` everywhere, so that it never competes with the page's headline.
- **Header:** a `<header>` whose container holds three rows. The top row is `flex items-center justify-between gap-4 py-2`: the date as a label in `text-muted-foreground` at the left (`WEDNESDAY 11 NOVEMBER 2026`, a `<time>` with `dateTime="2026-11-11"`), and at the right the "Subscribe" link to `#` styled as a default button. The second row is `flex items-center justify-between gap-4 border-t-2 py-4`, holding the masthead name; on the front page from `md` up it is `md:justify-center` instead, with the name alone in it. The third row is a `<nav>` with the `aria-label` "Sections" and `border-y py-2`, holding a `<ul>` with `flex flex-wrap gap-x-6 gap-y-2` of the section links as labels. The section links, in this order, each to `#`: News, Politics, City, Sport, Results, Business, Culture, Opinion. Each brief names the current one. The link for the current page is `text-foreground` with `aria-current="page"` and `underline decoration-2 underline-offset-4`; the others are `text-foreground hover:underline`.
- **Menus on small screens:** below `md` the third row is hidden, and a "Menu" button (`variant="outline"`) with `aria-expanded` and `aria-controls` sits at the right end of the second row. It shows the section links inside the `<header>`, under the second row: a second `<nav>` with the same `aria-label` "Sections", shown only below `md`, which carries the `id` that `aria-controls` names and has the `hidden` attribute while closed. It has a 1px bottom border and `py-2`; each link is a block with `py-2`. It closes on Escape and gives focus back to the button. Do not use a `Sheet` or a `Dialog`.
- **Footer:** a `<footer>` whose container is `flex flex-col gap-4 border-t-2 py-6`: the masthead name, a link to `/` in `font-heading text-h2 uppercase` with `self-start` so it is only as wide as its text, then a `<nav>` with the `aria-label` "Footer" holding a `<ul>` with `flex flex-wrap gap-x-6 gap-y-2` of the section links as label links, none of them current: no `aria-current` and no underline at rest. Then a label line in `text-muted-foreground` with the copyright year and the city.
- **Vertical rhythm:** written as gaps on stacks (`flex flex-col gap-*`), never as `space-y-*`, and never as a margin on an element inside a stack unless this brief names one. The container `<div>` inside `<main>` is a stack, `flex flex-col gap-12 pt-6 pb-12`: its items are the page's blocks (the page head, the lead row, each section). Inside a block: `gap-4` between a section head and what follows, between paragraphs and between stories in a list; `gap-2` inside a story unit and between a label and its value; `gap-1` between a line and a label line directly under it that belongs to it (a headline and its time, two lines of one byline) where this brief says so.
- **Grid:** a row of columns is `grid grid-cols-1` with `lg:grid-cols-12`, and a column spans what this brief gives it (`lg:col-span-8`, `lg:col-span-4`). Below `lg` the columns stack in source order.
- **Rules between columns:** columns of stories are divided by a 1px rule, not by space. The grid is `gap-4 lg:gap-0`: a row gap while the columns are stacked, and no column gap. The column after a rule has `lg:border-l lg:pl-6`, and the column before it `lg:pr-6`, so there is 24px of space on each side of the rule. Below `lg`, where the columns stack, the rule turns into a 1px top border on each column after the first, with `pt-4`, and the grid's `gap-4` above it; from `lg` up that is `lg:border-t-0 lg:pt-0`. A stacked column that opens with a section head takes no top border and no `pt-4`: the head's 2px rule divides it. Use the same construction for three stories in a row at `md` (`md:grid-cols-3`): each story after the first has `border-t pt-4 md:border-t-0 md:pt-0 md:border-l md:pl-6`, each story before the last `md:pr-6`, and the grid has `gap-4 md:gap-0`.
- **Rules between stories:** a list of stories is a `<ul>` (or `<ol>`) with `flex flex-col gap-4`, no markers and no padding. Each item after the first has a 1px top border and `pt-4`. Every line is `border-border` (nearly black on purpose), which the base styles set, so `border` alone draws it. Never use a pale line or `border-muted`.
- **Section head:** a section opens with a row `flex items-baseline justify-between gap-4 border-t-2 pt-2`: the section's name as an `<h2>` label in `font-mono text-label uppercase text-foreground` (it needs `font-mono`, since a heading gets the headline font on its own), and at the right end, where this brief gives one, a label link such as "All politics".
- **Story unit:** a story in a list is an `<article>`, a stack with `gap-2`: an optional kicker, the headline, an optional standfirst in `text-body`, and the label line. A story with a picture puts the figure first, except a page's lead story, whose headline comes before its picture (the brief says so). A story's headline is an `<h3>` inside a section and an `<h2>` where it stands under the `<h1>` with no section head between.
- **Labels:** section names, kickers, bylines, times, captions, table headers, nav links, and button and badge text are labels: `font-mono text-label uppercase`, a short fragment and never a sentence; only a caption may be a short sentence. A label line joins its parts with ` · ` (`BY HANA LINDQVIST · 09:40`). Names in a byline are uppercased like the rest of it, as `DESIGN.md` asks.
- **Text color:** headlines, body text, kickers and section names are `text-foreground`. Bylines, times, captions and helper text are `text-muted-foreground`. Paragraphs are never muted.
- **Red:** `text-primary` and `bg-primary` mean live, new or the main action, and nothing else: the "Live" marker, the "New" label (a `<span>` label `NEW` in `text-primary`, with no dot or badge, beside the time where the brief puts it), links in running text and the default button. Never a headline, a rule or a fill for decoration. The one other red is the focus outline from the rules: `outline-ring` is red in this theme, and stays so. Red text is fine on `bg-background` and `bg-card` in both modes.
- **Live marker:** `inline-flex items-center gap-2 font-mono text-label uppercase text-primary`, holding a dot (`size-2 shrink-0 rounded-full bg-primary`, `aria-hidden="true"`) and the word "Live". The word is always there: the dot never stands alone. The dot does not pulse or blink.
- **Yellow:** `bg-tertiary` is a fill only, always with `text-tertiary-foreground` on it, which is black in both modes. It marks one phrase (a `<mark>` with `bg-tertiary px-0.5 text-tertiary-foreground`) or the "Exclusive" badge. Never red text on yellow, and never yellow as text.
- **Badges:** the shadcn `Badge` with `className="h-auto rounded-none border-transparent bg-tertiary px-2 py-1 font-mono text-label uppercase text-tertiary-foreground"`, for "Exclusive" only. At most one on a page.
- **Dates:** "today" is always the sample date, Wednesday 11 November 2026, never the clock. Nothing on these pages reads the time. Dates and times take three forms: a time today is `09:40`; another day is `TUE 10 NOV`, with no time; the long form, in the header and a page head, is `WEDNESDAY 11 NOVEMBER 2026`. Every date and time in a label, in a byline or alone, is a `<time>` that wraps only the date or time (`UPDATED <time>09:40</time>`), with `dateTime="2026-11-11T09:40"` for a time and `dateTime="2026-11-10"` for a day or a long date.
- **Numbers:** every `<time>`, the long date included, and every score, position and count outside a sentence is `font-mono tabular-nums`, labels or not. In a table, numbers are right-aligned, and so is their column header. A **big figure** (a score in a score box, a number in a fact box, a rank in a numbered list) is `font-mono text-h2 tabular-nums`: this is the one use of a mono font at a heading size, because `DESIGN.md` sets scores in Chivo Mono with tabular figures.
- **Measure:** a paragraph of body text is at most `max-w-xl` wide (576px, about 65 to 70 characters a line in Newsreader at 18px), whatever column it sits in. A standfirst in `font-sans text-h2 font-normal` is capped at `max-w-3xl` instead.
- **Emphasis:** `<em>` is a real italic. Newsreader italic is also used for a pull quote. No bold inside paragraphs.
- **Links:** a link in running text is `text-primary underline underline-offset-4 hover:text-foreground` and inherits its size. A label link (in the nav, the footer, a section head) is `text-foreground hover:underline underline-offset-4`. Links get `transition-colors duration-150`. The masthead name points to `/`; every other link, the current page's own nav link and every story link included, points to `#`, unless this brief gives another `href`.
- **Buttons:** the shadcn `Button` with `className="h-auto px-4 py-2 font-mono text-label uppercase"`. It keeps its stock corners (`rounded-lg`, 2px here). The default variant (red) is the one main action, and "Subscribe" in the header is it on every page: any other button is `variant="secondary"` (black, cream in dark mode). Its stock hover cannot be told from its resting color: in `components/ui/button.tsx`, replace the `secondary` variant's `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80`, once, and add no hover class on the page. 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 that its own click disables, while focus stays on it, gets `focusableWhenDisabled`; on these pages that is only the live blog's "Show new update". Base UI then sets `aria-disabled="true"` and `data-disabled` instead of the `disabled` attribute, so the stock `disabled:` classes do nothing: on a page that uses the prop, and only there, add `data-disabled:pointer-events-none data-disabled:opacity-50` beside them in `components/ui/button.tsx`. A button whose click moves focus elsewhere or removes it does not get the prop. If the project has no `Button` yet, add it with `npx shadcn@latest add button`.
- **Button groups:** a set of buttons where one is chosen (an order, a filter) is a `role="group"` with an `aria-label` and `flex flex-wrap gap-1`. The chosen button has `aria-pressed="true"` and `variant="secondary"`; the rest have `aria-pressed="false"` and `variant="outline"`. Every button in it has the Buttons classes. Pressing the chosen button again changes nothing, and a status element keeps its text.
- **Announcements:** a message that tells a screen reader what changed goes into an element with `role="status"` that is on the page from the start, empty: one that arrives together with its text is often not announced. It is never hidden with `hidden` or `empty:hidden`; where its text is for screen readers only, it is `sr-only`. Each status element here is a `<p>` whose only class is `sr-only`: it adds no gap and has no margin.
- **Pictures:** there are no image files. A picture is a `<figure>`, a stack with `gap-2`: a plain `<div>` with an aspect ratio (`aspect-3/2` unless this brief says otherwise), `w-full bg-muted` (the flat muted block `DESIGN.md` names, used only for this fill), `role="img"` and an `aria-label` that describes the scene; then a `<figcaption>` label in `text-muted-foreground` that says what it shows and ends with the credit after ` · ` (`THE KESSEL STREET BRIDGE ON TUESDAY · PHOTOGRAPH: ODILE MARSH`). Nothing is written on the fill. Pictures have square corners and no border.
- **Boxes:** a box (a fact box, a score box, the "Live" strip) is a plain element with `border bg-card p-6`, `p-4` below `md`, and square corners. Do not use the shadcn `Card`. Never put a box inside a box.
- **Tables:** the shadcn `Table` with `text-body`. Header cells are labels in `text-muted-foreground`; they keep their stock `h-10` and padding, and so do body cells. Rows keep their stock borders and hover color. A table that is wider than a phone scrolls sideways in its own box, reachable with the keyboard: `tabIndex={0}`, `role="region"` and an `aria-label`. Edit `components/ui/table.tsx`: put these on its scroll box, with the focus outline from the rules, and add a required `label` prop for the name. The grid column or flex item that holds the scroll box gets `min-w-0`, or `min-w-lg` widens the column and the page instead of scrolling. Name the table with a visible `<h2>` or `<h3>` above it, not a `<caption>`, which would scroll with the table. Give the `Table` `min-w-lg` where this brief says it scrolls on a phone.
- **Icons:** from `lucide-react`, with `size-4`, `strokeWidth={1.5}` and `aria-hidden="true"`, only where this brief names one. Never emoji.
- **Corners:** square. `rounded-none` for boxes, pictures and badges (boxes and pictures need no class at all). Buttons and the stock `Input` keep `rounded-lg`, which is 2px. `rounded-full` only for the "Live" dot.
- **Surfaces:** flat: ink on paper. No shadows, no blur and no gradients in the code you write. The page is `bg-background`; a box is `bg-card`. A stock component that floats over the page (a menu, a dialog) keeps its border and the one short shadow it ships with.
- **Motion:** in the code you write, the only transition is color. Nothing moves on scroll, and nothing animates in when it is added to the page. The stock `Button` keeps its `transition-all` and its 1px press nudge, as the rules say.

## The page

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

Sample content, if the user gave none: **The Port Avery Ledger**, the daily paper of Port Avery, an invented harbour city. The date is Wednesday 11 November 2026. The lead story: the Kessel Street Bridge, the city's main river crossing, closed on Tuesday 10 November after inspectors found cracked bearings and stays shut for six months; the council holds an emergency vote on the £48m repair tonight. Every story, person, team and place is invented. Every story ran today and its label line shows a time, except that the swimmer's record in Sport ran yesterday and shows `TUE 10 NOV`. Write the headlines, standfirsts and bylines yourself, in plain news language: short headlines in uppercase, standfirsts that state the news in one sentence.

Build these parts in order. The page has one `<h1>`, the lead headline. The side column, the "Live" strip and each section have an `<h2>`; the stories under them have `<h3>` headlines.

### 1. Header

The header from the layout notes, with the front page's masthead: the name `text-h1` from `md` up, centered in its row. No section link is the current page on the front page.

### 2. Lead row

The first item of `<main>`: a row of two columns with a rule between them, as the layout notes describe.

**Lead story,** `lg:col-span-8 lg:pr-6`: an `<article>`, a stack with `gap-4`. Here, instead of the Story unit note's order, the picture comes last, so that the headline is the first thing seen; the order is the same at every width:

- A row with `flex items-center gap-2`: the kicker label `TRANSPORT` and the "Exclusive" badge (add it with `npx shadcn@latest add badge`). This is the page's one badge.
- The `<h1>`, a headline that is a link: `BRIDGE SHUT FOR SIX MONTHS`. It is `text-h1` below `md` and `text-display` from `md` up. Keep it under 30 characters, so it sets in two lines at most in eight columns.
- A standfirst in `font-sans text-h2 font-normal`: one sentence, sentence case, not uppercased.
- The label line in `text-muted-foreground`: `BY HANA LINDQVIST · 06:15`, the time a `<time>`.
- A picture, `aspect-3/2`, of the closed bridge, with its caption.

**Side column,** `lg:col-span-4 lg:border-l lg:pl-6`. Its `sr-only` heading is not a section head, so below `lg` it takes the stacked-column rule from the notes: `border-t pt-4 lg:border-t-0 lg:pt-0`. It opens with an `<h2>` "More top stories" that is `sr-only`. Then a list of five stories, divided by rules. Each is a story unit: a kicker, an `<h3>` small headline that is a link, one sentence in `text-body`, and the label line with the byline and the time (`BY PRIYA VENKATARAMAN · 08:10`). The first of the five also has a picture, `aspect-video`, before its kicker.

### 3. Live strip

The next item of `<main>`, across the full width: a box, `flex flex-col gap-2 md:flex-row md:flex-wrap md:items-baseline md:gap-x-6 md:gap-y-2`. It holds:

- An `<h2>` that is the live marker: it carries the Live marker note's classes and holds the dot and the word "Live".
- An `<h3>` small headline that is a link: `BRIDGE VOTE: COUNCIL MEETS AT 18:00`.
- The latest update, the live blog's first, at `17:30`: one `<p>` with `flex items-baseline gap-2 md:flex-1`, holding the time as a `<time>` label in `text-muted-foreground` and a `<span>` with one sentence in `text-body`. The `<p>` is one item of the box, so the time and the sentence stay together.

At `md` and up these sit in one line and wrap where they must; below `md` they stack.

### 4. Sections

Three sections, each an item of `<main>`: Politics, Sport and City. Each opens with the section head, with a label link at the right end ("All politics", "All sport", "All city"). Under it, with the section's `gap-4`, three stories in a row of three with rules, as the layout notes describe for `md`.

Each story is a story unit: a picture `aspect-3/2` with its caption, a kicker, an `<h3>` in `text-h2` that is a link (at most 40 characters, so it fits two lines in a third of the row at 1280px), one sentence in `text-body`, and the label line with the byline and the time.

- Politics: the opposition's budget plan, a by-election date, a row over council pay.
- Sport: Port Avery Harriers win the derby against Saltgate United, a swimmer's record, a new manager at Westmere Athletic.
- City: a market's last day, a ferry timetable change, the winter night shelter opening.

In the Sport section, the first story's label line ends with a label link "Results".

### 5. Footer

The footer from the layout notes. The copyright line: `© 2026 THE PORT AVERY LEDGER · PORT AVERY`.

### Behavior

- The "Menu" button works as the layout notes say: it opens and closes the section list, sets `aria-expanded`, closes on Escape and gives focus back to itself.
- Every link but the masthead name points to `#`, as the Links note says.

## Rules

- **Colors:** only the theme's colors, through the semantic classes (`bg-background`, `text-foreground`, `bg-card`, `bg-primary`, `text-primary-foreground`, `text-muted-foreground`, `border-border` and so on). No hex, rgb or named colors in components, and no Tailwind palette classes such as `bg-blue-500` or `text-gray-600`.
- **Spacing:** padding, margin and gap use only the scale's steps (`1`, `2`, `4`, `6`, `12`, `24`) plus 0, 1px and 2px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-3` or `gap-5`. `auto` margins are fine. Widths and heights such as `w-64` or `size-4` are sizes, not spacing. This applies to the code you write: a stock component keeps the spacing inside it, unless this brief changes it.
- **Type:** every piece of text you write gets its size from one of `text-display`, `text-h1`, `text-h2`, `text-body`, `text-label`, set on the element or on a parent it inherits from. Put `text-body` on the outermost element of the page you write. No other sizes. A stock shadcn component keeps the size it ships with, unless `DESIGN.md` or this brief gives that component its own style. A type class does not replace a weight class: where a stock component ships `font-medium` and the text should be regular, add `font-normal`.
- **Fonts:** only the theme's fonts, through `font-sans`, `font-heading` and `font-mono`. Do not add a font.
- **Corners:** only the `rounded-*` classes, which the theme already sets. No arbitrary radius, and `rounded-full` only where `DESIGN.md` or this brief names a full round, such as an avatar or a status dot. The theme allows a full round on a box up to 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 make one headline much larger than everything else on the screen.
- Do divide with rules and keep the columns tight.
- Do keep red for what is live, new or the main action. The focus outline is red as well: it marks where the reader is, and it is the one other use.
- Don't soften it: no rounded cards, no shadows, no pale grey borders.
- Don't set body copy in the headline font, or headlines in sentence case.
- 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 lead story's headline is the page's only `text-display` text and its only `<h1>`, and it sets in at most two lines at 1280px.
- [ ] From `lg` up the lead story takes eight of the twelve columns and the other stories stack in the other four, with a 1px rule between the two and between the stories in the column.
- [ ] The "Live" strip shows a red dot and the word "Live", and the dot is never shown without the word.
- [ ] Each section opens with a 2px rule above its name and holds three stories divided by 1px rules, which stack with top rules below `md`.
- [ ] At 375px the lead headline is `text-h1` and comes before the lead picture, as it does at 1280px.
- [ ] 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.
