/* ==========================================================================
   Dossi — landing hero
   Layer order inside .hero (z-index):
     1  extended sky + back photo
     2  DOSSI wordmark (+ seam blend)
     3  building cutout PNG   <- this is what occludes the wordmark
     4  scrims                <- these lift on scroll
     5  UI (nav / tagline / by-line / scroll cue)
   ========================================================================== */

:root{
  /* The white is DossierProof's, verbatim from its global.css, so the marketing
     site and the platform are the same surface. It is pure #FFFFFF — not the
     cream the hero handoff originally specified. */
  --marble:#FFFFFF;
  --graphite:#131313;
  --sage:#C2C0A9;          /* sampled from the Hendriks Housing logo */

  /* Finding-card tokens, from the handoff bundle and the platform's own
     `.fitem` rules. Severity colours are the report palette's, not the older
     results-page ones. */
  --dp-onyx:#000;
  --dp-anthracite:#333;
  --dp-muted:#8B8B85;
  --dp-pebble:#E5E5E2;
  --dp-basalt:#555;                      /* the platform's hover border */
  --dp-ease:cubic-bezier(.22,.61,.36,1); /* and its hover easing */
  --dp-red:#D2483B;
  --dp-amber:#D8941F;
  --dp-green:#3C8C5A;

  /* driven by src/hero-scroll.js, 0 at rest -> 1 when the shade has fully lifted */
  --lift:0;
  --ui:0;

  /* ---------------------------------------------------------------- SIZING
     The hero was drawn at one width and written in raw `vw`, which is right
     for the DOSSI mark and the photograph — the mark has to keep its exact
     proportion to the image or the heads stop cutting the letters — and wrong
     for everything else. Type in pure vw has no floor and no ceiling: at 2560
     the tagline reached 100px and ran through the centred scroll cue, and on a
     short 1366x625 laptop the vw-based vertical offsets are large relative to
     the height they have to live in.

     So the UI is `clamp(floor, the original vw, ceiling)` throughout. Every
     ceiling is set to what that vw resolved to at 1440, which means every
     laptop up to 1440 is pixel-identical to the design he approved and only
     bigger monitors are held back. The floors are what keeps it honest on a
     small or short screen.

     `.wordmark`, `.hero img` and `--img-h` are deliberately NOT clamped. */
  --hero-gutter:clamp(20px,2.6vw,40px);
}

*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
html,body{width:100%}
/* html is the scroll container, so the clip has to be here as well as on body —
   the mark's own box travels 34vw right as it exits and would widen the page. */
html{overflow-x:clip}
body{
  /* white, so section 02 can be transparent and overlap the hero's exit */
  background:var(--marble);font-family:'DM Sans',system-ui,sans-serif;
  -webkit-font-smoothing:antialiased;
  /* The mark translates 34vw right as it exits, which would otherwise widen the
     page. `clip`, not `hidden`: hidden creates a scroll container and the hero's
     sticky pin would stop working. */
  overflow-x:clip;
}
body.is-locked{overflow:hidden}
a{color:var(--marble);text-decoration:none}
:focus-visible{outline:1px solid var(--marble);outline-offset:3px}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

/* --------------------------------------------------------------- CURTAIN */
.curtain{
  position:fixed;inset:0;z-index:100;
  display:flex;align-items:center;justify-content:center;
  background:var(--graphite);
  transform:translateY(0);
  will-change:transform;
}
.curtain.is-lifting{
  transform:translateY(-100%);
  transition:transform 1.05s cubic-bezier(.76,0,.24,1);
}
.curtain.is-gone{display:none}

/* --------------------------------------------------------- NAV TRANSITION */
/* Same move as the curtain above (graphite, same lift easing), played a second
   time on nav clicks: comes down over the page, the jump happens while it's
   covered, then it lifts away.

   Two panels, mirroring Hendriks Housing's own curtain (their site preloader,
   as ported into dossierproof's `PageCurtain` + `.curtain-wipe` in its
   global.css): the graphite base holds the lockup in white, then a Marble panel
   holding the SAME lockup in ink wipes down over it, so the colours invert in
   place. Both lockups are clones of #curtain's, made in src/nav-curtain.js —
   the design cannot drift from the opening curtain's.

   The wipe's timing lives in the JS with HH's numbers on it. Only the two
   clip-path end states are here. */
.nav-curtain{
  position:fixed;inset:0;z-index:150;
  transform:translateY(-100%);
  pointer-events:none;
}
.nav-curtain.is-down{transform:translateY(0);pointer-events:auto}

.nav-curtain__base,
.nav-curtain__wipe{
  position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;
}
.nav-curtain__base{background:var(--graphite)}

.nav-curtain__wipe{
  background:var(--marble);
  clip-path:inset(0 0 100% 0);
}
.nav-curtain__wipe.is-wiped{clip-path:inset(0 0 0 0)}

/* The ink half of the lockup. The mark is swapped for its transparent cut in
   the JS; here the knock-out trick is switched off and the type takes the
   platform's black. The sage full stop is the logo's own colour and stays. */
.nav-curtain__wipe .curtain-mark{filter:none;mix-blend-mode:normal}
.nav-curtain__wipe .curtain-dossi,
.nav-curtain__wipe .curtain-hh-name{color:var(--dp-onyx)}
.nav-curtain__wipe .curtain-hh-sub{color:rgba(0,0,0,.72)}
.nav-curtain__wipe .curtain-divider{background:rgba(0,0,0,.28)}

/* ------------------------------------------------------------ STICKY NAV */
/* A way back for a reader deep in the page. The hero's topbar scrolls away with
   the hero; this is the same lockup and the same pill (literal clones, made in
   src/sticky-nav.js) on a bar that slides down only when the reader scrolls UP
   a good distance, and puts itself away the moment they scroll down again.

   Translucent rather than solid so it reads as a thin sheet over the page it
   belongs to, and so it works over both surfaces the page alternates between —
   white and Alabaster. It sits under the nav curtain (150), never over it. */
.sticky-nav{
  position:fixed;top:0;left:0;right:0;z-index:90;
  /* Almost invisible, his word: no band, no hairline, no blur — those made it
     read as a piece of chrome bolted onto the page. What is left is a soft white
     fade that has no edge of its own and simply stops, there only so the lockup
     and the pill never have to sit on a line of body copy. */
  background:linear-gradient(180deg,
    rgba(255,255,255,.94) 0%,
    rgba(255,255,255,.82) 42%,
    rgba(255,255,255,0) 100%);
  transform:translateY(-100%);opacity:0;visibility:hidden;
  transition:transform .55s var(--dp-ease),opacity .4s var(--dp-ease),visibility 0s .55s;
  will-change:transform;
}
.sticky-nav.is-in{
  transform:translateY(0);opacity:1;visibility:visible;
  transition:transform .55s var(--dp-ease),opacity .4s var(--dp-ease),visibility 0s;
}
.sticky-nav__in{
  display:flex;align-items:center;justify-content:space-between;
  /* Exactly the hero topbar's geometry: `.topbar` is at top 2.2vw with a 2.6vw
     edge, so the same numbers here land the lockup and the pill on the same
     line and the same inset they occupy at the top of the page — scrolling back
     up returns the header to where it was, rather than to a near-miss of it.
     Nothing overrides the pill's size either, for the same reason.
     The bottom padding is the odd one out on purpose: it is not spacing, it is
     the room the fade needs to reach nothing before the bar ends. */
  padding:2.2vw 2.6vw 2.4vw;
}

@media (max-width:700px){
  .sticky-nav__in{padding:4.5vw 5vw 5vw}   /* the hero topbar's own mobile numbers */
}

.curtain-lockup{display:flex;align-items:center;gap:3.2vw;padding:0 6vw}

.curtain-cell{display:flex;align-items:center;gap:1.1vw}
.curtain-cell--hh{flex-direction:column;align-items:flex-start;gap:.25vw}

.curtain-mark{
  width:4.2vw;height:4.2vw;min-width:38px;min-height:38px;flex:none;
  /* The source mark is a black glyph on an opaque white square. invert() turns it
     into a white glyph on black, and screen() drops black to the layer below —
     so the square vanishes into the curtain and only the glyph survives. */
  filter:invert(1) sepia(.14) saturate(.6) brightness(1.02);
  mix-blend-mode:screen;
  opacity:0;transform:translateY(.35em);
}
.curtain-mark.is-in{opacity:1;transform:none;transition:opacity .5s ease,transform .5s cubic-bezier(.16,1,.3,1)}

