/* Professional surface. Loaded only by /index.html.
   Deliberately shares nothing with the Matrix stylesheets. */

:root {
    --bg:          #0a0a0a;
    --surface:     #111111;
    --hairline:    #1f1f1f;
    --hairline-lit:#2e2e2e;
    --text:        #e8e8e8;
    --text-muted:  #8a8a8a;
    --accent:      #7ee787;

    --font-sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, system-ui, "Segoe UI", sans-serif;
    --font-mono: "Fira Code", ui-monospace, SFMono-Regular, Menlo, monospace;

    --dur: 150ms;
    --measure: 68ch;
    /* 880, not the spec's 720: the two-column hero needs the width, and a
       single container value keeps the header aligned with the hero. Prose
       is still bounded by --measure, not by the column. */
    --col: 880px;

    /* diagram-only tones, deliberately below body-text contrast */
    --wire:      #262626;
    --node-idle: #4d4d4d;
    /* One full traversal of the hero diagram, down the stack and back up.
       Every stop in the topo keyframes is a percentage, so the schedule is
       duration-independent: changing this one value rescales travel, dwell,
       and both end pauses together and cannot break the one-active-layer
       invariant. Tune the pace here, never in the keyframes. */
    --topo-cycle: 30s;
}

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

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

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-sans);
    font-size: clamp(1rem, 0.97rem + 0.15vw, 1.0625rem);
    line-height: 1.65;
    -webkit-font-smoothing: antialiased;
}

/* Single measure. Whitespace is the primary structural device. */
.wrap {
    width: 100%;
    max-width: var(--col);
    margin-inline: auto;
    padding-inline: 1.5rem;
}

h1, h2, h3 { line-height: 1.2; margin: 0; font-weight: 600; letter-spacing: -0.015em; }
h1 { font-size: clamp(2rem, 1.6rem + 2vw, 2.75rem); }
h2 { font-size: 0.8125rem; }
h3 { font-size: 1.0625rem; }

p { margin: 0 0 1rem; max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

a {
    color: var(--text);
    text-decoration: underline;
    text-decoration-color: var(--text-muted);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.25em;
    transition: color var(--dur) ease;
}
a:hover { color: var(--accent); text-decoration-color: var(--accent); }

/* Focus rings use --accent (12.89:1), never --hairline (1.20:1, below the
   WCAG 1.4.11 3:1 threshold for non-text contrast). Never suppressed. */
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 2px;
}

/* Mono is an accent, not the body voice: labels, metadata, tech lines,
   the wordmark, and the palette. */
.mono { font-family: var(--font-mono); font-variant-ligatures: none; }

.section-label {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    text-transform: lowercase;
    color: var(--text-muted);
    margin: 0 0 1.5rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--hairline);
}

section { padding-block: clamp(3rem, 2rem + 4vw, 5rem); }

.skip-link {
    position: absolute; left: -9999px;
    background: var(--surface); color: var(--text);
    padding: 0.75rem 1rem; z-index: 100;
}
.skip-link:focus { left: 1rem; top: 1rem; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
    }
}

/* ---- header ---- */
.site-header { border-bottom: 1px solid var(--hairline); }
.header-inner {
    display: flex; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: 1rem; row-gap: 0.5rem; min-height: 64px;
}
.wordmark { font-size: 0.875rem; color: var(--text); text-decoration: none; }
.wordmark:hover { color: var(--accent); }
.site-nav { display: flex; align-items: center; flex-wrap: wrap; gap: 1.25rem; row-gap: 0; }
.site-nav a {
    font-size: 0.875rem; color: var(--text-muted); text-decoration: none;
    min-height: 44px; display: inline-flex; align-items: center;
}
.site-nav a:hover { color: var(--accent); }
.matrix-entry { font-size: 1rem; }

