/* ===========================================================================
   Andrew Hall, personal career site
   Implements work/career-site/DIRECTION.md as amended by REVISION-1 and
   REVISION-2.

   Material budget for the whole page, three items and no more:
     1. elevation  -> the nav's translucent material
     2. perspective-> the tier stack beside role 01
     3. obscuring  -> the archive blur veil
   Nothing else casts a shadow or occupies z.

   Type: three weights only, 400 / 500 / 600.
   No text-transform anywhere. No positive letter-spacing above 0.01em.
   =========================================================================== */

/* ----------------------------------------------------------------- fonts -- */
/* Self-hosted, same origin, no third-party request. DIRECTION.md section 3
   aims this page at hiring managers on locked-down government workstations,
   which are exactly the environments that block fonts.googleapis.com and
   fonts.gstatic.com. On that failure the page fell back to system-ui, where
   font-variation-settings: "wdth" 118 silently does nothing and the whole
   width-contrast type idea disappears with no visible error. Hotlinking also
   put an EU visitor's IP in front of a third party for no benefit.

   Both files are the VARIABLE builds. Archivo carries both axes it is used
   for, wdth 62 to 125 and wght 100 to 900, so font-stretch must be declared
   as a range or the browser clamps the width axis to normal. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url("fonts/archivo-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url("fonts/archivo-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/publicsans-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/publicsans-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------------------------------------------------------------- tokens -- */
:root {
  color-scheme: light dark;

  --base: #F1F3F5;
  --raised: #FAFBFC;
  --ink: #14181C;
  --ink-2: #565E67;
  /* AA fix: was #8A929B, which measured 2.83:1 on --base at 13 to 14.5px and
     had no large-text exemption. #676F78 measures 4.58:1 on --base and 4.92:1
     on --raised. It sits on the same cool-grey ramp (R, R+8, R+17) as ink and
     ink-2, so the scale keeps its shape. */
  --ink-3: #676F78;
  --line: #DBDFE4;
  --accent: #A94D14;
  --accent-display: #C85A17;

  /* Primary button. Its own ramp, deliberately NOT --accent-display, which
     DIRECTION.md section 3 restricts to type 28px and up and which fails AA
     behind a 15px label. Hover moves the field away from the label's
     luminance, so contrast rises on hover instead of falling; active moves one
     step further. Same rule in both themes, opposite directions. */
  --btn-fg: #F1F3F5;
  --btn-bg: #A94D14;         /* label 5.02:1 */
  --btn-bg-hover: #8E3F0E;   /* label 6.58:1 */
  --btn-bg-active: #78340B;  /* label 8.23:1 */

  /* nav translucent material, the one elevation layer */
  --nav-bg: rgba(241, 243, 245, 0.78);
  /* archive veil tint, the one obscuring layer */
  --veil-tint: rgba(241, 243, 245, 0.42);
  /* Frame hairline for the two work captures. Neutral, not an accent.
     Both captures are screens of real UI, one light and one dark, so each
     needs an edge the page can hold it by in the scheme where it is the
     brighter surface. The frame does the work; the image is never touched. */
  --media-edge: #C6CCD3;

  /* Tier stack cards. Diagram only, used nowhere else on the page.
     --plane-bg is the front card, the clean surface a buyer sees; the deeper
     cards mix toward --plane-bg-deep so the stack recedes without any card
     losing its edge. --plane-shadow is the only box-shadow in this file. */
  --plane-bg: #FFFFFF;
  --plane-bg-deep: #E8ECF0;
  --plane-line: #CDD3DA;
  --node: #7C8590;
  --plane-shadow: 0 1px 2px rgba(20, 24, 28, 0.05),
                  0 8px 18px -8px rgba(20, 24, 28, 0.22);

  --gutter: clamp(20px, 4vw, 32px);
  --edge: calc(max(0px, (100vw - 1240px) / 2) + var(--gutter));
  --section-gap: clamp(72px, 9vw, 128px);
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  --display: "Archivo", system-ui, sans-serif;
  --text: "Public Sans", system-ui, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --base: #101317;
    --raised: #171B20;
    --ink: #E8EBEE;
    --ink-2: #99A1AA;
    /* AA fix: was #6D767F, 4.03:1 on --base. #7E8790 measures 5.10:1 on
       --base and 4.74:1 on --raised. */
    --ink-3: #7E8790;
    --line: #262B31;
    --accent: #E8823C;
    --accent-display: #E8823C;

    /* Dark inverts the button: the label is the dark token on an amber field,
       so hover brightens rather than darkens. --accent-display is identical to
       --accent in this theme, which is why the old hover did nothing at all. */
    --btn-fg: #101317;
    --btn-bg: #E8823C;         /* label 6.82:1 */
    --btn-bg-hover: #F59A59;   /* label 8.56:1 */
    --btn-bg-active: #FBAA6E;  /* label 9.82:1 */

    --nav-bg: rgba(16, 19, 23, 0.78);
    --veil-tint: rgba(16, 19, 23, 0.42);
    /* In dark the archive capture is a light plate on a dark page. A hairline
       well above --line reads as a deliberate frame instead of a light leak. */
    --media-edge: #49525B;

    --plane-bg: #222932;
    --plane-bg-deep: #161B21;
    --plane-line: #3B4550;
    --node: #79838E;
    --plane-shadow: 0 1px 2px rgba(0, 0, 0, 0.5),
                    0 10px 22px -10px rgba(0, 0, 0, 0.85);
  }
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--base);
  color: var(--ink-2);
  font-family: var(--text);
  font-size: 16px;
  line-height: 1.6;
  font-weight: 400;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, p, dl, dd, figure, figcaption, ol, ul { margin: 0; }