.curtain-dossi{
  font-family:'DM Sans',system-ui,sans-serif;font-weight:400;
  font-size:clamp(26px,3.5vw,64px);letter-spacing:-.04em;line-height:1;
  color:var(--marble);
}
.curtain-hh-name{
  font-weight:700;font-size:2.05vw;letter-spacing:-.005em;line-height:1;
  color:var(--marble);
}
.curtain-hh-name b{color:var(--sage);font-weight:700}
.curtain-hh-sub{
  font-weight:400;font-size:1.02vw;line-height:1;
  color:rgba(255,255,255,.82);
}
/* Optical match to the real lockup: HOUSING is tracked out to exactly HENDRIKS'
   width (measured, not guessed — .8em lands both runs on 146.5px at 1440).
   The negative margin removes the trailing letter-space so the cell stays honest.
   Do not use text-indent here: every .ch is its own block container, so it would
   be applied once per character. */
.curtain-hh-sub{letter-spacing:.8em;margin-right:-.8em}

.curtain-divider{
  display:block;width:1px;height:3.9vw;min-height:36px;flex:none;
  background:rgba(255,255,255,.34);
  transform:scaleY(0);transform-origin:center;
}
.curtain-divider.is-in{transform:scaleY(1);transition:transform .5s cubic-bezier(.16,1,.3,1)}

/* per-character reveal — .ch is the clip, .ch-i is what moves */
.split .ch{display:inline-block;overflow:hidden;vertical-align:top}
.split .ch-i{display:inline-block;transform:translateY(105%);opacity:0}
.split.is-in .ch-i{
  transform:translateY(0);opacity:1;
  transition:transform .42s cubic-bezier(.16,1,.3,1),opacity .3s linear;
  transition-delay:var(--d,0ms);
}

/* Phones: the two marks stack and the divider lies flat between them —
   side by side, DOSSI cannot hold one line at 390px. */
@media (max-width:700px){
  .curtain-lockup{flex-direction:column;gap:5.5vw;padding:0 8vw}
  .curtain-cell{gap:2.6vw}
  .curtain-cell--hh{align-items:center;gap:1.2vw}
  .curtain-mark{width:10.5vw;height:10.5vw}
  .curtain-dossi{font-size:clamp(26px,10.5vw,52px)}
  .curtain-hh-name{font-size:6vw}
  .curtain-hh-sub{font-size:2.99vw}
  .curtain-divider{
    width:24vw;height:1px;min-height:1px;
    transform:scaleX(0);
  }
  .curtain-divider.is-in{transform:scaleX(1)}
}

/* ------------------------------------------------------------------ HERO */
/* Two acts inside one pin: the shade lifting / photo panning, then the outro
   closing onto the mark. See LIFT_SHARE / CLOSE_START in hero-scroll.js. */
/* Marble, not graphite: this is what shows outside the closing aperture, and it
   has to match the studio wall so the frame reads as one white surface. */
.hero-pin{position:relative;height:560vh;background:var(--marble)}
.hero{
  position:sticky;top:0;
  width:100%;height:100vh;overflow:hidden;
  color:var(--marble);
  z-index:1;
  /* transform + opacity on the exit are set from src/hero-scroll.js — the
     headline leaves along an orbit arc, which needs trig, not a calc() */
}

/* The plate carries every layer that must stay in register: both photos and the
   wordmark between them. It pans up on scroll to bring the foot of the buildings
   into view; --pan-max is measured in src/hero-scroll.js. */
.hero-plate{
  position:absolute;inset:0;z-index:0;
  /* The outro's push-in rides on the same transform as the pan. --drift is the
     camera following the headline upward, which slides the team down and out. */
  transform:translate3d(0,calc(-1 * var(--pan-max,0px) * var(--lift) + var(--drift,0px)),0)
            scale(calc(1 + .17 * var(--close,0)));
  will-change:transform;
}
/* the one-off settle as the curtain clears — kept off .hero-plate so it cannot
   put a 1.6s transition on the scroll-driven pan */
.hero-plate-in{position:absolute;inset:0;transform:scale(1.045)}
body.is-revealed .hero-plate-in{transform:none;transition:transform 1.6s cubic-bezier(.16,1,.3,1)}

/* Both <img> layers MUST keep an identical box / object-fit / object-position /
   filter — they are the same photograph, and only the knocked-out sky in the
   front PNG makes the wordmark disappear behind the real roofline. */
.hero img{
  /* No grade. The photograph is shown as shot, and stays that way at every
     scroll position — there was a warm sepia/brightness/contrast grade here that
     relaxed to neutral as --lift rose, and a warm key light over it; both are
     gone. If it should come back, this is the only place it lived. */
  filter:none;
  /* --img-h is the photo's true rendered height, so object-fit never crops it and
     the hero alone does the clipping — that is what leaves room to pan into. */
  position:absolute;top:0;left:0;width:100%;height:var(--img-h,100%);
  object-fit:cover;object-position:center top;
}
.img-back{z-index:1}
.img-front{z-index:3}

/* Wordmark placement is measured, not guessed. At any viewport wider than the
   photo's 3:2 aspect, image rows sit at (frac × 66.67)vw. Head tops under the
   letters run .295 (the tall man, centre-left) then a cluster at .355–.383;
   the letter bottoms land at ≈.378, so four of those heads cut into the letters
   and the rest sit just below. HEAD_TOPS in _check.cjs holds the same table. */
.wordmark-wrap{
  position:absolute;top:5.45vw;left:0;right:0;z-index:2;
  display:flex;align-items:baseline;justify-content:center;gap:1.4vw;
}
/**
 * DM Sans, to hendrikshousing.com's own headline spec — which is itself a
 * literal reading of the AI Master Identity 05.1: regular weight, "tight line
 * spacing and slightly tight letter spacing". Their live headline runs 400 /
 * -0.04em / line-height 1.
 *
 * This replaced Bodoni Moda from the Claude Design handoff. The identity permits
 * exactly one typeface and names no serif or display face anywhere, so the
 * wordmark was the site's one real brand deviation.
 *
 * font-size and top are re-solved for DM Sans's own metrics — the brief was to
 * keep the same size and place on screen, and a geometric sans at the same
 * point size sets to a different width and sits on a different baseline than a
 * high-contrast serif does.
 */
.wordmark{
  /* THE MASK, minimal-goods' `.header_text-wrap`. It has to be THIS element and
     not .wordmark-wrap: hero-close.js writes an inline transform here to rise
     and scale the headline into the closing mark, so a mask on the wrap would
     clip the outro instead of travelling with it. This box is exactly the line
     box and the ink sits well inside it, so the clip does nothing at rest. */
  overflow:hidden;
  font-family:'DM Sans',system-ui,sans-serif;font-size:22.95vw;font-weight:400;
  /* Back to the tight setting (08-21). It was briefly tracked out to .34em to
     match the by-line; he preferred it as it was, so this is DM Sans's own
     spec again: 05.1's "large headings use ... slightly tight letter spacing",
     which is also what hendrikshousing.com's hero uses. */
  letter-spacing:-.04em;line-height:1;color:var(--dp-onyx);
}
/* THE MOVER, minimal-goods' `.header_text-move`. `--exit-slide` runs 0 -> 1
   across the exit (hero-scroll.js) and 100% is the headline's own height, so at
   1 it sits a full line box below the mask and is completely gone. `block` is
   required: transforms do not apply to a non-replaced inline element. */
.wordmark-ink{
  display:block;
  transform:translateY(calc(100% * var(--exit-slide, 0)));
}

/* Outro layers, both idle until --close moves. The veil sits above the people
   and below the front wordmark, so the photo washes out while the type stays
   crisp on top of it. */
.outro-veil{
  position:absolute;inset:-20%;z-index:4;pointer-events:none;
  background:var(--marble);opacity:0;
}
.wordmark-wrap--front{z-index:5;opacity:0}

/* Scrims — the "darker shade" that lifts away on scroll.
   The team photo is tonally split: white studio wall on top, dark floor below.
   So the hero's ink is split with it — graphite type over the wall, marble type
   over the floor — and the scrims run the same way: a light wash up top to make
   the graphite wordmark and nav bite, a dark gradient at the foot to carry the
   marble tagline. Both lift on scroll. */
