Sections & Components
The site’s visual rhythm comes from a small set of section-level building blocks that get reused across every page: textured backgrounds, floating decorative “blobs,” animated wave dividers, and cards. None of them are page-specific — the same handful of classes assemble into the home hero, the About header, the Blog header, and every post header. This page covers each piece and, most importantly, how they’re meant to combine.
Section backgrounds
Four background classes give a section its base texture, all built the same way — a background-color plus a repeating background-image at 24px 24px, so every dotted or gridded surface on the site shares one visual rhythm:
.section-dark-dotted /* --nk-c0, white dots at 5% opacity — used for headers/CTAs */
.section-light-dotted /* --nk-c3, dark dots at 3.5% — used for secondary content bands */
.section-white-dotted /* --nk-c4, dark dots at 3.5% — About page's alternating sections */
.section-light-grid /* --nk-c3, dark grid lines at 1.8% — Home's "By the Numbers" only */
.section-light-dotted also carries a built-in top/bottom inset shadow, so it reads as a shallow recess when it’s sandwiched between two differently-toned sections (its original and still-primary use, Home’s Featured Roles between two .bg-c4 bands). A .dots-seamless modifier strips that shadow for the one place two .section-light-dotted sections instead sit back-to-back with nothing between them and are meant to read as a single continuous section rather than two stacked ones — the Blog page’s Featured Post + Post Grid, both dotted, no seam.
Most dark (and some light) dotted headers/sections also include {% include contour-lines.html %} as their first child — drifting SVG contour lines behind the content (see JavaScript). Not every dotted section has them, though: Home’s “From the Blog” section is plain .section-light-dotted with no contour lines at all, matching the flatter, quieter treatment of Areas of Focus rather than Featured Roles’ animated one.
Decorative blobs
Soft, semi-transparent circles (or free-form blob shapes) that drift slowly behind a header’s content — a bit of organic movement without competing with the text on top. Each page header that has one defines it as an inline <svg> with a dedicated position class:
<svg viewBox="0 0 200 200" class="about-header-blob">
<circle cx="100" cy="100" r="100" fill="var(--nk-c1)"></circle>
</svg>
<svg viewBox="0 0 200 200" class="about-header-blob-2">
<circle cx="100" cy="100" r="100" fill="var(--nk-c2)"></circle>
</svg>
Every blob position class (.home-hero-blob-1/2, .about-header-blob/-2, .blog-header-blob/-2, .post-header-blob, .post-cta-blob-1/2) is just position: absolute with its own corner offset, size, and opacity — then all of them share one @keyframes blob-float animation (a 6-waypoint drift-and-scale loop), each given its own animation-duration and a negative animation-delay so a page with two blobs never has them moving in sync. The convention is one larger, more visible blob (opacity ~0.12) plus one larger but fainter second blob (opacity ~0.07–0.08) anchored to the opposite corner — visible on the About and Blog headers:
All blob animation is skipped under prefers-reduced-motion: reduce.
Wave dividers
The curved SVG shape at the bottom edge of a header or section, giving the straight edge between two background colors a soft, hand-drawn boundary instead of a hard line:
<svg class="wave-divider wave-divider--64" viewBox="0 0 1440 100" preserveAspectRatio="none">
<path d="M0,40 C240,90 480,0 720,40 C960,80 1200,10 1440,50 L1440,100 L0,100 Z" class="fill-c3"></path>
</svg>
The base .wave-divider class is position: absolute; bottom: -1px with a default 44px height; --48, --56, and --64 modifiers adjust that. The path is filled with whatever color the next section is (.fill-c3, .fill-c4, .fill-c0 — matching utility classes to the palette above), so it reads as that section’s color curving up into the one above it. Every wave divider drifts gently side to side via site.js — see Wave dividers for how that animation works and the one case (the stats section) that needed a different approach.
div clipped to a wave shape via clip-path, not an SVG fill. That section's background is a grid, and a grid pattern squeezed through a wave divider's non-uniform SVG scaling renders inconsistently — full story in the JavaScript page.
Cards
Three card variants, all sharing .hover-card for the lift-on-hover behavior (translateY(-4px) + shadow):
.post-card— blog grid cards: icon, category/date label, title, summary.role-card— company/logo cards on Home’s “Featured Roles”, reused as-is by “From the Blog” (icon in place of a logo, “latest”/”featured” label instead of a date range) so both sections share one visual language.featured-post-card— the Blog page’s single featured post, a two-column grid (icon, then text) rather than stacked
.pillar-card (Home’s “Areas of Focus”) and .info-card (About page’s highlighted current-role box) are simpler — a top accent border or flat background, no hover lift, since they aren’t links.
Putting it together: the header pattern
Every page header on the site follows the same recipe, which is why they all feel like the same site despite different content:
<header class="section-dark-dotted text-c3 position-relative overflow-hidden">
<svg class="about-header-blob">...</svg>
<svg class="about-header-blob-2">...</svg>
<div class="container position-relative page-header-inner">
<!-- eyebrow, h1, intro paragraph -->
</div>
<svg class="wave-divider" ...>...</svg>
</header>
.section-dark-dottedfor the base texture, plus{% include contour-lines.html %}as the first child for the drifting contour lines- Two blobs, largest/most-visible one first
- Content wrapped in
.container.position-relativeso it stacks above the blobs (which arez-index: 0) without needing an explicitz-indexitself - A wave divider closing out the bottom edge, filled with whatever color comes next
The home hero, About header, Blog header, and every post header are all this same structure with different content and blob/wave color choices. When adding a new page header, start from an existing one and swap the content rather than building the pattern from scratch.
Content sections between headers alternate .bg-c4 (plain) and .section-white-dotted / .section-light-dotted to keep a long page from feeling flat, as seen across the About page’s four jump-linked sections (Leadership, Digital, Research, Education).