ol, ul { padding: 0; list-style: none; }
img { display: block; max-width: 100%; height: auto; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

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

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

.skip {
  position: absolute; left: var(--gutter); top: -100px; z-index: 100;
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line); border-radius: 6px;
  padding: 10px 16px; font-size: 14.5px; font-weight: 500;
}
.skip:focus { top: 12px; }

/* ------------------------------------------------------------ typography -- */
.hero__name,
.section__title,
.role__title,
.project__title,
.tok {
  font-family: var(--display);
  font-weight: 600;
}

.hero__name {
  font-variation-settings: "wdth" 118, "wght" 600;
  font-size: clamp(46px, 8.4vw, 88px);
  line-height: 0.98;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.section__title {
  font-variation-settings: "wdth" 116, "wght" 600;
  font-size: clamp(31px, 3.6vw, 44px);
  line-height: 1.05;
  letter-spacing: -0.015em;
  color: var(--ink);
}

.role__title,
.project__title {
  font-variation-settings: "wdth" 115, "wght" 600;
  font-size: clamp(26px, 2.5vw, 34px);
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--ink);
}

/* The one accented token per role. Width and color shift, not a size shift.
   Sanctioned exception to the "Archivo never below 24px" rule, per REVISION-1. */
.tok {
  font-variation-settings: "wdth" 118, "wght" 600;
  font-size: 0.97em;
  color: var(--accent);
}

.lead {
  font-size: clamp(19px, 1.55vw, 23px);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 62ch;
}

.num { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------- structure -- */
.container {
  width: 100%;
  max-width: 1240px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--section-gap); }
/* One section gap at every boundary, never two stacked. The hero is not a
   .section (it owns a capped top padding, per DIRECTION.md section 4), so it
   has to be named here or the hero-to-Record boundary silently doubles:
   the hero's bottom gap plus Record's top gap, 230px at 1280 against 115px
   everywhere else. REVISION-1 is explicit that the hero flows straight into
   Record with no interstitial, so it gets the same single gap as the rest. */
.section + .section,
.hero + .section { padding-top: 0; }

/* the sticky nav is 64px, keep anchor targets clear of it */
[id] { scroll-margin-top: 88px; }

