# Build a game home page with the Ember theme

You are building one page in an existing project: the home page of a team arena game, with an opening that fills the first screen with the title and one action, the four classes, the current season, the latest news and the system requirements in a table. 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 Ember variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/ember.json` and save https://strictthemes.com/themes/ember/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/ember/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:** Oxanium for headings, Sofia Sans for body text, Martian Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 72px/800, `text-h1` 44px/700, `text-h2` 24px/700, `text-body` 16px/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`), 12px (`3`), 24px (`6`), 48px (`12`), 80px (`20`).
- **Corners:** `rounded-sm` 1px, `rounded-md` 3px, `rounded-xl` 6px (the `lg` corner in `DESIGN.md`). `rounded-lg` is 3px as well: it is shadcn's name for the base corner, the one its stock buttons and inputs use.
- **Colors (light mode):** primary #9D0208, on-primary #FFFFFF, secondary #03071E, on-secondary #FFF5E9, tertiary #FFBA08, on-tertiary #03071E, neutral #6A4B52, background #FFF5E9, surface #FFFFFF, foreground #03071E, border #E5CBAE, error #D00000, heat-low #DC2F02, heat-mid #F48C06, heat-high #FFBA08.

### How Ember pages are laid out

- **Page:** one `<div>` wraps the whole page: `min-h-screen bg-background text-body text-foreground`. Inside it sit the `<header>`, the `<main>` holding everything between the two, and the `<footer>`. Ember lives in dark mode: build and look at every part in dark mode first, then in light mode, and keep both right.
- **Container:** `mx-auto max-w-[1280px] px-3 md:px-6`. This width is the one arbitrary value allowed. The `<main>` element is itself a container and also carries `pt-12`. The header and the footer run the full width of the window, with their lines; the container is a `<div>` inside each. Every other line stays inside the container.
- **Grid:** where this brief gives columns, the element is `grid grid-cols-1 md:grid-cols-12 md:gap-x-6` (24px gutters) and each item says its span, such as `md:col-span-7`. The grid sets columns only, never vertical space. Below `md` items stack in source order.
- **Vertical rhythm:** written as a top margin on the later element, never as `space-y-*` and never as a gap on a column of mixed content. A heading under a label has `mt-2`. A paragraph under a heading, and a paragraph under a paragraph, has `mt-3`. A block under text (a row of buttons, a list, a table, a meter, a row of cards) has `mt-6`. A new group inside a section has `mt-12`. A `<section>` inside `<main>` has `mt-20` (80px), except the first, which has none: the `pt-12` on `<main>` is the space above it. Where two grid items share a row from `md` up and stack below it, the second gets `mt-6 md:mt-0` when it follows on from the first (a picture beside its text) and `mt-12 md:mt-0` when it is a separate group (a card, a column with its own heading); each brief writes which. Gaps are only for items side by side in a row (`flex`, `flex-wrap`, a grid of cards), where this brief or these notes name one. Inside a list of rows, a row has its own top border and padding and no margin. Where a part of a brief gives a margin that differs from this note, the brief's part wins.
- **Headings:** headings are Oxanium, which `<h1>` to `<h6>` get on their own; no full stop. Every heading set at `text-h1` or `text-display`, at any width, is `uppercase`: the page's big title and every section `<h2>`. Write their text in sentence case in the code; the class sets the capitals. A heading in `text-h2` alone (in a card, a row, a tile) stays in sentence case. The page's big title is `text-h1 md:text-display`, the only `text-display` text on the page unless a brief gives a big figure that size. A section's `<h2>` is `text-h2 md:text-h1 uppercase`. Keep every word of an uppercase heading to nine letters or fewer, so nothing runs out of the screen at 375px. Titles wrap; never truncate one.
- **Labels:** versions, dates, times, ranks, regions, prices, eyebrows, captions, nav links, table headers and button and badge text are labels: `font-mono text-label uppercase`. Write their text in sentence case ("Patch 4.2 · Tue 10 Nov"); the `uppercase` class sets the capitals. `text-label` already carries the tracking; add no `tracking-*` class. A label is a short fragment; anything that is a sentence is body text. Parts of a label line are joined with ` · `. A label on its own line is a block element such as a `<p>`. Martian Mono is wide: a label of more than about 30 characters will not fit a phone's line, so keep them short.
- **Figures:** every number that stands on its own (a rank, a rating, a score, a price, a count, a version, a date or a time in a label, a table cell or a big figure) gets `font-mono tabular-nums`. A **big figure** is a number set large: `font-mono text-h1 tabular-nums`, or `font-mono text-h2 tabular-nums` where a brief says so; it is never uppercased and has no label classes. Thousands take a comma (`2,418`), a change takes a sign (`+21`, and `−17` with the minus sign `−`, not a hyphen), a rate takes one decimal (`55.4%`). A number inside a sentence is plain text.
- **Dates:** "today" is always the sample date, Thursday 12 November 2026, never the clock; nothing on these pages reads the time. In a label a date is short, `Tue 10 Nov`, with the year only where a brief writes one (`Tue 10 Nov 2026`); in a sentence it is written in full, `Tuesday 10 November`. Times are 24-hour (`09:40`). A date or a time that stands on its own is a `<time>` with a `dateTime` (`2026-11-10`, or `2026-11-12T09:40` with a time). A date range in a label (`6 Oct – 12 Jan 2027`) is two `<time>` elements joined by an en dash in plain text.
- **Text color:** headings, body text, nav links, table body cells, big figures and labels that name a thing (a player, an item) are `text-foreground`. Other labels, captions and helper text are `text-muted-foreground`, and muted text sits only on `bg-background` or `bg-card`. Running text (the sentences under a heading, a summary, a description) is never muted. A button's text color comes from its variant. `text-primary` is never used: red is not a text color in this theme.
- **Red:** `bg-primary` is ember red, brighter in dark mode, for the screen's one main action: a default `Button` or a link styled as one. Each brief names it or says the page has none, and there is never a second. It is never a text color, a border, a band or the fill of anything else. The focus outline from the rules (`outline-ring`) is red too, and stays.
- **Amber:** `bg-tertiary` is for one badge on a page, always with `text-tertiary-foreground` (blue-black in both modes). It is never a text color, a border or a fill of anything else.
- **Heat:** `bg-heat-low` (deep red), `bg-heat-mid` (orange) and `bg-heat-high` (amber) are the theme's own three-step scale, the same in both modes. They are fills only: for meters, rank marks, the viewer's row in a table, chart bars and the shapes in a drawn picture. Never a text color, a border, a button, or a fill with text on it. `bg-heat-low` sits close to the page in dark mode and `bg-heat-high` close to it in light mode, so a heat fill never carries meaning alone: the value or the step is always written next to it in words or figures.
- **Meters:** a meter is a `<div>` with `role="meter"`, `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, an `aria-valuetext` in words and an `aria-labelledby` that points to its label. Its track is `h-3 w-full border border-border bg-muted`, with square corners, and holds one fill, `h-full` with one heat class and its width as an inline `style` (`style={{ width: "61%" }}`). A meter always has its value written beside or above it as a label or a big figure. Each brief says which value takes which of the three steps.
- **Rank marks:** a small square, `size-3 shrink-0 rounded-sm` with one heat class and `aria-hidden="true"`, always followed by the step's name in words in a row `flex items-center gap-2`. The tiers of Ranked Clash are Inferno (rating 2,800 and up, `bg-heat-high`), Blaze (2,600 to 2,799, `bg-heat-mid`) and Cinder (under 2,600, `bg-heat-low`).
- **Measure:** a paragraph of running text is at most `max-w-xl` wide.
- **Links:** a text link is `text-foreground underline decoration-1 underline-offset-4 hover:decoration-2`: it is the text color, underlined, and its line thickens under the pointer. A link that holds a title (inside a heading, and the game's name in the header) is `text-foreground decoration-1 underline-offset-4 hover:underline`. A nav link is a label in `text-foreground` with `underline-offset-4 hover:underline`; the link for the current page has `aria-current="page"` and `underline decoration-2`. Links get `transition-colors duration-150`.
- **Link destinations:** the five pages are `/` (Game), `/patch`, `/leaderboard`, `/store` and `/player`; where the project has another router layout, use its paths. A link whose destination this brief does not name points to `#`.
- **Header:** a `<header>` with `border-b border-border`. Its container `<div>` is also the row: `flex items-center justify-between gap-6 py-3`. On the left the game's name, "Ashfall", a link to `/` in `font-heading text-h2 uppercase`, never marked as the current page. On the right, a `<nav>` with the `aria-label` "Main" and `hidden md:block`, holding a `<ul>` with `flex gap-6` of five label links, in this order: Game (`/`), Patch notes (`/patch`), Leaderboard (`/leaderboard`), Store (`/store`), Profile (`/player`). The brief names the current one.
- **Menu on small screens:** below `md` the `<nav>` is hidden and a "Menu" button (`variant="outline"`, with the button classes below) stands at the right end of the row, with `aria-expanded` and `aria-controls`. It shows the links as a list inside the `<header>`, under its row: a second `<nav>` with the same `aria-label` and `md:hidden`, which carries the `id` that `aria-controls` names and has the `hidden` attribute while closed. It has a 1px top border across the full width (`border-t border-border`, on the `<nav>` itself, with the container `<div>` inside it) and, inside the container, `py-2`; each link is a block with `py-2`. It closes on Escape, pressed while focus is on the Menu button or in the menu (one `onKeyDown` handler on the `<header>`, acting only while the menu is open), and focus then goes to the Menu button; it also closes when a link in it is chosen. Do not use a `Sheet` or a `Dialog`.
- **Footer:** a `<footer>` with `mt-20`, `border-t border-border` and `py-12`. Inside it, the container `<div>` holds a grid (`md:grid-cols-12`): columns 1–6 hold the game's name as plain text in `font-heading text-h2 uppercase` and, with `mt-2`, a `<p>` in `text-body text-muted-foreground`: "A team arena game by Lowlight Games."; columns 7–12 hold a `<ul>` (not a `<nav>`) of the same five links as the header, as text links in `text-body`, with `flex flex-wrap gap-x-6 gap-y-2`, and `mt-6 md:mt-0`. Under the grid, with `mt-12`, a label line in `text-muted-foreground`: `© 2026 Lowlight Games`.
- **Buttons:** the shadcn `Button` with `className="h-auto px-6 py-2 font-mono text-label uppercase"`. It keeps its stock `rounded-lg` corner (3px). The default variant (red) is the screen's one main action, as the Red note says. Every other action is `variant="secondary"` (blue-black, warm white in dark mode), or `variant="outline"` for quiet ones. The `secondary` variant's stock hover is the same color as its fill: in `components/ui/button.tsx`, replace its `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80`, once, on every page, whether or not the page has a secondary button, and add no hover class where you use it. A button that sends a form needs `type="submit"`: this `Button` is `type="button"` unless told otherwise. A link that looks like a button (to another page, or to a part of this one) is an `<a>` with the same classes: write `cn(buttonVariants({ variant }), "…")`, because `buttonVariants` alone does not merge classes. Such a link is `inline-flex`, so a top margin on it works: it needs no wrapper. 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 (a region, a class) 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 others have `aria-pressed="false"` and `variant="outline"`. Every button in it has the Buttons classes. Pressing the chosen button again changes nothing. Not `Tabs`: there are no tab panels.
- **Badges:** the shadcn `Badge` (add it with `npx shadcn@latest add badge`) with `variant="secondary"` and `className="h-auto rounded-sm border-transparent bg-tertiary px-2 py-1 font-mono text-label uppercase text-tertiary-foreground"`: the variant is named only because its classes hold no red, and these classes replace its fill and text color. One or two words, and at most one badge on a page.
- **Status dots:** the only full round in this theme: `size-2 shrink-0 rounded-full bg-foreground`, `aria-hidden="true"`, always beside a word that says the state. It does not pulse or blink. Only where a brief names one.
- **Announcements:** a message that tells a screen reader what changed goes into an element that is on the page from the start, empty or with its first text: one that arrives together with its text is often not announced. It is never hidden with `hidden` or `empty:hidden`. Each brief says whether it is a visible line with `aria-live="polite"` or a `<p>` with `role="status"` and `sr-only`.
- **Cards:** a card is a plain element with `rounded-xl border border-border bg-card p-6` (6px corners, 24px padding, a 1px border). Inside it, a label comes first where it has one, and the rest follows the rhythm above. Never put a card inside a card, and never a box with a border on all four sides inside a card: divide a card's parts with `border-t border-border` and `pt-3`. Do not use the shadcn `Card`.
- **Pictures:** there are no image files. A picture is a plain element in a fixed aspect ratio, `bg-muted`, with `role="img"` and an `aria-label` that says what it shows, and the shapes in it are plain `<div>`s with heat fills or `bg-foreground`, square corners, no border and no text, placed with `flex` or with `absolute` and fraction classes (`left-1/4`, `bottom-1/3`, `w-1/12`, `h-1/2`) as each brief gives them. A picture that stands on the page has `relative overflow-hidden rounded-xl border border-border`. A picture at the top of a tile has no corners of its own and only a bottom border (`border-b border-border`): the tile is `overflow-hidden` and clips it, so it never reads as a card inside a card. Nothing glows: no gradient, no blur, no shadow, no opacity trick.
- **Lists of rows:** a list of news or entries is an `<ol>` or `<ul>` with no markers and no padding. Each row has `border-t border-border py-6`, and the list has `border-b border-border` so the last row closes.
- **Tables:** the shadcn `Table` (add it with `npx shadcn@latest add table`) with `text-body`. Edit `components/ui/table.tsx`: its scroll box, the `<div>` around the `<table>`, gets `tabIndex={0}`, `role="region"`, an `aria-label` from a new required `label` prop, and the focus outline. Header cells are labels in `text-muted-foreground` with `px-3`; they keep the stock `h-10`. Body cells have `px-3 py-2`. Add `first:pl-0 last:pr-0` to every cell, unless a brief says otherwise. Numbers are `font-mono tabular-nums` and right-aligned (`text-right`), and so is their column header. Rows are divided by 1px borders, with no zebra stripes. The stock `TableBody` drops its last row's border: take `[&_tr:last-child]:border-0` out of it. The rows do nothing, so take `hover:bg-muted/50` out of the stock `TableRow`. Every cell keeps the stock `whitespace-nowrap`. The table's name is the heading above it and the `label`; it has no `<caption>`. The grid column that holds the table gets `min-w-0`, and a table that a brief says scrolls on a phone gets the `min-w-*` class the brief gives, so it scrolls in its box and the page does not.
- **Icons:** from `lucide-react`, with `size-4`, `strokeWidth={1.5}` and `aria-hidden="true"`, and only where a brief names one. Never emoji.
- **Corners:** hard. `rounded-sm` (1px) for badges and rank marks, `rounded-lg` (3px, the stock corner) for buttons, `rounded-md` (3px as well) for a player's mark, `rounded-xl` (6px) for cards, tiles and pictures. Meter tracks and the shapes in a picture are square. `rounded-full` is for the status dot only, 8px across. No pills, no circles around a person: a player's mark is a square.
- **Surfaces:** flat and dark: no shadow, glow, blur or gradient in the code you write. A card is `bg-card`, one step lighter than the page. A stock component that floats keeps its one short shadow.
- **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: **Ashfall**, an invented free-to-play team arena game for PC by an invented studio, **Lowlight Games**. Two teams of five fight over a burning salt flat in rounds; the first team to win four rounds wins the match. Four classes: **Bulwark** (front line, difficulty low), **Cinderblade** (close damage, difficulty high), **Flarecaster** (ranged damage, difficulty medium) and **Mender** (support, difficulty medium). The modes are Ranked Clash, Quick Clash and, since patch 4.2, Duel Pits, a 2v2 mode. **Season 4: Dry Lightning** runs from Tuesday 6 October 2026 to Tuesday 12 January 2027, 98 days; by today, Thursday 12 November 2026, 37 of them have gone and 61 are left and it is week 6 of 14. It brought the map Glasswater Yard and a 60-tier season pass. Patch 4.2 came out on Tuesday 10 November 2026.

