/* ==========================================================================
   Rachel Young â€” portfolio
   Tokens â†’ base â†’ layout â†’ components â†’ responsive
   ========================================================================== */

:root {
  /* Palette */
  --ink:        #17332C;
  --ink-soft:   #2C4A43;
  --teal:       #1FBFA8;
  --teal-deep:  #0E7A6B;
  --green-deep: #0B5A3A;
  --mint:       #DFF7F1;
  --mint-line:  #CDE9E2;
  --terra:      #C97B4A;
  --sand:       #F0AD6B;
  --cream:      #FBF2EC;
  --cream-warm: #FCF1E9;
  --card:       #FBFAF7;
  --card-line:  #E7E0D6;
  --body:       #3A332C;
  --muted:      #5A5044;
  --faint:      #9A8C7A;

  /* Hairlines and soft borders. The two terra tints differ slightly and are
     kept as authored rather than merged into one. */
  --terra-line: #F3D3B8;
  --green-line: #CFE6D8;
  --sand-line:  #F3DCC9;
  --teal-line:  #CDEDE4;

  /* Muted label ink, shared by the hero and About stat tiles. */
  --stat-label: #4A5B54;

  --white: #FFFFFF;
  --pill-green: #E3F2E9;
  /* Warm neutrals */
  --ink-warm:         #4A3F36;
  --faint-warm:       #8A7E70;
  --placeholder:      #A9A296;
  --cream-line:       #EAD9C7;
  --cream-line-deep:  #CDB8A6;
  --cream-track:      #F6E7D8;
  --terra-tint:       #FCEEE3;

  /* Cool neutrals */
  --teal-ink:         #3A5B54;
  --teal-muted:       #5A7168;
  --teal-muted-deep:  #4C7268;
  --teal-line-soft:   #B9E0D6;

  /* Hero gradient stops */
  --grad-teal:        #BEE9DC;
  --grad-sand:        #EFD3B4;
  --grad-amber:       #E9B98A;

  /* Status */
  --success:          #34C77B;
  --error-bg:         #FBEDE6;
  --error-line:       #E8C7B4;
  --error-ink:        #7A3B1E;

  /* WhatsApp brand mark - fixed by the platform, not ours to re-theme. */
  --wa-brand:         #25D366;
  --wa-ink:           #07331B;

  /* Pill padding, shared by .tag and .cert-skills li. */
  --pill-pad: 12px 20px;

  /* Type */
  --font-display: 'Space Grotesk', system-ui, sans-serif;
  --font-body:    'Inter', system-ui, sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, monospace;

  /* Layout */
  --wrap: 1120px;
  --gutter: 48px;
  --section-y: 80px;
  --header-h: 74px;

  /* Spacing scale. Every vertical gap in the file comes from here — a literal
     px value in a margin is a bug unless it's an optical nudge under 4px. */
  --sp-1:   4px;
  --sp-2:   8px;
  --sp-3:  12px;
  --sp-4:  16px;
  --sp-5:  20px;
  --sp-6:  24px;
  --sp-7:  32px;
  --sp-8:  40px;
  --sp-9:  48px;
  --sp-10: 64px;

  /* Vertical rhythm, named by the relationship rather than the size, so a
     section can't quietly invent its own value.

     The order encodes proximity: a label sits closer to its heading than the
     heading sits to its intro, which sits closer than the content below it.
     Keep --gap-label-title < --gap-title-intro < --gap-title-content
     < --gap-block at every breakpoint or the grouping stops reading.

       eyebrow                 ─┐ --gap-label-title
       H2                      ─┤ --gap-title-intro     ← heading group
       intro paragraph         ─┘ --gap-title-content
       first content block     ─┐ --gap-block
       next content block      ─┘

     Sections without an intro simply skip that step: --gap-title-content is
     the H2's own bottom margin, so it lands the same either way. */
  --gap-label-title:   var(--sp-3);
  --gap-title-intro:   var(--sp-4);
  --gap-title-content: var(--sp-7);
  --gap-block:         var(--sp-9);

  /* The section H2, as a token rather than a literal, because the step under
     a kicker is measured against it — see --title-cap below. Overridden per
     breakpoint further down, same as the font-size it used to be. */
  --title-size: 38px;
  /* How far a heading's line box sits above its own capitals: half the
     leading, plus the gap between the font's ascender and its cap height.
     Steady at 0.29 of the type size across every breakpoint here, measured
     rather than guessed.

     It matters because the kicker → H2 step is a step to a *line box*, and a
     line box carries this much air of its own. Anything that follows a kicker
     without one — the photograph in Hard Moments — lands its hard edge that
     much higher and reads tight against a page where every other section has
     the air built in. */
  --title-cap: calc(var(--title-size) * 0.29);
}

/* ---------- Base ---------- */

* { box-sizing: border-box; }
.md{display: none !important;}
.dk{display: flex !important;}
/* `hidden` has to beat the display values set on .contact-form / .skill-group */
[hidden] { display: none !important; }

html {
  scroll-behavior: smooth;
  /* Exactly the header, no more: a snapped section then occupies the whole
     strip of viewport below it, which is what the section heights assume. */
  scroll-padding-top: var(--header-h);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-display);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }

a { color: var(--teal); text-decoration: none; }
a:hover { color: var(--ink); }

h1, h2, h3, h4 { font-family: var(--font-display); letter-spacing: -0.01em; }

p, li, label, input, textarea, button { font-family: var(--font-body); }

:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--ink);
  color: var(--white);
  padding: 12px 20px;
  border-radius: 0 0 10px 0;
  font-family: var(--font-body);
  font-size: 14px;
}
.skip-link:focus { left: 0; color: var(--white); }

/* ---------- Layout primitives ---------- */

.container {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: var(--section-y) var(--gutter);
}

.section-alt   { background: var(--white); }
.section-cream { background: var(--cream); }
.section-warm  { background: var(--cream-warm); }
.section-mint  { background: var(--mint); }

/* One section per screen.
   Every section fills the window and centres its own content, and the page
   snaps to section tops — so you land on a whole section instead of half of
   one. Snapping is `proximity`, not `mandatory`, so a section that outgrows a
   short window can still be scrolled through normally. Off below 901px, where
   the stacked layouts are taller than the viewport by design. */
@media (min-width: 901px) {
  html { scroll-snap-type: y proximity; }

  /* Tighter than the mobile rhythm: the sections are screen-height anyway,
     and the spare room is what lets the dense ones fit. */
  :root {
    --section-y: 32px;

    /* The gap between every pair of sections, and the only one there is.
       Sections are screen-height and their content is stretched to fill that
       screen (see "Filling the screen" further down), so the only space left
       between the last line of one section and the first of the next is this
       band — half of it below the fold line, half above. That is what makes
       every seam on the page measure the same: centred content hands the next
       section half its own leftover air, so a short section opened a gap twice
       the size of its neighbours' and a tall one closed it to nothing.

       Proportional to the window so the rhythm scales with it. The ceiling is
       set by the tallest section that can't be squeezed — the hero at 780px
       tall needs 593 of the 706 available, leaving 113 — so 14vh (109px) is
       the most this can be without pushing the hero off its own screen.

       The second half of the min() is the section that can't shrink. About
       bottoms out around 648px — three paragraphs, a pull quote and a stat
       row, none of which compress — and the hero is a shade under it, so on
       any window where 14vh would leave less than that, the seam gives way
       instead of the section. Without it the seam and the tightening block
       below step on different breakpoints, and the band between them —
       windows a shade over 800px tall — cut three sections at once. */
    --seam: min(14vh, calc(100svh - var(--header-h) - 648px));
  }

  main > section {
    /* The sticky header covers the top of the viewport, so a section only
       ever gets the strip below it. Sizing to 100svh instead would push
       every section 74px past the fold and pad the short ones with air. */
    min-height: calc(100vh - var(--header-h));
    min-height: calc(100svh - var(--header-h));
    display: grid;
    align-content: center;
    align-content: safe center;
    scroll-snap-align: start;
  }

  /* The container fills the whole strip rather than shrinking to its content,
     and half a seam of padding holds the ink off each edge. Every section
     below then hands its leftover height to one named block, so the ink runs
     edge to edge and the air above and below it is always half a seam. */
  main > section > .container {
    width: 100%;
    min-height: calc(100vh - var(--header-h));
    min-height: calc(100svh - var(--header-h));
    padding-block: calc(var(--seam) / 2);
    display: flex;
    flex-direction: column;
  }
}

/* The label opens with a small line mark rather than the old · bullet. Every
   mark is stroked in `currentColor`, so the two colour modifiers below re-tint
   the glyph and the words in one move and nothing else has to know about it. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--teal);
}
.eyebrow svg { flex-shrink: 0; }
.eyebrow--terra { color: var(--terra); }
.eyebrow--deep  { color: var(--teal-deep); }

/* The bottom margin is the H2 → content step for every section. Where an
   intro paragraph follows instead, that paragraph re-declares the same token
   as its own bottom margin, so the distance to the content is identical with
   or without one. Blocks below must not add a competing top margin: adjacent
   margins collapse, so the larger silently wins and the rhythm drifts. */
.section-title {
  font-size: var(--title-size);
  font-weight: 600;
  color: var(--ink);
  margin: var(--gap-label-title) 0 var(--gap-title-content);
}

.section-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 16px;
  margin: var(--gap-label-title) 0 var(--gap-title-content);
}
.section-head .section-title { margin: 0; max-width: 18ch; }
.section-head .section-title--wide { max-width: 26ch; }
.section-head p {
  font-size: 15px;
  color: var(--muted);
  margin: 0;
  max-width: 44ch;
}

/* ---------- Scroll reveal ---------- */
/* Scoped to .js so the page still renders with scripting off. */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}
.js [data-reveal].in { opacity: 1; transform: none; }

/* ---------- Buttons ---------- */

.btn {
  display: inline-block;
  border-radius: 999px;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: transform 0.22s ease, box-shadow 0.22s ease, background 0.22s ease, color 0.22s ease;
}
.btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 14px 28px -14px rgba(23, 51, 44, 0.5);
}
.btn:active { transform: translateY(0); }

.btn--solid {
  background: var(--ink);
  color: var(--white);
  border: 1.5px solid var(--ink);
  padding: 15px 32px;
}
.btn--solid:hover { color: var(--white); }

.btn--ghost {
  background: transparent;
  border: 1.5px solid var(--ink);
  color: var(--ink);
  padding: 15px 32px;
}

.btn--sm { padding: 10px 22px; font-size: 13.5px; }

/* ---------- Header ---------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--mint);
  border-bottom: 1px solid rgba(23, 51, 44, 0.08);
}

.header-inner {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 20px var(--gutter);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
}

.brand {
  font-size: 21px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.nav {
  display: flex;
  gap: 30px;
  align-items: center;
  font-size: 14.5px;
  white-space: nowrap;
}

.navlink {
  position: relative;
  color: var(--ink);
}
.navlink::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -4px;
  width: 0;
  height: 1.5px;
  background: var(--teal);
  transition: width 0.28s ease;
}
.navlink:hover::after,
.navlink[aria-current='true']::after { width: 100%; }

/* Mobile menu button â€” hidden on desktop */
.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  border: 1.5px solid rgba(23, 51, 44, 0.18);
  border-radius: 12px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  padding: 0;
  align-items: center;
  justify-content: center;
}
.nav-toggle span {
  display: block;
  width: 20px;
  height: 1.8px;
  background: currentColor;
  border-radius: 2px;
  position: relative;
  transition: transform 0.25s ease, background 0.1s ease;
}
.nav-toggle span::before,
.nav-toggle span::after {
  content: '';
  position: absolute;
  left: 0;
  width: 20px;
  height: 1.8px;
  background: currentColor;
  border-radius: 2px;
  transition: transform 0.25s ease, top 0.25s ease;
}
.nav-toggle span::before { top: -6px; }
.nav-toggle span::after  { top: 6px; }

.nav-toggle[aria-expanded='true'] span { background: transparent; }
.nav-toggle[aria-expanded='true'] span::before { top: 0; transform: rotate(45deg); }
.nav-toggle[aria-expanded='true'] span::after  { top: 0; transform: rotate(-45deg); }

/* ---------- Hero ---------- */

.hero .container {
  padding-top: 40px;
  padding-bottom: 40px;
  display: grid;
  grid-template-columns: 1.25fr 0.75fr;
  gap: 56px;
  align-items: center;
}

.hero-kicker {
  display: flex;
  align-items: center;
  /* Wrapping matters once the certification shares this line: on a narrow
     left column the two halves drop onto two rows instead of squeezing the
     job title into a wrap of its own. */
  flex-wrap: wrap;
  gap: 6px 10px;
  margin-bottom: 22px;
}
.hero-kicker span {
  font-family: var(--font-mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--terra);
}
.hero-kicker svg { flex-shrink: 0; color: var(--terra); }

/* Certification, stated in the banner line rather than given a button of its
   own — it's a fact about the role, so it sits on the role's line. Ink against
   the kicker's terra is what separates it from the job title; the dot does the
   rest. No font-size here on purpose: `.hero-kicker span` above already sizes
   it at every breakpoint. */
.hero-kicker .hero-kicker-cert {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  font-weight: 600;
}
.hero-kicker .hero-kicker-cert::before {
  content: '';
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--terra);
  flex-shrink: 0;
}