/* ---- footer ---- */
.site-footer {
    border-top: 1px solid var(--hairline);
    padding-block: clamp(3rem, 2rem + 4vw, 5rem);
}
.footer-links {
    list-style: none; margin: 1.5rem 0 0; padding: 0;
    display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem;
    font-family: var(--font-mono); font-size: 0.875rem;
}
.footer-links a { min-height: 44px; display: inline-flex; align-items: center; }
.alt-interface {
    margin-top: 2.5rem; font-size: 0.8125rem; color: var(--text-muted);
}
.alt-interface a { color: var(--text-muted); }

/* ---- hero ---- */
.hero { padding-block: clamp(3.5rem, 2.5rem + 5vw, 7rem) clamp(3rem, 2rem + 4vw, 5rem); }
.positioning {
    margin: 0.75rem 0 2rem; font-size: 1rem;
    color: var(--text); letter-spacing: 0.02em;
}
.hero-thesis { color: var(--text); font-size: 1.0625rem; }
.hero-role {
    margin-top: 2rem; font-size: 0.8125rem; color: var(--text-muted);
}
.hero-actions {
    list-style: none; margin: 2rem 0 0; padding: 0;
    display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem;
    font-family: var(--font-mono); font-size: 0.875rem;
}
.hero-actions a { min-height: 44px; display: inline-flex; align-items: center; }

/* ---- selected systems ----
   Single column, not a grid: each card states a thesis, and a thesis needs a
   full measure of text. A grid forces identical dimensions, which makes every
   card read as equally weighted. */
.system {
    border: 1px solid var(--hairline);
    border-radius: 4px;
    padding: 1.75rem;
    margin-bottom: 1.25rem;
    background: var(--surface);
    transition: border-color var(--dur) ease;
}
.system:hover { border-color: var(--hairline-lit); }
.system--lead { padding: 2.25rem; }
.system h3 { margin-bottom: 0.75rem; }
.system-thesis { color: var(--text); }
.system-notes {
    margin: 1.25rem 0 0; padding-left: 1.1rem;
    max-width: var(--measure); color: var(--text-muted);
    font-size: 0.9375rem;
}
.system-notes li { margin-bottom: 0.5rem; }
.system-notes li::marker { color: var(--accent); }
.system-tech {
    margin-top: 1.25rem; font-size: 0.8125rem; color: var(--text-muted);
}
.system-evidence { margin-top: 0.5rem; font-size: 0.8125rem; }
.see-all { margin-top: 2rem; font-size: 0.875rem; }

.sr-only {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Empty until their tasks land. Without this they contribute ~320px of dead
   space between the last card and the footer. */
section:empty { padding-block: 0; }

/* ---- background depth ----
   A technical field, not content: grid lines at 2.2% white and one very low
   radial. Fixed and non-interactive, so it never enters layout or hit-testing. */
body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image:
        radial-gradient(60rem 40rem at 78% -10%, rgba(126, 231, 135, 0.035), transparent 70%),
        linear-gradient(to right, rgba(255, 255, 255, 0.022) 1px, transparent 1px),
        linear-gradient(to bottom, rgba(255, 255, 255, 0.022) 1px, transparent 1px);
    background-size: 100% 100%, 72px 72px, 72px 72px;
}

/* ---- motion system ----
   One entrance shape, one duration, one easing. The hero entrance is pure CSS
   with `both` fill, so there is no state where content stays invisible. */
@keyframes enter {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
}

.hero-copy > *,
.hero-visual {
    animation: enter 420ms ease both;
}
.hero-copy > :nth-child(1) { animation-delay: 40ms; }
.hero-copy > :nth-child(2) { animation-delay: 100ms; }
.hero-copy > :nth-child(3) { animation-delay: 160ms; }
.hero-copy > :nth-child(4) { animation-delay: 220ms; }
.hero-copy > :nth-child(5) { animation-delay: 280ms; }
.hero-copy > :nth-child(6) { animation-delay: 340ms; }
/* The diagram resolves after the copy has landed. */
.hero-visual { animation-delay: 420ms; animation-duration: 560ms; }

