Design system

Style guide

Typography, color, and component standards for GuerillaType. The single source of truth for the look and feel.

Typography

Three families, each with a distinct job. Loaded once, used everywhere.

RoleFamilyWhereWhy
DisplayLora (serif)Headlines, hero titles, results-card numbers, blockquotesWarm, readable serif with a clean italic. Replaces Fraunces (whose italic "f" was too curly for my subtext).
BodyInter (sans)All running text, buttons, navigationDesigned for screens, neutral, holds up at all sizes.
MonoJetBrains MonoThe typing surface, tokens, kbd, table-style statsEven-width characters so columns line up; ligatures off for honest character counting.

Type scale

Fluid clamp() from small phones to wide desktops. No magic numbers in templates — only token names.

TokenRangeUse
--fs-20013–14.4 pxEyebrow, captions, mono labels
--fs-30014.4–16 pxBody
--fs-40016–18 pxLede paragraphs, large body
--fs-50018–22 pxSubheads, h3, sidecard quotes
--fs-60024–32 pxSection h2
--fs-70032–46 pxBig stat numbers, page titles
--fs-80042–64 pxArticle h1
--fs-display48–88 pxHero .h-display
--fs-typing22–33 pxThe typing surface itself

Hierarchy rules

  • h1 sits flush with no top margin (page heads style it).
  • h2 has a top hairline rule on long-form articles, generous breathing room.
  • h3 has half the top space of h2.
  • Adjacent-sibling overrides eliminate double-spacing when an h2 immediately precedes a paragraph or list.

Color

Two themes, both warm. Dark default; light is paper-cream.

Dark theme (default)

TokenHexUse
--bg-0#14161ePage background
--bg-1#1c1f29Cards, sticky chrome
--bg-2#252836Hover states, inputs
--fg-0#ece6d6Primary text (warm fog)
--fg-1#c9c2b1Secondary text
--fg-2#8a8678Muted, captions
--rule#2d3040Borders, hairlines
--accent#e58060Persimmon coral — caret, links, highlight
--accent-deep#c1413cHover, deeper accents
--secondary#6ba9b3Teal — secondary buttons, alt highlights
--good#8fbf90Clean keys on heatmap, success
--bad#d76050Incorrect keystrokes, slow keys

Light theme

TokenHexUse
--bg-0#f4ecdbWarm paper background
--bg-1#ebe2cdCards
--fg-0#1c1d22Deep ink
--accent#c1413cTypewriter-ribbon red
--secondary#1f3d4aDeep ocean teal

Spacing

Eight steps, geometrically progressive. Used for margins, padding, and gaps.

--space-1 (4 px) · --space-2 (8) · --space-3 (12) · --space-4 (16) · --space-5 (24) · --space-6 (32) · --space-7 (40) · --space-8 (48) · --space-10 (72) · --space-12 (104).

Radius

--radius: 2 px is the default — soft hairline edges. The typing surface itself stays at 0 for pixel-perfect span alignment. --radius-lg: 4 px for large cards. --radius-pill: 999 px reserved for status chips.

Breakpoints

Seven canonical stops. No ad-hoc values. New responsive rules MUST snap to one of these.

360 (small phone) · 480 (large phone) · 640 (phone landscape) · 768 (tablet portrait) · 1024 (tablet landscape) · 1180 (standard desktop) · 1280 (--max-width).

Components

Every interactive element renders at ≥ 44 × 44 px (WCAG 2.2 AA). Visible focus rings on every focusable element. Hairline 1 px borders, never thicker. Animations under 250 ms; motion respects prefers-reduced-motion.

Buttons

  • .btn — neutral, hairline border.
  • .btn--primary — filled persimmon, white text.
  • .btn--secondary — teal-bordered, fills on hover.
  • .btn--ghost — visible hairline border, transparent fill, brightens to accent on hover.
  • .btn--danger — filled with the bad-state color; reserved for destructive confirms in the styled modal system.
  • .btn--big — larger padding for hero CTAs.
  • .btn--small — compact, for in-card actions.

Modals

Native <dialog> element, never custom overlay divs. The .info-modal chrome is shared by:

  • The site-wide info modal (window.openInfoModal('home' | 'practice')).
  • Confirm + prompt replacements built by util/modal.js — the confirmModal({ title, message, confirmLabel, danger }) helper returns a Promise; promptModal({ title, label, initial }) returns Promise<string|null>. Both auto-focus the cancel button on destructive actions so a stray Enter doesn't fire.
  • The "Add to collection" picker on /quotes/.

Native confirm() and prompt() calls are banned site-wide.

Em-dash and quotes — never

Typeable content (corpus, UI strings, blog posts, lesson descriptions) uses -- instead of , straight quotes (", ') instead of curly, and ... instead of . The book ingest pipeline applies this normalization automatically. See the decision post.

Eyebrows

Mono caps, persimmon, with a 1.2 rem leading rule. Used to introduce hero titles and article heads.

Editorial divider (.rule)

Two flex-grow hairlines flanking a mono-caps label. Used to break long pages into named sections without the heaviness of an h2.

Brand mark + favicon

The brand is a stylized gorilla holding a keyboard. The same figure appears in three places: the favicon (browser tab), the header mark (next to the wordmark), and the footer mark (above the wordmark).

Variants

Two color variants, theme-conditional:

  • Dark figure -- used in light theme. Dark navy on whatever the page background is.
  • Light figure -- used in dark theme. Warm off-white (rgb(236, 237, 239)), readable on the dark navy --bg-0.

Both variants share the same outline; only the fill changes. The light version is generated programmatically from the dark source by recolouring every non-transparent pixel.

Sizes shipped

Every PNG is square and centered around the figure with a small amount of breathing room.

FileSizeUsed for
favicon-16.png16×16Browser tab fallback
favicon-32.png32×32Browser tab (modern)
favicon-48.png48×48Windows shortcut, browser cache
favicon-64.png64×64High-DPI tab, retina header
apple-touch-icon.png180×180iOS home screen
icon-192.png192×192PWA install (Android), header <img> source
icon-256.png256×256High-DPI PWA tile
icon-512.png512×512PWA splash, share-card avatar

Each size also has a -light sibling: favicon-32-light.png, icon-192-light.png, etc.

Downloads

Wiring

The browser tab favicon ships as paired <link rel="icon" media="(prefers-color-scheme: …)"> tags so OS-level dark/light preference picks the right variant. The header and footer brand marks use a CSS background-image keyed off [data-theme], which is the attribute the manual day/night toggle controls -- so user preference (not just OS) is respected:

.site-brand__mark {
  background-image: url("/assets/img/icon-192.png");
}
[data-theme="dark"] .site-brand__mark {
  background-image: url("/assets/img/icon-192-light.png");
}

Regenerating the icon set

The PNGs are generated by a one-off Node script that uses sharp (already a dependency via @11ty/eleventy-img). To rebuild after changing the source GuerillaType Favicon.png, run the same pipeline: center-crop to 540×540, knock the off-white background to transparent (channel threshold ≥ 230 → alpha 0, soft-edge band 195-230 → proportional alpha), then resize to each target size for both dark and light variants.

Accessibility standards

  • WCAG 2.2 AA contrast across all themes (4.5:1 for body text, 3:1 for large text).
  • 44 × 44 px minimum touch targets, enforced via a base rule with opt-out flag.
  • Skip link to #main.
  • Native <details> / <dialog> for collapsibles and modals.
  • aria-current="page" on active nav links.
  • Live regions on results, toasts, live stats.
  • prefers-reduced-motion respected globally.
  • Keyboard-shortcut overlay covers every interaction (press ?).