/* ==========================================================================
   Tokens
   ========================================================================== */

:root {
  --bg: #0A0A0B;
  --bg-elevated: #121214;
  --bg-card: #141416;
  --border: rgba(255, 255, 255, 0.07);
  --border-strong: rgba(255, 255, 255, 0.13);
  --text: #EDEDEC;
  --text-secondary: #A0A0A0;
  /* Spec value #6E6E73 measures 3.90:1 against --bg and fails WCAG AA for the
     small text it carries (11px eyebrows, card domains, captions). Raised to
     the nearest value that clears 4.5:1 against the *lightest* surface it sits
     on, which is --bg-card, not --bg: 4.82:1 on --bg-card, 5.19:1 on --bg. */
  --text-muted: #82828A;
  --accent: #C9A96E;
  /* Two companions to the bronze, tuned to sit at the same optical weight
     on the ground: 8.84, 8.21 and 8.09 against --bg. Assigned by meaning,
     one per engagement, not sprinkled for variety. */
  --accent-slate: #8FAAC4;
  --accent-jade: #7FB09B;
  --accent-dim: rgba(201, 169, 110, 0.13);

  --font-display: "Bricolage Grotesque", "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;

  --container: 1120px;
  --measure: 680px;
  --section-gap: 72px;
  --radius: 16px;
  --radius-sm: 10px;
  --radius-pill: 999px;
  --nav-h: 60px;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

@media (min-width: 900px) {
  :root { --section-gap: 120px; }
}

/* ==========================================================================
   Fonts — self hosted, latin subset
   ========================================================================== */

/* Variable file — one download covers the whole 200 to 800 weight range. */
@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("fonts/bricolage-grotesque-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Variable file — one download covers both 400 and 500. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("fonts/inter-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ==========================================================================
   Base
   ========================================================================== */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 400;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* Grain overlay. feTurbulence as an inline data URI — this is what stops the
   dark ground from banding. Fixed, non-interactive, 2.5% opacity. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: 0.025;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23g)'/%3E%3C/svg%3E");
  background-repeat: repeat;
}

h1, h2, h3, p, dl, dd, figure, blockquote, ul { margin: 0; }

ul {
  padding: 0;
  list-style: none;
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

/* ==========================================================================
   Typography
   ========================================================================== */

h1 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(3rem, 7.5vw, 5.5rem);
  line-height: 1.0;
  letter-spacing: -0.035em;
  color: var(--text);
  text-wrap: balance;
}

h2 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.875rem, 3.4vw, 2.75rem);
  line-height: 1.12;
  letter-spacing: -0.028em;
  color: var(--text);
  text-wrap: balance;
}

h3 {
  font-family: var(--font-body);
  font-size: 1.125rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text);
}

p {
  color: var(--text-secondary);
  text-wrap: pretty;
}

.lede {
  font-size: clamp(1.25rem, 2vw, 1.5rem);
  line-height: 1.45;
  letter-spacing: -0.011em;
  color: var(--text);
}

.eyebrow {
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-muted);
}

/* A section label that carries the h2 for document outline purposes but
   reads as an eyebrow. */
.h2-eyebrow {
  font-family: var(--font-body);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-muted);
}

.measure { max-width: var(--measure); }

/* ==========================================================================
   Links, buttons, focus
   ========================================================================== */

a {
  color: var(--text);
  text-decoration: none;
  transition: color 180ms ease, border-color 180ms ease;
}

a:hover { color: var(--accent); }

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

.link-underline {
  border-bottom: 1px solid var(--border-strong);
  padding-bottom: 2px;
}

.link-underline:hover { border-color: var(--accent); }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0 1.5rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1;
  color: var(--text);
  transition: color 180ms ease, border-color 180ms ease, background-color 180ms ease;
}

.btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.btn--primary { background: var(--bg-elevated); }

.btn--secondary { border-color: var(--border); }

.skip-link {
  position: absolute;
  left: 1rem;
  top: -6rem;
  z-index: 10000;
  padding: 0.75rem 1rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 0.9375rem;
  font-weight: 500;
}

.skip-link:focus { top: 1rem; }

/* ==========================================================================
   Floating pill navigation
   Near-opaque rather than blurred: a backdrop-filter here would be
   glassmorphism, and it costs a compositing pass on every scroll frame.
   ========================================================================== */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 1rem;
  padding: 0.875rem 1.25rem;
  pointer-events: none;
}

.nav > * { pointer-events: auto; }

