/* ==========================================================================
   Design tokens.

   Modelled on apple.com: a tinted page ground, borderless rounded cards for
   contrast, and Apple's type metrics (17px body, -0.022em tracking, tight
   negative tracking on display sizes).

   The site ships a light and a dark theme. Light is the default; the dark
   set is defined once below and applied either by the OS preference or by
   an explicit data-theme on <html> (the nav toggle, remembered in
   localStorage).

   Dark bands (.stage-dark, .tile-dark) are a deliberate design device, not
   a theme response — in the dark theme they lift off the page ground
   instead of dropping below it, so the band still reads as a band.
   ========================================================================== */
:root {
    --bg: #f5f5f7;
    --card: #ffffff;
    --card-2: #f5f5f7;
    --chip-bg: #f0f0f2;
    --field-bg: #f5f5f7;
    --text: #1d1d1f;
    /* Darker than Apple's #6e6e73/#86868b pair: those fall to 3.3-3.6:1 on a
       near-white ground, under the 4.5:1 needed for small text. */
    --text-dim: #515154;
    --text-faint: #6e6e73;
    --hairline: rgba(0, 0, 0, 0.1);
    --btn-border: rgba(0, 0, 0, 0.24);
    --btn-hover: rgba(0, 0, 0, 0.05);
    --link: #0066cc;
    --cta: #0071e3;
    --cta-hover: #0077ed;
    --shadow: 0 4px 24px rgba(0, 0, 0, 0.05);
    --tip-bg: #1d1d1f;
    --tip-fg: #ffffff;
    --green: #1a7f37;
    --red: #cf222e;
    --purple: #8250df;
    /* Empty-day cells must clear the panel they sit on (--card). */
    --l0: #e1e4e8;
    --l1: #9be9a8;
    --l2: #40c463;
    --l3: #30a14e;
    --l4: #216e39;
    --syn-comment: var(--text-faint);
    --syn-keyword: #1a63c4;
    --syn-string: #0e7a52;
    --syn-number: #b03a76;
    --syn-title: #a6500f;
    --syn-literal: #6b4fc9;

    /* Stage grounds. apple.com stacks full-bleed bands and alternates these
       three; the page never shows a single uniform ground. */
    --stage-a: #ffffff;
    --stage-b: #fbfbfd;
    --stage-c: #f5f5f7;
    /* The dark band. Its own surfaces are tokens too, so the dark theme can
       lift the whole band without touching the component layer. */
    --stage-dark: #000000;
    --stage-dark-text: #f5f5f7;
    --stage-dark-dim: #a1a1a6;
    --stage-dark-link: #2997ff;
    --band-card: #1d1d1f;
    --band-card-2: #2c2c2e;
    --band-hairline: rgba(255, 255, 255, 0.12);
    --band-btn-border: rgba(255, 255, 255, 0.28);
    --band-btn-hover: rgba(255, 255, 255, 0.1);

    --r-sm: 12px;
    --r: 18px;
    --r-lg: 22px;
    /* Apple's tiles are much rounder than a normal card. */
    --r-tile: 28px;
    --pill: 980px;
    --wrap: 980px;
    --wrap-wide: 1240px;
    /* For grids whose tiles carry visuals and need the room. */
    --wrap-full: 1680px;
    --gutter: 22px;
    --section: clamp(52px, 6vw, 84px);
    --stage-pad: clamp(58px, 7vw, 104px);

    --sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
        "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
    --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
        "Liberation Mono", monospace;

    color-scheme: light;
}

/* ==========================================================================
   Dark theme

   The same token set, re-valued. Written twice on purpose: once for the OS
   preference (skipped when the reader has explicitly chosen light) and once
   for the explicit choice, which has to win over the media query in both
   directions. No component rule below is theme-aware.
   ========================================================================== */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg: #000000;
        --card: #1d1d1f;
        --card-2: #2c2c2e;
        --chip-bg: #2c2c2e;
        --field-bg: #1d1d1f;
        --text: #f5f5f7;
        --text-dim: #a1a1a6;
        --text-faint: #86868b;
        --hairline: rgba(255, 255, 255, 0.12);
        --btn-border: rgba(255, 255, 255, 0.28);
        --btn-hover: rgba(255, 255, 255, 0.08);
        --link: #2997ff;
        --shadow: none;
        --tip-bg: #f5f5f7;
        --tip-fg: #1d1d1f;
        --green: #3fb950;
        --red: #f85149;
        --purple: #a371f7;
        /* GitHub's own dark scale; --l0 clears the panel it sits on. */
        --l0: #2c2c2e;
        --l1: #0e4429;
        --l2: #006d32;
        --l3: #26a641;
        --l4: #39d353;
        --syn-keyword: #ff7b72;
        --syn-string: #a5d6ff;
        --syn-number: #79c0ff;
        --syn-title: #d2a8ff;
        --syn-literal: #79c0ff;

        /* The stage ladder inverts: bands climb away from black instead of
           settling toward grey. */
        --stage-a: #000000;
        --stage-b: #0a0a0a;
        --stage-c: #141416;
        /* The dark band now lifts off the page rather than dropping below it,
           and its surfaces lift with it. */
        --stage-dark: #1c1c1e;
        --band-card: #2c2c2e;
        --band-card-2: #3a3a3c;
        --band-hairline: rgba(255, 255, 255, 0.14);
        --band-btn-border: rgba(255, 255, 255, 0.3);
        --band-btn-hover: rgba(255, 255, 255, 0.12);

        color-scheme: dark;
    }
}

:root[data-theme="dark"] {
    --bg: #000000;
    --card: #1d1d1f;
    --card-2: #2c2c2e;
    --chip-bg: #2c2c2e;
    --field-bg: #1d1d1f;
    --text: #f5f5f7;
    --text-dim: #a1a1a6;
    --text-faint: #86868b;
    --hairline: rgba(255, 255, 255, 0.12);
    --btn-border: rgba(255, 255, 255, 0.28);
    --btn-hover: rgba(255, 255, 255, 0.08);
    --link: #2997ff;
    --shadow: none;
    --tip-bg: #f5f5f7;
    --tip-fg: #1d1d1f;
    --green: #3fb950;
    --red: #f85149;
    --purple: #a371f7;
    /* GitHub's own dark scale; --l0 clears the panel it sits on. */
    --l0: #2c2c2e;
    --l1: #0e4429;
    --l2: #006d32;
    --l3: #26a641;
    --l4: #39d353;
    --syn-keyword: #ff7b72;
    --syn-string: #a5d6ff;
    --syn-number: #79c0ff;
    --syn-title: #d2a8ff;
    --syn-literal: #79c0ff;

    /* The stage ladder inverts: bands climb away from black instead of
       settling toward grey. */
    --stage-a: #000000;
    --stage-b: #0a0a0a;
    --stage-c: #141416;
    /* The dark band now lifts off the page rather than dropping below it,
       and its surfaces lift with it. */
    --stage-dark: #1c1c1e;
    --band-card: #2c2c2e;
    --band-card-2: #3a3a3c;
    --band-hairline: rgba(255, 255, 255, 0.14);
    --band-btn-border: rgba(255, 255, 255, 0.3);
    --band-btn-hover: rgba(255, 255, 255, 0.12);

    color-scheme: dark;
}

/* ==========================================================================
   Base
   ========================================================================== */
*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    padding: 0;
    font-family: var(--sans);
    font-size: 17px;
    line-height: 1.47059;
    letter-spacing: -0.022em;
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.wrap {
    max-width: var(--wrap);
    margin: 0 auto;
    padding: 0 var(--gutter);
}