.hero h1 {
  font-size: 56px;
  font-weight: 600;
  line-height: 1.1;
  margin: 0;
  background: linear-gradient(100deg, var(--teal) 0%, var(--sand) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero-lede {
  font-size: 18px;
  color: var(--ink-warm);
  line-height: 1.65;
  max-width: 50ch;
  margin: 26px 0 0;
}

.hero-actions {
  display: flex;
  gap: 14px;
  margin-top: 34px;
  flex-wrap: wrap;
}

/* ---------- Hero voice intro ---------- */

/* Sits directly under the hero photo, so face and voice read as one thing. */
.voice-wrap { display: flex; }

.voice {
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  gap: 11px;
  padding: 6px 16px 6px 6px;
  border: 1.5px solid var(--mint-line);
  border-radius: 999px;
  background: var(--white);
  cursor: pointer;
  text-align: left;
  max-width: 100%;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.voice:hover {
  transform: translateY(-2px);
  box-shadow: 0 16px 30px -18px rgba(23, 51, 44, 0.45);
  border-color: var(--teal);
}
.voice:active { transform: translateY(0); }

/* Elapsed-time fill, sits behind the label */
.voice-progress {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  background: var(--mint);
  transition: width 0.15s linear;
}

.voice-icon,
.voice-label,
.voice-wave,
.voice-time { position: relative; }

.voice-icon {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Icon swap is class-driven: the `hidden` property does nothing on SVG. */
.voice-icon .icon-play { margin-left: 2px; }
.voice .icon-pause { display: none; }
.voice.is-playing .icon-play { display: none; }
.voice.is-playing .icon-pause { display: block; }

.voice-label {
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.2;
  white-space: nowrap;
}

/* Waveform. It idles gently — a small "there's a voice here" wave — and
   goes full-amplitude while the clip plays. */
.voice-wave {
  display: flex;
  align-items: center;
  gap: 3px;
  height: 18px;
  flex-shrink: 0;
}
.voice-wave i {
  display: block;
  width: 3px;
  height: 18px;
  border-radius: 2px;
  background: var(--teal-deep);
  opacity: 0.45;
  transform: scaleY(0.3);
  animation: voiceWave 1.5s ease-in-out infinite;
  transition: opacity 0.2s ease;
}
.voice-wave i:nth-child(2) { animation-delay: 0.18s; }
.voice-wave i:nth-child(3) { animation-delay: 0.36s; }
.voice-wave i:nth-child(4) { animation-delay: 0.12s; }
.voice-wave i:nth-child(5) { animation-delay: 0.3s; }

@keyframes voiceWave {
  0%, 100% { transform: scaleY(0.28); }
  50%      { transform: scaleY(0.62); }
}
@keyframes voiceWavePlaying {
  0%, 100% { transform: scaleY(0.25); }
  50%      { transform: scaleY(1); }
}

.voice:hover .voice-wave i,
.voice:focus-visible .voice-wave i { opacity: 0.75; animation-duration: 1s; }

.voice-time {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--faint);
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

/* No duration yet (the clip hasn't been asked for) — don't hold a gap open. */
.voice-time:empty { display: none; }

/* Compact variant, sitting beside the "read the full story" arrow. */
.voice--story {
  gap: 9px;
  padding: 4px 14px 4px 4px;
  border-color: var(--cream-line);
}
.voice--story .voice-icon { width: 28px; height: 28px; }
.voice--story .voice-label { font-size: 13px; }
.voice--story .voice-wave { height: 14px; }
.voice--story .voice-wave i { height: 14px; background: var(--terra); }
.voice--story .voice-progress { background: var(--cream-track); }
.voice--story:hover { border-color: var(--terra); }

/* Playing state */
.voice.is-playing .voice-icon { background: var(--teal-deep); }
.voice.is-playing .voice-wave i {
  opacity: 1;
  animation-name: voiceWavePlaying;
  animation-duration: 0.85s;
}

.hero-aside {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.hero-photo {
  border-radius: 20px;
  overflow: hidden;
  aspect-ratio: 4 / 4.85;
  box-shadow: 0 30px 60px -30px rgba(23, 51, 44, 0.4);
}
.hero-photo picture { display: block; width: 100%; height: 100%; }
.hero-photo img { width: 100%; height: 100%; object-fit: cover; }

.hero-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.hero-stat {
  background: var(--mint);
  border-radius: 20px;
  padding: 20px 18px;
  text-align: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.hero-stat .num {
  font-size: 32px;
  font-weight: 600;
  color: var(--teal);
  line-height: 1;
}

.hero-stat .label {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  color: var(--stat-label);
  line-height: 1.4;
  margin-top: 7px;
}

/* ---------- About ---------- */

/* No top margin: .section-title's bottom margin is the gap to this grid.
   Adding one here would collapse against it and whichever is larger wins. */
.about-grid {
  display: grid;
  grid-template-columns: 1.5fr 1fr;
  gap: 56px;
  align-items: start;
}

.prose p {
  font-size: 17px;
  color: var(--body);
  line-height: 1.8;
  margin: 0 0 16px;
}
.prose p:last-child { margin-bottom: 0; }

.pullquote {
  border-left: 2px solid var(--sand);
  padding: 4px 0 4px 26px;
}
.pullquote p {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.55;
  margin: 0;
}
.pullquote cite {
  display: block;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--terra);
  margin-top: 14px;
  font-style: normal;
}

/* Rachel's own words, closing the About prose */
.prose .prose-quote {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.55;
  border-left: 2px solid var(--teal);
  padding-left: 22px;
  margin: var(--sp-6) 0 0;
}

.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
  margin-top: var(--gap-block);
}
/* Same card as .hero-stat: mint tile, centred, rather than a hairline rule. */
.stat {
  background: var(--mint);
  border-radius: 20px;
  padding: 20px 18px;
  text-align: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.stat .num {
  font-size: 32px;
  font-weight: 600;
  color: var(--teal);
  line-height: 1;
}
.stat .label {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  color: var(--stat-label);
  line-height: 1.4;
  margin-top: 7px;
}

/* ---------- Experience — timeline slider ----------
   A rail of four stops over a single detail card. Tabs and tabpanels, so the
   keyboard works for free. Per-stop colour arrives as custom properties on the
   markup (`--acc`, `--bd`, `--pill`), which is why there's one rule here
   instead of four near-identical ones. */

.exp-intro {
  font-family: var(--font-body);
  font-size: 16px;
  color: var(--muted);
  line-height: 1.65;
  margin: var(--gap-title-intro) 0 var(--gap-title-content);
  max-width: 58ch;
}

/* Collapses against .section-title's bottom margin to exactly one step —
   and when .exp-intro is present, sits below that instead. Same distance. */
.exp { margin-top: var(--gap-title-content); }

.exp-rail-wrap { position: relative; }

/* Ends land on the first and last circle centres — 4 equal columns puts those
   at 12.5% and 87.5%. Change the number of stops and this has to change too. */
.exp-rail {
  position: absolute;
  left: 12.5%;
  right: 12.5%;
  top: 28px;
  height: 2px;
  background: linear-gradient(90deg, var(--teal), var(--teal-deep), var(--terra));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.9s cubic-bezier(0.4, 0, 0.2, 1);
}
.exp.in .exp-rail { transform: scaleX(1); }

.exp-nodes {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
  align-items: start;
}

.exp-node {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 5px;
  border: 0;
  border-radius: 12px;
  background: none;
  cursor: pointer;
}
.exp-node:focus-visible { outline-offset: 6px; }

/* Opaque and stacked over the rail, so the line reads as running between the
   stops rather than through them. */
/* Each stop's palette follows from its index, so the markup passes no colour.
   Keep these four in step with the four .exp-node / .exp-card pairs. */
.exp-node[data-idx="0"], .exp-card[data-idx="0"] { --acc: var(--teal-deep);  --bd: var(--teal);       --pill: var(--mint); }
.exp-node[data-idx="1"], .exp-card[data-idx="1"] { --acc: var(--terra);      --bd: var(--terra);      --pill: var(--cream-warm); }
.exp-node[data-idx="2"], .exp-card[data-idx="2"] { --acc: var(--green-deep); --bd: var(--green-deep); --pill: var(--pill-green); }
.exp-node[data-idx="3"], .exp-card[data-idx="3"] { --acc: var(--terra);      --bd: var(--terra);      --pill: var(--cream-warm); }

.exp-dot {
  position: relative;
  z-index: 1;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--white);
  border: 1.5px solid var(--bd);
  color: var(--acc);
  box-shadow: 0 8px 22px -14px var(--acc);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.3s ease, background 0.3s ease, color 0.3s ease,
              opacity 0.5s ease;
}
/* Rounding the image rather than clipping the tile — `overflow: hidden` would
   crop the shadow that lifts the circle off the cream. */
.exp-dot img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
}
/* Selected state lives here rather than being written into element styles by
   the script: the colour is already on the stop as --acc. Declared before the
   hover rule so an equal-specificity hover still wins on the active stop. */
.exp-node[aria-selected="true"] .exp-dot {
  background: var(--acc);
  color: var(--white);
  transform: scale(1.08);
}
.exp-node[aria-selected="true"] .exp-co { color: var(--acc); }

.exp-node:hover .exp-dot { transform: scale(1.06); }

.exp-node-text { display: block; margin-top: 14px; }

.exp-when {
  display: block;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint-warm);
}

.exp-co {
  display: block;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  margin-top: 6px;
  white-space: nowrap;
  transition: color 0.3s ease;
}

/* ---- The card ---- */

.exp-stage {
  position: relative;
  max-width: 720px;
  margin: 34px 0 0;
}

.exp-card {
  background: var(--white);
  border: 1px solid var(--card-line);
  border-radius: 18px;
  padding: 28px 32px;
  box-shadow: 0 26px 50px -34px rgba(23, 51, 44, 0.4);
  transition: opacity 0.45s ease, transform 0.45s ease;
}
/* Pre-paint state, scoped to .js: the script sets these inline the moment it
   runs. Without scripting none of it applies and every card just stacks. */
.js .exp-card + .exp-card {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  opacity: 0;
  transform: translateX(44px);
  pointer-events: none;
}
html:not(.js) .exp-card + .exp-card { margin-top: 16px; }
/* Every card is already on screen, so the arrows would be controls that do
   nothing. The rail's line goes too — with no active stop it means nothing. */
html:not(.js) .exp-controls { display: none; }
html:not(.js) .exp-rail { transform: none; }

.exp-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 14px;
  margin: 0 0 16px;
}

.exp-pill {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--acc);
  background: var(--pill);
  border-radius: 999px;
  padding: 5px 13px;
}

.exp-loc {
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--faint-warm);
}

.exp-name {
  font-family: var(--font-display);
  font-size: 25px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.25;
  letter-spacing: -0.01em;
  margin: 0;
}
/* Only the stops with a real employer URL are links — see the TODOs. */
.exp-name a {
  color: inherit;
  border-bottom: 2px solid transparent;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.exp-name a:hover {
  color: var(--acc);
  border-bottom-color: var(--acc);
}

.exp-role {
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 600;
  color: var(--acc);
  margin: 8px 0 0;
}

.exp-desc {
  font-family: var(--font-body);
  font-size: 16px;
  color: var(--muted);
  line-height: 1.7;
  margin: 14px 0 0;
  max-width: 56ch;
}

/* ---- Controls ---- */

.exp-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 22px;
  margin-top: 24px;
}

.exp-arrow {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1.5px solid var(--cream-line-deep);
  background: transparent;
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: transform 0.22s ease, box-shadow 0.22s ease, background 0.2s ease,
              border-color 0.2s ease, color 0.2s ease;
}
.exp-arrow:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--white);
  transform: translateY(-2px);
  box-shadow: 0 14px 26px -16px rgba(23, 51, 44, 0.65);
}
.exp-arrow:active { transform: translateY(0); }

/* Pause / play for the autoplaying row. Off by default: above 900px the rail
   is driven by the page's own scroll, so there is nothing to pause. The row
   layout switches it on. */
.exp-play { display: none; }
.exp-play .icon-play,
.exp-play .icon-pause { display: none; }
.exp-play.is-playing .icon-pause { display: block; }
.exp-play:not(.is-playing) .icon-play { display: block; }

.exp-counter {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--faint-warm);
  font-variant-numeric: tabular-nums;
}

/* Stops rise as the line reaches them. */
.js .exp:not(.in) .exp-dot,
.js .exp:not(.in) .exp-node-text { opacity: 0; transform: translateY(14px); }
.exp-node-text { transition: opacity 0.5s ease, transform 0.5s ease; }
.exp.in .exp-dot,
.exp.in .exp-node-text { opacity: 1; }
.exp.in .exp-node:nth-child(1) .exp-dot,
.exp.in .exp-node:nth-child(1) .exp-node-text { transition-delay: 0.08s; }
.exp.in .exp-node:nth-child(2) .exp-dot,
.exp.in .exp-node:nth-child(2) .exp-node-text { transition-delay: 0.24s; }
.exp.in .exp-node:nth-child(3) .exp-dot,
.exp.in .exp-node:nth-child(3) .exp-node-text { transition-delay: 0.4s; }
.exp.in .exp-node:nth-child(4) .exp-dot,
.exp.in .exp-node:nth-child(4) .exp-node-text { transition-delay: 0.56s; }

/* ---------- Hard moments ---------- */

.moment-grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: 48px;
  align-items: center;
  /* The H2 lives inside the grid, so this is the kicker → H2 gap — plus the
     air a heading's line box would have brought with it. What follows the
     kicker here is the top edge of a photograph, and an edge has no leading:
     without --title-cap the picture sits ~11px higher under its kicker than
     every heading on the page does under theirs. */
  margin-top: calc(var(--gap-label-title) + var(--title-cap));
}

