/* ============================================================
   Keerthana Sanjay — Portfolio
   ============================================================ */

/* ---------- PolySans Trial ----------
   Neutral = body (400), Bulky = headings (700), Slim declared for future use.

   HEADS UP: these trial cuts carry only 68 glyphs — A-Z, a-z, 0-9 and
   . , : /   Everything else (hyphen, apostrophe, em dash, & % @ • ; ! ?)
   is absent and falls through to the next family in the stack, so those
   characters render in a different face mid-word. That is expected here,
   not a bug. Replacing these with a full-charset licensed cut fixes it
   with no other change.

   The fallback is deliberately a neutral grotesk (Helvetica Neue / Arial)
   rather than a system serif or rounded face — its punctuation sits closest
   to PolySans, which keeps the mixing as quiet as it can be.

   .otf only: no .woff2 was supplied. woff2 would be ~5-10x smaller; if you
   get one, add it as the first src and it will win automatically.          */
@font-face {
  font-family: "PolySans";
  src: url("../fonts/PolySans-Neutral.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "PolySans";
  src: url("../fonts/PolySans-Bulky.otf") format("opentype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "PolySans";
  src: url("../fonts/PolySans-Slim.otf") format("opentype");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Core palette — white ground, ink text (18.9:1, far past WCAG AA) */
  --bg:         #ffffff;
  --text:       #100e11;
  --text-dim:   rgba(16, 14, 17, 0.62);     /* 5.33:1 on the ground */
  --line:       rgba(16, 14, 17, 0.16);     /* rules/borders on the ground */
  --ink:        #100e11;
  --ink-soft:   #1b1826;
  --paper:      #fbf7f2;
  --paper-warm: #f0e9e0;

  /* Playful accents. These are FILL colours — they carry --ink or --paper on
     top, never the reverse. The one that also works as TEXT on the white
     ground is --acc-purple (5.03:1); the lemon is 1.13:1 there, so it is
     strictly a fill or a colour for use on the dark ink panels. */
  --acc-purple: #904ada;
  --acc-yellow: #f6f189;
  --acc-green:  #3fca6c;
  --acc-pink:   #ff5fa8;
  --acc-blue:   #4c63e6;
  --acc-deep:   #2a1b6b;

  /* Type — one family, two weights: Bulky (700) heads, Neutral (400) body */
  --display: "PolySans", "Helvetica Neue", Arial, sans-serif;
  --body: "PolySans", -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
  --display-weight: 700;

  /* Type scale. Flat, not fluid — 20px everywhere that isn't a heading;
     16px only for labels that must stay subordinate to an adjacent title
     (facts dt, eyebrows, tags, section counts, cite, footer). */
  --fs-body:  1.25rem;   /* 20px — everything that isn't a heading        */
  --fs-label: 1rem;      /* 16px — labels subordinate to an adjacent title */

  /* Rhythm */
  --gutter: clamp(20px, 5vw, 80px);
  --maxw: 1440px;
  --readw: 720px;
  --radius: 34px;
  --radius-sm: 18px;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-soft: cubic-bezier(0.65, 0, 0.35, 1);

  /* Ring metrics, shared by the case-study media and the work cards. Each
     surface supplies its own colour: --ring-c on the case-study pages,
     --card-accent on the cards. */
  --ring-w:   clamp(2px, .275vw, 3.5px);
  --ring-gap: clamp(8px, 1.1vw, 14px);
  --ring-r:   clamp(26px, 3.4vw, 44px);
  --ring-t:   calc(var(--ring-gap) + var(--ring-w));   /* total thickness */
  --ring-in:  calc(var(--ring-r) - var(--ring-t));     /* inner radius */

  /* Hero grid. Kept well under the --line value used for real rules, so it
     reads as texture rather than as structure. */
  --grid-cell: clamp(44px, 5vw, 68px);
  --grid-line: rgba(16, 14, 17, 0.13);
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body, h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl, dd { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img, video, svg { display: block; max-width: 100%; }
img, video { height: auto; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }

body {
  font-family: var(--body);
  font-size: var(--fs-body);
  line-height: 1.65;
  color: var(--text);
  background: var(--bg);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--acc-yellow); color: var(--ink); }

/* ---------- Layout helpers ---------- */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--read { max-width: calc(var(--readw) + var(--gutter) * 2); }

.display {
  font-family: var(--display);
  font-weight: var(--display-weight);
  line-height: 1.04;
  letter-spacing: -0.005em;
  color: var(--text);
}

.eyebrow {
  font-size: var(--fs-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ---------- Custom cursor ---------- */
.cursor {
  position: fixed;
  top: 0; left: 0;
  width: 22px; height: 22px;
  border-radius: 50%;
  /* Lemon was 1.13:1 on the white ground — only its ring was visible. The
     accent purple carries itself at 5.03:1 and matches the hero and pills. */
  background: var(--acc-purple);
  pointer-events: none;
  z-index: 9999;
  box-shadow: 0 0 0 1.5px rgba(16, 14, 17, .18);
  transform: translate3d(-100px, -100px, 0);
  transition: width .3s var(--ease), height .3s var(--ease), opacity .3s;
  will-change: transform;
}
/* Half the old 74px — at full size it swallowed the tab labels it sat on. */
.cursor.is-big { width: 37px; height: 37px; }
@media (hover: none), (pointer: coarse) { .cursor { display: none; } }

/* ============================================================
   Header
   ============================================================ */
.site-head {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 22px var(--gutter);
  /* Opaque, or page content scrolls visibly through the fixed header. */
  background: var(--bg);
  transition: transform .5s var(--ease), background-color .35s var(--ease);
}
.site-head.is-hidden { transform: translateY(-120%); }
/* With the menu open the header sits on the light overlay, so drop the
   purple and let the overlay show through instead. */
body.menu-open .site-head { background: transparent; }

.mark { display: flex; align-items: center; }
.mark__name {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: var(--fs-body);
  color: var(--text);
  line-height: 1;
  display: block;
}
.mark__text { opacity: 0; animation: fadeUp .7s var(--ease) 1.1s forwards; }

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* Menu toggle */
.menu-btn {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 20px 11px 22px;
  border-radius: 30px;
  /* Inverted against the ground: solid ink on white, flipping to an outline
     on hover. Tracks --bg/--ink, so it stays paired if the ground changes. */
  border: 1.5px solid var(--ink);
  background: var(--ink);
  color: var(--bg);
  font-size: var(--fs-body);
  transition: background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
}
.menu-btn:hover { background: transparent; color: var(--ink); border-color: var(--ink); }
.menu-btn__bars { display: grid; gap: 5px; width: 20px; }
.menu-btn__bars i {
  display: block; height: 2px; border-radius: 2px;
  background: currentColor;
  transition: transform .4s var(--ease), opacity .3s;
}
body.menu-open .menu-btn__bars i:nth-child(1) { transform: translateY(7px) rotate(45deg); }
body.menu-open .menu-btn__bars i:nth-child(2) { opacity: 0; }
body.menu-open .menu-btn__bars i:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
body.menu-open .menu-btn { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* ============================================================
   Fullscreen menu
   ============================================================ */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 150;
  background: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 120px 0 60px;
  clip-path: circle(0% at calc(100% - 80px) 44px);
  transition: clip-path .8s var(--ease);
  pointer-events: none;
}
body.menu-open .overlay { clip-path: circle(150% at calc(100% - 80px) 44px); pointer-events: auto; }

.overlay__nav { display: grid; gap: clamp(2px, 1vw, 10px); }
.overlay__nav a {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(2.2rem, 8.5vw, 5.6rem);
  line-height: 1.1;
  width: max-content;
  max-width: 100%;
  transform: translateY(40px);
  opacity: 0;
  transition: transform .6s var(--ease), opacity .6s var(--ease), letter-spacing .35s var(--ease);
}
body.menu-open .overlay__nav a { transform: none; opacity: 1; }
/* All four sit black on the light overlay; only the stagger differs. */
.overlay__nav a { color: var(--ink); }
.overlay__nav a:nth-child(1) { transition-delay: .16s; }
.overlay__nav a:nth-child(2) { transition-delay: .22s; }
.overlay__nav a:nth-child(3) { transition-delay: .28s; }
.overlay__nav a:nth-child(4) { transition-delay: .34s; }
.overlay__nav a:hover { letter-spacing: 0.012em; }

.overlay__foot {
  margin-top: clamp(40px, 7vw, 90px);
  display: flex;
  flex-wrap: wrap;
  gap: 12px 34px;
  color: rgba(16, 14, 17, 0.62);
  font-size: var(--fs-body);
  opacity: 0;
  transform: translateY(20px);
  transition: all .6s var(--ease) .42s;
}
body.menu-open .overlay__foot { opacity: 1; transform: none; }
.overlay__foot a { border-bottom: 1.5px solid rgba(16, 14, 17, .25); }
.overlay__foot a:hover { border-color: var(--ink); }

/* ============================================================
   First fold — hero and marquee share exactly one viewport, so the
   marquee sits on the bottom edge and everything below needs a scroll.
   ============================================================ */
.fold {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}
.fold .hero {
  flex: 1 1 auto;
  min-height: 0;      /* the fold owns the height now */
}

/* ============================================================
   Hero
   ============================================================ */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 150px 0 100px;
}
/* A faint grid in the empty lower half of the fold. Masked so it is invisible
   behind the headline and reaches full strength only where it meets the black
   strip, which keeps it from competing with the type. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(to right,  var(--grid-line) 0 1px, transparent 1px var(--grid-cell)),
    repeating-linear-gradient(to bottom, var(--grid-line) 0 1px, transparent 1px var(--grid-cell));
  -webkit-mask-image: linear-gradient(to bottom, transparent 22%, #000 92%);
          mask-image: linear-gradient(to bottom, transparent 22%, #000 92%);
}
/* Content rides above the grid. */
.hero > .wrap { position: relative; z-index: 1; }

/* Bodies with mass: these drift toward the pointer as it comes near, the pull
   easing off with distance. js/main.js drives the transform, so nothing here
   may animate transform on these same elements or the two will fight. */
.mark__name,
.menu-btn,
.hero__title .line,
.hero__meta span { will-change: transform; }

.hero__meta {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px 30px;
  font-size: var(--fs-body);
  color: var(--text);
  margin-bottom: clamp(30px, 6vw, 70px);
}
.hero__meta span { display: inline-flex; align-items: center; gap: 9px; }

.hero__title {
  font-size: clamp(2.4rem, 9.4vw, 8.6rem);
  max-width: 15ch;
}
.hero__title .line { display: block; overflow: hidden; }
.hero__title .line > span {
  display: block;
  transform: translateY(105%);
  animation: riseIn 1s var(--ease) forwards;
}
.hero__title .line:nth-child(1) > span { animation-delay: .30s; }
.hero__title .line:nth-child(2) > span { animation-delay: .40s; }
.hero__title .line:nth-child(3) > span { animation-delay: .50s; }
@keyframes riseIn { to { transform: none; } }

.hero__title em {
  font-style: normal;
  display: inline-block;
  /* Purple on white is 5.03:1 — clears AA for body text, never mind display. */
  color: var(--acc-purple);
}

/* The hand is a mask rather than an <img> or inline SVG, so it takes its
   colour from currentColor and the file is fetched once for every place it
   appears. Its artwork is 77 × 103, hence the 0.748 width ratio. */
.wink {
  display: inline-block;
  height: 0.74em;
  width: calc(0.74em * 0.748);
  vertical-align: -0.04em;
  background: currentColor;
  color: var(--acc-purple);
  -webkit-mask: url("../assets/img/hand.svg") no-repeat center / contain;
          mask: url("../assets/img/hand.svg") no-repeat center / contain;
  animation: wobble 4.5s var(--ease-soft) infinite;
  transform-origin: 60% 80%;
}
@keyframes wobble {
  0%, 62%, 100% { transform: rotate(0deg); }
  68% { transform: rotate(15deg); }
  74% { transform: rotate(-11deg); }
  80% { transform: rotate(9deg); }
  86% { transform: rotate(-4deg); }
}

/* ---------- Marquee ---------- */
.marquee {
  background: var(--ink);
  color: var(--paper);
  padding: 20px 0;
  overflow: hidden;
  display: flex;
  user-select: none;
}
.marquee__track {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 34px;
  padding-right: 34px;
  animation: roll 34s linear infinite;
  white-space: nowrap;
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(1.1rem, 2.2vw, 1.7rem);
  color: inherit;
}
.marquee__track span:nth-child(even) { color: var(--acc-purple); font-size: .8em; }
@keyframes roll { to { transform: translateX(-100%); } }
.marquee:hover .marquee__track { animation-play-state: paused; }

/* ============================================================
   Sections
   ============================================================ */
.section { padding: clamp(70px, 11vw, 150px) 0; }

.section__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 18px 40px;
  margin-bottom: clamp(34px, 5vw, 62px);
}
/* Work heading centres over its centred cards. Scoped, so the Experience
   head keeps its title-left / count-right split. */
#work .section__head { justify-content: center; text-align: center; }
.section__title {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(1.8rem, 5vw, 3.6rem);
  line-height: 1.05;
  color: var(--text);
}
.section__count {
  font-size: var(--fs-label);
  color: var(--text-dim);
  letter-spacing: .1em;
}

/* ---------- What I do ----------
   Three peer cards between the ribbon and the work. Same paper panel as the
   project cards, but a hairline instead of an accent ring: these are context,
   not destinations, so nothing here should compete with the case studies. */
.section--services { padding-bottom: 0; }
/* The three cards are a deck rather than a row: one is face-up, the rest sit
   behind it offset and tilted, and the button pages through them. */
.deck__bar {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: clamp(18px, 2.2vw, 28px);
}
.deck__meter { width: 30px; height: 30px; flex-shrink: 0; }
.deck__meter circle {
  fill: none;
  stroke-width: 3;
  /* Start the arc at 12 o'clock instead of 3, where a bare circle begins. */
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
}
.deck__track { stroke: var(--line); }
.deck__fill {
  stroke: var(--acc-purple);
  stroke-linecap: round;
  transition: stroke-dashoffset .5s var(--ease);
}
.deck__count {
  font-size: var(--fs-label);
  letter-spacing: .14em;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;   /* stops the counter jittering */
}
.deck__next {
  margin-left: auto;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  /* Matches the cards it pages through: purple hairline, purple chevron. */
  border: 1px solid var(--acc-purple);
  color: var(--acc-purple);
  transition: background .35s var(--ease), color .35s var(--ease),
              border-color .35s var(--ease);
}
/* currentColor, so the glyph inverts with the button rather than needing its
   own hover rule. The source file's #e3e3e3 fill is dropped for the same reason. */
.deck__next svg {
  width: 21px;
  height: 21px;
  fill: currentColor;
  display: block;
}
/* Fills with the same purple the cards take on hover, so the control and the
   thing it controls behave alike. White on it is 5.03:1. */
.deck__next:hover {
  background: var(--acc-purple);
  border-color: var(--acc-purple);
  color: #fff;
}

/* Grid stacking rather than absolute positioning, so the stack still sizes
   itself to the tallest card instead of needing a hardcoded height. */
.deck__stack {
  display: grid;
  /* The box sizes to the untransformed card, but the tilted slots behind it
     hang below that edge — 46px at full width, from 26px of offset plus what
     the rotation drops the far corner by. Without this the section's bottom
     gap measures the same as the top but *looks* ~50px tighter. Scales with
     the card width, since the rotation's contribution does too. */
  padding-bottom: clamp(34px, 3.6vw, 48px);
}
.deck__slot {
  grid-area: 1 / 1;
  /* --d is the card's depth in the stack, written by js/main.js. */
  --d: 0;
  z-index: calc(10 - var(--d));
  transform:
    translate3d(calc(var(--d) * -16px), calc(var(--d) * 13px), 0)
    rotate(calc(var(--d) * -2deg))
    scale(calc(1 - var(--d) * 0.035));
  transition: transform .55s var(--ease), opacity .45s var(--ease);
}
/* Only the face-up card takes the pointer, so hover and clicks can't land on
   a card buried behind it. */
.deck__slot:not([data-front]) { pointer-events: none; }
.deck__slot:not([data-front]) .service { box-shadow: 0 18px 40px -26px rgba(16,14,17,.45); }
.service {
  /* The page's own white, not `transparent`. Reads identically — no visible
     fill, the ring still defines the card — but it is opaque, so the face-up
     card hides the ones stacked behind it. Truly transparent and you read all
     three sets of copy through each other. */
  background: var(--bg);
  /* A hairline in the accent the ✦ marks already use. Thinner than the
     case-study ring on purpose: stacked, three 3.5px strokes read as heavy. */
  border: 1px solid var(--acc-purple);
  border-radius: var(--radius);
  /* Block 35% tighter than the inline padding's original, inline 20% tighter —
     each clamp stop scaled by the same factor so it holds at every width. */
  padding: clamp(15.6px, 1.82vw, 24.7px) clamp(19.2px, 2.24vw, 30.4px);
  transition: background .4s var(--ease), transform .55s var(--ease),
              box-shadow .55s var(--ease);
}
/* Fills with the accent and lifts, matching the project cards. The .is-in half
   of the selector is required: `.reveal.is-in { transform: none }` sits later
   in the file at equal specificity and would otherwise cancel the lift. */
.service:hover,
.service.is-in:hover {
  background: var(--acc-purple);
  transform: translateY(-10px) rotate(.5deg);
  box-shadow: 0 34px 70px -30px rgba(0,0,0,.55);
}
/* Ink on this purple is only 3.8:1 and the mark would vanish into the fill,
   so the whole card inverts to white — 5.03:1 for both type sizes. */
.service:hover .service__mark,
.service:hover .service__title,
.service:hover .service__txt { color: #fff; }
.service__mark,
.service__title,
.service__txt { transition: color .4s var(--ease); }
.service__mark {
  display: block;
  font-size: 13px;
  line-height: 1;
  color: var(--acc-purple);
}
.service__title {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: var(--fs-body);    /* 20px — now clears the 16px body under it */
  text-transform: uppercase;
  letter-spacing: .05em;
  line-height: 1.2;
  color: var(--ink);
  margin: 5px 0 12px;
}
.service__txt {
  font-size: var(--fs-label);   /* 16px */
  line-height: 1.6;
  color: var(--text-dim);
}
/* Held to the same column as the case-study cards, so the row lines up with
   the work below instead of running wider than it. Three fixed tracks rather
   than auto-fit: inside the narrower column, auto-fit would collapse to two. */
@media (min-width: 900px) {
  .deck { max-width: 70%; margin-inline: auto; }
}

/* ---------- Work cards ---------- */
.work { display: grid; gap: 120px; }
/* Cards run at 70% of the content column on wide screens — full width
   below 900px, where 70% would leave them too narrow to read. */
@media (min-width: 900px) {
  .work { max-width: 70%; margin-inline: auto; }
}

.card {
  /* Two very different insets, measured off the reference: the media is held
     tight to the card edge (~1.6% of the card's width) while the text column
     is pulled well in (~8%). The wide text gutter against the near-bleed
     image is what gives the card its proportions — keep the ratio if these
     ever change. */
  --card-pad:    clamp(10px, 1.6vw, 14px);
  --card-gutter: clamp(8px, 1.3vw, 18px);
  display: block;
  background: var(--paper);
  border-radius: var(--radius);
  /* Hairline in the card's own accent — the card padding is the gap, so the
     paper reads as the white inside the stroke. */
  border: 1px solid var(--card-accent);
  padding: var(--card-pad);
  color: var(--ink);
  transition: transform .55s var(--ease), box-shadow .55s var(--ease);
  /* Paper on white is a 3% step, so the card needs a resting shadow to read
     as an object rather than a colour shift. */
  box-shadow: 0 3px 24px -14px rgba(16,14,17,.34);
}
/* Every card lifts the same way — the tilt used to alternate by position,
   which made the two projects feel like different components.
   The .is-in half of the selector outweighs `.reveal.is-in { transform: none }`,
   which sits later in the file at equal specificity and was cancelling the lift. */
.card:hover,
.card.is-in:hover { transform: translateY(-10px) rotate(.5deg); box-shadow: 0 34px 70px -30px rgba(0,0,0,.55); }

.card__media {
  /* Derived from the stroke plus the inset, so the media corner stays
     concentric with the card's. */
  border-radius: calc(var(--radius) - 1px - var(--card-pad));
  overflow: hidden;
  aspect-ratio: 16 / 9;
  display: grid;
  place-items: center;
}
.card__media img {
  width: 100%; height: 100%;
  object-fit: contain;
  padding: 6%;
  transition: transform 1.1s var(--ease);
}
.card:hover .card__media img { transform: scale(1.045); }

.card__body {
  padding: clamp(20px, 2.6vw, 30px) var(--card-gutter) clamp(14px, 2vw, 22px);
}
/* Title, then a rule, then the chips and the CTA share one row. */
.card__foot {
  margin-top: clamp(18px, 2.4vw, 26px);
  padding-top: clamp(16px, 2vw, 22px);
  border-top: 1.5px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px 24px;
}
.card__title {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(1.4rem, 3.6vw, 2.9rem);
  line-height: 1.06;
  color: var(--ink);
  max-width: 18ch;
}
.card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.tag {
  font-size: var(--fs-label);
  letter-spacing: .09em;
  text-transform: uppercase;
  padding: 8px 15px;
  border-radius: 30px;
  background: rgba(16, 14, 17, .1);
  color: var(--ink);
  white-space: nowrap;
}
/* Solid tag takes its card's panel colour, with a text colour chosen per
   card — white fails on the coral (2.48:1), ink passes at 7.74:1. */
.tag--solid { background: var(--card-accent, var(--acc-blue)); color: var(--card-accent-ink, #fff); }
/* Case-study hero tags sit on the ground, not on paper — invert them so they
   match the menu button: solid ink, ground-coloured text. */
.cs-hero__kicker .tag,
.cs-hero__kicker .tag--solid {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg);
}

.card__go {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: var(--fs-body);
  color: var(--ink);
  padding: 12px 24px;
  border-radius: 30px;
  background: var(--acc-yellow);
  transition: transform .4s var(--ease), background .4s var(--ease);
  white-space: nowrap;
  /* Holds the right edge at every width: space-between covers the one-line
     desktop case, but once the body wraps — or stacks on mobile — the pill
     needs an auto margin to stay at the end instead of dropping left. */
  margin-left: auto;
}
.card:hover .card__go { transform: translateX(6px); background: var(--ink); color: #fff; }

/* Sits under the last case study, centred on the same column the cards use,
   so the section closes with a note that the list is still growing. */
.work__more {
  margin-top: clamp(30px, 4vw, 52px);
  text-align: center;
  font-size: var(--fs-body);
  color: var(--text);
}

/* ---------- Experience ---------- */
.xp { border-top: 1.5px solid var(--line); }
.xp__row {
  display: grid;
  grid-template-columns: 6.5rem 1fr auto;
  align-items: center;
  gap: 16px 28px;
  padding: clamp(20px, 2.6vw, 30px) clamp(4px, 1.6vw, 22px);
  border-bottom: 1.5px solid var(--line);
  position: relative;
  isolation: isolate;
  transition: padding-left .5s var(--ease), color .4s var(--ease);
}
.xp__row::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--acc-purple);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform .5s var(--ease);
}
.xp__row:hover::before { transform: scaleY(1); }
/* Everything goes plain white on the purple — 5.03:1. The org line used to be
   dimmed to 72%, which only reaches 3.38:1 against this fill. */
.xp__row:hover { color: #fff; padding-left: clamp(18px, 3.4vw, 48px); }
.xp__row:hover .xp__year { color: #fff; }
.xp__row:hover .xp__org { color: #fff; }

.xp__year { font-family: var(--display); font-weight: var(--display-weight); font-size: clamp(1.05rem, 2vw, 1.55rem); color: var(--text); transition: color .4s var(--ease); }
.xp__role { font-size: clamp(1rem, 2.1vw, 1.45rem); font-family: var(--display); font-weight: var(--display-weight); color: var(--text); }
.xp__row:hover .xp__role { color: #fff; }
.xp__org { font-size: var(--fs-label); color: var(--text-dim); transition: color .4s var(--ease); }

/* ---------- Contact / Footer ---------- */
.outro { padding: clamp(80px, 12vw, 170px) 0 0; }
/* A panel carrying the mark, a quiet line and one very large call to action,
   with the links hanging off its bottom edge as tabs. At rest the panel is a
   purple fill with white type; hovering it inverts to paper with purple type
   (5.03:1 either way). */
.contact__panel {
  background: var(--acc-purple);
  border-radius: var(--radius);
  padding: clamp(44px, 7vw, 96px) clamp(20px, 3vw, 48px) clamp(14px, 1.8vw, 26px);
  text-align: center;
  overflow: hidden;
  transition: background .45s var(--ease);
}
/* Same mask, inheriting the panel's colour — white at rest, purple on hover. */
.contact__mark {
  display: block;
  height: clamp(30px, 3.6vw, 48px);
  width: clamp(22px, 2.7vw, 36px);
  margin-inline: auto;
  background: currentColor;
  color: #fff;
  -webkit-mask: url("../assets/img/hand.svg") no-repeat center / contain;
          mask: url("../assets/img/hand.svg") no-repeat center / contain;
  transition: color .45s var(--ease);
}
.contact__panel:hover .contact__mark { color: var(--acc-purple); }
.contact__lede {
  margin-top: clamp(20px, 3vw, 34px);
  font-size: var(--fs-body);
  color: #fff;
  transition: color .45s var(--ease);
}
.contact__title {
  margin-top: clamp(34px, 6vw, 86px);
  font-family: var(--display);
  font-weight: var(--display-weight);
  /* Sized to run nearly the full width of the panel, as in the reference. */
  font-size: clamp(2.6rem, 13.5vw, 11rem);
  line-height: .86;
  letter-spacing: -0.025em;
  color: #fff;
  transition: color .45s var(--ease);
}
.contact__panel:hover { background: var(--paper); }
.contact__panel:hover .contact__lede,
.contact__panel:hover .contact__title { color: var(--acc-purple); }

/* Tabs hang below the panel: square where they meet it, round at the foot. */
.contact__tabs {
  --tab-pad-y: clamp(12px, 1.5vw, 17px);
  --tab-grow:  clamp(4.5px, .55vw, 5.7px);   /* ~10% of a tab's height */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: clamp(6px, 1.2vw, 16px);
  padding-inline: clamp(10px, 4vw, 64px);
  /* Reserve the growth, so a hovered tab drops into space that already
     exists instead of pushing the footer down. */
  padding-bottom: var(--tab-grow);
}
.contact__tab {
  background: var(--paper);
  color: var(--text);
  font-size: var(--fs-label);
  padding: var(--tab-pad-y) clamp(18px, 2.6vw, 34px);
  /* Square where they meet the panel, round at the foot. */
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  transition: background .35s var(--ease), color .35s var(--ease),
              padding-top .35s var(--ease), padding-bottom .35s var(--ease);
}
/* Hover pays the tab out like a ribbon. The growth is split across both
   paddings, so the tab's top edge stays put, the whole length is added
   below, and the label rides down to sit centred in the taller tab. */
/* Four tabs stop fitting on one row at 417px and wrap 3 + 1, which reads as a
   mistake. "work" is the one that drops: on the home page it only jumps down
   the same page, and every layout keeps it in the header menu.
   The row also gives up its side padding here — at 320px the remaining three
   need 265px and the padding left only 254px, so they wrapped too. */
@media (max-width: 417px) {
  .contact__tab:first-child { display: none; }
  .contact__tabs { padding-inline: 0; }
}
.contact__tab:hover {
  background: var(--ink);
  color: #fff;
  padding-top: calc(var(--tab-pad-y) + var(--tab-grow) / 2);
  padding-bottom: calc(var(--tab-pad-y) + var(--tab-grow) / 2);
  /* Cancels the added length in the layout so the tab overflows into the
     row's reserved padding — the ribbon drops without moving the footer. */
  margin-bottom: calc(-1 * var(--tab-grow));
}

.site-foot {
  margin-top: clamp(70px, 11vw, 140px);
  padding: 30px 0 34px;
  border-top: 1.5px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px 24px;
  font-size: var(--fs-label);
  color: var(--text-dim);
}
.site-foot a:hover { color: var(--text); }
.site-foot__set { display: flex; gap: 10px; align-items: center; }

/* ============================================================
   Case study pages
   ============================================================ */
.progress {
  position: fixed;
  top: 0; left: 0;
  height: 3px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: var(--ink);
  z-index: 300;
}

.cs-hero { padding: 170px 0 clamp(50px, 7vw, 90px); }
.cs-hero__kicker { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: clamp(20px, 3vw, 34px); }
.cs-hero__title {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(2rem, 7.4vw, 6rem);
  line-height: 1.05;
  color: var(--text);
  max-width: 16ch;
}
.cs-hero__lede {
  margin-top: clamp(20px, 3vw, 32px);
  font-size: clamp(1.5rem, 2.6vw, 2.25rem);   /* 24 -> 36px */
  line-height: 1.35;
  color: var(--text);
  max-width: 30ch;
  font-family: var(--display);
  font-weight: var(--display-weight);
}

.facts {
  margin-top: clamp(40px, 6vw, 70px);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 26px 34px;
  padding-top: clamp(26px, 4vw, 40px);
  border-top: 1.5px solid var(--line);
}
.facts dt { font-size: var(--fs-label); letter-spacing: .17em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 10px; }
.facts dd { font-size: var(--fs-body); color: var(--text); }
.facts dd + dd { margin-top: 4px; }

/* Sticky section nav */
.cs-nav {
  position: sticky;
  /* Sits directly under the fixed header when it's visible, and reclaims the
     top of the viewport when the header hides on scroll-down. --head-offset
     is kept in sync by js/main.js (measured, so it survives responsive
     changes to the header's height). */
  top: var(--head-offset, 0px);
  z-index: 90;
  /* Same white as the ground, so it needs a hairline to separate from the
     content scrolling under it. */
  background: var(--bg);
  color: var(--text);
  border-bottom: 1.5px solid var(--line);
  transition: top .5s var(--ease);
}
.cs-nav__inner { display: flex; gap: 6px; overflow-x: auto; padding-block: 12px; scrollbar-width: none; }
.cs-nav__inner::-webkit-scrollbar { display: none; }
.cs-nav a {
  flex-shrink: 0;
  font-size: var(--fs-body);
  padding: 9px 18px;
  border-radius: 30px;
  color: var(--text);
  transition: background .35s var(--ease), color .35s var(--ease);
}
.cs-nav a:hover { background: rgba(16, 14, 17, .08); }
/* Active state inverts to a solid ink pill, mirroring the menu button. */
.cs-nav a.is-active { background: var(--ink); color: var(--bg); }

/* Content blocks */
.block { padding: clamp(48px, 8vw, 100px) 0 0; }
.block__label {
  font-size: var(--fs-label);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--text);
  margin-bottom: 18px;
}
.block__title {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(1.5rem, 4.4vw, 3.1rem);
  line-height: 1.1;
  color: var(--text);
  max-width: 20ch;
  margin-bottom: clamp(18px, 2.4vw, 28px);
}
.prose { max-width: var(--readw); }
.prose p {
  font-size: var(--fs-body);
  line-height: 1.65;
  color: var(--text);
}
.prose p + p { margin-top: 1.15em; }
.prose strong { color: var(--text); font-weight: 500; }

.quote {
  margin: clamp(34px, 5vw, 56px) 0 0;
  padding: clamp(26px, 3.6vw, 40px);
  border-radius: var(--radius-sm);
  background: var(--ink);
  color: var(--paper);
  border-left: 4px solid var(--acc-pink);
  max-width: var(--readw);
}
.quote p { font-family: var(--display); font-weight: var(--display-weight); font-size: clamp(1.1rem, 2.1vw, 1.5rem); line-height: 1.35; color: var(--paper); }
.quote cite { display: block; margin-top: 14px; font-style: normal; font-size: var(--fs-label); color: rgba(251, 247, 242, .66); }

/* Stat cards */
.stats {
  margin-top: clamp(34px, 5vw, 56px);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 16px;
}
.stat {
  background: var(--paper);
  color: var(--ink);
  border-radius: var(--radius-sm);
  padding: clamp(22px, 3vw, 30px);
  transition: transform .5s var(--ease);
}
.stat:hover { transform: translateY(-6px) rotate(-.6deg); }
.stat__num { font-family: var(--display); font-weight: var(--display-weight); font-size: clamp(2rem, 4.6vw, 3.1rem); line-height: 1; color: var(--acc-blue); }
.stat__head {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(1.05rem, 1.9vw, 1.3rem);
  line-height: 1.15;
  color: var(--ink);
  margin-top: 10px;
}
.stat__txt { margin-top: 12px; font-size: var(--fs-body); line-height: 1.5; color: rgba(16, 14, 17,.75); }
.stat:nth-child(2) .stat__num { color: #a3007b; }
.stat:nth-child(3) .stat__num { color: #066607; }
.stat:nth-child(2) { background: var(--paper-warm); }

/* Media frames */
.figure { margin: clamp(40px, 6vw, 80px) 0 0; }

/* A paragraph directly above a figure is introducing it. The full section gap
   makes the two read as unrelated, so pull the figure up close. */
.prose + .figure { margin-top: clamp(14px, 1.8vw, 22px); }

/* Caption sits beside the media rather than under it, once there's room.
   :has() so a figure with no caption keeps the media full-width instead of
   being squeezed into column one against an empty column. */
@media (min-width: 1000px) {
  .figure:has(figcaption) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(15rem, 22rem);
    gap: clamp(24px, 3vw, 48px);
    align-items: center;   /* caption sits level with the middle of the media */
  }
  .figure:has(figcaption) figcaption {
    margin-top: 0;
    max-width: none;       /* the column already constrains the measure */
  }
  /* When the media is capped narrow, shrink its COLUMN to match. Capping the
     frame inside a full-width column left a dead gap before the caption. */
  .figure:has(.figure__frame--inset) {
    grid-template-columns: minmax(0, 560px) minmax(15rem, 22rem);
  }
}

/* A caption can carry more than one paragraph. */
.figure figcaption p + p { margin-top: 1em; }
.figure__frame {
  background: var(--paper);
  border-radius: var(--radius);
  padding: clamp(14px, 2vw, 26px);
  overflow: hidden;
}
.figure__frame > img,
.figure__frame > video {
  width: 100%;
  border-radius: calc(var(--radius) - 16px);
  display: block;
}
.figure--bare .figure__frame { background: transparent; padding: 0; }

/* ---------- Ring frames (case studies) ----------
   Sticker-style framing: a bold accent stroke, a white gap inside it, deep
   round corners. Each page sets --ring-c to its own accent — the same colour
   its card carries on the home page, so the two stay tied together.

   The three ring values drive the inner radius as well, keeping the corners
   concentric: change one and every piece of media follows. */
.page-ems     { --ring-c: var(--acc-blue); }
.page-checkin { --ring-c: #ff7f61; }

/* Media that already sits in a wrapper: a real border, with the wrapper's
   padding as the gap. Wins over .figure--bare, which zeroes both. */
.page-ems .figure__frame,
.page-checkin .figure__frame {
  background: #fff;
  border: var(--ring-w) solid var(--ring-c);
  border-radius: var(--ring-r);
  padding: var(--ring-gap);
}
.page-ems .figure__frame > img,
.page-ems .figure__frame > video,
.page-checkin .figure__frame > img,
.page-checkin .figure__frame > video {
  border-radius: var(--ring-in);
}

/* .figure--bare opts out — the image stands on its own, no ring, and gets a
   fixed 40px of air from the paragraph introducing it. */
.page-ems .figure--bare .figure__frame,
.page-checkin .figure--bare .figure__frame {
  background: transparent;
  border: 0;
  padding: 0;
}
.page-ems .prose + .figure--bare,
.page-checkin .prose + .figure--bare { margin-top: 40px; }

/* Bare image grids draw the same ring with two spread shadows instead.
   Padding on an <img> can't work here: the picture is clipped at the padding
   box, so it would sit square-cornered inside the gap. Shadows follow the
   element's own radius, so the ring stays concentric — and it travels with
   the image on hover rather than the image sliding around inside it. */
.page-checkin .phones img,
.page-checkin .gallery img {
  border-radius: var(--ring-in);
  background: #fff;
  box-shadow: 0 0 0 var(--ring-gap) #fff,
              0 0 0 var(--ring-t) var(--ring-c);
}
/* Shadows take up no space. Pad the grid so the rings sit inside the text
   column instead of bleeding past it, and widen the gaps by two rings so
   neighbours keep their original breathing room. */
.page-checkin .phones,
.page-checkin .gallery { padding: var(--ring-t); }
.page-checkin .phones  { gap: calc(clamp(14px, 2vw, 26px) + 2 * var(--ring-t)); }
.page-checkin .gallery { gap: calc(clamp(12px, 1.6vw, 20px) + 2 * var(--ring-t)); }
/* Narrow media, held to one width so they stay identical on wide screens. */
.figure__frame--inset { max-width: 560px; }
.figure__frame--photo { padding: 0; background: var(--ink); }
.figure__frame--photo > img { border-radius: var(--radius); }
.figure figcaption {
  margin-top: 18px;
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--text);
  max-width: 62ch;
}

.gallery {
  margin: clamp(40px, 6vw, 80px) 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: clamp(12px, 1.6vw, 20px);
  align-items: start;
}
.gallery img {
  width: 100%;
  border-radius: var(--radius-sm);
  transition: transform .6s var(--ease);
}
.gallery img:hover { transform: translateY(-8px); }

/* Numbered core flows */
.flow {
  padding-top: clamp(46px, 7vw, 88px);
  margin-top: clamp(46px, 7vw, 88px);
  border-top: 1.5px solid var(--line);
}
.flow:first-of-type { border-top: 0; margin-top: clamp(14px, 2vw, 24px); padding-top: 0; }
.flow__no {
  display: inline-block;
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: 15px;
  line-height: 1;
  padding: 10px 15px;
  border-radius: 30px;
  background: var(--acc-yellow);
  color: var(--ink);
  margin-bottom: 20px;
}
.flow__title {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(1.3rem, 3.3vw, 2.35rem);
  line-height: 1.12;
  color: var(--text);
  max-width: 22ch;
  margin-bottom: clamp(16px, 2vw, 22px);
}
.flow:nth-of-type(2) .flow__no { background: var(--acc-green); }
.flow:nth-of-type(3) .flow__no { background: var(--acc-blue); color: var(--paper); }
.flow:nth-of-type(4) .flow__no { background: var(--ink); color: var(--paper); }

/* Phone screen rows */
.phones {
  margin-top: clamp(30px, 4.5vw, 52px);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: clamp(14px, 2vw, 26px);
  align-items: start;
}
.phones--pair { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); max-width: 780px; }
.phones img {
  width: 100%;
  border-radius: 26px;
  background: var(--paper);
  box-shadow: 0 24px 50px -28px rgba(0,0,0,.7);
  transition: transform .6s var(--ease);
}
.phones img:hover { transform: translateY(-10px); }

/* Principle chips */
.chips { display: flex; flex-wrap: wrap; gap: 10px; margin-top: clamp(24px, 3vw, 34px); }
.chip {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(.95rem, 1.7vw, 1.15rem);
  padding: 11px 22px;
  border-radius: 30px;
  color: var(--ink);
}
.chip:nth-child(1) { background: var(--acc-yellow); }
.chip:nth-child(2) { background: var(--acc-green); }
.chip:nth-child(3) { background: var(--acc-blue); color: var(--paper); }
.chip:nth-child(4) { background: var(--ink); color: var(--paper); }

/* Next project */
.next {
  margin-top: clamp(80px, 12vw, 160px);
  display: block;
  border-top: 1.5px solid var(--line);
  padding-top: clamp(34px, 5vw, 56px);
}
.next__label { font-size: var(--fs-label); letter-spacing: .2em; text-transform: uppercase; color: var(--text-dim); }
.next__title {
  font-family: var(--display);
  font-weight: var(--display-weight);
  font-size: clamp(1.6rem, 6.4vw, 5rem);
  line-height: 1.06;
  color: var(--text);
  margin-top: 14px;
  transition: color .4s var(--ease), transform .5s var(--ease);
}
.next:hover .next__title { color: var(--acc-purple); transform: translateX(12px); }

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: var(--fs-body);
  color: var(--text-dim);
  transition: gap .35s var(--ease), color .35s var(--ease);
}
.back-link:hover { gap: 14px; color: var(--text); }

/* ============================================================
   Reveal on scroll
   ============================================================ */
.reveal {
  opacity: 0;
  transform: translateY(34px);
  transition: opacity .9s var(--ease), transform .9s var(--ease);
}
.reveal.is-in { opacity: 1; transform: none; }

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 760px) {
  .xp__row { grid-template-columns: 4.6rem 1fr; }
  .xp__org { grid-column: 2; margin-top: -6px; }
  /* Chips take the row, CTA drops below them and holds the right edge. */
  .card__foot { row-gap: 18px; }
  .hero { min-height: auto; padding-top: 132px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
  .hero__title .line > span { transform: none; }
  .mark__text { opacity: 1; }
}
