/* ─────────────────────────────────────────────────────────
   Inaudito — creative & digital product studio
   Ink and impression: a brand is a fingerprint.
   ───────────────────────────────────────────────────────── */

:root {
  /* the nav contraction: one duration and one curve, so nothing arrives early */
  --nav-t: .62s;
  --nav-e: cubic-bezier(.25, .8, .3, 1);

  /* pigment */
  --ink:     #000000;   /* the ground: every dark surface on the site */
  /* The old warm near-black. The light sections derive their text from it, and
     they should keep deriving from a warm base — pure black pulls that copy
     both cooler and heavier (body would go 8.0:1 → 9.8:1, a visible change to
     type nobody asked to change while blackening a background). */
  --ink-warm: #14100E;
  --ink-2:   #1C1613;   /* raised surface */
  --clay:    #924324;   /* brand terracotta */
  --ember:   #C86B3C;   /* lifted clay — the only accent legible as text on ink */
  --bone:    #EDE7DE;   /* the paper the impression lands on */
  --bone-2:  #DDD4C7;
  --text:    #EDE7DE;
  --text-2:  #C4B7AC;
  --ash:     #96887E;
  --line:    #362B25;

  --f-display: 'Archivo', 'Helvetica Neue', system-ui, sans-serif;
  --f-body:    'Newsreader', Georgia, 'Times New Roman', serif;
  --f-hero:    'Lora', Georgia, 'Times New Roman', serif;
  --f-script:  'Caveat', 'Bradley Hand', cursive;

  --wrap: 1240px;
  --gut:  clamp(1.25rem, 5vw, 3.5rem);
  --pad:  clamp(5rem, 11vw, 9.5rem);

  --ease: cubic-bezier(.2, .75, .2, 1);
}

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

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

body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font-family: var(--f-body);
  font-size: clamp(1.0625rem, .3vw + 1rem, 1.1875rem);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }
h1, h2, h3 { margin: 0; font-weight: 400; }
p { margin: 0; }
a { color: inherit; }

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

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 99;
  background: var(--ember); color: var(--ink); padding: .75rem 1.25rem;
  font-family: var(--f-display); font-size: .875rem;
}
.skip:focus { left: 1rem; top: 1rem; }

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gut); }
.section { padding-block: var(--pad); }

/* ── shared type ─────────────────────────────────────────── */

.eyebrow {
  font-family: var(--f-display);
  font-variation-settings: 'wght' 600, 'wdth' 88;
  font-size: .72rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ash);
  margin-bottom: 1.75rem;
}

.h2 {
  font-family: var(--f-hero);
  font-variation-settings: 'wght' 600;
  font-size: clamp(1.9rem, 4.2vw, 3.3rem);
  line-height: 1.06;
  letter-spacing: -.015em;
  max-width: 24ch;
}

/* ── buttons ─────────────────────────────────────────────── */

.btn {
  display: inline-block;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 600, 'wdth' 92;
  font-size: .8125rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 1.05em 1.9em;
  white-space: nowrap;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color .35s var(--ease), color .35s var(--ease),
              border-color .35s var(--ease);
}
.btn--solid { background: var(--clay); color: var(--bone); }
.btn--solid:hover { background: var(--ember); }

.btn[disabled] { opacity: .4; cursor: not-allowed; }

/* ── header ──────────────────────────────────────────────────
   Three separate terracotta pills — brand, links, action — floating
   over a transparent bar, matching the Framer site's nav. Its 5%
   black shadow was built for a light page and vanishes on ink, so
   the depth here is carried at 35%.                              */

.head {
  position: fixed; inset: 0 0 auto; z-index: 50;
  display: flex; flex-direction: column; align-items: center;
  padding-top: 20px;
  pointer-events: none;                 /* only the pills take clicks */
  transition: padding-top var(--nav-t) var(--nav-e);
}
.head__bar {
  width: 100%; max-width: 1260px;
  padding-inline: clamp(12px, 4vw, 30px);
  /* space-between distributes the free space evenly, so the two gaps stay equal
     at every width. Equal side tracks did not: they left the smaller brand pill
     36px more slack than the button, which showed once the row converged. */
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  transition: max-width var(--nav-t) var(--nav-e);
}

.head__mark,
.head__nav,
.head__cta,
.head__burger {
  pointer-events: auto;
  flex: none;                           /* the pills keep their size as the row narrows */
  height: 46px;
  background: var(--clay);
  border-radius: 20px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, .35);
  transition: height var(--nav-t) var(--nav-e), padding var(--nav-t) var(--nav-e),
              box-shadow var(--nav-t) var(--nav-e), background-color .3s var(--ease);
}

.head__mark {
  display: grid; place-items: center;
  padding-inline: 18px;
}
.head__mark img { width: auto; height: 33.6px; transition: height var(--nav-t) var(--nav-e); }

.head__nav {
  display: flex; align-items: center; gap: 6px;
  padding-inline: 6px;
}
.head__nav a {
  display: grid; place-items: center;
  height: 34px;
  padding-inline: 24px;
  border-radius: 16px;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 100;
  font-size: .9375rem;
  letter-spacing: 0;
  white-space: nowrap;
  text-decoration: none;
  color: #fff;                          /* 6.96:1 on the clay */
  transition: height var(--nav-t) var(--nav-e), padding var(--nav-t) var(--nav-e),
              background-color .3s var(--ease);
}
.head__nav a:hover { background: rgba(255, 255, 255, .15); }

.head__cta {
  display: grid; place-items: center;
  padding-inline: 26px;
  border-radius: 21px;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 100;
  font-size: .9375rem;
  white-space: nowrap;
  text-decoration: none;
  color: #fff;
}
.head__cta:hover { background: color-mix(in srgb, var(--clay) 84%, #fff); }

/* contracted: the row draws in and the pills gather, but stop short of touching.
   At 558px they meet edge to edge and the movement reads as a collision; 680
   leaves an even 67px between each pair. */
.head.is-stuck .head__bar { max-width: 680px; }
.head.is-stuck { padding-top: 10px; }
.head.is-stuck .head__mark,
.head.is-stuck .head__nav,
.head.is-stuck .head__cta,
.head.is-stuck .head__burger {
  height: 40px;
  box-shadow: 0 10px 26px rgba(0, 0, 0, .5);
}
.head.is-stuck .head__mark { padding-inline: 16px; }
.head.is-stuck .head__mark img { height: 26.4px; }
.head.is-stuck .head__nav a { height: 30px; padding-inline: 18px; }
.head.is-stuck .head__cta { padding-inline: 20px; }

/* ── hamburger + panel (below 1024px) ────────────────────── */

.head__burger {
  display: none;                        /* desktop-first; the media query turns it on */
  place-items: center;
  width: 46px;
  padding: 0;
  border: 0;
  cursor: pointer;
  transition: height var(--nav-t) var(--nav-e), width var(--nav-t) var(--nav-e),
              background-color .3s var(--ease);
}
.head.is-stuck .head__burger { width: 40px; }

.burger { position: relative; display: block; width: 18px; height: 12px; }
.burger span {
  position: absolute; left: 0; right: 0; height: 2px;
  border-radius: 2px; background: #fff;
  transition: top var(--nav-t) var(--nav-e), transform var(--nav-t) var(--nav-e);
}
.burger span:first-child { top: 0; }
.burger span:last-child  { top: 10px; }
.head.is-open .burger span:first-child { top: 5px; transform: rotate(45deg); }
.head.is-open .burger span:last-child  { top: 5px; transform: rotate(-45deg); }

.menu {
  width: 100%; max-width: 1260px;
  padding-inline: clamp(12px, 4vw, 30px);
  margin-top: 8px;
  pointer-events: auto;
}
.menu[hidden] { display: none; }
.menu__panel {
  background: var(--clay);
  border-radius: 20px;
  box-shadow: 0 14px 34px -10px rgba(0, 0, 0, .6);
  padding: 10px;
  display: grid; gap: 4px;
  transform-origin: top center;
  opacity: 0; transform: translateY(-10px) scale(.98);
  transition: opacity .3s var(--nav-e), transform .3s var(--nav-e);
}
.head.is-open .menu__panel { opacity: 1; transform: none; }

.menu__links { display: grid; gap: 4px; }
.menu__links a {
  display: flex; align-items: center;
  min-height: 48px;                     /* comfortable touch target */
  padding-inline: 16px;
  border-radius: 14px;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 100;
  font-size: 1rem;
  text-decoration: none;
  color: #fff;
  transition: background-color .3s var(--ease);
}
.menu__links a:hover { background: rgba(255, 255, 255, .15); }

.menu__cta {
  display: flex; align-items: center; justify-content: center;
  min-height: 48px;
  margin-top: 6px;
  border-radius: 14px;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 600, 'wdth' 100;
  font-size: 1rem;
  text-decoration: none;
  /* bone fill: a terracotta button inside a terracotta panel would vanish,
     and this is the one action the whole page is built to get */
  background: var(--bone);
  color: var(--ink);
  transition: background-color .3s var(--ease);
}
.menu__cta:hover { background: color-mix(in srgb, var(--bone) 86%, var(--ink)); }

/* Below 1024px the three pills merge into one wide rounded bar, inset 24px from
   every edge so it floats just inside the viewport. The terracotta moves off the
   individual items onto the row itself; the logo and burger become transparent
   items inside it.

   These selectors carry .is-stuck alongside the plain ones on purpose. The
   contraction rules defined earlier — max-width 680px, padding-top 10px — are
   more specific, and without matching them here they would pull the bar inward
   and upward the moment the page scrolls. */
@media (max-width: 1023px) {
  .head, .head.is-stuck { padding: 24px 24px 0; }

  .head__bar,
  .head.is-stuck .head__bar {
    pointer-events: auto;
    max-width: none;
    padding-inline: 24px;
    height: 72px;
    border-radius: 30px;
    background: var(--clay);
    box-shadow: 0 12px 28px -6px rgba(0, 0, 0, .45);
  }
  .head.is-stuck .head__bar { box-shadow: 0 14px 32px -6px rgba(0, 0, 0, .6); }

  .head__mark, .head__burger,
  .head.is-stuck .head__mark, .head.is-stuck .head__burger {
    height: 100%;
    width: auto;
    padding: 0;
    background: none;
    border-radius: 0;
    box-shadow: none;
  }
  .head__mark img, .head.is-stuck .head__mark img { height: 33.6px; }

  .head__nav, .head__cta { display: none; }
  .head__burger { display: grid; }

  /* the panel floats below on the same margins, matching the bar's radius */
  .menu { max-width: none; padding-inline: 24px; margin-top: 10px; }
  .menu__panel {
    border-radius: 28px;
    padding: 12px 12px 16px;
    box-shadow: 0 16px 32px -8px rgba(0, 0, 0, .55);
  }
  .menu__links a { padding-inline: 12px; }   /* 12 + 12 lands on the logo's 24px */
  .menu__cta { margin-inline: 12px; }
}

@media (min-width: 1024px) {
  .menu { display: none; }
}

/* ── hero ────────────────────────────────────────────────── */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex; align-items: center;
  padding-block: clamp(6.5rem, 18vh, 11rem) clamp(1.5rem, 6vh, 5rem);
  overflow: hidden;
}
/* The photograph's own ground samples at #19110E. That used to be near-identical
   to --ink; against pure black it now reads slightly lighter, which is why the
   bottom fade below matters — it ramps over 24% of the height, long enough that
   the photo still dissolves into the page rather than meeting it at an edge. */
.hero__bg { position: absolute; inset: 0; z-index: 1; overflow: hidden; pointer-events: none; }
.hero__bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 70% 48%;
}
.hero::after {
  content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  /* The photograph under the headline is already almost black — measured at
     0.0085 to 0.0123 luminance — so bone type clears 13.7:1 on it with no
     overlay at all. The old ramp held 70% ink out to 55% of the width, which
     bought no legibility and cost the paper plane 70% of its brightness. This
     one is gone by 66%, where the plane is, and still leaves the headline
     above 15:1. The bottom fade stays: the photo's own ground is #19110E, and
     that is what dissolves it into the black page below. */
  background:
    linear-gradient(96deg,
      color-mix(in srgb, var(--ink) 62%, transparent) 18%,
      color-mix(in srgb, var(--ink) 20%, transparent) 46%,
      transparent 66%),
    linear-gradient(to top, var(--ink) 2%, transparent 26%);
}
.hero__in {
  position: relative; z-index: 3;
  width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gut);
}

