Back
/// hugo.cli / system

The system manual.

The design system behind hugo.mourlev.at — tokens, components, and the conventions that hold them together. Browse the foundations, inspect each component in a live playground, then read or copy the written reference.

Token-driven OKLCH palette Fluid type scale 8pt grid

01 Palette

Raw OKLCH-tuned grays and the brand + status accents. Theme-agnostic, consumed by semantic tokens.

gray-0#ffffff
gray-1oklch(0.98)
gray-2oklch(0.94)
gray-3oklch(0.87)
gray-4oklch(0.75)
gray-5oklch(0.60)
gray-6oklch(0.47)
gray-7oklch(0.35)
gray-8oklch(0.25)
gray-9oklch(0.17)
gray-10oklch(0.10)
gray-11oklch(0.04)
orange-400#ff8533
orange-500#ff6a00
orange-600#e05a00
green-500#2ed573
green-600#26b462

02 Semantic colors

Semantic layer consumed by every component. Change any one of these and the whole system shifts.

--color-bgPage background
--color-surfaceSubtle surface
--color-surface-hoverHovered surface
--color-surface-elevatedFloating panels
--color-text-strongMaximum contrast
--color-text-primaryBody copy
--color-text-secondaryParagraph 70%
--color-text-mutedLabels, meta
--color-text-faintWhisper
--color-borderSubtle rule
--color-border-strongEmphasised rule
--color-brandAccent orange
--color-status-availableAvailable pill

03 Foreground opacity scale

Theme-aware alpha steps from --color-fg: white on dark, black on light. Replaces every hand-rolled rgba(255,255,255,X) that used to live in the codebase.

fg-2
fg-4
fg-6
fg-8
fg-10
fg-15
fg-20
fg-30
fg-40
fg-50
fg-60
fg-70
fg-80
fg-90

04 Typography

Manrope (variable, 200-800) for prose and headings. JetBrains Mono for labels, metadata, and the TUI lane.

Aatext-4xlclamp(3.5rem, 3rem + 2.5vw, 5rem)
Scaling the experiencetext-3xlclamp(2.5rem, 2rem + 1.5vw, 3.5rem)
Design as a strategic levertext-2xlclamp(1.8rem, 1.5rem + 1vw, 2.3rem)
Preserving the exact standardstext-xlclamp(1.4rem, 1.2rem + 0.6vw, 1.75rem)
Industrializing design is not a surrender of soul.text-lgclamp(1.1rem, 1rem + 0.4vw, 1.3rem)
The quick brown fox jumps over the lazy dog.text-baseclamp(0.95rem, 0.9rem + 0.25vw, 1.05rem)
The quick brown fox jumps over the lazy dog.text-smclamp(0.78rem, 0.74rem + 0.2vw, 0.88rem)
The quick brown foxtext-xsclamp(0.6rem, 0.58rem + 0.1vw, 0.68rem)

04·live Type tester

Push Manrope's variable weight axis. Change the copy, size, tracking — the font renders live.

Scaling the experience.

05 Spacing scale

8pt grid. Use for padding, gap, and margin — keeps rhythm consistent across sections and components.

space-10.25rem · 4px
space-20.5rem · 8px
space-30.75rem · 12px
space-41rem · 16px
space-51.5rem · 24px
space-62rem · 32px
space-73rem · 48px
space-84rem · 64px
space-96rem · 96px
space-108rem · 128px

06 Radius

Corner rounding scale. Pill for tags / buttons; XL for elevated panels; SM for inputs and microsurfaces.

radius-sm4px
radius-md8px
radius-lg12px
radius-xl16px
radius-pill9999px

07 Motion

Click any card to play its timing live. Durations use ease-out by default; the ease cards use duration-base.

08 Components

Every reusable UI piece of the site — primitives, cards, shell, overlays, motion, TUI. Each story renders the real site class, with live controls for variants, states, and props.

09 TUI palette

The retro-terminal lane has its own color system, three themes deep. Switch with /theme default, /theme matrix, or /theme retro inside the TUI.

default /theme default
bg
text
accent
green
cyan
yellow
matrix /theme matrix
bg
text
accent
green
cyan
yellow
retro /theme retro
bg
text
accent
green
cyan
yellow

10 Documentation

