/* ==========================================================================
   Proof: the scroll-driven testimonial rail
   --------------------------------------------------------------------------
   Built on the scrollcraft engine, but deliberately NOT using its stylesheet.
   scrollcraft.css carries global html/body resets that would fight tokens.css
   and site.css on an already-deployed page, so only the structural mechanics
   the engine actually needs are reproduced here, scoped under .proof, and
   themed with JJB's own tokens rather than scrollcraft's.
   ========================================================================== */

/* ---- engine mechanics (scoped) ------------------------------------------ */

.proof.sc-act--pinned { position: relative; }

.proof .sc-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: clip;
}

/* Cues start hidden and are driven by act progress. */
.proof [data-sc-cue] { opacity: 0; will-change: opacity, transform; }

/* Kinetic line splitting. The bottom padding buys back room for descenders,
   which a mask clipped to the line box would shear off. */
.proof .sc-split { display: inline-block; overflow: hidden; vertical-align: top; }
.proof .sc-split--line { display: block; padding-bottom: 0.14em; margin-bottom: -0.14em; }
.proof .sc-split__i { display: inline-block; will-change: transform, opacity; }
.proof .sc-is-split { opacity: 1 !important; }

/* The rail itself. */
.proof [data-sc-pan] { display: flex; will-change: transform; }

/* ---- the stage ---------------------------------------------------------- */

/* Composition: header clearance at the top, the cards commanding the middle,
   the calibration rule anchored to the bottom like the readout on an
   instrument. Measured first pass left 285px empty above the cards and 200px
   below the rule, 54% of the stage doing nothing, which reads as sparse rather
   than considered. The rail now grows to claim that space and the cards carry
   a floor height so they fill it. */
.proof__stage {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  /* The site header is fixed, so the stage's content is pushed clear of it
     rather than sliding underneath. */
  padding-top: calc(var(--header-h, 84px) + var(--sp-5));
  /* The extra bottom room is not taste, it is clearance. The chat launcher is
     fixed to the bottom right, and with the rule sitting flush at the bottom of
     the stage the counter rendered underneath it and could not be read.
     Measured at 1440: counter x1336-1393 inside launcher x1237-1397. This
     lifts the whole rule above the launcher's top edge at every size. */
  padding-bottom: calc(var(--sp-6) + 3.5rem);
}

/* ---- the rail ----------------------------------------------------------- */

.proof__rail {
  flex: 1;
  gap: clamp(1rem, 0.5rem + 1.2vw, 1.75rem);
  padding-inline: var(--sp-6);
  align-items: center;
}

.proof__item { flex: none; }

/* The heading rides the rail as its first item. Two reasons: it stops
   competing with the fixed header for the top of the frame, and it adds the
   width the lateral travel needs. A rail narrower than the viewport travels
   zero and reads as a dead pinned screen. */
/* The ceilings here are set by the overflow requirement, not by taste. A rail
   whose items stop growing while the viewport keeps growing loses its travel:
   measured at 2560 with a 25rem card ceiling, overflow fell to 823px against a
   1273px threshold, so four viewport-heights of scroll bought almost no lateral
   movement and the act read as static. The items scale with the viewport
   instead. Verified healthy at 1440, 1920 and 2560. */
.proof__lede { width: clamp(17rem, 30vw, 36rem); display: flex; flex-direction: column; justify-content: center; }

.proof__title {
  font-family: var(--font-display);
  font-size: clamp(1.85rem, 1.3rem + 2vw, 3rem);
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--heading);
  text-wrap: balance;
}

.proof__sub {
  margin-top: var(--sp-4);
  font-size: var(--t-body-sm);
  line-height: 1.7;
  color: var(--text-muted);
  max-width: 34ch;
}

/* ---- a card ------------------------------------------------------------- */