.hero__title {
  font-family: var(--f-hero);
  /* 2.4em on a phone (~41px) ramping to 3.4em from ~1200px up (~65px).
     em, so it still tracks body's own fluid base. */
  font-size: clamp(2.4em, 5.4vw, 3.4em);
  line-height: 1.28;
  letter-spacing: 0;
  margin-bottom: 2rem;
  /* the first line reaches the plane's wing tip; a faint ink halo keeps it clean */
  text-shadow: 0 0 24px color-mix(in srgb, var(--ink) 78%, transparent);
}
.hero__title .line {
  display: block;
  font-variation-settings: 'wght' 600;
}
/* Caveat carries a much smaller x-height, so 36px would read as a size drop.
   Set optically instead — change the multiplier, not the 36px above. */
.hero__title .line--em {
  font-family: var(--f-script);
  font-size: 1.45em;
  line-height: 1.05;
  color: var(--ember);
}

.hero__lede {
  /* 608px at 20px puts it on exactly two lines, breaking after "team —".
     The line needs 589px; the break only changes past 623px, so there is
     real margin on both sides. Ends at ~52% of a 1440px viewport, stopping
     just short of the paper plane. */
  max-width: 38rem;
  color: var(--text-2);
  font-size: clamp(1.0625rem, .35vw + 1rem, 1.25rem);
  line-height: 1.55;
  margin-bottom: 2.5rem;
}
.hero__act { display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap; }

/* ── marquee ─────────────────────────────────────────────────
   Two identical groups inside one track, shifted by exactly -50%,
   which is one group's width — so the loop has no seam. Swapping
   the spans for <img> logos needs no change here. */

.marquee {
  margin-top: clamp(1rem, 2.6vh, 2rem);
  padding-top: 20px;          /* the extra air above the strip */
  /* no width of its own: it fills .hero__in, so it starts and ends exactly
     where the headline and the lede do, clipped by the same gutters */
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
}
.marquee__track {
  display: flex;
  width: max-content;
  animation: marquee var(--duration, 43s) linear infinite;
  will-change: transform;
}
.marquee:hover .marquee__track,
.marquee:focus-within .marquee__track { animation-play-state: paused; }

.marquee__group { display: flex; align-items: center; flex: none; }

/* the padding lives inside the item, so the groups tile evenly and the
   seam spaces like every other join */
.marquee__item {
  display: inline-flex; align-items: center;
  height: 48px;
  /* All the spacing is padding-right now that the dot is gone. It has to live
     inside the item, not as a gap on the group: the loop shifts by exactly one
     group's width, so a gap between items would leave the seam a join short. */
  padding-right: 4.8rem;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 92;
  font-size: 1.5rem;
  letter-spacing: .085em;     /* bigger type needs less tracking to read as a set */
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ash);
}

/* 40px is 20% of the source file's 200px, and normalises every logo to one
   optical height regardless of what it was exported at */
.marquee__item img { height: 40px; width: auto; display: block; }

@keyframes marquee { to { transform: translateX(var(--shift, -50%)); } }

/* ── project gallery ─────────────────────────────────────────
   One card component at every breakpoint; only the grid changes.
   Source order is 1-6 and the desktop grid fills rows across, so
   the columns come out as left 1,4 · centre 2,5 · right 3,6 with
   no reordering. */

.gallery__head { margin-bottom: clamp(3rem, 6vw, 5rem); }   /* deliberately wider than the card gap */

.gallery__grid {
  display: grid;
  align-items: start;          /* without this the row stretches cards and aspect-ratio is ignored */
  gap: clamp(.45rem, .84vw, .6rem);
  grid-template-columns: 1fr;
}

.pj {
  position: relative;
  overflow: hidden;
  /* square rather than portrait: every source so far is landscape, and a 4/5
     card showed only 53% of their width against 67% here (78% for the 1.28 one).
     The desktop step re-derives from this on its own — the centre column is 18%
     wider, so it stays 18% taller whatever the ratio. */
  aspect-ratio: 1 / 1;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--ink-2);
  /* the only thing the card itself does is warm its border, over 700ms */
  transition: border-color .7s cubic-bezier(.4, 0, .2, 1);
}
.pj:hover { border-color: color-mix(in srgb, var(--clay) 50%, transparent); }
.pj__img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  filter: grayscale(1);
  opacity: .9;
}

/* The zoom rides on the images, never on .pj__color itself — scaling that
   container would scale its clip-path with it and the circle would grow along
   with the picture instead of sweeping across it. Both layers share one
   duration and one curve so the reveal's edge never shows a size mismatch. */
.pj__img,
.pj__color img {
  transform: scale(1);
  transition: transform 1s cubic-bezier(.15, .85, .35, 1);
}
.pj:hover .pj__img,
.pj:hover .pj__color img { transform: scale(1.04); }

/* The colour twin sits on top of the grey one and is clipped to a circle of
   zero radius. It opens to 150% from wherever the cursor crossed the card's
   edge — the JS writes that point into --reveal-x/y on enter — so the colour
   floods outward from the place you arrived, over 2.8s. */
.pj__color { position: absolute; inset: 0; z-index: 1; }
.pj__color,
.pj.is-revealed .pj__color { transition: clip-path 2.8s cubic-bezier(.15, .85, .35, 1); }
.pj__color { clip-path: circle(0% at var(--reveal-x, 50%) var(--reveal-y, 50%)); }
.pj.is-revealed .pj__color { clip-path: circle(150% at var(--reveal-x, 50%) var(--reveal-y, 50%)); }
.pj__color img { width: 100%; height: 100%; object-fit: cover; display: block; }

.pj__veil {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(to top, rgba(0, 0, 0, .2), transparent);
  opacity: 0;
  transition: opacity .7s cubic-bezier(.4, 0, .2, 1);
}
.pj:hover .pj__veil { opacity: 1; }

/* four architectural marks, top corners only, arriving a beat after the rest */
.pj__corner { position: absolute; top: 0; z-index: 3; pointer-events: none;
  opacity: 0; transition: opacity .7s cubic-bezier(.4, 0, .2, 1) .1s; }
.pj__corner--l { left: 0; }
.pj__corner--r { right: 0; }
.pj__corner::before,
.pj__corner::after { content: ''; position: absolute; top: 0;
  background: color-mix(in srgb, var(--bone) 30%, transparent); }
.pj__corner::before { width: 32px; height: 1px; }
.pj__corner::after  { width: 1px; height: 32px; }
.pj__corner--l::before, .pj__corner--l::after { left: 0; }
.pj__corner--r::before, .pj__corner--r::after { right: 0; }
.pj:hover .pj__corner { opacity: 1; }

/* until the real files land, the card shows the name it is waiting for */
.pj__slot { display: none; }
.pj.is-empty { display: grid; place-items: center;
  outline: 1px dashed color-mix(in srgb, var(--ember) 28%, transparent); outline-offset: -12px; }
.pj.is-empty .pj__img,
.pj.is-empty .pj__color { display: none; }
.pj.is-empty .pj__slot {
  display: block;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 88;
  font-size: .72rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ash);
}