.scrims{position:absolute;inset:0;z-index:4;opacity:calc(1 - var(--lift));pointer-events:none}
.scrim-uniform{position:absolute;inset:0;background:rgba(19,19,19,.05)}
/* There WAS a white wash over the top 46% here — .62 -> .34 -> 0 — to make the
   graphite wordmark and nav bite against the wall. It sat ABOVE the wordmark
   (scrims are z4, the wordmark z2), so it washed out the upper half of the
   letters and left their feet dark: on the first frame DOSSI read as a
   light-to-dark gradient, and the letters only went evenly black once the
   scrims had lifted. That change is what looked off against the rest of the
   site, so the wash is gone and the first frame is the settled one.
   The wall is bright enough on its own — the type still bites without it. */
.scrim-bottom{position:absolute;bottom:0;left:0;right:0;height:34%;
  background:linear-gradient(0deg,rgba(19,19,19,.66) 0%,rgba(19,19,19,.28) 60%,rgba(19,19,19,0) 100%)}
/* (a warm key light lived here, screen-blended from the upper left — removed
   with the grade, so the photograph carries no colour cast of ours at all) */

/* The glare sweeps inside the clip, so it only ever crosses the closed mark.
   Inset far past the edges so the rotated band still covers at full scale. */
/* The hero's own clip cuts the outer half of the stroke, which is what leaves a
   true hairline rather than a 2px band. */
.hero-outline{
  position:absolute;inset:0;z-index:5;width:100%;height:100%;
  pointer-events:none;overflow:visible;opacity:0;
}
.hero-outline polygon{fill:none;stroke:rgba(19,19,19,.17);stroke-width:2}

/* `overlay`, not `screen`: the closed mark is a pale frame, and screen-blending
   white onto near-white is a no-op — the sweep was invisible. Overlay brightens
   the mid-tones and lifts the graphite letters hard, so the glare actually
   crosses the whole shape. */
.hero-glare{
  position:absolute;inset:-60%;z-index:6;pointer-events:none;opacity:0;
  mix-blend-mode:overlay;will-change:transform,opacity;
  background:linear-gradient(102deg,
    rgba(255,255,255,0) 41%,
    rgba(255,255,255,.30) 47%,
    rgba(255,255,255,.78) 50%,
    rgba(255,255,255,.30) 53%,
    rgba(255,255,255,0) 59%);
}

.hero-ui{position:absolute;inset:0;z-index:5;opacity:calc(1 - var(--ui))}
.hero-ui[data-faded="1"]{pointer-events:none}

/* The by-line rides inside the plate, so it pans with the photograph and stays
   pinned to the floor below the team's feet (.915 down the frame — the lowest
   shoes land at ≈.86). It starts below the fold and fades in with --lift, so
   scrolling is what brings it into view; the bottom scrim lifting off at the
   same time is what makes it read as arriving rather than just being there. */
.by-line{
  position:absolute;top:calc(var(--img-h,100%) * .915);left:0;right:0;
  z-index:4;text-align:center;
  font-size:clamp(13px,1.55vw,24px);font-weight:500;letter-spacing:.34em;text-transform:uppercase;
  color:rgba(255,255,255,.94);white-space:nowrap;
  text-shadow:0 1px 18px rgba(0,0,0,.45);
  /* Starts only once the tagline and scroll cue have gone (they finish at
     --lift .74), so the two never sit on top of each other mid-scroll. */
  opacity:calc((var(--lift) - .58) / .3);
}

.topbar{
  position:absolute;top:clamp(18px,2.2vw,34px);
  left:var(--hero-gutter);right:var(--hero-gutter);
  display:flex;justify-content:space-between;align-items:center;
}
.topbar-lockup{display:flex;align-items:center;gap:clamp(9px,1vw,16px);flex:none}
.hero .topbar-lockup img,
.sticky-nav .topbar-lockup img{position:static;object-fit:contain}
.hero .topbar-lockup__hh,
.sticky-nav .topbar-lockup__hh{width:auto;height:clamp(19px,1.5vw,24px)}
.topbar-lockup__divider{
  display:block;width:1px;height:clamp(22px,1.75vw,28px);flex:none;
  background:rgba(19,19,19,.28);
}
.hero .topbar-lockup__dossi,
.sticky-nav .topbar-lockup__dossi{width:clamp(22px,1.95vw,31px);height:clamp(22px,1.95vw,31px);opacity:.88}

/* nav links sit over the white wall, so they are graphite, not marble */
.topbar-links{
  position:absolute;left:50%;transform:translateX(-50%);
  display:flex;gap:clamp(24px,2.6vw,42px);
  font-size:clamp(12px,.95vw,15px);font-weight:400;letter-spacing:.01em;
}
.topbar-links a{color:rgba(19,19,19,.88)}
.topbar-links a:hover{color:var(--graphite)}

.tagline{
  position:absolute;bottom:clamp(30px,4.2vw,66px);left:var(--hero-gutter);
  /* Capped at what 52vw resolves to at 1440, so every laptop up to that width
     is unchanged and only larger monitors are held back. Without the cap the
     line reaches the centred scroll cue: at 2560 it was 100px type running
     straight through it. */
  max-width:min(52vw,750px);
}
.tagline h2{
  font-size:clamp(26px,3.9vw,58px);
  font-weight:300;letter-spacing:-.018em;line-height:1.12;text-wrap:pretty;
}

.scrollcue{
  position:absolute;bottom:clamp(16px,2.4vw,38px);left:50%;transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;gap:clamp(7px,.7vw,11px);
}
.scrollcue span{
  font-size:clamp(9px,.68vw,11px);letter-spacing:.22em;text-transform:uppercase;
  color:rgba(255,255,255,.8);text-shadow:0 2px 10px rgba(0,0,0,.6);
}
.scrollcue i{display:block;width:1px;height:clamp(26px,3.4vw,52px);
  background:linear-gradient(0deg,rgba(255,255,255,.75),rgba(255,255,255,0))}

/* the hero UI arrives after the curtain has cleared */
.hero-ui .topbar,
.hero-ui .tagline,
.hero-ui .scrollcue{opacity:0;transform:translateY(1.1vh)}
body.is-revealed .hero-ui .topbar,
body.is-revealed .hero-ui .tagline,
body.is-revealed .hero-ui .scrollcue{
  opacity:1;transform:none;
  transition:opacity .8s ease,transform .9s cubic-bezier(.16,1,.3,1);
}
body.is-revealed .hero-ui .tagline{transition-delay:.06s}
body.is-revealed .hero-ui .scrollcue{transition-delay:.22s}

/* (the photograph's settle lives on .hero-plate-in, above) */

/* ----------------------------------------------- NAV: rolling text links */
.roll-link{display:flex;align-items:center;min-height:44px;padding:0 .2vw}
.roll-clip{position:relative;display:inline-block;overflow:hidden;vertical-align:top;line-height:1.25}
.roll{position:relative;display:block}
.roll-bottom{position:absolute;top:0;left:0;white-space:nowrap}
.roll-top .ch-i{transform:translateY(0)}
.roll-bottom .ch-i{transform:translateY(100%)}
.roll-link .ch{display:inline-block;overflow:visible;vertical-align:top}
.roll-link .ch-i{
  display:inline-block;opacity:1;
  transition:transform .4s cubic-bezier(.45,0,.55,1);
  transition-delay:var(--d-out,0ms);   /* leaving replays the cascade backwards */
}
.roll-link:hover .ch-i,
.roll-link:focus-visible .ch-i{transition-delay:var(--d,0ms)}
.roll-link:hover .roll-top .ch-i,
.roll-link:focus-visible .roll-top .ch-i{transform:translateY(-100%)}
.roll-link:hover .roll-bottom .ch-i,
.roll-link:focus-visible .roll-bottom .ch-i{transform:translateY(0)}
/* a space collapses to zero width inside inline-block — hold it open */
.roll-link .ch--space{width:.28em}