Build these parts in order.

### 1. Header

The header from the layout notes. "Game" is the current page.

### 2. Opening

The first `<section>`, which fills the first screen: a grid (`md:grid-cols-12`) with `content-center md:min-h-svh md:items-center`, so that from `md` up its content sits in the middle of a box at least as tall as the window. Below `md` the section is only as tall as its content, and the title starts right under the header.

- Columns 1–7: a label in `text-muted-foreground`, `Lowlight Games · 5v5 arena`; with `mt-2`, the `<h1>` "Ashfall" in `text-h1 md:text-display uppercase`; with `mt-6`, one sentence in `font-sans text-h2 font-normal`, within `max-w-xl`: "Two teams of five, one burning salt flat, first to four rounds." With `mt-6`, a row `flex flex-wrap items-center gap-6` holding the default-variant link "Play free" (to `#`), the page's one red action, and a label in `text-muted-foreground`: `PC · Free to play · Season 4`.
- Columns 8–12, with `mt-12 md:mt-0`: the key art, a picture that stands on the page (`relative aspect-square w-full overflow-hidden rounded-xl border border-border bg-muted`) with the `aria-label` "A watchtower on a salt flat at dusk, a square sun above it and a line of fire on the horizon". Its shapes, each a `<div>` with `absolute`: the sun, `left-1/3 top-1/6 aspect-square w-1/3 bg-heat-high`; the ground, `inset-x-0 bottom-0 h-1/3 bg-heat-low`; the line of fire on the ground's top edge, `inset-x-0 bottom-1/3 h-2 bg-heat-mid`; the tower, standing on the ground, `bottom-1/3 left-1/6 h-1/2 w-1/12 bg-foreground`.