.pj__btn {
  position: absolute; left: 50%; bottom: clamp(1rem, 2vw, 1.5rem);
  z-index: 4;
  transform: translate(-50%, 32px);
  opacity: 0;
  transition: transform .7s cubic-bezier(.4, 0, .2, 1),
              opacity .7s cubic-bezier(.4, 0, .2, 1),
              background-color .35s var(--ease), border-color .35s var(--ease);
  width: 88%;
  display: flex; align-items: center; justify-content: center; gap: .55rem;
  padding: .95rem 1.3rem;
  border-radius: 999px;
  border: 1px solid rgba(237, 231, 222, .3);
  background: color-mix(in srgb, var(--ink) 45%, transparent);
  backdrop-filter: blur(12px) saturate(1.2);
  color: var(--bone);
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 96;
  font-size: .8125rem; letter-spacing: .05em;
  text-decoration: none; white-space: nowrap;
}
.pj__btn svg {
  width: 13px; height: 13px; flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.pj:hover .pj__btn,
.pj:focus-within .pj__btn { transform: translate(-50%, 0); opacity: 1; }
.pj__btn:hover {
  background: color-mix(in srgb, var(--clay) 76%, transparent);
  border-color: var(--ember);
}

@media (min-width: 768px) {
  .gallery__grid { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 1280px) {
  /* .wrap caps at 1128px of content from 1240px up, so every figure below is a
     constant: 337px sides against a 398px centre, 18% larger.

     All six cards share the same 4/5 ratio, so a column 18% wider gives a card
     18% taller. The step is entirely a consequence of that — nothing is
     translated and nothing carries an offset. */
  .gallery__grid {
    grid-template-columns: 1fr 1.18fr 1fr;
    gap: 10px;
    /* The gallery steps outside the 1128px text column. Tightening the gap alone
       only bought 8px a card; the width is where the room actually is — 405px
       sides here against 337px before. The `100vw - 96px` floor keeps a real
       margin at every width, so it never reaches the viewport edges. */
    width: min(1320px, 100vw - 96px);
    margin-inline: calc(50% - min(1320px, 100vw - 96px) / 2);
  }

  /* The rows size themselves to the taller centre card, and the alignment does
     the rest: row one hangs from its bottom edge, row two sits on its top edge.
     So card 2 starts 76px above cards 1 and 3 and all three finish level, then
     cards 4, 5 and 6 all begin level and card 5 runs 76px past the other two.
     The vertical gap comes out at 28px in all three columns, and the grid box
     contains the whole composition — no margin compensation needed. */
  .pj:nth-child(-n + 3) { align-self: end; }     /* first row: same bottom */
  .pj:nth-child(n + 4)  { align-self: start; }   /* second row: same top   */
}

/* ── philosophy ──────────────────────────────────────────────
   Framer's header block is 560px wide, centred, with 12px between
   the three lines and 64px down to the cards; the cards sit in a
   row with 16px between them. Same proportions here. */

.phil__head {
  /* the title needs 831px for its balanced two-line break at 52.8px; 40rem left
     it 191px short and it fell to three. The lede keeps its own 34rem measure. */
  max-width: 54rem;
  margin-inline: auto;
  text-align: center;
  display: grid; gap: .75rem;
}
.phil__head .eyebrow { margin-bottom: 0; }   /* the grid gap does the spacing */

.phil__title {
  font-family: var(--f-hero);
  font-variation-settings: 'wght' 600;
  font-size: clamp(1.9rem, 4.2vw, 3.3rem);      /* the shared h2 size */
  /* 1.45 so the line carrying Caveat (1.52em tall) matches the Latin ones to
     within 0.02em. Tighter would need the cursive at line-height .78-.91, and
     Caveat's own glyphs stand 1.26em tall — it would collide with the line above. */
  line-height: 1.45;
  letter-spacing: -.015em;
  text-wrap: balance;
}
.phil__title em {
  /* Caveat, the same cursive voice as the hero — never the body serif's italic.
     1.52em rather than the 1.31em that matches x-height: a handwriting face reads
     smaller than its x-height implies, and 1.16x over the metric value is the
     correction that proved right in the hero. */
  font-family: var(--f-script);
  font-style: normal;
  font-variation-settings: 'wght' 500;
  font-size: 1.45em;
  line-height: 1;
  color: var(--ember);
}
.phil__lede { color: var(--ash); max-width: 34rem; margin-inline: auto; }

.phil__cards {
  margin-top: clamp(3rem, 6vw, 4rem);
  display: grid; gap: 1rem;
  grid-template-columns: 1fr;
}
/* ── philosophy cards ────────────────────────────────────────
   Two identical stacks of tags. The base is masked with a hole
   where the lens sits; the lit copy is clipped to that same
   circle and scaled from the lens point, so the loupe genuinely
   magnifies what is under it rather than swapping in a variant. */

.pcard {
  background: var(--ink-2);
  border: 1px solid var(--line);
  display: flex; flex-direction: column;
}
.pcard__visual {
  --lx: 0px; --ly: 0px; --r: 29px;   /* just inside the glass */
  position: relative;
  height: clamp(180px, 22vw, 220px);
  overflow: hidden;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--ink) 78%, var(--ink-2));
  touch-action: none;
}
.pcard__layer {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: center; gap: 10px;
}
.pcard__layer--base {
  -webkit-mask-image: radial-gradient(circle var(--r) at calc(50% + var(--lx)) calc(50% + var(--ly)), transparent 99%, #000 100%);
          mask-image: radial-gradient(circle var(--r) at calc(50% + var(--lx)) calc(50% + var(--ly)), transparent 99%, #000 100%);
}
.pcard__layer--lit {
  clip-path: circle(var(--r) at calc(50% + var(--lx)) calc(50% + var(--ly)));
  pointer-events: none;
}
.pcard__zoom {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: center; gap: 10px;
  transform: scale(1.35);
  transform-origin: calc(50% + var(--lx)) calc(50% + var(--ly));
}

.pcard__row { display: flex; gap: 10px; width: max-content; }
.pcard__visual.is-live .pcard__row:nth-child(odd)  { animation: prow-l 28s linear infinite; }
.pcard__visual.is-live .pcard__row:nth-child(even) { animation: prow-r 28s linear infinite; }
@keyframes prow-l { from { transform: translateX(0) }         to { transform: translateX(-33.333%) } }
@keyframes prow-r { from { transform: translateX(-33.333%) }  to { transform: translateX(0) } }

.tag {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .32rem .72rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 55%, transparent);
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 96;
  font-size: .72rem;
  letter-spacing: .04em;
  color: var(--ash);
  white-space: nowrap;
}
.tag::before {
  content: ''; flex: none;
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--ash);
}
.pcard__layer--lit .tag {
  color: var(--ember);
  border-color: color-mix(in srgb, var(--ember) 40%, transparent);
  background: var(--ink);
}
.pcard__layer--lit .tag::before { background: var(--clay); }

.pcard__lens {
  position: absolute; top: 50%; left: 50%;
  width: 96px; height: 96px;
  margin: -43.5px 0 0 -48px;   /* aligns the glass, not the whole svg, on the reveal */
  translate: var(--lx) var(--ly);
  z-index: 3;
  padding: 0; border: 0; background: none;
  cursor: grab;
  touch-action: none;
  filter: drop-shadow(0 6px 16px rgba(0, 0, 0, .55));
}
.pcard__lens:active { cursor: grabbing; }
.pcard__lens svg { width: 100%; height: 100%; display: block; }

/* the rows run under the card edges rather than stopping at them */
.pcard__visual::before,
.pcard__visual::after {
  content: ''; position: absolute; inset-block: 0; width: 18%;
  z-index: 2; pointer-events: none;
}
.pcard__visual::before { left: 0;  background: linear-gradient(to right, var(--ink-2), transparent); }
.pcard__visual::after  { right: 0; background: linear-gradient(to left,  var(--ink-2), transparent); }

.pcard__body { padding: clamp(1.4rem, 2.4vw, 1.9rem); }
.pcard__body h3 {
  font-family: var(--f-hero);
  font-variation-settings: 'wght' 600;
  font-size: 1.25rem; line-height: 1.2;
  margin-bottom: .8rem;
}
.pcard__body p { color: var(--text-2); font-size: 1rem; line-height: 1.6; }

/* ── philosophy on paper ─────────────────────────────────────
   The only light section besides the case study. Inverting the
   ground is not enough: --ash reaches 3.40:1 on this white and
   --ember 3.70:1, both short of AA, so every text colour here is
   re-derived from --ink and --clay, which sit at 18.8:1 and
   6.8:1. Tokens are scoped, so nothing outside is disturbed. */

.paper {
  /* the card is pure white and the ground steps down to meet it. #FFFEFC has
     one point of headroom to white, so a card "lighter than the background"
     only reads if the background moves — this is a 5.9% luminance lift. */
  --paper:  #FBF8F3;
  --card:   #FFFFFF;
  --panel:  #FBFBFB;   /* sampled from strategic.png and human.png — both
                          grounds are identical, so the tag card matches them */
  --rule:   color-mix(in srgb, var(--ink-warm) 14%, var(--paper));
  --body:   color-mix(in srgb, var(--ink-warm) 74%, var(--paper));   /* 5.4:1 */
  --quiet:  color-mix(in srgb, var(--ink-warm) 60%, var(--paper));   /* 5.0:1 on paper */
  --tag:    color-mix(in srgb, var(--ink-warm) 66%, var(--paper));   /* 5.8:1 on the panel */
  /* the ground colour is untouched; the pattern rides on top of it */
  background-color: var(--paper);
  background-image: repeating-linear-gradient(145deg,
                      #F6F1E8 0 1px,   /* #FCF8F2 sat 0.126% off the ground: invisible */
                      transparent 1px 4px);
  color: var(--ink);
}

.paper .eyebrow { color: var(--clay); }
.phil__title em      { color: var(--clay); }
.phil__lede          { color: var(--quiet); }

.philosophy .pcard          { background: var(--card); border-color: var(--rule); }
.philosophy .pcard__visual  { background: var(--panel); border-bottom-color: var(--rule); }
.philosophy .pcard__visual::before { background: linear-gradient(to right, var(--panel), transparent); }
.philosophy .pcard__visual::after  { background: linear-gradient(to left,  var(--panel), transparent); }
.philosophy .pcard__body p  { color: var(--body); }

.philosophy .tag {
  background: color-mix(in srgb, var(--paper) 70%, transparent);
  border-color: var(--rule);
  color: var(--tag);          /* --quiet would drop to 4.2:1 against the panel */
}
.philosophy .tag::before { background: var(--tag); }

.philosophy .pcard__layer--lit .tag {
  background: var(--paper);
  border-color: color-mix(in srgb, var(--clay) 40%, transparent);
  color: var(--clay);
}
.philosophy .pcard__layer--lit .tag::before { background: var(--clay); }

.philosophy .pcard__lens { filter: drop-shadow(0 8px 16px rgba(20, 16, 14, .28)); }
/* an image card: the loupe magnifying a picture is what a loupe is for, so the
   zoom goes further here than it does over rows of tags */
.pcard--image .pcard__layer,
.pcard--image .pcard__zoom { display: block; }
.pcard--image .pcard__zoom { transform: scale(1.9); }
.pcard__img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 52%;
  display: block;
}

/* The dashed route is baked into the photograph, so it cannot be animated.
   The picture is instead wrapped in an SVG that shares its coordinate system —
   `slice` crops image and overlay together — and a pulse travels the traced
   path toward the arrow. Gated on .is-live so the base and the magnified copy
   run off the same clock; a clone that started later would show a second pulse
   lagging behind the first inside the loupe. */
