Paulo Antunes

// pantunes.dev · design system

Design System

The tokens, type, components and accessibility decisions behind this site. Everything here is rendered with the same stylesheet the site uses, and the colors and contrast ratios are read straight from the source tokens at build time, so this page can’t drift from the real thing.

Palette: Monokai · Stack: SCSS + custom properties · Target: WCAG 2.2 AA

Color

A dark Monokai base: warm near-black grounds, off-white text with hue-matched greys, and four saturated accents used sparingly. Green marks interaction and focus, cyan is the primary action, orange is secondary information.

Accents

  • --accent #a6e22e
  • --accent-btn #66d9ef
  • --accent-ink #1a1a1a
  • --green #a6e22e
  • --cyan #66d9ef
  • --orange #fd971f
  • --pink #ff6b9d

Backgrounds

  • --bg-hero #272822
  • --bg-about #20211b
  • --bg-contact #20211b
  • --bg-card #31322b
  • --bg-terminal #1b1a17

Text

  • --white #fff
  • --ink #f8f8f2
  • --ink-2 #cfcec3
  • --ink-3 #c9c8bd
  • --ink-4 #a8a99c
  • --ink-muted #b1a993

Borders

  • --border #3a3b32
  • --border-2 #41423a
  • --border-hov #5a5b50

Effects

  • --red #f92672
  • --black #000
  • --bg-video #020205

Window chrome

  • --chrome-top #3a3833
  • --chrome-bottom #322f2a
  • --chrome-title #b9b6a8
  • --chrome-ink #d7d6cb
  • --dot-close #ff5f57
  • --dot-close-ink #511a17
  • --dot-min #febc2e
  • --dot-max #28c840

Contrast

Every text token against every background token, computed with the WCAG 2.x relative-luminance formula. AA needs 4.5:1 for body text and 3:1 for large text (24px, or 18.7px bold). Pairs marked AA Large are only used for headings or UI accents, never for body copy.

Contrast ratio of each text token (rows) on each background token (columns)
Text \ Background --bg-hero--bg-about--bg-card--bg-terminal
--white Aa 14.86:1 AAA Aa 16.22:1 AAA Aa 12.95:1 AAA Aa 17.40:1 AAA
--ink Aa 13.94:1 AAA Aa 15.22:1 AAA Aa 12.15:1 AAA Aa 16.33:1 AAA
--ink-2 Aa 9.39:1 AAA Aa 10.25:1 AAA Aa 8.18:1 AAA Aa 11.00:1 AAA
--ink-3 Aa 8.83:1 AAA Aa 9.64:1 AAA Aa 7.70:1 AAA Aa 10.34:1 AAA
--ink-4 Aa 6.24:1 AA Aa 6.81:1 AA Aa 5.44:1 AA Aa 7.31:1 AAA
--ink-muted Aa 6.35:1 AA Aa 6.92:1 AA Aa 5.53:1 AA Aa 7.43:1 AAA
--green Aa 9.58:1 AAA Aa 10.46:1 AAA Aa 8.35:1 AAA Aa 11.22:1 AAA
--cyan Aa 9.01:1 AAA Aa 9.84:1 AAA Aa 7.85:1 AAA Aa 10.55:1 AAA
--orange Aa 6.81:1 AA Aa 7.43:1 AAA Aa 5.93:1 AA Aa 7.97:1 AAA
--pink Aa 5.55:1 AA Aa 6.06:1 AA Aa 4.84:1 AA Aa 6.50:1 AA

Component pairs

Where Text Background Ratio
Primary button (CTA) --accent-ink --accent-btn Aa 10.56:1 AAA
Submit button --bg-hero --accent Aa 9.58:1 AAA
Text selection --bg-hero --pink Aa 5.55:1 AA
Input placeholder --ink-muted --bg-card Aa 5.53:1 AA
Skip link --bg-hero --accent Aa 9.58:1 AAA

Typography

Three self-hosted families. Space Grotesk carries headings and UI, Hanken Grotesk carries long-form reading, and Space Mono carries labels, code and the terminal voice. Display sizes scale fluidly with clamp().

Display · .hero-headline
Space Grotesk 700 · clamp(2rem, 4.5vw, 3.75rem) · 1.05

20 years building a web that works for everyone.

Heading 2 · .about-title
Space Grotesk 700 · clamp(2rem, 3.5vw, 2.5rem) · 1.08

About me

Heading 3 · .skills-title
Space Grotesk 700 · 22px