a {
    color: var(--link);
    text-decoration: none;
}
a:visited {
    color: var(--link);
}
a:hover {
    text-decoration: underline;
    text-underline-offset: 2px;
}

p {
    margin: 0 0 14px 0;
}

h1,
h2,
h3 {
    font-weight: 600;
    color: var(--text);
}

/* Display sizes track tighter, the way SF Pro Display does. Apple sets
   display type at 600, never 700 — the extra weight is what made this read
   as a generic portfolio rather than an Apple page. */
h1 {
    font-size: clamp(2.1rem, 4vw, 3rem);
    line-height: 1.08;
    letter-spacing: -0.026em;
    font-weight: 600;
    margin: 0 0 16px 0;
}

h2 {
    font-size: clamp(1.5rem, 2.2vw, 1.95rem);
    line-height: 1.12;
    letter-spacing: -0.02em;
    margin: 0;
}

h3 {
    font-size: 1.3rem;
    line-height: 1.2;
    letter-spacing: -0.02em;
    margin: 0 0 8px 0;
}

ul {
    margin: 12px 0 0 0;
    padding-left: 20px;
}

li {
    margin-bottom: 9px;
    color: var(--text-dim);
    font-size: 0.94rem;
    line-height: 1.5;
}

li b,
li strong {
    color: var(--text);
    font-weight: 600;
}

b,
strong {
    font-weight: 600;
}

small {
    color: var(--text-dim);
    font-size: 0.82rem;
}

.meta {
    color: var(--text-faint);
}

.clear {
    clear: both;
}

/* Several components set an explicit display and are also toggled with the
   hidden attribute (.oss-toolbar, .oss-orgs, #note-subfilters, #gb-form).
   An author display always beats the UA sheet's [hidden] rule, so without
   this they stay on screen empty until their data arrives. */
[hidden] {
    display: none !important;
}

hr {
    border: none;
    border-top: 1px solid var(--hairline);
    margin: 32px 0;
}

::selection {
    background: rgba(41, 151, 255, 0.28);
}

:focus-visible {
    outline: 3px solid var(--link);
    outline-offset: 3px;
    border-radius: 6px;
}

/* Apple separates by ground colour and gaps, not rules. The legacy
   "* * *" dividers collapse into plain section spacing. */
.divider {
    height: 0;
    border: 0;
    margin: var(--section) 0 0 0;
    font-size: 0;
    line-height: 0;
    color: transparent;
}

/* ==========================================================================
   Nav — Apple's is 44px tall with 12px links
   ========================================================================== */
.topnav {
    position: sticky;
    top: 0;
    z-index: 50;
    background: color-mix(in srgb, var(--bg) 82%, transparent);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    backdrop-filter: saturate(180%) blur(20px);
    border-bottom: 1px solid var(--hairline);
}

.topnav-inner {
    max-width: var(--wrap);
    margin: 0 auto;
    padding: 0 var(--gutter);
    height: 44px;
    display: flex;
    align-items: center;
    /* Apple spaces the nav items apart across the full bar rather than
       bunching them at the left. */
    justify-content: space-between;
    gap: 20px;
    overflow-x: auto;
    scrollbar-width: none;
}
.topnav-inner::-webkit-scrollbar {
    display: none;
}

.topnav a {
    color: var(--text-dim);
    font-size: 0.75rem;
    letter-spacing: -0.01em;
    white-space: nowrap;
    flex-shrink: 0;
    transition: color 0.2s ease;
}
.topnav a:hover {
    color: var(--text);
    text-decoration: none;
}

.nav-logo {
    display: flex;
    align-items: center;
    flex-shrink: 0;
}
.nav-logo img {
    width: 22px;
    height: 22px;
    border-radius: 6px;
    object-fit: cover;
    display: block;
}

h1,
h2 {
    scroll-margin-top: 76px;
}
section[id],
.section {
    scroll-margin-top: 76px;
}

/* ==========================================================================
   Stages — the core apple.com primitive.

   The page is a stack of full-bleed bands. Each one owns its ground colour,
   centres its content, and holds very little: an optional eyebrow, one
   large headline, one normal-weight subhead, and a row of blue chevron
   links. Anything denser goes in a tile inside the stage, not in the
   stage's own type.
   ========================================================================== */
.stage {
    position: relative;
    padding: var(--stage-pad) 0;
    text-align: center;
    background: var(--stage-a);
    color: var(--text);
    scroll-margin-top: 44px;
}

/* Alternating grounds. Applied per-section so the page never shows one
   uniform colour top to bottom. */
.stage-tint {
    background: var(--stage-b);
}
.stage-grey {
    background: var(--stage-c);
}

/* A permanently dark band. Overrides the text tokens locally so every
   child — headline, subhead, links — flips without theme-specific rules
   scattered through the component layer. */
.stage-dark {
    background: var(--stage-dark);
    --text: var(--stage-dark-text);
    --text-dim: var(--stage-dark-dim);
    --text-faint: #86868b;
    --link: var(--stage-dark-link);
    --card: var(--band-card);
    --card-2: var(--band-card-2);
    --chip-bg: var(--band-card-2);
    --hairline: var(--band-hairline);
    --btn-border: var(--band-btn-border);
    --btn-hover: var(--band-btn-hover);
    --shadow: none;
    color: var(--stage-dark-text);
}

.stage-inner {
    max-width: var(--wrap);
    margin: 0 auto;
    padding: 0 var(--gutter);
}
.stage-inner.wide {
    max-width: var(--wrap-wide);
}

/* Widest container, with a tighter gutter to match. Three project tiles in
   the standard 1240px wrap left each one about 390px, which is too narrow
   for a tile that has to hold a chart or a diagram. */
.stage-inner.widest {
    max-width: var(--wrap-full);
    padding-left: clamp(16px, 2.2vw, 32px);
    padding-right: clamp(16px, 2.2vw, 32px);
}

/* Stage headline. Much larger than a normal h2 and set at 600 — this is
   the single biggest thing that reads as "Apple". */
.stage-title {
    font-size: clamp(2.25rem, 4.6vw, 3.5rem);
    line-height: 1.06;
    letter-spacing: -0.03em;
    font-weight: 600;
    margin: 0;
    color: var(--text);
}

/* "Experience. Where I've worked." — the statement at full contrast with
   the descriptor trailing in grey on the same line, exactly the pattern
   apple.com/store uses for its section heads. */
.section-head {
    margin-bottom: 34px;
}
.section-head h2 {
    display: inline;
    font-size: clamp(2.25rem, 4.6vw, 3.5rem);
    line-height: 1.06;
    letter-spacing: -0.03em;
    font-weight: 600;
}
.section-head h2::after {
    content: ".";
}
.head-sub {
    display: inline;
    font-size: clamp(2.25rem, 4.6vw, 3.5rem);
    line-height: 1.06;
    letter-spacing: -0.03em;
    /* Normal weight, not semibold. Apple's grey continuation is the same
       size as the statement but never as heavy. */
    font-weight: 600;
    color: var(--text-dim);
    margin-left: 0.25em;
}

/* Stage subhead — one short line under the headline, normal weight. */
.stage-sub {
    font-size: clamp(1.15rem, 2vw, 1.6rem);
    line-height: 1.25;
    letter-spacing: -0.02em;
    font-weight: 400;
    color: var(--text-dim);
    margin: 12px 0 0 0;
    max-width: 34ch;
    margin-left: auto;
    margin-right: auto;
}

/* Lead paragraphs on a stage of their own — larger than body copy, centred,
   and measured short so they read as display text rather than prose. */