.pcard__art { display: block; width: 100%; height: 100%; }
.pcard__pulse {
  fill: none;
  stroke: #F0824A;
  stroke-width: 8;
  stroke-linecap: round;
  stroke-dasharray: .09 .91;
  stroke-dashoffset: 1;
  opacity: 0;
  filter: drop-shadow(0 0 7px rgba(240, 130, 74, .65));
}
.pcard__visual.is-live .pcard__pulse {
  opacity: 1;
  animation: pulse 3.6s linear infinite;
}
/* the dash pattern sums to exactly one path length, so 1 and 0 render alike */
@keyframes pulse { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
/* the edge fades exist to soften rows entering and leaving; over a still
   image they would just band the sides */
.pcard--image .pcard__visual::before,
.pcard--image .pcard__visual::after { display: none; }

/* the card shell the component actually describes: a rounded frame with the
   panel nested inside it, lifting on hover. It only works on paper — over ink
   the shadow had nothing to fall on, which is why it was left flat before. */
.philosophy .pcard {
  border-radius: 2rem;
  padding: .5rem;
  box-shadow: 0 2px 4px -2px rgba(20, 16, 14, .06),
              0 18px 40px -20px rgba(20, 16, 14, .22);
  transition: transform .5s var(--ease), box-shadow .5s var(--ease);
}
.philosophy .pcard:hover {
  transform: translateY(-4px);
  box-shadow: 0 3px 6px -2px rgba(20, 16, 14, .08),
              0 30px 58px -22px rgba(20, 16, 14, .30);
}
.philosophy .pcard__visual {
  border-radius: 1.6rem;
  border-bottom: 0;
}
.philosophy .pcard__body { padding: 1.4rem 1.15rem 1.6rem; }

/* one dot or one icon, not both */
.philosophy .tag::before { display: none; }
.tag__i {
  flex: none;
  width: 13px; height: 13px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.pcard__layer--lit .tag__i { stroke: var(--clay); }

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

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

/* ── services ────────────────────────────────────────────── */

/* the disciplines strip: the logo marquee's loop, with chips for items */
.marquee--tags { margin-top: clamp(2rem, 4vw, 3rem); padding-top: 0; }
.marquee--tags .marquee__group { list-style: none; margin: 0; padding: 0; }
/* nothing in it to click or read closely, so it never pauses */
.marquee--tags:hover .marquee__track,
.marquee--tags:focus-within .marquee__track { animation-play-state: running; }
.marquee--tags .marquee__item {
  height: auto;
  padding-right: .75rem;       /* the join, inside the item so the seam matches */
  font-size: inherit; letter-spacing: 0; text-transform: none;
}
.chip {
  display: inline-flex; align-items: center; gap: .6rem;
  padding: .6rem 1.1rem .6rem .95rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, .07);
  color: #fff;
  opacity: .75;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 96;
  font-size: .875rem; letter-spacing: .01em;
  white-space: nowrap;
}
.chip__i {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}

.svc { list-style: none; margin: clamp(3rem, 6vw, 5rem) 0 0; padding: 0; }
.svc__item {
  display: grid; gap: 1.25rem 4rem;
  grid-template-columns: 1fr;
  padding-block: clamp(2.25rem, 4vw, 3.25rem);
  border-top: 1px solid var(--line);
}
.svc__item:last-child { border-bottom: 1px solid var(--line); }
.svc__name {
  font-family: var(--f-hero);
  font-variation-settings: 'wght' 600;
  font-size: clamp(1.85rem, 3.6vw, 2.9rem);
  line-height: 1;
  letter-spacing: -.01em;
}
.svc__head { display: grid; gap: .9rem; align-content: start; }
.svc__kicker {
  margin: 0;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 96;
  font-size: .75rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ember);
}
.svc__main { display: grid; gap: 1.4rem; justify-items: start; }
.svc__body { color: var(--text-2); max-width: 52ch; margin: 0; }

/* the disciplines: filled chips, so they read as labels rather than links */
.svc__tags { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: .5rem; }
.svc__tag {
  padding: .42rem .85rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--clay) 22%, var(--ink-2));
  color: var(--bone);
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 96;
  font-size: .72rem; letter-spacing: .08em; text-transform: uppercase;
  white-space: nowrap;
}

.svc__link {
  display: inline-flex; align-items: center; gap: .55rem;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 96;
  font-size: .8125rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--bone); text-decoration: none;
  transition: color .3s var(--ease);
}
.svc__link span { transition: transform .3s var(--ease); }
.svc__link:hover, .svc__link:focus-visible { color: var(--ember); }
.svc__link:hover span, .svc__link:focus-visible span { transform: translateX(4px); }

/* ── our numbers ─────────────────────────────────────────────
   The floating-icons mechanism, with figures instead of icons:
   scattered absolute placement, a slow independent drift, and
   repulsion from the cursor inside 150px. The component's demo
   floated other companies' logos, which have no place here — and
   for a section called Our Numbers the numbers are the content. */

.numbers__stage {
  position: relative;
  min-height: clamp(28rem, 64vh, 40rem);
  display: grid;
  place-items: center;
}
.numbers__core { max-width: 34rem; text-align: center; display: grid; justify-items: center; gap: 1rem; }
.numbers__core .eyebrow { margin-bottom: 0; }
.numbers__core .btn { margin-top: .75rem; }
.numbers__lede { color: var(--quiet); }

.stat {
  opacity: 0;
  scale: .5;
  transition: opacity .6s cubic-bezier(.22, 1, .36, 1) var(--d),
              scale   .6s cubic-bezier(.22, 1, .36, 1) var(--d);
  position: absolute;
  left: var(--x); top: var(--y);
  /* the repulsion offset rides on top of the centring, and the transition
     is what gives it the spring's settle without a spring */
  translate: calc(-50% + var(--dx, 0px)) calc(-50% + var(--dy, 0px));
  pointer-events: none;
}
/* drift lives on the inner element so it never fights the repulsion above it */
/* fires when the section reaches the viewport, not on page load — the
   entrance is mid-page and would otherwise play to nobody */
.stat.in { opacity: 1; scale: 1; }

.stat__float {
  display: grid; gap: .3rem;
  padding: .9rem 1.3rem;
  border-radius: 24px;
  text-align: center;
  background: color-mix(in srgb, var(--card) 80%, transparent);
  backdrop-filter: blur(12px);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
  box-shadow: 0 20px 25px -5px rgba(20, 16, 14, .16),
              0 8px 10px -6px rgba(20, 16, 14, .16);
  animation: drift var(--dur, 8s) ease-in-out infinite;
}
/* the source's own path: y 0,-8,0,8,0 · x 0,6,0,-6,0 · rotate 0,5,0,-5,0 */
@keyframes drift {
  0%   { transform: translate(0px,  0px) rotate(0deg); }
  25%  { transform: translate(6px, -8px) rotate(5deg); }
  50%  { transform: translate(0px,  0px) rotate(0deg); }
  75%  { transform: translate(-6px, 8px) rotate(-5deg); }
  100% { transform: translate(0px,  0px) rotate(0deg); }
}
.stat__n {
  font-family: var(--f-hero);
  font-variation-settings: 'wght' 600;
  font-size: 1.8rem; line-height: 1;
  color: var(--clay);
}
.stat__l {
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 90;
  font-size: .66rem; letter-spacing: .15em; text-transform: uppercase;
  color: var(--quiet); white-space: nowrap;
}

/* below 900px the chips would land on the copy, so they stop floating and
   simply sit under it — the DOM already has the core first */
@media (max-width: 899px) {
  .numbers__stage { display: block; min-height: 0; }
  .numbers__core { margin-inline: auto; }
  .stat {
    position: static; translate: none;
    display: inline-block; margin: .4rem .3rem 0;
  }
  .stat__float { animation: none; }
  .numbers__stage { text-align: center; }
}

/* ── work — printed on paper ─────────────────────────────── */

.work .eyebrow { color: var(--clay); }

/* ── start / qualification form ──────────────────────────── */

.start__lede { color: var(--text-2); max-width: 40ch; margin-top: 1.5rem; }

.plainly { display: none; max-width: 40ch; }
.plainly a { color: var(--ember); }

.form { position: relative; }
.form__status {
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 92;
  font-size: .78rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ember); min-height: 1.4em; margin-bottom: 1rem;
}

.fields { display: grid; gap: 1rem; grid-template-columns: repeat(2, 1fr); }
.field { display: block; }
.field--full { grid-column: 1 / -1; }
.field__label {
  display: block; margin-bottom: .55rem;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 88;
  font-size: .7rem; letter-spacing: .18em; text-transform: uppercase; color: var(--ash);
}
.field input, .field select, .field textarea {
  width: 100%; padding: .95rem 1rem;
  background: var(--ink-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 0;
  font-family: var(--f-body); font-size: 1rem; line-height: 1.4;
  transition: border-color .3s var(--ease);
}
.field textarea { resize: vertical; }
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--ash); }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--ember); outline: none; }

.form__nav { display: flex; gap: .75rem; margin-top: 2.25rem; }

.done { max-width: 46ch; }
.done p { color: var(--text-2); margin-top: 1.5rem; }

/* ── footer ──────────────────────────────────────────────────
   The curtain: this box occupies a screen of scroll and clips
   what is inside it, so the footer beneath is uncovered rather
   than pushed up. */

.curtain {
  position: relative;
  height: 100svh;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
.cine {
  position: fixed; bottom: 0; left: 0;
  width: 100%; height: 100svh;
  display: flex; flex-direction: column; justify-content: space-between;
  overflow: hidden;
  background: var(--ink);
  color: var(--text);
}

.cine__aurora {
  position: absolute; left: 50%; top: 50%;
  width: 80vw; height: 60vh;
  border-radius: 50%;
  filter: blur(80px);
  background: radial-gradient(circle at 50% 50%,
    color-mix(in srgb, var(--clay) 34%, transparent) 0%,
    color-mix(in srgb, var(--ember) 16%, transparent) 40%,
    transparent 70%);
  animation: aurora 8s ease-in-out infinite alternate;
  pointer-events: none; z-index: 0;
}
@keyframes aurora {
  from { transform: translate(-50%, -50%) scale(1);   opacity: .6; }
  to   { transform: translate(-50%, -50%) scale(1.1); opacity: 1; }
}

/* the wordmark as scenery: outlined, bled off the bottom, fading upward */
.cine__giant {
  position: absolute; bottom: -5vh; left: 50%;
  z-index: 0; pointer-events: none; user-select: none; white-space: nowrap;
  transform: translate(-50%, var(--gy, 45vh)) scale(var(--gs, .4));
  opacity: var(--go, 0);
  filter: blur(var(--gb, 30px));
  font-family: var(--f-display);
  font-variation-settings: 'wght' 900, 'wdth' 112;
  /* "INAUDITO" mide 5.115em en Archivo 900 con este tracking, así que
     18.8vw lo deja al 96% del ancho — a 26vw se salía al 133% */
  font-size: 18.8vw; line-height: .75; letter-spacing: -.05em;
  color: transparent;
  -webkit-text-stroke: 1px color-mix(in srgb, var(--bone) 8%, transparent);
  background: linear-gradient(180deg, color-mix(in srgb, var(--bone) 12%, transparent) 0%, transparent 60%);
  -webkit-background-clip: text; background-clip: text;
}

.cine__core {
  position: relative; z-index: 2;
  flex: 1;
  display: grid; align-content: center;
  gap: clamp(2rem, 4vh, 3rem);
  width: 100%; max-width: var(--wrap); margin-inline: auto;
  padding: clamp(4.5rem, 9vh, 6.5rem) var(--gut) clamp(2.5rem, 5vh, 3.5rem);
}
/* the two halves are also the animation's two stagger steps */
.cine__lead {
  display: grid; gap: 1rem; align-content: start;
  transform: translateY(var(--ty, 340px)) scale(var(--ts, .9));
  opacity: var(--to, 0);
  filter: blur(var(--tb, 26px));
}
.cine__lead .eyebrow { margin-bottom: 0; }
/* the way out for anyone who would rather not fill anything in */
.cine__call { margin-top: .75rem; justify-self: start; font-size: .8rem; }
/* the same blur as the left column: without it, matching progress still reads as
   the form arriving first, because a sharp edge always looks closer than a soft one */
.cine__form {
  transform: translateY(var(--ly, 340px)) scale(var(--fs, .9));
  opacity: var(--lo, 0);
  filter: blur(var(--fb, 26px));
}
@media (min-width: 1040px) {
  .cine__core {
    grid-template-columns: minmax(0, 26rem) minmax(0, 1fr);
    gap: clamp(3rem, 6vw, 6rem);
    align-content: center;
  }
}

/* Below the desktop breakpoint the brief is taller than a viewport, and the
   footer is a fixed 100svh box with overflow hidden — it would cut the form off.
   So the curtain mechanism is desktop-only; elsewhere the footer simply flows. */
/* The brief needs about 690px of height inside a fixed 100svh box. Below that —
   whether because the window is narrow or simply short — the curtain would clip
   the send button, so the footer stops being fixed and just flows. */
@media (max-width: 1039px), (max-height: 719px) {
  .curtain { height: auto; clip-path: none; }
  .cine { position: relative; height: auto; min-height: 100svh; }
  .cine__core { padding-top: 8rem; padding-bottom: 3rem; }
}
/* the metallic wash: bright at the cap line, falling away toward the baseline */
.cine__title {
  font-family: var(--f-hero);
  font-variation-settings: 'wght' 600;
  font-size: clamp(1.9rem, 4.2vw, 3.3rem);   /* the site's shared h2 size */
  line-height: 1.06; letter-spacing: -.02em;
  background: linear-gradient(180deg, var(--bone) 0%, color-mix(in srgb, var(--bone) 38%, transparent) 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  filter: drop-shadow(0 0 20px color-mix(in srgb, var(--bone) 14%, transparent));
}

.pill {
  display: inline-flex; align-items: center; gap: .7rem;
  padding: .95rem 1.7rem;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--bone) 9%, transparent);
  background: linear-gradient(145deg,
    color-mix(in srgb, var(--bone) 4%, transparent) 0%,
    color-mix(in srgb, var(--bone) 1%, transparent) 100%);
  box-shadow: 0 10px 30px -10px rgba(0, 0, 0, .5),
              inset 0 1px 1px color-mix(in srgb, var(--bone) 11%, transparent),
              inset 0 -1px 2px rgba(0, 0, 0, .8);
  backdrop-filter: blur(16px);
  font-family: var(--f-display);
  font-variation-settings: 'wght' 600, 'wdth' 94;
  font-size: .82rem; letter-spacing: .06em;
  color: var(--text-2); text-decoration: none; white-space: nowrap;
}