@media (min-width: 900px) {
  .nav {
    grid-template-columns: 1fr auto 1fr;
    padding: 1rem 2rem;
  }
}

.nav__mark {
  display: flex;
  align-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-pill);
}

.nav__mark svg { display: block; }

.nav__pill {
  display: none;
  justify-self: center;
  background: rgba(18, 18, 20, 0.94);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 0.3125rem;
}

@media (min-width: 900px) {
  .nav__pill { display: block; }
}

.nav__pill ul {
  display: flex;
  align-items: center;
  gap: 0.125rem;
}

.nav__pill a {
  display: block;
  padding: 0.5rem 0.9375rem;
  border-radius: var(--radius-pill);
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.2;
  color: var(--text-secondary);
  transition: color 180ms ease, background-color 180ms ease;
}

.nav__pill a:hover { color: var(--text); }

.nav__pill a[aria-current="true"] {
  color: var(--text);
  background: rgba(255, 255, 255, 0.06);
}

.nav__cta {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 1.125rem;
  background: rgba(18, 18, 20, 0.94);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1;
  color: var(--text);
  transition: color 180ms ease, border-color 180ms ease;
}

.nav__cta:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* ==========================================================================
   Layout
   ========================================================================== */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: 1.5rem;
}

@media (min-width: 900px) {
  .container { padding-inline: 2.5rem; }
}

.section {
  padding-block: var(--section-gap);
  border-top: 1px solid var(--border);
  scroll-margin-top: calc(var(--nav-h) + 1.5rem);
}

.section--flush { border-top: 0; }

.section__head { margin-bottom: 3.5rem; }

.section__head .eyebrow { margin-bottom: 1rem; }

.section__head h2 { max-width: 22ch; }

/* The rationed accent hairline — one per section, 40px, nothing heavier. */
/* Each case study carries its own hue: bronze for the oncology work,
   slate for the institutional B2B engagement, jade for the product role.
   Only the rule, the eyebrow and the disclosure take it, so the page still
   reads as one palette rather than three. */
.case-tint { --accent: var(--case-accent, #C9A96E); }

/* The eyebrow names the engagement, so it is the right place to carry the
   hue. .case__index and the disclosure already read from --accent, so
   rebinding the token above tints them with no further rules. */
.case .eyebrow { color: var(--accent); }
.case-tint--slate { --case-accent: var(--accent-slate); }
.case-tint--jade { --case-accent: var(--accent-jade); }

.rule-accent {
  width: 40px;
  height: 1px;
  border: 0;
  margin: 0 0 1.5rem;
  background: var(--accent);
}

/* ==========================================================================
   1. Hero — full height, cinematic
   ========================================================================== */

.hero {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 100svh;
  padding-block: calc(var(--nav-h) + 3rem) var(--section-gap);
  border-top: 0;
}

/* The only glow on the page. Soft-stopped gradient rather than a blur
   filter — same read, no per-frame paint cost. */
.hero::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  /* Clamped to 100%: at 150% this pushed document scrollWidth past the
     viewport on narrow screens, which nudged the fixed nav off-screen. */
  width: min(1400px, 100%);
  height: min(900px, 110%);
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: -1;
  background: radial-gradient(
    ellipse 50% 50% at 50% 50%,
    rgba(201, 169, 110, 0.06) 0%,
    rgba(201, 169, 110, 0.032) 34%,
    rgba(201, 169, 110, 0.01) 60%,
    rgba(201, 169, 110, 0) 78%
  );
}

.hero__lede {
  margin-top: 1.75rem;
  max-width: 26ch;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2.5rem;
}

/* ==========================================================================
   2. Proof — oversized numerals on an elevated full-bleed band
   ========================================================================== */

/* Full-bleed elevated band. Sits flush against the foot of its section so
   the next section's border-top supplies the closing hairline — otherwise
   the two rules stack into a 2px line. */
.band {
  background: var(--bg-elevated);
  border-top: 1px solid var(--border);
  padding-block: clamp(3rem, 6vw, 4.5rem);
  margin-top: 2.5rem;
}

.section--band { padding-bottom: 0; }

.proof {
  display: grid;
  grid-template-columns: 1fr;
  gap: 3rem;
}

.numeral {
  display: flex;
  align-items: baseline;
  gap: 0.0625em;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(3.5rem, 8vw, 5.5rem);
  line-height: 0.95;
  letter-spacing: -0.045em;
  color: var(--accent);
}

.numeral__unit {
  font-size: 0.42em;
  letter-spacing: -0.02em;
  color: var(--accent);
}

.proof__label {
  margin-top: 1.25rem;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text);
}