/* Scroll reveal. Gated on .pro-js so the page is complete without JavaScript,
   and revealed once — never re-hidden on scroll-back. */
.pro-js .reveal {
    opacity: 0;
    transform: translateY(10px);
}
.pro-js .reveal.is-in {
    opacity: 1;
    transform: none;
    transition: opacity 420ms ease, transform 420ms ease;
}

@media (prefers-reduced-motion: reduce) {
    .pro-js .reveal { opacity: 1; transform: none; }
}

/* ---- header: matrix discovery ---- */
.matrix-slot { position: relative; display: inline-flex; align-items: center; }
.matrix-entry {
    color: var(--text-muted);
    font-size: 0.8125rem;
    letter-spacing: 0.02em;
}
.matrix-entry .caret { color: var(--accent); margin-right: 0.15em; }
.matrix-entry:hover .caret,
.matrix-entry:focus-visible .caret { animation: caret 1.1s steps(1) infinite; }

/* Bound to hover/focus rather than running forever: an indefinite blink is the
   same WCAG 2.2.2 problem as an indefinite rotation. */
@keyframes caret {
    0%, 55%   { opacity: 1; }
    56%, 100% { opacity: 0.3; }
}

/* Absolutely positioned, so appearing and disappearing costs zero layout. */
.matrix-cue {
    position: absolute;
    top: calc(100% - 8px);
    right: 0;
    white-space: nowrap;
    font-size: 0.6875rem;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    background: var(--surface);
    border: 1px solid var(--hairline-lit);
    border-radius: 3px;
    padding: 0.25rem 0.5rem;
    pointer-events: none;
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 300ms ease, transform 300ms ease;
}
.matrix-cue.is-shown { opacity: 1; transform: none; }

/* ---- hero ---- */
.hero-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 240px;
    gap: 3.5rem;
    align-items: start;
}
.hero-copy { min-width: 0; }

.ident-anchor { color: var(--text); }
.ident-sep { color: var(--hairline-lit); margin: 0 0.4em; }

/* The sizer reserves the widest label so swapping causes no reflow. */
.ident-rotator { position: relative; display: inline-block; vertical-align: bottom; }
.ident-sizer { visibility: hidden; }
.ident-item {
    position: absolute; left: 0; top: 0;
    color: var(--accent);
    opacity: 0;
    transition: opacity 300ms ease;
}
.ident-item.is-active { opacity: 1; }

.hero-support { color: var(--text-muted); font-size: 0.9375rem; margin-top: 1rem; }

/* ---- hero visual: abstraction-layer topology ---- */
.hero-visual { min-width: 0; }
.topology { display: block; width: 100%; height: auto; overflow: visible; }

.topo-wire line { stroke: var(--wire); stroke-width: 1; }
.topo-bound line {
    stroke: var(--wire); stroke-width: 1; stroke-dasharray: 2 5;
}
/* At 7px against --wire these read as compression artifacts rather than
   labels. 8px against --node-idle stays quiet but resolves as text. */
.topo-bound-label {
    fill: var(--node-idle); font-family: var(--font-mono); font-size: 8px;
    text-anchor: end; letter-spacing: 0.04em;
}

/* Each layer is one group (circle + label) so one animated property pair
   drives both: `stroke` inherits down to the child circle, `fill` inherits
   down to the child text. That lets a single @keyframes rule per layer light
   up the ring and brighten its label together, and guarantees they can never
   fall out of sync the way two independently-animated elements could. The
   circle and the label must NOT set their own `stroke`/`fill` respectively,
   or the explicit value on the child would beat the inherited animated one. */
.topo-layer { stroke: var(--node-idle); fill: var(--text-muted); }
.topo-label {
    font-family: var(--font-mono); font-size: 10px;
    dominant-baseline: middle; letter-spacing: 0.02em;
}
.topo-node { fill: var(--bg); stroke-width: 1.5; }
.topo-packet { fill: var(--accent); }

