Noir Book Design System

The complete visual language for the UX Design Library. Every token, component, and rule in one living reference. This page uses the same shared.css, what you see is what the site renders.

Rules

Four rules are non-negotiable. They keep Noir Book legible, calm, and readable for people with eye trouble. Break any of them and the design-system guardrail or an accessibility review will catch it.

1. 15px is the content floor, 13px the nav chrome floor

Content (anything someone reads, card descriptions, body paragraphs, captions, labels) never renders below 15px. Nav chrome (nav links, breadcrumbs, topic-number codes, tier-counts, filter buttons, view toggles) can drop to 13px via --fs-nano, paired with a semibold or bolder weight to keep small text firm. Nothing anywhere goes below 13px. The --fs-* token scale is the whole vocabulary, hardcoded sizes are banned and caught by review.

2. High contrast by default, no light grey in text

Body copy runs at --ink-soft (#1F1C18, near-black on cream). Secondary copy and captions use --ink-medium (#3A362F), which is also the floor for any muted text. The legacy --ink-muted and --ink-light tokens are aliased to the same value so older CSS cannot re-introduce light grey by accident. --ink-faint is reserved for decorative non-text elements (icons, dashed borders). --ink-hairline is for dividers and card borders only, never for type. If you need a muted caption, use --ink-medium. Never lower the contrast; darken the background instead.

3. Weight builds hierarchy, thin is reserved for display

Default body weight is --fw-regular (400). Secondary and UI chrome use --fw-medium (500) or --fw-semibold (600) for firmness at small sizes. --fw-bold (700) is for emphasis inside titles via <strong>. --fw-thin (200) is reserved for display h1 and h2 where it pairs with a bold <strong> to create the signature weight-contrast look. Do not use thin on body copy, captions, or small UI, it disappears at low weight.

4. Animation signals interactivity

A hover lift, a drawn outline, or any transform belongs only on an element the user can actually click or select. On the homepage, topic cards lift and draw a full copper outline because they're links. Static display surfaces on topic pages, method cards, case studies, callouts, template boxes, at-a-glance blocks, pitfalls, are inert. They keep their copper corner strip at rest but do not animate on hover. If you add a hover effect to a new component, ask whether clicking it does something. If not, the hover comes off.

Typography

Two typefaces, one with attitude. Fira Sans carries both display and body roles, with weight contrast (thin 200 for editorial restraint, bold 700 for emphasis) doing the work a serif used to. JetBrains Mono handles code and the topic-number codes. Unifying on Fira means every page reads as one voice, with refinement coming from weight and spacing rather than typeface switching [commas, no em-dashes].

Display & Body — Fira Sans
The quick brown fox jumps
Headings, hero, topic numbers, card titles, nav, body, UI labels
Fira Sans is a humanist sans with a wide weight range. Use weight 200 (thin) for hero and section titles, weight 300 (light) for long-form body and subtitles, 500-600 for UI elements and small caps, and 700 (bold) for emphasis inside titles via <strong>. The weight gap is what carries the typographic identity.
200 Thin 300 Light 400 Regular 500 Medium 600 Semi 700 Bold
Mono — JetBrains Mono
font-family: var(--font-mono);
Used for code snippets, technical references, topic numbering in cross-reference cards, and the design system's own token names. Tabular figures for aligned numbers.

Type scale (--fs-* tokens)

The whole library renders through this scale. To shrink or grow everything, edit --fs-* values in shared.css :root, one line per role. Hardcoded sizes are banned and caught by review. 15px is the floor.

Every method
--fs-hero
clamp(36, 6vw, 56) / 200 thin
Homepage hero h1
Topic Title
--fs-h1
clamp(32, 5vw, 48) / 200
Topic page, request, ds-hero
Section Heading
--fs-h2
28px / 200
h2, tier headers
Subsection
--fs-h3
22px / 600
h3, section subtitles
Card & Component Title
--fs-h4
18px / 600
h4, card titles
Subtitles, hero subs, emphasis paragraphs at the body-large size.
--fs-body-lg
17px / 300
subtitles
Standard body copy for topic content and long-form reading. 16px with 1.8 line-height for comfort.
--fs-body
16px / 300
body copy, topic-content p
The 15px floor. Topic descriptions, captions, labels, tier counts, meta, nav links. Nothing in the library renders smaller than this.
--fs-micro
15px / 300
labels, captions, UI (floor)
SECTION LABEL · BADGE TEXT · NAV LINKS
--fs-micro
15px / 600 uppercase
section labels, badges
01
52px / 400 Playfair
Card numbers (decorative)
28
36px / 700 Playfair
Stat figures

Typography rules

DoUse Fira Sans everywhere. Carry hierarchy with weight (200 thin for hero / section titles, 300 light for body, 500-600 for UI, 700 for emphasis inside titles). JetBrains Mono only for the topic-number codes and the token names in this design system.
Don'tReach for a second display face to add variety. The weight gap is the variety. Don't use 400 regular for hero [it reads flat], and don't use weight 700 for body copy [it loses the refinement].

Colour

A warm, neutral palette inspired by ink on aged paper. Colours are defined as CSS custom properties and adapt automatically in dark mode. All text colours meet WCAG AA contrast requirements against their intended backgrounds.

Paper tones (backgrounds)

Use --paper for the page. --paper-warm for cards, hover states, expandable headers. --paper-deep for deeper UI wells. --paper-white for inputs and template boxes.

--paper
Light #F5F0E8 / Dark #28231E
Page background
--paper-warm
Light #F2EDE5 / Dark #161A18
Card surface, method card bg, tier groups, expandable headers
--paper-deep
Light #E8DFD0 / Dark #1F2421
Topic-card hover, expandable open state, deep UI wells
--paper-white
Light #FDFBF4 / Dark #161A18
Search input, template box bg, expandable content, search dropdown

Ink tones (text & borders)

Text hierarchy flows from --ink (headings) through --ink-soft (body strong) and --ink-medium (body) to --ink-muted (captions). All text tokens pass WCAG AA on --paper. Unlike v49, --ink is never used as a surface, the nav flips with theme now, so the ink stack is purely for text and icons.

--ink
Light #1A1815 / Dark #EEF0EA
Headings, strong text
--ink-soft
Light #3D3A35 / Dark #D6DAD2
Body copy strong, method-card descriptions
--ink-medium
Light #5A564E / Dark #C8D0CC
Standard body, subtitles, nav links, topic-card descriptions
--ink-muted
Light #7A736A / Dark #A0ACA5
Captions, footnotes, footer copy
--ink-light
Light #8A8074 / Dark #8E958E
Placeholder text, TOC inactive items, breadcrumb separators
--ink-faint
Light #A0958A / Dark #7A7F77
Subtlest inactive state, dashed template field borders
--ink-hairline
Light #DDD6C8 / Dark #2A302D
Hairline dividers, card borders, nav border, TOC rail

Copper — the one accent

v50 drops every legacy accent (rust, teal, navy, plum, gold) in favor of a single copper family. Copper covers links, hero emphasis, CTAs, card left-strips, nav active state, reading progress, check marks, bookmarks, source labels. Three intensities cover the range: --ink for Tier 1 and warnings, --ink for most UI, --ink for solid CTAs and larger fills, --ink reserved for use on dark surfaces.

--ink
Light #6E3D20 / Dark #B87548
Tier 1 badge, pitfall icon, warning callout strip, "essential" labels
--ink
Light #A06A42 / Dark #D4956A
Links, hero em, card left-strip, filter-tab active (outline), labels, nav hover, checkmark, bookmark
--ink
Light #C0825A / Dark #D4956A
Logo-mark bg, filter-tab active fill, submit button, process-step numbers, reading progress bar
--ink
Light #D4956A / Dark #E0A57A
Reserved for use on dark surfaces; automatic fallback in dark-mode for --ink

Discipline pigments

The library's four disciplines need distinct categorical signal, but not at the cost of the one-accent rule. So they're desaturated earth tones, each 40-60% saturation, all warm enough to live next to copper without fighting it. Each discipline has a matching -bg token at ~8% alpha for very faint card tints.

--disc-ux
Light #4B7260 / Dark #6FA084
UX Design pill, UX disc dot, UX topic-number prefix
--disc-pm
Light #8F4A2A / Dark #C88158
Product pill, PM disc dot, PM topic-number prefix, NN/g source marker
--disc-mk
Light #4F5E72 / Dark #7B8AA4
Marketing pill, MK disc dot, MK topic-number prefix
--disc-cs
Light #7E4863 / Dark #B07490
Content pill, CS disc dot, CS topic-number prefix

Tier & content-type bands

Tier and content-type (theory / practical / examples) are mapped to three intensities of copper, not to hue. Tier 1 reads darker (more essential), Tier 4 fades to a warm neutral. This collapses 4+3 categorical colors to a single tonal progression.

--tier1-color
= --ink
Tier 1 badge (most essential)
--tier2-color
= --ink
Tier 2 badge
--tier3-color
= --ink
Tier 3 badge
--tier4-color
= --ink-medium
Tier 4 badge (supplementary, neutral)

Legacy aliases

Old library accent names survive as aliases so component CSS written against v49 still resolves. --rust = --disc-pm (burnt copper), --teal = --disc-ux (emerald), --navy = --disc-mk (slate), --plum = --disc-cs (mauve), --gold = --ink. No legacy accent introduces a new hue, they're all rewired onto the palette above.

Dark mode philosophy

Dark mode is the career-site dark section, treated as the opposite of the cream section. Paper flips to #0D1210 (near-black with green undertone), surface to #161A18, text to #EEF0EA and #C8D0CC, hairline to #2A302D. Copper shifts up to the bright variant. Discipline pigments shift up ~20% luminance to stay legible on deep dark. Shadows deepen to pure black with higher alpha.

Borders, Radius & Shadow

Editorial structure first, soft modern affordances second. Most editorial surfaces (cards, callouts, templates) keep sharp ink-on-paper edges. Interactive controls and overlays (search inputs, dropdowns, toasts, theme toggle, burger panel) use a small radius scale and a discreet shadow scale to read as tappable.

Border weights

WeightValueUse
Standard1px solid --inkMethod cards, template boxes, expandables, case studies, topic header bottom, deep-dive separator
Accent1px solid [color]Tier badges, split badges, callouts, NN/g pill, case study borders
Faint1px solid --ink-fainth2 and h3 border-bottom, template header divider, inactive filter pills
Hairline1px solid --ink-hairlineChecklist separators, pitfall dividers, search result dividers, scrolled nav border
Heavy left3px solid --inkBurger panel left edge
Dashed2px dashed --ink-faintTemplate fields (fill-in areas)

Radius scale

Editorial surfaces (cards, method blocks, callouts, badges) stay square. Radius is reserved for interactive controls, overlays, and pills. Use a token; don't invent intermediate values.

TokenValueUse
--radius-xs4pxTier badges, logo mark, zero-suggest CTA
--radius-sm6pxSection result rows, small chip surfaces
--radius-md8pxTheme toggle, burger button, view toggles
--radius-lg10pxSearch inputs, search dropdown, nav-search wrap
--radius-xl12pxTier groups, search result blocks, collapsed tier headers
--radius-pill999pxDiscipline pills, filter pills, footer pills, search phrase pills, toast

Shadow scale

Shadows are warm and low-intensity in light mode (rgba of --ink) and pure black in dark mode for legibility. The --shadow-* tokens auto-flip across themes; consumers always reference the token, never a raw rgba().

TokenUse
--shadow-smSubtle lift, reserved (available for new components)
--shadow-mdScrolled home-nav drop shadow
--shadow-lgTopic-card hover lift
--shadow-focusSearch input focus, burger search input focus
--shadow-popoverSearch dropdown popover
--scrimBurger overlay backdrop (rgba scrim, not box-shadow)

Z-index scale

All stacking contexts use the --z-* scale so layering decisions live in one place. New floating UI should reuse an existing token, not invent a number.

TokenValueUse
--z-back-to-top90Back-to-top button on topic pages
--z-progress99Reading progress bar under nav
--z-nav100Sticky site-nav
--z-search200Search dropdown popover
--z-toast300Filter feedback toast
--z-burger-overlay300Burger menu scrim
--z-burger-panel301Burger menu panel (above its own scrim)

Key rules

DoEditorial cards (method, template, case study, expandable, callouts, tier groups) keep square corners. Use radius tokens only on inputs, controls, overlays, and pills. Use shadow tokens for elevation, never raw rgba.
Don'tNever invent a one-off radius or rgba shadow. If the new use case doesn't fit a token, propose a new token rather than hard-coding a value.

Spacing & Layout

Spacing follows a 4px-based scale exposed as --space-* tokens. Layout constraints (page width, content measure, sidebar, nav height) are exposed as named layout tokens.

Spacing scale

Use a token, not a magic number. The scale is denser at the small end (where eye-distance differences matter most) and sparser at the large end. --space-7: 28px exists because the editorial card padding has historically been 28px; otherwise prefer 24 or 32.

TokenValueTypical use
--space-14pxPill remove icon padding, search dropdown margin-top
--space-28pxTOC link padding, badge gap, pitfall icon gap
--space-312pxCross-ref gap, tier group internal gaps
--space-416pxContainer mobile padding, callout title bottom-margin, list padding
--space-520pxTier group padding, search dropdown padding
--space-624pxComponent vertical margin, callout block padding
--space-728pxMethod card / template box internal padding (legacy editorial size)
--space-832pxv48 method card / case study padding (comfort pass)
--space-1040pxContainer desktop padding, nav-inner padding
--space-1248pxHero padding-bottom, footer padding
--space-1456pxTopic-page section spacing (v48 comfort pass)
--space-1664pxTopic layout sidebar gap, h2 top margin in topics, hero padding-top
--space-2496pxDeep-dive section margin-top, site-footer margin-top

Layout widths

TokenValueUse
--max-width1140pxPage container max-width (.container, .nav-inner)
--content-width680pxMax-width on body paragraphs (prevents overly long measure)
--sidebar-width200pxTOC sidebar on topic pages (desktop only)
--nav-height72pxSticky nav height; used for sticky TOC offset and reading-progress top

Grid system

Homepage tier groups use a 3-column topic grid (compact view) or 2-column (full view), wrapped in a rounded .tier-group container with --paper-warm background and --radius-xl. Topic cards inside a tier group are bordered with hairline dividers (1px --ink-hairline) on right and bottom edges.

Topic pages use a 2-column grid: var(--sidebar-width) 1fr with align-items: start. The sidebar is position: sticky at top: calc(var(--nav-height) + var(--space-8)).

Components

Every reusable element in the library. Components reference design tokens for all colour, shadow, radius, spacing, and z-index values. The build script verifies this on each build and warns on any raw colour literals introduced outside the :root token blocks.

Callouts

Use .callout.tip for practical advice, .callout.warning for common mistakes, .callout.info for context or decision guidance. Always include a .callout-title. Callouts use a full 2px outline in the accent colour — never a single-side border.

Practical tip

This is a tip callout. Use it for actionable advice the reader can apply immediately.

Common mistake

This is a warning callout. Use it to flag pitfalls and errors to avoid.

Context

This is an info callout. Use it for background context or decision guidance.

<div class="callout tip"> <div class="callout-title">Practical tip</div> <p>Your advice here.</p> </div>

Badges

Tier badges identify priority level. Split badges show the theory/practical/examples content ratio. Both use border: 2px solid box style — never border-left.

Tier 1 Tier 2 Tier 3 Tier 4
25% Theory 50% Practical 25% Examples

Method card

Used in topic pages to describe specific methods or techniques. Warm background, ink border, with an optional method tag (inverted ink label).

User Interviews Core Method

Use when: Starting any project, validating assumptions, exploring a problem space.

One-on-one conversations with real or potential users to uncover what matters to people and why.

Case study

Highlights real-world examples. Uses rust accent colour to distinguish from method cards.

Case Study

Airbnb — Rebuilding trust through design

How Airbnb used design thinking to transform their service after a period of declining bookings.

Checklist (interactive)

Click items to toggle checked/unchecked state. Visual only — state does not persist across page loads. Designed for screenshot-based note-taking during project work.

  • Define the problem statement clearly
  • Identify key stakeholders to interview
  • Prepare interview guide with open questions
  • Schedule and conduct at least 5 interviews

Expandable section

Used for deep-dive appendix content and optional detail. Warm header, white content area, chevron indicator.

DoChevrons are stroked SVG polylines with no fill — two lines meeting at a point. Consistent across homepage collapse chevrons and expandable section chevrons. Use stroke-linecap: round and stroke-linejoin: round.
Don'tNever use filled triangles (▶ ▶) or solid caret characters for chevrons. No filled shapes — chevrons are always open stroked lines.
DoChecklist ticks use CSS-drawn straight lines (border-right + border-bottom, rotated) with border-radius for rounded ends. Tick sits visually centred-low within the checkbox.
Don'tNever use Unicode tick characters (✓ ✔) or font glyphs for checkmarks. They render inconsistently across platforms and can't be precisely positioned.

Template box

Fillable templates with dashed-border fields. Uses --paper-white background to stand out from the page.

Template Problem Statement
User
Who is experiencing this problem?
Need
What do they need to accomplish?

Process steps

Numbered steps with 32px ink-black squares and 20px Playfair Display numbers inside, nudged up 3px for optical centering. Used for sequential processes.

Empathize

Understand the user's world through observation and interviews.

Define

Synthesize findings into a clear problem statement.

Cross-reference cards

Link to related topics at the bottom of each topic page. Hairline border, hover reveals ink border and warm background.

Pitfall

Used in "Common pitfalls" sections. Icon + text layout with .pitfall container, .pitfall-icon for the exclamation mark, and a nested div for the heading and description.

<div class="pitfall"> <div class="pitfall-icon">!</div> <div> <h4>Pitfall title</h4> <p>Description of what goes wrong and how to avoid it.</p> </div> </div>

At a Glance

Overview block in the topic header, below content-splits. Gives readers a quick scan of what the topic covers before they commit to reading. Uses .at-a-glance with full 2px ink border. Items are color-coded by content type using data-type attributes (theory/practical/examples). Grid collapses from 2-col to 1-col at ≤960px.

At a glance Overview
  • Core concepts and principles
  • Methods, tools, and templates
  • Step-by-step workflows
  • Real-world examples and case studies
<div class="at-a-glance"> <div class="at-a-glance-title">At a glance <span>Overview</span></div> <ul> <li data-type="theory">...</li> <li data-type="practical">...</li> <li data-type="examples">...</li> </ul> </div>

UX Patterns

Interactive behaviours and navigation patterns used across the site.

Navigation

Sticky .site-nav at top, --nav-height (72px) tall, --ink background, no bottom border at rest. Logo uses Playfair Display 900 with a 40px --paper square mark (radius --radius-xs) and margin-right: auto. Nav links, discipline pills, search, theme toggle and burger button cluster on the right with --space-6 gap. Nav links are 11px uppercase Outfit 600 in --paper-deep. Theme toggle and burger button are 40px squares with --radius-md.

Subpage variant: the .subpage-nav modifier shows a smaller logo (32px mark), reveals the .nav-back-link and .nav-title, and reflows the right cluster (search, theme toggle, burger). On mobile, the search input expands when focused and the nav-title fades out to give it room (.search-expanded state).

Reading progress

A 3px --rust line fixed directly under the nav bar. Width updates on scroll from 0% to 100%. Topic pages only.

Sticky TOC sidebar

The .topic-sidebar element is position: sticky with align-self: start. Coloured dots (7px circles) indicate section type — gold for theory, teal for practical, plum for examples. Active item gets an ink-coloured left border and bold weight via scrollspy.

Search

Available on all pages. Two placements: Homepage — full-width input in the filter bar, 2px ink border, 14px Outfit, placeholder "Search topics, methods, frameworks...". Topic pages — 220px input in the nav bar, same styling.

Behaviour: Typing 2+ characters opens a dropdown grouped by topic. Each result row shows a title and a type badge (theory / practical / examples / method). Maximum 8 topic groups displayed. Fuzzy matching via Levenshtein distance (threshold ≤1) catches typos on queries of 3+ characters. Dropdown closes on click outside, Escape key, or result selection.

Homepage dual sync: The filter bar search (#searchInput) and the scrolled nav search (#navSearchInput) stay synced — typing in either updates both. The filter bar input drives filtering; the nav input is a convenience duplicate visible after scrolling past 120px.

Search index: Built by build.js from content file metadata. Lives in js/search.js as SEARCH_INDEX — an array of topic objects, each containing topic (title), url, discipline, sections[] (with id, title, type, keywords), and methods[] (with name, section, keywords). Search matches against topic titles, section titles + keywords, and method names + keywords.

Clear button: An × button appears inside the search box (absolute positioned, right) when the input has a value. Class .has-query on the .search-box wrapper controls visibility.

Safety: All user input rendered in dropdown "no results" messages is escaped via escapeHtml() to prevent HTML injection.

<div class="search-box"> <input type="text" id="searchInput" placeholder="Search topics, methods, frameworks..." autocomplete="off" oninput="syncSearch(this)"> <button class="search-clear" onclick="clearSearch()">&times;</button> <div class="search-dropdown" id="searchDropdown"></div> </div>

Homepage filtering

Discipline toggles — four buttons in a row (UX Design, Product Management, Marketing & Growth, Content Strategy). Each has a coloured dot and a dynamically computed count. Active state uses the discipline colour for border and text; inactive state is 50% opacity with faint border. Single click toggles one discipline on/off. Double-click (within 400ms) isolates that discipline — or re-enables all if it was already isolated. First single click shows a hint toast.

Tier filter — text-link buttons: All, Core toolkit (Tier 1), Specialized (Tier 2), Situational (Tier 3), Reference (Tier 4). Active state is bold with ink underline. Only one tier active at a time. "All" shows every tier.

Sort — two text buttons (Discipline, Tier). Active sort is bold ink. A chevron SVG (▲ / ▼) indicates direction. Clicking the active sort reverses direction. When sorting by discipline, topic cards within each tier grid are reordered (UX → PM → MK → CS) and separator rows are inserted between disciplines. When sorting by tier, the tier <section> elements are reordered in the DOM.

Show filters — Connected, NN/g, Bookmarked

Three toggle buttons in the controls row. Order: Connected, NN/g, Bookmarked. Each acts as an additive filter — when active, only matching cards are shown. Multiple filters can be active simultaneously (AND logic: a card must match all active filters). Default: all off (show everything).

Connected — shows topics that span multiple disciplines. Styled with teal border and teal background tint when active. A card matches when it has a data-cross attribute (listing the other disciplines it connects to, e.g. data-cross="pm mk"). Connected topics also display small discipline badges (e.g. PM, MK) in the card's top-right corner. These are auto-generated from the cross_discipline field in the content file's MANIFEST block.

NN/g — shows topics sourced from or enriched by Nielsen Norman Group material. Styled with orange border (--nng) and orange background tint when active. A card matches when it has data-sources="nng". This attribute is built from the sources field in the MANIFEST block.

Bookmarked — shows topics the user has bookmarked. Styled with gold border and gold background tint when active. Bookmarks are stored in localStorage under key bb_bookmarks as a JSON object keyed by topic number (e.g. {"UX.1.01": true}). A bookmark icon (SVG bookmark path, 16×20px) appears on each topic card — invisible at rest, visible on hover, always visible when bookmarked (gold fill).

<!-- Filter button markup --> <button class="filter-btn" data-filter="connected" onclick="toggleFilter('connected',this)">Connected</button> <button class="filter-btn" data-filter="nng" onclick="toggleFilter('nng',this)">NN/g</button> <button class="filter-btn" data-filter="bookmarked" onclick="toggleFilter('bookmarked',this)">Bookmarked</button> <!-- Topic card with cross-discipline + source data --> <a class="topic-card" data-discipline="ux" data-cross="pm mk" data-sources="nng">...</a>
DoShow contextual zero-results messaging when filters yield nothing: "No bookmarked topics" with bookmark guidance, "No NN/g-sourced topics" explaining the source, "No cross-discipline topics found" suggesting broader filters.
Don'tNever combine filter state with search state ambiguously. The "Clear all filters" button resets disciplines, tier, search, and show-filters together — never partially.

Filter persistence

Filter state (disciplines, tier, sort, sort direction, show-filters, search query, view mode) is saved to sessionStorage under key bb_filters on every change. Restored on page load so navigating to a topic and back preserves the user's filter context. Bookmarks use localStorage (persist across sessions). View mode (compact / full) is part of the session state.

Dark mode

Three-way toggle: system default, manual dark, manual light. Preference saved to localStorage. All colours adapt via CSS custom properties — no separate stylesheets. Toggle button in nav shows moon/sun icon.

Back to top

44px ink-black square, fixed bottom-right, appears after 400px scroll. Hover changes to --rust. Topic pages only.

Collapsible sections

Homepage tier groups can be collapsed and expanded. Add .collapsible-section to the section, .collapsible-header with onclick="toggleSection(this)" to the header, an SVG chevron inside .collapse-chevron, and wrap the body content in .collapsible-body. The chevron is a stroked polyline (no fill) that rotates -90° when collapsed. Filter tab clicks auto-expand matching collapsed sections.

<section class="tier-group collapsible-section"> <div class="tier-header collapsible-header" onclick="toggleSection(this)"> <h2>Section Title</h2> <span class="collapse-chevron"> <svg viewBox="0 0 14 14"><polyline points="3,5 7,9 11,5"/></svg> </span> </div> <div class="collapsible-body"> <!-- Section content --> </div> </section>

Burger menu (replaces Floating Mobile TOC)

At ≤960px the burger button (.burger-btn, 40px square, --radius-md) appears in the right of the nav. Clicking opens .burger-overlay (a --scrim backdrop at --z-burger-overlay) plus a slide-in .burger-panel from the right (max 320px wide / 85vw, --paper background, 3px ink left border, --z-burger-panel). The panel contains a header with close button, a search input (which uses --shadow-focus on focus), inline page links, and the current page's TOC if applicable. Closes on backdrop click, close button, Escape, or link click. The legacy .mobile-toc-btn is kept as display:none for back-compat.

Discipline pills (in nav)

Four .disc-toggle pills inside .nav-filters (UX, PM, MK, CS). At rest: outlined with --paper-deep, 0.5 opacity. Hover lifts the border to --paper. Active state uses the --disc-{ux|pm|mk|cs} tokens for border + text and --disc-{ux|pm|mk|cs}-bg for the wash. Because the nav background is --ink (which flips), the discipline tokens flip the opposite way: bright pastels in light mode, deep saturated tones in dark mode. A small .disc-dot uses the same token for the leading bullet.

Pills shape: --radius-pill, font 12px Outfit 600. Each pill includes a 2-letter .disc-code suffix (UX/PM/MK/CS) at 0.85 opacity so the pill maps to the topic-number prefix.

Search phrase pills + clear

Both the homepage filter-bar search and the topic-page nav search support phrase pills: typing a comma or pressing Enter commits the typed text as a removable pill chip and clears the input. Pills accumulate as the user refines a multi-term search, so each commit is preserved as a discrete filter token rather than a single concatenated string.

Pill markup is .search-phrase-pill (paper-warm background, ink-faint border, --radius-pill) with a small .pill-remove × button. A .search-clear button (×) appears on the right edge of the input when the box has any value (.has-query state on .search-box), clearing both the input and all committed pills.

In the nav-search variant (.nav-search-pills inside .nav-search-wrap), pills sit inline with the input behind a translucent white wash (the one intentional theme-invariant rgba in shared.css, since the nav background is always dark) and scroll horizontally if they overflow.

Source provenance markers

Any element with a data-source attribute renders a small uppercase label in the top-right corner via ::after, coloured --nng for NN/g sources. Targets: .method-card, .callout, .case-study, .template-box, .pitfall, and details.expandable. Section-level provenance (an entire <section data-source="…">) renders a visible inline .source-label next to the h2.

File Architecture

How the codebase is organised and where each concern lives.

library-v49/ ├── css/ │ ├── shared.css ← Design system: ALL tokens (colour, spacing, radius, │ │ shadow, z-index, layout, typography), ALL components │ ├── home.css ← Homepage layout (hero, tier groups, topic cards, filters) │ └── topic.css ← Topic page layout (header, sidebar, progress bar, comfort pass) ├── js/ │ ├── theme.js ← 3-way toggle (system / manual dark / manual light) + localStorage │ ├── search.js ← Generated SEARCH_INDEX + fuzzy matching + dropdown │ ├── checklist.js ← Click-to-toggle checklist items (in-page only) │ ├── mobile-toc.js ← Legacy floating TOC (kept for back-compat, hidden in v48) │ └── topic-nav.js ← v48: wires topic-page nav search + discipline pill behaviour ├── content/ ← Source fragments (what you edit) │ ├── _components.html ← Component markup cheat-sheet │ ├── _recipe.html ← Target line counts and section structure per tier │ └── *.html ← topic content fragments ├── topics/ ← Built output (generated by build.js, do NOT hand-edit) │ └── *.html ← complete topic pages ├── index.html ← Homepage (topic grid, 4 tiers, search, filters) ├── design-system.html ← This file ├── request-topic.html ← Suggest-a-topic form ├── build.js ← content/ → topics/, regenerates SEARCH_INDEX + manifest + README ├── library-manifest.json ← AI-readable routing table (full topic metadata) ├── README.md ← Agent instructions + human docs └── CHANGELOG.md ← Per-version changes

Separation of concerns

shared.css owns all visual tokens (colour, spacing, radius, shadow, z-index, layout, typography) and all component styles. Page CSS files (home.css, topic.css) own only layout-specific rules and the v48 comfort pass. Inline styles in index.html own homepage-only filter bar mechanics. To change the visual identity globally, edit only the :root token blocks in shared.css.

The SEARCH_INDEX in js/search.js is regenerated by build.js from each content file's SEARCH_INDEX block, and the same is true for library-manifest.json entries. Hand-editing either file risks being overwritten on the next build.

A token guardrail in build.js scans css/ on every build and warns on raw hex / rgba colour literals declared outside the :root token blocks. The intent is that shared.css's :root remains the single source of truth for colour and shadow values.

Responsive Design

Two breakpoints handle the transition from desktop to mobile. The site uses a mobile-first override approach — desktop styles are the default, with breakpoints scaling down.

Breakpoints

BreakpointWidthWhat changes
Tabletmax-width: 960pxHero collapses to single column. Topic cards switch from 3-column to 2-column grid. Topic sidebar (TOC) is hidden — replaced by floating "Contents" button with slide-up TOC panel. At-a-glance grid collapses to 1-col. Nav link gap tightens.
Mobilemax-width: 768pxNav links hidden, mobile back link appears. All grids collapse to single column. Type scale reduces. Container padding drops to 16px. Logo and theme toggle shrink.

Navigation on mobile

At ≤768px, .nav-links is hidden entirely. On topic pages, a .nav-back element (hidden on desktop) becomes visible to provide a way back to the homepage. This is a separate element from the nav-links back link — it lives outside the ul so it survives the display: none.

The logo shrinks to 1.2rem with a 32px mark. The theme toggle shrinks to 32px. These elements plus the back link must fit comfortably on a 375px screen.

Type scale changes

ElementDesktopMobile (≤768px)
Homepage h162px2rem (32px)
Homepage subtitle17px15px
Topic h13rem (48px)2rem (32px)
Topic subtitle1.125rem (18px)1rem (16px)
Card numbers52px36px
Logo1.5rem1.2rem

Grid behaviour

GridDesktopTablet (≤960px)Mobile (≤768px)
Hero1fr 1fr1fr1fr
Topic cards3 columns2 columns1 column
Pathway cards3 columns2 columns1 column
Topic layoutsidebar + contentcontent onlycontent only
Prev/next nav1fr 1fr1fr 1fr1fr
Cross-ref cardsauto-fill 200pxauto-fill 200px1fr

Rules for new topics

DoEvery new topic page must include: the .nav-back element in the nav for mobile access, an .at-a-glance block in the header with 4–6 items covering what the topic contains, a .mobile-toc-btn and .mobile-toc-overlay panel mirroring the sidebar TOC, and a <script src="../js/mobile-toc.js"> include. Always test components at 375px width before shipping. Use relative units (rem, %) for anything that should scale — hardcoded px for things that shouldn't (borders, small icons).
Don'tDon't add new fixed-width grids without a mobile override. Don't use padding over 16px on containers at mobile width. Don't hide navigation elements without providing a mobile alternative.

Accessibility

Contrast ratios and accessibility compliance for the Design System palette.

TokenLight ratioWCAG level
--ink on --paper15.7:1AAA — headings, strong text
--ink-soft on --paper10.2:1AAA — body text
--ink-muted on --paper4.7:1AA — nav links, labels, descriptions
--ink-light on --paper4.8:1AA — breadcrumbs, TOC links, status text
--ink-faint on --paper3.5:1AA Large — card numbers (52px), borders

All text-bearing tokens pass WCAG AA (4.5:1) for normal text, except --ink-faint which is used only for large decorative text (52px card numbers) and passes AA Large (3:1). Dark mode equivalents are also verified.