.proof__context {
  margin-top: 0.5rem;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--text-muted);
  max-width: 32ch;
}

@media (min-width: 640px) {
  .proof {
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
  }

  .proof__item { padding-inline: 2.5rem; }

  .proof__item:first-child { padding-inline-start: 0; }

  .proof__item:last-child { padding-inline-end: 0; }

  .proof__item + .proof__item { border-left: 1px solid var(--border); }
}

/* ==========================================================================
   3-5. Case studies
   ========================================================================== */

.case__index {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(2.5rem, 5vw, 3.5rem);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--accent);
  margin-bottom: 1.25rem;
}

.case__title {
  margin-top: 1rem;
  max-width: 24ch;
}

.case__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
  margin-top: 3.5rem;
}

@media (min-width: 900px) {
  .case__grid {
    grid-template-columns: 240px minmax(0, 1fr);
    gap: 4.5rem;
  }
}

.case__rail { font-size: 0.9375rem; }

.case__rail div { padding-block: 0.9375rem; }

.case__rail div + div { border-top: 1px solid var(--border); }

/* Pinned narrative.
   The rail holds still while the beats scroll past it and a marker tracks
   position. Nothing is behind a click: every word is in the document and
   visible to a skimmer and a crawler alike, which matters more than usual
   on a page whose author is selling SEO. */

@media (min-width: 900px) {
  .case__rail {
    position: sticky;
    top: calc(var(--nav-h) + 2rem);
    align-self: start;
  }
}

.case__marks {
  display: none;
  margin: 1.75rem 0 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 900px) {
  .case__marks { display: grid; gap: 0.625rem; }
}

.case__mark {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  font-size: 0.8125rem;
  color: var(--text-muted);
  transition: color 300ms ease;
}

.case__mark i {
  display: block;
  width: 16px;
  height: 1px;
  background: var(--border-strong);
  transition: width 300ms var(--ease), background-color 300ms ease;
}

.case__mark.is-on { color: var(--accent); }
.case__mark.is-on i { width: 30px; background: var(--accent); }

.case__beat {
  padding: 1.75rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: opacity 320ms ease, border-color 320ms ease;
}

.case__beat + .case__beat { margin-top: 0.875rem; }

.case__beat h3 { margin: 0 0 0.625rem; }
.case__beat p { margin: 0; }

/* Only dimmed once the script confirms it can undim them again. */
.js-beats .case__beat { opacity: 0.42; }
.js-beats .case__beat.is-on { opacity: 1; border-color: var(--border-strong); }

@media (prefers-reduced-motion: reduce) {
  .js-beats .case__beat { opacity: 1; }
}

.case__rail dt {
  font-size: 0.6875rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-muted);
  margin-bottom: 0.375rem;
}

.case__rail dd {
  color: var(--text-secondary);
  line-height: 1.5;
}

.case__body > h3 { margin-top: 2.5rem; }

.case__body > h3:first-child { margin-top: 0; }

.case__body > p { margin-top: 0.875rem; }

/* <details> — CSS only, no JS. Default marker replaced by a rotating chevron. */
.case__more {
  margin-top: 2.75rem;
  border-top: 1px solid var(--border);
  padding-top: 1.5rem;
}

.case__more summary {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  list-style: none;
  padding-block: 0.25rem;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--accent);
  transition: color 180ms ease;
}

.case__more summary::-webkit-details-marker { display: none; }

.case__more summary::marker { content: ""; }

.case__more summary:hover { color: var(--text); }

.chev {
  width: 8px;
  height: 8px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 180ms ease;
}

.case__more[open] .chev { transform: translateY(1px) rotate(225deg); }

.case__more .details-body { padding-top: 1.25rem; }

.case__more .details-body > * + * { margin-top: 1rem; }

/* ==========================================================================
   6. Selected work
   ========================================================================== */

/* Carousel — centre-stage.
   The middle card is the subject; its neighbours sit back, scaled and
   dimmed, and clicking one brings it forward. There are no arrows or dots,
   because the scaled cards are the affordance: something smaller and dimmer
   beside something sharp reads as "click me" without a caption.

   Underneath it is still a scroll-snap row, so with no JavaScript at all
   every card is full size and the whole thing is an ordinary scroller. */

.carousel { position: relative; }