The written reference — token architecture, component catalogue, and conventions that govern every visual decision on the site.

01 · Philosophy

  • Tokens first. A color only appears in a component as var(--color-*). No hardcoded hex values inside modules.
  • Four layers, referenced top-down:
    1. Primitives — typography, spacing, radius, motion, layout.
    2. Palette — raw OKLCH grayscale + accents (orange, green).
    3. Semantic--color-bg, --color-text-*, --color-brand, --color-border*, etc. Dark lane only.
    4. Legacy aliases--bg, --text, --white, --border, --radius, --font, --mono, --ease so existing rules keep rendering without a global find-and-replace.
  • No light theme. The single lane is dark — color-scheme: dark. The TUI ships its own palette, independent.
  • Fluid scale. Typography + spacing grow with the viewport via clamp() — one scale for every breakpoint.

Tokens live in src/css/tokens.css. Any semantic change happens there — it cascades.

02 · Colors

2.1 — Raw palette (OKLCH grayscale)

Perceptually uniform source, used only to derive the semantic tokens. Do not consume directly in components.

TokenOKLCHIndicative usage
--gray-0oklch(1 0 0)Pure white (avoid outside borders)
--gray-1oklch(0.98)Hi-contrast on dark
--gray-5oklch(0.60)Mid-gray
--gray-6oklch(0.47)Text-muted zone
--gray-9oklch(0.17)Surface zone
--gray-10oklch(0.10)Surface elevated
--gray-11oklch(0.04)--color-bg source

2.2 — Accents

TokenValueUsage
--orange-400#ff8533Light variant
--orange-500#ff6a00--color-brand source
--orange-600#e05a00Hover / pressed
--green-500#2ed573--color-status-available source
--green-600#26b462Dark variant

2.3 — Semantic tokens (what you actually consume)

Component working layer. Any theme shift would flow through these tokens alone.

TokenValueRole
--color-bgvar(--gray-11)Page background
--color-surfacergba(255,255,255,0.02)Subtle surface
--color-surface-hoverrgba(255,255,255,0.05)Surface hover
--color-surface-pressedrgba(255,255,255,0.08)Active / pressed
--color-surface-elevated#0a0a0aCards, raised panels
--color-surface-floatingrgba(10,10,10,0.96)Overlays (legal sheet, consent)
--color-text-strong#ffffffTitle, max emphasis
--color-text-primary#e0e0e0Main copy
--color-text-secondaryrgba(255,255,255,0.7)Paragraphs
--color-text-muted#808080Labels, meta
--color-text-faintrgba(255,255,255,0.3)Whisper
--color-borderrgba(255,255,255,0.06)Subtle rule
--color-border-strongrgba(255,255,255,0.15)Emphasised rule
--color-focus-ringrgba(255,255,255,0.55):focus-visible outline
--color-brandvar(--orange-500)Accent orange
--color-brand-softrgba(255,106,0,0.25)Glow, hover halo
--color-status-availablevar(--green-500)Availability pill
--color-emphasisrgba(255,255,255,0.85)<strong> / heavy copy

2.4 — Foreground opacity scale

Replaces every hand-rolled rgba(255,255,255,X). One base token --color-fg = white, stepped from 2 % to 90 %.

--color-fg-2 · -4 · -5 · -6 · -8 · -10 · -12 · -15 · -18 · -20 · -25 · -30 · -35 · -40 · -45 · -50 · -55 · -60 · -65 · -70 · -75 · -80 · -85 · -90

Typical use: borders (--color-fg-6), quiet surfaces (--color-fg-10), secondary text (--color-fg-70), separators (--color-fg-15).

2.5 — TUI palette (terminal lane)

Independent of the main palette. Three themes stacked, switched via /theme <name> inside the TUI.

Themebgtextaccentgreencyanyellow
default#0d1117#e6edf3#ff6a00#56d364#58a6ff#f9c74f
matrix#000a00rgba(0,255,70,.8)#00ff46#00ff46#00cc36#66ff66
retro#1a0a2ergba(255,183,77,.85)#ff6ec7#00ff9f#00d4ff#ffb74d

03 · Typography

3.1 — Families

TokenFamilyUsage
--font-sansManropeProse, headings, UI — variable weight 200–800
--font-monoJetBrains MonoLabels, meta, TUI, code

