/* ============================================================
   TANYI ZAPATA — v2
   Foundation · Hero · About · structural stubs
   ============================================================ */

/* ---------- 1. TOKENS ------------------------------------- */
:root{
  /* palette (fixed by brief) */
  --linen:#F6F2EB;
  --stone:#DDD1C2;
  --espresso:#262321;
  --charcoal:#171717;
  --clay:#B59278;
  --olive:#737667;
  --slate:#7A8794;
  --ivory:#FBF9F5;
  /* derived tints of espresso — micro-labels that must stay AA legible */
  --ink-soft:#5F5A55;
  --ink-soft-dark:rgba(246,242,235,.68);
  --hairline:rgba(38,35,33,.18);
  --hairline-dark:rgba(246,242,235,.22);

  /* type families — three, each with one job */
  --display:"Cormorant Garamond",Georgia,"Times New Roman",serif;
  --body:"Manrope",system-ui,-apple-system,"Helvetica Neue",Arial,sans-serif;
  --util:"Space Grotesk","SFMono-Regular",ui-monospace,monospace;
  /* the wordmark only — Fraunces at wght 900 / SOFT 100 / WONK 1: heavy, soft
     rounded terminals, deliberately nothing like the body face. Used for
     .hero__name and nowhere else; if it spreads, the contrast it exists to
     create is gone. */
  --name:"Fraunces","Cormorant Garamond",Georgia,serif;

  /* type scale */
  --t-micro:clamp(.625rem,.585rem + .12vw,.7rem);
  --t-small:clamp(.78rem,.75rem + .12vw,.875rem);
  --t-body:clamp(1rem,.965rem + .16vw,1.1rem);
  --t-lead:clamp(1.02rem,.98rem + .22vw,1.2rem);
  --t-quote:clamp(1.5rem,1.15rem + 1.5vw,2.4rem);
  --t-head:clamp(2.1rem,1.35rem + 2.6vw,3.65rem);
  --t-title:clamp(2.9rem,1.6rem + 5.4vw,7.25rem);

  /* spacing */
  --s-1:.375rem; --s-2:.75rem;  --s-3:1.125rem; --s-4:1.75rem;
  --s-5:2.5rem;  --s-6:3.5rem;  --s-7:5rem;     --s-8:7rem;
  --gutter:clamp(20px,4.4vw,72px);
  --maxw:1680px;
  --sect-y:clamp(5.5rem,10vw,11rem);

  /* motion */
  --ease:cubic-bezier(.16,1,.3,1);
  --ease-soft:cubic-bezier(.22,1,.36,1);
}