.stage-lead {
    font-size: clamp(1.2rem, 2.1vw, 1.75rem);
    line-height: 1.35;
    letter-spacing: -0.02em;
    font-weight: 400;
    color: var(--text-dim);
    max-width: 44ch;
    margin: 0 auto 20px auto;
}
.stage-lead:last-of-type {
    margin-bottom: 0;
}
.stage-lead a {
    color: var(--link);
}

/* ==========================================================================
   Hero — the opening stage
   ========================================================================== */
.hero {
    background: var(--stage-a);
    color: var(--text);
    padding: clamp(80px, 11vw, 150px) 0 clamp(64px, 9vw, 112px);
    text-align: center;
}

/* The roles lead, above the name — Apple's small coloured overline. */
/* The roles lead the hero now: larger than the name's supporting copy and
   close enough to the name itself that the two read as a pair rather than a
   billboard with a caption. */
.hero-roles {
    display: block;
    font-size: clamp(1.35rem, 2.5vw, 1.9rem);
    line-height: 1.2;
    letter-spacing: -0.02em;
    font-weight: 600;
    color: var(--cta);
    margin: 0 0 10px 0;
}
.hero-roles .sep {
    color: currentColor;
    opacity: 0.5;
    padding: 0 5px;
}
.hero-roles .dim {
    color: currentColor;
}

.hero p {
    color: var(--text-dim);
    font-size: clamp(1.1rem, 1.8vw, 1.45rem);
    line-height: 1.32;
    letter-spacing: -0.02em;
    /* Wide enough that the two-clause line lands in three rows rather than
       five, which is what pushed the intro copy off a laptop screen. */
    max-width: 52ch;
    margin: 0 auto 12px auto;
}

.hero-contact {
    margin-top: 26px !important;
}
.hero-contact small {
    color: var(--text-faint);
    font-size: 0.85rem;
}

/* Long-form article header. Centred like a stage, but the title is set well
   below hero scale so it stays a headline rather than a billboard. */
.article-hero {
    padding: clamp(56px, 8vw, 104px) 0 clamp(28px, 4vw, 48px);
}
.article-hero h1 {
    font-size: clamp(2.4rem, 5.2vw, 4rem);
    margin-bottom: 0;
}
.article-hero .hero-roles {
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin-bottom: 16px;
}

/* ==========================================================================
   Calls to action

   Apple pairs at most one filled pill with blue chevron text links. The
   old design used five outlined pills in a row, which is why the hero read
   as a link dump rather than a stage.
   ========================================================================== */
.links {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 28px;
    justify-content: center;
    align-items: center;
    margin: 28px 0 0 0 !important;
    max-width: none !important;
}

/* Default: a blue chevron text link. */
.links a {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    color: var(--link);
    font-size: 1.0625rem;
    letter-spacing: -0.01em;
    transition: opacity 0.2s ease;
}
.links a::after {
    content: "\203A";
    font-size: 1.15em;
    line-height: 1;
    transform: translateY(-1px);
}
.links a:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}
.links a:hover::after {
    text-decoration: none;
}

/* The one filled pill. */
.links a.primary {
    background: var(--cta);
    border-radius: var(--pill);
    color: #fff;
    padding: 11px 22px;
    font-size: 1rem;
    transition: background 0.2s ease;
}
.links a.primary::after {
    content: none;
}
.links a.primary:hover {
    background: var(--cta-hover);
    text-decoration: none;
}
.links svg {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
}

/* Standalone chevron link, for use outside .links rows. */
.chev {
    color: var(--link);
    font-size: 1.0625rem;
    letter-spacing: -0.01em;
    white-space: nowrap;
}
.chev::after {
    content: " \203A";
    font-size: 1.15em;
}

/* ==========================================================================
   Tiles — the apple.com/store grid

   Two up, 12px gutters, deeply rounded, each one centring a headline over
   its own ground. Used for the shorter, list-like sections.
   ========================================================================== */
.tile-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    text-align: left;
}

.tile {
    background: var(--card);
    border-radius: var(--r-tile);
    padding: 40px 34px;
    overflow: hidden;
}

/* A tile that leads with a centred headline, store-style. */
.tile-head {
    text-align: center;
    margin-bottom: 26px;
}
.tile-title {
    font-size: clamp(1.5rem, 2.4vw, 2rem);
    line-height: 1.1;
    letter-spacing: -0.025em;
    font-weight: 600;
    margin: 0;
}
.tile-sub {
    font-size: 1.0625rem;
    line-height: 1.4;
    font-weight: 400;
    color: var(--text-dim);
    margin: 8px 0 0 0;
}
.tile-full {
    grid-column: 1 / -1;
}

/* auto-fit rather than a fixed two columns: two tiles sit two across, and a
   third joins the same row instead of dropping to a new one. Empty tracks
   collapse, so the count drives the layout. */
.tile-grid.auto {
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}

/* A dark tile, for the same contrast a dark stage gives but inside the
   grid. Reuses the stage-dark token overrides. */
.tile-dark {
    background: var(--stage-dark);
    --text: var(--stage-dark-text);
    --text-dim: var(--stage-dark-dim);
    --text-faint: #86868b;
    --link: var(--stage-dark-link);
    --card-2: var(--band-card-2);
    --chip-bg: var(--band-card-2);
    --hairline: var(--band-hairline);
    color: var(--stage-dark-text);
}

/* ==========================================================================
   Project tiles

   Projects sit side by side rather than each taking a full stage. The tile
   is a flex column so the link row can be pinned to the bottom, keeping
   those rows aligned across tiles of differing content length.
   ========================================================================== */
.project-tile {
    display: flex;
    flex-direction: column;
    text-align: center;
}

.project-tile .project-card {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
}

/* Down from the full display size — inside a tile this is a sub-heading. */
.project-tile .project-title {
    font-size: clamp(1.5rem, 2.4vw, 2rem);
    line-height: 1.1;
    letter-spacing: -0.025em;
    margin-bottom: 12px;
}

.project-tile .project-card p {
    font-size: 1.0625rem;
    line-height: 1.45;
    max-width: 52ch;
    margin-bottom: 14px;
}

.project-tile .project-media {
    margin: 22px 0 0;
}

/* The dark tile's own surface is already near-black, so the media panel
   lifts off it rather than sitting on the light --card-2. */
.tile-dark .project-media {
    background: rgba(255, 255, 255, 0.045);
}

/* ==========================================================================
   Video demo in a project tile

   The recording is a whole interface, so at tile width its text is far too
   small to read. It loops silently as a moving thumbnail and opens full
   size in an overlay on click, which is where it is actually watchable.
   ========================================================================== */
.video-media {
    position: relative;
    display: block;
    width: 100%;
    padding: 0;
    border: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
}
.video-media video {
    width: 100%;
    height: auto;
    display: block;
}

.play-badge {
    position: absolute;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 14px 7px 11px;
    border-radius: var(--pill);
    background: rgba(0, 0, 0, 0.62);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: #fff;
    font-size: 0.85rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    white-space: nowrap;
    transition: background 0.2s ease;
}
.play-badge svg {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
}
.video-media:hover .play-badge {
    background: rgba(0, 0, 0, 0.8);
}

/* ==========================================================================
   Video overlay
   ========================================================================== */
.vbox {
    position: fixed;
    inset: 0;
    z-index: 120;
    background: rgba(0, 0, 0, 0.86);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(16px, 4vw, 56px);
}
.vbox video {
    width: 100%;
    max-width: 1400px;
    max-height: 100%;
    border-radius: var(--r);
    background: #000;
    display: block;
}
.vbox-close {
    position: absolute;
    top: 18px;
    right: 20px;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    border: none;
    background: rgba(255, 255, 255, 0.14);
    color: #fff;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
}
.vbox-close:hover {
    background: rgba(255, 255, 255, 0.26);
}