/* The second story's kicker is commented out in the markup. With nothing above
   it the step has nothing to step down from, and it becomes dead air at the
   top of the section — which also handed the section before it a seam 12px
   wider than every other. Matched rather than named by id, so uncommenting
   the kicker puts the step back on its own. */
main > section:not(:has(> .container > .eyebrow)) .moment-grid { margin-top: 0; }

/* The second story mirrors the first: text left, picture right. */
.moment-grid--flip { grid-template-columns: 1.15fr 0.85fr; }
.moment-grid--flip .moment-panel,
.moment-grid--flip .moment-photo { order: 2; }
.moment-grid--flip .moment-body { order: 1; }

.moment-panel {
  min-height: 320px;
  border-radius: 20px;
  background: linear-gradient(150deg, var(--grad-teal) 0%, var(--grad-sand) 55%, var(--grad-amber) 100%);
  color: var(--ink);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 32px;
  text-align: center;
  box-shadow: 0 30px 60px -34px rgba(74, 63, 54, 0.45);
}
.moment-panel svg {
  width: 34px;
  height: 34px;
  padding: 14px;
  box-sizing: content-box;
  background: rgba(255, 255, 255, 0.72);
  border-radius: 50%;
  color: var(--ink);
}
.moment-panel span {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink);
}

.moment-title {
  font-size: 30px;
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 var(--gap-title-intro);
  line-height: 1.2;
}

/* The rounding lives on the picture, not the figure, so the caption below it
   sits outside the frame. */
.moment-photo { margin: 0; }
.moment-photo picture {
  display: block;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 30px 60px -30px rgba(74, 63, 54, 0.42);
}
.moment-photo img {
  width: 100%;
  height: 100%;
  min-height: 360px;
  object-fit: cover;
}

/* The Denny's frame is a landscape source in a portrait-ish column, so `cover`
   throws away most of its width. Centre the crop on Rachel rather than on the
   middle of the plate, which is where the default would land it. */
.moment-grid--flip .moment-photo img { object-position: 57% center; }

/* Says plainly that the picture is a depiction, not documentation. */
.moment-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 12px;
  font-family: var(--font-body);
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--faint);
}
.moment-note svg { flex-shrink: 0; margin-top: 1px; }

.moment-body p {
  font-size: 16.5px;
  color: var(--body);
  line-height: 1.8;
  margin: 0 0 16px;
}
.moment-body p.emphasis {
  color: var(--ink);
  font-weight: 600;
  margin-bottom: 0;
}

/* ---------- Full story ----------
   The opening lines are always on the page — enough to start reading and
   decide. The rest is folded behind the arrow below them, and script.js
   animates the height between the peek and the full text. No inner
   scrollbox: the section grows instead.

   `--story-peek` is the visible height when collapsed, and the collapse only
   applies under `html.js` — with scripting off the whole story shows and the
   button is hidden, rather than trapping the text behind a dead control. */

.story {
  --story-peek: 190px;
  /* The fade has to end on the section's own background. Override this if a
     story is ever dropped into a section with a different ground. */
  --story-fade: var(--cream-warm);
  margin-top: 22px;
}

.story-body {
  position: relative;
  overflow: hidden;
}
.story-body p {
  font-size: 15.5px;
  line-height: 1.85;
  color: var(--body);
  margin-bottom: 16px;
}
.story-body p:last-child { margin-bottom: 0; }

.js .story-body.is-collapsed { max-height: var(--story-peek); }

/* The text runs out under a fade rather than a hard cut, so it reads as
   "continues" instead of "ends here". Sits on the animated bottom edge, so
   it tracks the height as it travels. */
.story-body::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 76px;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(252, 241, 233, 0) 0%, var(--story-fade) 88%);
  opacity: 0;
  transition: opacity 0.35s ease;
}
.js .story-body.is-collapsed::after { opacity: 1; }

html:not(.js) .story-toggle { display: none; }

/* Read it, or hear her tell it. */
.story-actions {
  display: flex;
  align-items: center;
  gap: 10px 18px;
  flex-wrap: wrap;
  margin-top: 14px;
}
.story-actions .voice-wrap { display: flex; }

.story-toggle {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--font-body);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  transition: color 0.2s ease;
}
.story-toggle:hover { color: var(--terra); }

/* The arrow itself: small, round, and nudging downward until it's used. */
.story-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--white);
  border: 1.5px solid var(--cream-line);
  color: var(--ink);
  box-shadow: 0 8px 18px -12px rgba(74, 63, 54, 0.55);
  animation: storyNudge 2.4s ease-in-out infinite;
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1),
              background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.story-toggle:hover .story-arrow {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--white);
  animation: none;
}
.story-toggle[aria-expanded='true'] .story-arrow {
  transform: rotate(180deg);
  animation: none;
}
.story-toggle[aria-expanded='true'] .story-label { color: var(--muted); }

@keyframes storyNudge {
  0%, 100%   { transform: translateY(0); }
  45%        { transform: translateY(4px); }
  70%        { transform: translateY(0); }
}

/* While a story is open the section is taller than the window on purpose —
   snapping would fight you halfway down the text. */
main > section.story-open { scroll-snap-align: none; }

@media (min-width: 901px) {
  /* Open, the text column is much taller than the photo. Top-align the row
     and let the photo ride along beside the reading instead of sitting
     marooned in the middle of it. */
  .story-open .moment-grid { align-items: start; }
  .story-open .moment-photo {
    position: sticky;
    top: calc(var(--header-h) + 24px);
    /* Closed, the picture is stretched by the row to fill the section's band.
       Top-aligning drops that stretch, so without a height here the picture
       visibly shrinks the moment the story opens — the button reads as if it
       resized the photo. This is the height the band was already giving it:
       one screen, less the seam and whatever sits above the row. */
    --moment-lead: 0px;
    height: calc(100svh - var(--header-h) - var(--seam) - var(--moment-lead));
  }
  /* A kicker above the row costs it that much: the line itself, plus the step
     under it — and only the first story carries one, the second one's being
     commented out in the markup. Matched rather than named by id, so
     uncommenting it puts this right on its own. */
  .story-open:has(> .container > .eyebrow) .moment-photo {
    --moment-lead: calc(14px + var(--gap-label-title) + var(--title-cap));
  }
}

.tag-row {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
  margin-top: 28px;
}
/* One pill, two consumers: the Experience tags and the certification skills.
   Kept as a single rule so the two can't drift apart again. */
.tag,
.cert-skills li {
  font-family: var(--font-body);
  font-size: 12.5px;
  color: var(--ink);
  background: var(--mint);
  border-radius: 999px;
  padding: var(--pill-pad);
  font-weight: 600;
}

/* ---------- Skills ---------- */

.skills-intro {
  font-size: 16px;
  color: var(--muted);
  line-height: 1.65;
  margin: var(--gap-title-intro) 0 var(--gap-title-content);
  max-width: 62ch;
}

/* Certification — one wide band above the filter tabs.
   Laid out sideways rather than as a tall feature card on purpose: the Skills
   section already has to clear one screen on desktop, and a stacked version
   pushes the third group off the bottom. The thumbnail keeps the certificate
   itself in view — a claim you can see is worth more than a line of text —
   and the terra border is the only one on the page, which is what makes the
   band read as the exception rather than a fourth skill group. */
.cert {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  padding: var(--sp-4) var(--sp-5);
  background: var(--cream);
  border: 1px solid var(--terra-line);
  border-radius: 18px;
  scroll-margin-top: calc(var(--header-h) + var(--sp-5));
}

/* The scan. Decorative in the accessibility tree — the heading and the button
   beside it already say what it is and where it leads — so the band offers
   one link to screen readers rather than the same link twice. */
.cert-shot {
  flex: 0 0 200px;
  display: block;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--card-line);
  background: var(--white);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.cert-shot img { display: block; width: 100%; height: auto; }
.cert-shot:hover {
  transform: translateY(-2px);
  box-shadow: 0 16px 30px -18px rgba(23, 51, 44, 0.45);
}

.cert-body { flex: 1 1 auto; min-width: 0; }

.cert-eyebrow {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-2);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--terra);
}
.cert-eyebrow svg { flex-shrink: 0; }

.cert-title {
  font-family: var(--font-display);
  font-size: 24px;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 0;
}
/* The same pill as `.tag` in Experience, so the two skill lists read as one
   component: filled mint, no outline, and no per-breakpoint resizing. */
.cert-skills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  list-style: none;
  margin: var(--sp-4) 0;
  padding: 0;
}
/* .cert-skills li is styled with .tag above. */
.cert-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px var(--sp-4);
  margin: var(--sp-3) 0 0;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--faint);
}
/* The credential code is what makes the claim checkable, so it stays in full
   rather than being truncated — it just has to be allowed to break. */
.cert-id { overflow-wrap: anywhere; }

.cert-link { flex-shrink: 0; }

/* ----- Certification tab on its own -----
   `.cert-solo` is set on the grid by the script when this tab is picked, and
   is on in the markup because Certification is the tab the section opens on.
   The certificate has a screen to itself, so the scan is read rather than
   recognised: everything below is the same band, bigger, with the details
   stacked beside a certificate that's the widest thing in the section. */
.cert-solo .cert {
  gap: var(--sp-8);
  padding: var(--sp-7);
}
.cert-solo .cert-shot { flex: 0 1 520px; }
.cert-solo .cert-eyebrow { font-size: 11.5px; margin-bottom: var(--sp-3); }
.cert-solo .cert-title { font-size: 32px; }
.cert-solo .cert-skills { margin: var(--sp-5) 0; gap: var(--sp-3); }
.cert-solo .cert-meta { margin-top: var(--sp-5); font-size: 11.5px; }

/* Filter tabs sit on one row above the groups. */
.skill-tabs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 16px;
  margin-bottom: var(--sp-6);
}
.skill-tablist {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.skill-tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  padding: 9px 18px;
  border-radius: 999px;
  border: 1.5px solid var(--mint-line);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: transform 0.22s ease, box-shadow 0.22s ease, background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.skill-tab:hover {
  transform: translateY(-2px);
  box-shadow: 0 14px 28px -14px rgba(23, 51, 44, 0.5);
}
.skill-tab[aria-selected='true'] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--white);
}
.skill-tab .dot { width: 8px; height: 8px; border-radius: 50%; }

/* Each category keeps its own labelled band, with a count and a rule that
   runs out to the edge of the grid. */
.skill-group { display: block; margin-bottom: var(--sp-7); }
.skill-group:last-child { margin-bottom: 0; }
.skill-group-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
/* A group's accent is data, not decoration: it follows from data-group, so the
   markup carries no colour of its own. The tabs mirror the same mapping. */
.skill-group[data-group="cert"]  { --group-dot: var(--terra);      --group-rule: var(--terra-line); }
.skill-group[data-group="tools"] { --group-dot: var(--green-deep); --group-rule: var(--green-line); }
.skill-group[data-group="hard"]  { --group-dot: var(--terra);      --group-rule: var(--sand-line);  }
.skill-group[data-group="soft"]  { --group-dot: var(--teal-deep);  --group-rule: var(--teal-line);  }
.skill-tab[data-filter="cert"]  .dot { background: var(--terra); }
.skill-tab[data-filter="tools"] .dot { background: var(--green-deep); }
.skill-tab[data-filter="hard"]  .dot { background: var(--terra); }
.skill-tab[data-filter="soft"]  .dot { background: var(--teal-deep); }

.skill-group-dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--group-dot);
}
.skill-group-name {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  color: var(--ink);
  margin: 0;
}
.skill-group-count {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--faint);
}
.skill-group-rule { flex: 1; height: 1px; background: var(--group-rule); }

.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-4);
}
/* One row per group. Four columns is the width every card on the page is cut
   to — the same figure `.card-grid--tools` reaches by flex — so a group that
   wanted a fifth column would be quietly making its own cards narrower than
   the ones a section away. There is no `--5`; wrap instead. */
.card-grid--4 { grid-template-columns: repeat(4, 1fr); }

/* Tools carry seven cards, and five equal columns made every one of them worse:
   titles broke onto two lines and the short cards sat half empty under the
   tallest one in the row. Four wider cards fix the wrap; flex rather than grid
   so the trailing row of three centres under the four above it. */
.card-grid--tools {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-4);
}
/* Tools are the one group carrying outside brand marks, so the tiles take the
   warm cream instead of the near-white every other card uses — it settles the
   multicolour logos down and ties the group back to the certification band
   above, the other place a third-party credential shows up. */
.card-grid--tools .skill-card {
  flex: 0 1 calc(25% - 12px);
  min-width: 0;
  background: var(--cream);
}

.skill-card {
  display: flex;
  gap: var(--sp-3);
  /* Centred, not top-aligned: with two-line descriptions a top-pinned tile
     left a wedge of dead space under every short card in the row. */
  align-items: center;
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: 16px;
  padding: 20px 22px;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
/* Strengths is the one card grid sitting on .section-cream. The near-white
   --card is only a shade off that background, so the tiles lose their edges
   there — plain white is what gives them back. Skills & Tools keeps --card
   (and --cream for the brand tiles) because it sits on white already. */
#strengths .skill-card { background: var(--white); }
.skill-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 30px -18px rgba(23, 51, 44, 0.35);
  border-color: var(--mint-line);
}
.skill-card .icon {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.skill-card h4 {
  font-size: 16.5px;
  color: var(--ink);
  margin: 0 0 5px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.skill-card p {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.55;
  margin: 0;
}

.icon--teal  { background: var(--mint);     color: var(--teal-deep); }
.icon--terra { background: var(--terra-tint);         color: var(--terra); }
.icon--green { background: var(--pill-green);         color: var(--green-deep); }
.icon--warm  { background: var(--cream-warm); color: var(--terra); }
/* Brand marks bring their own colours, so the tile behind them stays neutral —
   a tinted one fights the logo. The colour is for the two tools with no public
   mark (iPass, POS), whose line icons still draw in currentColor. */
.icon--brand { background: var(--white); border: 1px solid var(--card-line); color: var(--green-deep); }
.skill-card .icon svg { display: block; }

.chip {
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--terra);
  background: var(--cream-warm);
  border: 1px solid var(--terra-line);
  border-radius: 999px;
  padding: 2px 6px;
}

/* Earned, not in progress — the deep green is the Tools group's own colour,
   so it reads as settled rather than as another terra "working on it" flag. */
.chip--earned {
  color: var(--green-deep);
  background: var(--mint);
  border-color: var(--mint-line);
}

/* ---------- Hobbies ---------- */

.hobby-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}