/*
   Traversal schedule: down through six layers, dwell at the bottom, back up
   through six layers, dwell at the top, repeat. The percentages below are the
   schedule; they are independent of the cycle length, which lives in
   --topo-cycle. The seconds column is the 18.4s reference the ratios were
   derived from (percent = seconds / 18.4 * 100) and is kept only so the
   proportions stay legible; multiply by --topo-cycle / 18.4s for real times.
   At the current 30s cycle that is 1.47s of travel between layers, 0.98s of
   dwell on each, and 2.28s of extra pause at each end. Re-derive from this
   table rather than from the percentages if the proportions ever change.

   ref (s)  | % of cycle | state
   0.0      | 0          | at layer 1 (top pause begins)
   2.0      | 10.87      | leave layer 1
   2.9      | 15.76      | arrive layer 2
   3.5      | 19.02      | leave layer 2
   4.4      | 23.91      | arrive layer 3
   5.0      | 27.17      | leave layer 3
   5.9      | 32.07      | arrive layer 4
   6.5      | 35.33      | leave layer 4
   7.4      | 40.22      | arrive layer 5
   8.0      | 43.48      | leave layer 5
   8.9      | 48.37      | arrive layer 6
   10.9     | 59.24      | leave layer 6 (bottom pause ends)
   11.8     | 64.13      | arrive layer 5
   12.4     | 67.39      | leave layer 5
   13.3     | 72.28      | arrive layer 4
   13.9     | 75.54      | leave layer 4
   14.8     | 80.43      | arrive layer 3
   15.4     | 83.70      | leave layer 3
   16.3     | 88.59      | arrive layer 2
   16.9     | 91.85      | leave layer 2
   17.8     | 96.74      | arrive layer 1
   18.4     | 100        | cycle end (top pause continues into next cycle)

   The packet's transform holds its offset across each dwell (paired
   percentage stops, equal value) and interpolates linearly across each
   travel segment. Offsets are the node spacing (60px) times layers crossed.
*/
@keyframes topo-packet-travel {
    0%, 10.87%   { transform: translateY(0); }
    15.76%, 19.02%  { transform: translateY(60px); }
    23.91%, 27.17%  { transform: translateY(120px); }
    32.07%, 35.33%  { transform: translateY(180px); }
    40.22%, 43.48%  { transform: translateY(240px); }
    48.37%, 59.24%  { transform: translateY(300px); }
    64.13%, 67.39%  { transform: translateY(240px); }
    72.28%, 75.54%  { transform: translateY(180px); }
    80.43%, 83.70%  { transform: translateY(120px); }
    88.59%, 91.85%  { transform: translateY(60px); }
    96.74%, 100%    { transform: translateY(0); }
}
.topo-packet { animation: topo-packet-travel var(--topo-cycle) linear infinite; }

/* One @keyframes per layer: each holds the idle colours except during its own
   dwell windows (from the table above), when it switches to the accent pair.
   Each edge is written as two offsets 0.03% apart (about 9ms of a 30s
   cycle) rather than as one repeated offset. Two keyframes at the exact same
   percentage do NOT produce a zero-duration jump: the engine keeps both, and
   because they carry different values it interpolates across the ENTIRE gap
   to the next differing offset, not just up to the duplicate. On this table
   that gap can span most of the cycle, which smears one layer's colour into
   the next dwell window instead of cutting it off, briefly making several
   layers read as active at once. Separating the edge into two distinct,
   nearly-adjacent offsets keeps the fast snap while giving the interpolator
   an unambiguous, vanishingly short segment to run it over. 0.03% is far
   inside the 4.89% minimum gap between any two layers' windows, so no two
   layers can straddle it at once. */