On a phone the text comes first and the key art under it.

### 3. Classes

The second `<section>`, with `mt-20`. An `<h2>` "Four classes" in `text-h2 md:text-h1 uppercase`, then with `mt-3` one sentence: every match lets a team take any mix of the four, and a player can switch between rounds.

With `mt-6`, a `<ul>` with `grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4`. Each `<li>` is a tile: `flex flex-col overflow-hidden rounded-xl border border-border bg-card`, holding an `<article>`:

- At the top, the class's emblem, a picture at the top of a tile (`aspect-video border-b border-border bg-muted flex items-center justify-center`) with the `aria-label` "Emblem of the" and the class's name. Inside it, one or two shapes, each a `<div>` with square corners:
  - Bulwark, a shield: `h-1/2 w-1/3 bg-heat-mid`.
  - Cinderblade, a blade: `h-2/3 w-1/12 bg-heat-high`.
  - Flarecaster, a flare: `aspect-square h-1/3 bg-heat-high`.
  - Mender, two lights: a row `flex h-1/3 gap-3` holding two `<div>`s, each `aspect-square h-full`, the first `bg-heat-mid`, the second `bg-heat-high`.
- Under it, the text part with `p-6`: a label in `text-muted-foreground`, the role (`Front line`, `Close damage`, `Ranged damage`, `Support`); with `mt-2`, the name as an `<h3>` in `text-h2`; with `mt-3`, two sentences on how the class plays; with `mt-3`, a label in `text-muted-foreground`: `Difficulty · Low` (`Low`, `High`, `Medium`, `Medium`, as the sample gives).