/* ------------------------------------------------- NAV: the Contact pill */
.link-button{display:flex;gap:.28vw;align-items:stretch;height:2.6vw;min-height:34px;width:max-content}
.link-button__text{
  display:flex;align-items:center;flex-grow:1;
  padding:0 1.5vw;background:var(--graphite);
  font-size:.72vw;font-weight:500;letter-spacing:.1em;text-transform:uppercase;
  color:var(--marble);white-space:nowrap;
  will-change:clip-path;
}
.link-button__label{
  background-image:linear-gradient(120deg,
    rgba(255,255,255,.72) 0%,rgba(255,255,255,.72) 40%,
    #FFFFFF 50%,
    rgba(255,255,255,.72) 60%,rgba(255,255,255,.72) 100%);
  background-size:300% 100%;background-position:100% 0;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
}
.link-button__icon{
  position:relative;aspect-ratio:1;height:100%;flex:none;
  perspective:600px;will-change:clip-path;
  --half:17px;
}
.link-button__cube{position:absolute;inset:0;transform-style:preserve-3d;will-change:transform}
.link-button__face{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  backface-visibility:hidden;background:var(--graphite);isolation:isolate;color:var(--marble);
}
.link-button__face--front{transform:translateZ(var(--half))}
.link-button__face--side{transform:rotateY(-90deg) translateZ(var(--half))}
.link-button__wipe{
  position:absolute;inset:0;z-index:0;background:var(--marble);
  transform-origin:left center;transform:scaleX(0);
}
.link-button__face svg{position:relative;z-index:1;width:.85vw;height:.85vw;min-width:13px;min-height:13px;mix-blend-mode:difference}

/* ======================================================== SECTION 02 ===== */
/* Transparent, and pulled up over the hero's exit by src/story.js — the copy and
   the first cards have to arrive WHILE the headline is still heading out, not a
   screen of scrolling later. The white comes from the body underneath. */
.story{position:relative;z-index:2}
.story-pin{position:relative;height:520vh}
.story-stage{position:sticky;top:0;height:100vh;overflow:hidden}

/* --- the copy. Same face and weight as the hero tagline, deliberately: this
   is the same voice continuing, not a new one. Lines cross-fade in place. --- */
.story-copy{
  position:absolute;inset:0;z-index:3;pointer-events:none;
  display:flex;align-items:center;justify-content:center;
  padding:0 8vw;
}
.story-line{
  position:absolute;max-width:38vw;
  font-size:3.1vw;font-weight:300;letter-spacing:-.018em;line-height:1.2;
  color:var(--graphite);text-wrap:pretty;text-align:center;
  overflow:hidden;            /* the mask the line rises out of */
  will-change:opacity;
  /* a white halo, so the line still reads cleanly against a card behind it */
  text-shadow:0 0 22px #fff,0 0 44px #fff,0 0 70px #fff;
}
/* one mask per rendered line — see src/split-lines.js */
.story-line .ln{display:block;overflow:hidden}
.story-line .ln-i{display:block;will-change:transform}
.story-line em{font-style:normal;font-weight:500}

/* --- the orbiting cards --------------------------------------------------
   Positioned by src/story.js. Every card sits at the stage centre — behind the
   copy — and is pushed outward along its own angle as the section scrolls, so
   the cards emerge from behind the text and circle it. */
.story-cards{position:absolute;inset:0;z-index:1}

/* The Dossi finding card, built to the handoff bundle and cross-checked against
   the platform's own `.fitem` in dossierproof/frontend/src/styles/global.css.
   Every number here is deliberate: 1.5px border, 10.5px labels, the 34px rule
   and the 14px radius. Do not round them.

   Colour discipline: the severity colour appears ONLY on the 7px dot and the
   34px rule. No tinted background, no accent stripe, no coloured text, no
   badge. The black border plus halo is the SELECTION ring — it means the card
   is open in the detail panel, and is never a status. */
/* The orbit transform lives on the wrapper and the hover transform on the card,
   so the two never overwrite each other — the platform's hover includes a 2px
   lift, and the orbit owns .fcard-orbit's transform outright. */
.fcard-orbit{
  position:absolute;top:50%;left:50%;width:380px;
  will-change:transform,opacity;
}
.fcard{
  position:relative;width:100%;cursor:pointer;
  background:#fff;border:1.5px solid var(--dp-pebble);border-radius:14px;
  padding:15px 17px 14px;overflow:hidden;
  /* verbatim from .fitem in the platform's global.css */
  transition:transform .32s var(--dp-ease),box-shadow .32s var(--dp-ease),
             border-color .32s var(--dp-ease);
}
.fcard:hover{
  transform:translateY(-2px);
  box-shadow:0 10px 24px -14px rgba(0,0,0,.24);
  border-color:var(--dp-basalt);
}
.fcard-cat{
  display:flex;align-items:center;gap:8px;padding-right:34px;
  font-size:10.5px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--dp-anthracite);
}
.fcard-dot{width:7px;height:7px;border-radius:50%;flex:none}
.fcard-title{
  font-size:14px;font-weight:500;line-height:1.35;letter-spacing:-.005em;
  margin-top:8px;text-wrap:pretty;color:var(--dp-onyx);
}
.fcard-rule{display:block;height:2px;width:34px;margin-top:13px;border-radius:2px}
.fcard-meta{
  font-size:10.5px;font-weight:600;letter-spacing:.01em;
  color:var(--dp-muted);margin-top:11px;
}
.fcard[data-flag="red"]   .fcard-dot,.fcard[data-flag="red"]   .fcard-rule{background:var(--dp-red)}
.fcard[data-flag="amber"] .fcard-dot,.fcard[data-flag="amber"] .fcard-rule{background:var(--dp-amber)}
.fcard[data-flag="green"] .fcard-dot,.fcard[data-flag="green"] .fcard-rule{background:var(--dp-green)}

@media (max-width:700px){
  .story-line{font-size:5.6vw;max-width:88vw}
  .fcard-orbit{width:280px}
  .fcard{padding:13px 15px 12px}
  .fcard-title{font-size:13px}
}

/* ------------------------------------------------------------ responsive */
@media (max-width:700px){
  .hero-pin{height:170vh}
  /* Portrait shows only the centre ~24% of the photo's width, where the head
     tops run .295 / .368 / .383. The photo is zoomed 1.15 (see hero-scroll.js)
     so the team fills the frame and there is pan room; these two numbers put the
     letter bottoms at ≈.375 of the photo, into the three heads in that column. */
  .wordmark{font-size:30vw}
  .wordmark-wrap{top:70vw}
  .by-line{font-size:3.4vw;letter-spacing:.3em}
  .topbar{top:4.5vw;left:5vw;right:5vw}
  .topbar-lockup{gap:3.2vw}
  .hero .topbar-lockup__hh,
  .sticky-nav .topbar-lockup__hh{height:4.4vw;min-height:18px}
  .topbar-lockup__divider{height:5.2vw;min-height:20px}
  .hero .topbar-lockup__dossi,
  .sticky-nav .topbar-lockup__dossi{width:5.7vw;height:5.7vw;min-width:21px;min-height:21px}
  .topbar-links{display:none}
  .link-button{height:8.4vw;gap:1vw}
  .link-button__text{padding:0 4vw;font-size:2.4vw}
  .link-button__face svg{width:3.4vw;height:3.4vw}
  .tagline{bottom:12vw;left:5vw;max-width:82vw}
  .tagline h2{font-size:7vw}
  .scrollcue{bottom:5vw}
  .scrollcue span{font-size:2.1vw}
  .scrollcue i{height:9vw}
  .after p{font-size:2.4vw}
}

@media (prefers-reduced-motion:reduce){
  .curtain.is-lifting{transition-duration:.3s}
  .split .ch-i,
  .curtain-mark,
  .curtain-divider{transition-duration:.01ms!important}
  .hero-plate-in{transform:none}
}

/* ==========================================================================
   THE PAGE — sections 03, 04, 05

   Light consultative mode, per the AI Master Identity 05.3: white base,
   generous white space, Onyx headings, Basalt body copy, Pebble rules, and
   Sandstone reserved for the one call-out that deserves attention. One fixed
   left and right content line runs through all three sections (05.3's
   alignment grid), small uppercase section labels sit above each title, and
   every block carries a distinct information unit rather than decorating.

   Type sizes come from 05.1's digital scale, not invented: Title 112 /
   Subtitle 80 / Heading Big 64 / Normal 48 / Small 32 / Body 18 / 16 / 14.
   ========================================================================== */

