---
name: unbranded
description: Build pages that belong to no brand (explainers, small tools, reports, forms) on the Unbranded design system, version 1.0.0. Use for any page that is not Greenville Triumph work and not about Sean Tippen.
---

# Unbranded 1.0.0

Unbranded is a CSS design system for pages that carry no brand. It ships three files, four presets, five starters, and a checker. The point of it is that a page built on it does not look machine-made: the choices that produce the generic AI look (fonts, hues, gradients, card rows) are already made or removed, and the checker fails the rest.

| What | Where |
|---|---|
| Reference page | https://unbranded.seantippen.com/ |
| Tokens | https://unbranded.seantippen.com/1.0.0/tokens.css |
| Components | https://unbranded.seantippen.com/1.0.0/components.css |
| Behaviours | https://unbranded.seantippen.com/1.0.0/ub.js |
| Every class and token | https://unbranded.seantippen.com/1.0.0/registry.md (readable) and registry.json (for tools) |
| Starters | https://unbranded.seantippen.com/1.0.0/starters/ (argument, article, tool, report, form) |
| Current version | https://unbranded.seantippen.com/versions.json |

## 1. The loop

1. Fill the brief card (section 4) before writing markup.
2. Pick the preset and the starter from section 5. Copy the starter from the host; do not start from a blank file.
3. Paste the head block (section 3). Set `data-preset` on `<html>`.
4. Build from the classes in registry.md and the tokens in section 6. Page-local classes take the `pg-` prefix.
5. Run ub-check (section 15). Fix until it exits 0.
6. Report what the page is, which preset, starter, and signature move it uses, and the ub-check result.

## 2. Hard rules

Floor rules (F) never have an exception. Reflex rules (R) block unless waived once per page with a written reason (section 15). The ID after each rule is what ub-check prints.

1. Link tokens.css, components.css, and ub.js at an exact version, with the design-system meta tag, viewport, lang, title, and favicon links. F01
2. Use only classes listed in registry.md. Unknown `ub-` classes fail. F02
3. Use only tokens listed in registry.json, plus the documented knobs. F03
4. One design system per page. Never link StDesign, the Triumph systems, or their class names. F04
5. No color literals in page CSS: no hex, rgb, hsl, oklch, or named colors. Use role tokens. F05
6. Fonts come only from `--ub-font-*`. Never name a font. F06
7. No gradients, gradient text, or transparent text fill. F07
8. No backdrop blur. F08
9. No scale on hover, press, or focus. Press feedback is a color change or a 1px shift. F09
10. Never put a panel or box inside another panel or box. F10
11. No colored stripe on one side of a box. Callouts use a full border. F11
12. No icon tile above a heading. F12
13. No spaced-out uppercase labels (eyebrows, kickers). F13
14. No emoji, except as content inside `data-emoji-content`. F14
15. No em-dashes in any text. F15
16. No placeholder text or hosts: lorem ipsum, John Doe, Jane Smith, Acme, placehold.co, picsum, pravatar. F16
17. No accent bar under or beside a heading. F17
18. No background image or pattern on the page or a section. F18
19. Spacing, radius, and z-index come from tokens or knobs, never px or rem literals. R01
20. Headings and paragraphs are left aligned. R02
21. No row of three or more identical cards with a heading or icon each. Use a table, a dl, or a list. R03
22. No 01, 02, 03 section markers. Numbered content goes in an `<ol>`. R04
23. The spinner is the only infinite animation. No entrance or scroll-reveal animation. R05
24. No bounce or elastic easing. R06
25. No `!important` in page CSS. R07
26. Every CDN file is pinned to an exact version with SRI and crossorigin. R08
27. Charts take colors only through `ub.chart.theme()`. The accent is never a series. R09
28. Warning colors appear only in status UI. R10
29. Every page has a brief card. R11
30. A figure with %, x, or + has a source line. R12
31. Numbered scale tokens (`--ub-neutral-6`, `--ub-accent-3`) never appear in page CSS. R13
32. One solid accent button per section. R14

## 3. Head block