Below `md` the tiles stack; at `md` they sit two by two, from `lg` four in a row.

### 4. Current season

The third `<section>`, with `mt-20`, a grid (`md:grid-cols-12`):

- Columns 1–7: a label in `text-muted-foreground`, `Season 4 · 6 Oct – 12 Jan 2027`, the range as two `<time>` elements (`2026-10-06`, `2027-01-12`); with `mt-2`, the `<h2>` "Dry lightning" in `text-h2 md:text-h1 uppercase`; with `mt-3`, two paragraphs within `max-w-xl`, the second with `mt-3`: what the season brought (the map Glasswater Yard, a salt works where storms cut the light; a 60-tier season pass; ranks reset to Cinder at the start), and that patch 4.2 added Duel Pits. With `mt-6`, a secondary-variant link "See the ranking" to `/leaderboard`.
- Columns 9–12, with `mt-12 md:mt-0`: a card from the layout notes, `md:self-start`. A label `Season progress` with an `id` for the meter; with `mt-2`, a big figure `61` followed in the same `<p>` by a `<span>` label in `text-muted-foreground` with `ml-2`, `days left`; with `mt-6`, a meter from the layout notes: `aria-valuemin={0}`, `aria-valuemax={98}`, `aria-valuenow={37}`, `aria-valuetext="37 of 98 days gone"`, its fill `bg-heat-mid` at `37.8%`; with `mt-3`, a label in `text-muted-foreground`: `37 of 98 days gone · Week 6 of 14`.