/* ---------- 2. BASE --------------------------------------- */
*,*::before,*::after{box-sizing:border-box}
/* The clip is on the ROOT as well as on body, and both are load-bearing.
   `overflow-x` only reaches the viewport by propagating from <html>, and when
   <html> is `visible` the engine takes it from <body> instead — a hand-off
   older iOS Safari does not make for `clip`. Declaring it here means the
   viewport clips on its own account and never depends on that propagation, so
   a section that forgets its own clip can't open a horizontal scrollbar.
   Note `overflow-y` computes to `auto` alongside a non-visible `overflow-x` —
   harmless on the root, which is the page's scroller already, but it is why
   this belongs on <html> and NOT on .intro-wrap: there it would create a
   scrollport and break the sticky burst stage. */
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;overflow-x:clip}
body{
  margin:0;
  background:var(--linen);
  color:var(--espresso);
  font-family:var(--body);
  font-size:var(--t-body);
  font-weight:300;
  line-height:1.72;
  overflow-x:clip;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
img{display:block;max-width:100%}
h1,h2,h3,p,figure,blockquote,ul{margin:0}
/* filtered-out grid items use the hidden attribute; this keeps it authoritative
   over the display value their own layout rules set */
[hidden]{display:none!important}
a{color:inherit}
::selection{background:var(--clay);color:var(--ivory)}

:focus-visible{outline:2px solid var(--espresso);outline-offset:4px}
.sect--dark :focus-visible{outline-color:var(--linen)}

.u-sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
.skip-link{
  position:absolute;top:-120px;left:var(--gutter);z-index:100;
  padding:.7rem 1.15rem;background:var(--espresso);color:var(--ivory);
  font-family:var(--util);font-size:var(--t-micro);letter-spacing:.16em;
  text-transform:uppercase;text-decoration:none;
}
.skip-link:focus{top:1rem}

/* ---------- 3. LAYOUT PRIMITIVES -------------------------- */
.wrap{width:100%;max-width:var(--maxw);margin-inline:auto;padding-inline:var(--gutter)}
/* overflow-x:clip (not hidden) keeps decorative overhang — tilted frames,
   patches, the reveal zoom — from ever creating a horizontal scrollbar. */
/* The linen default is load-bearing, not decoration: the film-strip backdrop is
   position:fixed and stays composited for the whole document, so any section
   left transparent lets it bleed through over that section's content. Sections
   opt out via --stone / --ivory / --dark below, never by having no background. */
.sect{position:relative;overflow-x:clip;padding-block:var(--sect-y);scroll-margin-top:4.5rem;background:var(--linen)}
.sect--stone{background:var(--stone)}
.sect--ivory{background:var(--ivory)}
.sect--dark{background:var(--charcoal);color:var(--linen)}

/* ---------- 4. TYPE ROLES --------------------------------- */
.label{
  font-family:var(--util);
  font-size:var(--t-micro);
  font-weight:500;
  letter-spacing:.24em;
  text-transform:uppercase;
  line-height:1.45;
  color:var(--ink-soft);
  margin:0;
}
.sect--dark .label{color:var(--ink-soft-dark)}

.title{
  font-family:var(--display);
  font-weight:300;
  font-size:var(--t-title);
  line-height:.94;
  letter-spacing:-.005em;
}

/* the stitch motif — the single decorative device on this page */
.stitch{display:inline-block;padding-bottom:.3em;border-bottom:1px dashed var(--clay)}
.annot{
  font-family:var(--display);
  font-style:italic;font-weight:400;
  font-size:clamp(1rem,.9rem + .4vw,1.35rem);
  letter-spacing:.02em;line-height:1;
  color:var(--espresso);
}

/* ---------- 5. BUTTONS ------------------------------------ */
.btn{
  position:relative;
  display:inline-flex;align-items:center;justify-content:center;
  padding:1.05rem 2.1rem;
  background:var(--espresso);color:var(--ivory);
  font-family:var(--body);font-size:var(--t-small);font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;text-decoration:none;
  border:1px solid var(--espresso);border-radius:0;cursor:pointer;
  transition:letter-spacing .5s var(--ease-soft);
}
.btn::after{
  content:"";position:absolute;inset:-8px;
  border:1px dashed var(--clay);opacity:0;pointer-events:none;
  transition:opacity .45s var(--ease-soft),inset .45s var(--ease-soft);
}
.btn:hover{letter-spacing:.19em}
.btn:hover::after,.btn:focus-visible::after{opacity:1;inset:-5px}
.btn--ghost{background:transparent;color:var(--espresso)}
.sect--dark .btn{background:var(--linen);color:var(--charcoal);border-color:var(--linen)}

/* ---------- 6. NAV ---------------------------------------- */
/* Set like the slate's foot, not like a website header. The links are in the
   utility face at micro size in wide caps — the same register as `.slate__k`,
   `.slate__foot` and `.label` — so the bar reads as a lab annotation on the
   film rather than as app chrome. Discretion here is bought with SCALE, not
   with lower contrast: the type stays espresso, because at the top of the
   mobile hero the veil is only ~60% linen and anything softer sits on bare
   film. One centred lockup at every width, which also settles the client's
   "keep everything centred" note — it only ever wrapped because the links were
   sentence-case body copy; in micro-caps the whole cluster fits 375px. */
.nav{
  position:fixed;inset-block-start:0;inset-inline:0;z-index:60;
  transition:background .6s var(--ease-soft),transform .4s var(--ease-soft);
}
.nav.is-stuck{
  background:color-mix(in srgb,var(--linen) 86%,transparent);
  -webkit-backdrop-filter:blur(16px) saturate(1.1);
  backdrop-filter:blur(16px) saturate(1.1);
}
/* GIVING THE SCREEN BACK. A fixed bar at the page's own generous padding is
   ~92px — a tenth of a laptop screen, held for the entire scroll, over a site
   whose whole point is full-bleed film. Two moves, and the reading state gets
   all of it:
   1. once it is stuck the bar condenses to ~63px, since the airy padding is
      doing a job on arrival that it is not doing at 4000px down;
   2. it leaves entirely while you scroll DOWN and returns the moment you
      scroll up, so it costs nothing while you are reading and is one flick
      away when you want it. `js/main.js` owns the direction test.
   `.sect`'s 4.5rem scroll-margin still clears the condensed bar, which is what
   an anchor jump lands against. */
.nav.is-stuck .nav__inner{padding-block:clamp(.5rem,.8vw,.75rem)}
.nav.is-hidden{transform:translateY(-100%)}
/* At rest the bar sits straight on the hero's film, and the hero's centre scrim
   is painted ABOVE that section's own linen washes — so it lays roughly 50%
   black across the nav band and drops espresso micro-caps to ~3.9:1 there.
   The bar carries its own short linen wash instead of the hero being retuned
   around it: the nav is z-index 60, so this sits above everything the hero
   paints, and it costs the hero nothing once `.is-stuck` swaps in the real
   backdrop. Fades out below the bar so there is no visible edge. */
.nav::before{
  content:"";position:absolute;inset-block-start:0;inset-inline:0;
  height:calc(100% + 2.25rem);pointer-events:none;
  background:linear-gradient(to bottom,
    color-mix(in srgb,var(--linen) 90%,transparent) 0,
    color-mix(in srgb,var(--linen) 72%,transparent) 55%,
    color-mix(in srgb,var(--linen) 0%,transparent) 100%);
  transition:opacity .6s var(--ease-soft);
}
.nav.is-stuck::before{opacity:0}
/* The bar closes on a dashed clay hairline rather than a solid rule with a
   shadow under it — the same stitch the rest of the page is sewn with
   (`.stitch`, `.hero__card::after`, `.about__frame`, the Community dividers),
   so the header is joined to the document by the site's own connective tissue.
   A strip of sprocket perforations was built here first, on the logic that the
   header should end as a piece of film. At 5px it read as a black-and-white
   checker stripe across the top of every screen — the loudest thing on the
   page, and the exact opposite of what a nav should be doing. One accessory
   removed; the film reference is already carried by the type. */
.nav::after{
  content:"";position:absolute;left:0;right:0;bottom:0;
  border-bottom:1px dashed color-mix(in srgb,var(--clay) 52%,transparent);
  opacity:0;transition:opacity .6s var(--ease-soft);
}
.nav.is-stuck::after{opacity:1}
.nav__inner{
  /* above .nav::before — a positioned pseudo-element otherwise paints over
     in-flow children, which would bury the links under their own scrim */
  position:relative;z-index:1;
  display:flex;align-items:center;justify-content:center;
  gap:clamp(1.1rem,2.4vw,2.4rem);
  width:100%;max-width:var(--maxw);margin-inline:auto;
  padding:clamp(1rem,1.7vw,1.6rem) var(--gutter);
}
/* the mark, not a spaced-out luxury monogram — .18em reads as a set of
   initials, .32em read as a fashion house. text-indent cancels the trailing
   letter-space so the pair stays optically centred in the lockup. */
.nav__mark{
  font-family:var(--display);
  font-size:clamp(1.05rem,1.4vw,1.35rem);font-weight:400;
  letter-spacing:.18em;text-indent:.18em;line-height:1;text-decoration:none;
  color:var(--espresso);
}
.nav__list{
  display:flex;align-items:center;
  gap:clamp(.85rem,2vw,2.1rem);
  list-style:none;padding:0;margin:0;
}
.nav__link{
  position:relative;display:inline-block;
  padding:.75rem .1rem;          /* the bar is only ~68px tall; this is the tap target */
  /* its own ramp rather than --t-micro: that token tops out at 11.2px, which
     is a caption size, not a target you aim at from across a 1440 screen.
     Floors just above micro on a phone, where the cluster has to stay on one
     line at 320px. */
  font-family:var(--util);font-size:clamp(.66rem,.6rem + .22vw,.8rem);font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;
  color:var(--espresso);text-decoration:none;white-space:nowrap;
  transition:color .45s var(--ease-soft);
}
/* a grease-pencil tick, not an underline — an editor marking the frame they
   want. Short and centred, so it reads as a mark rather than as a rule; the
   full dashed underline stays the body-copy motif (.stitch, .btn::after). */
.nav__link::after{
  content:"";position:absolute;left:50%;bottom:.42rem;
  width:13px;height:0;
  border-bottom:1.5px solid var(--clay);
  transform:translateX(-50%) scaleX(0);
  transition:transform .45s var(--ease);
}
.nav__link:hover,.nav__link:focus-visible{color:var(--clay)}
.nav__link:hover::after,.nav__link:focus-visible::after{transform:translateX(-50%) scaleX(1)}

@media (max-width:560px){
  .nav__inner{gap:.8rem;padding-block:.85rem}
  .nav__list{gap:.72rem}
  .nav__link{letter-spacing:.09em;padding-inline:0}
  .nav__mark{font-size:1rem;letter-spacing:.14em;text-indent:.14em}
}

/* ---------- 7. REVEAL / MOTION UTILITIES ------------------ */
/* Every "hidden" starting state is gated on .js, so a no-JS (or broken-JS)
   visitor gets the full page rather than a blank one. */
[data-reveal]{
  transition:opacity .95s var(--ease-soft) var(--d,0s),
             transform 1.05s var(--ease) var(--d,0s);
}
.js [data-reveal]{opacity:0;transform:translateY(24px)}
[data-reveal].is-in{opacity:1;transform:none}

/* Masked image reveal.
   NOTE: the observed element is always the *outer* wrapper, never .fig__reveal —
   a fully clip-pathed element reports a zero-area intersection rect, so an
   IntersectionObserver watching it would never fire. */
.fig__reveal{
  transition:transform 1.15s var(--ease) var(--d,0s),
             clip-path 1.25s var(--ease) var(--d,0s);
}
.js .fig__reveal{transform:translateY(30px);clip-path:inset(0 0 100% 0)}
.is-in .fig__reveal{transform:none;clip-path:inset(0 0 0 0)}
.fig__reveal img{transition:transform 2.2s var(--ease) var(--d,0s)}
.js .fig__reveal img{transform:scale(1.08)}
.is-in .fig__reveal img{transform:scale(1)}

/* line-by-line mask */
.rv-lines[data-reveal]{opacity:1;transform:none}
.line{display:block;overflow:hidden;padding-bottom:.05em}
.line > span{display:block;transition:transform 1.05s var(--ease) var(--d,0s)}
.js .line > span{transform:translateY(110%)}
.rv-lines.is-in .line > span{transform:none}

/* staggered group */
.rv-stagger[data-reveal]{opacity:1;transform:none}
.rv-stagger > *{transition:opacity .9s var(--ease-soft),transform 1s var(--ease)}
.js .rv-stagger > *{opacity:0;transform:translateY(18px)}
.rv-stagger.is-in > *{opacity:1;transform:none}
.rv-stagger.is-in > *:nth-child(1){transition-delay:.05s}
.rv-stagger.is-in > *:nth-child(2){transition-delay:.16s}
.rv-stagger.is-in > *:nth-child(3){transition-delay:.27s}
.rv-stagger.is-in > *:nth-child(4){transition-delay:.38s}
/* A child past the last step here gets NO delay, so it lands before every one
   of its siblings and the stagger reads backwards. Extend this ladder whenever
   a `.rv-stagger` group grows — the About copy went to five paragraphs on
   2026-08-06 and hit exactly that. */
.rv-stagger.is-in > *:nth-child(5){transition-delay:.49s}

/* Same contract as .rv-stagger, but for long sets (the 24-logo wall) where the
   delay is carried per-child on --d instead of by nth-child. The wrapper is the
   observed element, so the whole group cascades from one trigger. */
.rv-cascade[data-reveal]{opacity:1;transform:none}
.rv-cascade > *{
  transition:opacity .85s var(--ease-soft) var(--d,0s),
             transform .95s var(--ease) var(--d,0s);
}
.js .rv-cascade > *{opacity:0;transform:translateY(16px)}
.rv-cascade.is-in > *{opacity:1;transform:none}

/* ---------- 8. PATCHES ------------------------------------ */
.patch{
  position:absolute;z-index:6;pointer-events:none;
  transform:translate3d(0,var(--py,0px),0);
  will-change:transform;
}
.patch__in{display:block;transform:rotate(var(--r,0deg))}
.patch__in[data-reveal]{
  transition:opacity 1.1s var(--ease-soft) var(--d,0s),
             transform 1.3s var(--ease) var(--d,0s);
}
.js .patch__in[data-reveal]{
  opacity:0;
  transform:translate(var(--fx,0px),var(--fy,0px)) rotate(0deg);
}
.patch__in[data-reveal].is-in{
  opacity:1;transform:rotate(var(--r,0deg));
  animation:sway 2.6s cubic-bezier(.4,0,.2,1) calc(var(--d,0s) + 1.3s) 1 forwards;
}
.patch img{width:100%;height:auto;filter:drop-shadow(0 10px 22px color-mix(in srgb,var(--espresso) 20%,transparent))}
@keyframes sway{
  0%{transform:rotate(var(--r,0deg))}
  32%{transform:rotate(calc(var(--r,0deg) + 1.7deg))}
  66%{transform:rotate(calc(var(--r,0deg) - .9deg))}
  100%{transform:rotate(var(--r,0deg))}
}