:root{
  --sandstone:#FFF8DB;
  --sand-edge:#EFE4B8;
  /* Alabaster, from the Brand Style Guide's palette page — "soft informational
     panels and cool backgrounds". It is the page's second surface: whole
     sections sit on it, rows warm to it on hover, and the cursor takes it. */
  --alabaster:#EEF2F2;
  --alabaster-edge:#DFE6E6;
  --edge:clamp(24px,7vw,120px);          /* the fixed content line */
  --measure:1240px;
  /* veryworkinprogress's easing, taken off their own .btn rules */
  --vw-ease:cubic-bezier(.76,0,.24,1);
}

/* TRANSPARENT, and the white comes from `body` — the same reason `.story` is.
   The seam is this element's first child and is pulled up over the orbit's
   tail, which drags `.page`'s own box up with it (adjoining margins collapse).
   With a white background here that box painted straight over the last copy
   line and the departing cards. Every section inside sets its own surface. */
.page{position:relative;z-index:2;color:var(--dp-onyx)}
/* the global rule paints links marble, which is right over the photograph and
   invisible here; the pill sets its own colours, so it is excluded */
.page a:not(.pill){color:inherit}
.page :focus-visible{outline:1px solid var(--dp-onyx);outline-offset:3px}

.sec{padding:clamp(90px,13vw,200px) var(--edge);position:relative}
.sec-in{max-width:var(--measure);margin:0 auto}

/* Alabaster sections. Two of the five sit on it, so the page alternates
   surface rather than running as one unbroken white — and a white finding card
   on Alabaster reads as an object on a page instead of a box on a void. */
.sec--alabaster{background:var(--alabaster)}

/* ------------------------------------------------------------------- seam
   The join between the orbiting cards and the first section to be read.

   The orbit ends with the cards flying outward, so the seam answers it: a stem
   draws down from where they left, lands on a single dot, and the dot releases
   a hairline that runs the full width of the page. Motion collapsing to a
   point, and the point becoming the line the document starts on. Nothing else,
   because everything here is one hairline wide. */
.seam{
  /* Tall, and pulled up over the orbit's tail by story.js — the stem has to
     reach the last copy line while that line is still lit. Transparent, or it
     would cover the departing cards; the white comes from `body`. */
  position:relative;height:clamp(320px,92vh,900px);
  display:flex;flex-direction:column;align-items:center;justify-content:flex-end;
  pointer-events:none;
}
/* The stem's LENGTH is driven by `--seam-lead`, which story.js sets from the
   last copy line's own rise. So the line does not merely appear near the text;
   it draws itself as that text lands, and hands the reader down to the rule.
   `--seam` is the observer's draw-in, kept so the seam still resolves if it is
   entered without the orbit having run (deep link, reduced motion). */
/* No local `--seam-lead` here: story.js sets it on the root, and a declaration
   on this element would shadow the inherited value at 0 forever. */
.seam-stem{
  width:1px;
  /* Reaches from the dot up to just under the last copy line. The growth is
     measured against that: at 52vh the stem topped out ~200px short of the
     text, which read as two separate marks rather than one line handing over. */
  height:calc(clamp(70px,9vh,110px) + var(--seam-lead,0) * clamp(150px,72vh,760px));
  background:linear-gradient(to bottom,rgba(202,202,202,0),var(--dp-pebble) 22%);
  transform:scaleY(0);transform-origin:top center;
  transition:transform 1.1s var(--dp-ease);
}
.seam-dot{
  width:7px;height:7px;border-radius:50%;background:var(--dp-onyx);
  transform:scale(0);transition:transform .5s var(--dp-ease) .55s;
  /* The dot rests ON the hairline, so it has to paint over it. `.seam-rule` is
     absolutely positioned and is the last child, so without this it paints last
     and its 1px of Pebble runs across the dot's bottom edge — which reads as the
     dot being sliced flat rather than as a circle sitting on a line. */
  position:relative;z-index:1;
}
.seam-rule{
  position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--dp-pebble);
  transform:scaleX(0);transform-origin:center;
  transition:transform 1.2s var(--dp-ease) .5s;
}
.seam.is-in .seam-stem{transform:scaleY(1)}
.seam.is-in .seam-dot{transform:scale(1)}
.seam.is-in .seam-rule{transform:scaleX(1)}
@media (prefers-reduced-motion:reduce){
  .seam-stem,.seam-dot,.seam-rule{transition:none}
}

/* Section label. Same 10.5px / 700 / .11em uppercase as a finding card's
   document slug, with the same 7px dot, so the page reads as one family with
   the cards that orbit above it. */
.eyebrow{
  display:flex;align-items:center;gap:9px;
  font-size:10.5px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;
  color:var(--dp-anthracite);
}
.eyebrow::before{
  content:"";width:7px;height:7px;border-radius:50%;background:var(--dp-onyx);flex:none;
}
.eyebrow .num{color:var(--dp-muted);font-weight:600}

.sec-title{
  margin-top:clamp(20px,2.2vw,34px);max-width:17ch;
  font-size:clamp(2rem,5vw,4rem);       /* Heading Small -> Heading Big */
  font-weight:400;line-height:1.04;letter-spacing:-.035em;text-wrap:balance;
}
.sec-lede{
  margin-top:clamp(18px,2vw,30px);max-width:62ch;
  font-size:clamp(1rem,1.35vw,1.125rem);line-height:1.62;color:var(--dp-basalt);
}
.sec-lede + .sec-lede{margin-top:1.1em}
.sec-lede strong{font-weight:500;color:var(--dp-onyx)}

/* ------------------------------------------------------------------ blocks
   The finding card, grown up into a text block: same white ground, same 1.5px
   Pebble border, same 14px radius, same uppercase slug over a title, the same
   34px rule as an editorial mark, and the platform's own hover verbatim. */
/* Standing head over a group of blocks, so a grid of cards reads as an answer
   to the section's question rather than as loose topics. */
.blocks-head{
  margin-top:clamp(52px,6vw,92px);padding-bottom:14px;
  border-bottom:1px solid var(--dp-pebble);
  font-size:10.5px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;
  color:var(--dp-muted);
}
.blocks-head + .blocks{margin-top:clamp(18px,2vw,28px)}

/* Six columns, so a block can take two of them and be genuinely wider rather
   than merely first in a flexible row. */
.blocks{
  display:grid;gap:clamp(14px,1.5vw,22px);
  grid-template-columns:repeat(6,1fr);
  margin-top:clamp(40px,5vw,76px);
}
.block{grid-column:span 2}
.block--wide{grid-column:span 3}
.block--full{grid-column:1 / -1}
/* full-width block: the figure sets alongside the prose rather than above it,
   or a 3.4rem numeral on its own line leaves a hole the width of the page */
.block--full{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,2.2fr);
  gap:clamp(20px,3vw,56px);align-items:start}
.block--full .block-cat{grid-column:1 / -1}
.block--full .block-fig{margin-top:6px}
.block--full .block-rule{display:none}
.block--full .block-body{margin-top:0;font-size:clamp(1rem,1.25vw,1.125rem)}
@media (max-width:760px){
  .block--full{grid-template-columns:1fr}
  .block--full .block-body{margin-top:14px}
}
/* doubled class: `.block` sets its own white ground further down the file, and
   at equal specificity the later rule would win */
.block.block--alabaster{background:var(--alabaster);border-color:var(--alabaster-edge)}

/* The five languages, set as type rather than as a metadata line — this is the
   card most clients read first. */
.langs{display:flex;flex-wrap:wrap;gap:8px 10px;margin-top:20px}
.langs strong{
  font-size:.9375rem;font-weight:500;letter-spacing:-.01em;color:var(--dp-onyx);
  background:var(--marble);border:1px solid var(--dp-pebble);border-radius:999px;
  padding:6px 14px;
}
.block--alabaster:hover{border-color:var(--dp-onyx)}

/* four equal blocks, for a group that is a list rather than a hierarchy */
.blocks--four{grid-template-columns:repeat(4,1fr)}
.blocks--four .block{grid-column:span 1}