```html
<!doctype html>
<html lang="en" data-preset="standard">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="design-system" content="unbranded 1.0.0">
<title>Literal page title</title>
<link rel="preload" as="font" type="font/woff2" crossorigin href="https://unbranded.seantippen.com/1.0.0/fonts/schibsted-grotesk-latin-wght-normal.woff2">
<link rel="preload" as="font" type="font/woff2" crossorigin href="https://unbranded.seantippen.com/1.0.0/fonts/public-sans-latin-wght-normal.woff2">
<link rel="stylesheet" href="https://unbranded.seantippen.com/1.0.0/tokens.css" integrity="sha384-XDeiGpxMH40GoET+QMRWCAR5eq1KmvHub/TMCLGlumygsWgGzX7BB89qEK7gSAF2" crossorigin="anonymous">
<link rel="stylesheet" href="https://unbranded.seantippen.com/1.0.0/components.css" integrity="sha384-SKo2Dp7MntS9/kGGp647kNZZWg7YuCN3WaxZb1oIG+mvmBq+bEnXhadFdj5L+qLP" crossorigin="anonymous">
<script defer src="https://unbranded.seantippen.com/1.0.0/ub.js" integrity="sha384-KBr0JNa28XlPRogXFivF133uGU0fVJDWyEt7mG+uwMHPlPQoJ3DYPmG4iAtD242u" crossorigin="anonymous"></script>
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<!-- ub-brief ... -->
</head>
```

- Preloads change with the preset. Load that preset's display and body files:

| Preset | Display file | Body file |
|---|---|---|
| standard | schibsted-grotesk-latin-wght-normal.woff2 | public-sans-latin-wght-normal.woff2 |
| tool | funnel-display-latin-wght-normal.woff2 | public-sans-latin-wght-normal.woff2 |
| reading | literata-latin-opsz-normal.woff2 | public-sans-latin-wght-normal.woff2 |
| report | source-serif-4-latin-opsz-normal.woff2 | public-sans-latin-wght-normal.woff2 |

- No Google Fonts link. The fonts come with tokens.css.
- Copy the default favicon set from https://unbranded.seantippen.com/1.0.0/favicon/ into the deploy root. A project with its own mark uses its own set.
- Client demos copy the whole version folder into their own deploy and link it relatively (`./unbranded/1.0.0/tokens.css`), so no seantippen.com URL appears in a client's source.
- The integrity hashes for this version are in versions.json and on the reference page.

## 4. Brief card

A comment in the head, filled before any markup:

```html
<!-- ub-brief
purpose: One sentence saying what this page is for.
reader: Who reads it and what they already know.
must-make-clear: The one thing the reader must leave knowing.
preset: standard
starter: argument
signature-move: inverse band
sources: Where every number on the page comes from.
-->
```

Pick one signature move per page:

| Move | How |
|---|---|
| Oversized sourced figure | One number at `--ub-text-5xl` with its source line under it |
| Inverse band | One `.ub-band` with `data-scheme` opposite to the page |
| Accent band | One `.ub-band` with `data-ground="accent"` |
| Side-by-side comparison table | A table whose columns are the things compared |
| Margin column | `.ub-with-sidebar` with notes in `.ub-sidebar` |
| Large index list as navigation | A sidenav or contents list that is the main way through the page |
| Annotated diagram | Inline SVG of the page's own mechanism, labeled with real values, colored with tokens |
| Real screenshot | At native resolution, no device frame |

ub-check warns when a page repeats the starter or the signature move of the last three pages in its folder.

## 5. Presets and starters

| Preset | Use for | Never use for | Starter |
|---|---|---|---|
| standard (default) | General pages, explainers, demos, light tools | Dense data entry | argument, form |
| tool | Small apps, admin screens, data entry, queues | Long reading | tool |
| reading | Long reads, essays, documentation | Tables of figures | article |
| report | Reports, tables, finance and operations summaries | Marketing or persuasion | report |

| Starter | Structure |
|---|---|
| argument | Headline that states the claim; evidence beside it in a 7 to 5 grid; sections on alternating grounds; sourced figures |
| article | One column at the prose measure; contents list; notes in the margin on wide screens |
| tool | App shell: appbar, sidebar of queues, toolbar, sortable table, drawer for one record |
| report | Title block with as-of date and source; one oversized figure; up to four stats; tables; chart; notes |
| form | Narrow page; fieldsets; validation after interaction; error summary; confirmation on an accent band |