Both fonts are self-hosted (src/assets/fonts/*.woff2) and preloaded in <head> to eliminate FOUT.

3.2 — Fluid scale (clamp())

TokenRangeUsage
--text-xs0.6rem → 0.68remMono uppercase labels
--text-sm0.78rem → 0.88remCaptions
--text-base0.95rem → 1.05remBody copy
--text-lg1.1rem → 1.3remLead paragraph
--text-xl1.4rem → 1.75remSub-headings
--text-2xl1.8rem → 2.3remSection titles
--text-3xl2.5rem → 3.5remPage titles
--text-4xl3.5rem → 5remHero / display

3.3 — Conventions

  • Labels (.label in base.css): --font-mono, --text-xs, uppercase, letter-spacing 0.14em, color --color-brand.
  • Emphasis: <strong> inherits --color-emphasis. In statements, <strong> chars turn into .char-bold at 800 uppercase.
  • Tracking: headings from -0.02em to -0.05em depending on size (larger = tighter).

04 · Spacing — 8pt grid

Only these tokens should drive padding, margin, gap. No arbitrary values.

Tokenrempx
--space-000
--space-10.25rem4
--space-20.5rem8
--space-30.75rem12
--space-41rem16
--space-51.5rem24
--space-62rem32
--space-73rem48
--space-84rem64
--space-96rem96
--space-108rem128

Derived layout tokens:

  • --container = clamp(1.5rem, 4vw, 4rem) — container horizontal padding.
  • --section = clamp(6rem, 12vh, 10rem) — vertical padding between sections.

05 · Radius

TokenValueUsage
--radius-sm4pxInputs, micro-surfaces
--radius-md8pxStandard cards
--radius-lg12pxContent panels
--radius-xl16pxFloating panels (legal sheet)
--radius-pill9999pxPills, tags, round buttons

06 · Motion

Every animation consumes one of 5 durations and one of 4 eases. No free-form timings.

6.1 — Durations

TokenValueUsage
--duration-instant100msImmediate feedback (toast, focus ring)
--duration-fast200msHover, toggle
--duration-base300msDefault for most transitions
--duration-slow600msReveal, char lit, page transitions
--duration-deliberate900msMarked entry effects

6.2 — Eases

Tokencubic-bezierCharacter
--ease-out(0.19, 1, 0.22, 1)Default — soft exit
--ease-in-out(0.65, 0, 0.35, 1)Two-way
--ease-spring(0.2, 0.9, 0.3, 1.2)Slight overshoot
--ease-power3(0.33, 1, 0.68, 1)Stronger ease-out

6.3 — Conventions

  • Every transition respects prefers-reduced-motion: reduce — section-level opt-out.
  • Entry animations (reveal) use GSAP + ScrollTrigger (src/js/main.js).
  • The hero ASCII animation is orchestrated in src/js/hero.js — 3 phases: scramble → color transition → cross-fade.

07 · Elevation (shadows)

TokenValueUsage
--shadow-sm0 1px 2px rgba(0,0,0,0.4)Subtle cards
--shadow-md0 6px 20px rgba(0,0,0,0.35)Panels, overlays
--shadow-lg0 20px 60px rgba(0,0,0,0.6)Modals, floating menus

08 · Components

Every component ships in the main CSS bundle and is consumed through its root class. Section 08 above exposes each component in playground mode (variants / states / props).

8.1 — Primitives

NameRoot classFile
Section label.labelbase.css
Expertise tag.exp-tags spansections.css
Availability pill.exp-availablesections.css
Speaking venue chip.speaking-venuesections.css
Speaking CTA.speaking-ctasections.css
Consent button.consent-btn[.--accept]consent.css
Copy button.copy-btnsections.css
Writing arrow.writing-arrowsections.css

8.2 — Content blocks

NameRoot classAnatomy
Experience item.exp-itemlogo · title-row (+ availability) · date · desc · tags
Expertise card.expertise-itemicon · num · title · desc (hover lift)
Speaking item.speaking-itemtype · venue · title · desc · CTA
Writing item.writing-itemtitle · date · arrow
Connect link.connect-linklabel · arrow (hover pad-left)

8.3 — Shell & nav

NameRoot classNote
Footer legal button.footer-legal buttonAnimated underline reveal
View toggle (CLI/site).view-toggleFloating pill, revealed after hero
Scroll progress bar.scroll-progressscaleX(0→1) in rAF
Meta chip.ds-chipMono uppercase pill

8.4 — Overlays

NameRoot classInteractions
Legal sheet.legal-sheetSwipe-to-dismiss, focus trap, scroll lock, Escape
Consent banner.consent-bannerGA4 gate, localStorage, consent:change event

8.5 — Motion-enhanced

EffectImplementation
Reveal on scroll.reveal + ScrollTrigger (main.js)
Availability pulseCSS keyframes exp-available-pulse (2s infinite)
Statement char-by-charProgress 0→1 → .char.lit per segment
Hero ASCII scramble3-phase rAF loop with dedicated glyph set
Legal sheet slide-inTransform + opacity, spring ease

8.6 — TUI

ElementClass
User message.tui-block-user
Assistant message.tui-block-assistant
Tool message.tui-block-tool
Quick command chip.tui-cmd-btn[.active]
Status bar.tui-statusbar
Theme palette--tui-{default/matrix/retro}-*

09 · Focus & accessibility

  • Focus visible: outline 2px solid var(--color-focus-ring) + outline-offset of 2 to 4px. Applied to every :focus-visible.
  • Tab order preserved by construction — no custom tabindex outside overlays (the legal sheet installs a focus trap).
  • Reduced motion: every animation has its fallback via @media (prefers-reduced-motion: reduce).
  • Contrast: palette tuned to ≥ AA on --color-bg. --color-text-faint is reserved for decorative whisper (never informational copy).

10 · Interactive explorer

This page exposes every token and component in Storybook-style playground mode:

  • Sections 01–07: palette, semantic, fg opacity, typography + type tester, spacing, radius, motion player.
  • Section 08 · Components: filterable grid (search + category chips — Primitives / Content / Shell / Overlays / Motion / TUI). Each story has: live canvas (with bg/surface/elevated background toggle), variant/state/props controls, copyable source block.
  • Section 09 · TUI palette: the 3 terminal themes.

:hover and :focus are simulated through a data-state attribute mirrored in ds.css — scoped to .ds-story-canvas, zero production impact.

Access

  • Direct URL: /design-system.html.
  • Easter egg: Konami code (↑↑↓↓←→←→BA) on the home page → reveals the /// glyph in the footer which links to this page. The unlock persists in localStorage:hm:ds-unlocked.

11 · Code conventions

  • Tokens only. A component never consumes a hex / rgba value directly. Go through var(--*).
  • Naming. Component classes in kebab-case, prefixed by their scope (exp-, speaking-, writing-, tui-, legal-, consent-, ds-).
  • CSS modules in src/css/ — one file per domain (sections.css, tui.css, legal.css, consent.css, animations.css, responsive.css). Inclusion order enforced by scripts/build.mjs (CSS_ORDER).
  • JS modules in src/js/. Same ordering pattern (JS_ORDER in build.mjs).
  • No inline CSS except when a style depends on a runtime value (e.g. style="transform: scaleX(X)" for scroll-progress, or palette swatches that must display a var(--token) as background).

12 · Build & loading

  • esbuild for JS (concat + minify + sourcemap).
  • Lightning CSS for CSS (bundle + minify, targets via browserslist).
  • html-minifier-terser for HTML.
  • Cache busting: 8-char hash in the filename (main.{hash}.{css|js}, ds.{hash}.{css|js}). immutable · max-age=31536000 headers on hashed files through the generated .htaccess.
  • Self-hosted fonts, preload-ed in <head> to avoid FOUT.
  • OG image generated at build time (1200×630, SVG with Manrope/JBM embedded as base64 → PNG via sharp).

13 · Quick cheatsheet

NeedToken / class
Main textcolor: var(--color-text-primary)
Emphasis textcolor: var(--color-text-strong)
Subtle borderborder: 1px solid var(--color-border)
Accent pillbackground: var(--color-brand); border-radius: var(--radius-pill);
Hover state in a DS story preview[data-state="hover"] on .ds-story-canvas
New spacingCheck --space-* before any custom value
One-off new colorUse a --color-fg-* opacity step