@media (max-width:1080px){
  .blocks,.blocks--four{grid-template-columns:repeat(4,1fr)}
  .block,.block--wide,.blocks--four .block{grid-column:span 2}
}
@media (max-width:640px){
  .blocks,.blocks--four{grid-template-columns:1fr}
  .block,.block--wide,.blocks--four .block{grid-column:auto}
}
.block{
  position:relative;background:#fff;
  border:1.5px solid var(--dp-pebble);border-radius:14px;
  padding:clamp(22px,2vw,30px) clamp(22px,2vw,30px) clamp(24px,2.2vw,32px);
  transition:transform .34s var(--dp-ease),box-shadow .34s var(--dp-ease),
             border-color .34s var(--dp-ease);
}
/* A livelier hover than the platform's 2px lift, at his ask (08-21): the card
   comes up 5px and grows a little, the shadow deepens, the border goes to
   Onyx, and the severity rule extends. The rule is the tell — it is the one
   part of the card that is a mark rather than a box, so extending it reads as
   the card opening rather than the whole thing merely inflating. */
.block:hover{
  transform:translateY(-5px) scale(1.014);
  box-shadow:0 22px 44px -26px rgba(0,0,0,.34);
  border-color:var(--dp-onyx);
}
.block-rule,.fcard-rule{transition:width .34s var(--dp-ease),background-color .34s var(--dp-ease)}
/* The severity rule grows 34 -> 64px on hover. One declaration for all three
   places a card appears: the blocks in 01, the cards in the thread, and (08-21,
   his ask) the orbiting cards in 02, which until now had only the lift. The
   34px rest width is the handoff's editorial mark and does not change. */
.block:hover .block-rule,
.page .fcard:hover .fcard-rule,
.fcard-orbit .fcard:hover .fcard-rule{width:64px}
/* Set a step up from a finding card's document slug (10.5px / .11em): on the
   page this line is the block's actual heading, not metadata, so it carries
   Onyx and a touch more size. Still the same uppercase family. */
.block-cat{
  display:flex;align-items:center;gap:9px;
  font-size:11.5px;font-weight:700;letter-spacing:.1em;
  text-transform:uppercase;color:var(--dp-onyx);
}
.block-dot{width:7px;height:7px;border-radius:50%;background:var(--dp-onyx);flex:none}
.block[data-flag="red"]   .block-dot,.block[data-flag="red"]   .block-rule{background:var(--dp-red)}
.block[data-flag="amber"] .block-dot,.block[data-flag="amber"] .block-rule{background:var(--dp-amber)}
.block[data-flag="green"] .block-dot,.block[data-flag="green"] .block-rule{background:var(--dp-green)}
.block-title{
  margin-top:12px;font-size:clamp(1.05rem,1.5vw,1.35rem);font-weight:500;
  line-height:1.24;letter-spacing:-.02em;text-wrap:pretty;
}
.block-rule{display:block;height:2px;width:34px;border-radius:2px;background:var(--dp-onyx);margin-top:16px}
.block-body{
  margin-top:16px;font-size:1rem;line-height:1.62;color:var(--dp-basalt);text-wrap:pretty;
}
.block-meta{
  margin-top:18px;font-size:10.5px;font-weight:600;letter-spacing:.01em;color:var(--dp-muted);
}

/* A figure inside a block: the number carries the point, the label explains it. */
.block-fig{
  font-size:clamp(2.4rem,4.4vw,3.4rem);font-weight:400;line-height:1;
  letter-spacing:-.05em;font-variant-numeric:tabular-nums;
}

/* ------------------------------------------------------------------- steps
   Numbered, one per row, so the sequence reads as a sequence. */
.steps{margin-top:clamp(40px,5vw,76px);border-top:1px solid var(--dp-pebble)}
.step{
  position:relative;
  display:grid;grid-template-columns:88px minmax(0,1.05fr) minmax(0,1.25fr);
  gap:clamp(18px,3vw,56px);align-items:start;
  padding:clamp(28px,3.4vw,48px) clamp(14px,1.6vw,24px);
  border-bottom:1px solid var(--dp-pebble);
  transition:background-color .4s var(--dp-ease);
}
/* Alabaster warms the row under the pointer (his ask, 08-21). The panel is
   bled out past the content line with a negative-inset pseudo-element rather
   than a background on the row itself, so the wash runs to the edge of the
   section the way the guide's informational panels do, while the type stays on
   the grid. Behind the content, so nothing has to be re-stacked. */
.step::before{
  content:"";position:absolute;
  top:0;bottom:0;left:calc(-1 * clamp(14px,1.6vw,24px));right:calc(-1 * clamp(14px,1.6vw,24px));
  background:var(--alabaster);opacity:0;
  transition:opacity .4s var(--dp-ease);
  pointer-events:none;
}
.step > *{position:relative}
.step:hover::before{opacity:1}
/* the index goes from grey to black as the row lights, so the eye lands on
   where it is in the sequence */
.step-num{
  font-size:10.5px;font-weight:700;letter-spacing:.11em;color:var(--dp-muted);
  padding-top:.5em;transition:color .4s var(--dp-ease);
}
.step:hover .step-num{color:var(--dp-onyx)}
@media (prefers-reduced-motion:reduce){
  .step,.step::before,.step-num{transition:none}
}
.step-title{
  font-size:clamp(1.3rem,2.4vw,2rem);font-weight:400;line-height:1.15;
  letter-spacing:-.03em;text-wrap:balance;
}
.step-body{font-size:1rem;line-height:1.62;color:var(--dp-basalt);text-wrap:pretty}
.step-body + .step-body{margin-top:1em}
.step-body strong{font-weight:500;color:var(--dp-onyx)}

/* The Sandstone `.callout` that used to close the benefits section was removed
   at his ask (08-21) — the "Dossi does not replace your notary" note. Its rules
   went with it; Sandstone now appears only on the thread's sender band. */

/* a call to action sitting inside a block, under its copy */
.block-act{margin-top:22px}

/* ============================================ 04b — HOW A FINDING IS FOUND ==
   Pinned and scroll-driven, the only section below the hero that is. Driven by
   src/catch.js through --lit / --underline / --on / --in. */
.catch{position:relative;background:var(--marble)}
/* The worked example takes the Alabaster ground: its white document panel and
   white finding card need a surface to sit ON, and this is the page's one
   full-bleed use of the colour. */
.catch--alabaster{background:var(--alabaster)}
.catch--alabaster .doc-chrome{background:var(--marble)}
.catch--alabaster .doc-fade{background:linear-gradient(to bottom,rgba(255,255,255,0),#fff)}
.catch-pin{position:relative;height:300vh}
.catch-stage{
  position:sticky;top:0;min-height:100vh;
  display:flex;flex-direction:column;justify-content:center;
  gap:clamp(24px,3vw,44px);
  padding:clamp(60px,7vw,110px) var(--edge);
  max-width:calc(var(--measure) + var(--edge) * 2);margin:0 auto;
}
.catch-head .sec-title{margin-top:clamp(14px,1.4vw,22px);max-width:20ch}
.catch-cols{
  display:grid;gap:clamp(22px,3vw,56px);
  grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);
  align-items:center;
}

/* ---- the document page. A fixed-height window onto a taller block of text;
   catch.js scrolls the block, so the page reads as being travelled down. */
.doc{
  position:relative;background:var(--marble);
  border:1.5px solid var(--dp-pebble);border-radius:14px;overflow:hidden;
  box-shadow:0 24px 60px -40px rgba(0,0,0,.3);
}
.doc-chrome{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:12px 17px;border-bottom:1px solid var(--dp-pebble);
  background:var(--alabaster);
  font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--dp-anthracite);
}
.doc-page{color:var(--dp-muted);font-weight:600;letter-spacing:.04em}
.doc-body{
  height:clamp(300px,44vh,440px);overflow:hidden;
  padding:clamp(20px,2vw,30px) clamp(20px,2.2vw,34px);
  font-size:12.5px;line-height:1.85;color:var(--dp-basalt);
  /* the reader is driven, never dragged */
  scrollbar-width:none;
}
.doc-body::-webkit-scrollbar{display:none}
.doc-body p + p{margin-top:1em}
.doc-h{font-weight:700;color:var(--dp-anthracite);letter-spacing:-.005em}
.doc-h:not(:first-child){margin-top:1.5em}

/* The clause. Sandstone ground washes in on the approach; the rule draws left
   to right as it arrives. Staggering the two is what makes it read as being
   noticed rather than merely switched on. */
