Overview
Dark-first personal portfolio for Jorge Roa — Stanford SDE and organized crime researcher. The palette references the subject matter: deep navy environments, cool blue precision, and a warm amber accent that signals authority without sterility. The site is always dark — not as an aesthetic trend, but because the research domain (surveillance, security, cartel mapping) maps naturally to low-light contexts.
Register: Brand (portfolio — design IS the product)
Type strategy: Single family (Quicksand) across all weights. The hierarchy is achieved through dramatic weight contrast: 300 for body, 600–700 for headings. No second display face; Quicksand’s variable weight range is wide enough to create distinct visual steps without a second import. JetBrains Mono is used exclusively for metadata captions (dates, tags).
Motion: Entrance-only animations for all UI elements. No ambient or looping motion (removed). SVG stroke-drawing on the homepage logo is the signature interaction. prefers-reduced-motion is respected throughout with inline overrides on every animation block.
Colors
Two-color strategy: cool blue (#64B5F6) as the precision accent, warm amber (#E8A87C) as the authority accent. Blue handles interactive states, outlines, focus, and code. Warm handles hierarchy inflection points: section heading underlines, stat numbers, the media page “the press” bold, and the homepage research tagline.
All neutrals are tinted toward the blue-navy hue (chroma 0.005–0.01 at OKLCH), never pure black or white. Body background is oklch(5% 0.005 230) — dark navy, not #000.
Do: Use warm for any element that signals “this matters” at a glance — first-order hierarchy signals. Reserve blue for interaction and secondary emphasis.
Don’t: Use warm as a general accent or on hover states. It should appear sparingly so it lands with weight.
Typography
Single family: Quicksand (Google Fonts, variable weight 300–700). Load only wght axis. JetBrains Mono for mono contexts.
Scale (1.25 ratio from 1rem base): - --text-xs: 0.75rem - --text-sm: 0.875rem - --text-base: 1rem - --text-md: 1.125rem - --text-lg: 1.4rem - --text-xl: 1.75rem - --text-2xl: 2.25rem - --text-3xl: clamp(2.2rem, 4.5vw, 3.4rem) — page section headings - --text-hero: clamp(2.5rem, 5vw, 5.5rem) — homepage hero only
Weight roles: 300 (body, descriptive text), 400 (UI labels, dates), 500 (card titles, listing titles), 600 (section headings, page h1s), 700 (bold emphasis inline, chip labels).
Prose cap: max-width: var(--prose-width) = 68ch on all body paragraph content.
Elevation
Three levels. No backdrop-filter on content cards (removed — glassmorphism was identified as an anti-pattern).
| Level | Use | Shadow |
|---|---|---|
| Resting | Media tiles, pub cards | border: 1px solid var(--line) only |
| Hover | Tile hover, card hover | box-shadow: 0 14px 30px rgba(0,0,0,.4) |
| Overlay | Navbar scrolled, dropdown | backdrop-filter: blur(12px) + box-shadow: 0 2px 24px rgba(0,0,0,.55) |
Blur is used only for the navbar — a true overlay layer above page content. Never on content cards.
Components
Media tile: CSS-columns masonry, natural image aspect ratios (no forced crop). Hover: border-color shifts to --blue-line, title color shifts to --blue, image scales to 1.03 (anchored transform-origin: center bottom to avoid cropping overlay text). Flag emoji always visible at 0.45 opacity; full at hover.
Filter bar: Pill buttons, border: 1px solid var(--line-blue), transparent background. Active state: --blue-soft fill + --blue text. JS filters the masonry in-place with no page reload.
Publication card: Solid dark background (rgba(22,29,39,0.95)), no blur. Peer-reviewed variant uses a left-aligned journal cover image (full opacity) + right metadata column. Hover: translateY(-6px) + top 3px gradient accent (--blue → --blue-2 → --blue-3).
Navbar: Fixed, transparent at rest. On scroll: rgba(15,20,25,0.92) + backdrop-filter: blur(12px). Nav links: slide-in gradient underline on hover (--blue → --blue-2). Mobile: inline collapse panel, no blur during transition.
Code blocks: Solarized Dark (#002b36 background, #fdf6e3 text). Syntax mapped to Solarized palette. Copy button in Solarized blue. Code-fold toggle: FontAwesome </> icon + rotate-on-open chevron.
Do’s and Don’ts
Do: - Use --warm for first-order hierarchy signals (section heading underlines, stat numbers, homepage tagline) - Use --blue for all interactive states, focus rings, and secondary emphasis - Use weight 600+ for any heading at h1/h2 level — weight 300 there reads as timid - Respect prefers-reduced-motion on every animation block, not just the last one - Use var(--prose-width) (68ch) as max-width on all prose paragraph content - Build entrance animations only — no ambient or looping motion on UI chrome
Don’t: - Use backdrop-filter: blur() on content cards — only the navbar overlay earns blur - Set headings at font-weight: 300 — reserve light weight for body and supporting text - Introduce a third accent color without retiring one of the existing two - Use pure #000 or #fff — always tint neutrals toward the blue-navy hue - Repeat raw hex values across stylesheets — use the :root token system in styles.css - Add will-change on non-compositable properties (letter-spacing, width, color)