Markdown files that help a developer or an AI assistant apply the CNO system correctly.
Three Markdown files tell an AI how to build in the CNO style. Give them to it once and everything it makes follows the rules. Keep all three together.
1. Put the three files together in a folder, for example docs/cno/.
2. Add the tokens and fonts to the project.
3. In the project root, add AGENTS.md (and CLAUDE.md for Claude Code) with this line:
Follow the CNO developer system in docs/cno/AGENTS.md. Read the other files in that folder when it tells you to.
Attach the three files, then ask.
I have attached the CNO developer system files (AGENTS.md, design-tokens.md and components.md). Read AGENTS.md first and follow it for everything you make for me. My request:
A skill for AI coding agents. Give it a screen to build and it uses the CNO tokens and component specs, starts from a working page, and checks the result for values that are not in the system.
Download the zip below and unzip it into ~/.claude/skills, or from a terminal (replace <your-host> with where this site is deployed):
curl -L <your-host>/admin/system/skills/cno-ui.zip -o cno-ui.zip && unzip -o cno-ui.zip -d ~/.claude/skills
/cno-ui build a settings page with a filter card and a table of stories, in the CNO style
| File | What it is | About |
|---|---|---|
| SKILL.md | The workflow | What the agent does, in order: read, plan, build, check, hand over. Start here. |
| rules.md | The rules | What to always and never do, with a quick reference of which token to use. |
| design-tokens.md | Colour, type and shape values | Every token with hex, RGB and use. |
| components.md | Component specs | Specs and starter CSS for each component. |
| tokens.css | The tokens as CSS | Custom properties to link into any page. |
| fonts.css | The web fonts | @font-face for ProximaNova 300 to 800 (relative paths to ../fonts/). |
| starter.html | Starter page | Header, page title, button, table and footer built from the tokens. |
| check.py | The checker | Flags colours, font sizes, radii and fonts that are not in the system. |
# CNO developer system: instructions for AI agents Applies to any UI that should look like Canada's National Observer tools and pages: static prototypes, admin screens and mock-ups. Read these in order: 1. `design-tokens.md`: every colour, type, radius and shadow value, and the CSS custom properties 2. `components.md`: specs and starter CSS for each component ## Always - **Use the tokens** (`var(--color-*)`, `--font-size-*`, `--radius-*`, `--shadow-*`) instead of hard-coded values. - **Fonts:** `ProximaNova` for UI (link `fonts.css`; each weight has its own file), `Noto Serif` only for article body text. Set `-webkit-font-smoothing: antialiased` on body. - **Colour is meaning:** black frames (sidebar, header), white carries content, Subscribe orange `var(--color-accent)` is the single call to action, light blue `var(--color-link)` is for links (dark blue `var(--color-primary)` on hover and in admin tables). - **Buttons are pills:** `--radius-btn-sm|md|lg`, bold 16 to 18px text, 150ms colour fade. - **Headings are extra bold (800)** with a 1px `var(--color-border)` rule under section headers. - **Almost no shadows.** Separate with hairlines; elevation is only for menus and dialogs. - **Use made-up data** in prototypes. Never put real reader or donor data in a repo or public page. ## Never - Invent new colours, radii or font sizes. Pick the nearest token. - Use orange for decoration or more than one call to action per view. - Use the serif for UI controls. ## Quick reference | Need | Use | | --- | --- | | Body text | `var(--color-text)` on `var(--color-surface)` | | Muted label | `var(--color-text-muted)` | | Link | `var(--color-link)`, hover `var(--color-primary)` | | Call to action | `var(--color-accent)` pill button, hover `var(--color-ink)` | | Sidebar / header | `var(--color-ink)` with `var(--color-text-on-dark)` | | Hairline | `1px solid var(--color-border)` (rows: `--color-border-light`) | | Footer | `var(--color-surface-alt)` | | Success / error / warning | `--color-success*`, `--color-danger*`, `--color-warning*` | ## When unsure Do not guess. Flag the question and take the safest option. Tokens tagged **Ours** in `tokens.css` are admin-tool choices, not site values. Source of truth: the Developer System at `/admin/system/`.
# Design tokens
Named values for the CNO interface, extracted from the saved nationalobserver.com pages. Use the token, never the raw value.
## Colour
| Colour | Token | Hex / value | RGB | Use |
| --- | --- | --- | --- | --- |
| Text | `--color-text` | `#000000` | R0 G0 B0 | body text (--cno-base-font-color), table cells, headings |
| Text on dark | `--color-text-on-dark` | `#ffffff` | R255 G255 B255 | text and icons on the black sidebar and on filled buttons |
| Text muted | `--color-text-muted` | `#666666` | R102 G102 B102 | labels, captions (--cno-gray-500) |
| Text disabled | `--color-text-disabled` | `#999999` | R153 G153 B153 | disabled text (--cno-gray-700) |
| Primary | `--color-primary` | `#003f74` | R0 G63 B116 | links in the admin, active states, focus (--cno-blue-dark) |
| Primary hover | `--color-primary-hover` | `#3ba5d1` | R59 G165 B209 | link and button hover (--cno-blue, the site's link colour) |
| Link | `--color-link` | `#3ba5d1` | R59 G165 B209 | links on the public site (a { color: var(--cno-blue) }) |
| Accent | `--color-accent` | `#fe3b1f` | R254 G59 B31 | Subscribe orange: the active-item bar and the one call to action (--cno-subscribe-orange) |
| Brand red | `--color-brand-red` | `#ff3a1e` | R255 G58 B30 | red of the CNO logo mark (--cno-red-300) |
| Ink | `--color-ink` | `#000000` | R0 G0 B0 | sidebar, header and footer-dark surfaces (--cno-black) |
| Ink soft | `--color-ink-soft` | `#222222` | R34 G34 B34 | hover on black surfaces (--cno-gray-100) |
| Surface | `--color-surface` | `#ffffff` | R255 G255 B255 | cards, menus, main area (--cno-white) |
| Page bg | `--color-page-bg` | `#f9f9f9` | R249 G249 B249 | code boxes, dev box, page tint (--cno-gray-1200) |
| Surface alt | `--color-surface-alt` | `#f2f2f2` | R242 G242 B242 | footer background (--cno-gray-1100) |
| Border | `--color-border` | `#dddddd` | R221 G221 B221 | section header rule (--cno-gray-950) |
| Border light | `--color-border-light` | `#e2e2e2` | R226 G226 B226 | table row dividers (--cno-gray-1000) |
| Border strong | `--color-border-strong` | `#cccccc` | R204 G204 B204 | input borders (--cno-gray-900) |
| Hover | `--color-hover` | `rgba(0, 0, 0, 0.04)` | | round icon button hover |
| Focus visible | `--color-focus-visible` | `rgba(0, 0, 0, 0.12)` | | keyboard focus fill on rows |
| Icon muted | `--color-icon-muted` | `#757575` | R117 G117 B117 | icons (--cno-gray-600) |
| Backdrop | `--color-backdrop` | `rgba(0, 0, 0, 0.8)` | | behind a dialog (--cno-modal-background-color) |
| Link underline | `--color-link-underline` | `rgba(0, 63, 116, 0.4)` | | underline on admin links |
| Disabled bg | `--color-disabled-bg` | `#e2e2e2` | R226 G226 B226 | disabled button fill (--cno-gray-1000) |
| Disabled text | `--color-disabled-text` | `#999999` | R153 G153 B153 | disabled button text (--cno-gray-700) |
| Success | `--color-success` | `#0db500` | R13 G181 B0 | success (--cno-green) |
| Success bg | `--color-success-bg` | `#d4f4d4` | R212 G244 B212 | success fill (--cno-green-light) |
| Success text | `--color-success-text` | `#054f00` | R5 G79 B0 | success text (--cno-green-dark) |
| Danger | `--color-danger` | `#ff0c00` | R255 G12 B0 | error (--cno-red) |
| Danger bg | `--color-danger-bg` | `#ffcecc` | R255 G206 B204 | error fill (--cno-red-light) |
| Danger text | `--color-danger-text` | `#990700` | R153 G7 B0 | error text (--cno-red-dark) |
| Warning | `--color-warning` | `#ffad02` | R255 G173 B2 | warning (--cno-yellow-dark) |
| Warning bg | `--color-warning-bg` | `#fdca40` | R253 G202 B64 | highlight fill (--cno-gold) |
## Typography
Sans `ProximaNova, "Helvetica Neue", Helvetica, Arial, "Nimbus Sans L", sans-serif`; serif `"Noto Serif", Georgia, Times, "Times New Roman", serif`.
| Token | Size / line height | Weight | Use |
| --- | --- | --- | --- |
| `--font-size-display` | 54px / 60px | 800 | Page title on wide screens |
| `--font-size-title` | 32px / 36px | 800 | Page title |
| `--font-size-h2` | 32px / 40.8px | 800 | Section heading |
| `--font-size-lg` | 18px / 24px | 400 | Site base text, large buttons |
| `--font-size-md` | 16px / 24px | 400 | Admin body text, medium and small buttons |
| `--font-size-table` | 15px / 22px | 400 | Table body |
| `--font-size-nav` | 14px / 20px | 700 | Sidebar items (uppercase) |
| `--font-size-thead` | 13px / 20px | 800 | Table head (uppercase) |
| `--font-size-caption` | 13px / 18px | 400 | Code badges, captions |
## Shape and elevation
| Token | Value | Use |
| --- | --- | --- |
| `--radius-sm` | 4px | Cards, inputs, code boxes |
| `--radius-btn-sm` | 15px | Small pill button |
| `--radius-btn-md` | 20px | Medium pill button |
| `--radius-btn-lg` | 24px | Large pill button |
| `--radius-round` | 50% | Icon buttons, dots |
| `--shadow-1` | 0 1px 2px rgba(0, 0, 0, 0.12) | Resting card (admin only; the site uses almost none) |
| `--shadow-8` | 0 6px 16px rgba(0, 0, 0, 0.16) | Menus and hovered tiles |
| `--shadow-24` | 0 16px 40px rgba(0, 0, 0, 0.3) | Dialogs |
## All tokens
Generated from `tokens.css`, so it is always complete.
| Token | Value | Use | Source |
| --- | --- | --- | --- |
| `--color-text` | `#000000` | body text (--cno-base-font-color), table cells, headings | nationalobserver.com CSS |
| `--color-text-on-dark` | `#ffffff` | text and icons on the black sidebar and on filled buttons | nationalobserver.com CSS |
| `--color-text-muted` | `#666666` | labels, captions (--cno-gray-500) | nationalobserver.com CSS |
| `--color-text-disabled` | `#999999` | disabled text (--cno-gray-700) | nationalobserver.com CSS |
| `--color-primary` | `#003f74` | links in the admin, active states, focus (--cno-blue-dark) | nationalobserver.com CSS |
| `--color-primary-hover` | `#3ba5d1` | link and button hover (--cno-blue, the site's link colour) | nationalobserver.com CSS |
| `--color-link` | `#3ba5d1` | links on the public site (a { color: var(--cno-blue) }) | nationalobserver.com CSS |
| `--color-accent` | `#fe3b1f` | Subscribe orange: the active-item bar and the one call to action (--cno-subscribe-orange) | nationalobserver.com CSS |
| `--color-brand-red` | `#ff3a1e` | red of the CNO logo mark (--cno-red-300) | nationalobserver.com CSS |
| `--color-ink` | `#000000` | sidebar, header and footer-dark surfaces (--cno-black) | nationalobserver.com CSS |
| `--color-ink-soft` | `#222222` | hover on black surfaces (--cno-gray-100) | nationalobserver.com CSS |
| `--color-surface` | `#ffffff` | cards, menus, main area (--cno-white) | nationalobserver.com CSS |
| `--color-page-bg` | `#f9f9f9` | code boxes, dev box, page tint (--cno-gray-1200) | nationalobserver.com CSS |
| `--color-surface-alt` | `#f2f2f2` | footer background (--cno-gray-1100) | nationalobserver.com CSS |
| `--color-border` | `#dddddd` | section header rule (--cno-gray-950) | nationalobserver.com CSS |
| `--color-border-light` | `#e2e2e2` | table row dividers (--cno-gray-1000) | nationalobserver.com CSS |
| `--color-border-strong` | `#cccccc` | input borders (--cno-gray-900) | nationalobserver.com CSS |
| `--color-hover` | `rgba(0, 0, 0, 0.04)` | round icon button hover | Ours (admin tool) |
| `--color-focus-visible` | `rgba(0, 0, 0, 0.12)` | keyboard focus fill on rows | Ours (admin tool) |
| `--color-icon-muted` | `#757575` | icons (--cno-gray-600) | nationalobserver.com CSS |
| `--color-backdrop` | `rgba(0, 0, 0, 0.8)` | behind a dialog (--cno-modal-background-color) | nationalobserver.com CSS |
| `--color-link-underline` | `rgba(0, 63, 116, 0.4)` | underline on admin links | Ours (admin tool) |
| `--color-disabled-bg` | `#e2e2e2` | disabled button fill (--cno-gray-1000) | nationalobserver.com CSS |
| `--color-disabled-text` | `#999999` | disabled button text (--cno-gray-700) | nationalobserver.com CSS |
| `--color-success` | `#0db500` | success (--cno-green) | nationalobserver.com CSS |
| `--color-success-bg` | `#d4f4d4` | success fill (--cno-green-light) | nationalobserver.com CSS |
| `--color-success-text` | `#054f00` | success text (--cno-green-dark) | nationalobserver.com CSS |
| `--color-danger` | `#ff0c00` | error (--cno-red) | nationalobserver.com CSS |
| `--color-danger-bg` | `#ffcecc` | error fill (--cno-red-light) | nationalobserver.com CSS |
| `--color-danger-text` | `#990700` | error text (--cno-red-dark) | nationalobserver.com CSS |
| `--color-warning` | `#ffad02` | warning (--cno-yellow-dark) | nationalobserver.com CSS |
| `--color-warning-bg` | `#fdca40` | highlight fill (--cno-gold) | nationalobserver.com CSS |
| `--font-family` | `ProximaNova, "Helvetica Neue", Helvetica, Arial, "Nimbus Sans L", sans-serif` | --cno-base-font-family: the UI typeface | nationalobserver.com CSS |
| `--font-family-serif` | `"Noto Serif", Georgia, Times, "Times New Roman", serif` | --cno-serif-font-family: article body and pull quotes | nationalobserver.com CSS |
| `--letter-spacing` | `0` | the site sets none | nationalobserver.com CSS |
| `--font-size-title` | `32px` | h1 at the smallest breakpoint (2rem) | nationalobserver.com CSS |
| `--line-height-title` | `36px` | h1 (2.25rem) | nationalobserver.com CSS |
| `--font-weight-title` | `800` | h1 | nationalobserver.com CSS |
| `--font-size-display` | `54px` | h1 at the widest breakpoint (3.375rem) | nationalobserver.com CSS |
| `--line-height-display` | `60px` | h1 at the widest breakpoint (3.75rem) | nationalobserver.com CSS |
| `--font-size-h2` | `32px` | h2 (2rem) | nationalobserver.com CSS |
| `--line-height-h2` | `40.8px` | h2 (2.55rem) | nationalobserver.com CSS |
| `--font-size-lg` | `18px` | base text (--cno-base-font-size) and large buttons | nationalobserver.com CSS |
| `--line-height-lg` | `24px` | base line height (--cno-base-line-height) | nationalobserver.com CSS |
| `--font-size-md` | `16px` | admin body text, medium and small buttons (1rem) | nationalobserver.com CSS |
| `--line-height-md` | `24px` | admin body line height | Ours (admin tool) |
| `--font-size-table` | `15px` | table body in the admin (dense, between 14 and 16) | Ours (admin tool) |
| `--line-height-table` | `22px` | table body | Ours (admin tool) |
| `--font-size-nav` | `14px` | sidebar items, uppercase 700 | Ours (admin tool) |
| `--line-height-nav` | `20px` | sidebar items | Ours (admin tool) |
| `--font-size-thead` | `13px` | table head, 800 | Ours (admin tool) |
| `--line-height-thead` | `20px` | table head | Ours (admin tool) |
| `--font-size-caption` | `13px` | code badges, captions | Ours (admin tool) |
| `--line-height-caption` | `18px` | captions | Ours (admin tool) |
| `--font-weight-regular` | `400` | body | nationalobserver.com CSS |
| `--font-weight-semibold` | `600` | subheads | nationalobserver.com CSS |
| `--font-weight-bold` | `700` | buttons, nav | nationalobserver.com CSS |
| `--font-weight-thead` | `800` | headings (extrabold file) | nationalobserver.com CSS |
| `--radius-sm` | `4px` | cards, inputs, code (--cno-border-radius) | nationalobserver.com CSS |
| `--radius-btn-sm` | `15px` | small pill button (.9375rem) | nationalobserver.com CSS |
| `--radius-btn-md` | `20px` | medium pill button (1.25rem) | nationalobserver.com CSS |
| `--radius-btn-lg` | `24px` | large pill button (1.5rem) | nationalobserver.com CSS |
| `--radius-round` | `50%` | icon buttons, dots | Ours (admin tool) |
| `--space-1` | `4px` | Tailwind 1 | nationalobserver.com CSS |
| `--space-2` | `8px` | Tailwind 2 | nationalobserver.com CSS |
| `--space-3` | `12px` | Tailwind 3 | nationalobserver.com CSS |
| `--space-4` | `16px` | Tailwind 4 | nationalobserver.com CSS |
| `--space-5` | `20px` | Tailwind 5 (footer side padding) | nationalobserver.com CSS |
| `--space-6` | `24px` | Tailwind 6 (paragraph margin 1.5rem) | nationalobserver.com CSS |
| `--space-8` | `32px` | Tailwind 8 | nationalobserver.com CSS |
| `--space-10` | `40px` | Tailwind 10 | nationalobserver.com CSS |
| `--page-padding` | `40px` | main area padding | Ours (admin tool) |
| `--card-padding` | `24px` | card padding | Ours (admin tool) |
| `--shadow-1` | `0 1px 2px rgba(0, 0, 0, 0.12)` | the site uses almost no shadows; resting card (admin only) | Ours (admin tool) |
| `--shadow-8` | `0 6px 16px rgba(0, 0, 0, 0.16)` | menus and hovered tiles | Ours (admin tool) |
| `--shadow-24` | `0 16px 40px rgba(0, 0, 0, 0.3)` | dialogs | Ours (admin tool) |
| `--duration-hover` | `150ms` | --cno-transition-speed-default: colour and background fades | nationalobserver.com CSS |
| `--duration-menu-item` | `150ms` | same as hover | nationalobserver.com CSS |
| `--ease-standard` | `ease-in-out` | transition timing on links and buttons | nationalobserver.com CSS |
| `--transition-focus` | `150ms ease-in-out` | focus and underline transitions | nationalobserver.com CSS |
| `--sidebar-width` | `240px` | admin left nav | Ours (admin tool) |
| `--content-max-width` | `1290px` | --cno-main-content-max-width-wide (80.625rem) | nationalobserver.com CSS |
| `--article-max-width` | `790px` | article column (max-w-search_result) | nationalobserver.com CSS |
| `--nav-indicator-height` | `4px` | active item bar | Ours (admin tool) |
| `--header-height` | `200px` | site header, collapsed (12.5rem) | nationalobserver.com CSS |
| `--footer-padding` | `20px` | footer side padding (px-5) | nationalobserver.com CSS |
## CSS
```css
/* CNO design tokens: extracted from the saved nationalobserver.com pages (the --cno-* custom properties and the compiled Tailwind/Drupal CSS).
One token per line with its use as the trailing comment: the docs, tokens.json and the skill are generated from this file.
Source tag at the start of each comment:
[css] the value is written literally in the saved nationalobserver.com CSS (:root --cno-* or a component rule)
[ours] defined by us for the admin tool, not taken from the site */
:root {
/* Color: text */
--color-text: #000000; /* [css] body text (--cno-base-font-color), table cells, headings */
--color-text-on-dark: #ffffff; /* [css] text and icons on the black sidebar and on filled buttons */
--color-text-muted: #666666; /* [css] labels, captions (--cno-gray-500) */
--color-text-disabled: #999999; /* [css] disabled text (--cno-gray-700) */
/* Color: brand and neutrals */
--color-primary: #003f74; /* [css] links in the admin, active states, focus (--cno-blue-dark) */
--color-primary-hover: #3ba5d1; /* [css] link and button hover (--cno-blue, the site's link colour) */
--color-link: #3ba5d1; /* [css] links on the public site (a { color: var(--cno-blue) }) */
--color-accent: #fe3b1f; /* [css] Subscribe orange: the active-item bar and the one call to action (--cno-subscribe-orange) */
--color-brand-red: #ff3a1e; /* [css] red of the CNO logo mark (--cno-red-300) */
--color-ink: #000000; /* [css] sidebar, header and footer-dark surfaces (--cno-black) */
--color-ink-soft: #222222; /* [css] hover on black surfaces (--cno-gray-100) */
--color-surface: #ffffff; /* [css] cards, menus, main area (--cno-white) */
--color-page-bg: #f9f9f9; /* [css] code boxes, dev box, page tint (--cno-gray-1200) */
--color-surface-alt: #f2f2f2; /* [css] footer background (--cno-gray-1100) */
--color-border: #dddddd; /* [css] section header rule (--cno-gray-950) */
--color-border-light: #e2e2e2; /* [css] table row dividers (--cno-gray-1000) */
--color-border-strong: #cccccc; /* [css] input borders (--cno-gray-900) */
--color-hover: rgba(0, 0, 0, 0.04); /* [ours] round icon button hover */
--color-focus-visible: rgba(0, 0, 0, 0.12); /* [ours] keyboard focus fill on rows */
--color-icon-muted: #757575; /* [css] icons (--cno-gray-600) */
--color-backdrop: rgba(0, 0, 0, 0.8); /* [css] behind a dialog (--cno-modal-background-color) */
--color-link-underline: rgba(0, 63, 116, 0.4); /* [ours] underline on admin links */
--color-disabled-bg: #e2e2e2; /* [css] disabled button fill (--cno-gray-1000) */
--color-disabled-text: #999999; /* [css] disabled button text (--cno-gray-700) */
/* Color: status */
--color-success: #0db500; /* [css] success (--cno-green) */
--color-success-bg: #d4f4d4; /* [css] success fill (--cno-green-light) */
--color-success-text: #054f00; /* [css] success text (--cno-green-dark) */
--color-danger: #ff0c00; /* [css] error (--cno-red) */
--color-danger-bg: #ffcecc; /* [css] error fill (--cno-red-light) */
--color-danger-text: #990700; /* [css] error text (--cno-red-dark) */
--color-warning: #ffad02; /* [css] warning (--cno-yellow-dark) */
--color-warning-bg: #fdca40; /* [css] highlight fill (--cno-gold) */
/* Typography */
--font-family: ProximaNova, "Helvetica Neue", Helvetica, Arial, "Nimbus Sans L", sans-serif; /* [css] --cno-base-font-family: the UI typeface */
--font-family-serif: "Noto Serif", Georgia, Times, "Times New Roman", serif; /* [css] --cno-serif-font-family: article body and pull quotes */
--letter-spacing: 0; /* [css] the site sets none */
--font-size-title: 32px; /* [css] h1 at the smallest breakpoint (2rem) */
--line-height-title: 36px; /* [css] h1 (2.25rem) */
--font-weight-title: 800; /* [css] h1 */
--font-size-display: 54px; /* [css] h1 at the widest breakpoint (3.375rem) */
--line-height-display: 60px; /* [css] h1 at the widest breakpoint (3.75rem) */
--font-size-h2: 32px; /* [css] h2 (2rem) */
--line-height-h2: 40.8px; /* [css] h2 (2.55rem) */
--font-size-lg: 18px; /* [css] base text (--cno-base-font-size) and large buttons */
--line-height-lg: 24px; /* [css] base line height (--cno-base-line-height) */
--font-size-md: 16px; /* [css] admin body text, medium and small buttons (1rem) */
--line-height-md: 24px; /* [ours] admin body line height */
--font-size-table: 15px; /* [ours] table body in the admin (dense, between 14 and 16) */
--line-height-table: 22px; /* [ours] table body */
--font-size-nav: 14px; /* [ours] sidebar items, uppercase 700 */
--line-height-nav: 20px; /* [ours] sidebar items */
--font-size-thead: 13px; /* [ours] table head, 800 */
--line-height-thead: 20px; /* [ours] table head */
--font-size-caption: 13px; /* [ours] code badges, captions */
--line-height-caption: 18px; /* [ours] captions */
--font-weight-regular: 400; /* [css] body */
--font-weight-semibold: 600; /* [css] subheads */
--font-weight-bold: 700; /* [css] buttons, nav */
--font-weight-thead: 800; /* [css] headings (extrabold file) */
/* Radius */
--radius-sm: 4px; /* [css] cards, inputs, code (--cno-border-radius) */
--radius-btn-sm: 15px; /* [css] small pill button (.9375rem) */
--radius-btn-md: 20px; /* [css] medium pill button (1.25rem) */
--radius-btn-lg: 24px; /* [css] large pill button (1.5rem) */
--radius-round: 50%; /* [ours] icon buttons, dots */
/* Spacing */
--space-1: 4px; /* [css] Tailwind 1 */
--space-2: 8px; /* [css] Tailwind 2 */
--space-3: 12px; /* [css] Tailwind 3 */
--space-4: 16px; /* [css] Tailwind 4 */
--space-5: 20px; /* [css] Tailwind 5 (footer side padding) */
--space-6: 24px; /* [css] Tailwind 6 (paragraph margin 1.5rem) */
--space-8: 32px; /* [css] Tailwind 8 */
--space-10: 40px; /* [css] Tailwind 10 */
--page-padding: 40px; /* [ours] main area padding */
--card-padding: 24px; /* [ours] card padding */
/* Elevation */
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.12); /* [ours] the site uses almost no shadows; resting card (admin only) */
--shadow-8: 0 6px 16px rgba(0, 0, 0, 0.16); /* [ours] menus and hovered tiles */
--shadow-24: 0 16px 40px rgba(0, 0, 0, 0.3); /* [ours] dialogs */
/* Motion */
--duration-hover: 150ms; /* [css] --cno-transition-speed-default: colour and background fades */
--duration-menu-item: 150ms; /* [css] same as hover */
--ease-standard: ease-in-out; /* [css] transition timing on links and buttons */
--transition-focus: 150ms ease-in-out; /* [css] focus and underline transitions */
/* Layout */
--sidebar-width: 240px; /* [ours] admin left nav */
--content-max-width: 1290px; /* [css] --cno-main-content-max-width-wide (80.625rem) */
--article-max-width: 790px; /* [css] article column (max-w-search_result) */
--nav-indicator-height: 4px; /* [ours] active item bar */
--header-height: 200px; /* [css] site header, collapsed (12.5rem) */
--footer-padding: 20px; /* [css] footer side padding (px-5) */
}
```
# Components
Specs for the CNO admin components. Each lists the values; build with the tokens in `design-tokens.md`.
## Admin shell
Black left nav with the white CNO logo, the uppercase section links and a content area. Collapses to a top bar below 720px.
| Property | Value |
| --- | --- |
| Sidebar | fill var(--color-ink), width var(--sidebar-width), sticky full height; logo 160px wide, padding 24px |
| Item | var(--font-size-nav) / 700 uppercase, padding 12px 24px, hover white at 12%, 150ms |
| Active item | white at 18% fill and a 4px bar (var(--nav-indicator-height)) in var(--color-accent) |
| Main | padding var(--page-padding); on the white pages the main area is var(--color-surface) |
| Mobile | at 720px the sidebar becomes a 64px top bar; the arrow button opens the menu |
```css
.side{width:var(--sidebar-width);background:var(--color-ink);color:var(--color-text-on-dark)}
.side nav a{padding:12px 24px;font:700 var(--font-size-nav)/var(--line-height-nav) var(--font-family);text-transform:uppercase}
.side nav a[aria-current=page]{box-shadow:inset var(--nav-indicator-height) 0 0 var(--color-accent)}
```
## Page title and section rule
The site's heading style: extra bold, with a hairline rule under the title and under each section header.
| Property | Value |
| --- | --- |
| Title | var(--font-size-title) / var(--line-height-title), weight 800; var(--font-size-display) from the wide breakpoint on the site |
| Rule | 1px solid var(--color-border) with 12px padding above it (the site's front-page section headers use border-bottom gray-950) |
| Lead | sans (var(--font-family)) at var(--font-size-md); the admin uses no serif |
```css
h1{font:800 var(--font-size-title)/var(--line-height-title) var(--font-family);padding-bottom:12px;border-bottom:1px solid var(--color-border)}
```
## Buttons
The site's pill buttons (.cno-button): bold 16px text on a fully rounded fill. Subscribe orange is the one loud button.
| Property | Value |
| --- | --- |
| Large | height 44px, padding 0 28px, 18px / 700, radius var(--radius-btn-lg) |
| Medium | height 40px, padding 0 24px, 16px / 700, radius var(--radius-btn-md) |
| Small | height 32px, padding 0 20px, 16px / 700, radius var(--radius-btn-sm) |
| Fill | Subscribe: var(--color-accent) (public site only). The admin has no filled buttons: its actions are icon links (next component) |
| Motion | color and background fade 150ms ease-in-out (var(--transition-focus)) |
| Disabled | var(--color-disabled-bg) fill, var(--color-disabled-text) text |
```css
.cno-button{display:inline-block;height:40px;padding:0 24px;border:0;border-radius:var(--radius-btn-md);
background:var(--color-accent);color:#fff;font:700 var(--font-size-md)/40px var(--font-family);
transition:background-color var(--duration-hover) var(--ease-standard)}
.cno-button:hover{background:var(--color-ink)}
```
## Icon link
The admin's quiet action: a text link with a 18px icon, no fill or border. Used for Open the page, Copy link, Back to all pages and Open developer spec.
| Property | Value |
| --- | --- |
| Text | var(--font-size-md) / 600, var(--color-primary), no underline at rest |
| Icon | 18px Material icon in currentColor, 4px from the text; leading for navigation back / open, trailing for forward |
| Hover | var(--color-primary-hover) with a 2px-offset underline, 150ms |
| Focus | 2px outline var(--color-primary) |
| Where | cno-admin/admin/admin.css (.ilink) |
```css
.ilink{display:inline-flex;align-items:center;gap:var(--space-1);color:var(--color-primary);font:600 var(--font-size-md)/var(--line-height-md) var(--font-family)}
.ilink svg{width:18px;height:18px;fill:currentColor}
.ilink:hover{color:var(--color-primary-hover);text-decoration:underline;text-underline-offset:2px}
```
## Links
Public links are light blue and darken on hover; the admin tables use the dark blue with a faint underline.
| Property | Value |
| --- | --- |
| Site link | var(--color-link), no underline, hover var(--color-primary), 150ms (a{color:var(--cno-blue)}) |
| Admin link | var(--color-primary), underline var(--color-link-underline), full colour underline on hover |
```css
a{color:var(--color-link);text-decoration:none;transition:color var(--duration-hover) var(--ease-standard)}
a:hover{color:var(--color-primary)}
```
## Card and tiles
Flat white card with a hairline border. The admin home tiles lift to elevation 8 on hover.
| Property | Value |
| --- | --- |
| Card | var(--color-surface), 1px var(--color-border-light) border, radius var(--radius-sm), padding var(--card-padding) |
| Tile title | var(--font-size-lg) / 700 in var(--color-primary), hover var(--color-primary-hover) |
| Hover | var(--shadow-8), 150ms |
```css
.card{background:var(--color-surface);border:1px solid var(--color-border-light);border-radius:var(--radius-sm);padding:var(--card-padding)}
```
## List table with developer row
The Public page list: one row per page, a twisty that opens a dark-on-grey developer box with the files, tokens, AI files and skill, and an inline code viewer.
| Property | Value |
| --- | --- |
| Head cell | var(--font-size-thead) / 800 uppercase, 1px var(--color-border-light) rule |
| Body cell | var(--font-size-table), padding 8px, 51px rows |
| Twisty | 40px round button, Material ChevronRight 24px, turns 90deg while open (150ms) |
| Developer box | var(--color-page-bg), 1px var(--color-border), radius var(--radius-sm), padding 16px |
| Code viewer | opens inside the box: header with file name, copy and close; 12px monospace; max height 360px |
```css
table{border-collapse:collapse;width:100%}
th{font:800 var(--font-size-thead)/var(--line-height-thead) var(--font-family);text-transform:uppercase}
td{padding:8px;font-size:var(--font-size-table);border-bottom:1px solid var(--color-border-light)}
```
## Status chips
Small pill labels for state. Colour is the meaning; light fill with dark text.
| Property | Value |
| --- | --- |
| Shape | height 24px, padding 0 12px, radius var(--radius-btn-sm), 13px / 700 |
| Success | var(--color-success-bg) fill, var(--color-success-text) text |
| Warning | var(--color-warning-bg) fill, var(--color-ink) text |
| Error | var(--color-danger-bg) fill, var(--color-danger-text) text |
```css
.chip{display:inline-flex;align-items:center;height:24px;padding:0 12px;border-radius:var(--radius-btn-sm);font:700 13px var(--font-family)}
.chip--ok{background:var(--color-success-bg);color:var(--color-success-text)}
```
## Gift pill (share full article)
The site's small outlined pill button with a gift icon. It opens the Gift Articles popover. Shown only on paywalled articles to subscribers.
| Property | Value |
| --- | --- |
| Size | height 2rem, padding 0 1rem 0 .75rem, radius .9375rem (the site's .cno-button--small), gap .5rem; icon 1.125rem in currentColor |
| Text | ProximaNova 700, 1rem / 2rem, colour var(--cno-gray-100), no wrap |
| Border and fill | 1px solid var(--cno-gray-100), transparent; hover and open fill var(--cno-gray-1100) |
| Motion | color and background-color 150ms ease-in-out (var(--cno-transition-speed-default)) |
| Focus | 2px outline var(--cno-blue-dark), offset 2px |
| Phones | below 640px the label is hidden and the pill becomes a 1.875rem round icon button |
| Visibility | only when the article is paywalled and the viewer is a subscriber |
| Where | cno-article-share/share.css (.cno-pill) and share.js |
```css
.cno-pill{display:inline-flex;align-items:center;gap:.5rem;height:2rem;padding:0 1rem 0 .75rem;border:1px solid var(--cno-gray-100);
border-radius:.9375rem;background:transparent;color:var(--cno-gray-100);font:700 1rem/2rem var(--cno-base-font-family);
transition:color var(--cno-transition-speed-default) ease-in-out,background-color var(--cno-transition-speed-default) ease-in-out}
.cno-pill:hover,.cno-pill[aria-expanded=true]{background:var(--cno-gray-1100)}
```
## Share icon and popover
One round share icon replaces the icon row (option A). Hover or tap opens a popover with the share options. The gift pill opens the same popover with the gift URL.
| Property | Value |
| --- | --- |
| Icon button | 1.875rem round, fill var(--cno-gray-900), icon 1rem in var(--cno-gray-100) (the same disc as .social-link in .share-links--gray-black); hover and open fill var(--cno-gray-800) |
| Popover box | white, 1px solid var(--cno-gray-950), radius var(--cno-border-radius), padding 1rem 1.25rem .5rem, width 18rem (max 100vw - 2.5rem), shadow 0 .25rem .75rem rgba(0,0,0,.12) (the one shadow; the site has none), z-index 40 |
| Position | 0.625rem below the button, left-aligned; shifted left to stay 12px inside the viewport; a .75rem caret follows the button centre |
| Title and text | title ProximaNova 800 1rem / 1.5rem; text 400 1rem / 1.5rem in var(--cno-gray-500), .75rem below |
| Rows | flex, gap .75rem, padding .5rem 0, 600 1rem / 1.5rem, separated by 1px var(--cno-gray-1000); hover text var(--cno-blue-dark); 1.875rem grey disc icon (var(--cno-gray-900) fill, .9rem icon) |
| Open / close | hover (desktop) opens after 120ms and closes 250ms after leaving unless pinned by a click; click or tap toggles; Esc and outside click close; one open at a time |
| Phones | below 640px the share icon calls navigator.share({title, url}) and falls back to this popover |
| Where | cno-article-share/share.css (.cno-iconbtn, .cno-pop) and share.js |
```css
.cno-pop{position:absolute;top:calc(100% + .625rem);left:0;z-index:40;width:18rem;max-width:calc(100vw - 2.5rem);
padding:1rem 1.25rem .5rem;background:var(--cno-white);border:1px solid var(--cno-gray-950);
border-radius:var(--cno-border-radius);box-shadow:0 .25rem .75rem rgba(0,0,0,.12)}
.cno-pop__row{display:flex;align-items:center;gap:.75rem;padding:.5rem 0;font:600 1rem/1.5rem var(--cno-base-font-family)}
```
## Toast
Short confirmation, used for Link copied.
| Property | Value |
| --- | --- |
| Box | fixed, centred, 1.5rem from the bottom, padding .5rem 1.25rem, radius 1.25rem, fill var(--cno-gray-100), white text 700 1rem / 1.5rem |
| Motion | fades and rises 1rem over var(--cno-transition-speed-default); visible for 1.8s; role=status, aria-live=polite |
```css
.cno-toast{position:fixed;left:50%;bottom:1.5rem;padding:.5rem 1.25rem;border-radius:1.25rem;background:var(--cno-gray-100);color:var(--cno-white);font:700 1rem/1.5rem var(--cno-base-font-family)}
```
## Footer
The site footer is a light grey band (gray-1100) with generous padding.
| Property | Value |
| --- | --- |
| Bar | var(--color-surface-alt), padding 60px var(--footer-padding) 120px on the site |
| Text | var(--font-size-md) |
```css
footer{background:var(--color-surface-alt);padding:60px var(--footer-padding) 120px}
```