/* Nothing should autoplay for a visitor who asked for stillness; the poster
   and the badge already say what it is. */
@media (prefers-reduced-motion: reduce) {
    .video-media video {
        display: none;
    }
    .video-media {
        aspect-ratio: 1200 / 722;
        background-image: url("assets/projects/debatebench-demo.jpg");
        background-size: cover;
        background-position: center;
    }
}

/* Pinned to the bottom of the tile so link rows line up side by side. */
.project-tile .project-links {
    margin: auto 0 0 0 !important;
    padding-top: 22px;
    font-size: 1rem;
}
.project-tile .project-links a {
    font-size: 1rem;
}

@media (max-width: 800px) {
    .tile-grid {
        grid-template-columns: minmax(0, 1fr);
    }
    .tile {
        padding: 32px 24px;
    }
}

/* ==========================================================================
   Sections
   ========================================================================== */
.section {
    margin: 0;
}

.eyebrow {
    display: block;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-bottom: 10px;
}

/* Scroll reveal. Only ever hidden when scripting is available, so a failed
   or absent observer can never leave the page blank. */
html.js .reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 0.6s cubic-bezier(0.28, 0.11, 0.32, 1),
        transform 0.6s cubic-bezier(0.28, 0.11, 0.32, 1);
}
html.js .reveal.in {
    opacity: 1;
    transform: none;
}
@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }
    html.js .reveal {
        opacity: 1;
        transform: none;
        transition: none;
    }
}

/* ==========================================================================
   Card primitive — borderless, rounded, contrast by ground
   ========================================================================== */
.entry,
.blog-card,
.paper,
.gh-panel,
.oss-list,
.gb-panel {
    background: var(--card);
    border-radius: var(--r-tile);
    box-shadow: var(--shadow);
    border: none;
}

/* Inside a tile the card primitive would stack a card on a card. Apple
   separates items within a tile by hairlines instead, so the tile stays the
   only rounded surface. */
.tile .blog-card,
.tile .paper {
    background: none;
    box-shadow: none;
    border-radius: 0;
    padding-left: 0;
    padding-right: 0;
    margin: 0;
    border-top: 1px solid var(--hairline);
}
/* The item directly after the tile's heading opens the list, so it carries
   no rule. Keyed off the heading rather than :first-of-type, which the
   heading's own <div> would otherwise claim. */
.tile-head + .blog-card,
.tile-head + .paper {
    border-top: none;
    padding-top: 0;
}

/* Panels sitting inside a centred stage carry left-aligned content. */
.stage .oss-list,
.stage .gh-panel,
.stage .gb-panel,
.stage .blog-card,
.stage .paper {
    text-align: left;
}

/* Experience. Dense bullet content, so it stays left-aligned inside its
   tile even though the surrounding stage centres. */
.entry {
    padding: 28px 34px 26px;
    margin-bottom: 10px;
    border-radius: var(--r-tile);
    text-align: left;
}
.entry:last-child {
    margin-bottom: 0;
}

@media (max-width: 700px) {
    .entry {
        padding: 26px 24px 28px;
    }
}

.entry-top {
    display: flex;
    align-items: flex-start;
    gap: 16px;
}

.entry-logo {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    object-fit: contain;
    border-radius: 11px;
    background: #fff;
    padding: 4px;
}

.entry-meta {
    flex: 1 1 auto;
    min-width: 0;
}

.entry-head-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 14px;
    flex-wrap: wrap;
}

.entry-head {
    font-weight: 600;
    font-size: 1.22rem;
    letter-spacing: -0.02em;
    line-height: 1.2;
}

.entry-head .meta {
    display: block;
    font-weight: 400;
    font-size: 0.76rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-top: 6px;
}

.entry-links {
    font-size: 0.8rem;
    color: var(--text-faint);
    flex-shrink: 0;
}
.entry-links a {
    color: var(--link);
}

.entry-sub {
    color: var(--text-dim);
    font-size: 0.88rem;
    margin: 8px 0 0 0;
}

.entry ul {
    margin-top: 18px;
}

/* Collapsed detail. Native <details>, so it works without scripting and
   the content stays in the page for search and for Cmd-F. */
.entry-details {
    margin-top: 14px;
}

.entry-details > summary {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    width: fit-content;
    cursor: pointer;
    list-style: none;
    color: var(--link);
    font-size: 0.95rem;
    letter-spacing: -0.01em;
}
/* Safari draws its own triangle unless this is cleared too. */
.entry-details > summary::-webkit-details-marker {
    display: none;
}
.entry-details > summary:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* Chevron, rotating to point down when open. */
.entry-details > summary::after {
    content: "";
    width: 7px;
    height: 7px;
    border-right: 1.6px solid currentColor;
    border-bottom: 1.6px solid currentColor;
    transform: rotate(-45deg) translate(-1px, -1px);
    transition: transform 0.2s ease;
}
.entry-details[open] > summary::after {
    transform: rotate(45deg) translate(-1px, -1px);
}

.entry-details > summary:focus-visible {
    outline: 3px solid var(--link);
    outline-offset: 3px;
    border-radius: 6px;
}

.entry-details ul {
    margin-top: 14px;
}

/* ==========================================================================
   Projects — eyebrow, headline, copy, then the image, then CTAs
   ========================================================================== */
/* A project is now a stage, not a card: centred headline, one line of copy,
   chevron links, and the image carrying the lower half. */
.project-card {
    background: none;
    border-radius: 0;
    box-shadow: none;
    overflow: visible;
    margin: 0;
    text-align: center;
}

.project-body {
    padding: 0;
}

.project-title {
    margin: 0 0 14px 0;
    font-size: clamp(2.25rem, 4.6vw, 3.5rem);
    line-height: 1.06;
    letter-spacing: -0.03em;
    font-weight: 600;
}

.project-card p {
    margin: 0 auto 18px auto;
    color: var(--text-dim);
    font-size: clamp(1.05rem, 1.6vw, 1.3rem);
    line-height: 1.4;
    letter-spacing: -0.02em;
    max-width: 46ch;
}

.project-tags {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 7px;
    margin: 0 0 4px 0 !important;
}

.project-tag {
    display: inline-flex;
    align-items: center;
    border-radius: 980px;
    padding: 4px 12px;
    font-size: 0.72rem;
    font-weight: 500;
    letter-spacing: 0;
    background: var(--chip-bg);
    color: var(--text-dim);
}

.project-media {
    display: block;
    position: relative;
    margin: 40px auto 0;
    background: var(--card-2);
    border-radius: var(--r-tile);
    overflow: hidden;
}
.project-media img,
.project-media video {
    width: 100%;
    height: auto;
    display: block;
    transition: transform 0.7s cubic-bezier(0.28, 0.11, 0.32, 1);
}
.project-card:hover .project-media img {
    transform: scale(1.015);
}

/* Chevron links, matching the hero. */
.project-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 12px 28px;
    padding: 0;
    /* Overrides the 46ch measure .project-card sets on its paragraphs —
       without this the centred row is centred inside a narrow left-hung box. */
    max-width: none;
    margin: 20px auto 0 auto !important;
}
.project-links a {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 1.0625rem;
    letter-spacing: -0.01em;
    color: var(--link);
}
.project-links a::after {
    content: "\203A";
    font-size: 1.15em;
    line-height: 1;
    transform: translateY(-1px);
}
.project-links a:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}
.project-links svg {
    width: 14px;
    height: 14px;
}
.project-emoji {
    font-size: 0.95em;
    line-height: 1;
}