@keyframes topo-l1 {
    0%, 10.87%      { stroke: var(--accent); fill: var(--text); }
    10.90%, 96.71%  { stroke: var(--node-idle); fill: var(--text-muted); }
    96.74%, 100%    { stroke: var(--accent); fill: var(--text); }
}
@keyframes topo-l2 {
    0%, 15.73%      { stroke: var(--node-idle); fill: var(--text-muted); }
    15.76%, 19.02%  { stroke: var(--accent); fill: var(--text); }
    19.05%, 88.56%  { stroke: var(--node-idle); fill: var(--text-muted); }
    88.59%, 91.85%  { stroke: var(--accent); fill: var(--text); }
    91.88%, 100%    { stroke: var(--node-idle); fill: var(--text-muted); }
}
@keyframes topo-l3 {
    0%, 23.88%      { stroke: var(--node-idle); fill: var(--text-muted); }
    23.91%, 27.17%  { stroke: var(--accent); fill: var(--text); }
    27.20%, 80.40%  { stroke: var(--node-idle); fill: var(--text-muted); }
    80.43%, 83.70%  { stroke: var(--accent); fill: var(--text); }
    83.73%, 100%    { stroke: var(--node-idle); fill: var(--text-muted); }
}
@keyframes topo-l4 {
    0%, 32.04%      { stroke: var(--node-idle); fill: var(--text-muted); }
    32.07%, 35.33%  { stroke: var(--accent); fill: var(--text); }
    35.36%, 72.25%  { stroke: var(--node-idle); fill: var(--text-muted); }
    72.28%, 75.54%  { stroke: var(--accent); fill: var(--text); }
    75.57%, 100%    { stroke: var(--node-idle); fill: var(--text-muted); }
}
@keyframes topo-l5 {
    0%, 40.19%      { stroke: var(--node-idle); fill: var(--text-muted); }
    40.22%, 43.48%  { stroke: var(--accent); fill: var(--text); }
    43.51%, 64.10%  { stroke: var(--node-idle); fill: var(--text-muted); }
    64.13%, 67.39%  { stroke: var(--accent); fill: var(--text); }
    67.42%, 100%    { stroke: var(--node-idle); fill: var(--text-muted); }
}
@keyframes topo-l6 {
    0%, 48.34%      { stroke: var(--node-idle); fill: var(--text-muted); }
    48.37%, 59.24%  { stroke: var(--accent); fill: var(--text); }
    59.27%, 100%    { stroke: var(--node-idle); fill: var(--text-muted); }
}

.topo-l1 { animation: topo-l1 var(--topo-cycle) linear infinite; }
.topo-l2 { animation: topo-l2 var(--topo-cycle) linear infinite; }
.topo-l3 { animation: topo-l3 var(--topo-cycle) linear infinite; }
.topo-l4 { animation: topo-l4 var(--topo-cycle) linear infinite; }
.topo-l5 { animation: topo-l5 var(--topo-cycle) linear infinite; }
.topo-l6 { animation: topo-l6 var(--topo-cycle) linear infinite; }

.hero-visual-caption {
    color: var(--text-muted); font-size: 0.6875rem; margin-top: 0.75rem;
}

/* Frozen, the diagram must still read as a diagram: park the signal on the top
   layer and hold that layer active rather than leaving an empty end state. */
@media (prefers-reduced-motion: reduce) {
    .topo-packet { animation: none; transform: translateY(0); }
    .topo-layer { animation: none; }
    .topo-l1 { stroke: var(--accent); fill: var(--text); }
}

/* ---- selected systems: engineered system record ---- */
.system-index {
    font-size: 0.6875rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin: 0 0 0.75rem;
}
.system-desc { color: var(--text-muted); font-size: 0.9375rem; margin-top: 0.75rem; }

.system-spec {
    display: grid;
    grid-template-columns: 8.5rem minmax(0, 1fr);
    gap: 0.5rem 1.25rem;
    margin: 1.75rem 0 0;
    padding-top: 1.25rem;
    border-top: 1px solid var(--hairline);
    font-size: 0.875rem;
}
.system-spec dt {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    padding-top: 0.15rem;
}
.system-spec dd { margin: 0; color: var(--text); }

