/* measure.so/v2 — built to the CoreShift reference frame-by-frame.
 *
 * DELIBERATELY NOT OUR BRAND. Oliver: "disregard anything you know about
 * our current brand energy or colors, you have to only use our logo and
 * that exact video template". So the palette, the type, the radii and
 * the motion all come from the nineteen frames in
 * scratchpad/frames/f001-f019.jpg, and the ONLY carry-over is the
 * wordmark.
 *
 * What the frames actually establish, measured off them rather than
 * remembered:
 *   - the whole site sits in ONE white rounded slab inset on a grey page
 *   - the nav is a centred white pill, ghost "Sign in" + black CTA pill
 *   - headlines are huge, geometric, very bold, tight, centred
 *   - accents are four saturated hues on rounded-square tiles
 *   - the hero assembles with a blur-in (frame 19 catches it mid-reveal)
 */

:root {
  --page:  #e6e6e7;        /* the grey the slab floats on */
  --slab:  #ffffff;
  --wash:  #f4f6f9;        /* section bands inside the slab */
  --ink:   #0b0b0f;
  --gray:  #6f7482;
  --line:  #e8eaef;

  /* THREE COLOURS AND A WHITE PAGE.
     Oliver: "white for the background for most of it, and then other
     accent colours can be the sky blue, navy blue like the logo, and
     then the neon green for the buttons - the same one that's on v1."

     So the carnival goes. The token NAMES stay, because forty rules
     reference them and renaming those is churn with no benefit, but
     each now points at the agreed palette:

       --violet  was #7c5cfc  ->  logo navy, the structural accent
       --cyan    was #35c3f3  ->  sky blue, unchanged, it was already
                                  the sky and it matches the hero
       --yellow  was #ffd84d  ->  a navy tint, so nothing reads gold
       --coral   was #f4553d  ->  v1's neon, #c9f542, buttons only
     --neon-ink is the dark ink neon needs; white on neon is unreadable
     at 1.6:1 and that is what the old coral rules assumed. */
  --violet: #16324e;
  --cyan:   #35c3f3;
  --yellow: #5b7d99;
  --coral:  #c9f542;
  --neon-ink: #16290a;

  --geo: "Poppins", "Sofia Pro", "Century Gothic", "Avenir Next",
         "Segoe UI Variable Display", system-ui, sans-serif;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--slab); color: var(--ink);
  font-family: var(--geo); font-size: 16px; line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* FULL BLEED. The reference frames all show the site as a rounded panel
   floating on grey, and I built that faithfully - but Oliver, looking at
   it: "I need it full bleed it's currently like on a card". He is right
   and the reference is not the client. A 1180px slab inset 22px on a
   2000px display spends a fifth of the screen on grey margin, and every
   band inside it inherits that ceiling, which is the same "thin in the
   middle of the screen" complaint from the landing pages wearing a
   different costume.

   The slab keeps its role as the white ground the bands sit on. It just
   stops being a card: no ceiling, no radius, no float. */
.slab {
  max-width: none; margin: 0; background: var(--slab);
  border-radius: 0; box-shadow: none;
}
/* NO `overflow: hidden` HERE, and it is not an oversight.
   An ancestor with overflow:hidden becomes the containing block for
   position:sticky, and a container that does not itself scroll cannot
   hold anything stuck - so every sticky descendant silently degrades to
   a static element that scrolls away. That is what Oliver hit: "it moves
   when I scroll, so I just completely lose it, and I'm on a blank screen
   as it's changing above the fold". The pinned sequence scrolled off,
   leaving 440vh of empty white behind it, and the nav pill was quietly
   broken the same way.
   The clip existed to round the corners of the old card. The card is
   gone, the radius is 0, and there is nothing left to clip. */

/* ---- nav: a centred pill, not a full-width bar ---------------------- */
.v2nav {
  /* FIXED, NOT STICKY, AND OUTSIDE .slab.
     It used to be sticky inside .slab - which begins BELOW the story -
     so it could never appear over the 3D model; it waited underneath it.
     Oliver: "it should sit during the three d model still at the top,
     and it can continue throughout the model and then follow the page
     down." Fixed does exactly that, and the z-index clears the story's
     own overlays. */
  /* CENTRED WITHOUT A TRANSFORM, on purpose. The nav also carries .rv,
     and the reveal's `html.js .rv.on { transform: none }` outranks a
     `transform: translateX(-50%)` here on specificity - so the centring
     was being cancelled the moment the nav revealed itself, parking it
     with its LEFT edge at 50%. left/right/margin-auto cannot be undone
     by a transform rule. Third collision between the reveal system and
     a component on this page. */
  position: fixed; top: 14px; left: 0; right: 0; margin: 0 auto;
  z-index: 60; width: fit-content;
  display: flex; align-items: center; gap: 26px;
  padding: 8px 8px 8px 20px;
  background: rgba(255,255,255,.92); backdrop-filter: blur(12px);
  border: 1px solid var(--line); border-radius: 999px;
  box-shadow: 0 1px 2px rgba(11,11,15,.04), 0 10px 24px rgba(11,11,15,.06);
}
.v2nav .mark img { height: 19px; display: block; }
.v2nav a.lnk {
  color: var(--ink); text-decoration: none; font-size: .875rem;
  font-weight: 500; opacity: .82;
}
.v2nav a.lnk:hover { opacity: 1; }
.v2nav .spacer { width: 6px; }
.btn-ghost, .btn-dark, .btn-coral {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; text-decoration: none; font-weight: 600;
  font-size: .875rem; padding: 9px 18px; white-space: nowrap;
  transition: transform .16s, box-shadow .16s, background .16s;
}
.btn-ghost { background: #fff; color: var(--violet);
  border: 1px solid rgba(22, 50, 78, .22); }