None of the starters has a hero, a card row, testimonials, pricing, or an FAQ block, and a page built from them must not add one.

Four attributes work on `<html>` or on any element, so a section can switch:

| Attribute | Values | Default |
|---|---|---|
| `data-preset` | standard, tool, reading, report | standard |
| `data-scheme` | light, dark, auto | light |
| `data-density` | compact, regular, comfortable | The preset's |
| `data-contrast` | more | Off |

Light is the default. Use dark only when the reader asked for it or for one inverse band.

## 6. Token digest

Role tokens are the contract. Pages use these; components use the scales.

| Token | Use | Never use for |
|---|---|---|
| `--ub-bg` | Page ground | A second page color picked for variety |
| `--ub-surface` | Panels | The page ground |
| `--ub-surface-raised` | Inputs, popovers, menus | Resting panels |
| `--ub-surface-sunken` | Wells, code, sunken bands, table headers | Text |
| `--ub-surface-overlay` | Dialogs and drawers | Inline content |
| `--ub-fg` | Body text and headings | Text on a tinted or solid fill |
| `--ub-fg-muted` | Labels, captions, metadata | Body paragraphs |
| `--ub-fg-faint` | Placeholders, disabled text | Anything the reader must read |
| `--ub-border` | Separators, panel edges | Control edges |
| `--ub-border-strong` | Header rules, emphasis | Decorative bars |
| `--ub-border-control` | Input and checkbox edges | Panels |
| `--ub-accent` | The one primary button fill, selected marks | Headings, body text, borders, icons, a second button, a chart series |
| `--ub-accent-hover` | Primary button hover | Anything else |
| `--ub-on-accent` | Text on `--ub-accent` | Text on other fills |
| `--ub-accent-subtle` | Selected rows, the accent band | Large page areas |
| `--ub-accent-border` | Edge of a selected item | Ordinary borders |
| `--ub-accent-text` | Links, selected text | Headings |
| `--ub-focus` | Focus ring | Anything else |
| `--ub-success`, `-subtle`, `-border`, `-text`, `--ub-on-success` | Success status with an icon and a word | Decoration |
| `--ub-warning` family | Warning status only | Any decoration, ever |
| `--ub-danger` family | Errors and destructive actions | Emphasis |
| `--ub-info` family | Neutral notices | A second accent |
| `--ub-chart-1` to `-8` | Chart series, in order | UI chrome |
| `--ub-seq-1` to `-7` | Sequential data (heatmaps, ranges) | Categories |
| `--ub-div-1` to `-9` | Diverging data around a midpoint | Categories |
| `--ub-chart-grid` | Chart gridlines | Borders |
| `--ub-scrim` | Behind modal dialogs | Washes over content |
| `--ub-space-1` to `-24` | Padding and gaps (n x 4px) | Font sizes |
| `--ub-gutter`, `--ub-gap` | Page side padding; grid gaps | Inside components |
| `--ub-block`, `--ub-section` | Between blocks; between sections | Inside components |
| `--ub-text-xs` to `-5xl` | Font sizes | Spacing |
| `--ub-text-body`, `--ub-text-ui` | Body and UI text at the current density | Headings |
| `--ub-h1` to `--ub-h4` | Heading sizes at the current density | Body |
| `--ub-font-display`, `-body`, `-ui`, `-mono` | The only font families | Never name a font |
| `--ub-weight-body`, `-medium`, `-strong`, `--ub-weight-heading` | Weights | |
| `--ub-radius-control`, `-surface`, `-overlay` | Controls; panels; dialogs and popovers | Mixing across roles |
| `--ub-radius-full` | Badges, switches, spinners | Buttons and panels |
| `--ub-shadow-overlay` | Dialogs, popovers, menus, toasts | Resting panels |
| `--ub-dur-fast`, `-base`, `-slow`, `--ub-ease-out`, `--ub-ease-in-out` | State transitions | Entrance animation |
| `--ub-z-sticky`, `-overlay`, `-toast` | Stacking | Dialogs (they use the top layer) |
| `--ub-measure`, `--ub-measure-ui`, `--ub-measure-heading` | Prose, helper text, and heading widths | |
| `--ub-page-narrow`, `-default`, `-wide` | `--ub-center-max` values | |
| `--ub-control-xs`, `-sm`, `-md`, `-lg` | Control heights | |
| `--ub-icon-sm`, `-md`, `-lg` | 16, 20, 24 px; nothing larger | |