/* Padding, icon and title all step down to `.skill-card`'s values — the two
   grids sit one section apart and the hobby boxes were reading as the larger
   of the two for no reason other than their stacked layout. */
.hobby-card {
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: 16px;
  padding: 20px 22px;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.hobby-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 30px -18px rgba(23, 51, 44, 0.35);
  border-color: var(--mint-line);
}
.hobby-card .icon {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--sp-3);
}
.hobby-card .icon svg { width: 21px; height: 21px; }
/* Same "compact card title" step as .skill-card h4 — keep the two in step. */
.hobby-card h3 {
  font-size: 16.5px;
  color: var(--ink);
  margin: 0 0 5px;
  font-weight: 600;
}
.hobby-card p {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.55;
  margin: 0;
}

/* ---------- CSAT ----------
   The last box in the hobby row's family, not a form borrowed from Contact.
   Same warm card, same hairline, same 16px radius and same lift on hover as
   `.hobby-card`, and the type steps off the same two sizes — 16.5px title,
   13px body. Laid out as one row (question left, stars right) so it stays a
   box the height of a card instead of a stacked panel. */

.csat { margin-top: var(--sp-4); }

.csat-form,
.csat-done {
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: 16px;
  padding: 18px 22px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.csat-form:hover,
.csat-done:hover { border-color: var(--mint-line); }

.csat-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.csat-form[hidden],
.csat-done[hidden],
.csat-more[hidden] { display: none; }

/* Question and scale side by side, wrapping to a stack only when there is not
   room for both — the stars are the widest fixed thing here, so they set the
   break rather than a breakpoint. */
.csat-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-6);
}

.csat-head { min-width: 0; }
.csat-kicker {
  display: block;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--teal);
  margin-bottom: 5px;
}
.csat-head h3 {
  font-size: 16.5px;
  font-weight: 600;
  color: var(--ink);
  margin: 0;
  line-height: 1.35;
}

.csat-pick {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  flex-shrink: 0;
}
.csat-stars { display: flex; gap: 2px; }

/* The star is the control — no box, no fill, just the outline warming to sand
   as it is chosen. Sized to the hobby-card icon rather than to a form control,
   which is what kept the old version reading as a panel. */
.csat-star {
  border: 0;
  background: none;
  padding: 3px;
  cursor: pointer;
  color: var(--cream-line-deep);
  line-height: 0;
  border-radius: 8px;
  transition: color 0.15s ease, transform 0.15s ease;
}
.csat-star svg { width: 25px; height: 25px; }
.csat-star.is-lit { color: var(--sand); }
.csat-star.is-lit svg { fill: currentColor; }

/* `is-previewing` is set by the script only while a run is actually being
   previewed. Gating on :hover/:focus-within instead looked right until a click
   both chose a star and left focus in the group — the preview cleared, the
   gate stayed open, and every star went grey under the visitor's cursor. */
.csat-stars.is-previewing .csat-star:not(.is-hot) { color: var(--cream-line-deep); }
.csat-stars.is-previewing .csat-star:not(.is-hot) svg { fill: none; }
.csat-stars.is-previewing .csat-star.is-hot { color: var(--sand); }
.csat-stars.is-previewing .csat-star.is-hot svg { fill: currentColor; }

.csat-star:hover { transform: scale(1.1); }
.csat-star:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* Ends of the scale while nothing is picked; a single centred phrase once one
   is. The min-height stops that swap from nudging the card. */
.csat-scale {
  display: flex;
  justify-content: space-between;
  /* Wider than the five stars, which is what sets the column's width — the two
     end labels are longer than the row they describe, and letting them wrap or
     spill was the alternative. The stars then centre over them. */
  width: 100%;
  min-width: 215px;
  white-space: nowrap;
  margin: 0;
  min-height: 14px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}
.csat-scale--picked {
  justify-content: center;
  color: var(--terra);
}

/* The follow-up opens under a hairline rather than as a second card, so the
   box grows by one row instead of turning into a panel. */
.csat-more {
  border-top: 1px solid var(--card-line);
  padding-top: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.csat-more label {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.5;
}
.csat-more label span {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
}
/* Textarea and button on one line — a full-width send button under a two-row
   field is the shape of a contact form, which is the thing this is not. */
.csat-send-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
}
.csat-more textarea {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-body);
  font-size: 13.5px;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--card-line);
  border-radius: 11px;
  padding: 9px 12px;
  outline: none;
  resize: vertical;
  min-height: 42px;
  transition: border-color 0.18s ease;
}
.csat-more textarea:focus { border-color: var(--teal); }
.csat-more textarea::placeholder { color: var(--placeholder); }
.csat-send {
  flex: 0 0 auto;
  padding: 11px 24px;
  font-weight: 600;
}

.csat-form .form-error { font-size: 13px; }

/* Confirmation keeps the box's own footprint: a mark and two lines on one row,
   not a full-height panel with a centred tick. */
.csat-done {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}
.csat-done .check {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--mint);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--teal-deep);
}
.csat-done h3 {
  font-size: 16.5px;
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 3px;
}
.csat-done p {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.55;
  margin: 0;
}

/* ---------- Contact ---------- */

.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 56px;
  align-items: start;
  /* As in .moment-grid — the H2 is inside, so this is the kicker → H2 gap. */
  margin-top: var(--gap-label-title);
}

.contact-intro h2 {
  font-size: 42px;
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 var(--gap-title-intro);
}
.contact-intro > p {
  font-size: 16px;
  color: var(--teal-ink);
  line-height: 1.7;
  margin: 0 0 var(--gap-title-content);
  max-width: 42ch;
}

.contact-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.contact-list li {
  display: flex;
  align-items: center;
  gap: 11px;
  color: var(--teal-deep);
}
.contact-list svg { flex-shrink: 0; }
.contact-list a,
.contact-list span {
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: 13.5px;
  overflow-wrap: anywhere;
}
.contact-list a:hover { color: var(--teal-deep); }

.socials {
  display: flex;
  gap: 12px;
  margin-top: 28px;
  flex-wrap: wrap;
}
.socials a {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--white);
  border: 1px solid var(--teal-line-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.socials a:hover {
  background: var(--ink);
  color: var(--white);
  border-color: var(--ink);
}

.contact-form,
.form-done {
  background: var(--white);
  border: 1px solid var(--mint-line);
  border-radius: 20px;
  box-shadow: 0 24px 46px -28px rgba(23, 51, 44, 0.3);
}
.contact-form {
  padding: 28px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.contact-form label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--teal-muted);
  display: block;
  margin-bottom: 7px;
}
.contact-form input,
.contact-form textarea {
  width: 100%;
  font-size: 15px;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--mint-line);
  border-radius: 11px;
  padding: 12px 14px;
  outline: none;
  transition: border-color 0.18s ease, background 0.18s ease;
}
.contact-form textarea { resize: vertical; min-height: 108px; }
.contact-form input:focus,
.contact-form textarea:focus {
  border-color: var(--teal);
  background: var(--white);
}
.contact-form input::placeholder,
.contact-form textarea::placeholder { color: var(--placeholder); }

.contact-form .btn--solid {
  padding: 15px;
  font-weight: 600;
  text-align: center;
}

/* Only rendered when a send actually fails. Warm rather than alarming — it
   still ends with a way to reach her. */
.form-error {
  margin: 0;
  padding: 12px 14px;
  border-radius: 12px;
  background: var(--error-bg);
  border: 1px solid var(--error-line);
  color: var(--error-ink);
  font-size: 0.92rem;
  line-height: 1.5;
}
.form-error a { color: inherit; font-weight: 600; }
.form-error[hidden] { display: none; }

.form-done {
  padding: 44px 28px;
  text-align: center;
}
.form-done .check {
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: var(--mint);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 16px;
  color: var(--teal-deep);
}
.form-done h3 {
  font-size: 22px;
  color: var(--ink);
  margin: 0 0 8px;
  font-weight: 600;
}
.form-done p {
  font-size: 14.5px;
  color: var(--muted);
  margin: 0;
}

.site-footer {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.06em;
  color: var(--teal-muted);
  margin-top: var(--gap-block);
  padding-top: 26px;
  border-top: 1px solid rgba(23, 51, 44, 0.12);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
}

/* Build credit. flex-basis: 100% drops it onto its own row below the two info
   spans, so the footer stays a two-column line with a quiet signature under it. */
.site-footer__credit {
  flex-basis: 100%;
  font-size: 10.5px;
  letter-spacing: 0.08em;
  color: var(--faint);
}

.site-footer__credit a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid rgba(23, 51, 44, 0.18);
  padding-bottom: 1px;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.site-footer__credit a:hover,
.site-footer__credit a:focus-visible {
  color: var(--teal-deep);
  border-bottom-color: var(--teal-deep);
}

/* ---------- Floating chat ----------
   Bottom-right "message Rachel" widget. Everything in it is built or driven
   by script.js, so the whole block is scoped to .js — with scripting off the
   launcher never appears and the contact section is the only route, which is
   what we want rather than a dead button. */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.chat { display: none; }
.js .chat {
  display: block;
  position: fixed;
  right: 26px;
  bottom: 26px;
  /* Over the sticky header (50) but under the skip link (100). */
  z-index: 80;
  font-family: var(--font-body);
}

/* --- Launcher --- */

.chat-launcher {
  position: relative;
  display: block;
  width: 68px;
  height: 68px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--ink);
  cursor: pointer;
  box-shadow: 0 18px 34px -14px rgba(23, 51, 44, 0.55);
  transition: transform 0.22s ease, box-shadow 0.22s ease;
  /* The ring is drawn with a border on the face, not on the button, so the
     shadow underneath stays a clean circle. */
}
.chat-launcher:hover { transform: translateY(-3px) scale(1.04); }
.chat-launcher:active { transform: translateY(0) scale(0.98); }

.chat-launcher-face {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  overflow: hidden;
  border: 2.5px solid var(--white);
  transition: opacity 0.2s ease, transform 0.28s ease;
}
.chat-launcher-face picture,
.chat-launcher-face img { display: block; width: 100%; height: 100%; }
.chat-launcher-face img { object-fit: cover; object-position: 50% 22%; }

/* Open state swaps the portrait for a close X, in place. */
.chat-launcher-x {
  position: absolute;
  inset: 0;
  margin: auto;
  color: var(--white);
  opacity: 0;
  transform: rotate(-90deg) scale(0.6);
  transition: opacity 0.2s ease, transform 0.28s ease;
}
.chat.is-open .chat-launcher-face { opacity: 0; transform: scale(0.6); }
.chat.is-open .chat-launcher-x { opacity: 1; transform: rotate(0) scale(1); }

/* "She's around" dot — same green in both places it appears. */
.chat-dot {
  position: absolute;
  right: 3px;
  bottom: 3px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--success);
  border: 2.5px solid var(--white);
}
.chat-launcher > .chat-dot::after {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  border: 2px solid var(--success);
  animation: chatPing 2.4s ease-out infinite;
}
@keyframes chatPing {
  0%        { transform: scale(1);   opacity: 0.7; }
  70%, 100% { transform: scale(2.1); opacity: 0; }
}
.chat.is-open .chat-launcher > .chat-dot { display: none; }

.chat-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 21px;
  height: 21px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--terra);
  border: 2px solid var(--cream);
  color: var(--white);
  font-size: 11px;
  font-weight: 700;
  line-height: 17px;
}
.chat-badge[hidden] { display: none; }

/* --- Nudge bubble --- */

.chat-nudge {
  position: absolute;
  right: 6px;
  bottom: 82px;
  width: 258px;
  padding: 15px 17px;
  background: var(--white);
  border: 1px solid var(--card-line);
  border-radius: 18px 18px 6px 18px;
  box-shadow: 0 20px 40px -22px rgba(23, 51, 44, 0.55);
  animation: chatNudgeIn 0.42s cubic-bezier(0.22, 1.2, 0.4, 1) both;
}
.chat-nudge p {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--body);
}
.chat-nudge strong { color: var(--ink); }
/* Tail, pointing down at the launcher */
.chat-nudge::after {
  content: '';
  position: absolute;
  right: 20px;
  bottom: -7px;
  width: 14px;
  height: 14px;
  background: var(--white);
  border-right: 1px solid var(--card-line);
  border-bottom: 1px solid var(--card-line);
  transform: rotate(45deg);
}
.chat-nudge-x {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--faint);
  cursor: pointer;
  padding: 0;
}
.chat-nudge-x:hover { background: var(--cream); color: var(--ink); }

@keyframes chatNudgeIn {
  from { opacity: 0; transform: translateY(10px) scale(0.94); }
  to   { opacity: 1; transform: none; }
}

/* --- Panel --- */