.doc mark{
  --lit:0;--underline:0;
  position:relative;background:transparent;color:var(--dp-basalt);
  box-shadow:inset 0 0 0 100vmax rgba(255,248,219,calc(var(--lit)));
  transition:color .3s var(--dp-ease);
  color:color-mix(in srgb,var(--dp-onyx) calc(var(--lit) * 100%),var(--dp-basalt));
}
.doc mark::after{
  content:"";position:absolute;left:0;right:0;bottom:-2px;height:2px;
  background:var(--dp-red);border-radius:2px;
  transform:scaleX(var(--underline));transform-origin:left center;
}
.doc-fade{
  position:absolute;left:1px;right:1px;bottom:1px;height:56px;border-radius:0 0 13px 13px;
  background:linear-gradient(to bottom,rgba(255,255,255,0),#fff);
  pointer-events:none;
}

/* ---- the right column */
.catch-out{display:flex;flex-direction:column;gap:clamp(22px,2.4vw,34px)}

.catch-steps{list-style:none;display:flex;flex-direction:column;gap:12px}
.catch-step{
  --on:0;
  display:flex;align-items:center;gap:12px;
  font-size:13px;font-weight:500;letter-spacing:-.005em;
  color:color-mix(in srgb,var(--dp-onyx) calc(var(--on) * 100%),var(--dp-pebble));
}
.catch-step em{font-style:normal;font-weight:600}
.catch-tick{
  position:relative;width:16px;height:16px;flex:none;border-radius:50%;
  border:1.5px solid color-mix(in srgb,var(--dp-onyx) calc(var(--on) * 100%),var(--dp-pebble));
}
.catch-tick::after{
  content:"";position:absolute;inset:3px;border-radius:50%;
  background:var(--dp-onyx);transform:scale(var(--on));
}
.catch-step:last-child .catch-tick::after{background:var(--dp-red)}
.catch-step:last-child .catch-tick{
  border-color:color-mix(in srgb,var(--dp-red) calc(var(--on) * 100%),var(--dp-pebble));
}

/* the card assembles: rises, settles, and the quote follows it in */
.catch-card{--in:0}
.catch-card .fcard{
  opacity:var(--in);
  transform:translateY(calc((1 - var(--in)) * 22px)) scale(calc(.97 + .03 * var(--in)));
  box-shadow:0 calc(var(--in) * 26px) calc(var(--in) * 52px) -30px rgba(0,0,0,.4);
}
.catch-quote{
  margin-top:16px;padding-left:16px;border-left:2px solid var(--dp-pebble);
  opacity:clamp(0,calc(var(--in) * 2 - 1),1);
  transform:translateY(calc((1 - var(--in)) * 12px));
}
.catch-quote p{
  font-size:14px;line-height:1.6;color:var(--dp-anthracite);text-wrap:pretty;
}
.catch-quote cite{
  display:block;margin-top:9px;font-style:normal;
  font-size:10.5px;font-weight:600;letter-spacing:.01em;color:var(--dp-muted);
}
.catch-note{
  font-size:10.5px;font-weight:600;letter-spacing:.01em;color:var(--dp-muted);
}

@media (max-width:900px){
  .catch-pin{height:260vh}
  .catch-cols{grid-template-columns:1fr;gap:24px}
  .doc-body{height:clamp(240px,34vh,320px)}
}
@media (prefers-reduced-motion:reduce){
  .catch-pin{height:auto}
  .catch-stage{position:relative}
}

/* ------------------------------------------------------------------ thread
   The finding and the exchange it triggers, as one sequence in time. A single
   spine runs the length of it with a node per event, so this reads as four
   moments on one timeline rather than four cards in a row (his note: the
   timeline was bland and it was hard to tell who was who). Vertical, because
   a thread is vertical everywhere the reader has ever seen one. */
.thread{list-style:none;margin-top:clamp(40px,5vw,76px);max-width:760px}
.thread-item{display:grid;grid-template-columns:34px minmax(0,1fr);gap:clamp(14px,1.6vw,24px)}

/* the spine: one rule per row, so consecutive rows join seamlessly and the
   ends stop at their own node instead of overshooting */
.thread-rail{position:relative}
.thread-rail::before{
  content:"";position:absolute;left:50%;top:0;bottom:0;width:1px;margin-left:-.5px;
  background:var(--dp-pebble);
}
.thread-item:first-child .thread-rail::before{top:26px}
.thread-item:last-child .thread-rail::before{bottom:auto;height:26px}
.thread-node{
  position:absolute;left:50%;top:19px;width:13px;height:13px;margin-left:-6.5px;
  border-radius:50%;background:var(--marble);border:1.5px solid var(--dp-onyx);
}
/* The sender's colour rides the node as well as the panel, so the spine itself
   says who is speaking. Both are from the brand palette: Sandstone is ours,
   Alabaster is theirs. */
.thread-item[data-side="us"] .thread-node{background:var(--sandstone)}
.thread-item[data-side="them"] .thread-node{background:var(--alabaster);border-color:var(--dp-basalt)}
.thread-node--done{background:var(--dp-green);border-color:var(--dp-green)}

.thread-body{padding-bottom:clamp(30px,3.4vw,52px);min-width:0}
.thread-when{
  font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--dp-muted);margin-bottom:14px;
}
.thread-note{
  margin-top:14px;font-size:.9375rem;line-height:1.62;color:var(--dp-basalt);text-wrap:pretty;
}

/* The finding card again, at rest — the same component as the orbit above, so
   the thing being acted on is visibly the thing that was found.

   The ORBIT's cards keep the platform's exact hover (he asked for that
   verbatim, and the checker holds it there). Down here on the page the same
   component gets the page's livelier hover instead, because here it is a thing
   you can look at rather than a thing flying past. */
.thread .fcard{width:100%;cursor:default}
.page .fcard{
  transition:transform .34s var(--dp-ease),box-shadow .34s var(--dp-ease),
             border-color .34s var(--dp-ease);
}
.page .fcard:hover{
  transform:translateY(-5px) scale(1.014);
  box-shadow:0 22px 44px -26px rgba(0,0,0,.34);
  border-color:var(--dp-onyx);
}

/* the mail panels */
.mail{
  background:#fff;border:1.5px solid var(--dp-pebble);border-radius:14px;
  overflow:hidden;flex:0 0 auto;
  transition:transform .32s var(--dp-ease),box-shadow .32s var(--dp-ease),
             border-color .32s var(--dp-ease);
}
.mail:hover{
  transform:translateY(-5px) scale(1.008);
  box-shadow:0 22px 44px -26px rgba(0,0,0,.34);
  border-color:var(--dp-onyx);
}
/* Sender colour. Both from the brand palette, and applied to the header band
   only — the message itself stays white, so the panels still read as documents
   rather than as chat bubbles. */
.mail--us .mail-head{background:var(--sandstone);border-bottom-color:var(--sand-edge)}
.mail--them .mail-head{background:var(--alabaster);border-bottom-color:var(--alabaster-edge)}

.mail-head{
  padding:13px 17px;border-bottom:1px solid var(--dp-pebble);
  display:flex;flex-direction:column;gap:6px;background:#FCFCFC;
}
/* one line for who, instead of a From/To stack — it is a thread, the direction
   is already obvious from the spine */
.mail-who{
  display:flex;align-items:center;gap:9px;
  font-size:12px;font-weight:600;letter-spacing:-.005em;color:var(--dp-onyx);
}
.mail-who em{font-style:normal;font-weight:500;color:var(--dp-basalt)}
.mail-avatar{
  display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:22px;height:22px;border-radius:50%;
  background:var(--marble);border:1px solid rgba(0,0,0,.14);
  font-size:9px;font-weight:700;letter-spacing:.04em;color:var(--dp-onyx);
}
.mail-line{
  font-size:10.5px;font-weight:600;letter-spacing:.01em;color:var(--dp-muted);
  display:flex;gap:6px;
}
.mail-line b{font-weight:700;color:var(--dp-anthracite);min-width:38px}
.mail-subject{
  font-size:13px;font-weight:500;letter-spacing:-.005em;color:var(--dp-onyx);
}
/* the attachment their reply carries — the thing we were asking for */
.mail-attach{
  display:flex;align-items:center;gap:9px;
  padding:12px 17px;border-top:1px solid var(--dp-pebble);background:#FCFCFC;
  font-size:10.5px;font-weight:600;letter-spacing:.01em;color:var(--dp-anthracite);
}
.mail-attach::before{
  content:"";width:7px;height:7px;border-radius:1px;background:var(--dp-basalt);flex:none;
}
.mail-body{padding:16px 17px 18px;font-size:13.5px;line-height:1.66;color:var(--dp-basalt)}
.mail-body p + p{margin-top:.9em}
.mail-body .sig{color:var(--dp-anthracite)}
.mail-note{
  padding:12px 17px;border-top:1px solid var(--dp-pebble);background:#FCFCFC;
  font-size:10.5px;font-weight:600;letter-spacing:.01em;color:var(--dp-muted);
  display:flex;align-items:center;gap:8px;
}
.mail-note::before{
  content:"";width:7px;height:7px;border-radius:50%;background:var(--dp-green);flex:none;
}