Tokens that do not exist and must not be invented: gradients, glass or blur, glow, colored shadows, a resting shadow, hover scale, entrance animation, bounce easing, eyebrow tracking, a second accent, background patterns, and a single generic radius.

## 7. Type and rhythm

- Headings use `--ub-font-display` at `--ub-h1` to `--ub-h4`; one `h1` per page, levels in order.
- `.ub-display` (4xl, or 5xl with `data-size="5xl"`) is for one short headline of six words or fewer.
- `.ub-lead` is the one paragraph under the headline that says what the page is for.
- Prose sits in `.ub-flow`, which puts more space above headings than below them. Paragraphs stop at `--ub-measure`.
- Minimum sizes: 12px captions, 14px dense UI, 16px prose. Inputs never render under 16px.
- Tables use the UI face, tabular figures, headers on one line, numbers right aligned with `.ub-num`, and are sized to their content. Bold the figure that answers the question.
- Breakpoints are 40rem and 64rem. Components use container queries (`.ub-cq`).
- Density: `compact` for tools (control md 2rem, UI text 14px), `regular` default, `comfortable` for reading (body steps up to `--ub-text-lg`). Touch screens raise controls to 44px in every density.

## 8. Component decision table

| Need | Use | Never | Also called |
|---|---|---|---|
| An action | `.ub-button` (one solid per view; soft, outline, ghost, or link for the rest) | A styled div or link that acts as a button | btn, cta |
| An icon-only action | `.ub-icon-button` with `aria-label` | An icon without a label | icon-btn |
| A labeled control | `.ub-field` with `.ub-field-label`, `.ub-input`, `.ub-field-help`, `.ub-field-error` | Placeholder as the label | text-field |
| Checkbox or radio | `.ub-check` in a `.ub-fieldset` | Custom-drawn boxes | checkbox, radio |
| A setting that applies at once | `.ub-switch` (checkbox with `role="switch"`) | A switch that needs a save button | toggle |
| Two to five peer options | `.ub-segmented` (radios in a fieldset) | Tabs for a filter | toggle-group |
| Grouping one task's content | `.ub-panel` with `.ub-panel-head` | A panel inside a panel; a grid of identical panels | card, tile, box |
| Label and value pairs | `.ub-dl` | A two-column table | key-value |
| A message with a status | `.ub-callout` with `data-tone`, icon, and title | A colored side stripe | alert, banner, notice |
| Records with several values | `.ub-table` in `.ub-table-wrap`, `data-sortable` for sorting | Cards for rows | data grid |
| Key figures | `.ub-stats` of two to four `.ub-stat`, each with a basis or source | Invented or round numbers | metric, kpi |
| A chart | `.ub-chart` frame with title, subtitle with units, plot, legend for 2+ series, source | A bare canvas | graph |
| A figure or image | `.ub-figure` with a source line | Stock photos as decoration | image |
| A status word or count | `.ub-badge` with `data-tone` | A badge above a heading | tag, pill, chip |
| A short hint | `.ub-tip` with `data-tip` | Essential information in a tooltip | tooltip |
| Floating options | `.ub-popover` with `popover` and `popovertarget` | A custom dropdown script | dropdown |
| A confirmation or short task | `.ub-dialog` opened with `command="show-modal"` | A full page for a yes or no | modal |
| One record beside a list | `.ub-drawer` | Navigating away and back | sheet, side-panel |
| Confirming a finished action | `ub.toast(text, {tone})` | Toasts for errors that need action | snackbar |
| Peer views of one dataset | `.ub-tabs` | Tabs that hide content meant to be read in order | |
| Optional detail | `.ub-accordion` of `<details name>` | Hiding the main content | FAQ |
| Nothing to show | `.ub-empty`: what is missing, what to do, one button | An illustration | zero-state |
| Loading | `.ub-skeleton` (static) or `.ub-spinner` | Shimmer | loader |
| Progress | `.ub-progress` on `<progress>` or `<meter>` | A div bar | |
| Top of a page | `.ub-appbar` with a text name or a mark | A stacked wordmark next to the title | header, navbar |
| Page links | `.ub-nav`, `.ub-sidenav` | | |
| App layout | `.ub-app` with `.ub-app-side` and `.ub-app-main` | | shell |
| Controls above a table | `.ub-toolbar` | | filter bar |
| Copyable code | `.ub-code` with `.ub-code-copy` | | |
| A full-width section | `.ub-band` with `data-ground` or `data-scheme` | Background images | section |