/* The four followers are records, not essays: a narrower label column and a
   tighter row gap than the lead's read as denser data, keeping GPS the one
   card with room to breathe. */
.system-spec--compact {
    grid-template-columns: 7rem minmax(0, 1fr);
    gap: 0.35rem 1.25rem;
}

.system-tech { padding-top: 1.5rem; }
.system-evidence { color: var(--text-muted); }

/* ---- narrow viewports ----
   The topology collapses rather than compressing: it moves below the copy and
   the boundary captions drop out (too tight against the labels at this
   width), while the spine and all six layers survive. */
@media (max-width: 819px) {
    .hero-grid { grid-template-columns: 1fr; gap: 0; }
    .hero-visual { max-width: 300px; margin-top: 3rem; }
    .topo-bound-label { display: none; }
}

/* The labelled matrix entry is ~56px wider than the bare glyph it replaced,
   which overflowed a 320px viewport by 15px. Beside the wordmark the nav has
   only ~172px, so it wrapped internally and stranded the matrix control on a
   left-aligned second row — which also dragged its cue off-screen. Stacking
   the header gives the nav the full measure and keeps the matrix entry last,
   at the right edge, so the cue stays anchored inside the padding. */
@media (max-width: 420px) {
    .header-inner {
        flex-direction: column;
        align-items: stretch;
        padding-block: 0.625rem;
    }
    .site-nav {
        width: 100%;
        justify-content: space-between;
        gap: 0.5rem;
    }
    .site-nav a { font-size: 0.8125rem; }
    .matrix-entry { font-size: 0.75rem; }
}

@media (max-width: 559px) {
    /* Inline, the identity line wraps and orphans the separator at the end of
       the first line. Stack the two labels instead and drop the separator. */
    .positioning {
        display: flex; flex-direction: column;
        align-items: flex-start; gap: 0.1rem;
    }
    .ident-sep { display: none; }

    .system-spec { grid-template-columns: 1fr; gap: 0.15rem; }
    .system-spec dt { padding-top: 0.65rem; }
    .system-spec dt:first-of-type { padding-top: 0; }
    .system--lead { padding: 1.5rem; }
}

/* ---- selected systems: outbound links ----
   Only two of the five records have a link: a verifiable, still-reachable
   public repository. The other three carry none, on purpose, rather than
   pointing at dead DNS or a private source. */
.system-links {
    list-style: none;
    margin: 0.75rem 0 0;
    padding: 0;
    font-size: 0.8125rem;
}
.system-links a {
    color: var(--text-muted);
    min-height: 44px;
    display: inline-flex;
    align-items: center;
}
.system-links a:hover { color: var(--accent); }

/* ---- experience ----
   Deliberately not five more cards after the five boxed systems above: no
   border, no background surface, no rail, no dots, no icons. A hairline top
   rule between rows is the only separator, carried by the adjacent-sibling
   selector so it also falls between the current role and the first prior
   role without a dedicated class. The current role reads as the anchor
   (more block padding, a larger heading, its two sentences in full prose at
   --text); the four prior roles compress into a dense one-line record, the
   contrast the section is built around. */
.role { padding-block: 2rem; }
.role:first-child { padding-top: 0; }
.role + .role { border-top: 1px solid var(--hairline); }

.role-dates {
    font-size: 0.8125rem;
    color: var(--text-muted);
}
.role-org {
    margin: 0.35rem 0 0;
    font-size: 0.8125rem;
    color: var(--text-muted);
}

.role--current { padding-bottom: 2.5rem; }
.role--current .role-dates { margin-bottom: 0.75rem; }
.role--current h3 { font-size: 1.25rem; }
.role--current .role-org { margin-bottom: 1.5rem; }
/* Explicit rather than relied-on: the brief calls out --text for the anchor
   role's prose specifically, and paragraphs elsewhere in this section (the
   muted metadata) rely on the same base color being overridden. */