.chat-panel {
  position: absolute;
  right: 0;
  bottom: 84px;
  width: 366px;
  max-width: calc(100vw - 36px);
  max-height: min(600px, calc(100vh - 130px));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: 24px;
  box-shadow: 0 40px 70px -30px rgba(23, 51, 44, 0.6);
  transform-origin: 100% 100%;
  animation: chatPanelIn 0.34s cubic-bezier(0.22, 1.15, 0.4, 1) both;
}
/* It only takes focus so the panel is where the keyboard lands when it opens
   on a phone — it isn't a control, so it shouldn't wear a focus ring. */
.chat-panel:focus,
.chat-panel:focus-visible { outline: none; }
@keyframes chatPanelIn {
  from { opacity: 0; transform: translateY(16px) scale(0.92); }
  to   { opacity: 1; transform: none; }
}

.chat-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 15px;
  background: var(--mint);
  border-bottom: 1px solid var(--mint-line);
  flex-shrink: 0;
}

.chat-avatar {
  position: relative;
  flex-shrink: 0;
  display: block;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  overflow: visible;
}
.chat-avatar--lg { width: 46px; height: 46px; }
.chat-avatar picture { display: block; width: 100%; height: 100%; }
.chat-avatar img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  object-position: 50% 22%;
  border: 2px solid var(--white);
}
.chat-avatar .chat-dot {
  right: -1px;
  bottom: -1px;
  width: 12px;
  height: 12px;
  border-width: 2px;
  border-color: var(--mint);
}

.chat-who { flex: 1; min-width: 0; line-height: 1.3; }
.chat-who strong {
  display: block;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}
.chat-who span {
  display: block;
  font-size: 11.5px;
  color: var(--teal-muted-deep);
  margin-top: 2px;
}

.chat-x {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  padding: 0;
}
.chat-x:hover { background: rgba(23, 51, 44, 0.09); }

/* --- Thread --- */

.chat-log {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 16px 15px 6px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  scrollbar-width: thin;
}

.chat-day {
  align-self: center;
  margin: 0 0 4px;
  padding: 3px 11px;
  border-radius: 999px;
  background: rgba(23, 51, 44, 0.06);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
}

/* One row = avatar gutter + bubble. The gutter stays even when the avatar is
   omitted, so a run of her messages lines up flush. */
.chat-row {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  max-width: 90%;
  animation: chatMsgIn 0.32s cubic-bezier(0.22, 1.15, 0.4, 1) both;
}
.chat-row--me { align-self: flex-end; flex-direction: row-reverse; }
.chat-row--her { align-self: flex-start; }
.chat-row .chat-avatar { align-self: flex-end; margin-bottom: 2px; }
.chat-row .chat-avatar .chat-dot { display: none; }
.chat-row--her.no-face { padding-left: 42px; }

@keyframes chatMsgIn {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: none; }
}

.chat-bubble {
  padding: 10px 14px;
  border-radius: 18px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--body);
  overflow-wrap: anywhere;
}
.chat-row--her .chat-bubble {
  background: var(--white);
  border: 1px solid var(--card-line);
  border-bottom-left-radius: 6px;
}
.chat-row--me .chat-bubble {
  background: var(--mint);
  border: 1px solid var(--mint-line);
  color: var(--ink);
  border-bottom-right-radius: 6px;
}
.chat-bubble p { margin: 0; font-size: inherit; line-height: inherit; }
.chat-bubble a { color: var(--teal-deep); text-decoration: underline; text-underline-offset: 2px; }
.chat-bubble p + p { margin-top: 7px; }
.chat-bubble .chat-time {
  display: block;
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--faint);
  text-align: right;
}

/* Typing dots */
.chat-typing {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 13px 15px;
}
.chat-typing i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--faint);
  animation: chatType 1.1s ease-in-out infinite;
}
.chat-typing i:nth-child(2) { animation-delay: 0.16s; }
.chat-typing i:nth-child(3) { animation-delay: 0.32s; }
@keyframes chatType {
  0%, 100% { opacity: 0.3; transform: translateY(0); }
  40%      { opacity: 1;   transform: translateY(-3px); }
}

/* --- Voice message ---
   Plays the same assets/rachel-intro.mp3 the hero button does, off the same
   <audio> element, so the two controls stay in sync and the clip is only ever
   downloaded once. */

/* The voice note is its own control, so the bubble drops its padding and the
   row takes the full width of the log — the waveform needs the room. */
.chat-row--voice { max-width: 100%; width: 100%; }
.chat-row--voice .chat-bubble { flex: 1; min-width: 0; }
.chat-bubble--voice { padding: 0; }

.chat-voice {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 13px 9px 9px;
}
.chat-voice-btn {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: var(--ink);
  color: var(--white);
  cursor: pointer;
  padding: 0;
  transition: background 0.2s ease;
}
.chat-voice-btn:hover { background: var(--teal-deep); }
.chat-voice-btn .icon-play { margin-left: 2px; }
.chat-voice-btn .icon-pause { display: none; }
.chat-voice.is-playing .chat-voice-btn { background: var(--teal-deep); }
.chat-voice.is-playing .icon-play { display: none; }
.chat-voice.is-playing .icon-pause { display: block; }

/* Click anywhere on the bars to seek. */
.chat-wave {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 2px;
  height: 26px;
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
}
.chat-wave i {
  flex: 1;
  min-width: 2px;
  height: var(--h, 40%);
  border-radius: 2px;
  background: var(--mint-line);
  transition: background 0.12s linear;
  pointer-events: none;
}
.chat-wave i.on { background: var(--teal-deep); }

.chat-voice-time {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

/* --- Composer --- */

.chat-foot {
  flex-shrink: 0;
  padding: 10px 15px 13px;
  background: var(--white);
  border-top: 1px solid var(--card-line);
}

.chat-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
}
.chat-chip {
  border: 1px solid var(--mint-line);
  background: var(--mint);
  color: var(--ink);
  border-radius: 999px;
  padding: 7px 13px;
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.18s ease, transform 0.18s ease;
}
.chat-chip:hover { background: var(--teal); color: var(--white); transform: translateY(-1px); }

.chat-input {
  width: 100%;
  min-height: 42px;
  max-height: 110px;
  resize: none;
  padding: 11px 14px;
  border: 1px solid var(--card-line);
  border-radius: 14px;
  background: var(--card);
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--ink);
  outline: none;
  transition: border-color 0.18s ease, background 0.18s ease;
}
.chat-input:focus { border-color: var(--teal); background: var(--white); }
.chat-input::placeholder { color: var(--placeholder); }

.chat-send {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  margin-top: 9px;
}
.chat-go {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 11px 10px;
  border: 0;
  border-radius: 12px;
  font-family: var(--font-body);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--white);
  cursor: pointer;
  transition: transform 0.18s ease, box-shadow 0.18s ease, filter 0.18s ease;
}
.chat-go svg { flex-shrink: 0; }
/* Answering a question: one Send, with a quiet way back out beside it. */
.chat-send--step { grid-template-columns: 1fr auto; }