/* ------------------------------------------------------------------- nav -- */
#sentinel { position: absolute; top: 0; left: 0; width: 1px; height: 1px; }

.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  height: 64px;
  display: flex;
  align-items: center;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color 260ms var(--ease), border-color 260ms var(--ease);
}

/* Material item 1 of 3: elevation. One blur, no rim light, no inner glow. */
.nav.is-stuck {
  background: var(--nav-bg);
  border-bottom-color: var(--line);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
}

.nav__inner { display: flex; align-items: center; }
.nav__list { display: flex; gap: clamp(18px, 2.6vw, 34px); }
.nav__list a {
  font-size: 14.5px;
  font-weight: 400;
  color: var(--ink-2);
  transition: color 160ms linear;
}
.nav__list a:hover { color: var(--ink); text-decoration: none; }
.nav__list a.is-active { color: var(--accent); font-weight: 500; }

/* ------------------------------------------------------------------ hero -- */
.hero {
  padding-top: clamp(56px, 8vw, 96px);
  padding-bottom: var(--section-gap);
}

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: end;
}

.hero__scope {
  margin-top: 28px;
  font-size: clamp(19px, 1.55vw, 23px);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 34ch;
}

.hero__actions {
  margin-top: 36px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.btn {
  display: inline-block;
  border-radius: 6px;
  padding: 12px 22px;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.2;
  border: 1px solid transparent;
  transition: background-color 160ms linear, border-color 160ms linear, color 160ms linear;
}
.btn:hover { text-decoration: none; }

/* No lift, no shadow, no scale. The page has no hover elevation anywhere and
   its one call to action does not get to invent it. The whole feedback is a
   one-step tonal shift of the field, which also raises label contrast. */
.btn--primary {
  background: var(--btn-bg);
  color: var(--btn-fg);
  border-color: var(--btn-bg);
}
.btn--primary:hover,
.btn--primary:focus-visible {
  background: var(--btn-bg-hover);
  border-color: var(--btn-bg-hover);
}
.btn--primary:active {
  background: var(--btn-bg-active);
  border-color: var(--btn-bg-active);
}

.btn--quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--line);
}
.btn--quiet:hover { border-color: var(--ink-3); }

.standing { border-top: 1px solid var(--line); }
.standing__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  padding: 16px 0;
}
.standing__row + .standing__row { border-top: 1px solid var(--line); }
.standing dt {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1.4;
  color: var(--ink-3);
}
.standing dd {
  font-size: 16px;
  line-height: 1.45;
  color: var(--ink);
}

/* ---------------------------------------------------------------- record -- */
.section__title + .lead { margin-top: 24px; }
.record__lead { margin-bottom: clamp(48px, 6vw, 76px); }

/* Two-track measure. Narrow left rail carries when and where.
   Wide right track carries what. Twelfth column stays empty. */
.role {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 8fr) minmax(0, 1fr);
  gap: clamp(20px, 2.6vw, 32px);
  padding-block: clamp(36px, 4vw, 52px);
  border-top: 1px solid var(--line);
}
.roles > .role:last-child { border-bottom: 1px solid var(--line); }

.role__meta > p {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1.4;
  color: var(--ink-3);
}
.role__dates { color: var(--ink-2); }
.role__org { margin-top: 6px; }
.role__loc { margin-top: 2px; }

.role__scope {
  margin-top: 14px;
  font-size: clamp(18px, 1.4vw, 20px);
  line-height: 1.5;
  color: var(--ink);
  max-width: 62ch;
}

.role__detail { margin-top: 26px; }

.bullets li {
  position: relative;
  padding-left: 18px;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--ink-2);
  break-inside: avoid;
}
.bullets li + li { margin-top: 12px; }
.bullets li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 6px;
  height: 1px;
  background: var(--ink-3);
}

.bullets--two { columns: 2; column-gap: clamp(24px, 3vw, 44px); }
.bullets--two li { margin-top: 0; margin-bottom: 12px; }
.bullets--two li + li { margin-top: 0; }