.pill svg { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor;
            stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.pill:hover {
  border-color: color-mix(in srgb, var(--bone) 22%, transparent);
  background: linear-gradient(145deg,
    color-mix(in srgb, var(--bone) 9%, transparent) 0%,
    color-mix(in srgb, var(--bone) 2%, transparent) 100%);
  color: var(--bone);
}

.cine__bar {
  position: relative; z-index: 2;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 1.5rem;
  padding: 0 var(--gut) 2rem;
  perspective: 800px;
}
.cine__legal {
  font-family: var(--f-display);
  font-variation-settings: 'wght' 600, 'wdth' 90;
  font-size: .66rem; letter-spacing: .18em; text-transform: uppercase; color: var(--ash);
}
.cine__top { width: 48px; height: 48px; padding: 0; justify-content: center; cursor: pointer; }
.cine__top svg { transition: transform .3s var(--ease); }
.cine__top:hover svg { transform: translateY(-5px); }

/* magnetic: JS writes the offsets, these two curves carry them */
.mag {
  transform: translate(var(--mx, 0px), var(--my, 0px))
             rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg))
             scale(var(--ms, 1));
  transform-style: preserve-3d;
  transition: transform .4s cubic-bezier(.2, .7, .2, 1);
}
.mag.is-releasing { transition: transform 1.2s cubic-bezier(.2, 1.5, .35, 1); }

@media (max-width: 720px) {
  .cine__bar { justify-content: center; text-align: center; }
}

/* ── motion ──────────────────────────────────────────────── */

.reveal { opacity: 0; transform: translateY(20px); }
.reveal.in {
  opacity: 1; transform: none;
  transition: opacity .85s var(--ease) var(--d, 0ms), transform .85s var(--ease) var(--d, 0ms);
}

/* the photograph settles as the words rise */
.hero__bg img { opacity: 0; transform: scale(1.055); }
body.ready .hero__bg img {
  opacity: 1; transform: none;
  transition: opacity 1.4s var(--ease), transform 2s cubic-bezier(.16, .84, .24, 1);
}

/* TextEffect, blur preset: every word resolves out of focus, and stays.
   The line itself only gates the first paint — the segments do the work. */
.hero__title .line { opacity: 0; }
.hero__title .seg {
  display: inline-block;
  white-space: pre;
  opacity: 0;
  filter: blur(12px);
}
body.ready .hero__title .line { opacity: 1; }
body.ready .hero__title .seg {
  opacity: 1;
  filter: blur(0px);
  /* Opacity resolves well ahead of the blur, so the line is readable early
     and keeps settling afterwards rather than being illegible for two seconds. */
  transition: opacity 1s cubic-bezier(.22, .61, .36, 1) var(--t),
              filter 1.9s cubic-bezier(.22, .61, .36, 1) var(--t);
}

/* ── breakpoints ─────────────────────────────────────────── */

/* the ellipsis break is content, so it is held rather than left to wrap —
   from 580px, where the long line clears the column by 46px */
@media (min-width: 580px) {
  .hero__title .line { white-space: nowrap; }
}

@media (min-width: 780px) {
  .svc__item { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); }
  
}

@media (min-width: 1000px) {
  .svc__item { grid-template-columns: minmax(0, 22rem) minmax(0, 1fr); }
  
}

@media (max-width: 779px) {
  /* the copy runs the full width here, so the photograph has to sit further back */
  .hero__bg img { object-position: 66% 42%; opacity: .55; }
  .hero::after { background:
    linear-gradient(to bottom, var(--ink) 6%, color-mix(in srgb, var(--ink) 62%, transparent) 44%, var(--ink) 97%); }
}

/* below ~411px "We build relevance" no longer fits on one line at 2.4em.
   Trimming Caveat's optical multiplier keeps the accent line whole without
   shrinking the headline away from the 2.4em asked for. */
@media (max-width: 420px) {
  .hero__title .line--em { font-size: 1.3em; }
}

/* On a phone the copy runs the full column, so left alignment has nothing to
   align against. Centring the hero reads as composed rather than ragged. */
@media (max-width: 640px) {
  .hero .eyebrow,
  .hero__title,
  .hero__lede { text-align: center; }
  .hero__lede { margin-inline: auto; }
  .hero__act { justify-content: center; }
}

@media (max-width: 560px) {
  .fields { grid-template-columns: 1fr; }
  .form__nav { flex-direction: column-reverse; }
  .form__nav .btn { width: 100%; text-align: center; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .hero__bg img { opacity: 1; transform: none; }
  .hero__title .line { opacity: 1; }
  .hero__title .seg { opacity: 1; filter: none; }
  .marquee__track, .cine__aurora, .cine__heart { animation: none !important; }
  .pcard__row, .pcard__pulse, .stat__float { animation: none !important; }
}

/* ── case study ──────────────────────────────────────────────
   The page is an impression: it opens on ink, and at the moment the
   identity appears the ground is pressed onto paper and stays there.
   Every project sets its own --tint-ink / --tint-paper on <body>, so the
   type, grid and grounds stay Inaudito's while the accent is the client's. */

/* One ground for the whole page. `.paper` on <body> hands over the site's
   established pale ground, its texture and its derived text colours; only the
   two aliases the case study's own rules read are added here.

   The ink half is gone, and with it the press's meaning as a change of ground —
   what survives of that moment is the photograph arriving in colour, which was
   always the stronger half of it. */
.cs-page { color: var(--body); --dim: var(--quiet); }
/* --ash is tuned for the ink ground and reaches only 3.24:1 on paper, short of
   AA for type this small; --quiet is the same role re-derived for this ground. */
.cs-page .eyebrow { color: var(--dim); }
.cs__band { background: transparent; }

/* ── hero ────────────────────────────────────────────────── */

.cs__hero { padding-block: clamp(9rem, 20vh, 13rem) clamp(3rem, 8vh, 5rem); }
.cs__title {
  font-family: var(--f-hero);
  font-variation-settings: 'wght' 600;
  font-size: clamp(2.6rem, 7vw, 5.2rem);
  line-height: 1; letter-spacing: -.025em;
  margin: 0 0 1.75rem;
}
.cs__lede {
  font-size: clamp(1.05rem, 1.4vw, 1.3rem);
  line-height: 1.55; color: var(--dim);
  max-width: 40ch; margin: 0 0 clamp(2.5rem, 6vh, 4rem);
}

/* ── PRUEBA: the hero photograph ─────────────────────────────
   A trial, not a decision. The copy and a photograph share a row above
   the meta rule, which stays full width underneath both — so the rule
   still reads as the line that closes the hero rather than as a border
   under the text alone.

   The image takes the row's height rather than setting it: the figure
   drops its aspect ratio at this size and simply stretches, so the
   photograph is exactly as tall as the eyebrow, title and lede together.
   Below 900px there is no second column to stretch into, so the ratio
   comes back and it sits under the copy. The lede's bottom margin moves
   to the grid, or it would make the copy column taller than its text and
   the photograph would overshoot it. */

.cs__hero-grid { display: grid; gap: clamp(1.75rem, 4vw, 3.25rem); margin-bottom: clamp(2.5rem, 6vh, 4rem); }
.cs__hero-grid .cs__lede { margin-bottom: 0; }
.cs__hero-fig {
  position: relative; margin: 0;
  aspect-ratio: 4 / 3;
  border-radius: 4px; overflow: hidden;
  background: color-mix(in srgb, var(--ink-warm) 6%, var(--paper));
  border: 1px solid var(--rule);
}
.cs__hero-fig img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.cs__hero-fig img[hidden] { display: none; }

/* A transparent mark rather than a photograph. It has no edges of its own, so
   the plate it would sit on is removed — no ground, no rule, no corner radius —
   and the paper of the page is its background. `contain` replaces `cover` for
   the same reason: a photograph may be cropped to fill its box, a logotype may
   not, so it is fitted whole and centred in whatever room the row gives it. */
.cs__hero-fig--mark {
  background: none; border: 0; border-radius: 0;
  aspect-ratio: auto;
  min-height: clamp(11rem, 34vw, 16rem);   /* the row it would otherwise collapse to */
}
.cs__hero-fig--mark img { object-fit: contain; }

@media (min-width: 900px) {
  .cs__hero-grid { grid-template-columns: minmax(0, 1fr) minmax(0, .85fr); align-items: stretch; }
  .cs__hero-copy { display: flex; flex-direction: column; justify-content: flex-end; }
  .cs__hero-fig { aspect-ratio: auto; }
  /* Stretched by the row like the photograph, so the mark is centred against the
     full height of the copy rather than setting a height of its own. */
  .cs__hero-fig--mark { min-height: 0; }
}

.cs__meta { display: grid; gap: 1.25rem 2.5rem; margin: 0; border-top: 1px solid var(--rule); padding-top: 1.5rem; }
.cs__meta dt, .cs__facts dt {
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 92;
  font-size: .68rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--dim); margin-bottom: .35rem;
}
.cs__meta dd { margin: 0; font-size: .95rem; }