What I do

Lead · .hero-sub
Hanken Grotesk 400 · clamp(16px, 1.8vw, 18px) · 1.55

Code, accessibility, tight deadlines, demanding clients — different facets of the same craft.

Body · .about-text
Hanken Grotesk 400 · clamp(14px, 1.65vw, 16.5px) · 1.62

// In 1999 the web became my playground; in 2006, my profession. Body copy stays near 65 characters per line for comfortable reading.

Eyebrow · .hero-eyebrow
Space Mono 400 · 13px · 0.08em

// 20 years · web · html · css · js · a11y

Label · .field-label
Space Mono · uppercase

EMAIL

Spacing & layout

Content sits in a single 1280px column with a 20px gutter. Sibling groups are laid out with flex or grid and gap, never per-element margins. Section rhythm is fluid, and wide media (the hero video, the work band) caps at 1520px.

ValueUse
--max-w: 1280pxContent column
--px: 20pxSide gutter at every width
1520pxMax width of full-bleed media
clamp(50px, 10vw, 100px)Vertical section padding
gap: 16px · 24px · 80pxCards · stacks · two-column layouts
radius: 3px · 4px · 8px · 10pxInputs & tags · cards & chips · CTA · terminal window
500 · 560 · 700 · 860pxBreakpoints: compact hero · skills grid collapses · hero stacks and nav hides · about stacks

Components

Live components with the site’s real classes. Hover them, or press Tab to walk through their focus states.

Buttons

.btn-cta · .btn-submit · .btn-submit:disabled — min-height 44px

Chips, tags & small controls

Own clients front-end PT

.agent-chip · .exp-tag · .client-role · .site-nav-lang · .clients-toggle

Form fields

Please enter a valid email address.

.form-group · .field-label · [aria-invalid="true"] + .field-hint — errors are announced with role="alert" on submit

Cards

.skill-card · .client-card.is-own · .client-card.is-taoti — origin is stated in text, not only color

Terminal

.modal-box · .sh-bar · .sh-line + .sh-g .sh-c .sh-o .sh-d .sh-w — role="dialog", aria-modal, Esc to close, focus returns to the trigger

Motion

Motion is short, eased out and tied to a moment: a heading types itself when its section arrives, content rises into place, and only two things loop. Every animation has a reduced-motion path.

PatternTimingReduced motion
Heading typing30–50ms / char (hero) · 55–85ms (sections)Full text shown at once
Revealopacity + translateY(8px) · .5s ease · 150–300ms staggerEverything visible at load
Hover lifttranslateY(-5px) · .22sNo lift
Cursor blink1.1s step-end ∞—
Agent-ready pulse2s ease-out ∞Static ring
Work band60s linear ∞Starts paused; pause button always present
Easing--ease-out: cubic-bezier(0.25, 1, 0.5, 1)—

Accessibility

Accessibility is part of the system, not a pass at the end. These are the rules every component follows.

  • 1.4.3Body text meets 4.5:1 on its background. The contrast matrix above is the source of truth.
  • 1.4.1Color never carries meaning alone: card origin, form errors and required fields are also stated in text.
  • 2.4.7Every interactive element has a visible focus ring, 2px, in the accent of its context.
  • 2.4.11Focused elements are never hidden: the work band stops and scrolls the focused card into view.
  • 2.2.2Anything that moves on its own for more than 5 seconds can be paused, and the choice is remembered.
  • 2.3.3prefers-reduced-motion is respected by every animation.
  • 2.5.8Primary controls are at least 44px tall; small controls are at least 32px.
  • 4.1.2Native elements first: real buttons, links, labels and lists; ARIA only where HTML has no equivalent.

How it’s built

The stack behind this site, kept deliberately small.

Pages
PHP templates rendered to static HTML at build time, in English and Portuguese.
Styles
SCSS with CSS custom properties. The same tokens drive this page.
JavaScript
Vanilla JS bundled with webpack. No framework.
Hosting
Vercel, deployed on every push to GitHub, with a preview for each branch.
Functions
The contact form and the MCP server run as Vercel Functions.
For agents
llms.txt, resume.json, AGENTS.md and an MCP server at /api/mcp.
Security
Strict Content Security Policy with no inline scripts, same-origin form endpoints, and rate limits.
Accessibility
Audited with axe-core against WCAG 2.2 AA, with contrast on gradients checked by hand.
Tools
Built with Claude Code as a pair programmer.