/* Role 01 only: bullets run single column with the diagram alongside. */
.role__detail--withfig {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(240px, 320px);
  gap: clamp(28px, 3.4vw, 48px);
  align-items: start;
}

/* ------------------------------------------------------------ tier stack -- */
/* The one depth moment on the page. Four supplier tiers drawn as a stack of
   cards. Tier 1 is the top card, the surface a buyer sees; each card below is
   one layer further into the chain, stepping down and to the right and sitting
   further back. The only flagged node is in tier 4, the bottom card and the
   last one to arrive. Fires once on entry, then holds. It does not loop,
   breathe, orbit, float, or re-trigger. Entirely abstract.

   Three page rules are relaxed inside this block and nowhere else on the page,
   by explicit instruction: box-shadow, a corner radius above 6px, and spring
   easing with overshoot. Do not carry any of the three outside these cards. */

.tiers {
  /* fan geometry. --fan-* is constant and sizes the box; --step-* is what
     animates, so card width and stage height never shift. */
  --fan-x: 18px;
  --fan-y: 64px;
  --fan-z: 30px;
  --card-h: 54px;

  --step-x: var(--fan-x);
  --step-y: var(--fan-y);
  --step-z: var(--fan-z);

  /* slight overshoot, then settle. Scoped to this element. */
  --spring: cubic-bezier(0.34, 1.5, 0.6, 1);
  --open-dur: 500ms;
  --open-stagger: 120ms;
}

.tiers__stage {
  position: relative;
  height: calc(3 * var(--fan-y) + var(--card-h));
  perspective: 1400px;
}

.tiers__inner {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
}

/* Shallow geometry on purpose. No rotateX: a tilt of any real size smears the
   card edges and the label text, which is what made the earlier version read
   as low resolution. Depth comes from the stack, the offset, the scale falloff
   applied to the translated Z depth, and the surface shading falloff. */
.plane {
  --d: calc(var(--i) / 3);

  position: absolute;
  top: 0;
  left: 0;
  width: calc(100% - 3 * var(--fan-x));
  min-width: 0;
  height: var(--card-h);

  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 0 16px;

  border-radius: 14px;
  border: 1px solid var(--plane-line);
  background: var(--plane-bg);
  background: color-mix(in oklab, var(--plane-bg), var(--plane-bg-deep) calc(var(--d) * 100%));
  box-shadow: var(--plane-shadow);

  transform: translate3d(
    calc(var(--i) * var(--step-x)),
    calc(var(--i) * var(--step-y)),
    calc(var(--i) * var(--step-z) * -1)
  );
  transition: transform var(--open-dur) var(--spring);
  transition-delay: calc(var(--i) * var(--open-stagger));
}

.plane__label {
  font-size: 14px;
  font-weight: 500;
  line-height: 1;
  color: var(--ink-2);
  flex: 0 0 auto;
}

.plane__nodes {
  display: flex;
  gap: 10px;
  flex: 0 1 auto;
}

.node {
  display: block;
  width: 12px;
  height: 12px;
  flex: 0 0 auto;
  border-radius: 4px;
  background: var(--node);
  /* One state change, three properties: the flag's fill, its shape and its
     ring all resolve together. Not a new motion behavior. */
  transition: background-color 260ms linear,
              border-radius 260ms linear,
              outline-color 260ms linear;
}

/* WCAG 1.4.1: amber alone carried the flag, and against the plain nodes that
   is about 1.45:1 of luminance separation, so the page's signature element
   lost its argument in greyscale, monochrome print, or achromatopsia.
   Amber stays the primary signal and gains two channels that survive with no
   hue at all: the flagged node is the only round one in a row of rounded
   squares, and the only one wearing a separating ring. An outline, not a
   box-shadow, so the one-shadow material budget is untouched. No glow, no
   pulse, no extra animation. */
.node--flag {
  background: var(--accent);
  border-radius: 50%;
  outline: 2px solid var(--accent);
  outline-offset: 2.5px;
}