.chat-cancel {
  border: 0;
  background: none;
  padding: 0 12px;
  font-family: var(--font-body);
  font-size: 12.5px;
  color: var(--faint);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.chat-cancel:hover { color: var(--ink); }

.chat-go--mail { background: var(--ink); }
.chat-go[disabled] { opacity: 0.55; cursor: default; transform: none; }
.chat-input[disabled] { opacity: 0.6; cursor: default; }
.chat-go--wa   { background: var(--wa-brand); color: var(--wa-ink); }
.chat-go:hover { transform: translateY(-2px); box-shadow: 0 12px 22px -12px rgba(23, 51, 44, 0.6); }
.chat-go:active { transform: translateY(0); }

/* While Rachel is mid-turn the composer is locked, so the controls stop
   inviting a click — no lift on hover, no pointer. */
.chat-go[disabled]:hover { transform: none; box-shadow: none; }
.chat-cancel[disabled] { opacity: 0.45; cursor: default; }
.chat-cancel[disabled]:hover { color: inherit; }

.chat-note {
  margin: 9px 0 0;
  text-align: center;
  font-size: 11px;
  color: var(--faint);
}

/* ---------- Filling the screen ----------
   Companion to the one-section-per-screen rule near the top of the file, kept
   down here so it lands after the components it adjusts. Blocks that would
   otherwise leave a section half empty grow with the window, so spare height
   becomes picture and card instead of padding. */

@media (min-width: 901px) {
  /* Each section hands its leftover height to exactly one block, named here
     rather than picked with `:last-child` — Contact's footer has to stay on
     the bottom edge, and the hero's two columns are the container itself.
     Every one of these is a grid, so the height lands in stretched rows:
     picture, panel and card grow, and the ink reaches the padding edge
     instead of floating in the middle of the screen. */
  #about .about-grid,
  #hardmoment .moment-grid,
  #hardmoment-2 .moment-grid,
  #contact .contact-grid { flex: 1 1 auto; min-height: 0; }

  /* Hobbies is the one section with two blocks and no block that wants the
     spare height — the row is a row of cards and the CSAT box is a box. So it
     centres its stack in the band instead of handing the leftover to one of
     them and stretching it out of shape. */
  #hobbies > .container { justify-content: center; }

  /* Hobbies. Back to one row of four, each card a step smaller — halving the
     row into two rows of two was a way of filling a band that now has the CSAT
     box in it as well. At a quarter width a hobby card is a mark, a name and a
     line at the size those three want to be, and the row costs one card's
     height instead of two. Density steps to `.skill-card`'s, which is the
     other four-across grid on the page. */
  .hobby-grid { grid-template-columns: repeat(4, 1fr); gap: 16px; }
  .hobby-card { padding: 18px 20px; }
  .hobby-card h3 { font-size: 16px; }
  .hobby-card p { font-size: 12.5px; max-width: 32ch; }
  .hobby-card .icon {
    width: 40px;
    height: 40px;
    border-radius: 12px;
    margin-bottom: var(--sp-2);
  }
  .hobby-card .icon svg { width: 20px; height: 20px; }

  /* One step up on the desktop card, and the gap to the hobby row opens to a
     proper block step now that the section is no longer filling the band. */
  .csat { margin-top: var(--sp-5); }
  .csat-form,
  .csat-done { padding: 20px 24px; }

  /* Hero. The container *is* the two-column grid here, so it takes the strip
     and the seam padding directly instead of through a child. */
  .hero .container {
    min-height: calc(100vh - var(--header-h));
    min-height: calc(100svh - var(--header-h));
    padding-block: calc(var(--seam) / 2);
    align-items: stretch;
  }
  .hero-copy {
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  /* Moments. The picture and the panel take the height; the prose stays
     centred against them rather than being stretched into a tall column of
     gaps. The old clamped min-heights are gone — they were standing in for
     the stretch this now does properly, and left as floors they would fight
     it on short windows. */
  .moment-grid { align-items: stretch; }
  .moment-body { display: flex; flex-direction: column; justify-content: center; }
  .moment-photo { display: flex; flex-direction: column; }
  .moment-photo picture { flex: 1 1 auto; min-height: 0; }
  .moment-photo img { height: 100%; min-height: 260px; }
  .moment-panel { min-height: 260px; }

  /* About: the portrait grows, the copy beside it stays centred. */
  .about-grid { align-items: stretch; }
  .about-grid > .prose { display: flex; flex-direction: column; justify-content: center; }

  /* Contact: the two columns take the height and the footer sits on the
     bottom edge, so the leftover reads as layout rather than as a gap under
     the form. */
  .contact-grid { align-items: stretch; }
  .contact-intro { display: flex; flex-direction: column; justify-content: center; }
  #contact .site-footer { margin-top: auto; padding-top: 40px; }
}

/* Tall windows. Same one-screen rule, but the spare height goes into bigger
   type and roomier components instead of empty bands top and bottom. */
@media (min-width: 1200px) and (min-height: 950px) {
  :root {
    --section-y: 56px;
    /* Spare height goes into the heading step; the block step is already
       generous at 48px and growing it just pulls sections apart. */
    --gap-title-content: var(--sp-8);
  }

  .hero h1 { font-size: 62px; }
  .hero-lede { font-size: 19px; margin-top: 30px; }
  .hero-actions { margin-top: 40px; }
  .hero-photo { aspect-ratio: 4 / 5.15; }
  .hero-stat { padding: 24px 20px; }

  :root { --title-size: 42px; }
  .prose p { font-size: 18px; line-height: 1.85; }
  .pullquote p { 
    font-size: 18px; }
  .stat { padding: 24px 20px; }

  .exp-dot { width: 64px; height: 64px; }
  .exp-rail { top: 32px; }
  .exp-stage { margin-top: 40px; }
  .exp-card { padding: 34px 38px; }
  .exp-name { font-size: 28px; }
  .exp-desc { font-size: 17px; }

  .moment-title { font-size: 33px; }
  .moment-body p { font-size: 17.5px; }

  /* Padding, icon and title are already at their spec maximum in the base
     rule — only the description takes the extra half-point here. */
  .skill-card p { font-size: 13.5px; }

  .contact-intro h2 { font-size: 48px; }
  .contact-intro > p { font-size: 17.5px; }
  .contact-form { padding: 34px; gap: 20px; }
  .contact-form input,
  .contact-form textarea { font-size: 16px; padding: 14px 16px; }
  .contact-form textarea { min-height: 150px; }
}

/* ==========================================================================
   Responsive
   ========================================================================== */

/* Laptop / small desktop. The gutter tightens early (1200 rather than 1080)
   because between those widths the extra text wrapping is what pushes the
   denser sections past one screen. */
@media (max-width: 1200px) {
  :root { --gutter: 32px; }
  .skill-card { padding: 18px 18px; }
  .skill-card p { line-height: 1.5; }
}

/* Narrow desktop: the two-column sections still have to clear one screen, so
   the body copy steps down a notch. */
@media (max-width: 1080px) {
  /* Seven links plus the button, and the band starts at 901px — the gap is
     what gives on the way down, before anything has to wrap. */
  .nav { gap: 16px; font-size: 14px; }
  .hero h1 { font-size: 48px; }
  :root { --title-size: 33px; }
  .prose p { font-size: 15.5px; line-height: 1.7; }
  .prose .prose-quote { font-size: 17px; }
  .pullquote p { 
    font-size: 15.5px; }
  .moment-body p { font-size: 15.5px; }
  .exp-intro { font-size: 15px; }
  .exp-dot { width: 52px; height: 52px; }
  .exp-rail { top: 26px; }
  .exp-co { font-size: 14px; }
  .exp-stage { margin-top: 28px; }
  .exp-card { padding: 26px 28px; }
  .exp-name { font-size: 23px; }
  .exp-desc { font-size: 15px; margin-top: 12px; }
  .skill-card h4 { font-size: 16px; }
  .skill-card p { font-size: 12.5px; }
}

/* Tablet â€” collapse the nav into a menu, relax the multi-column grids */
@media (max-width: 900px) {
  :root { --section-y: 64px; }

  /* Nav */
  .nav-toggle { display: inline-flex; }
  .nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    padding: 12px var(--gutter) 22px;
    background: var(--mint);
    border-bottom: 1px solid rgba(23, 51, 44, 0.1);
    box-shadow: 0 24px 34px -26px rgba(23, 51, 44, 0.6);
    font-size: 16px;
    white-space: normal;
    /* Collapsed state â€” animated so the menu doesn't pop */
    display: flex;
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    visibility: hidden;
    transition: max-height 0.3s ease, opacity 0.2s ease, visibility 0s linear 0.3s;
  }
  .site-header.nav-open .nav {
    max-height: 560px;
    opacity: 1;
    visibility: visible;
    transition: max-height 0.35s ease, opacity 0.25s ease, visibility 0s;
  }
  .nav .navlink {
    padding: 13px 2px;
    border-bottom: 1px solid rgba(23, 51, 44, 0.08);
  }
  .nav .navlink::after { display: none; }
  .nav .btn {
    margin-top: 14px;
    text-align: center;
    padding: 14px 22px;
    font-size: 15px;
  }

  /* Hero */
  .hero .container {
    grid-template-columns: 1fr;
    gap: 40px;
    padding-top: 32px;
    padding-bottom: 64px;
  }
  .md{display: flex !important;}
  .dk{display: none !important;}
  /* Stacked, the photo leads and the copy follows underneath it. */
  .hero-aside {
    order: 1;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: stretch;
    gap: 16px;
  }
  .hero-copy { order: 2; }
  .hero-photo { flex: 1 1 58%; aspect-ratio: 4 / 4.4; }
  .hero-stats { flex: 1 1 34%; }
  /* Photo and stats sit side by side; the voice pill takes the row below. */
  .voice-wrap { flex: 1 1 100%; order: 3; }
  .hero-stat {
    flex: 1 1 42%;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  /* Content grids */
  .about-grid,
  .moment-grid,
  .moment-grid--flip,
  .contact-grid { grid-template-columns: 1fr; gap: 36px; }

  /* Stacked, both stories read picture-then-text like the first one */
  .moment-grid--flip .moment-panel,
  .moment-grid--flip .moment-photo,
  .moment-grid--flip .moment-body { order: 0; }

  .moment-photo img { min-height: 300px; }
  .moment-panel { min-height: 200px; }

  /* Experience — the stage goes full width under a tighter rail. Full width
     means a card sliding in from +44px runs past the viewport edge, so the
     stage clips it here; above 900px the card is 720px in a wider track and
     has room to travel in the open. The padding/margin pair leaves room for
     the card shadow without changing the gap down to the arrows. */
  .exp-stage {
    overflow: hidden;
    padding-bottom: 26px;
    margin-bottom: -26px;
  }
  .exp-nodes { gap: 12px; }
  .exp-dot { width: 50px; height: 50px; }
  .exp-rail { top: 25px; }
  .exp-co { font-size: 13.5px; }
  .exp-stage { max-width: none; margin-top: 26px; }
  /* The row is the only layout that autoplays, and only down to 641px — see
     the phone block, where both the button and the timer stand down. */
  .exp-play { display: inline-flex; align-items: center; justify-content: center; }

  .card-grid    { grid-template-columns: repeat(2, 1fr); }
  .card-grid--tools .skill-card { flex-basis: calc(50% - 8px); }
  .cert { gap: var(--sp-5); }
  .cert-shot { flex-basis: 160px; }
  .cert-title { font-size: 21px; }
  .cert-solo .cert-shot { flex-basis: 46%; }
  .cert-solo .cert-title { font-size: 26px; }
  .hobby-grid   { grid-template-columns: repeat(2, 1fr); }
}

/* Tablet proper — 641–900px.
   Everything above 900 is a two-column desktop; everything below 641 is a
   phone. Without this band the phone stack simply inherited the tablet's
   extra 200–300px of width, which is the wrong thing to spend it on: text
   lines stretched to 100+ characters, the hero stats inflated into empty
   slabs, and the story photo grew taller than the window. The rules here
   spend the width on layout instead of on line length.

   Two ideas run through the whole block:
   - Running text is capped to a measure (--measure) rather than filling the
     column, so a line stays inside the 45–75 character band where the eye
     tracks reliably. Blocks stay flush left rather than centring, so the
     return sweep always lands on the same edge as the headings above them.
   - Nothing decorative gets to stretch. A card is as tall as what's in it
     and an image is capped so the copy underneath stays reachable. */
@media (min-width: 641px) and (max-width: 900px) {
  :root {
    /* Resolved against each block's own font-size, so it lands around 65
       characters whatever size the block is set in. */
    --measure: 34em;
  }

  /* ---- Hero ----
     The photo and the stats share a row. The photo is capped so the H1 —
     the one thing the page has to say first — is not pushed off screen,
     and the stats become a single column of two so each card is only as
     tall as its own number instead of stretching to the photo's height. */
  .hero .container { gap: 32px; padding-bottom: 56px; }
  .hero h1 { font-size: clamp(40px, 5.4vw, 48px); }
  .hero-aside { gap: 20px; }
  .hero-photo {
    flex: 1 1 58%;
    aspect-ratio: 4 / 4.1;
    max-height: 400px;
  }
  /* Near 900px the cap flattens the frame below its 4:5 source, and a centred
     crop takes the top of her head with it. Bias the window upward. */
  .hero-photo img { object-position: 50% 30%; }
  .hero-stats {
    flex: 1 1 36%;
    grid-template-columns: 1fr;
    align-content: center;
    gap: 14px;
  }
  .hero-stat { padding: 18px 16px; }
  .hero-stat .num { font-size: 30px; }
  .hero-lede { font-size: 17px; }

  /* ---- Running text ----
     The desktop sizes step down at 1080/1200 to keep whole sections on one
     screen. That constraint is off here (no scroll-snap below 901), so the
     body copy goes back up to a comfortable reading size and the measure
     does the containing instead. */
  .prose p,
  .prose .prose-quote { font-size: 17px; line-height: 1.75; max-width: var(--measure); }
  .exp-intro,
  .skills-intro,
  .exp-desc,
  .section-head p { font-size: 15.5px; max-width: var(--measure); }
  /* No cap on these two: they sit in the hard-moment text column, which is
     already narrower than the measure. */
  .moment-body p,
  .story-body p { font-size: 16px; line-height: 1.8; }

  /* Stacked rather than left-title / right-intro. In a row the two only stay
     apart while the intro happens to be too wide to fit beside the heading —
     a few characters shorter and it snaps up onto the title's line, which is
     a layout that shouldn't turn on the length of a sentence. Stacking also
     matches .exp-intro and .skills-intro, which already sit under their
     headings. */
  .section-head { display: block; }
  .section-head p { margin-top: var(--gap-title-intro); }
  /* The Skills and Strengths switches sit in a `.section-head` too, and a
     block layout gives them no step of their own — the flex gap that held
     them off the heading only exists while the head is a row. Same step the
     intro paragraph above gets, so a heading followed by a switch reads like
     a heading followed by a line of copy. */
  .skills-head .skill-tabs { margin-top: var(--gap-title-intro); }

  /* A blockquote carries a 40px UA margin on both sides, which pushed the
     testimonial off the prose's left edge once the grid stacked. Flush it. */
  .pullquote {
    margin: 0;
    max-width: var(--measure);
  }
  .pullquote p { font-size: 17px; }

  /* ---- About stats ----
     Four columns across ~700px gives each label ~150px, so every one of them
     wraps to two ragged lines. Two columns lets the labels sit on one line
     and pairs the numbers into a shape the eye can compare. */
  .stats {
    grid-template-columns: repeat(2, 1fr);
    gap: 14px;
  }

  /* ---- Hard moments ----
     Stacked, the 4:5 photo rendered ~840x1050 — taller than the window, so
     you landed on a picture with the story's title and first line below the
     fold. Cropping it to a banner isn't an option: the faces sit near the top
     of the frame and the dog near the bottom, so any wide crop loses one or
     the other. Narrowing the column does the same job without touching the
     composition, and hands the recovered width to the story instead.

     Top-aligned rather than centred, because once the story is expanded the
     text column runs several times the photo's height and a centred photo
     ends up marooned halfway down it. Sticky keeps it alongside the reading,
     the same way it behaves above 901px. */
  .moment-grid { grid-template-columns: 0.82fr 1.18fr; }
  .moment-grid--flip { grid-template-columns: 1.18fr 0.82fr; }
  .moment-grid,
  .moment-grid--flip { gap: 28px; align-items: start; }

  /* The stacked layout dropped the alternation; side by side it's back on,
     so the second story mirrors the first instead of repeating it. */
  .moment-grid--flip .moment-panel,
  .moment-grid--flip .moment-photo { order: 2; }
  .moment-grid--flip .moment-body { order: 1; }

  .story-open .moment-photo {
    position: sticky;
    top: calc(var(--header-h) + 20px);
  }

  .moment-photo img { min-height: 0; }
  .moment-panel { min-height: 260px; padding: 28px 20px; }
  .moment-title { font-size: 27px; }
  .story { --story-peek: 190px; }

  /* ---- Contact ----
     Back to two columns. Stacked, the details sat in a ~250px column with
     half the section empty beside them, while the form stretched to the full
     width — a 750px-wide "Your name" field misreads how much you are being
     asked for, and the submit button became a slab. Side by side, the field
     widths match the answers again. */
  .contact-grid {
    grid-template-columns: 0.9fr 1.1fr;
    gap: 32px;
  }
  .contact-intro h2 { font-size: 34px; }
  .contact-intro > p { font-size: 15.5px; }
  .contact-form { padding: 24px; gap: 16px; }
  .contact-form input,
  .contact-form textarea { font-size: 15px; padding: 12px 14px; }
  .contact-form textarea { min-height: 120px; }

  /* ---- Skills ----
     12px descriptions are a desktop-density compromise; at arm's length on a
     tablet they read as noise. One step back up. */
  .skill-card { padding: 14px 15px; }
  .skill-card p { font-size: 13px; line-height: 1.5; }
  .hobby-card p { font-size: 14px; }
}

/* Short desktop windows (13" laptops, a browser with lots of toolbars).
   Same one-screen goal, less air and slightly smaller display type. */
@media (min-width: 901px) and (max-height: 800px) {
  :root {
    --section-y: 36px;
    /* Tightest of any breakpoint — this is the one that has to clear a
       short window with the whole section on screen. */
    --gap-title-content: var(--sp-5);
    --gap-block: var(--sp-6);
    /* The seam steps down with everything else. Below 800px the sections that
       can't shrink any further — Skills at four groups, and the hero — need
       nearly the whole window, so 14vh of it is height the page hasn't got.
       Graded, not clamped, because it has to move with the rest of the
       tightening here rather than against it — and the same valve applies,
       against the 570px the hero bottoms out at once the cuts here have run. */
    --seam: min(7vh, calc(100svh - var(--header-h) - 570px));
  }
  .hero-photo { aspect-ratio: 4 / 4.3; }
  .hero h1 { font-size: 46px; }
  .hero-lede { font-size: 17px; margin-top: 20px; }
  .hero-actions { margin-top: 26px; }
  :root { --title-size: 32px; }
  .contact-intro h2 { font-size: 36px; }
  .moment-photo img { min-height: 320px; }
  .moment-title { font-size: 27px; }
  .prose p { font-size: 15.5px; line-height: 1.7; margin-bottom: 14px; }
  .pullquote p { 
   font-size: 15.5px;
  }
  .exp-intro { font-size: 15px; }
  .exp-dot { width: 50px; height: 50px; }
  .exp-rail { top: 25px; }
  .exp-node-text { margin-top: 11px; }
  .exp-co { font-size: 14px; }
  .exp-stage { margin-top: 22px; }
  .exp-card { padding: 22px 26px; }
  .exp-meta { margin-bottom: 12px; }
  .exp-name { font-size: 22px; }
  .exp-desc { font-size: 15px; margin-top: 10px; }
  .exp-controls { margin-top: 16px; }
  .skills-intro { font-size: 14.5px; }
  .skill-tabs { margin-bottom: var(--sp-4); }
  .skill-group { margin-bottom: var(--sp-5); }
  .skill-group-head { margin-bottom: var(--sp-3); }
  .skill-group-name { font-size: 16px; }
  .skill-card { padding: 14px 16px; }
  /* Base is 16.5px; a short window can't afford it across three groups. */
  .skill-card h4 { font-size: 15.5px; }
  .skill-card p { font-size: 12.5px; line-height: 1.5; }
  .skill-card .icon { width: 36px; height: 36px; }

  /* The skills stay — one row of pills is what the description used to cost
     in three lines, and they're the part worth reading. */
  .cert { gap: var(--sp-5); padding: var(--sp-3) var(--sp-4); }
  .cert-shot { flex-basis: 140px; }
  .cert-title { font-size: 19px; }
  .cert-skills { margin: var(--sp-4) 0; gap: 6px; }
  .cert-meta { margin-top: var(--sp-2); }

  /* Hobbies has two blocks in one band on a short window. The hobby row gives
     up its description line height and the CSAT box its padding — the stars
     and the question are the part that has to stay readable. */
  .hobby-card { padding: 14px 16px; }
  .hobby-card p { line-height: 1.45; }
  .csat { margin-top: var(--sp-4); }
  .csat-form,
  .csat-done { padding: 15px 18px; }
  .csat-form { gap: var(--sp-3); }
  .csat-more { padding-top: var(--sp-3); }
  .csat-more textarea { min-height: 38px; }
}

/* Laptop-height desktop — the band between the short-window rules above and a
   window tall enough for the full rhythm. Skills is the one section with three
   stacked bands to fit, so it is the only thing that steps down here: a single
   moderate notch, rather than the crush the short-window block applies. */
@media (min-width: 901px) and (min-height: 801px) and (max-height: 950px) {
  .skill-tabs { margin-bottom: var(--sp-5); }
  .skill-group { margin-bottom: var(--sp-6); }
  .skill-group-head { margin-bottom: var(--sp-3); }
  .skill-card { padding: 16px 18px; }
  .skill-card p { line-height: 1.5; }
  .skill-card .icon { width: 38px; height: 38px; }
  .cert-shot { flex-basis: 172px; }
  .cert-title { font-size: 22px; }
  .cert { padding: var(--sp-3) var(--sp-5); }
}

/* ---------- Skills and Strengths on one screen ----------
   Two labelled groups per section instead of four plus a certificate, which
   is the whole point of the split — the cards keep the size they always had
   and the section stops being a wall. Sits after the height blocks above
   because it wins against them on the properties they share. */

@media (min-width: 901px) {
  /* The heading row owns the step down to the grid, so the switch inside it
     drops the margin it carried when it was a band of its own. */
  .skills-head { align-items: center; }
  .skills-head .skill-tabs { margin-bottom: 0; }

  .skill-grid {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
    /* Takes the leftover height like every other section's one named block.
       No `min-height: 0` here or on the groups: content size is the floor, so
       a window too short to hold them squeezes nothing and the height block
       below does the fitting instead. */
    flex: 1 1 auto;
  }
  /* The gap replaces these; a margin on the last group would push the grid
     past the band it was just fitted into. */
  .skill-group { display: flex; flex-direction: column; margin-bottom: 0; }
  .skill-group-head { margin-bottom: var(--sp-3); }

  /* Card size is a constant — both sections, both states.

     Letting the groups take the leftover height is what made Strengths wrong.
     One row of cards per group meant the whole spare band went into two rows,
     stretching them into slabs under All that snapped back to normal the
     moment you picked a single tab: the same four cards at two different
     sizes depending on which tab you were standing on. Skills never showed it
     — a certificate band and seven tool cards over two rows already fill the
     band, so there was nothing spare to hand out — which is exactly why Skills
     is the one that looks right and the one to match.

     So nothing stretches. Both sections' cards now sit at the size the cards
     have everywhere else on the page, and the spare height goes to the space
     either side of the grid instead — the one place it can go that doesn't
     read as content. */
  .skill-grid { justify-content: center; }
  .skill-group,
  .skill-group .card-grid,
  .skill-group .cert { flex: 0 1 auto; }

  /* With a section to itself the certificate is the point of the screen, so
     the scan gets the width. Three columns in a row left it at about 360px
     — the button was taking a column of its own beside a body that could not
     give any up. As a two-column grid the button stacks under the details it
     belongs to, and the scan takes half the card: a third again as wide, and
     legible rather than merely recognisable. */
  .cert-solo .cert {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: var(--sp-7);
    align-items: center;
    padding: var(--sp-6);
  }
  .cert-solo .cert-shot { grid-area: 1 / 1 / span 2 / auto; align-self: center; }
  .cert-solo .cert-body { grid-area: 1 / 2; align-self: end; }
  .cert-solo .cert-link {
    grid-area: 2 / 2;
    align-self: start;
    justify-self: start;
    margin-top: var(--sp-5);
  }
  .cert-solo .cert-title { font-size: 28px; }
}

/* Short desktop windows. One tier now instead of three — two groups on screen
   leave far less to take back. */
@media (min-width: 901px) and (max-height: 800px) {
  .skill-group-head { margin-bottom: var(--sp-2); }
  .skill-card { padding: 16px 18px; }
  .skill-card p { line-height: 1.5; }
  .skill-card .icon { width: 38px; height: 38px; }
}

/* Large phone */
@media (max-width: 640px) {
  :root {
    --gutter: 20px;
    --section-y: 52px;
    /* One step down the scale each. --gap-label-title and --gap-title-intro
       hold at 12/16 — they're already tight enough that shrinking them would
       start to break the heading group apart rather than compact it. */
    --gap-title-content: var(--sp-6);
    --gap-block: var(--sp-7);
  }

  .brand { font-size: 19px; }

  .hero h1 { font-size: clamp(32px, 8.6vw, 42px); line-height: 1.14; }
  .hero-kicker span { font-size: 11px; letter-spacing: 0.09em; }
  .hero-lede { font-size: 16.5px; margin-top: 20px; }
  .hero-actions { margin-top: 26px; gap: 10px; }
  .hero-actions .btn {
    width: 100%;
    text-align: center;
    padding: 15px 20px;
    white-space: normal;
  }

  /* Single column again, so the pill goes straight back under the photo */
  .voice-wrap { order: 0; }
  .voice { width: 100%; padding-right: 16px; }
  .voice-wave { margin-left: auto; }
  .voice-time { margin-left: 4px; }
  .hero-aside { flex-direction: column; }
  .hero-photo { aspect-ratio: 4 / 4.6; }

  .section-title,
  .section-head .section-title { font-size: 28px; max-width: none; }
  .contact-intro h2 { font-size: 30px; }

  /* Every heading gap here comes from the tokens redefined above, so there
     are no per-section margins left to keep in sync. The head block stacks
     instead of sitting in a row, which is the only real change. */
  .section-head { display: block; }
  .section-head p { margin-top: var(--gap-title-intro); max-width: none; }
  /* As in the tablet block above — a stacked `.section-head` gives the switch
     no step of its own, and both breakpoints carry their own copy of the
     stacking rule. */
  .skills-head .skill-tabs { margin-top: var(--gap-title-intro); }

  .prose p { font-size: 16px; line-height: 1.75; }
  .pullquote { padding-left: 20px;  margin-left: auto; }
  .pullquote p { 
    font-size: 16px; 
  }

  .stats { grid-template-columns: repeat(2, 1fr); gap: 14px; }

  .moment-grid { gap: 28px; }
  .moment-photo img { min-height: 230px; }
  .story { --story-peek: 155px; }
  .story-body p { font-size: 15px; line-height: 1.8; }
  .moment-panel { min-height: 170px; padding: 26px 20px; }
  .moment-title { font-size: 25px; }
  .moment-body p { font-size: 15.5px; }

  /* Experience — the rail scrolls sideways rather than crushing four stops
     into the width. Fixed-width stops keep the line ends on the outer two. */
  .exp-nodes {
    display: flex;
    gap: 0;
    overflow-x: auto;
    padding-bottom: 6px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .exp-nodes::-webkit-scrollbar { display: none; }
  .exp-node { flex: 0 0 128px; }
  .exp-rail { left: 64px; right: 64px; top: 24px; }
  .exp-dot { width: 48px; height: 48px; }
  .exp-co { font-size: 13px; }
  .exp-card { padding: 22px; }
  .exp-name { font-size: 21px; }
  .exp-desc { font-size: 15px; }

  /* No pause button on a phone, because nothing autoplays on a phone — the
     script gates the timer on the same 641px. Four controls under a card is
     one more than the width can carry, and the two it can are the arrows.
     Hiding the button on its own would have left the roles turning over with
     no reachable way to stop them, so the timer goes with it. */
  .exp-play { display: none; }

  /* Touch targets â€” keep the interactive bits thumb-sized */
  .brand { padding: 8px 0; }
  .skill-tab { padding: 12px 18px; }
  .contact-list { gap: 2px; }
  .contact-list li { min-height: 40px; }
  .contact-list a { display: block; padding: 11px 0; }

  /* Tabs scroll sideways instead of stacking into a tall block */
  .skills-intro { font-size: 15px; max-width: none; }
  .skill-group { margin-bottom: 18px; }
  .skill-group-name { font-size: 16px; }
  .skill-tabs { margin-bottom: 22px; }
  .skill-tablist {
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-bottom: 4px;
    max-width: 100%;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .skill-tablist::-webkit-scrollbar { display: none; }
  .skill-tab { flex: 0 0 auto; }

  .card-grid { grid-template-columns: 1fr; }
  .card-grid--tools .skill-card { flex-basis: 100%; }

  /* Stacked, with the scan on top and the button full width — sideways at
     this size left the thumbnail too small to read as a certificate. */
  .cert {
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-4);
    padding: var(--sp-4);
  }
  .cert-shot,
  .cert-solo .cert-shot { flex-basis: auto; }
  .cert-title,
  .cert-solo .cert-title { font-size: 21px; }
  .cert-solo .cert { gap: var(--sp-4); padding: var(--sp-4); }
  .cert-link { text-align: center; }

  /* Hobbies — two by two, on screen, no swipe. This was a snapping side-rail
     of 78%-wide cards, which is a reasonable shape for a row of photographs
     and the wrong one for four boxes carrying a mark, a name and one line:
     three of the four were off-screen, and getting to them cost a gesture
     nothing else in the section asks for. Two columns fit all four at once
     down to 320px, and the whole section scrolls the one direction the page
     already scrolls. Densities step down to suit the narrower column. */
  .hobby-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
    margin: 0;
    padding: 0;
    overflow: visible;
  }
  .hobby-card { padding: 15px 14px; }
  .hobby-card .icon {
    width: 36px;
    height: 36px;
    border-radius: 11px;
    margin-bottom: var(--sp-2);
  }
  .hobby-card .icon svg { width: 18px; height: 18px; }
  .hobby-card h3 { font-size: 14.5px; margin-bottom: 4px; }
  .hobby-card p { font-size: 11.5px; line-height: 1.5; }

  /* CSAT on a phone. The question stacks over the stars, and the row of five
     grows to fill the width — a star at 25px is a comfortable click and a
     miserable tap, so the glyph goes up and the padding with it. The send
     button takes the full width under the field rather than sitting beside a
     box squeezed down to nothing. */
  .csat { margin-top: var(--sp-4); }
  .csat-form,
  .csat-done { padding: 18px; }
  .csat-row { gap: var(--sp-4); }
  .csat-head,
  .csat-pick { width: 100%; }
  .csat-stars { justify-content: space-between; width: 100%; max-width: 260px; }
  .csat-star { padding: 6px; }
  .csat-star svg { width: 30px; height: 30px; }
  .csat-scale { max-width: 260px; }
  .csat-send-row { flex-direction: column; align-items: stretch; }
  .csat-send { width: 100%; }
  .csat-done { align-items: flex-start; }

  .contact-form { padding: 22px; }
  .form-done { padding: 36px 22px; }

  .site-footer {
    flex-direction: column;
    gap: 6px;
    font-size: 11px;
    letter-spacing: 0.04em;
  }

  .site-footer__credit { margin-top: 4px; font-size: 10px; }

  /* Chat: the panel spans the screen instead of floating in a corner of it. */
  .js .chat { right: 16px; bottom: 16px; left: 16px; }
  .chat-launcher { width: 60px; height: 60px; margin-left: auto; }
  .chat-nudge { right: 0; bottom: 74px; width: auto; max-width: 280px; margin-left: auto; }
  .chat-nudge::after { right: 18px; }

  .chat-panel {
    left: 0;
    right: 0;
    bottom: 74px;
    width: auto;
    max-width: none;
    max-height: min(560px, calc(100svh - 116px));
  }
  .chat-log { padding: 14px 12px 6px; }
  .chat-row { max-width: 88%; }
  .chat-foot { padding: 10px 12px 12px; }
}

/* Small phone */
@media (max-width: 380px) {
  :root { --gutter: 16px; }
  .hero-stat .num { font-size: 34px; }
  .contact-list a,
  .contact-list span { font-size: 12.5px; }

  .chat-voice { min-width: 0; }
  .chat-chips { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .chat-chips::-webkit-scrollbar { display: none; }
  .chat-chip { flex: 0 0 auto; }
}

/* ---------- Experience — vertical rail (desktop only) ----------
   Above 900px the timeline turns on its side: the stops become a vertical
   rail down the left column and the detail card sits beside them on the
   right. Placed after the responsive blocks on purpose — it has to land after
   the width/height tweaks that size the horizontal rail, so those don't
   reintroduce a `top` offset on a line that now runs top-to-bottom.
   Below 901px nothing here applies and the row layout stands. */

@media (min-width: 901px) {
  .exp {
    /* One knob for the circle: the rail's position and its two end points are
       all derived from it, so a bigger dot keeps the line centred. */
    --dot: 56px;
    display: grid;
    grid-template-columns: minmax(210px, 260px) minmax(0, 1fr);
    grid-template-rows: auto auto;
    column-gap: clamp(36px, 5vw, 72px);
    align-items: start;
  }

  .exp .exp-rail-wrap {
    grid-column: 1;
    grid-row: 1 / span 2;
    align-self: center;
  }

  /* Ends land on the first and last circle centres, which in a column of
     dot-height rows is half a dot in from each edge. */
  .exp .exp-rail {
    left: calc(var(--dot) / 2 - 1px);
    right: auto;
    top: calc(var(--dot) / 2);
    bottom: calc(var(--dot) / 2);
    width: 2px;
    height: auto;
    background: linear-gradient(180deg, var(--teal), var(--teal-deep), var(--terra));
    transform: scaleY(0);
    transform-origin: top center;
  }
  .exp.in .exp-rail { transform: scaleY(1); }
  html:not(.js) .exp .exp-rail { transform: none; }

  .exp .exp-nodes {
    display: flex;
    flex-direction: column;
    gap: clamp(30px, 4vh, 54px);
  }

  .exp .exp-node {
    flex-direction: row;
    align-items: center;
    text-align: left;
    gap: 16px;
    width: 100%;
    padding: 4px;
  }

  .exp .exp-dot { width: var(--dot); height: var(--dot); flex-shrink: 0; }
  /* The stop grows toward the card instead of around its own centre, so the
     line of circles stays plumb. */
  .exp .exp-node:hover .exp-dot { transform: scale(1.06); }
  .exp .exp-node-text { margin-top: 0; }
  .exp .exp-co { font-size: 15px; white-space: normal; }

  /* ---- Card column ---- */

  .exp .exp-stage {
    grid-column: 2;
    grid-row: 1;
    max-width: none;
    margin: 0;
    /* The card now fills its column, so a neighbour sliding in from +44px
       would push the page sideways. Clip it, and pay back the space the
       shadow needs with a padding/margin pair. */
    overflow: hidden;
    padding: 0 22px 30px;
    margin: 0 -22px -30px;
  }

  .exp .exp-controls {
    grid-column: 2;
    grid-row: 2;
    justify-content: flex-start;
    margin-top: var(--sp-6);
  }

  /* Without scripting the cards stack, so the column has to grow with them
     rather than sit centred against a single card. */
  html:not(.js) .exp .exp-rail-wrap { align-self: start; }
}

/* Tall windows get the larger circle the horizontal rail already used. */
@media (min-width: 1200px) and (min-height: 950px) {
  .exp { --dot: 64px; }
}

/* Short desktop windows: tighten the column so all four stops and the card
   still clear one screen. */
@media (min-width: 901px) and (max-height: 800px) {
  .exp { --dot: 50px; }
  .exp .exp-nodes { gap: 22px; }
}

/* ---------- Experience — the rail as a wheel (desktop, scripted) ----------
   `.exp-wheel` is added by the script once the rail is running vertically. The
   column becomes a short window with one stop centred in it: its neighbours
   sit half in view and faded, and the page's own scroll spins the strip so the
   next stop rolls into the middle while its card changes beside it. The fade
   is a mask rather than a gradient overlay so the cream behind it can be any
   colour. Nothing here applies without scripting — the stops would be parked
   off-centre with no way to move them. */

@media (min-width: 901px) {
  .exp.exp-wheel .exp-rail-wrap {
    /* Three and a half stops or so. Tighter than this and the neighbours are
       half-cropped as well as faded, which is what made the column read as one
       stop floating in space rather than a list you're moving through. */
    height: clamp(260px, 40vh, 420px);
    overflow: hidden;
    /* The fade only has to say "this continues past the frame". Held in from
       the edges it did that by rubbing out the neighbours entirely — they
       carry the sense of a list, so they have to stay readable. */
    /* #000 here is a mask alpha channel, not a palette colour - it means
       'fully opaque'. Deliberately not tokenised. */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 16%, #000 84%, transparent 100%);
            mask-image: linear-gradient(to bottom, transparent 0%, #000 16%, #000 84%, transparent 100%);
  }

  /* The line is a fixed track the stops travel along, so it spans the window
     and the mask frays both ends — it reads as a rail continuing past view. */
  .exp.exp-wheel .exp-rail { top: 0; bottom: 0; }

  /* The script moves this every frame the page scrolls, so the transition is
     only here to take the edge off a chunky wheel tick — anything longer and
     the strip visibly trails the scroll. */
  .exp.exp-wheel .exp-nodes {
    transition: transform 0.16s ease-out;
    will-change: transform;
  }

  /* The centred stop leads, but the others are still meant to be read — they
     are the rest of the history, not decoration.

     Faded part by part, deliberately. Opacity on the whole stop makes the
     circle's white fill translucent too, and the rail runs straight through the
     middle of it — that opaque fill is the only thing keeping the line reading
     as running *between* stops rather than across them. So the text and the
     icon fade, the border eases off towards the paper, and the tile itself
     stays solid. All of it multiplies with the mask above, so these sit higher
     than they look like they should. */
  .exp.exp-wheel .exp-node-text {
    opacity: 0.62;
    transition: opacity 0.45s ease;
  }
  .exp.exp-wheel .exp-dot > svg,
  .exp.exp-wheel .exp-dot > img {
    opacity: 0.55;
    transition: opacity 0.45s ease;
  }
  .exp.exp-wheel .exp-dot {
    border-color: var(--card-line);
    border-color: color-mix(in srgb, var(--bd) 42%, var(--white));
    box-shadow: none;
    transition: transform 0.3s ease, background 0.3s ease, color 0.3s ease,
                border-color 0.45s ease, box-shadow 0.45s ease, opacity 0.5s ease;
  }

  .exp.exp-wheel .exp-node[aria-selected="true"] .exp-node-text,
  .exp.exp-wheel .exp-node[aria-selected="true"] .exp-dot > svg,
  .exp.exp-wheel .exp-node[aria-selected="true"] .exp-dot > img { opacity: 1; }
  .exp.exp-wheel .exp-node[aria-selected="true"] .exp-dot {
    border-color: var(--bd);
    box-shadow: 0 8px 22px -14px var(--acc);
  }

  /* The stops arrive together here. The staggered reveal is built for a row
     that is standing still; on a strip that is already turning, stops fading
     in one at a time reads as a stutter. */
  .exp.exp-wheel.in .exp-node .exp-dot,
  .exp.exp-wheel.in .exp-node .exp-node-text { transition-delay: 0.06s; }
}

/* ---------- Experience — the section holds while the wheel turns ----------
   Everywhere else on desktop a section is exactly one screen and the page
   snaps between them. This one is the exception: it runs longer than a screen
   and pins its own content under the header for the extra length, so the
   scroll that would have carried you into Skills turns the rail instead. The
   next section only arrives once the last stop has been through. Below 901px
   none of this applies — the row layout scrolls like any other block. */

@media (min-width: 901px) {
  .js #experience {
    /* Scroll spent stepping through the stops, on top of the one screen the
       section actually shows. Three transitions at a little over half a
       screen each: long enough that a stop reads as a stop, short enough that
       nobody feels held. Four stops is what this is sized for — add one and
       this wants another ~55vh. */
    --exp-track: 165vh;
    /* The `main > section` rule makes every section a centring grid. Here the
       section has to be a plain block so the container below can be a sticky
       child with the whole extra length to travel; the centring moves with it. */
    display: block;
    /* `main > section` also sets `align-content: safe center`, which Chrome now
       honours on block containers too — left in place it centres this whole
       container inside the tall section, so the track opens with a screen of
       nothing and the pin only engages halfway down. */
    align-content: normal;
    min-height: calc(100vh - var(--header-h) + var(--exp-track));
    min-height: calc(100svh - var(--header-h) + var(--exp-track));
  }

  .js #experience > .container {
    position: sticky;
    top: var(--header-h);
  }

  /* The rail and the card take the leftover height, the same way every other
     section hands it to one block. Centring the group instead — which is what
     this did before — left the ink floating in the middle of a screen-tall
     section, so the seam either side of Experience came out at nearly twice
     every other seam on the page.

     The height goes to the card, not to the rail: the rail is a *window* onto
     a taller strip of stops, and a window taller than its strip would leave
     the script translating four stops around inside empty space. So the
     window keeps its clamp and stays centred in its column, and the card
     beside it stretches. */
  #experience .exp {
    flex: 1 1 auto;
    min-height: 0;
    grid-template-rows: minmax(0, 1fr) auto;
  }
  /* Stretching the card itself was the first thing tried here and it read as
     a hollow tile — four lines of text adrift in five hundred pixels of white.
     The grid's second row does the same job honestly: the counter goes to the
     bottom edge, the heading is already at the top, and the rail and the card
     sit centred between them at the size they want to be. The ink spans the
     band, which is all the seam asks for. */
  #experience .exp-stage { align-self: center; }
  #experience .exp-controls { align-self: end; }
  /* The window is the one thing here that can take height without looking
     stretched — it is a view onto a longer strip, so a taller one simply
     shows more of the line. Left at its clamp it sat as a short column in the
     middle of a tall row, and the section read as a heading, a hole, and then
     some content. */
  #experience .exp .exp-rail-wrap { height: auto; align-self: stretch; }

  /* Snapping and a pinned track don't mix, in two ways. A section taller than
     the viewport that is still a snap target gets *clamped* inside it — the
     snapport has to stay within the snap area — so trying to leave at the
     bottom pulls you back to the last pinned position and the section reads as
     stuck. And `proximity` measures from where a gesture ends, so a long flick
     partway down can find the next section's snap point and take you there
     with stops still unread. So this section is not a snap target at all, and
     the script drops snapping site-wide for as long as the track is engaged
     (see `.exp-hold`). Skills keeps its own snap point, which is what catches
     you on the way out. */
  .js #experience { scroll-snap-align: none; }

  /* Flex doesn't collapse margins. Left alone, the H2's bottom margin and the
     rail's own top margin would stack into two steps where the rest of the
     page reads one — the heading-to-content gap is `--gap-title-content`, the
     same single step it is everywhere else. */
  .js #experience .exp { margin-top: 0; }

  /* The scroll is the control now — arrows beside it would be a second one
     pulling the same lever, and clicking one would have to scroll the page to
     agree with itself. The counter stays: it's the read-out that tells you how
     much of the section is left to go. */
  .js .exp .exp-arrow { display: none; }
  .js .exp .exp-controls { gap: 0; }
}

html.exp-hold { scroll-snap-type: none; }

/* ---------- Preferences ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; scroll-snap-type: none; }
  .js [data-reveal] { opacity: 1; transform: none; transition: none; }
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  /* The bars would otherwise freeze mid-keyframe as five tiny dots. */
  .voice-wave i { transform: scaleY(0.6); opacity: 0.6; }

  /* The experience slider still swaps cards — it just cuts rather than slides,
     and the contents arrive with the card instead of trailing behind it. */
  /* The timeline still highlights on hover and click — it just arrives whole
     instead of drawing itself in. */
  /* The timeline still switches stops — it just cuts, and the ring stops
     turning rather than freezing mid-rotation. */
  /* Autoplay is already off (the script checks the same query). The stops and
     the card still switch — they just cut instead of sliding. */
  .exp-play { display: none !important; }
  .js .exp:not(.in) .exp-dot,
  .js .exp:not(.in) .exp-node-text { opacity: 1; transform: none; }
  .exp-rail { transform: none !important; }
  .exp-node:hover .exp-dot { transform: none; }
  .js .exp-card + .exp-card { transform: none; }

  /* The chat still opens and messages still arrive — they just appear rather
     than slide in, and the launcher stops pinging. script.js also drops the
     typing pauses so the thread is complete the moment the panel opens. */
  .chat-launcher > .chat-dot::after { animation: none; opacity: 0; }
  .chat-typing i { opacity: 0.6; transform: none; }
}