.role-desc { color: var(--text); font-size: 1rem; }
.role-desc + .role-desc { margin-top: 0.85rem; }

/* Prior roles put the date beside the title instead of above it (the
   current role's treatment) so the row reads as one dense line rather than
   a second small card; that pairing is exactly what has to un-pair at
   narrow widths below. */
.role-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
}
.role-head h3 { min-width: 0; }
.role-line {
    margin-top: 0.5rem;
    font-size: 0.9375rem;
    color: var(--text-muted);
}

/* Reuse the selected-systems breakpoint. A long title (the LinkedIn red
   team role) plus a long date range is the pair most likely to collide on
   one flex row at this width; stacking removes the collision outright
   rather than shrinking type to avoid it. */
@media (max-width: 559px) {
    .role-head { flex-direction: column; align-items: flex-start; gap: 0.25rem; }
}

/* The degree line closes the experience section rather than heading a section of
   its own: it is context for the roles above it, not a claim that stands alone. */
.role-education {
    margin-top: 2rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--hairline);
    font-size: 0.8125rem;
    color: var(--text-muted);
    line-height: 1.9;
}
/* The degree sits at --text; the minor and specialisations stay quieter, so the
   line reads as one fact with its detail rather than as two competing claims. */
.role-education { color: var(--text); }
.role-education-detail { color: var(--text-muted); }

/* ---- command palette ----
   Cmd-K / Ctrl-K only; there is no trigger chip to style because there is no
   trigger chip. `hidden` (not opacity) keeps the closed palette out of the
   tab order and the accessibility tree, so there is no overlay to fade in;
   the backdrop just dims, and the dialog has no shadow, no blur, and no
   scale-in, matching the flat, hairline-bordered surfaces used everywhere
   else here. Anchored toward the top of the viewport, not centered, so it
   reads as a command line dropping in rather than a modal taking over. */
.cmdk-backdrop {
    position: fixed;
    inset: 0;
    z-index: 100;
    background: rgba(10, 10, 10, 0.72);
    display: flex;
    justify-content: center;
    /* align-items defaults to stretch, which would pull the dialog to fill
       this container's full cross-axis height (up to max-height) even when
       the option list is far shorter. flex-start keeps the dialog exactly
       as tall as its content. */
    align-items: flex-start;
    padding: 10vh 1rem 1rem;
}
.cmdk-backdrop[hidden] { display: none; }

.cmdk-dialog {
    width: min(480px, calc(100vw - 2rem));
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--hairline-lit);
}

.cmdk-input {
    width: 100%;
    border: none;
    border-bottom: 1px solid var(--hairline);
    background: transparent;
    color: var(--text);
    font-size: 1rem;
    padding: 0.875rem 1rem;
}
.cmdk-input::placeholder { color: var(--text-muted); }
/* The dialog's own hairline border already frames the control; an inset
   ring on the input as well would double that edge. */
.cmdk-input:focus-visible { outline: none; }

.cmdk-list {
    overflow-y: auto;
    padding: 0.375rem;
}

.cmdk-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    min-height: 44px;
    padding: 0.5rem 0.625rem;
    color: var(--text);
    cursor: pointer;
}
.cmdk-option[aria-selected="true"] { color: var(--accent); }
.cmdk-option-hint {
    font-size: 0.75rem;
    color: var(--text-muted);
    flex-shrink: 0;
}
.cmdk-option[aria-selected="true"] .cmdk-option-hint { color: var(--accent); }

/* The payoff entry carries the accent as a static color, not as motion: no
   blink, no pulse. Selection (above) already overrides this when it is the
   active row, so the two rules never fight. */
.cmdk-option--accent .cmdk-option-label { color: var(--accent); }