/* With JS on, the stack rests collapsed: you see tier 1, and tier 1 looks
   fine. Without JS it renders fully open and static, which is the honest
   fallback. Same for reduced motion, which never enters this block. */
@media (prefers-reduced-motion: no-preference) {
  .js .tiers:not(.is-open) {
    --step-x: 5px;
    --step-y: 7px;
    --step-z: 11px;
  }
  /* Collapsed rest state: tier 1 looks fine, and the flag has not shown itself
     yet. Its shape and ring are withheld along with its color, so the
     non-color channels do not give the answer away before the stack opens. */
  .js .tiers:not(.is-open) .node--flag {
    background: var(--node);
    border-radius: 4px;
    outline-color: transparent;
  }
}

/* Amber lands as tier 4 settles, so the last thing the eye gets is the flag in
   the deepest card. Sequence ends at roughly 1.06s and stops there. */
.js .tiers.is-open .node--flag {
  background: var(--accent);
  transition-delay: 800ms;
}

/* ------------------------------------------------------------------ work -- */
.work__lead { margin-bottom: clamp(48px, 6vw, 76px); }

/* Full-bleed media bands. The only edge to edge layout on the page. */
.band {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(28px, 4vw, 56px);
  align-items: center;
}
.band + .band { margin-top: clamp(56px, 7vw, 104px); }

.band--medialeft .band__body { padding-right: var(--edge); }
.band--mediaright .band__body { padding-left: var(--edge); }

.project__url { margin-top: 8px; font-size: 14.5px; }
.project__desc {
  margin-top: 18px;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 60ch;
}

.tags {
  margin-top: 22px;
  display: flex;
  flex-wrap: wrap;
  column-gap: 26px;
  row-gap: 8px;
}
.tags span {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--ink-3);
}

/* ---------------------------------------------------- archive media veil -- */
.media {
  position: relative;
  display: block;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--media-edge);
  background: var(--raised);
}
.media:hover { text-decoration: none; }
/* the bled edge stays square, the same rule the clip follows */
.band--medialeft .media { border-left: 0; border-radius: 0 6px 6px 0; }
.band--mediaright .media { border-right: 0; border-radius: 6px 0 0 6px; }
/* Native ratio of the capture, 1918 x 912. No forced box, so the chart is
   never letterboxed, pillarboxed, or cut through a person card. */
.media__img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1918 / 912;
}

/* Material item 3 of 3: the obscuring layer. The project is literally
   encrypted at rest, so the surface holds nothing readable until it clears. */
.media__veil {
  position: absolute;
  inset: 0;
  background-color: var(--veil-tint);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  opacity: 0;
  transition: opacity 520ms var(--ease);
  pointer-events: none;
}
.js .media__veil { opacity: 1; }
.js .media.is-revealed .media__veil,
.js .media:hover .media__veil,
.js .media:focus-visible .media__veil { opacity: 0; }

/* ---------------------------------------------------------- brew clip --- */
/* A real capture of the game, not an embedded player. The only chrome is the
   single pause/replay button below, which WCAG 2.2.2 requires of an 8.0s clip
   that starts on its own; there is no scrubber, timecode or volume.
   Flat: no shadow, no tilt, no perspective, no hover state. The poster is the
   clip's final frame, so the element is the same still image before it plays
   and after it ends. It reads as a picture that moved once. */
.clip {
  position: relative;
  display: block;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--media-edge);
  background: var(--raised);
}
.band--mediaright .clip { border-right: 0; border-radius: 6px 0 0 6px; }
.band--medialeft .clip { border-left: 0; border-radius: 0 6px 6px 0; }

.clip__video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1280 / 614;
  /* Kept: the video is not the control and must not swallow the click, take
     focus, or raise a native context menu. It does not block the button,
     which is a sibling, and hover still resolves to .clip underneath. */
  pointer-events: none;
}