/* ── the numbered rail ───────────────────────────────────── */

.cs__sec { padding-block: clamp(3.5rem, 9vh, 6.5rem); }
.cs__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.75rem; }
.cs__num {
  display: flex; align-items: center; gap: .6rem; align-self: start;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 92;
  font-size: .78rem; letter-spacing: .12em;
  color: var(--dim); margin: 0;
}
.cs__dot { width: 1.05em; height: 1.05em; flex: none; border: 1px solid currentColor; border-radius: 50%; position: relative; }
.cs__dot::before, .cs__dot::after { content: ''; position: absolute; inset: 0; margin: auto; background: currentColor; }
.cs__dot::before { width: .5em; height: 1px; }
.cs__dot::after  { width: 1px;  height: .5em; }
.cs__main { display: grid; gap: clamp(2rem, 5vh, 3.25rem); min-width: 0; }

/* ── content ─────────────────────────────────────────────── */

.cs__label {
  font-family: var(--f-display);
  font-variation-settings: 'wght' 600, 'wdth' 88;
  font-size: .7rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--tint); margin: 0 0 .7rem;
}
.cs__p { font-size: 1rem; line-height: 1.7; max-width: 52ch; margin: 0; color: inherit; }
.cs__p--wide { max-width: 68ch; }
.cs__pair { display: grid; gap: 2rem; }

.cs__findings { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; counter-reset: f; }
.cs__findings li {
  display: grid; grid-template-columns: 3.5rem minmax(0, 1fr); gap: 1rem;
  padding: 1.5rem 0; border-top: 1px solid var(--rule);
}
.cs__findings li:last-child { border-bottom: 1px solid var(--rule); }
.cs__f-num {
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 92;
  font-size: .78rem; letter-spacing: .1em; color: var(--tint);
}
.cs__findings p { margin: 0; line-height: 1.65; max-width: 62ch; }
.cs__findings em { font-style: italic; }
.cs__verdict {
  font-family: var(--f-hero); font-variation-settings: 'wght' 600;
  font-size: clamp(1.3rem, 2.4vw, 1.9rem); line-height: 1.3;
  max-width: 30ch; margin: 0;
}

/* ── the pillars: what the system decided ────────────────────
   The findings list above is a rail of problems, so a second rail of
   the same shape would read as more of them. These are the answers, and
   four of them pair naturally: two columns from 900px, one below, with
   the rules redrawn per row so a two-column grid still closes. */

.cs__pillars { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.cs__pillars li {
  display: grid; gap: .55rem; align-content: start;
  padding: 1.5rem 0; border-top: 1px solid var(--rule);
}
.cs__pillars li:last-child { border-bottom: 1px solid var(--rule); }
.cs__pil-num {
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 92;
  font-size: .78rem; letter-spacing: .1em; color: var(--tint);
}
.cs__pillars h3 {
  font-family: var(--f-hero); font-variation-settings: 'wght' 600;
  font-size: clamp(1.05rem, 1.7vw, 1.3rem); line-height: 1.25;
  margin: 0; letter-spacing: -.01em;
}
.cs__pillars p { margin: 0; line-height: 1.65; max-width: 48ch; color: var(--dim); }

/* ── the process: three verbs ────────────────────────────────
   Research, translation, review are one sequence rather than three
   independent notes, so they sit side by side on a shared top rule
   and read left to right — the order is the argument. */

.cs__steps { display: grid; gap: 1.75rem; margin: 0; }
.cs__steps > div { padding-top: 1.1rem; border-top: 1px solid var(--rule); }
.cs__steps dt {
  font-family: var(--f-display);
  font-variation-settings: 'wght' 600, 'wdth' 88;
  font-size: .7rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--tint); margin-bottom: .6rem;
}
.cs__steps dd { margin: 0; font-size: .95rem; line-height: 1.65; color: var(--dim); }

/* ── from a component to a sales conversation ────────────────
   The touchpoint list is inventory, not prose: seven short labels that
   want to be scanned. Chips read as a set at a glance where a bulleted
   column would read as seven separate claims. */

.cs__touch { padding-block: clamp(3rem, 8vh, 5.5rem); }
.cs__touch-head {
  font-family: var(--f-hero); font-variation-settings: 'wght' 600;
  font-size: clamp(1.4rem, 3vw, 2.1rem); line-height: 1.2;
  letter-spacing: -.02em; max-width: 24ch; margin: 0 0 1.75rem;
}
.cs__chips { list-style: none; margin: 0 0 clamp(2.5rem, 6vh, 4rem); padding: 0; display: flex; flex-wrap: wrap; gap: .6rem; }
.cs__chips li {
  border: 1px solid var(--rule); border-radius: 999px;
  padding: .5rem 1.05rem;
  background: color-mix(in srgb, var(--ink-warm) 4%, var(--paper));
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 92;
  font-size: .82rem; letter-spacing: .03em;
}

/* The one line of the project anyone would quote back. It carries no
   quotation marks: the rule and the measure already mark it as speech,
   and glyph marks at this size hang off the left edge of the column. */
.cs__quote { margin: 0; border-left: 2px solid var(--tint); padding-left: clamp(1.25rem, 3vw, 2rem); }
.cs__quote p {
  font-family: var(--f-hero); font-variation-settings: 'wght' 600;
  font-size: clamp(1.3rem, 2.6vw, 2rem); line-height: 1.3;
  letter-spacing: -.015em; max-width: 26ch; margin: 0;
}

/* ── the numbers ─────────────────────────────────────────────
   The only section that cannot be written from the design files, and
   on this project it is the one with real figures in it. The number
   takes the tint and the display face so it is legible from across a
   room; the sentence under it says what was measured and where. */

.cs__metrics { display: grid; gap: 0; margin: 0; }
.cs__metrics > div { padding: 1.5rem 0; border-top: 1px solid var(--rule); }
.cs__metrics > div:last-child { border-bottom: 1px solid var(--rule); }
.cs__m-n {
  display: block;
  font-family: var(--f-hero); font-variation-settings: 'wght' 600;
  font-size: clamp(2rem, 4.5vw, 3rem); line-height: 1;
  letter-spacing: -.03em; color: var(--tint); margin-bottom: .5rem;
}
.cs__m-l {
  display: block;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 600, 'wdth' 88;
  font-size: .68rem; letter-spacing: .18em; text-transform: uppercase;
  margin-bottom: .4rem;
}
.cs__m-p { margin: 0; font-size: .88rem; line-height: 1.6; color: var(--dim); max-width: 34ch; }

/* ── the press: ink gives way to paper ───────────────────── */

.cs__press {
  position: relative;
  background: transparent;
  padding-block: clamp(2rem, 6vh, 5rem);
  overflow: hidden;
}
.cs__press-fig { position: relative; margin: 0 auto; width: min(100% - 2 * var(--gut), 1400px); }
.cs__press-fig img {
  display: block; width: 100%; height: auto; border-radius: 4px;
  /* the old brand is the desaturated one; colour arrives as you reach it */
  filter: saturate(calc(.15 + .85 * var(--press, 0)));
}

/* ── the examination ─────────────────────────────────────── */

.cs__exam { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); }
.cs__exam-stage {
  position: relative; aspect-ratio: 3 / 2;
  border-radius: 4px; overflow: hidden; background: var(--rule);
}
.cs__exam-base, .cs__exam-lens img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cs__exam-base { filter: saturate(.35); transition: filter .6s var(--ease); }
.cs__exam.is-live .cs__exam-base { filter: saturate(.28) brightness(.86); }
.cs__exam-lens {
  position: absolute; inset: 0; pointer-events: none;
  clip-path: circle(0px at var(--x, 50%) var(--y, 50%));
  transition: clip-path .55s var(--ease);
}
.cs__exam.is-live .cs__exam-lens { clip-path: circle(var(--r, 13%) at var(--x, 50%) var(--y, 50%)); }
.cs__exam-lens img { transform: scale(2.3); transform-origin: var(--x, 50%) var(--y, 50%); }

.cs__exam-pins { list-style: none; margin: 0; padding: 0; }
.cs__exam-pins button {
  position: absolute; transform: translate(-50%, -50%);
  left: var(--px); top: var(--py);
  width: 2rem; height: 2rem; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--f-display); font-size: .62rem; letter-spacing: .06em;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  color: var(--ink-warm);
  border: 1px solid color-mix(in srgb, var(--ink-warm) 25%, transparent);
  cursor: pointer;
  transition: background .3s var(--ease), color .3s var(--ease), transform .3s var(--ease);
}
.cs__exam-pins button:hover, .cs__exam-pins button:focus-visible,
.cs__exam-pins button[aria-current='true'] {
  background: var(--tint); color: var(--paper);
  transform: translate(-50%, -50%) scale(1.15);
  outline: none;
}

.cs__exam-caps { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; counter-reset: c; }
.cs__exam-caps li {
  padding-left: 1rem; border-left: 2px solid var(--rule);
  opacity: .45; transition: opacity .4s var(--ease), border-color .4s var(--ease);
}
.cs__exam-caps li[aria-current='true'] { opacity: 1; border-left-color: var(--tint); }
.cs__exam-caps h3 {
  font-family: var(--f-hero); font-variation-settings: 'wght' 600;
  font-size: 1.05rem; margin: 0 0 .3rem;
}
.cs__exam-caps p { margin: 0; font-size: .9rem; line-height: 1.6; color: var(--dim); }
.cs__exam-caps em { font-style: italic; }

/* ── outcome ─────────────────────────────────────────────── */

.cs__outcome { display: grid; gap: 0; margin: 0; }
.cs__outcome > div { padding: 1.25rem 0; border-top: 1px solid var(--rule); }
.cs__outcome > div:last-child { border-bottom: 1px solid var(--rule); }
.cs__outcome dt {
  font-family: var(--f-display); font-size: .68rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--tint); margin-bottom: .35rem;
}
.cs__outcome dd { margin: 0; color: var(--dim); }

/* ── other work ──────────────────────────────────────────── */