Layout: `.ub-stack`, `.ub-flow`, `.ub-center`, `.ub-cluster`, `.ub-grid` (7 to 5; `data-fit` only for galleries of like items), `.ub-with-sidebar`, `.ub-switcher`, `.ub-cover`, `.ub-frame`, `.ub-reel`. Each takes knobs such as `--ub-stack-space` and `--ub-grid-cols`, set inline with token values.

## 9. Snippets

```html
<!-- Button -->
<button class="ub-button" type="button">Save changes</button>
<button class="ub-button" type="button" data-variant="outline" data-tone="neutral">Cancel</button>

<!-- Field -->
<div class="ub-field">
  <label class="ub-field-label" for="email">Email address</label>
  <input class="ub-input" id="email" type="email" required aria-describedby="email-err">
  <p class="ub-field-error" id="email-err">Email address must include @.</p>
</div>

<!-- Panel -->
<section class="ub-panel" aria-labelledby="p1">
  <div class="ub-panel-head"><h2 class="ub-panel-title" id="p1">Request 2213</h2></div>
  <dl class="ub-dl"><dt>Team</dt><dd>Water billing</dd></dl>
</section>

<!-- Table -->
<div class="ub-table-wrap" role="region" aria-label="Open requests by team" tabindex="0">
  <table class="ub-table" data-sortable>
    <caption>Open requests by team</caption>
    <thead><tr><th scope="col" aria-sort="none"><button type="button" class="ub-sort">Team</button></th>
      <th scope="col" aria-sort="none" class="ub-num"><button type="button" class="ub-sort">Open</button></th></tr></thead>
    <tbody><tr><th scope="row">Permits</th><td class="ub-num">214</td></tr></tbody>
  </table>
</div>

<!-- Stat -->
<div class="ub-stat"><p class="ub-stat-value">657</p><p class="ub-stat-label">Open requests</p><p class="ub-stat-basis">All teams, Nov 1</p></div>

<!-- Chart frame (Chart.js 4.4.7, colors through the bridge) -->
<figure class="ub-chart" data-state="ready">
  <figcaption class="ub-chart-head"><h3 class="ub-chart-title">Requests opened per week</h3><p class="ub-chart-sub">All teams, 12 weeks</p></figcaption>
  <div class="ub-chart-plot"><canvas role="img" aria-label="Line chart: requests rose from 412 to 546 per week"></canvas></div>
  <p class="ub-chart-source">Source: request log export.</p>
</figure>
<script>document.addEventListener("DOMContentLoaded", () => ub.chart.theme(new Chart(canvas, config)));</script>

<!-- Dialog -->
<button class="ub-button" type="button" data-tone="danger" command="show-modal" commandfor="del">Delete 3 requests</button>
<dialog class="ub-dialog" id="del" aria-labelledby="del-t">
  <div class="ub-dialog-head"><h2 class="ub-dialog-title" id="del-t">Delete 3 requests?</h2></div>
  <div class="ub-dialog-body"><p>This cannot be undone.</p></div>
  <div class="ub-dialog-actions">
    <button class="ub-button" type="button" data-variant="outline" data-tone="neutral" command="close" commandfor="del">Keep requests</button>
    <button class="ub-button" type="button" data-tone="danger">Delete 3 requests</button>
  </div>
</dialog>

<!-- Tabs -->
<div class="ub-tabs">
  <div class="ub-tablist" role="tablist" aria-label="Range">
    <button class="ub-tab" role="tab" type="button" id="t1" aria-controls="p-1" aria-selected="true">Week</button>
    <button class="ub-tab" role="tab" type="button" id="t2" aria-controls="p-2" aria-selected="false">Month</button>
  </div>
  <div role="tabpanel" id="p-1" aria-labelledby="t1">...</div>
  <div role="tabpanel" id="p-2" aria-labelledby="t2" hidden>...</div>
</div>

<!-- Empty state -->
<div class="ub-empty">
  <p class="ub-empty-title">No invoices yet</p>
  <p>Add one to start tracking.</p>
  <button class="ub-button" type="button">Add invoice</button>
</div>

<!-- Callout -->
<div class="ub-callout" data-tone="warning" role="note">
  <svg class="ub-icon" aria-hidden="true"><use href="#ub-i-triangle-alert"/></svg>
  <p class="ub-callout-title">2 days past due</p>
  <p>Overdue requests go to the team lead each morning.</p>
</div>
```

