Design System
This page is generated withThis document defines the design system for stoll.studio, including the theming system, colors, typography, spacing, layout, and components. All design tokens are implemented as CSS custom properties in a 5-tier architecture.
| Global Constants | Fixed values (black, white, phosphor): never change across themes |
| Base Tokens | Per-theme primitives (text, accents, surfaces, borders, spacing scale, type scale) |
| Status Tokens | Per-theme state values (info, success, error, warning) |
| Semantic Tokens | Reference base/status tokens for specific purposes (links, bullets, code) |
| Component Tokens | Reference base/semantic tokens for specific components (sticky bar, blockquotes, skip link) |
This page documents the design system. The CSS manifests it. The Figma file materializes it. As a space for visual exploration and adjustment. Plotting out the token architecture as Figma variables and building a dedicated Token Inspector page allows to see the system as a whole and refactor it with confidence.
| This page | Documents the system: tokens, components, decisions, changelog |
| themes.css | Manifests the system: CSS custom properties, theme definitions |
| Figma file | Materializes the system: Figma variables mirroring the token structure, Token Inspector page for visual review and color exploration |
Four themes available, activated via URL parameter (?theme=name) or the footer switcher. Themes override base color tokens; semantic and component tokens inherit automatically.
| Light | Light theme: white surface, Hanken Grotesk, yellow accent bar with black text |
| Dark | Dark theme: #1a1a1a surface, Hanken Grotesk, blue bar (#5dade2) with black text |
| Vibrant | Vibrant blue (#3D38F5) surface, pink (#FF0080) accents, lime (#D4FF00) links |
| Terminal | Single phosphor green (#00FF41) at varying opacities on pure black (#000), IBM Plex Mono throughout, normal weight (400) for all text |
Theme switching is handled by theme-switcher.js which reads the URL parameter, applies the data-theme attribute on <html>, and propagates the theme to all internal links. A critical inline script in <head> prevents flash of unstyled content.
Light Theme Colors
Text colors.
| Primary | #313131: main headings, strong text |
| Secondary | #515151: body text, paragraphs |
| Tertiary | #7a7a7a: muted text, bullets |
| Quaternary | #9a9a9a: dates, very muted elements |
| Light | #c0c0c0: taglines, very light text |
Accent, surface and border colors.
| Accent 1 | #ffff00: signature yellow |
| Accent 2 | #268bd2: blue (links, code) |
| Accent 3 | #ff0000: red accent |
| Surface | #ffffff: main background |
| Surface Emphasized | #f5f5f5: table headers, cards |
| Surface Secondary | #f9f9f9: code blocks, blockquotes |
| Border | #eee: primary borders |
| Border Secondary | #e5e5e5: secondary borders |
| Border Strong | #cccccc: structural dividers |
| Image BG | #d5d5d5: translucent image card background |
Status colors.
| Info | #268bd2: informational (blue) |
| Success | #27ae60: success states (green) |
| Error | #e74c3c: error states (red) |
| Warning | #f39c12: warning states (orange) |
Semantic and component colors.
| Divider | #cccccc: structural dividers (via Border Strong) |
| Link | #268bd2: links (via Info/Accent 2) |
| Hover | opacity: 0.75 on a:hover / a:focus, no color token, theme-agnostic |
| Code | #268bd2: inline code text (via Accent 2) |
| Bullet | #7a7a7a: list bullets (via Tertiary) |
| Meta | #7a7a7a: figcaptions, project meta table headers, blockquote citations (via Tertiary) |
| Meta Secondary | #9a9a9a: project subtitles, project credits (via Quaternary) |
| Sticky Bar BG | #ffff00: Accent 1 yellow |
| Sticky Bar Text | #000: black |
| Blockquote BG | #f9f9f9: blockquote background (via Surface Secondary) |
| Blockquote Border | #ffff00: blockquote left border (via Accent 1) |
| Skip Link BG | #000: skip-to-content background (global black) |
| Skip Link Text | #fff: skip-to-content text (global white) |
Dark Theme Colors
Text colors.
| Primary | #ffffff: main headings, strong text |
| Secondary | #e0e0e0: body text, paragraphs |
| Tertiary | #b0b0b0: muted text, bullets |
| Quaternary | #808080: dates, very muted elements |
| Light | #606060: taglines, very light text |
Accent, surface and border colors.
| Accent 1 | #ffff00: signature yellow |
| Accent 2 | #5dade2: blue |
| Accent 3 | #ff0000: red accent |
| Surface | #1a1a1a: main background |
| Surface Emphasized | #2a2a2a: table headers, cards |
| Surface Secondary | #242424: code blocks, blockquotes |
| Border | #333: primary borders |
| Border Secondary | #444: secondary borders |
| Border Strong | #666666: structural dividers |
Status colors.
| Info | #5dade2: informational (blue) |
| Success | #2ecc71: success states (green) |
| Error | #e74c3c: error states (red) |
| Warning | #f39c12: warning states (orange) |
Semantic and component colors.
| Divider | #666666: structural dividers (via Border Strong) |
| Link | #5dade2: links, interactive elements |
| Hover | opacity: 0.75 on a:hover / a:focus, no color token, theme-agnostic |
| Code | #5dade2: inline code text (via Accent 2) |
| Bullet | #b0b0b0: list bullets (via Tertiary) |
| Meta | #b0b0b0: figcaptions, project meta table headers, blockquote citations (via Tertiary) |
| Meta Secondary | #808080: project subtitles, project credits (via Quaternary) |
| Sticky Bar BG | #5dade2: Accent 2 blue |
| Sticky Bar Text | #000: black |
| Blockquote BG | #242424: blockquote background (via Surface Secondary) |
| Blockquote Border | #ffff00: blockquote left border (via Accent 1) |
| Skip Link BG | #000: skip-to-content background (global black) |
| Skip Link Text | #fff: skip-to-content text (global white) |
Vibrant Theme Colors
Text colors.
| Primary | #ffffff: main headings, strong text |
| Secondary | rgba(255, 255, 255, 0.75): body text |
| Tertiary | rgba(255, 255, 255, 0.6): muted text |
| Quaternary | rgba(255, 255, 255, 0.45): dates, very muted |
| Light | rgba(255, 255, 255, 0.3): taglines |
Accent, surface and border colors.
| Accent 1 | #FF0080: pink |
| Accent 2 | #D4FF00: lime |
| Accent 3 | #00FFD9: cyan-teal accent |
| Surface | #3D38F5: vibrant blue background |
| Surface Emphasized | #312CDB: table headers, cards |
| Surface Secondary | #4942F7: code blocks, blockquotes |
| Border | rgba(255, 255, 255, 0.15): primary borders |
| Border Secondary | rgba(255, 255, 255, 0.2): secondary borders |
| Border Strong | rgba(255, 255, 255, 0.3): structural dividers |
Status colors.
| Info | #69A0FF: informational (blue) |
| Success | #00FF33: success states (green) |
| Error | #FF3366: error states (red) |
| Warning | #FFB800: warning states (orange) |
Semantic and component colors.
| Divider | rgba(255, 255, 255, 0.3): structural dividers (via Border Strong) |
| Link | #D4FF00: lime links |
| Hover | opacity: 0.75 on a:hover / a:focus, no color token, theme-agnostic |
| Code | #D4FF00: inline code text (via Accent 2 lime) |
| Bullet | rgba(255, 255, 255, 0.4): list bullets (white-40) |
| Meta | rgba(255, 255, 255, 0.6): figcaptions, project meta table headers, blockquote citations (via Tertiary / white-60) |
| Meta Secondary | rgba(255, 255, 255, 0.45): project subtitles, project credits (via Quaternary / white-45) |
| Sticky Bar BG | #FF0080: Accent 1 pink |
| Sticky Bar Text | #fff: white |
| Blockquote BG | #4942F7: blockquote background (via Surface Secondary) |
| Blockquote Border | #FF0080: blockquote left border (via Accent 1 pink) |
| Skip Link BG | #FF0080: skip-to-content background (Accent 1 pink) |
| Skip Link Text | #fff: skip-to-content text (global white) |
Terminal Theme Colors
Text colors.
| Primary | #00FF41: phosphor green, all headings |
| Secondary | rgba(0, 255, 65, 0.85): body text |
| Tertiary | rgba(0, 255, 65, 0.70): muted text |
| Quaternary | rgba(0, 255, 65, 0.60): dates, very muted |
| Light | rgba(0, 255, 65, 0.50): taglines |
Accent, surface and border colors.
| Accent 1 | #00FF41: phosphor green (all accents unified) |
| Accent 2 | #00FF41: phosphor green (all accents unified) |
| Accent 3 | #00FF41: phosphor green (all accents unified) |
| Surface | #000000: pure black background |
| Surface Emphasized | #111111: table headers, cards |
| Surface Secondary | #222222: code blocks, blockquotes |
| Border | rgba(0, 255, 65, 0.15): primary borders |
| Border Secondary | rgba(0, 255, 65, 0.2): secondary borders |
| Border Strong | rgba(0, 255, 65, 0.30): structural dividers |
Status colors.
| Info | #00BFFF: informational (cyan-blue) |
| Success | #00FF41: success states (phosphor green) |
| Error | #FF0000: error states (red) |
| Warning | #FFFF00: warning states (yellow) |
Semantic and component colors.
| Divider | rgba(0, 255, 65, 0.30): structural dividers (via Border Strong) |
| Link | #00FF41: terminal green links (uses Primary color) |
| Hover | opacity: 0.75 on a:hover / a:focus, no color token, theme-agnostic |
| Code | #00FF41: inline code text (via Accent 2, phosphor green) |
| Bullet | rgba(0, 255, 65, 0.4): list bullets (phosphor-40) |
| Meta | rgba(0, 255, 65, 0.70): figcaptions, project meta table headers, blockquote citations (via Tertiary / phosphor-70) |
| Meta Secondary | rgba(0, 255, 65, 0.60): project subtitles, project credits (via Quaternary / phosphor-60) |
| Sticky Bar BG | #00FF41: terminal green |
| Sticky Bar Text | #000: black |
| Blockquote BG | #222222: blockquote background (via Surface Secondary) |
| Blockquote Border | #00FF41: blockquote left border (via Accent 1, phosphor) |
| Skip Link BG | #00FF41: skip-to-content background (Accent 1, phosphor) |
| Skip Link Text | #000: skip-to-content text (global black) |
Typography: Terminal theme uses monospace font throughout for authentic terminal aesthetic.
| Font Family | IBM Plex Mono everywhere (headings, body, navigation, all text) |
| Font Weight | All weights normalized to 400 (normal): headings, body, bold text all use regular weight for authentic terminal look |
Font families and their usage across the site.
| Primary | Hanken Grotesk (variable, 100–900): body text, headings, labels |
| Monospace | IBM Plex Mono: code blocks, navigation links |
Font sizes (desktop base: 20px, mobile base: 18px).
| H1 | 2rem (40px): page titles |
| H2 | 1.3rem (26px) desktop, 1.8rem mobile: main statements |
| H3 | 1.25rem (25px) desktop, 1.4rem mobile |
| Body | 1rem (20px) desktop, 1.05rem mobile |
| Masthead | 1.5rem (30px) desktop, 1.6rem mobile |
| Small/Meta | 0.8–0.85rem (16–17px): captions, dates, notes |
Font weights using variable font capabilities.
| 300 | Light |
| 400 | Regular (default body text) |
| 500 | Medium (navigation links) |
| 550 | Between medium and semi-bold (two-col labels) |
| 600 | Semi-bold (headings, bold text) |
Spacing scale using rem units (scales with base font size).
| XS | 0.25rem (5px): tight spacing, table cells |
| SM | 0.5rem (10px): small gaps, heading margins |
| MD | 1rem (20px): standard spacing, paragraph margins, navigation gap |
| LG | 1.5rem (30px): section spacing, figures |
| XL | 2rem (40px): large spacing, project credits |
| 2XL | 2.5rem (50px): section dividers (two-col margin-bottom) |
| 3XL | 3rem (60px): masthead margin |
Breakpoints and responsive behavior.
| Desktop | ≥769px: two-column grids, inline navigation, container max-width 72rem |
| Mobile | ≤768px: single column, hamburger menu with overlay, theme select dropdown, 18px base font |
| Small | ≤600px: project column grids collapse to single column |
Two-column structural layout (.two-col): the primary layout pattern used across all pages.
| .two-col | Grid section: 30% / 1fr on desktop, stacked on mobile. Top border, 0.75rem padding-top, 2.5rem margin-bottom. |
| .two-col-label | Left column: topic name or section headline. Font-weight 550, primary color. |
| .two-col-role | Right column, row 1: role or subtitle. Secondary color, 0.75rem margin-bottom. |
| .two-col-content | Right column: body text, tables, nested grids. Spans rows 2–20 (or 1–20 when no role). |
| .two-col-note | Left column, row 2: muted sidenote. 0.8rem, quaternary color. |
Column grids: nest inside .two-col-content. Shared across stoll.studio (project.css) and teaching.stoll.studio (course.css). Usage: <div class="columns columns-2">.
| .columns-2 | Two equal columns (1fr 1fr). Collapses to 1 column on mobile. |
| .columns-3 | Three equal columns (1fr 1fr 1fr). Collapses to 1 column on mobile. |
| .columns-4 | Four equal columns. Collapses to 2 columns on mobile (not 1: image grids stay readable at 2). |
| .columns-1-2 | Asymmetric, small left (1fr 2fr). Collapses to 1 column on mobile. |
| .columns-2-1 | Asymmetric, large left (2fr 1fr). Collapses to 1 column on mobile. |
Navigation. Desktop: horizontal inline with masthead (flexbox, baseline-aligned). Mobile: hamburger menu with full-screen overlay.
| Active state | Underline via aria-current="page", 6px offset, 2px thickness |
| Hover state | Same underline style as active state |
| Font | IBM Plex Mono, weight 500 |
| Mobile toggle | CSS-only hamburger icon (3 lines, 24×2px, 7px spacing), animates to X on open. Uses var(--color-primary). |
| Mobile overlay | Full-screen fixed overlay (z-index: 100001), var(--color-surface) background, vertical links at 1.5rem, fadein animation. |
| Mobile a11y | aria-expanded, aria-label toggling, Escape key closes, body scroll lock when open. |
Sticky bar: position sticky at top, z-index 99999.
| Light | Yellow (#ffff00) background, black text |
| Dark | Blue (#5dade2) background, black text |
| Vibrant | Pink (#FF0080) background, white text |
| Terminal | Terminal green (#00FF41) background, black text |
Content table (.content-table): a lightweight table used for structured lists across all pages.
| .pl-year | Optional first column: year/date, nowrap, secondary color, 1% width |
| .pl-name | Name column: nowrap, bold label with optional small subtitle |
| .pl-desc | Description column: secondary color, wrapping text |
Table behavior rules.
| First row | No top padding (aligns with label baseline) |
| Last row | No bottom border or padding |
| After paragraph | p + .content-table adds top border and padding on first row |
| Mobile | .pl-year hidden, data-year attribute shown as small label above name |
Other components.
| Custom bullets | ⚬ character, tertiary color, 0.9em indent. Navigation excluded. |
| Blockquotes | Surface-secondary background, 4px --color-accent-1 left border, 0.95rem text. Accent 1 is yellow in light/dark/terminal, pink in vibrant. |
| Figures | 1.5rem margin, caption 0.8rem italic in meta color |
| Header | Flex container: H1 left, inline meta right (language switcher, page note), baseline-aligned. Stacks vertically on mobile. |
| Footer | Desktop: flex row, copyright left, theme links + DS link right. Mobile: stacked column, right-aligned, theme <select> dropdown replaces inline links. |
Contact form (.contact-form): loaded via contact-form.css on the Contact page only. All fields are fully theme-aware through CSS custom properties.
| Field layout | .contact-form-field wrapper, 1rem bottom margin. Label above input, display: block, font-weight 500, --color-primary. |
| Inputs / textarea | Full-width, 0.5rem padding, 4px border-radius. Border: --color-divider. Background: --color-surface. Text: --color-primary via both color and -webkit-text-fill-color (prevents browser override). |
| Select | Native appearance removed. Custom ▾ arrow via ::after on the field wrapper (:has(select)). Options get explicit background/color to override browser defaults in dark themes. |
| Focus state | No default outline. Border color switches to --color-primary. |
| Autofill override | :-webkit-autofill uses -webkit-box-shadow: 0 0 0 1000px var(--color-surface) inset to suppress the browser's autofill background color, plus -webkit-text-fill-color: var(--color-primary). |
| Submit button | Filled: --color-primary background, --color-surface text. Hover: inverted (surface background, primary text). Disabled: 50% opacity. |
| Form messages | Hidden by default. Success: --color-success background, --color-black text. Error: --color-error background, --color-black text. Black is used (not primary) because --color-primary in terminal is the same green as --color-success. |
| AI agent-ready | Explicit <label for> associations, standard name attributes, no JS-only submission barrier: intentionally usable by web-browsing AI agents and MCP browser tools. |
Chrome WebMCP Early Preview ↗: a standard for exposing structured tools to AI agents. Standard HTML form actions map to the declarative model.
| Skip link | Hidden by default (top: -40px), visible on focus (top: 6px). High contrast black/white. |
| ARIA labels | Navigation: aria-label="Main navigation". Current page: aria-current="page". Mobile toggle: aria-expanded, aria-label toggling. |
| Semantic HTML | <header role="banner">, <nav role="navigation">, <main role="main">, <footer role="contentinfo"> |
| Hreflang | CV pages include hreflang tags for de/en with x-default fallback to English |
| poole.min.css | Base framework (Poole by @mdo) |
| poole.overrides.min.css | Custom overrides: two-col layout, content tables, navigation, footer, responsive styles |
| project.css | Project-specific components: column grids, figures, project cards and headers |
| animations.css | Animation definitions |
| contact-form.css | Contact page only: form fields, select arrow, autofill override, button states, success/error messages |
| themes.css | All CSS custom properties, theme definitions (light, dark, vibrant, terminal), and variable application rules. Loaded from /_themes/ submodule. |
| 2026-02-23 | Added .columns-4 grid variant (4 equal columns, collapses to 2 on mobile). Updated design system docs to document .columns-4 and note shared naming convention with teaching.stoll.studio. Added cross-site theme carry: links between sister sites now pass the current theme as a URL parameter. Added Design System link to teaching.stoll.studio footer. |
| 2026-02-22 | Token system refinements. Added --color-meta and --color-meta-secondary semantic tokens to all 4 theme sections (figcaptions, project meta headers, blockquote citations, subtitles, credits: cascade via Tertiary/Quaternary). Changed --color-divider to reference Border Strong instead of Border Secondary in all themes (unified cascade from :root). Removed hardcoded rgba overrides for --color-meta/--color-meta-secondary in vibrant and terminal theme blocks: now resolved via CSS cascade through base tokens. Updated Figma variables to match. |
| 2026-02-21 | Documentation audit against themes.css source of truth. Added missing tokens across all themes: --color-image-bg (#d5d5d5) to light theme; Accent 3, Border Strong, and full Status Colors tables to dark, vibrant, and terminal themes. Corrected all four themes' "Link Hover" rows: no per-theme hover color token exists; hover is a global opacity: 0.75 on a:hover/a:focus. Fixed terminal theme: all three accents unified to #00FF41 (was yellow/cyan); Surface Emphasized #0A0A0A→#111111, Surface Secondary #050505→#222222; text colors Secondary–Light corrected from hex to rgba opacity variants. Added Contact Form component section. Fixed form message text color: --color-surface → --color-black. Updated CSS Architecture table. |
| 2026-02-19 | Renamed .project-list → .content-table across both repos: it's the generic default table style, not project-specific |
| 2026-02-13 | Added Terminal theme with bright green on black aesthetic and IBM Plex Mono throughout. Renamed themes: "default" → "light", "teaching" → "vibrant" |
| 2026-02-12 | Mobile hamburger navigation with full-screen overlay, footer theme select dropdown for mobile, --color-divider semantic token for structural dividers |
| 2026-02-11 | Added theme switcher to footer, dark theme sticky bar fix, navigation active/hover states, content tables across all pages, CV language switcher, Projects in navigation |
| 2026-02-07 | Initial design system documentation, theming system with URL-based switching |
| 2026-02-22 | Status tokens: their own tier, or not? Status tokens (info, success, error, warning) are semantically meaningful: they describe intent, not raw values. That makes them feel like they belong in the semantic tier. But semantic tokens reference base tokens, and status colors don't have obvious base equivalents to reference. Promoting them to base tokens just to create a reference chain adds indirection without clarity. Keeping them as their own tier is a pragmatic compromise, but it breaks the clean base to semantic to component cascade logic. No resolution yet. |