.proof__card {
  width: clamp(17rem, 27vw, 35rem);
  min-height: min(28rem, 52svh);
  display: flex;
  flex-direction: column;
  padding: clamp(1.5rem, 1.1rem + 1.2vw, 2.4rem);
  border-radius: var(--r-lg);
  background-color: rgba(8, 20, 16, 0.66);
  box-shadow:
    inset 0 1px 0 0 rgba(255, 255, 255, 0.07),
    0 18px 44px -24px rgba(0, 0, 0, 0.85);
  transition:
    background-color 260ms var(--ease-out),
    box-shadow 260ms var(--ease-out);
}

/* The card the needle is currently sitting on. Deliberately brightened rather
   than dimming its neighbours: dropping opacity on the others would take their
   text contrast down with it, and this section has to survive a contrast
   measurement at every scroll position. */
.proof__card[data-on] {
  background-color: rgba(12, 30, 22, 0.82);
  box-shadow:
    inset 0 0 0 1px rgba(185, 253, 168, 0.34),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.10),
    0 22px 54px -24px rgba(0, 0, 0, 0.9);
}

.proof__discipline {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--green-300);
}

.proof__quote {
  margin: var(--sp-5) 0 0;
  font-family: var(--font-display);
  font-size: clamp(1.0625rem, 0.95rem + 0.5vw, 1.375rem);
  font-weight: 300;
  /* Light type on a dark ground needs a touch more leading and tracking than
     the same face would take on white. */
  line-height: 1.55;
  letter-spacing: 0.004em;
  color: var(--text-strong);
  text-wrap: pretty;
}

.proof__attrib {
  margin-top: auto;
  padding-top: var(--sp-5);
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.proof__who {
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--bone);
}

/* stone-600 measured 3.23:1 here against the composited card ground, under the
   4.5:1 needed at this size. stone-400 clears it comfortably. */
.proof__role {
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--stone-400);
}

/* The closing item resolves the rail instead of ending it. */
.proof__close {
  width: clamp(15rem, 22vw, 25rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.proof__close p {
  font-size: var(--t-body-sm);
  line-height: 1.7;
  color: var(--text-muted);
}

.proof__close .btn { margin-top: var(--sp-5); align-self: flex-start; }

/* ---- the signature move: the calibration rule --------------------------- */
/* A measurement rule beneath the rail. The needle tracks the reader's own
   scroll and lands on each discipline's tick as its card centres. The brand is
   called Calibrated; this is the section being calibrated by the reader. */

.proof__rule {
  position: relative;
  margin-inline: var(--sp-6);
  padding-top: var(--sp-5);
  border-top: 1px solid rgba(207, 247, 205, 0.14);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
}

.proof__ticks {
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 1px;
}

.proof__tick {
  position: absolute;
  top: 0;
  width: 1px;
  height: 9px;
  background-color: rgba(207, 247, 205, 0.28);
  transform: translateX(-50%);
  transition: background-color 220ms var(--ease-out), height 220ms var(--ease-out);
}

.proof__tick[data-on] {
  height: 16px;
  background-color: var(--signal);
}

/* The needle. Position comes straight from act progress, so it is the reader's
   hand moving it, not a timed animation. */
.proof__needle {
  position: absolute;
  top: -5px;
  left: calc(var(--sc-p, 0) * 100%);
  width: 2px;
  height: 19px;
  margin-left: -1px;
  background-color: var(--signal);
  box-shadow: 0 0 12px 0 rgba(185, 253, 168, 0.8);
}

.proof__readout {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--signal);
}

/* Same correction as .proof__role: stone-600 measured 3.59:1 on the page
   ground at this size. */
.proof__count {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  color: var(--stone-400);
  font-variant-numeric: tabular-nums;
}

/* ---- reduced motion ----------------------------------------------------- */
/* The rail's transform is its navigation, not decoration. Zeroing it the way
   parallax is zeroed would park the act on its first screenful and make every
   testimonial past the fold unreachable, which is a device degrading into
   missing content. Hand the travel back as an ordinary scroll region instead. */

@media (prefers-reduced-motion: reduce) {
  /* The engine writes height:Nvh inline on any pinned act, and it does that
     regardless of motion preference, so it has to be beaten with !important.
     Without this the section keeps its four viewport-heights while the stage
     collapses to its content, leaving roughly three screens of empty black.
     The harness does NOT catch it: nothing is failing to move, so there is no
     dead scroll to report. Caught by reading the reduced-motion sheet. */
  .proof.sc-act--pinned { height: auto !important; }

  .proof .sc-stage {
    position: static;
    height: auto;
    overflow: visible;
  }

  .proof__stage {
    padding-top: var(--sp-7);
    padding-bottom: var(--sp-7);
  }

  /* The rail's transform is its navigation, not decoration. Zeroing it alone
     parks the act on its first screenful and makes three of the five
     testimonials unreachable, which is a device degrading into missing
     content. They are re-laid as a grid instead, which reads better than a
     horizontal scroll region for something you are meant to sit and read. */
  .proof [data-sc-pan] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
    gap: clamp(1rem, 0.5rem + 1.2vw, 1.75rem);
    transform: none !important;
  }

  .proof__lede,
  .proof__close { width: auto; grid-column: 1 / -1; }
  .proof__card { width: auto; min-height: 0; }
  .proof .sc-split__i { transform: none !important; }
  .proof [data-sc-cue] { opacity: 1 !important; }

  /* A needle with no travel to report is an instrument showing a false
     reading, so the rule goes rather than sitting there frozen. */
  .proof__rule { display: none; }
}