/* The clip runs 8.0s, past the 5 second threshold in WCAG 2.2.2 for motion
   that starts on its own next to other content, so it needs a way to stop.
   This is the one piece of chrome the clip is allowed: no scrubber, no
   timecode, no volume, no fullscreen. One button. It is injected by script.js
   so it never exists in a state where it cannot work, and it stays hidden
   until the clip is hovered or something inside it takes focus. */
.clip__control {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  margin: 0;
  border: 1px solid var(--media-edge);
  border-radius: 6px;
  background: var(--raised);
  color: var(--ink);
  cursor: pointer;
  opacity: 0;
  transition: opacity 200ms var(--ease), border-color 160ms linear;
}
.clip__control svg { display: block; width: 14px; height: 14px; fill: currentColor; }

.clip:hover .clip__control,
.clip:focus-within .clip__control,
.clip__control:focus-visible,
.clip__control:focus { opacity: 1; }

.clip__control:hover { border-color: var(--ink-3); }

/* Touch and other hoverless pointers have no way to summon it, so there it
   stays put. */
@media (hover: none) {
  .clip__control { opacity: 1; }
}

/* ----------------------------------------------------------- credentials -- */
.creds__group { margin-top: clamp(40px, 4.5vw, 60px); }
.creds__heading,
.skills__heading {
  font-family: var(--text);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--ink);
  padding-top: 20px;
  border-top: 1px solid var(--line);
}

.deflist { margin-top: 18px; }
.deflist__row {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(16px, 2.6vw, 32px);
  padding-block: 12px;
}
.deflist dt {
  font-size: 16px;
  line-height: 1.5;
  color: var(--ink);
}
.deflist dd {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--ink-2);
}

/* ---------------------------------------------------------------- skills -- */
.skills__group { margin-top: clamp(36px, 4vw, 52px); }
.skills__set {
  margin-top: 18px;
  display: flex;
  flex-wrap: wrap;
  column-gap: clamp(22px, 2.6vw, 38px);
  row-gap: 10px;
  max-width: 68ch;
}
.skills__set span {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--ink-2);
}

/* --------------------------------------------------------------- contact -- */
.contact { padding-bottom: clamp(72px, 9vw, 128px); }
.contact__inner { border-top: 1px solid var(--line); padding-top: clamp(40px, 5vw, 64px); }
.contact__links {
  margin-top: 30px;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 28px;
  row-gap: 8px;
  font-size: 16px;
}
.contact__aside { color: var(--ink-3); font-size: 14.5px; }

.foot { border-top: 1px solid var(--line); }
.foot__inner { padding-block: 28px; }
.foot p {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--ink-3);
}

/* ---------------------------------------------------------------- motion -- */
/* Behavior 1 of 4: section entry reveal. 20px rise plus fade, once. */
.js .rise {
  opacity: 0;
  transform: translateY(20px);
}
.js .rise.is-in {
  opacity: 1;
  transform: none;
  transition: opacity 620ms var(--ease), transform 620ms var(--ease);
}

/* ------------------------------------------------------------ responsive -- */
@media (max-width: 1000px) {
  .role__detail--withfig { grid-template-columns: minmax(0, 1fr); }
  .tiers { max-width: 420px; }
}