/* ==========================================================================
   Open source
   ========================================================================== */
.oss-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin: 0 0 14px;
}

.oss-filters {
    display: inline-flex;
    gap: 4px;
    background: var(--card);
    border-radius: 980px;
    padding: 4px;
}

.oss-filter {
    font-family: inherit;
    font-size: 0.82rem;
    letter-spacing: -0.01em;
    background: none;
    color: var(--text-dim);
    border: none;
    border-radius: 980px;
    padding: 5px 16px;
    cursor: pointer;
    transition: background 0.2s ease, color 0.2s ease;
}
.oss-filter:hover {
    color: var(--text);
}
.oss-filter.active {
    background: var(--cta);
    color: #fff;
}

.oss-count {
    font-size: 0.8em;
    opacity: 0.7;
    margin-left: 6px;
    font-variant-numeric: tabular-nums;
}

.oss-sort-wrap {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
}

.oss-sort-label {
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-faint);
}

.oss-sort {
    font-family: inherit;
    font-size: 0.82rem;
    color: var(--text);
    background: var(--card);
    border: none;
    border-radius: 980px;
    padding: 7px 14px;
    cursor: pointer;
}

.oss-list:empty {
    display: none;
}
.oss-list {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.oss-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 15px 24px;
    border-bottom: 1px solid var(--hairline);
    color: var(--text);
    font-size: 0.94rem;
    transition: background 0.18s ease;
}
.oss-row:last-child {
    border-bottom: none;
}
.oss-row:hover {
    background: var(--card-2);
    text-decoration: none;
}

.oss-icon {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
}
.oss-icon.merged {
    color: var(--purple);
}
.oss-icon.open {
    color: var(--green);
}
.oss-icon.closed {
    color: var(--red);
}

.oss-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.oss-row:hover .oss-title {
    color: var(--link);
}

.oss-repo {
    font-family: var(--mono);
    font-size: 0.72rem;
    color: var(--text-faint);
    flex-shrink: 0;
}

.oss-date {
    font-size: 0.74rem;
    color: var(--text-faint);
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
}

.oss-diff {
    display: inline-flex;
    gap: 7px;
    font-family: var(--mono);
    font-size: 0.72rem;
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
}
.oss-add {
    color: var(--green);
}
.oss-del {
    color: var(--red);
}

.oss-more {
    font-family: inherit;
    font-size: 0.86rem;
    background: none;
    border: none;
    border-top: 1px solid var(--hairline);
    color: var(--link);
    cursor: pointer;
    padding: 15px 24px;
    text-align: left;
    transition: background 0.18s ease;
}
.oss-more:hover {
    background: var(--card-2);
}

.oss-fallback {
    padding: 15px 24px;
    color: var(--text-faint);
    font-size: 0.9rem;
}

/* Placeholder rows shown while the pull request feed is loading. */
.oss-row-skeleton {
    cursor: default;
}
.oss-row-skeleton:hover {
    background: none;
}
.oss-skel {
    height: 10px;
    border-radius: 4px;
    background: var(--card-2);
    animation: oss-skel-pulse 1.4s ease-in-out infinite;
}
.oss-skel-title {
    flex: 1;
    max-width: 340px;
}
.oss-skel-repo {
    width: 110px;
    margin-left: auto;
}
@keyframes oss-skel-pulse {
    0%,
    100% {
        opacity: 0.5;
    }
    50% {
        opacity: 1;
    }
}

.oss-orgs {
    margin-top: 24px;
}
.oss-orgs-group + .oss-orgs-group {
    margin-top: 20px;
}
.oss-orgs-label {
    display: block;
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-bottom: 12px;
}
.oss-orgs-list {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.oss-org-chip {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    background: var(--card);
    border-radius: 980px;
    padding: 6px 16px 6px 6px;
    font-size: 0.86rem;
    color: var(--text);
    transition: transform 0.2s ease;
}
.oss-org-chip:hover {
    transform: translateY(-1px);
    text-decoration: none;
}
.oss-org-chip img {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    display: block;
}
.oss-org-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 19px;
    height: 19px;
    padding: 0 6px;
    border-radius: 980px;
    background: var(--chip-bg);
    color: var(--text-dim);
    font-size: 0.68rem;
    font-weight: 600;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

@media (max-width: 620px) {
    .oss-repo,
    .oss-date,
    .oss-diff {
        display: none;
    }
}

/* ==========================================================================
   GitHub graph
   ========================================================================== */
.gh-panel {
    padding: 26px 30px 20px;
}

.gh-graph {
    width: 100%;
}
.gh-graph svg.gh-cal {
    display: block;
    width: 100%;
    height: auto;
}
.gh-month {
    font-family: var(--sans);
    font-size: 10px;
    fill: var(--text-faint);
}
.gh-legend {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 5px;
    margin-top: 10px;
    font-size: 11px;
    color: var(--text-faint);
}
.gh-graph .l0 {
    fill: var(--l0);
}
.gh-graph .l1 {
    fill: var(--l1);
}
.gh-graph .l2 {
    fill: var(--l2);
}
.gh-graph .l3 {
    fill: var(--l3);
}
.gh-graph .l4 {
    fill: var(--l4);
}
.gh-graph-caption {
    text-align: left;
    margin: 14px 0 0 0;
    color: var(--text-faint);
}

.gh-tooltip {
    position: absolute;
    opacity: 0;
    visibility: hidden;
    transform: translateY(4px);
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
    background: var(--tip-bg);
    color: var(--tip-fg);
    font-size: 12px;
    line-height: 1;
    padding: 7px 10px;
    border-radius: 8px;
    white-space: nowrap;
    pointer-events: none;
    z-index: 60;
}
.gh-tooltip.show {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

/* ==========================================================================
   Writing
   ========================================================================== */
.blog-card {
    display: block;
    padding: 28px 30px;
    margin-bottom: 14px;
    color: var(--text);
    transition: transform 0.3s ease;
}
.blog-card:hover {
    transform: translateY(-2px);
    text-decoration: none;
}
.blog-card-title {
    display: block;
    font-weight: 600;
    font-size: 1.45rem;
    letter-spacing: -0.022em;
    margin-bottom: 10px;
}
.blog-card:hover .blog-card-title {
    color: var(--link);
}
.blog-card-desc {
    display: block;
    color: var(--text-dim);
    font-size: 0.98rem;
    max-width: 62ch;
    margin-bottom: 12px;
}
.blog-card .meta {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* ==========================================================================
   Research
   ========================================================================== */
.paper {
    padding: 24px 30px;
    margin: 0 0 14px 0;
}
.paper-title {
    display: block;
    font-weight: 600;
    font-size: 1.12rem;
    letter-spacing: -0.02em;
    line-height: 1.3;
    margin-bottom: 10px;
    color: var(--text);
}
.paper-authors {
    display: block;
    color: var(--text-dim);
    font-size: 0.92rem;
}
.paper-venue {
    display: block;
    color: var(--text-faint);
    font-size: 0.88rem;
    margin-top: 4px;
}
.paper-badge {
    display: inline-block;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--link);
    background: color-mix(in srgb, var(--link) 16%, transparent);
    border-radius: 5px;
    padding: 2px 8px;
    margin-left: 9px;
    vertical-align: 2px;
}

/* ==========================================================================
   Guestbook / comments
   ========================================================================== */
.gb-panel {
    padding: 26px 30px;
}

.gb-btn {
    font-family: inherit;
    font-size: 0.86rem;
    letter-spacing: -0.01em;
    background: none;
    color: var(--link);
    border: 1px solid var(--link);
    border-radius: 980px;
    padding: 8px 18px;
    margin: 0 10px 10px 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 9px;
    vertical-align: middle;
    transition: background 0.2s ease;
}
.gb-btn svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}
.gb-btn:hover {
    background: color-mix(in srgb, var(--link) 12%, transparent);
}
#gb-form .gb-btn,
#cm-form .gb-btn {
    background: var(--cta);
    border-color: var(--cta);
    color: #fff;
}
#gb-form .gb-btn:hover,
#cm-form .gb-btn:hover {
    background: var(--cta-hover);
}