/* SIGN IN AND THE CTA ARE ONE BUTTON WITH TWO FILLS. They were 50px
   tall at 16px/400 and 40px at 14px/600 respectively - two different
   buttons sitting side by side. The geometry now comes from the shared
   rule above and only the fill differs: white with navy ink, or neon
   with dark ink. */
.btn-dark  { background: var(--coral); color: var(--neon-ink);
  border: 1px solid transparent; }   /* the ghost has a border; without
     a matching one these differ by exactly 2px of box */
.btn-dark:hover { filter: brightness(1.05); }
.btn-coral { background: var(--coral); color: var(--neon-ink); padding: 14px 30px;
             font-size: 1rem; box-shadow: 0 8px 20px rgba(150, 190, 40, .34); }
.btn-dark:hover, .btn-coral:hover { transform: translateY(-1px); }
.btn-coral:hover { box-shadow: 0 12px 28px rgba(150, 190, 40, .42); }

/* ---- hero ----------------------------------------------------------- */
.hero { padding: 96px 20px 66px; text-align: center; }
.t-cyan   { background: linear-gradient(160deg, #63d4f8, #22b2e6); }
.t-coral  { background: linear-gradient(160deg, #d8f96e, #b6e42a); }
/* The reference used two staff photos here. We do not sell people, so
   these are the quiet end of our own chain - the plan of a building and
   the rain that lands on it - held in white tiles so the four coloured
   ones still lead. */

h1.v2 {
  font-size: clamp(2.6rem, 6.4vw, 4.6rem); line-height: .98;
  letter-spacing: -.035em; font-weight: 700; margin: 0 0 18px;
}
.hero .sub {
  color: var(--gray); max-width: 30rem; margin: 0 auto 26px;
  font-size: 1.02rem; line-height: 1.5;
}

/* ---- the blur-in the reference actually uses (frame 19) -------------- */
/* THE CONTENT IS VISIBLE UNLESS JS SAYS OTHERWISE. This was
   `.rv { opacity: 0 }` unconditionally, so the page depended on a script
   running to show anything at all - and requestAnimationFrame DOES NOT
   FIRE IN A HIDDEN TAB. Open the page in a background tab, or let a
   screenshot tool load it unfocused, and every element stays at zero
   opacity indefinitely: a blank white slab with only the SVG wires
   showing, which is exactly what I photographed twice and misread once
   as an animation still in progress.
   The hiding is now scoped to html.js, set by an inline script in the
   head, so no-JS and throttled-rAF both get the page rather than
   nothing. The reveal is an enhancement, not a precondition. */
html.js .rv { opacity: 0; filter: blur(14px); transform: translateY(10px); }
html.js .rv.on {
  opacity: 1; filter: blur(0); transform: none;
  transition: opacity .7s cubic-bezier(.2,.7,.2,1),
              filter .7s cubic-bezier(.2,.7,.2,1),
              transform .7s cubic-bezier(.2,.7,.2,1);
}
@media (prefers-reduced-motion: reduce) {
  html.js .rv { opacity: 1; filter: none; transform: none; }
}

/* ---- band + cards --------------------------------------------------- */
.band { background: var(--wash); padding: 92px 28px; text-align: center; }
h2.v2 { font-size: clamp(2rem, 4.4vw, 3.1rem); line-height: 1.02;
  letter-spacing: -.03em; font-weight: 700; margin: 0 0 14px; }
.band .sub { color: var(--gray); max-width: 32rem; margin: 0 auto 40px; }
.cards {
  display: grid; gap: 22px; max-width: 1220px; margin: 0 auto;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); text-align: left;
}
.card {
  background: #fff; border: 1px solid var(--line); border-radius: 20px;
  padding: 0 0 22px; overflow: hidden;
  box-shadow: 0 1px 2px rgba(11,11,15,.04), 0 12px 30px rgba(11,11,15,.05);
  transition: transform .18s, box-shadow .18s;
}
.card:hover { transform: translateY(-3px);
  box-shadow: 0 2px 4px rgba(11,11,15,.06), 0 20px 44px rgba(11,11,15,.09); }
.card .top { height: 148px; background: var(--wash);
  border-bottom: 1px solid var(--line); display: grid; place-items: center; }
.card h3 { margin: 20px 22px 8px; font-size: 1.12rem; letter-spacing: -.01em; }
.card p { margin: 0 22px; color: var(--gray); font-size: .92rem; }

.foot2 { padding: 40px 20px 46px; text-align: center; color: var(--gray);
  font-size: .86rem; }
.foot2 a { color: var(--gray); }


/* =====================================================================
   HERO, FINISHED. Three things were wrong when it first rendered, all
   visible only on screen:

     1. the nav pill overflowed - "Measure an address" clipped, "Who it's
        for" wrapping to three lines - because a fit-content pill with
        eight children has no way to give way
     2. the tile graph ran past both edges of the slab, since the tiles
        were positioned in absolute px inside a box that scales
     3. nothing moved after the entrance. The reference is not static
        once it lands; the tiles breathe and the wires read as live.
   ===================================================================== */

/* ---- 1. the pill gives way instead of overflowing ------------------- */
.v2nav { max-width: calc(100% - 32px); }
.v2nav .lnk { white-space: nowrap; }
@media (max-width: 900px) {
  .v2nav { gap: 16px; padding-left: 16px; }
  .v2nav .lnk { font-size: .82rem; }
}
@media (max-width: 780px) {
  /* Below this the links cannot fit beside two buttons at a legible
     size, so they leave rather than shrink into unreadability. The
     sections they point at are still reachable by scrolling, which is
     what a visitor on a phone does anyway. */
  .v2nav .lnk { display: none; }
  .v2nav { gap: 10px; padding: 7px 7px 7px 16px; }
  .btn-ghost, .btn-dark { padding: 8px 14px; font-size: .82rem; }
}

/* The hero's tile graph and its wiring were removed with the tiles
   themselves. Everything that styled them - .wire, .tile, .t-*, the
   floaty/draw/pop keyframes and their reduced-motion overrides - is
   gone with them.

   MY FIRST ATTEMPT AT THIS LEFT BROKEN CSS, not merely dead CSS. A
   regex deleted `.wire { ... }` and welded the orphaned selector onto
   the next at-rule as `.wire @media (max-width: 880px) {`, and deleted
   `@keyframes floaty {` leaving its `50% { ... }` body dangling. Both
   are parse errors, and a CSS parse error does not fail loudly - the
   browser skips to the next brace it can match and silently drops
   whatever it swallowed on the way. */


/* =====================================================================
   SECTION 3 - HOW IT WORKS
   A set-piece, not three more cards. It draws the product's real
   sequence: drop the address point, take the outline, call each wall.
   ===================================================================== */

.how { padding: 104px 28px; background: var(--slab); }
.howin {
  max-width: 1220px; margin: 0 auto;
  display: grid; grid-template-columns: minmax(0,.86fr) minmax(0,1.14fr);
  gap: 64px; align-items: center;
}
.howtx { text-align: left; }
.eyebrow {
  margin: 0 0 14px; font-size: .74rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--violet);
}
.how h2.v2 { text-align: left; font-size: clamp(1.9rem, 3.3vw, 2.7rem); }

.steps { list-style: none; margin: 34px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 6px; }
.steps li {
  display: flex; gap: 16px; padding: 16px 18px;
  border-radius: 16px; border: 1px solid transparent;
  transition: background .45s, border-color .45s;
}
/* The list is the diagram's caption, so it lights with it rather than
   on hover - a visitor watching the figure should not have to guess
   which sentence the picture is currently illustrating. */
.steps li.lit { background: var(--wash); border-color: var(--line); }
.steps .num {
  flex: 0 0 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center; font-size: .84rem; font-weight: 700;
  background: #eceef3; color: var(--gray);
  transition: background .45s, color .45s, transform .45s;
}
.steps li.lit .num { background: var(--violet); color: #fff; transform: scale(1.06); }
.steps h3 { margin: 3px 0 4px; font-size: 1.04rem; letter-spacing: -.01em; }
.steps p  { margin: 0; color: var(--gray); font-size: .92rem; }

/* ---- the stage ------------------------------------------------------ */
.howfig { min-width: 0; }
.stage {
  position: relative; background: var(--wash);
  border: 1px solid var(--line); border-radius: 26px; overflow: hidden;
  box-shadow: 0 1px 2px rgba(11,11,15,.04), 0 18px 44px rgba(11,11,15,.06);
}
.stage .fig { display: block; width: 100%; height: auto; }
.badge {
  position: absolute; left: 16px; top: 14px;
  font-size: .68rem; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--gray);
  background: rgba(255,255,255,.9); border: 1px solid var(--line);
  padding: 5px 10px; border-radius: 999px;
}

/* the address point drops, then pings twice */
.stage .pin { opacity: 0; transform: translateY(-40px);
  transition: opacity .4s, transform .62s cubic-bezier(.2,1.5,.4,1); }
.stage.s1 .pin, .stage.s2 .pin, .stage.s3 .pin { opacity: 1; transform: none; }
.stage .ping { opacity: 0; transform-origin: 280px 214px; }
.stage.s1 .ping { animation: ping 1.5s ease-out .5s 2; }
@keyframes ping {
  from { opacity: .9; transform: scale(.4); }
  to   { opacity: 0;   transform: scale(4.2); }
}

/* the outline draws itself */
.stage .outline { stroke-dasharray: 720; stroke-dashoffset: 720;
  transition: stroke-dashoffset 1.15s cubic-bezier(.5,0,.2,1); }
.stage.s2 .outline, .stage.s3 .outline { stroke-dashoffset: 0; }
.stage .fillp { transition: opacity .8s .35s; }
.stage.s2 .fillp, .stage.s3 .fillp { opacity: .09; }

/* then each wall is called, one after another */
.stage .w { stroke-dasharray: 260; stroke-dashoffset: 260;
  transition: stroke-dashoffset .5s cubic-bezier(.4,0,.2,1); }
.stage.s3 .w { stroke-dashoffset: 0; }
.stage.s3 .w1 { transition-delay: .05s; }
.stage.s3 .w2 { transition-delay: .28s; }
.stage.s3 .w3 { transition-delay: .51s; }
.stage.s3 .w4 { transition-delay: .74s; }
.stage.s3 .w5 { transition-delay: .97s; }

/* the readouts, which are the point of the whole figure */
.chip {
  position: absolute; font-size: .76rem; font-weight: 600; color: var(--ink);
  background: #fff; border: 1px solid var(--line); border-radius: 999px;
  padding: 7px 13px; white-space: nowrap;
  box-shadow: 0 6px 18px rgba(11,11,15,.10);
}
/* Same reasoning as .tlist li: the chips ARE the readouts, so they are
   visible by default and only hidden where something can un-hide them. */
html.js .chip {
  opacity: 0; transform: translateY(8px) scale(.96);
  transition: opacity .4s, transform .4s cubic-bezier(.2,1.3,.4,1);
}
.chip em { font-style: normal; font-variant-numeric: tabular-nums; }
.c1 { left: 6%;  top: 25%; }
.c1 em { color: #0d6f92; }   /* was #1c9fd0, 3.03:1 on the white chip */
.c2 { right: 3%; top: 46%; }
.c2 em { color: var(--coral); }
.c3 { left: 47%; top: 6%; }
.c3 em { color: #0d6f92; }
.stage.s3 .chip { opacity: 1; transform: none; }
.stage.s3 .c1 { transition-delay: .55s; }
.stage.s3 .c2 { transition-delay: .8s; }
.stage.s3 .c3 { transition-delay: 1.15s; }

@media (max-width: 940px) {
  .howin { grid-template-columns: 1fr; gap: 40px; }
  .how h2.v2, .howtx { text-align: center; }
  .steps { text-align: left; max-width: 34rem; margin-inline: auto; }
  .how { padding: 78px 22px; }
}
@media (max-width: 560px) {
  .chip { font-size: .66rem; padding: 5px 9px; }
  .c1 { left: 2%; } .c2 { right: 1%; } .c3 { left: 40%; }
}

/* ---- scroll reveal --------------------------------------------------
   Same rule the hero learned the hard way: the hiding is scoped to
   html.js, so a blocked script or an absent IntersectionObserver leaves
   the section fully readable rather than blank. */
/* CONTENT IS NEVER HIDDEN BEHIND JAVASCRIPT. NOT AGAIN.

   This system has now blanked a section on Oliver four separate times:
   the hero when rAF did not fire in a background tab, the step list when
   two components shared the class name `on`, six panels a scroll jump
   skipped, and the how-it-works figure - which sat at opacity 0 with the
   `.on` class correctly applied, still invisible, and I could not tell
   you today why that last one lost.

   That final sentence is the argument. A reveal that hides content by
   default has a failure mode where the page is blank, and every fix
   ships another way to reach it. So the base state is now VISIBLE, and
   the reveal is an animation played on arrival rather than the thing
   that grants visibility. If the observer never fires, the reader sees
   the page. If it fires, they see it slide in.

   The blank-section class of bug is now unreachable, rather than fixed. */
html.js .sr { opacity: 1; }
html.js .sr.on { animation: srin .7s cubic-bezier(.2,.7,.2,1) both; }
@keyframes srin {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  html.js .sr, html.js .sr.on { opacity: 1; transform: none; animation: none; }
  .stage .pin, .stage .fillp { opacity: 1; transform: none; }
  .stage .outline, .stage .w { stroke-dashoffset: 0; }
  .stage .chip { opacity: 1; transform: none; }
  .stage .ping { display: none; }
}


/* =====================================================================
   SECTION 4 - THE OUTPUT
   Section 3 explains the method. Nobody buys a method. This is the
   artefact, so it is styled as the readout rather than as marketing.
   ===================================================================== */
.band.out { background: var(--wash); }
.readout {
  max-width: 760px; margin: 0 auto; text-align: left;
  background: #fff; border: 1px solid var(--line); border-radius: 22px;
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(11,11,15,.04), 0 20px 48px rgba(11,11,15,.07);
}
.rhead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
  padding: 18px 24px; border-bottom: 1px solid var(--line);
}
.raddr { font-weight: 600; letter-spacing: -.01em; }
.rpill {
  font-size: .72rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: #1a7f4b;
  background: #e8f7ee; border: 1px solid #cbead8;
  padding: 5px 11px; border-radius: 999px;
}
.wlist { list-style: none; margin: 0; padding: 6px 0; }
.wrow {
  display: grid; grid-template-columns: 7.5rem 1fr 11rem;
  align-items: center; gap: 16px; padding: 13px 24px;
}
.wrow + .wrow { border-top: 1px solid #f2f4f8; }
.wn { color: var(--gray); font-size: .9rem; }
.wbar { height: 8px; border-radius: 99px; background: #eef1f6; overflow: hidden; }
/* The bar is the measurement, so it draws rather than appearing - and it
   draws only once the panel has been revealed, which is what ties it to
   the scroll rather than to page load. */
.wbar i { display: block; height: 100%; width: 0; border-radius: 99px;
  transition: width .9s cubic-bezier(.3,.8,.3,1); }
.wbar i.co { background: linear-gradient(90deg, #ff8f79, #ef4227); }
.wbar i.cy { background: linear-gradient(90deg, #7fdcf9, #22b2e6); }
.readout.on .wbar i { width: var(--w); }
.wrow:nth-child(2) .wbar i { transition-delay: .10s; }
.wrow:nth-child(3) .wbar i { transition-delay: .20s; }
.wrow:nth-child(4) .wbar i { transition-delay: .30s; }
.wv { font-size: .88rem; text-align: right; color: var(--gray); }
.wv em { font-style: normal; font-weight: 700; font-variant-numeric: tabular-nums; }
.wv.co em { color: var(--coral); }
.wv.cy em { color: #0d6f92; }
.rfoot {
  display: flex; gap: 22px; flex-wrap: wrap; align-items: baseline;
  padding: 16px 24px; background: var(--wash); border-top: 1px solid var(--line);
  font-size: .86rem; color: var(--gray);
}
.rfoot b { color: var(--ink); font-variant-numeric: tabular-nums; }
.rnote { margin-left: auto; font-size: .68rem; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; }

/* =====================================================================
   SECTION 5 - THE CLOSE
   The only section that asks for anything, so it is the only one that
   gets the full-bleed colour.
   ===================================================================== */
.close {
  padding: 104px 28px 116px; text-align: center;
  background: var(--violet);
  color: #fff;
}
.close h2.v2 { color: #fff; margin-bottom: 16px; }
.close .sub { color: rgba(255,255,255,.86); max-width: 30rem; margin: 0 auto 34px; }
.tryform {
  display: flex; gap: 10px; max-width: 34rem; margin: 0 auto;
  background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.3);
  padding: 8px; border-radius: 999px; backdrop-filter: blur(8px);
}
.tryform input {
  flex: 1 1 auto; min-width: 0; border: 0; background: #fff; color: var(--ink);
  font: inherit; font-size: .95rem; padding: 13px 20px; border-radius: 999px;
}
.tryform input::placeholder { color: #98a0ae; }
.tryform input:focus-visible { outline: 3px solid rgba(255,255,255,.75);
  outline-offset: 2px; }
.tryform .btn-coral { flex: 0 0 auto; background: var(--coral);
  color: var(--neon-ink); box-shadow: none; }
.tryform .btn-coral:hover { filter: brightness(1.05); box-shadow: none; }

@media (max-width: 620px) {
  .wrow { grid-template-columns: 6rem 1fr; row-gap: 6px; }
  .wrow .wv { grid-column: 2; text-align: left; }
  .tryform { flex-direction: column; border-radius: 26px; }
  .tryform input, .tryform .btn-coral { width: 100%; }
  .close { padding: 76px 22px 84px; }
}


/* =====================================================================
   SECTION 2 - the cards get scenes, not spot icons
   A spot icon says "this is a category". A small scene says what the
   reader actually receives, which is the job the section has to do.
   ===================================================================== */
.card .top { padding: 0 10px; }
.mini { width: 100%; height: 100%; max-height: 128px; }

/* 1. the street, with the one house that has the problem */
.m1 .hit path { transition: transform .5s cubic-bezier(.2,1.4,.4,1); transform-origin: 116px 92px; }
.card.on .m1 .hit path { transform: translateY(-3px); }
html.js .m1 .drop { opacity: 0; transform: translateY(-10px);
  transition: opacity .45s .35s, transform .55s .35s cubic-bezier(.2,1.5,.4,1); }
.card.on .m1 .drop { opacity: 1; transform: none; }

/* 2. the portfolio, ranked - the bars grow because the ranking is the
      product, not the decoration */
.m2 .bv { transform: scaleX(0); transform-origin: 42px 0;
  transition: transform .75s cubic-bezier(.3,.8,.3,1); transition-delay: var(--d); }
.card.on .m2 .bv { transform: none; }

/* 3. the parcel: boundary draws, then the building lands on it */
.m3 .lot { stroke-dasharray: 340; stroke-dashoffset: 340;
  transition: stroke-dashoffset 1s cubic-bezier(.5,0,.2,1); }
.card.on .m3 .lot { stroke-dashoffset: 0; }
html.js .m3 .pad { opacity: 0; transform: scale(.9);
  transform-origin: 101px 62px;
  transition: opacity .5s .55s, transform .6s .55s cubic-bezier(.2,1.4,.4,1); }
.card.on .m3 .pad { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .m1 .drop, .m3 .pad { opacity: 1; transform: none; }
  .m2 .bv { transform: none; }
  .m3 .lot { stroke-dashoffset: 0; }
}


/* =====================================================================
   THE ROUTER - four readers, four doors
   ===================================================================== */
.band.router { background: var(--wash); }
/* TWO AND TWO, not three and a widow. auto-fit was packing three cards
   on a wide screen and dropping the fourth onto its own row, which reads
   as a mistake rather than a layout. Four audiences is a 2x2 grid;
   saying so explicitly is also stabler than letting the viewport decide
   how lonely the last card looks. */
.routes { display: grid; gap: 18px; max-width: 1080px; margin: 0 auto;
  grid-template-columns: repeat(2, minmax(0, 1fr)); text-align: left; }
@media (max-width: 760px) { .routes { grid-template-columns: 1fr; } }
.route {
  position: relative; display: block; text-decoration: none; color: inherit;
  background: #fff; border: 1px solid var(--line); border-radius: 22px;
  padding: 28px 28px 26px; overflow: hidden;
  display: flex; flex-direction: column; gap: 10px;
  box-shadow: 0 1px 2px rgba(11,11,15,.04), 0 14px 34px rgba(11,11,15,.05);
  transition: transform .22s, box-shadow .22s, border-color .22s;
}
/* THE RAIL IS GONE. It was carried over from v1 deliberately and Oliver
   is right that it does not belong: coloured left rails, tiny uppercase
   action links and a price whispered in grey are v1's language, and
   having them sit two sections above v2's pricing cards made the page
   look assembled from two sites. These are v2 cards now - same white
   ground, same hairline border, same radius, same real button - and the
   audience colour survives only in the eyebrow, which is where v2
   already puts a category. */
.route:hover { transform: translateY(-4px); border-color: #dfe3ea;
  box-shadow: 0 2px 5px rgba(11,11,15,.06), 0 26px 54px rgba(11,11,15,.09); }
.reyebrow { display: block; font-size: .7rem; font-weight: 700;
  /* WAS var(--rail), AND --rail NO LONGER EXISTS. The per-audience rail
     colours were defined on .r-vio/.r-cya/.r-yel/.r-cor and I deleted
     that block when the rails came off the cards - taking the token with
     it and leaving this one reference behind. An undefined custom
     property is not ignored: the declaration is invalid at
     computed-value time and colour falls back to INHERITED, so all four
     eyebrows quietly became body ink instead of an accent. Same failure
     that painted white text on a white button, found by the same audit.
     Navy, which is what the palette says an eyebrow is. */
  letter-spacing: .12em; text-transform: uppercase; color: var(--violet);
  margin-bottom: 4px; }
.route h3 { margin: 0 0 8px; font-size: 1.16rem; letter-spacing: -.015em; }
.route p { margin: 0 0 18px; color: var(--gray); font-size: .93rem; max-width: 30rem; }

/* =====================================================================
   PRICING
   ===================================================================== */
.pricing { background: var(--wash); padding: 100px 28px 108px; text-align: center; }
.pricing .sub { color: var(--gray); max-width: 33rem; margin: 0 auto 46px; }
.tiers { display: grid; gap: 20px; max-width: 1080px; margin: 0 auto;
  grid-template-columns: repeat(auto-fit, minmax(17.5rem, 1fr)); text-align: left; }
.tier {
  position: relative; background: #fff; border: 1px solid var(--line);
  border-radius: 22px; padding: 30px 28px 32px;
  display: flex; flex-direction: column;
  box-shadow: 0 1px 2px rgba(11,11,15,.04), 0 14px 34px rgba(11,11,15,.05);
  transition: transform .22s, box-shadow .22s;
}
.tier:hover { transform: translateY(-4px);
  box-shadow: 0 2px 5px rgba(11,11,15,.06), 0 26px 54px rgba(11,11,15,.09); }
/* The glow was still the old violet in raw rgba, so the token remap
   could not reach it - hardcoded colour survives a palette change,
   which is the argument for tokens in one line. Sky blue now, to match
   the hero photograph. */
.tier.feat { border-color: rgba(22, 50, 78, .30);
  box-shadow: 0 2px 6px rgba(22, 50, 78, .12),
              0 22px 50px rgba(22, 50, 78, .20); }
/* "BEST VALUE", because the two before it were both claims about
   customers we cannot support. It said "Most bought" first - invented
   while laying the cards out. That was replaced with "Most contractors
   start here", which is the SAME kind of claim about what contractors
   do, and the note justifying it pointed at the $2.00 rate, which is a
   fact about the price and not about anybody's behaviour. Oliver asked
   for "Best value" on 2026-08-21. It is a positioning statement rather
   than a measurement, which is the honest register for a badge, and it
   is the one the $2.00-per-lookup rate on pricing.html actually
   supports. */
.tier.feat::after {
  content: "Best value"; position: absolute; top: -11px; left: 28px;
  font-size: .66rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: #fff; background: var(--violet);
  padding: 5px 11px; border-radius: 999px;
}
.tname { font-size: .74rem; font-weight: 700; letter-spacing: .13em;
  text-transform: uppercase; color: var(--gray); }
.tprice { display: flex; align-items: baseline; gap: 7px; margin: 14px 0 6px; }
.tprice em { font-style: normal; font-size: .8rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .1em; color: #5f6675; }
.tprice b { font-size: 2.9rem; font-weight: 700; letter-spacing: -.035em;
  line-height: 1; font-variant-numeric: tabular-nums; }
.tprice .cur { font-size: 1.5rem; vertical-align: super; margin-right: 1px; }
.tsub { margin: 0 0 20px; color: var(--gray); font-size: .9rem; }
.tlist { list-style: none; margin: 0 0 26px; padding: 0; display: flex;
  flex-direction: column; gap: 10px; }
/* The ticks arrive one after another, so the list reads as it fills.
   THE HIDING IS SCOPED TO html.js AND THE REVEALING IS NOT. .tier gains
   its .on from script; the rule that hid these was unconditional, so on
   any page where that script did not run - a parse error, a blocked
   file, a browser with JS off - every plan's feature list was invisible
   and the pricing table listed prices for nothing. What a page shows
   must not depend on an animation getting a chance to finish. */
.tlist li { position: relative; padding-left: 26px; font-size: .91rem;
  color: #3d4655; }
html.js .tlist li { opacity: 0; transform: translateX(-6px);
  transition: opacity .4s, transform .4s; }
.tier.on .tlist li { opacity: 1; transform: none; }
.tier.on .tlist li:nth-child(1) { transition-delay: .30s; }
.tier.on .tlist li:nth-child(2) { transition-delay: .40s; }
.tier.on .tlist li:nth-child(3) { transition-delay: .50s; }
.tier.on .tlist li:nth-child(4) { transition-delay: .60s; }
.tlist li::before {
  content: ""; position: absolute; left: 3px; top: .42em;
  width: 11px; height: 6px; border-left: 2px solid var(--violet);
  border-bottom: 2px solid var(--violet); transform: rotate(-45deg);
}
.tbtn { margin-top: auto; display: block; text-align: center; text-decoration: none;
  font-weight: 600; font-size: .93rem; padding: 13px 18px; border-radius: 999px;
  background: #f2f3f7; color: var(--ink); transition: background .18s, color .18s; }
.tbtn:hover { background: #e7e9ef; }
.tier.feat .tbtn { background: var(--ink); color: #fff; }
.tier.feat .tbtn:hover { background: #24242e; }

@media (max-width: 560px) {
  .pricing { padding: 74px 22px 82px; }
  .route { padding: 22px 20px 18px 24px; }
}


/* =====================================================================
   THE BUNDLE: routing and pricing are one decision
   Oliver: "we should bundle those sections". Choosing who you are and
   choosing what you pay is a single act, and splitting it across two
   bands made the reader re-orient twice for one answer.
   ===================================================================== */
.bundle-rule { max-width: 1180px; margin: 72px auto 4px; height: 1px;
  background: linear-gradient(90deg, transparent, var(--line) 18%,
    var(--line) 82%, transparent); }
.band.router .tiers { margin-top: 42px; }

/* THE ROUTER'S ACTION LINE. `.rfoot` was already taken by the section-4
   readout, where it is a grey-filled footer bar - so the router's action
   inherited a grey slab that made every card look like it ended in a
   disabled button. Oliver: "I don't like the look of the buttons that
   lead to the funnel". They are not buttons any more and they never
   should have been: the whole card is the link, so the action is a
   quiet label with an arrow.
   Second time one class name has served two components on this page.
   The first cost the step list its reveal. */
.rmeta { margin-top: auto; padding-top: 18px;
  display: flex; flex-direction: column; gap: 12px; align-items: stretch; }
.rmeta .ract {
  order: 2; text-align: center; font-size: .93rem; font-weight: 600;
  letter-spacing: 0; text-transform: none; color: var(--ink);
  background: #f2f3f7; padding: 13px 18px; border-radius: 999px;
  transition: background .18s;
}
.route:hover .rmeta .ract { background: #e7e9ef; }
.rmeta .ract i { font-style: normal; display: inline-block;
  transition: transform .25s cubic-bezier(.2,1.4,.4,1); }
.route:hover .rmeta .ract i { transform: translateX(5px); }
.rmeta .rprice { order: 1; font-size: .8rem; letter-spacing: 0;
  text-transform: none; color: #5f6675; font-variant-numeric: tabular-nums; }   /* was #9aa2b1, 2.57:1 */

/* FIVE tiers now - Pro at $399 had no card, so the Platform card named
   a plan the page would not sell.

   THE COLUMN COUNT IS NEVER WRITTEN DOWN, and that is the whole point.
   This rule first said `repeat(5, 1fr)`, which reserves five tracks
   whether or not five cards exist. Oliver caught it within the hour:
   his browser still held the four-card markup and had already taken
   this stylesheet, so the cards filled tracks 1-4 and the empty fifth
   track pushed the whole block visibly left of centre. A deploy skew
   made it visible, but the fault is the hard-coded count - the same
   hole opens the moment anyone adds or removes a card and does not
   think to come here.

   auto-fit COLLAPSES tracks it cannot fill, so the row always holds
   exactly as many columns as there are cards. The track max is a
   length rather than 1fr so the leftovers are real space, and
   justify-content centres them - which is also what stops a wrapped
   row (five cards over four columns) from hugging the left edge.
   THE TRACK MAX IS WHAT SETS THE COLUMN COUNT, not the min: auto-fit
   fits as many tracks as it can at their MAXIMUM size. 15rem was the
   first try and it wrapped to 4+1 on a 1280px screen, because
   5x240+64 overflows the 1224px left after the section padding. 14rem
   gives 5x224+64 = 1184 and holds five across down to about 1240px,
   then steps to 4+1, 3+2, 2+2+1 - each centred. Six never fits. */
.tiers { grid-template-columns: repeat(auto-fit, minmax(12.5rem, 14rem));
  justify-content: center; max-width: 1280px; gap: 16px; }
.tier { padding: 26px 22px 28px; }
@media (max-width: 560px) {
  /* One up, and stretched: a 15rem card marooned in the middle of a
     phone reads as a rendering failure rather than a choice. */
  .tiers { grid-template-columns: minmax(0, 1fr); max-width: 26rem; }
}
.tprice em { align-self: flex-end; padding-bottom: .35rem; }
.tprice b.talk { font-size: 1.9rem; letter-spacing: -.02em; }
.tier .tbtn { margin-top: auto; }


/* =====================================================================
   THE LEAD PACKS - one long bar under the four plans
   Deliberately NOT a fifth card. A card would say "another plan"; this
   is a different kind of purchase - you are buying addresses, not the
   tool - and the shape should say so before the words do.
   ===================================================================== */
.leadbar {
  max-width: 1180px; margin: 22px auto 0; text-align: left;
  display: grid; grid-template-columns: minmax(0,1.25fr) minmax(0,.85fr);
  gap: 44px; align-items: center;
  background: var(--violet); color: #fff; border-radius: 22px;
  padding: 34px 38px;
  box-shadow: 0 2px 6px rgba(11,11,15,.10), 0 26px 60px rgba(11,11,15,.18);
}
.lbeye { display: block; font-size: .7rem; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--coral); margin-bottom: 12px; }
.leadbar h3 { margin: 0 0 12px; font-size: clamp(1.3rem, 2.2vw, 1.75rem);
  letter-spacing: -.025em; line-height: 1.1; }
.leadbar p { margin: 0 0 22px; color: rgba(255,255,255,.7); font-size: .95rem;
  max-width: 38rem; }
.lbcta { display: inline-block; text-decoration: none; font-weight: 600;
  font-size: .93rem; padding: 13px 24px; border-radius: 999px;
  background: #fff; color: var(--ink); transition: transform .18s; }
.lbcta:hover { transform: translateY(-2px); }

.packs { list-style: none; margin: 0; padding: 0; }
.packs li { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px;
  padding: 11px 0; font-variant-numeric: tabular-nums; }
.pkhead { font-size: .68rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: rgba(255,255,255,.42);
  border-bottom: 1px solid rgba(255,255,255,.14); padding-bottom: 8px; }
.pk { border-bottom: 1px solid rgba(255,255,255,.08); }
.pkn { font-size: 1.02rem; font-weight: 600; }
.pkp b { font-size: 1.02rem; }
.pke { color: rgba(255,255,255,.62); font-size: .92rem; }
.pkx { display: block !important; padding-top: 14px; font-size: .84rem;
  color: rgba(255,255,255,.62); }
.pkx b { color: #fff; }

@media (max-width: 860px) {
  .leadbar { grid-template-columns: 1fr; gap: 26px; padding: 28px 24px; }
}


/* ---------------------------------------------------------- documents
   The twelve report products, with a real sample behind each card.
   Shaped off .tier and .route so the block reads as part of the page
   rather than bolted onto it. */
/* THE PAGE GUTTER IS 28px AND IT COMES FROM .band. This section sits
   in .slab, which has none, so on a phone the cards ran flush to both
   screen edges while the pricing tiles beside them sat properly inset
   at 28. Same gutter, same max-width as .tiers, so the two blocks line
   up instead of one looking like a rendering fault. */
.instant, .docs, .docsintro{max-width:1080px;margin-left:auto;
  margin-right:auto;padding-left:28px;padding-right:28px}
.instant{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;
  margin-top:22px;margin-bottom:26px}
.instant .ibit{background:var(--wash);border:1px solid var(--line);
  border-radius:12px;padding:16px 18px}
.instant .ibit b{display:block;font-family:var(--geo);font-weight:600;
  font-size:1.05rem;color:var(--violet);letter-spacing:-.01em}
.instant .ibit span{display:block;margin-top:4px;color:var(--gray);
  font-size:.9rem;line-height:1.45}
@media(max-width:860px){.instant{grid-template-columns:1fr}}

.docs{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
/* A phone gets one column and a little more room inside each card:
   three lines of label at 20px padding on a 375px screen leaves 335,
   and the price and the call to action then wrap onto each other. */
@media(max-width:560px){
  /* 28px, NOT 20. .band uses 28 at every width, so the pricing tiles
     below sit at 28 on a phone too - a documents block at 20 reads as
     a misalignment the moment you scroll past it. The room comes back
     out of the card's own padding instead, where nobody can see the
     difference. */
  .doc{padding:15px 17px 13px}
  .instant .ibit{padding:13px 16px}
  .instant{gap:9px;margin-top:18px;margin-bottom:20px}
  .docs{gap:9px}
  .doc h3{font-size:1.06rem}
  .doc p{font-size:.9rem}
  .docsintro{padding-bottom:0}
}
@media(max-width:1000px){.docs{grid-template-columns:repeat(2,1fr)}}
@media(max-width:680px){.docs{grid-template-columns:1fr}}
.doc{display:flex;flex-direction:column;gap:6px;background:var(--slab);
  border:1px solid var(--line);border-radius:14px;padding:18px 20px 16px;
  text-decoration:none;color:inherit;transition:border-color .16s,
  transform .16s,box-shadow .16s}
.doc:hover{border-color:var(--violet);transform:translateY(-2px);
  box-shadow:0 8px 24px rgba(22,50,78,.09)}
.doc:focus-visible{outline:2px solid var(--violet);outline-offset:3px}
.doc .deyebrow{font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--gray);font-weight:600}
.doc h3{font-family:var(--geo);font-weight:600;font-size:1.02rem;
  line-height:1.25;margin:2px 0 0;letter-spacing:-.01em}
.doc p{color:var(--gray);font-size:.87rem;line-height:1.45;margin:0}
.doc .dmeta{display:flex;justify-content:space-between;align-items:baseline;
  margin-top:auto;padding-top:10px}
.doc .dsee{font-size:.85rem;font-weight:600;color:var(--violet)}
.doc .dsee i{font-style:normal;transition:transform .16s;display:inline-block}
.doc:hover .dsee i{transform:translateX(3px)}
.doc .dprice{font-size:.85rem;color:var(--gray);font-variant-numeric:tabular-nums}


/* The nav is sticky, so an in-page jump lands the heading underneath
   it. Every anchor on this page needs the clearance, not just the new
   one. */
#documents, #pricing, .bundle-rule[id] { scroll-margin-top: 92px; }

/* A WAY PAST THE HERO. .story is 820vh on desktop, so every section
   below it begins around 7,400px - eight screens of scrolling before a
   visitor learns the products exist. The effect stays; this is the
   door out of it. */
.herojump{display:inline-block;margin-top:16px;padding:11px 20px;
  border-radius:999px;background:var(--coral);color:var(--neon-ink);
  font-family:var(--geo);font-weight:600;font-size:.95rem;
  text-decoration:none;transition:transform .16s,box-shadow .16s}
.herojump:hover{transform:translateY(-1px);
  box-shadow:0 8px 22px rgba(0,0,0,.18)}
.herojump i{font-style:normal;display:inline-block;transition:transform .16s}
.herojump:hover i{transform:translateX(3px)}


/* ------------------------------------------------- one report's page
   /reports/<slug>: what a search lands on, what a cold email links, and
   where the buying happens. Same tokens as the rest of the site. */
.rp-page{background:var(--page)}
.rpwrap{max-width:52rem;margin:0 auto;padding:120px 24px 90px}
.rpeyebrow{font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--gray);font-weight:600;margin:0 0 10px}
.rptitle{font-family:var(--geo);font-weight:600;letter-spacing:-.03em;
  font-size:clamp(2rem,5vw,3rem);line-height:1.05;margin:0 0 14px}
.rplede{font-size:1.1rem;line-height:1.55;color:var(--gray);margin:0 0 30px;
  max-width:42rem}
.rpbuy{display:grid;grid-template-columns:auto 1fr auto;gap:18px 22px;
  align-items:center;background:var(--slab);border:1px solid var(--line);
  border-radius:16px;padding:22px 24px;margin-bottom:44px}
.rpprice{font-family:var(--geo);font-weight:600;font-size:2.3rem;
  letter-spacing:-.03em;line-height:1}
.rpprice .cur{font-size:1.2rem;vertical-align:super;margin-right:2px;
  color:var(--gray)}
.rpbuytext b{display:block;font-family:var(--geo);font-weight:600;
  font-size:1rem}
.rpbuytext span{display:block;color:var(--gray);font-size:.9rem;
  line-height:1.45;margin-top:2px}
.rpcta{white-space:nowrap}
.rpsample{grid-column:1 / -1;font-size:.9rem;font-weight:600;
  color:var(--violet);text-decoration:none}
.rpsample:hover{text-decoration:underline}
@media(max-width:760px){.rpbuy{grid-template-columns:1fr}}
.rph2{font-family:var(--geo);font-weight:600;letter-spacing:-.02em;
  font-size:1.5rem;margin:0 0 8px}
.rpsub{color:var(--gray);margin:0 0 20px;max-width:42rem}
.rplines{display:grid;grid-template-columns:1fr 1fr;gap:0 26px;
  margin-bottom:44px}
@media(max-width:760px){.rplines{grid-template-columns:1fr}}
.rpline{padding:11px 0;border-bottom:1px solid var(--line);font-size:.94rem}
.rpnot{margin:0 0 44px;padding-left:20px;color:var(--gray);max-width:42rem}
.rpnot li{margin-bottom:8px;line-height:1.5}
.rpfoot{display:flex;gap:20px;align-items:center;flex-wrap:wrap;
  border-top:1px solid var(--line);padding-top:26px}

/* The report page on a phone. 24px of gutter at 120px of top padding
   left the title crushed against a sticky nav; the buy block's three
   columns become one; and the two-column line list is unreadable at
   375px however hard it tries. */
@media(max-width:560px){
  .rpwrap{padding:96px 24px 64px}
  .rptitle{font-size:1.95rem}
  .rplede{font-size:1rem}
  .rpbuy{grid-template-columns:1fr;gap:14px;padding:18px 20px;
    text-align:left}
  .rpprice{font-size:2rem}
  .rpcta{width:100%;text-align:center}
  .rplines{grid-template-columns:1fr;gap:0}
  .rpline{padding:10px 0;font-size:.92rem}
  .rpfoot{flex-direction:column;align-items:stretch;gap:14px}
}
