# Build a leaderboard with the Ember theme

You are building one page in an existing project: the season's ranking of a team arena game, with the viewer's place, a filter by region, a table of players with rank, tier, rating, wins and win rate in which the viewer's own row is marked with a heat bar, and a key to the tiers. 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**. This is the Ranked Clash ladder of **Season 4: Dry Lightning**, in week 6 of 14, updated at 09:00 today, Thursday 12 November 2026. The viewer is **Juno Vane**, who plays in Europe: rating 2,418, 212 wins in 383 matches, global rank 18, rank 6 in Europe, tier Cinder. The ladder holds these nineteen players, in order of rating (region, rating, wins, matches, win rate):

| # | Player | Region | Rating | Wins | Matches | Win rate |
|---|---|---|---|---|---|---|
| 1 | Hollow Finch | Asia-Pacific | 2,961 | 301 | 490 | 61.4% |
| 2 | Red Static | Europe | 2,904 | 288 | 484 | 59.5% |
| 3 | Quiet Arson | Americas | 2,877 | 276 | 477 | 57.9% |
| 4 | Tin Saint | Asia-Pacific | 2,842 | 254 | 432 | 58.8% |
| 5 | Moss Cannon | Europe | 2,815 | 263 | 462 | 56.9% |
| 6 | Velvet Mortar | Americas | 2,790 | 241 | 421 | 57.2% |
| 7 | Dune Kestrel | Asia-Pacific | 2,761 | 230 | 406 | 56.7% |
| 8 | Ash Lantern | Europe | 2,733 | 247 | 440 | 56.1% |
| 9 | Pale Rook | Americas | 2,702 | 219 | 389 | 56.3% |
| 10 | Glass Hornet | Asia-Pacific | 2,676 | 226 | 407 | 55.5% |
| 11 | Iron Wren | Americas | 2,640 | 208 | 375 | 55.5% |
| 12 | Sour Comet | Europe | 2,611 | 215 | 393 | 54.7% |
| 13 | Brass Heron | Asia-Pacific | 2,583 | 198 | 364 | 54.4% |
| 14 | Cold Furnace | Americas | 2,547 | 201 | 373 | 53.9% |
| 15 | Night Tinder | Europe | 2,512 | 194 | 359 | 54.0% |
| 16 | Copper Wasp | Asia-Pacific | 2,486 | 187 | 350 | 53.4% |
| 17 | Grey Volley | Americas | 2,455 | 190 | 359 | 52.9% |
| 18 | Juno Vane | Europe | 2,418 | 212 | 383 | 55.4% |
| 19 | Static Moth | Europe | 2,390 | 176 | 336 | 52.4% |

Europe has seven players, the Americas six and Asia-Pacific six. Each player's tier follows from the rating, as the Rank marks note gives it: five Inferno, seven Blaze, seven Cinder.

Build these parts in order.

### 1. Header

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

### 2. Page head

The first `<section>`, a grid (`md:grid-cols-12`):

- Columns 1–7: a label in `text-muted-foreground`, `Season 4 · Ranked Clash · Week 6`; with `mt-2`, the `<h1>` "Ranking" in `text-h1 md:text-display uppercase`; with `mt-6`, one paragraph within `max-w-xl`: the ladder ranks every player with at least 25 ranked matches this season by rating, and it is updated every hour. With `mt-3`, a label in `text-muted-foreground`: `Updated 09:00`, the time a `<time dateTime="2026-11-12T09:00">`.
- Columns 9–12, with `mt-12 md:mt-0`: a card from the layout notes, `md:self-start`, the viewer's place. A label in `text-muted-foreground`, `Your place`; with `mt-2`, the name "Juno Vane" as a `<p>` in `font-heading text-h2`. With `mt-3`, a `<dl>` of three rows, each a `<div>` with `flex items-baseline justify-between gap-3 border-t border-border py-2`: the `<dt>` a label in `text-muted-foreground` (`Global rank`, `Europe rank`, `Rating`) and the `<dd>` a big figure in `font-mono text-h2 tabular-nums` (`18`, `6`, `2,418`). The rows stack rather than sit side by side, because the card is narrow at `md`. With `mt-6`, a secondary-variant link "Your profile" to `/player`.

### 3. Filter