@media (max-width: 900px) {
  .hero__grid { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .hero__scope { max-width: 40ch; }
  .hero__standing { margin-top: 12px; }

  .role { grid-template-columns: minmax(0, 1fr); }
  .role__meta { display: flex; flex-wrap: wrap; column-gap: 18px; row-gap: 2px; }
  .role__org, .role__loc { margin-top: 0; }

  .bullets--two { columns: 1; }
  .bullets--two li { margin-bottom: 12px; }

  .band { grid-template-columns: minmax(0, 1fr); gap: 28px; }
  .band--mediaright .band__media { order: -1; }
  .band--medialeft .band__body,
  .band--mediaright .band__body { padding-inline: var(--gutter); }

  .band--medialeft .clip,
  .band--mediaright .clip,
  .band--medialeft .media,
  .band--mediaright .media {
    border-left: 0;
    border-right: 0;
    border-radius: 0;
  }

  .deflist__row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
}

/* The figure shares a narrow grid column with role 01's bullets down to
   1000px. Tighten the fan there so the deepest card keeps its label, its
   nodes, and its air. */
@media (min-width: 1001px) and (max-width: 1120px) {
  .tiers { --fan-x: 13px; }
}

@media (max-width: 560px) {
  .tiers { --fan-x: 13px; --fan-y: 58px; --card-h: 50px; }
  .plane { gap: 10px; padding: 0 13px; }
  .plane__nodes { gap: 8px; }
}

/* -------------------------------------------------- accessibility guards -- */
@media (prefers-reduced-motion: reduce) {
  .js .rise,
  .js .rise.is-in { opacity: 1; transform: none; transition: none; }

  /* tier stack renders pre-separated and static: the collapsed rest state is
     scoped to prefers-reduced-motion: no-preference, so nothing to undo here */
  .plane { transition: none; }
  .node { transition: none; }

  /* archive renders revealed */
  .js .media__veil { opacity: 0; transition: none; }

  /* the brew clip renders its poster, which is the clip's final frame, and
     is never started. Enforced in script.js, not only here. */

  .nav { transition: none; }
  .btn, .nav__list a { transition: none; }
}

/* ----------------------------------------------------------------- print -- */
/* Everything below the first viewport used to print blank: .js .rise starts at
   opacity 0 and is only cleared when IntersectionObserver adds .is-in, and
   Chrome does not re-run IntersectionObserver for print. Short block, and it
   only undoes the things that hide content on paper. */
@media print {
  /* A reader on a dark desktop was printing #E8EBEE text onto white paper.
     Paper has one color scheme. */
  :root {
    color-scheme: light;
    --base: #F1F3F5; --raised: #FAFBFC;
    --ink: #14181C; --ink-2: #565E67; --ink-3: #676F78;
    --line: #DBDFE4; --accent: #A94D14;
    --plane-bg: #FFFFFF; --plane-bg-deep: #E8ECF0;
    --plane-line: #CDD3DA; --node: #7C8590; --plane-shadow: none;
    --media-edge: #C6CCD3;
  }

  body { background: #FFFFFF; }

  /* 1. the reveal never landed, so force every section visible */
  .js .rise,
  .js .rise.is-in { opacity: 1; transform: none; transition: none; }

  /* 2. the archive prints as the capture, not under a tint */
  .js .media__veil { display: none; }

  /* 3. the tier stack prints open, which is the state that carries its point.
        Matches the collapsed rule's specificity and comes later, so it wins. */
  .js .tiers:not(.is-open) {
    --step-x: var(--fan-x); --step-y: var(--fan-y); --step-z: var(--fan-z);
  }
  .js .tiers:not(.is-open) .node--flag {
    background: var(--accent); border-radius: 50%; outline-color: var(--accent);
  }
  .plane, .node { transition: none; print-color-adjust: exact; -webkit-print-color-adjust: exact; }

  /* 4. the clip prints as its poster. The video box is hidden rather than
        removed so the poster keeps the element's aspect ratio. */
  .clip {
    background-image: url("media/brew-balance-poster.jpg");
    background-size: cover;
    background-position: center;
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }
  .clip__video { visibility: hidden; }
  .clip__control { display: none; }

  /* 5. links stay readable and are not suffixed with their own href. Every URL
        worth having is already set as visible text. */
  a { color: inherit; text-decoration: underline; }

  /* 6. browsers drop background colors when printing, which left the primary
        button printing a near-white label onto white paper. On paper it is not
        a control anyway, so it prints as an outlined label in ink. */
  .btn--primary {
    background: none;
    color: var(--ink);
    border-color: var(--line);
    text-decoration: none;
  }

  .nav, .skip { display: none; }
  .role, .band, .creds__group, .skills__group { break-inside: avoid; }
}

@media (prefers-reduced-transparency: reduce) {
  .js .media__veil { opacity: 0; }
  .nav.is-stuck {
    background: var(--base);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