/* ------------------------------------------------------------------ closing */
.closing{
  padding:clamp(90px,12vw,180px) var(--edge) clamp(70px,9vw,140px);
  border-top:1px solid var(--dp-pebble);
}

/* ------------------------------------------------------------------ SIGN-OFF
   The black band that ends every page of hendrikshousing.com, brought over at
   his ask. Measured off their own footer rather than eyeballed:

     background   rgb(14,13,13) — #0E0D0D, which is also dossierproof's own
                  `--graphite`, so this is a colour the three properties share
     wordmark     their `Hendriks-Housing-logo-wit.svg`, the real asset, saved
                  as /assets/hh-logo-white.svg — 160x39, so ratio 4.10
     width        640px inside their 1280 container at a 1440 viewport, i.e.
                  half the measure. Ours is expressed the same way.

   Not brought over: their Service / Areas / Contact link columns and their
   agency credit. Those are hendrikshousing.com's own navigation and have
   nothing to say on this page. */
.signoff{
  background:#0E0D0D;
  padding:clamp(64px,8vw,120px) var(--edge) clamp(40px,5vw,72px);
  text-align:center;
}
.signoff-mark{
  display:block;width:min(50%,640px);margin:0 auto;
  max-width:100%;height:auto;
}
.signoff-line{
  margin-top:clamp(30px,4vw,58px);
  font-size:10.5px;font-weight:600;letter-spacing:.11em;text-transform:uppercase;
  color:rgba(255,255,255,.52);
}
@media (max-width:700px){
  .signoff-mark{width:min(74%,300px)}
}
.closing--alabaster{background:var(--alabaster);border-top-color:var(--alabaster-edge)}
.closing--alabaster .foot{border-top-color:var(--alabaster-edge)}
.closing-in{max-width:var(--measure);margin:0 auto}
.closing-title{
  font-size:clamp(2rem,5vw,4rem);font-weight:400;line-height:1.04;
  letter-spacing:-.035em;max-width:16ch;text-wrap:balance;
}
.actions{display:flex;flex-wrap:wrap;gap:14px;margin-top:clamp(28px,3.4vw,48px)}
.foot{
  display:flex;flex-wrap:wrap;gap:10px 26px;align-items:baseline;
  margin-top:clamp(60px,8vw,120px);padding-top:22px;border-top:1px solid var(--dp-pebble);
  font-size:10.5px;font-weight:600;letter-spacing:.11em;text-transform:uppercase;
  color:var(--dp-muted);
}
.foot .spacer{margin-left:auto}

/* ================================================================ PILL =====
   Shape and colour: the platform's `.btn`, verbatim from its global.css —
   999px radius, 13px/26px padding, DM Sans 500 at 14px, -.005em, body
   line-height 1.62, onyx fill with marble type.

   Motion: veryworkinprogress.com's, read off their own bundle — a panel that
   slides in from the left over .5s cubic-bezier(.76,0,.24,1) while the label
   rolls up character by character over .35s with a 28ms per-character delay,
   revealing a second copy underneath in the contrasting colour.

   The platform's own flood-from-the-dot hover is intentionally not used here.
   ========================================================================== */
.pill{
  position:relative;display:inline-flex;align-items:center;justify-content:center;
  overflow:hidden;border-radius:999px;border:1px solid transparent;
  padding:13px 26px;background:var(--dp-onyx);color:var(--marble);
  font-family:inherit;font-size:.875rem;font-weight:500;letter-spacing:-.005em;
  white-space:nowrap;cursor:pointer;text-decoration:none;
}
.pill--ghost{background:transparent;color:var(--dp-onyx);border-color:var(--dp-onyx)}

.pill__bg{
  position:absolute;inset:0;border-radius:inherit;background:var(--marble);
  transform:translate(-105%);transition:transform .5s var(--vw-ease);
  z-index:0;pointer-events:none;
}
.pill--ghost .pill__bg{background:var(--dp-onyx)}
.pill:hover .pill__bg,.pill:focus-visible .pill__bg{transform:translate(0)}
/* overflow clips at the padding edge, so the 1px transparent border leaves the
   old background showing as a ring once the panel is in. The platform hits the
   same thing with its flood and solves it the same way: carry the border. */
.pill{transition:border-color .5s var(--vw-ease)}
.pill:hover,.pill:focus-visible{border-color:var(--marble)}
.pill--ghost:hover,.pill--ghost:focus-visible{border-color:var(--dp-onyx)}

/* one line box tall, so the second copy sits exactly one row below */
.pill__text{position:relative;z-index:1;display:block;height:1.62em;overflow:hidden}
.pill__row{display:flex;line-height:1.62}
.pill__row--top{position:relative}
.pill__row--bottom{position:absolute;top:100%;left:0;color:var(--dp-onyx)}
.pill--ghost .pill__row--bottom{color:var(--marble)}
.pill__char{
  display:inline-block;line-height:1.62;white-space:pre;
  transition:transform .35s var(--vw-ease);transition-delay:calc(var(--i) * 28ms);
}
.pill:hover .pill__char,.pill:focus-visible .pill__char{transform:translateY(-100%)}

@media (prefers-reduced-motion:reduce){
  .pill__bg{transition-duration:.01ms}
  .pill__char{transition:none;transition-delay:0s}
}

/* ============================================================== CURSOR =====
   veryworkinprogress.com's floating dot, ported from their bundle: 8px, white,
   mix-blend-mode difference, following the pointer on a 0.12 lerp, growing to
   44px over anything clickable. The blend mode is what lets one dot work over
   both the white page and the black photograph without knowing which it is on.
   ========================================================================== */
.cursor-dot{
  position:fixed;top:0;left:0;width:8px;height:8px;border-radius:50%;
  background:#fff;mix-blend-mode:difference;pointer-events:none;z-index:100001;
  will-change:transform;opacity:0;
  transition:width .25s ease,height .25s ease,opacity .2s ease,
             background-color .25s ease;
}
.cursor-dot.is-live{opacity:1}
/* Over a button the dot expands AND turns Alabaster (his ask, 08-21).
   The blend mode has to be dropped for it: `difference` inverts whatever is
   under the dot, so a fixed colour is impossible while it is on — over white
   an Alabaster swatch composites to #111D1D, which is the near-black he saw.
   Blend off, Alabaster on.
   The hairline is what makes that safe. The dot is Alabaster over the black
   pill (crisp) and over the white page (nearly the same value), and the two
   pills invert as their panels slide in — so without an edge it would vanish
   on the light half of the cycle. */
.cursor-dot--hover{
  width:44px;height:44px;
  mix-blend-mode:normal;
  /* Translucent, so the button's own label still reads through it (his note,
     08-21: at full opacity the dot covered the text). Alabaster at .55 over
     black still lands close to the swatch and stays clearly the brand colour,
     while the label underneath keeps enough contrast to be read. */
  background:color-mix(in srgb,var(--alabaster) 55%,transparent);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.16);
  /* the label has to survive being behind it, whichever way round the pill's
     hover has flipped its colours */
  backdrop-filter:saturate(1.05);
}
/* the native arrow is hidden only where the dot is actually running */
html.has-cursor-dot,html.has-cursor-dot *{cursor:none}
@media (hover:none),(pointer:coarse){.cursor-dot{display:none}}

/* ---------------------------------------------------------------- reveal */
[data-reveal]{
  opacity:0;transform:translateY(18px);
  transition:opacity .8s var(--dp-ease),transform .8s var(--dp-ease);
  transition-delay:var(--d,0ms);
}
[data-reveal].is-in{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){
  [data-reveal]{transition:none}
}

/* ------------------------------------------------- page responsive rules */
@media (max-width:860px){
  .step{grid-template-columns:1fr;gap:14px}
  .step-num{padding-top:0}
}
@media (max-width:700px){
  .sec-title{max-width:none}
  .actions .pill{width:100%}
}