The second `<section>`, with `mt-20`. An `<h2>` "Top players" in `text-h2 md:text-h1 uppercase`. With `mt-6`, a button group with the `aria-label` "Region": Global, Europe, Americas, Asia-Pacific. "Global" is chosen at first. Under it, with `mt-3`, a count line, a `<p>` with `aria-live="polite"`: a label in `text-muted-foreground`, `Global · 19 players`, `Europe · 7 players`, `Americas · 6 players` or `Asia-Pacific · 6 players`.

### 4. Ranking table

In the same section, with `mt-6`, the `Table` from the layout notes with the `label` "Ranking" and `min-w-2xl`, so it scrolls in its box on a phone. In this table every cell keeps `px-3` and none gets `first:pl-0 last:pr-0`, so the viewer's bar has room at the left edge. The column headers are `<th scope="col">`. Columns:

1. `Rank`: the rank in the chosen list, `font-mono tabular-nums`, left-aligned with its header, so it sits next to the bar.
2. `Player`: the name in `text-body`. Juno Vane's name is a text link to `/player`; every other name is a text link to `#`. On the row ranked 1 in the chosen list, the page's one badge, "Rank 1", follows the name with `ml-2`: the name and the badge sit in a row `flex items-center gap-2` in place of the margin. On the viewer's row, the label `You` in `text-muted-foreground` follows the name in the same way.
3. `Tier`: a rank mark from the layout notes and the tier's name in `text-body`.
4. `Region`: in `text-body`, left-aligned.
5. `Rating`, 6. `Wins`, 7. `Win rate`: numbers, right-aligned with their headers, in `font-mono tabular-nums`. Win rate has one decimal and a `%`.

The viewer's row is marked with a heat bar at its left edge: its first cell is `relative`, and holds before the rank a `<span>` with `absolute inset-y-0 left-0 w-1 bg-heat-mid` and `aria-hidden="true"`. The row gets `aria-current="true"`. The bar, the word `You` and the link to the profile all mark it, so the bar is never the only sign. The row has no fill of its own.

When Americas or Asia-Pacific is chosen, the viewer is not in the list, and under the table, with `mt-3`, a `<p>` in `text-body` says so: "You play in Europe, so you are not in this list." With Global or Europe chosen it is not there.

### 5. Tiers

The third `<section>`, with `mt-20`. An `<h2>` "Tiers" in `text-h2 md:text-h1 uppercase`, then with `mt-3` one sentence: every rating starts the season at Cinder, and a tier is won and lost with the rating. With `mt-6`, a `<ul>` with `grid grid-cols-1 gap-6 md:grid-cols-3`, three entries, each an `<li>` with `border-t border-border pt-3`: a rank mark and the tier's name as a `<span>` in `font-heading text-h2`, in a row `flex items-center gap-2` (the mark keeps `size-3`); with `mt-2`, a label in `text-muted-foreground` with its range and its count on the ladder: `2,800 and up · 5 players`, `2,600 to 2,799 · 7 players`, `Under 2,600 · 7 players`.

### 6. Footer

The footer from the layout notes.

### Behavior

- Keep the nineteen players as an array, each with a name, a region, a rating, wins and matches. The win rate and the tier are worked out from them (win rate to one decimal); the rank is the position in the chosen list, sorted by rating, highest first. The chosen region is component state.
- Choosing a region shows only its players, ranked again from 1: in Europe, Red Static is 1 and Juno Vane is 6. With Global chosen, the ranks are the ones in the sample. The badge "Rank 1" moves to whoever is first in the shown list.
- Changing the region does not move focus and does not scroll the page. The count line announces the change.
- The tier counts are for the whole ladder and do not follow the filter.
- The page has no red action.

## 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.

- [ ] Choosing a region shows only its players, ranked again from 1 by rating, marks only the chosen button with `aria-pressed=true`, and the count line says how many players are shown.
- [ ] Ratings, wins and win rates are `font-mono tabular-nums` and right-aligned, with their column headers.
- [ ] The viewer's row (Juno Vane) has a `bg-heat-mid` bar at its left edge, the word You beside the name, and `aria-current=true`, and the bar is never the only sign.
- [ ] Every tier is a square rank mark followed by the tier's name in words, and no heat color is used for text.
- [ ] At 375px the table scrolls sideways in its own box with the name Ranking, and the page does not; the page has one badge, Rank 1, and no default (red) button.
- [ ] 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.