@media print {
  .site-header, .hero-actions, .contact-form, .nav-toggle, .chat { display: none !important; }
  .js [data-reveal] { opacity: 1; transform: none; }
  body { background: var(--white); }
  /* Screen-height sections would print as mostly blank pages. */
  main > section { min-height: 0; display: block; }
  /* Print the full story whether or not it was open on screen. */
  .story-body,
  .js .story-body.is-collapsed { height: auto !important; max-height: none !important; }
  .story-body::after { display: none; }
  .story-toggle { display: none; }

  /* A slider prints as one visible card and three blank ones — so on paper the
     roles go back to being a plain list. */
  /* One visible panel and three blank ones is no use on paper — print every
     role instead. */
  /* One visible card and three hidden ones is no use on paper — print every
     role instead, and drop the controls. */
  .exp-rail, .exp-controls { display: none !important; }
  .exp-stage { max-width: none; }
  .exp-card {
    position: static !important;
    opacity: 1 !important;
    transform: none !important;
    box-shadow: none;
    page-break-inside: avoid;
  }
  .exp-card + .exp-card { margin-top: 14px; }
  .exp-dot, .exp-node-text { opacity: 1 !important; transform: none !important; }

  /* The desktop track and its pin, undone: a 2.6-screen section that holds
     its content still is a screen idea, and the wheel crops the rail to one
     stop — on paper all four have to be there. */
  .js #experience { min-height: 0; }
  .js #experience > .container { position: static; min-height: 0; display: block; }
  .exp { display: block; }
  .exp-rail-wrap {
    height: auto;
    overflow: visible;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .exp-nodes { transform: none !important; }
  .exp-node { opacity: 1 !important; }
}