.cs__next { padding-block: clamp(4rem, 10vh, 7rem); }
.cs__next-head {
  display: flex; flex-wrap: wrap; gap: 1rem 2rem;
  align-items: baseline; justify-content: space-between;
  margin-bottom: clamp(2rem, 5vh, 3rem);
}
.cs__next-all, .cs__next-all:visited {
  font-family: var(--f-display);
  font-variation-settings: 'wght' 600, 'wdth' 92;
  font-size: .78rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--tint); text-decoration: none;
  display: inline-flex; align-items: center; gap: .4rem;
}
.cs__next-all svg { width: 1em; height: 1em; fill: none; stroke: currentColor; stroke-width: 1.5; }
.cs__next-grid { display: grid; gap: clamp(1.25rem, 2.5vw, 2rem); }
.cs__card { text-decoration: none; color: inherit; display: block; }
.cs__card figure {
  margin: 0 0 1rem; aspect-ratio: 4 / 3; overflow: hidden; border-radius: 4px;
  border: 1px solid var(--rule);
}
.cs__card img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* each project answers in its own colour only once you reach for it */
  filter: saturate(.2);
  transition: filter .7s var(--ease), transform .7s var(--ease);
}
.cs__card:hover img, .cs__card:focus-visible img { filter: saturate(1); transform: scale(1.03); }
.cs__card h3 {
  font-family: var(--f-hero); font-variation-settings: 'wght' 600;
  font-size: 1.15rem; margin: 0 0 .25rem;
  transition: color .4s var(--ease);
}
.cs__card:hover h3 { color: var(--tint); }
.cs__card p { margin: 0; font-size: .85rem; color: var(--dim); }

.cs__cta { padding-block: 0 clamp(6rem, 14vh, 9rem); }
.cs__cta .h2 { margin: 0 0 2rem; }

@media (min-width: 700px) {
  .cs__meta { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .cs__pair { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(2rem, 5vw, 4rem); }
  .cs__next-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .cs__exam { grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); align-items: start; }
  .cs__steps { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(1.5rem, 3.5vw, 2.75rem); }
  /* Four numbers across only once each has room for its sentence; below
     this they stack on the same rule pattern the findings use. */
  .cs__metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: clamp(1.5rem, 3.5vw, 2.75rem); }
  .cs__metrics > div:nth-last-child(2) { border-bottom: 1px solid var(--rule); }
  /* The pillars pair off: two columns, and the rule under the last row is
     redrawn on both cells so a two-column grid still closes. */
  .cs__pillars { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: clamp(2rem, 4.5vw, 3.5rem); }
  .cs__pillars li:nth-last-child(2) { border-bottom: 1px solid var(--rule); }
}
@media (min-width: 1040px) {
  .cs__grid { grid-template-columns: minmax(0, 8rem) minmax(0, 1fr); gap: 0 clamp(2rem, 5vw, 4rem); }
  .cs__metrics { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* Back to one row, so only the true last cell carries the closing rule. */
  .cs__metrics > div:nth-last-child(2) { border-bottom: 0; }
}

/* ── the system, in pieces ───────────────────────────────────
   Ported from the supplied HeroCollage. Its seven positions, widths,
   z-order and negative animation delays are kept exactly; what changed
   was the depth model, and is no longer. On the black ground the
   component's drop shadow was invisible and a hairline rim had to stand
   in for it; on the pale ground the shadow works exactly as written, so
   the rim is back to a faint edge and the shadow carries the lift. */

.cs__collage { padding-block: clamp(2rem, 6vh, 4rem) clamp(3rem, 8vh, 6rem); }
.cs__collage-lead {
  font-family: var(--f-hero); font-variation-settings: 'wght' 600;
  font-size: clamp(1.1rem, 1.9vw, 1.45rem); line-height: 1.4;
  color: var(--dim); max-width: 34ch;
  margin: 0 auto clamp(2rem, 5vh, 3.5rem); text-align: center;
}
.collage {
  position: relative;
  width: min(100% - 2 * var(--gut), 1152px);
  margin-inline: auto;
  aspect-ratio: 1152 / 600;      /* the component's own 600px at max width */
}
.tile {
  position: absolute;
  width: var(--w);
  z-index: var(--z);
  margin: 0;
  aspect-ratio: 3 / 2;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--rule);
  background: color-mix(in srgb, var(--ink-warm) 6%, var(--paper));
  transform: var(--t, none);
  box-shadow: 0 25px 50px -12px rgba(20, 16, 14, .25);
}
.tile--c0 { border-radius: 16px; }
.tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tile img[hidden] { display: none; }
.tile .cs__slot {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  padding: .5rem; text-align: center;
  font-family: var(--f-display); font-size: .6rem; letter-spacing: .08em;
  color: var(--dim);
}

/* the component's float, held on the composited property only so seven
   tiles animating at once stay off the layout thread */
@keyframes tile-float {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -15px; }
}
@media (prefers-reduced-motion: no-preference) {
  .tile { animation: tile-float 6s ease-in-out infinite; animation-delay: var(--d); }
}

/* Below the collage's own width the seven positions overlap into mush,
   so the arrangement is abandoned rather than scaled: the same tiles in
   document order, as a plain grid. */
@media (max-width: 900px) {
  .collage {
    aspect-ratio: auto;
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .75rem;
  }
  .tile {
    /* relative, not static: the slot label is absolute inside the tile, and
       against a static parent all seven stack at the collage's own centre */
    position: relative; width: auto; transform: none;
    box-shadow: none; animation: none;
  }
  .tile--c0 { grid-column: 1 / -1; }
}

/* ── the written line ────────────────────────────────────────
   Ported from the supplied HandwritingText. Its three insights are the
   whole reason this reads as a pen rather than a fade, and all three are
   kept: the glyphs are real outlines rather than a web font's filled
   shapes; every contour is its own path, because an SVG dash pattern
   restarts at each subpath and one path holding the word could only
   switch letters on and off; and the weight comes from a single filled
   copy of the whole word underneath, so the counters in the e's and a's
   stay holes instead of blobbing shut.

   What changed is when the work happens. The component parses the TTF in
   the browser because it supports words that cycle. This line never
   changes, so the parsing was done once at authoring time — the outlines
   and each contour's measured length are already in the markup. That
   drops opentype.js and the 400KB font file from the page, and leaves an
   effect that needs no JavaScript at all to run. Contours are sorted by
   their leftmost point, so the pen genuinely crosses the word left to
   right rather than following the font's own contour order. */

.hero__title .line--em { display: block; }
.ink {
  display: block;
  /* .line--em already scales the font to 1.45em, so this em is that em: the
     phrase's own ink extent is .947 of Caveat's em box, and nothing more. */
  /* Width-led rather than height-led: on a narrow column the box shrinks with
     the drawing instead of clamping the width and leaving slack above and below. */
  width: min(100%, calc(.947em * var(--ratio)));
  height: auto;
  aspect-ratio: var(--ratio);
  overflow: visible;
  color: var(--ember);
}
.ink__f { fill: currentColor; stroke: none; opacity: 0; }
.ink__s {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
}
@keyframes ink-draw { to { stroke-dashoffset: 0; } }
@keyframes ink-fill { to { opacity: 1; } }

body.ready .ink__s { animation: ink-draw var(--each) ease-out var(--d) forwards; }
body.ready .ink__f { animation: ink-fill 0.55s ease-out var(--fill-at) forwards; }

/* Without motion the line is simply written already — an outline that never
   inks in would read as unfinished rather than as restrained. */
@media (prefers-reduced-motion: reduce) {
  .ink__f { opacity: 1; }
  .ink__s { stroke-dashoffset: 0; }
  body.ready .ink__s, body.ready .ink__f { animation: none; }
}

/* Names the file a slot is waiting for. The base rule was dropped when the case
   study was renamed from .case__* to .cs__*; only the collage's smaller override
   came across, which hid the gap until a second gallery used the class. */
.cs__slot {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  padding: 1rem; text-align: center;
  font-family: var(--f-display);
  font-size: .72rem; letter-spacing: .1em;
  color: var(--dim);
}

/* ── every surface: the pinned column ────────────────────────
   Ported from the supplied sticky-scroll component. The mechanic is the
   whole of it and it is pure CSS: three columns, the outer two scrolling
   at their natural height and the middle one pinned for as long as they
   take to pass. Nothing measures anything — `position: sticky` on a
   viewport-tall column is the entire effect.

   Lenis is not installed. It replaces native scrolling with a JS-driven
   emulation, and two things on this site already read the real scroll
   position: the press's saturation and the header's direction toggle.
   Handing that number to a library that interpolates it would put both
   slightly out of step with the page, and sticky needs none of it. */

.cs__sticky { padding-block: clamp(2rem, 5vh, 4rem); }
.sticky {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: .5rem;
  width: min(100% - 2 * var(--gut), 1400px);
  margin-inline: auto;
}
.sticky__col { display: grid; gap: .5rem; align-content: start; min-width: 0; }
.sticky__fig {
  position: relative;
  margin: 0;
  aspect-ratio: 4 / 3;
  border-radius: 6px;
  overflow: hidden;
  background: color-mix(in srgb, var(--ink-warm) 6%, var(--paper));
  border: 1px solid var(--rule);
}
.sticky__fig img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  transition: transform .3s var(--ease);
}
.sticky__fig img[hidden] { display: none; }

@media (min-width: 900px) {
  .sticky { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; }
  /* the outer columns run at the component's own 384px per image */
  .sticky__col:not(.sticky__col--pin) .sticky__fig { aspect-ratio: auto; height: 384px; }
  /* pinned for the length of its neighbours, three rows filling the screen */
  .sticky__col--pin {
    position: sticky; top: 0;
    height: 100svh;
    align-content: stretch;
    grid-template-rows: repeat(3, minmax(0, 1fr));
  }
  .sticky__fig--tall { aspect-ratio: auto; height: 100%; }
}


/* ── the pieces ──────────────────────────────────────────────
   Ported from the supplied Tailwind gallery. Kept: the gradient-and-title
   that rises on hover, and click-to-enlarge. Changed: the ground, since
   this page is paper rather than the component's gray-900; the column
   arrangement, which Corina specified as 1 / 35-65 / 4; and the hover sits
   behind
   `@media (hover: hover)` — on a touch screen there is no hover state to
   reveal a caption, so the caption is simply always there. */

.cs__plates { padding-block: clamp(1rem, 3vh, 2rem) clamp(2rem, 6vh, 4rem); }
/* The plates ran inside .wrap, which is the text column's box — 1240px including
   its gutters, so 200px narrower than the pinned gallery below at a 1440 viewport.
   Galleries on this page share one measure, and it is not the reading measure. */
.cs__plates .wrap {
  max-width: none;
  width: min(100% - 2 * var(--gut), 1400px);
  padding-inline: 0;
  margin-inline: auto;
}
/* Three rows, each with its own columns, rather than one grid of many columns
   with spans. On a single 20-column grid a plate spanning 7 also swallows six
   internal gaps, so 7/20 lands at 34.5% rather than 35% — the gaps do not divide
   in proportion. `35fr 65fr` on a row of its own is exact at any width. */
.plates { display: grid; gap: 1rem; }
.plates__row { display: grid; gap: 1rem; grid-template-columns: minmax(0, 1fr); }