#gb-form,
#cm-form {
    margin: 16px 0;
}

#gb-message,
#cm-message {
    font-family: inherit;
    font-size: 0.95rem;
    width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--hairline);
    border-radius: var(--r-sm);
    padding: 13px 15px;
    margin-bottom: 12px;
    background: var(--field-bg);
    color: var(--text);
    resize: vertical;
}
#gb-message::placeholder,
#cm-message::placeholder {
    color: var(--text-faint);
}
#gb-message:focus,
#cm-message:focus {
    outline: none;
    border-color: var(--link);
}

/* The rows sit inside the panel's padding, but their hover wash and
   hairlines run a little wider so they read as one list rather than a
   stack of boxes. The side padding here is what the rows bleed back into. */
#gb-entries,
#cm-entries {
    max-height: 480px;
    overflow-y: auto;
    overflow-x: hidden;
    /* The bottom strip the fade sits over. Because it is padding rather
       than content, scrolling to the end leaves the last note fully lit —
       the gradient only ever dims empty space or a note still to come. */
    padding: 0 14px 44px;
    margin: 4px -14px 0;
    -webkit-mask-image: linear-gradient(
        to bottom,
        #000 calc(100% - 44px),
        transparent 100%
    );
    mask-image: linear-gradient(
        to bottom,
        #000 calc(100% - 44px),
        transparent 100%
    );
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--hairline) transparent;
}
#gb-entries::-webkit-scrollbar,
#cm-entries::-webkit-scrollbar {
    width: 6px;
}
#gb-entries::-webkit-scrollbar-thumb,
#cm-entries::-webkit-scrollbar-thumb {
    background: var(--hairline);
    border-radius: 980px;
}

.gb-entry {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 15px 14px;
    margin: 0 -14px;
    border-top: 1px solid var(--hairline);
    border-radius: var(--r-sm);
    transition: background 0.35s cubic-bezier(0.28, 0.11, 0.32, 1);
}
/* The list opens under the form, so the first row carries no rule. The
   hover wash would otherwise sit under a hairline it has swallowed. */
.gb-entry:first-child {
    border-top: none;
}
.gb-entry:hover,
.gb-entry:focus-within {
    background: var(--card-2);
}
.gb-entry:hover,
.gb-entry:hover + .gb-entry,
.gb-entry:focus-within,
.gb-entry:focus-within + .gb-entry {
    border-top-color: transparent;
}

.gb-avatar {
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    margin-top: 1px;
    object-fit: cover;
    background: var(--chip-bg);
    /* An inset ring instead of a border, so a light avatar still has an
       edge without the image being resized by it. */
    box-shadow: inset 0 0 0 1px var(--hairline);
}
.gb-avatar-mono {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-faint);
    font-size: 0.9rem;
    font-weight: 600;
    letter-spacing: 0;
    line-height: 1;
    user-select: none;
}

.gb-body {
    flex: 1 1 auto;
    min-width: 0;
}
.gb-meta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    row-gap: 2px;
}
.gb-name {
    font-weight: 600;
    font-size: 0.94rem;
    letter-spacing: -0.012em;
    overflow-wrap: anywhere;
}
.gb-date {
    color: var(--text-faint);
    font-size: 0.78rem;
    letter-spacing: -0.01em;
    white-space: nowrap;
    margin-left: 10px;
}
.gb-text {
    margin: 4px 0 0 0;
    color: var(--text-dim);
    font-size: 0.95rem;
    line-height: 1.47059;
    white-space: pre-wrap;
    overflow-wrap: break-word;
}

/* Empty and error states. Centred and airy rather than a stray line of
   small print at the top of the list. */
.gb-empty {
    margin: 0;
    padding: 30px 0 26px;
    text-align: center;
    font-size: 0.92rem;
}

.gb-delete {
    flex: 0 0 auto;
    align-self: flex-start;
    margin: -2px -4px 0 8px;
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: none;
    color: var(--text-faint);
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
    border-radius: 50%;
    opacity: 0;
    transition: opacity 0.25s ease, background 0.2s ease, color 0.2s ease;
}
.gb-entry:hover .gb-delete,
.gb-entry:focus-within .gb-delete {
    opacity: 1;
}
.gb-delete:hover {
    color: var(--red);
    background: color-mix(in srgb, var(--red) 14%, transparent);
}
.gb-delete:focus-visible {
    opacity: 1;
    outline: 2px solid var(--link);
    outline-offset: 2px;
}
/* Touch has no hover, so the control has to stay put. */
@media (hover: none) {
    .gb-delete {
        opacity: 1;
        color: var(--red);
    }
}

@media (max-width: 700px) {
    .gb-entry {
        gap: 12px;
        padding: 14px 12px;
        margin: 0 -12px;
    }
    #gb-entries,
    #cm-entries {
        padding: 0 12px;
        margin: 4px -12px 0;
    }
    .gb-avatar {
        width: 34px;
        height: 34px;
    }
}

/* ==========================================================================
   Email copy widget
   ========================================================================== */
.email-copy {
    position: relative;
    cursor: pointer;
    color: inherit;
    border-bottom: 1px dashed currentColor;
}
.email-copy-tip {
    position: absolute;
    left: 50%;
    bottom: 100%;
    transform: translateX(-50%) translateY(-4px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
    background: var(--tip-bg);
    color: var(--tip-fg);
    font-size: 12px;
    line-height: 1;
    padding: 6px 10px;
    border-radius: 6px;
    white-space: nowrap;
    pointer-events: none;
}
.email-copy-tip.show {
    opacity: 1;
    visibility: visible;
    transform: translateX(-50%) translateY(-7px);
}

/* ==========================================================================
   Footer
   ========================================================================== */
/* A quiet closing strip rather than a full stage: a hairline, one row of
   small links, and the date. It keeps the page ground, so it reads as the
   end of the last section instead of another band. */
.site-footer {
    background: var(--stage-a);
    border-top: 1px solid var(--hairline);
    padding: 30px 0 38px;
    text-align: left;
}

/* Side by side on a wide screen, stacked on a narrow one — either way the
   footer stays a couple of lines tall. */
.footer-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 22px 32px;
}

.cool-guys-label {
    color: var(--text-faint);
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin: 0 0 10px 0;
}

.cool-guys {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0;
}
/* Small, and in full colour: the size keeps the row quiet, so the faces
   do not need dimming on top of it. */
.cool-guys a img {
    width: 28px;
    height: 28px;
    border-radius: 8px;
    display: block;
    border: 1px solid var(--hairline);
    transition: transform 0.25s ease;
}
/* focus-visible as well as hover — a keyboard user never hovers. */
.cool-guys a:hover img,
.cool-guys a:focus-visible img {
    transform: translateY(-1px);
}

.webring {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0;
}
.webring img {
    image-rendering: pixelated;
    display: block;
}
.webring-nav {
    display: inline-flex;
    gap: 6px;
}
/* Outlined chips: they carry the same weight as the badge beside them, and
   sit quieter than three underlined links in a row. */