.work {
  --card-w: min(84vw, 420px);
  display: flex;
  gap: 0.75rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  list-style: none;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
  /* Half the track minus half a card, so the first and last can reach the
     middle. Without this the ends never centre and the effect breaks. */
  padding-inline: max(0px, calc(50% - var(--card-w) / 2));
  padding-block: 1.5rem;
  margin-block: -1.5rem;
}

.work::-webkit-scrollbar { display: none; }

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

.work > li {
  flex: 0 0 auto;
  width: var(--card-w);
  scroll-snap-align: center;
  display: flex;
}

@media (prefers-reduced-motion: reduce) {
  .work { scroll-behavior: auto; }
}

/* Centred under the track rather than pushed to one side: the whole section
   is built around a middle, so the controls belong on that axis too. */
.carousel__controls {
  display: flex;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 2.5rem;
}

.carousel__btn {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  background: transparent;
  color: var(--text-secondary);
  font-size: 1.05rem;
  cursor: pointer;
  transition: border-color 180ms ease, color 180ms ease, background-color 180ms ease;
}

.carousel__btn:hover:not([aria-disabled="true"]) {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--bg-card);
}

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

/* Announced rather than removed, so a control never disappears from under a
   keyboard user at the ends of the track. */
.carousel__btn[aria-disabled="true"] { opacity: 0.3; cursor: default; }

/* No JS, no controls: the track on its own is the whole feature. */
.carousel:not(.is-ready) .carousel__controls { display: none; }

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color 180ms ease, background-color 180ms ease;
}

/* Screenshots are bright and this page is not, so they sit knocked back at
   rest and come up on hover. Without that, five white thumbnails punch
   holes in the section and the eye stops reading the copy. */
.card__shot {
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
}

.card__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0;
  filter: saturate(0.72) brightness(0.68) contrast(1.04);
  transform: scale(1.005);
  transition: filter 260ms ease, transform 420ms var(--ease);
}

.card:hover .card__shot img,
.card:focus-visible .card__shot img {
  filter: saturate(1) brightness(0.94) contrast(1);
  transform: scale(1.03);
}

.card__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.5rem;
}

.card:hover {
  border-color: var(--border-strong);
  background: var(--bg-elevated);
  color: inherit;
}

.card:hover .card__title { color: var(--accent); }

.card__index {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--text-muted);
  margin-bottom: 0.75rem;
}

.card__title {
  padding-right: 1.5rem;
  transition: color 180ms ease;
}

.card__desc {
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--text-secondary);
}

.card__domain {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  margin-top: auto;
  padding-top: 1.5rem;
  font-size: 0.8125rem;
  color: var(--text-muted);
}

.card__arrow {
  flex: none;
  transition: transform 180ms ease, color 180ms ease;
}

.card:hover .card__arrow {
  color: var(--accent);
  transform: translate(2px, -2px);
}

/* Only once the script is driving it do the neighbours recede. Without JS
   every card stays at full size and full opacity. */
.carousel.is-ready .card {
  transform: scale(0.87);
  opacity: 0.45;
  transition: transform 460ms var(--ease), opacity 460ms var(--ease),
              border-color 180ms ease, background-color 180ms ease;
}

.carousel.is-ready li.is-active .card {
  transform: scale(1);
  opacity: 1;
  border-color: var(--border-strong);
}

/* A strict three-colour page cannot carry five full-colour screenshots.
   Off-centre they go monochrome; only the centred card is in colour, which
   also makes the carousel's focus state do double duty. */
.carousel.is-ready li:not(.is-active) .card__shot img {
  filter: saturate(0.4) brightness(0.42) contrast(1.04);
}

.carousel.is-ready li.is-active .card__shot img {
  filter: saturate(0.86) brightness(0.86) contrast(1.02);
}

@media (prefers-reduced-motion: reduce) {
  .carousel.is-ready .card { transition: opacity 200ms ease, border-color 180ms ease; }
  .carousel.is-ready .card,
  .carousel.is-ready li.is-active .card { transform: none; }
}

/* ==========================================================================
   7. Testimonials — as cards
   ========================================================================== */

.quotes {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

@media (min-width: 900px) {
  .quotes { grid-template-columns: repeat(3, 1fr); }
}

.quote {
  display: flex;
  flex-direction: column;
  padding: 2rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.quote blockquote {
  flex: 1;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.1875rem;
  line-height: 1.45;
  letter-spacing: -0.015em;
  color: var(--text);
}

.quote figcaption {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
}

.quote picture {
  display: flex;
  flex: none;
}

.quote img {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--border);
  flex: none;
}

.quote__name {
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--text);
}