## 10. Do and don't

| Do | Don't | Rule |
|---|---|---|
| A table of plans with one column per plan | Three pricing cards in a row | R03 |
| `.ub-callout` with a full border, icon, and title | A box with a thick colored left border | F11 |
| A heading that names the section | A small uppercase label above the heading | F13 |
| `color: var(--ub-fg-muted)` | `color: #6b7280` | F05 |
| A flat `--ub-bg` page with one inverse band | A gradient or a radial glow behind the hero | F07, F18 |
| `transition: background-color` on hover | `transform: scale(1.05)` on hover | F09 |
| A table row per record | A grid of identical cards with an icon each | R03, F12 |
| Content visible on load | Fade-in on scroll | R05 |
| One solid button; the rest outline or ghost | Two orange buttons side by side | R14 |
| `<ol>` for steps | Big "01", "02", "03" markers | R04 |
| A figure with its source line | "3x faster" with no source | R12 |
| `padding: var(--ub-space-4)` | `padding: 18px` | R01 |

## 11. Libraries

- **Charts:** Chart.js 4.4.7 from `https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.js` with `integrity="sha384-zYPBGXwO4633CABX/5Spf6emCKUJCfoOkhOMYyxMsatqQZPnDblmmOewfjsIVWCM"` and `crossorigin="anonymous"`. Chart.js cannot read OKLCH or CSS variables, so call `ub.chart.theme(chart)` after creating every chart. It sets series, tick, grid, tooltip, and font from tokens and re-runs when the preset or scheme changes. Dataset `ubColor: "--ub-chart-3"` picks a slot; never pass a color literal. More than eight series means grouping or direct labels.
- **Formatting:** `ub.fmt.number(n, digits)`, `ub.fmt.compact(n)`, `ub.fmt.currency(n, "USD")`, `ub.fmt.percent(0.42)`, `ub.fmt.date(d)` (one date format, "Nov 1, 2026").
- **Icons:** Lucide 1.66.0 as an inline sprite of only the icons the page uses: `<svg class="ub-icon" aria-hidden="true"><use href="#ub-i-search"/></svg>`. Build the sprite from the system's icon-sprite script. Icons only on controls, statuses, and navigation; never above a heading; never larger than 24px; never sparkles, rocket, zap, wand, or bot unless the page is about that thing.
- **Maps:** Leaflet 1.9.4 with MapLibre GL 5.24.0 and OpenFreeMap tiles (positron for light, dark for dark) inside `.ub-map`. Never CARTO, Stadia, or react-leaflet. Markers are `L.divIcon` with an SVG colored from a token.
- **Motion:** CSS transitions on state changes only (opacity, transform, color, background-color, border-color) with the duration tokens. GSAP 3.15 or Motion only for a sequence the page needs, stated in the brief card.
- **Images:** the page's own screenshots and real artifacts first, then CC0 archives, then a specific Unsplash or Pexels photo downloaded into the deploy. No hotlinks, no illustration sets, no device mockups. Every `<img>` has width, height, and alt. JPEG only for photos, at 4:4:4 chroma; everything else PNG.
- **React Bits, Magic UI, Aceternity, 21st.dev:** allowed, but anything taken from them must pass ub-check, and most of their effects (gradients, glows, blur) fail it.
- **Demo data:** seeded and plausible (1,847, not 2,000), clearly fictional organizations, some awkward values (a long name, a zero, a missing value), and a visible "Sample data" note.