.webring-nav a {
    font-size: 0.72rem;
    line-height: 1;
    padding: 5px 10px;
    border: 1px solid var(--hairline);
    border-radius: var(--pill);
    color: var(--text-faint);
    text-decoration: none;
    transition: color 0.2s ease, border-color 0.2s ease;
}
.webring-nav a:hover,
.webring-nav a:focus-visible {
    color: var(--text);
    border-color: var(--btn-border);
}

.footer-meta {
    margin: 22px 0 0 0;
}
/* Sits on the end of the date line rather than in a block of its own, so
   it reads as an aside. */
.footer-meta a {
    color: var(--text-faint);
    text-decoration: none;
}
.footer-meta a:hover,
.footer-meta a:focus-visible {
    color: var(--text);
    text-decoration: underline;
}

.other-stuff {
    margin-top: 44px;
    text-align: center;
}
.other-stuff a {
    color: var(--text-faint);
}
.other-stuff a:hover {
    color: var(--text);
}

/* ==========================================================================
   Notes grid (other.html)
   ========================================================================== */
.note-filters {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    margin: 0 0 20px 0;
}
.note-subfilters {
    margin-top: -10px;
}
.note-filter {
    font-family: inherit;
    font-size: 0.82rem;
    letter-spacing: -0.01em;
    background: var(--card);
    color: var(--text-dim);
    border: none;
    border-radius: 980px;
    padding: 7px 16px;
    cursor: pointer;
    transition: background 0.2s ease, color 0.2s ease;
}
.note-filter:hover {
    color: var(--text);
}
.note-filter.active {
    background: var(--cta);
    color: #fff;
}

.note-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    gap: 28px 18px;
    /* Cards read as a left-aligned list even though the stage centres. */
    text-align: left;
    margin-top: 34px;
}
.note-card[hidden] {
    display: none;
}
.note-thumb {
    display: block;
    border-radius: var(--r-sm);
    background: var(--card);
    overflow: hidden;
    transition: transform 0.3s ease;
}
.note-thumb:hover {
    transform: translateY(-3px);
}
.note-thumb img {
    display: block;
    width: 100%;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    object-position: top;
}
.note-card-title {
    font-weight: 600;
    font-size: 0.92rem;
    letter-spacing: -0.015em;
    margin-top: 12px;
}
.note-card-title a {
    color: var(--text);
}
.note-card-title a:hover {
    color: var(--link);
}
.note-tag {
    font-size: 0.76rem;
    color: var(--text-faint);
    margin-top: 3px;
}
.note-card-links {
    font-size: 0.78rem;
    margin-top: 5px;
}

/* ==========================================================================
   Blog article prose
   ========================================================================== */
.topnav + main.wrap {
    padding-top: clamp(44px, 6vw, 72px);
}

article {
    font-size: 1.0625rem;
    line-height: 1.6;
    max-width: 700px;
    margin: 0 auto;
}
article > h1 {
    font-size: clamp(2.2rem, 5vw, 3.4rem);
    margin-bottom: 24px;
}
article > p,
article > ul,
article > ol {
    color: var(--text-dim);
}
article h2 {
    margin: 44px 0 16px;
}
article h3 {
    font-size: 1.2rem;
    margin: 32px 0 10px;
}
article ul,
article ol {
    margin: 16px 0;
}

.post-meta {
    color: var(--text-faint);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin-bottom: 28px;
}

.tldr {
    background: var(--card);
    border-radius: var(--r);
    padding: 24px 28px;
    margin: 24px 0 34px 0;
    font-size: 0.98rem;
}
.tldr p {
    margin: 0 0 12px 0;
    color: var(--text-dim);
}
.tldr p:last-child {
    margin-bottom: 0;
}
.tldr strong {
    color: var(--text);
}
.tldr .tldr-links {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}
.tldr .tldr-links a {
    border: 1px solid var(--link);
    border-radius: 980px;
    padding: 6px 16px;
    font-size: 0.85rem;
    color: var(--link);
}
.tldr .tldr-links a:hover {
    background: color-mix(in srgb, var(--link) 12%, transparent);
    text-decoration: none;
}

article blockquote {
    margin: 22px 0;
    padding: 4px 0 4px 20px;
    border-left: 3px solid var(--hairline);
    color: var(--text-dim);
}
article blockquote p:last-child {
    margin-bottom: 0;
}

article code {
    font-family: var(--mono);
    font-size: 0.85em;
    letter-spacing: 0;
    background: var(--card);
    padding: 2px 6px;
    border-radius: 5px;
    color: var(--text);
}

article pre {
    background: var(--card);
    border-radius: var(--r-sm);
    padding: 20px 22px;
    overflow-x: auto;
    margin: 24px 0;
}
article pre code {
    background: none;
    padding: 0;
    font-size: 0.82rem;
    line-height: 1.6;
}

article table {
    border-collapse: collapse;
    width: 100%;
    margin: 24px 0;
    font-size: 0.9rem;
    background: var(--card);
    border-radius: var(--r-sm);
    overflow: hidden;
}
article th,
article td {
    border-bottom: 1px solid var(--hairline);
    padding: 11px 16px;
    text-align: left;
    color: var(--text-dim);
    vertical-align: top;
}
article th {
    color: var(--text);
    font-weight: 600;
    font-size: 0.82rem;
}
article tr:last-child td {
    border-bottom: none;
}

article figure {
    margin: 32px 0;
}
article figure img {
    max-width: 100%;
    display: block;
    margin: 0 auto;
    border-radius: var(--r-sm);
}
article figcaption {
    color: var(--text-faint);
    font-size: 0.84rem;
    text-align: center;
    margin-top: 12px;
}

/* Blog page furniture sits at article width too. */
#comments-thread,
#blog-views,
h2#stats,
.topnav + main.wrap > p {
    max-width: 700px;
    margin-left: auto;
    margin-right: auto;
}

/* ==========================================================================
   Syntax highlighting (highlight.js token classes)
   ========================================================================== */
.hljs-comment,
.hljs-quote {
    color: var(--syn-comment);
    font-style: italic;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-operator {
    color: var(--syn-keyword);
}
.hljs-string,
.hljs-doctag {
    color: var(--syn-string);
}
.hljs-number,
.hljs-symbol {
    color: var(--syn-number);
}
.hljs-title,
.hljs-title.function_,
.hljs-built_in {
    color: var(--syn-title);
}
.hljs-literal,
.hljs-meta {
    color: var(--syn-literal);
}
.hljs-params,
.hljs-variable,
.hljs-attr {
    color: inherit;
}

/* ==========================================================================
   Search — nav trigger and the ⌘K palette
   ========================================================================== */
.nav-search {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-family: inherit;
    font-size: 0.75rem;
    letter-spacing: -0.01em;
    color: var(--text-dim);
    background: none;
    border: 1px solid var(--hairline);
    border-radius: var(--pill);
    padding: 3px 10px 3px 9px;
    cursor: pointer;
    transition: color 0.2s ease, border-color 0.2s ease;
}
.nav-search:hover {
    color: var(--text);
    border-color: var(--text-faint);
}
.nav-search svg {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
}
.nav-search kbd {
    font-family: inherit;
    font-size: 0.95em;
    color: var(--text-faint);
    letter-spacing: 0;
}

/* ==========================================================================
   Theme toggle

   Sits beside the search trigger and borrows its pill. Which icon shows is
   decided in CSS, under the same two conditions the theme itself uses, so
   the button is already correct on first paint.
   ========================================================================== */
.nav-theme {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-dim);
    background: none;
    border: 1px solid var(--hairline);
    border-radius: var(--pill);
    width: 26px;
    height: 22px;
    padding: 0;
    cursor: pointer;
    transition: color 0.2s ease, border-color 0.2s ease;
}
.nav-theme:hover {
    color: var(--text);
    border-color: var(--text-faint);
}
.nav-theme svg {
    width: 13px;
    height: 13px;
}