### 5. Latest news

The fourth `<section>`, with `mt-20`. An `<h2>` "Latest news" in `text-h2 md:text-h1 uppercase`. With `mt-6`, a `<ul>` of four news items as a list of rows from the layout notes. Each row is an `<li>` holding an `<article>` that is a grid (`md:grid-cols-12`):

- Columns 1–2: the date, a `<p>` label in `text-muted-foreground` holding a `<time>`.
- Columns 3–12, with `mt-2 md:mt-0`: the title as an `<h3>` in `text-h2`, holding a title link from the layout notes; with `mt-3`, one sentence within `max-w-xl`.

The four items, newest first: `Tue 10 Nov`, "Patch 4.2: Duel Pits and a round of balance" (to `/patch`), which carries the page's one badge, "New": put the badge first in the text column, as a block on its own line (add `flex` to its classes; it keeps the stock `w-fit`), and give the `<h3>` `mt-2`; `Thu 5 Nov`, "The Ashfall Open: qualifiers start in December"; `Tue 20 Oct`, "Patch 4.1: matchmaking for groups of five"; `Tue 6 Oct`, "Season 4: Dry Lightning is live". The last three point to `#`.

### 6. System requirements

The fifth `<section>`, with `mt-20`. An `<h2>` "System specs" in `text-h2 md:text-h1 uppercase`, then with `mt-3` one sentence: Ashfall runs on PC and needs a connection for every match.