.quote__role {
  /* Two lines reserved whether the role needs them or not. One caption
     wrapping to a second line was pushing its rule up out of line with
     the other two. */
  display: block;
  min-height: 2.9em;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--text-muted);
}

/* ==========================================================================
   8. Capabilities — grouped lists on cards, hairlines kept
   ========================================================================== */

.caps {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

@media (min-width: 640px) {
  .caps { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 900px) {
  .caps { grid-template-columns: repeat(3, 1fr); }
}

.caps__group {
  padding: 2rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.caps__group h3 { margin-bottom: 0.5rem; }

.caps li {
  padding-block: 0.8125rem;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--text-secondary);
  border-top: 1px solid var(--border);
}

/* ==========================================================================
   9. Contact — centred closing statement
   ========================================================================== */

.contact { text-align: center; }

.contact .rule-accent { margin-inline: auto; }

.contact h2 {
  margin-inline: auto;
  max-width: 20ch;
}

.contact__links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.5rem;
  margin-top: 2.25rem;
  font-size: 0.9375rem;
}

.footer {
  border-top: 1px solid var(--border);
  padding-block: 2rem;
}

.footer p {
  font-size: 0.8125rem;
  color: var(--text-muted);
}

/* ==========================================================================
   Motion
   Elements are visible by default. script.js adds .js to <html> only after
   it confirms IntersectionObserver support and that reduced motion is not
   requested, so the page renders in full with JS off.
   ========================================================================== */

.js .reveal {
  opacity: 0;
  transform: translateY(8px);
}

.js .reveal.is-visible {
  opacity: 1;
  transform: none;
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  .js .reveal,
  .js .reveal.is-visible {
    opacity: 1;
    transform: none;
    transition: none;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   Motion graphics
   Added as one block so it can be tuned or deleted whole. Everything here
   animates transform or opacity only, so it runs on the compositor and
   never triggers layout or paint. No JavaScript is involved.
   The global prefers-reduced-motion rule further down neutralises every
   animation in this section; the two rules that need more than that say so.
   ========================================================================== */

/* --- Hero ambient loop --------------------------------------------------- */

.hero { isolation: isolate; overflow: hidden; }
.hero .container { position: relative; z-index: 1; }

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.hero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 60% 50%;
  opacity: 0;
  transition: opacity 900ms var(--ease);
}

/* Faded in by script.js once the file can actually paint, so the poster
   never flashes in and out under it. */
.hero__video.is-live { opacity: 0.92; }

/* Two scrims in one layer. The vertical pass seats the footage into the
   page top and bottom; the horizontal pass keeps the text column dark
   enough that body copy still clears 4.5:1 over the brightest frame. */
.hero__scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to right,
      rgba(10, 10, 11, 0.95) 0%,
      rgba(10, 10, 11, 0.92) 58%,
      rgba(10, 10, 11, 0.26) 80%,
      rgba(10, 10, 11, 0.06) 100%),
    linear-gradient(to bottom,
      rgba(10, 10, 11, 0.90) 0%,
      rgba(10, 10, 11, 0.10) 18%,
      rgba(10, 10, 11, 0.16) 74%,
      rgba(10, 10, 11, 0.92) 100%);
}

/* Portrait and small screens: the text runs full width, so the horizontal
   pass would sit under the copy instead of beside it. Vertical only, and
   the footage drops back further. */
@media (max-width: 899px) {
  .hero__video.is-live { opacity: 0.55; }
  .hero__scrim {
    background: linear-gradient(to bottom,
      var(--bg) 0%,
      rgba(10, 10, 11, 0.58) 26%,
      rgba(10, 10, 11, 0.68) 70%,
      var(--bg) 100%);
  }
}

/* --- Ambient glow drift -------------------------------------------------- */
/* The hero glow breathes: long, slow, low contrast, so it reads as depth
   rather than as something moving.

   Measured cost, via an interleaved A/B in a single Lighthouse batch:
   ~1ms of LCP (1806/1805 with, 1805/1804 without). An earlier back-to-back
   comparison appeared to show 75ms, but that was drift between measurement
   batches, not this animation. If you re-test it, interleave the runs —
   this page's LCP on a local server is bimodal at roughly 1730ms or 1805ms
   and consecutive batches are not comparable. */

.hero::before {
  animation: glow-drift 16s ease-in-out infinite;
}