## 12. React

- Install the adapters: https://unbranded.seantippen.com/1.0.0/adapters/tailwind-theme.css and adapters/shadcn.css.
- Order in globals.css: `@layer theme, base, ub, components, utilities;`, then `@import "tailwindcss";`, then tailwind-theme.css, then shadcn's own `@theme inline` color map, then tokens.css and components.css, then shadcn.css last (it is unlayered on purpose).
- Delete shadcn's `:root { ... }` and `.dark { ... }` value blocks; the adapter replaces them. The dark variant follows `data-scheme` (the snippet is at the top of shadcn.css).
- System components are plain classes in JSX: `<section className="ub-panel">`. Behaviour-heavy parts (dialog, select, tabs, menu) come from shadcn and are restyled by `data-slot` in the adapter.
- Recharts reads `var(--ub-chart-n)` directly and needs no bridge.

## 13. Copy

| Element | Rule | Example |
|---|---|---|
| Page headline | What the page is or does, under 10 words | Open requests by team |
| Section heading | Name the contents in one to three words | Overdue requests |
| Button | Verb and object, sentence case, three words or fewer | Export CSV |
| Never on a button | Submit, OK, Click here, Get started, Learn more | Read the fee schedule |
| Empty state | What is missing, what to do, one button | No invoices yet. Add one to start tracking. |
| Error | Field, problem, fix; no apology or blame | Email address must include @. |
| Success | Past tense, names the object, no exclamation | Request 2213 assigned to Permits. |
| Destructive confirm | Title names the object; the button repeats the action | Delete 3 requests? |
| Loading | Say what is loading | Loading requests |
| Numbers | Units in headers, one date format, a source for every figure, rates per unit | Revenue (USD); $15/match |
| Terms | One noun per thing across a product | Request, never ticket or case |

Also banned: em-dashes, buzzwords (seamless, supercharge, world-class, enterprise-grade, cutting-edge, unlock, elevate, streamline, empower, next-generation), aphorism cadence ("Not X. Y.", "Less X. More Y."), invented numbers, a subtitle that repeats the title, and the brand claiming feelings. Serial lists take the Oxford comma.

## 14. What does not apply

General frontend guidance tells a model to layer CSS gradients, use geometric background patterns, and pick fonts such as Space Grotesk, Playfair Display, Crimson Pro, IBM Plex, Source Sans 3, JetBrains Mono, Bricolage Grotesque, Newsreader, Clash Display, Satoshi, or Cabinet Grotesk. On Unbranded pages none of that applies. Backgrounds are flat, fonts come only from the preset, and a page never picks its own accent or hue.

Sean's other systems do not apply either: no design.seantippen.com fetch, no Inter or JetBrains Mono Google Fonts link, no zero-radius rule, and no green sTc favicon. Those belong to the Sean Tippen and Greenville Triumph systems.

## 15. Before you ship

```
node ~/.claude/skills/design/unbranded/check/ub-check.mjs page.html
node ~/.claude/skills/design/unbranded/check/ub-check.mjs ./site --json
```

- Exit 0: clean. Exit 2: blocking findings. Exit 1: a file could not be read.
- Each finding prints as `RULE-ID file:line message | fix: ...`. Fix every Floor finding and every unwaived Reflex finding, then run it again.
- Waive a Reflex rule once per page, only when the page truly needs the pattern: `<!-- ub-waive R02: reason of 24 or more characters that shares a word with the page -->`. Short or reused reasons are rejected. Floor rules F01 to F18 cannot be waived.
- Check the page at 390px wide as well as desktop: no element or table may run off the side.
- Look at it once in each scheme it supports.

## 16. Extending and versions

- Page-local classes use the `pg-` prefix and live in the page's own `<style>`. They use tokens only.
- When the same `pg-` pattern appears on three pages, propose it for the system instead of copying it a fourth time.
- Never edit or override the system files. Unlayered page CSS already beats every system rule, so a local override needs no `!important`.
- Pin the exact version in the URLs and the meta tag. There is no floating latest URL. To update, change the version in all four places and copy the new integrity hashes from versions.json.
- Current version: 1.0.0. Deprecated names: none.