/* ---- no-js -------------------------------------------------------------- */
/* Without the engine the .sc-stage class is never applied and the act never
   gets its height, so the rail would be an unscrollable overflowing flex row
   with three testimonials clipped off the side. Same grid fallback. */

html:not(.sc-ready) .proof [data-sc-pan] {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: clamp(1rem, 0.5rem + 1.2vw, 1.75rem);
}
html:not(.sc-ready) .proof__lede,
html:not(.sc-ready) .proof__close { width: auto; grid-column: 1 / -1; }
html:not(.sc-ready) .proof__card { width: auto; min-height: 0; }
html:not(.sc-ready) .proof [data-sc-cue] { opacity: 1; }
html:not(.sc-ready) .proof__rule { display: none; }

/* ---- phones ------------------------------------------------------------- */

@media (max-width: 700px) {
  .proof__rail { padding-inline: var(--sp-5); }
  .proof__rule { margin-inline: var(--sp-5); }
  .proof__readout, .proof__count { font-size: 0.625rem; letter-spacing: 0.1em; }
  .proof__stage { gap: var(--sp-5); }
}

/* ==========================================================================
   Static proof: subpages
   --------------------------------------------------------------------------
   Two or three testimonials cannot fill a rail. Measured against a desktop
   viewport they produce negative overflow, which means zero travel and a
   pinned act holding one motionless screen. So subpages get a static
   treatment on the site's existing reveal system instead. Same design
   language, no scroll device, no dead scroll.
   ========================================================================== */

.proof-static {
  display: grid;
  gap: clamp(1rem, 0.5rem + 1.2vw, 1.75rem);
  grid-template-columns: 1fr;
}

@media (min-width: 860px) {
  .proof-static--2 { grid-template-columns: repeat(2, 1fr); }
  .proof-static--3 { grid-template-columns: repeat(3, 1fr); }
}

/* A single testimonial has nothing to grid against, so it is centred and
   capped rather than stretched full-width, matching the shell--narrow pattern
   used elsewhere on the site for single-column content. */
.proof-static--1 {
  max-width: 42rem;
  margin-inline: auto;
}

/* The rail's floor height is there to fill a 100svh stage. In a static grid it
   would just make short quotes tall and empty. */
.proof-static .proof__card { width: auto; height: 100%; min-height: 0; }