@keyframes glow-drift {
  0%, 100% {
    transform: translate(-50%, -50%) scale(1);
    opacity: 1;
  }
  50% {
    transform: translate(-50%, -53%) scale(1.1);
    opacity: 0.78;
  }
}

/* --- Accent hairline draw-in --------------------------------------------- */
/* The bronze rule draws itself left to right as its section arrives. Uses
   the existing .reveal hook, so it needs no new JavaScript. Gated on .js
   so the rules are simply present when scripting is off. */

.js .reveal .rule-accent {
  transform: scaleX(0);
  transform-origin: left center;
}

.js .reveal.is-visible .rule-accent {
  animation: rule-draw 620ms var(--ease) 120ms forwards;
}

.js .hero .rule-accent {
  transform-origin: left center;
  animation: rule-draw 720ms var(--ease) 240ms both;
}

@keyframes rule-draw {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

/* --- Scroll progress hairline -------------------------------------------- */
/* A CSS scroll-driven animation: no scroll listener, no JS, no main-thread
   work. Where animation-timeline is unsupported the bar is removed rather
   than left sitting at zero width. */

.progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  z-index: 101;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  pointer-events: none;
  animation: progress-grow linear;
  animation-timeline: scroll(root block);
}

@keyframes progress-grow {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

@supports not (animation-timeline: scroll()) {
  .progress { display: none; }
}

/* The blanket reduced-motion rule collapses animation duration, which would
   strand these two mid-state rather than at rest. Handle them explicitly. */
@media (prefers-reduced-motion: reduce) {
  .progress { display: none; }

  .js .reveal .rule-accent,
  .js .reveal.is-visible .rule-accent,
  .js .hero .rule-accent {
    transform: none;
    animation: none;
  }
}

/* ==========================================================================
   Location and contact icons
   ========================================================================== */

/* Location. A small map earns its place only if it is instantly readable
   as one fact: she works from here. One pin, no arcs, no legend. */
.locate {
  display: inline-flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 2rem;
  padding: 0.875rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-card);
}

.locate__map { display: block; width: 148px; height: auto; flex: none; }

.locate__where { margin: 0; text-align: left; }
.locate__where b { display: block; font-weight: 500; color: var(--text); font-size: 0.9375rem; }
.locate__where span { display: block; font-size: 0.8125rem; color: var(--text-muted); margin-top: 0.125rem; }

/* Icon links. Labelled for assistive tech and given a visible label on the
   contact block, because an unlabelled glyph row is a guessing game. */
.iconrow {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.625rem;
  margin-top: 2rem;
}

.iconlink {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.75rem 1.125rem 0.75rem 0.9375rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.9375rem;
  text-decoration: none;
  transition: border-color 180ms ease, color 180ms ease, background-color 180ms ease;
}

.iconlink svg { flex: none; width: 17px; height: 17px; }

.iconlink:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--bg-card);
}

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

/* ==========================================================================
   Tech stack
   ========================================================================== */

.stack {
  display: grid;
  gap: 2.5rem 2rem;
  margin-top: 2.5rem;
  grid-template-columns: 1fr;
}

@media (min-width: 640px) { .stack { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .stack { grid-template-columns: repeat(4, 1fr); } }

.stack__group h3 {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 1.125rem;
  padding-bottom: 0.875rem;
  border-bottom: 1px solid var(--border);
}

.stack__list { display: grid; gap: 0.75rem; margin: 0; padding: 0; list-style: none; }

.tool {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.9375rem;
  color: var(--text-secondary);
}

.tool svg { flex: none; width: 18px; height: 18px; color: var(--text-muted); }

/* Not every tool she uses has an openly licensed mark, and inventing a
   logo for the ones that do not is worse than not drawing one. Those get a
   monogram in the same footprint so the column still lines up. */
.tool__mono {
  flex: none;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  font-size: 0.5625rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text-muted);
}

/* ==========================================================================
   Rendering budget
   ========================================================================== */

/* The browser skips layout, paint and style for sections it cannot see. The
   contain-intrinsic-size is a placeholder height so the scrollbar does not
   jump as each one is realised; it only needs to be the right order of
   magnitude, not exact. The hero is excluded because it is the first paint. */
.section:not(.hero) {
  content-visibility: auto;
  contain-intrinsic-size: auto 640px;
}

/* Print: no motion, no chrome, just the words. */
@media print {
  .nav, .nav__pill, .nav__cta, .hero__media, .grain, .carousel__controls { display: none !important; }
  body { background: #fff; color: #000; }
  .case__beat, .card, .quote { break-inside: avoid; }
}