.plate {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: 8px;
  overflow: hidden;
  margin: 0;
  background: color-mix(in srgb, var(--ink-warm) 6%, var(--paper));
  border: 1px solid var(--rule);
}
.plate > button {
  all: unset;
  display: block; position: absolute; inset: 0;
  cursor: zoom-in;
}
.plate > button:focus-visible { outline: 2px solid var(--tint); outline-offset: -3px; }
.plate img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* `display: block` above outranks the UA's `[hidden]` rule, so a plate waiting
   on its photograph rendered a broken-image icon over its own slot label. The
   sticky column and the collage tiles already carried this line; the plates
   never needed it until a case study opened with six empty ones. */
.plate img[hidden] { display: none; }

.plate__cap {
  position: absolute; inset: 0;
  display: flex; align-items: flex-end;
  padding: 1rem;
  background: linear-gradient(to top, rgba(0, 0, 0, .72), transparent 55%);
  color: #fff;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 500, 'wdth' 92;
  font-size: .82rem; line-height: 1.35;
}
/* A touch screen has no hover to reveal a caption with, so there it simply
   stays; only where hovering exists does it wait to be asked for. */
@media (hover: hover) {
  .plate__cap {
    opacity: 0;
    transform: translateY(1rem);
    transition: opacity .3s var(--ease), transform .3s var(--ease);
  }
  .plate > button:hover .plate__cap,
  .plate > button:focus-visible .plate__cap { opacity: 1; transform: translateY(0); }
}

@media (min-width: 640px) {
  .plates__row--split { grid-template-columns: 35fr 65fr; }
  /* Three into two columns leaves an orphan at half width, so at this size the
     last one takes the full row instead — 2 over 1, rather than 2 and a stub. */
  .plates__row--three { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .plates__row--three > :last-child { grid-column: span 2; }

  /* The banner is far wider than it is tall, so 4/3 would make it enormous. In
     the split row the narrow plate sets the row height and the wide one stretches
     to it, so both match at any width without either ratio being a guess. */
  .plate--full { aspect-ratio: 2 / 1; }
  .plates__row--split .plate:last-child { aspect-ratio: auto; height: 100%; }
}
@media (min-width: 900px) {
  .plates__row--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .plates__row--three > :last-child { grid-column: auto; }
}

/* ── the enlarged view ───────────────────────────────────── */

.lightbox {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center;
  background: rgba(0, 0, 0, .8);
  padding: clamp(1rem, 4vw, 3rem);
  cursor: zoom-out;
}
.lightbox[hidden] { display: none; }
.lightbox img {
  max-width: 90vw; max-height: 90vh;
  width: auto; height: auto;
  border-radius: 8px;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, .5);
  cursor: default;
}
.lightbox__close {
  all: unset;
  position: absolute; top: 1.25rem; right: 1.5rem;
  font-size: 2.4rem; line-height: 1; color: #fff;
  cursor: pointer; padding: .25rem .6rem; border-radius: 6px;
}
.lightbox__close:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* ── in their words ──────────────────────────────────────────
   Ported from the supplied testimonials marquee. The scroll itself is
   the site's existing `.marquee`, reused: it already clones the group,
   measures it and derives its duration, and it already pauses on hover
   and fades its edges with a mask rather than the component's two
   absolutely-positioned gradients — a mask works on any ground, and
   this section sits on paper while the other marquee sits on ink.

   The card keeps the component's shape: a top rule, a gradient falling
   away downward, 320px wide, and a lift on hover. The avatar does not
   survive — there are no photographs of the people, and a stock face
   beside a real client's name would be a lie about who said it. A
   monogram in the client's own colour stands in. */

/* The portal opens directly below, and its word is centred inside its own
   viewport-tall pin — so all the air between the two is this padding. */
.voices { text-align: center; padding-bottom: clamp(1.5rem, 3vh, 3rem); }
.voices__head { display: grid; justify-items: center; gap: 1.25rem; }
.voices__title { max-width: 24ch; margin: 0; }
.voices__lede {
  max-width: 46ch; margin: 0;
  font-size: clamp(1rem, 1.2vw, 1.1rem); line-height: 1.6;
  color: var(--quiet);
}

/* the hero's marquee carries its own spacing and a tighter mask; this one
   needs neither, so the modifier unsets rather than overrides piecemeal */
.marquee--voices {
  margin-top: clamp(2.5rem, 6vh, 4rem);
  padding-top: 0;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}
.marquee--voices .marquee__group { align-items: stretch; gap: 1rem; padding-right: 1rem; }

.voice {
  flex: none;
  width: 320px;
  display: flex; flex-direction: column;
  text-align: left;
  padding: clamp(1.1rem, 2vw, 1.5rem);
  border-radius: 10px;
  border-top: 1px solid color-mix(in srgb, var(--ink-warm) 16%, var(--paper));
  background: linear-gradient(to bottom,
                color-mix(in srgb, var(--ink-warm) 7%, var(--paper)),
                color-mix(in srgb, var(--ink-warm) 2%, var(--paper)));
  transition: background .3s var(--ease);
}
.marquee--voices .voice:hover {
  background: linear-gradient(to bottom,
                color-mix(in srgb, var(--ink-warm) 11%, var(--paper)),
                color-mix(in srgb, var(--ink-warm) 4%, var(--paper)));
}
.voice__who { display: flex; align-items: center; gap: .8rem; }
.voice__mark {
  width: 2.75rem; height: 2.75rem; flex: none;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--voice, var(--quiet));
  color: var(--paper);
  font-family: var(--f-hero);
  font-variation-settings: 'wght' 600;
  font-size: 1.15rem; line-height: 1;
}
.voice__name {
  font-family: var(--f-hero); font-variation-settings: 'wght' 600;
  font-size: 1rem; line-height: 1.2; margin: 0 0 .2rem;
}
.voice__sector { margin: 0; font-size: .78rem; color: var(--quiet); line-height: 1.3; }
/* deliberately not shaped like a quote: this is scaffolding, and a visitor
   should never have to work out whether it is one */
.voice__awaiting {
  margin: 1.1rem 0 0;
  font-family: var(--f-display);
  font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--quiet);
}

@media (max-width: 520px) { .voice { width: 15.5rem; } }

/* ── the portal ──────────────────────────────────────────────
   Glyph Portal © 2026 Christian Katzmann. MIT. Ported from React to
   this page's own idiom; the notice travels with the port.

   How the scroll length exists at all: the pin is sticky and one
   viewport tall, and the content below it carries a top margin of
   (length − 1) viewports. That margin is the only thing making the
   section taller than the screen, so it is the scroll budget — the
   camera reads its progress from how far the section's top has gone
   past the viewport's. */

.portal {
  /* Scroll travel, in viewport heights, and the only place it is written: the
     spacer below and the camera in script.js both read this. Two copies would
     drift, and a drift here means the camera finishes at the wrong moment. */
  --portal-len: 1.4;
  position: relative;
  isolation: isolate;
  /* The word sits on the pin's floor, 40px + 10% of the screen above the footer (script.js). The
     empty band above it is --portal-lift tall: the section slides up by that
     much under the one before, and the band stays unpainted and unclickable so
     that section still shows and works through it. */
  margin-top: calc(-1 * var(--portal-lift, 0px));
  background-color: transparent;
  background-image: repeating-linear-gradient(145deg, #F6F1E8 0 1px, transparent 1px 4px),
                    linear-gradient(var(--paper), var(--paper));
  background-repeat: no-repeat;
  background-position: 0 var(--portal-lift, 0px);
  background-size: 100% calc(100% - var(--portal-lift, 0px));
  pointer-events: none;
}
/* a 100svh probe: browser chrome resizing the viewport must not keep
   changing the scroll distance underneath the camera */
.portal__probe {
  position: absolute; inset: 0 auto auto 0;
  height: 100svh; width: 0;
  pointer-events: none; visibility: hidden;
}
.portal__pin {
  position: relative;
  height: var(--portal-h, 100svh);
  overflow: clip;
  isolation: isolate;
}
.portal[data-portal-motion='on'] .portal__pin { position: sticky; top: 0; }

/* the field is only ever seen through the letterforms */
.portal__field {
  position: absolute; inset: 0;
  opacity: 0;
  pointer-events: none;
  /* Black, so the word reads as ink on paper — the site's own metaphor — and so
     the moment the camera is inside the letter the screen already matches the
     footer it is about to hand over to. The warm tint keeps it from going flat
     at full screen without ever becoming a colour. */
  background:
    radial-gradient(circle at 30% 18%, color-mix(in srgb, var(--ink-warm) 85%, transparent), transparent 52%),
    var(--ink);
  transform: scale(var(--portal-field-scale, 1));
}
.portal[data-portal-ready] .portal__field { opacity: 1; }

/* The scan reads this face through getComputedStyle, so it has to be declared
   here rather than passed in: what the camera measures must be what paints. */
[data-portal-glyph] {
  font-family: var(--f-display);
  /* font-weight, not font-variation-settings. Canvas measures through the CSS
     font shorthand, which carries weight but not variation axes — and
     getComputedStyle reports fontWeight 400 when the axis is set that way, so
     the scan would size the word at 400 while the SVG painted it at 900. The
     width axis is gone too: it made no difference to the SVG here (464.8px
     with and without), and canvas cannot express a percentage stretch at all. */
  font-weight: 900;
  font-size: 100px;
  font-kerning: none;
  font-variant-ligatures: none;
  letter-spacing: 0;
}
.portal__art {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  overflow: visible; pointer-events: none;
}

.portal__choices { position: absolute; inset: 0; visibility: hidden; pointer-events: none; }
.portal[data-portal-choosing='true'] .portal__choices { visibility: visible; }
.portal__choices button {
  position: absolute;
  border: 0; padding: 0; margin: 0;
  background: transparent;
  cursor: pointer; pointer-events: auto;
  touch-action: pan-y;          /* a tap chooses; a drag still scrolls the page */
  box-sizing: border-box;
}
.portal__choices button:disabled { pointer-events: none; }
.portal__choices button:focus-visible { outline: 2px solid var(--clay); outline-offset: 5px; }

/* what a visitor with no JavaScript gets: the word, set, and nothing that moves */
.portal__poster {
  position: absolute; inset: 0;
  display: none; place-items: center;
  font-family: var(--f-display);
  font-variation-settings: 'wght' 900, 'wdth' 125;
  font-size: min(calc(100vw / 9), 34vh);
  line-height: 1;
  color: var(--clay);
}
.portal[data-portal-ready] .portal__poster { visibility: hidden; }

/* The word sits lower than centre so the opening frame is not half empty
   below it. This anchor is the camera's vertical origin too, so the value is
   shared with script.js through a custom property rather than written twice. */
.portal__travel { height: 0; }
.portal[data-portal-motion='on'] .portal__travel {
  height: calc(var(--portal-len) * var(--portal-h, 100svh));
}

/* No camera without motion: the pin releases, the field shows through the word
   where it stands, and the payoff is simply the next thing on the page. */
@media (prefers-reduced-motion: reduce) {
  .portal__pin { position: relative !important; height: auto !important; min-height: 60svh; }
}