.nav-theme .icon-sun {
    display: none;
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .nav-theme .icon-moon {
        display: none;
    }
    :root:not([data-theme="light"]) .nav-theme .icon-sun {
        display: block;
    }
}
:root[data-theme="dark"] .nav-theme .icon-moon {
    display: none;
}
:root[data-theme="dark"] .nav-theme .icon-sun {
    display: block;
}

.search-overlay {
    position: fixed;
    inset: 0;
    z-index: 100;
    background: rgba(0, 0, 0, 0.32);
    -webkit-backdrop-filter: saturate(120%) blur(4px);
    backdrop-filter: saturate(120%) blur(4px);
    display: flex;
    justify-content: center;
    /* Sits high rather than centred, so the list grows downward into empty
       space instead of shifting the field as results change. */
    align-items: flex-start;
    padding: clamp(56px, 12vh, 140px) var(--gutter) var(--gutter);
}

/* The scroll lock keeps the page still behind the palette. */
html.search-open,
html.search-open body {
    overflow: hidden;
}

.search-panel {
    width: 100%;
    max-width: 600px;
    background: var(--card);
    border-radius: var(--r);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22);
    overflow: hidden;
}

.search-field {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 16px;
    border-bottom: 1px solid var(--hairline);
}
.search-icon {
    width: 17px;
    height: 17px;
    flex-shrink: 0;
    color: var(--text-faint);
}
#search-input {
    flex: 1 1 auto;
    min-width: 0;
    font-family: inherit;
    font-size: 1.0625rem;
    letter-spacing: -0.022em;
    color: var(--text);
    background: none;
    border: none;
    outline: none;
    padding: 2px 0;
}
#search-input::placeholder {
    color: var(--text-faint);
}
.search-esc {
    flex-shrink: 0;
    font-family: inherit;
    font-size: 0.72rem;
    color: var(--text-faint);
    background: var(--chip-bg);
    border: none;
    border-radius: 6px;
    padding: 3px 7px;
    cursor: pointer;
}
.search-esc:hover {
    color: var(--text);
}

.search-results {
    list-style: none;
    margin: 0;
    padding: 6px;
    max-height: min(52vh, 420px);
    overflow-y: auto;
}
.sr {
    display: block;
    padding: 10px 12px;
    border-radius: 10px;
    cursor: pointer;
    margin: 0;
    color: var(--text);
    font-size: 0.94rem;
}
.sr.on {
    background: var(--card-2);
}
.sr-t {
    display: block;
    font-weight: 600;
    letter-spacing: -0.015em;
    line-height: 1.3;
}
.sr-m {
    display: block;
    margin-top: 2px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-faint);
}
.sr-s {
    display: block;
    margin-top: 4px;
    font-size: 0.86rem;
    line-height: 1.4;
    color: var(--text-dim);
    /* Snippets are one line so the list stays scannable. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.search-results mark {
    background: rgba(0, 113, 227, 0.16);
    color: inherit;
    border-radius: 3px;
    padding: 0 1px;
}

.search-empty,
.search-hint {
    margin: 0;
    padding: 14px 18px;
    font-size: 0.82rem;
    color: var(--text-faint);
}
.search-hint {
    border-top: 1px solid var(--hairline);
}

/* Briefly marks whatever the search jumped to on the current page. */
.search-hit {
    animation: search-flash 1.6s ease;
}
@keyframes search-flash {
    0%,
    60% {
        box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.55);
    }
    100% {
        box-shadow: 0 0 0 3px rgba(0, 113, 227, 0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .search-hit {
        animation: none;
    }
    .oss-skel {
        animation: none;
    }
}

@media (max-width: 620px) {
    .nav-search kbd {
        display: none;
    }
    /* The nav scrolls horizontally on small screens, which would carry the
       trigger off the end — and there is no ⌘K on a phone. Pin it so it is
       always reachable, with the links scrolling underneath. */
    .nav-search {
        position: sticky;
        right: 34px;
        background: var(--bg);
        box-shadow: -10px 0 10px -5px var(--bg);
    }
    .nav-theme {
        position: sticky;
        right: 0;
        background: var(--bg);
    }
    .search-overlay {
        padding-top: 40px;
    }
}

/* ==========================================================================
   Short viewports

   The hero is type only now, so it no longer has to hold a tall image band
   and both it and the intro copy fit a laptop screen with room to spare.
   These rules only trim the padding a little on short viewports; the type
   keeps its full size, which is what made the earlier version feel cramped.
   ========================================================================== */
@media (min-width: 700px) and (max-height: 1000px) {
    .hero {
        padding: clamp(44px, 7.4vh, 150px) 0 clamp(30px, 4.8vh, 112px);
    }
    .intro {
        padding: clamp(30px, 4.8vh, 104px) 0;
    }
    .intro .stage-lead {
        margin-bottom: 16px;
    }
    .intro .hero-contact {
        margin-top: 18px !important;
    }
}

/* ==========================================================================
   Responsive
   ========================================================================== */
@media (max-width: 700px) {
    .entry,
    .blog-card,
    .paper,
    .gh-panel,
    .gb-panel {
        padding-left: 22px;
        padding-right: 22px;
    }
    /* These two sit directly on the stage, which already supplies the
       gutter, so re-padding them would double it. */
    .project-body,
    .project-links {
        padding-left: 0;
        padding-right: 0;
    }
    .tile .blog-card,
    .tile .paper {
        padding-left: 0;
        padding-right: 0;
    }
    .oss-row,
    .oss-more {
        padding-left: 18px;
        padding-right: 18px;
    }
    .links {
        gap: 10px 20px;
    }
    .links a.primary {
        padding: 9px 18px;
    }
    .oss-sort-wrap {
        margin-left: 0;
    }
    .section-head {
        max-width: none;
    }
    /* Stages tighten up rather than keeping desktop's generous air. */
    .stage {
        padding: clamp(44px, 9vw, 64px) 0;
    }
    .hero {
        padding: 64px 0 52px;
    }
}

/* ==========================================================================
   Notes: showing only the newest few (other.html)
   ========================================================================== */
/* Only while the height is being animated, so a card's hover lift is never
   clipped at rest. */
.note-grid.is-animating {
    overflow: hidden;
}
.note-card.is-revealing {
    animation: note-reveal 0.42s cubic-bezier(0.28, 0.11, 0.32, 1) both;
}
@keyframes note-reveal {
    from {
        opacity: 0;
        transform: translateY(8px);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

.note-more {
    display: flex;
    justify-content: center;
    margin-top: 30px;
}
.note-more-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-family: inherit;
    font-size: 0.88rem;
    letter-spacing: -0.015em;
    color: var(--link);
    background: none;
    border: none;
    border-radius: var(--pill);
    padding: 8px 14px;
    cursor: pointer;
    transition: background 0.2s ease, color 0.2s ease;
}
.note-more-btn:hover {
    background: var(--btn-hover);
    color: var(--cta-hover);
}
.note-more-btn svg {
    width: 11px;
    height: 11px;
    transition: transform 0.3s ease;
}
.note-more-btn[aria-expanded="true"] svg {
    transform: rotate(180deg);
}

.note-empty {
    text-align: center;
    font-size: 0.9rem;
    color: var(--text-faint);
    margin: 34px 0 0;
}

@media (prefers-reduced-motion: reduce) {
    .note-card.is-revealing {
        animation: none;
    }
    .note-more-btn svg {
        transition: none;
    }
}