With `mt-6`, in columns 1–8 of a grid (`md:grid-cols-12`), with `min-w-0`, the `Table` from the layout notes with the `label` "System requirements" and `min-w-xl`. Columns: Part, Minimum, Recommended. The column headers are `<th scope="col">`. Each row's first cell is a `TableHead` with `scope="row"` and, in place of the header label classes, `py-2 font-sans text-body font-normal text-foreground`; like every cell it keeps `px-3` and `first:pl-0 last:pr-0`. Rows, written in words. Every cell here is a phrase, so every cell is body text in `font-sans`, and none takes the mono figure classes, not even "8 GB": System (64-bit Windows 10 · 64-bit Windows 11), Processor (4 cores at 3.0 GHz · 6 cores at 3.6 GHz), Memory (8 GB · 16 GB), Graphics (4 GB of video memory, DirectX 12 · 8 GB of video memory, DirectX 12), Storage (35 GB · 35 GB on an SSD), Network (Broadband · Broadband). These columns hold text, not numbers, so they stay left-aligned.

At 375px the table is wider than the screen and scrolls sideways in its own box; the page does not.

### 7. Footer

The footer from the layout notes.

### Behavior

- The page has no state but the small-screen menu's. "Play free" and the news links other than the patch point to `#`.
- The season meter and the days left are fixed sample figures: nothing reads the clock.

## 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`, `6`, `12`, `20`) 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 design the dark mode first: it is where this theme lives.
- Do keep red to one action per screen and amber to one badge.
- Do let numbers be large: a score or a rank can be the biggest thing on a page.
- Don't add glows, neon, gradients or blurred glass.
- Don't use a heat color for text or for a button.
- Don't introduce a color, size or radius that is not a token above.

## Check before you finish

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

- [ ] The opening is at least as tall as the window, and the title ASHFALL is the page's only `text-display` text and its `<h1>`.
- [ ] Play free is the page's only default (red) button or link, and no text on the page is set in `text-primary` or in a heat color.
- [ ] Each class tile has a drawn picture with `role=img` and an `aria-label`, and its difficulty is written in words.
- [ ] The season meter has `role=meter` with its values, and the days of the season are written beside it.
- [ ] The system requirements are a table with real header cells, in a box that scrolls sideways at 375px while 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.
