/* testimonials.css — 08 / Testimonials
 *
 * A normal-flow section, not a pinned stage. Designed at 1440px.
 *
 * THIS SECTION IS ASYMMETRICAL and it is the only one on the page that is.
 * The left side takes a normal page gutter; the right side has NO gutter —
 * the two hairlines and the card rail terminate exactly on the viewport edge,
 * and only right-hand TYPE is held off it, by one small value. A rule touching
 * the glass reads as deliberate; a word touching it reads as broken.
 *
 * That is why the section carries no horizontal padding: the side insets belong
 * to the rows inside, because the two sides differ.
 *
 * Classes are namespaced `ts-` because .card, .card__meta and data-rv are all
 * already taken by other sections on this page.
 */

.ts {
  position: relative;
  box-sizing: border-box;
  /* A full viewport, like Contact and the footer. min-height rather than height:
     the cards are tall and the rail is tall, so a short window must still be
     allowed to grow rather than clip. .ts__inner is the only direct child, so
     centring it in the column is what takes up the slack on a tall screen. */
  min-height: 100vh;
  display: flex; flex-direction: column; justify-content: center;
  background: var(--char-base);
  color: var(--on-dark);
  padding: clamp(61.2px, min(5cqi, 8vh), 92.16px) 0 clamp(54.4px, min(4.44cqi, 7.11vh), 81.92px);
  /* CLIP, not hidden, and horizontal only. The rail's last card crosses the
     section's right boundary by design; without this it puts a scrollbar on the
     document. `hidden` would additionally make .ts a scroll container. */
  overflow-x: clip;
  /* INLINE-SIZE, never `size`. handoff-2's DESIGN-SYSTEM-DELTA is explicit:
     size containment would make this section ignore its own content height, and
     Testimonials is precisely the section that then overflows — the cards are
     tall and .ts is sized by them. `inline-size` gives the card its cqi basis
     with no height risk. */
  container-type: inline-size;

  /* The three horizontal-rhythm values. They break the page's
     clamp(floor, min(Xcqi, Yvh), cap) pattern on purpose: they have no vertical
     component, so a vh term would be wrong. Each is used in four or five places
     and they must stay in lockstep, hence the names.

     The hold-off is roughly a QUARTER of the gutter. That ratio is what makes
     the right side read as flush rather than as a narrow gutter. */
  --t-gutter: clamp(40px, 4.5cqi, 108px);   /* left only  — 64.8 at 1440 */
  --t-hold:   clamp(16px, 1.25cqi, 28px);   /* right only — 18   at 1440 */
  /* Named because the card width has to subtract it — see .ts-card. */
  --t-gap:    clamp(17px, min(1.39cqi, 2.22vh), 25.6px);
}
/* FULL-BLEED. There is no centred 1440 measure any more: the rail has to reach
   the right edge of the window at every width, and a max-width would strand it
   in the middle of a wide screen with a gap on both sides. The left gutter and
   the right hold-off below are what hold the content in instead. */
.ts__inner { margin: 0; }

.ts__grid {
  display: grid;
  /* N3 — horizontal structure is PERCENTAGE-BASED. The rail was a flat 200px
     and the gutters 64px, so on a narrow window the rail ate the strip and on a
     wide one it stranded it.

     THE PERCENTAGES NOW RESOLVE AGAINST 100cqi − var(--t-gutter), not against
     100cqi − two gutters: there is only one gutter left. That widens both the
     meta track and the gap by about 18% at every size — at 1440 the track is
     192px and the gap 55px. If the meta column ever reads too wide, swap the
     track for clamp(150px, 12cqi, 240px) and leave the rest alone; do NOT
     compensate by putting the right gutter back. */
  grid-template-columns: 14% minmax(0, 1fr);
  column-gap: 4%;
  row-gap: 0;
  padding: 0 0 0 var(--t-gutter);
}
.ts__main { min-width: 0; }


/* --- left rail ------------------------------------------------------------ */

.ts__rail { display: flex; flex-direction: column; gap: clamp(23.8px, min(1.94cqi, 3.11vh), 35.84px); }

.ts__eyebrow {
  font-family: var(--hud); font-weight: 400;
  font-size: clamp(12.8px, min(0.89cqi, 1.42vh), 16.38px); letter-spacing: .22em; line-height: 1.7;
  color: var(--red-dark);
}
.ts__railblock {
  border-top: 1px solid rgba(244, 242, 237, .14);
  padding-top: clamp(17px, min(1.39cqi, 2.22vh), 25.6px);
  font-family: var(--hud); font-weight: 400;
  font-size: clamp(10.56px, min(0.73cqi, 1.17vh), 13.52px); letter-spacing: .16em; line-height: 1.8;
  color: var(--ts-ink-3);
}
.ts__count {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(35.36px, min(2.89cqi, 4.62vh), 53.25px); line-height: 1;
  color: var(--ts-ink-2);
}
.ts__count sup { font-size: clamp(14.96px, min(1.22cqi, 1.96vh), 22.53px); }
/* Fixed 8px, not a clamp: it is optical spacing under a single figure. */
.ts__countcap { margin-top: 8px; }
.ts__source {
  /* Secondary ink, not headline ink: the value is the loudest thing in the meta
     stack but it is still a label, and full --on-dark competed with the H2. */
  color: var(--ts-ink-2);
  font-family: var(--body);
  font-size: clamp(12.92px, min(1.06cqi, 1.69vh), 19.46px);
  letter-spacing: 0;
}


/* --- header --------------------------------------------------------------- */

.ts__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 3.3%;
  /* PADDING-RIGHT IS 0 AND THERE IS NO NEGATIVE MARGIN. The row's border-box has
     to end on the viewport edge so its border-bottom does too. The obvious
     alternative — padding-right here plus a matching negative margin-right —
     does not cancel out: the border-box then overshoots the window by the
     hold-off and only looks right because something else clips it. The hold-off
     belongs to the <p> inside, and only to the <p>. */
  padding: 0 0 clamp(23.8px, min(1.94cqi, 3.11vh), 35.84px) 0;
  border-bottom: 1px solid rgba(244, 242, 237, .14);
}
.ts__title {
  margin: 0;
  font-family: var(--display); font-weight: 900;
  font-size: clamp(47.6px, min(3.89cqi, 6.22vh), 71.68px); line-height: 1.02;
  letter-spacing: -.03em; text-transform: uppercase;
  max-width: 16ch;
}
.ts__title-dim { color: var(--ts-ink-3); }
.ts__intro {
  margin: 0;
  /* Both measure caps are required: the H2 has to break to two lines at
     reference width, and this paragraph is right-aligned, which reads badly
     past about 38 characters. */
  max-width: 38ch;
  text-align: right;
  font-family: var(--body);
  font-size: clamp(13.6px, min(1.11cqi, 1.78vh), 20.48px); line-height: 1.62;
  color: var(--ts-ink-2);
  /* The hold-off lives here rather than on the row — see the note on .ts__head.
     content-box so it does not eat into the 38ch measure. */
  padding-right: var(--t-hold);
  box-sizing: content-box;
}


/* --- pager ---------------------------------------------------------------- */

.ts__pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(20.4px, min(1.67cqi, 2.67vh), 30.72px);
  /* The arrows sit on the hold-off, not on the glass: a 46px hit target in the
     very corner of the viewport is both awkward to hit and unresolved to look
     at. This is the one row where the right inset applies to the row itself,
     because its content is a control and not type. */
  padding: clamp(22.1px, min(1.81cqi, 2.89vh), 33.28px) var(--t-hold)
           clamp(15.3px, min(1.25cqi, 2vh), 23.04px) 0;
}
.ts__counter {
  font-family: var(--hud); font-weight: 400;
  font-size: clamp(10.88px, min(0.76cqi, 1.21vh), 13.93px); letter-spacing: .18em; text-transform: uppercase;
  color: var(--ts-ink-3);
}
.ts__counter b { color: var(--red-dark); font-weight: 400; }

/* 10px flat — the pair reads as one control, so the gap does not scale. */
.ts__navs { display: flex; gap: 10px; }
.ts__nav {
  width: clamp(39.1px, min(3.19cqi, 5.11vh), 58.88px); height: clamp(39.1px, min(3.19cqi, 5.11vh), 58.88px);
  display: grid; place-items: center;
  border: 1px solid rgba(244, 242, 237, .22);
  background: none;
  cursor: pointer;
  font-family: var(--hud); font-size: clamp(13.6px, min(1.11cqi, 1.78vh), 20.48px);
  color: var(--on-dark);
  user-select: none;
  transition: background .2s, border-color .2s;
}
.ts__nav:hover { background: var(--red); border-color: var(--red); }
.ts__nav[disabled] { opacity: .3; cursor: default; }
.ts__nav[disabled]:hover { background: none; border-color: rgba(244, 242, 237, .22); }


/* --- the strip ------------------------------------------------------------ */

.ts__strip {
  display: flex;
  gap: var(--t-gap);
  /* AUTO here, clip on the section. Mixing the two up either kills the scroll
     or leaks a scrollbar onto the document. */
  overflow-x: auto;
  /* VERTICAL PADDING ONLY, and it must stay that way: the card width below
     resolves its 100% against this element's CONTENT box, so any horizontal
     padding here silently shrinks the basis and the three cards stop filling
     the rail. The trailing hold-off is a flex child for exactly that reason.
     8px flat — it is clearance for the card's own edge, not a rhythm value. */
  padding: 8px 0;
  scroll-behavior: smooth;
  /* Without snapping a trackpad drag can rest between positions and the edge
     shows an arbitrary partial card — the exact defect the derived card width
     below exists to prevent. */
  scroll-snap-type: x mandatory;
  /* Scrollbar hidden by design — the pager is the affordance. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.ts__strip::-webkit-scrollbar { display: none; }
/* Trailing spacer, exactly one hold-off wide. It is what lets the last card
   clear the viewport edge when the rail is scrolled home; without it the last
   card ends flush against the glass and reads as a clipping bug. */
.ts__strip::after { content: ""; flex: none; width: var(--t-hold); }

.ts-card {
  flex: none;
  /* THE CARD WIDTH IS DERIVED, NOT CLAMPED, and this is the whole point of the
     section. Three cards plus THREE gaps plus the hold-off fill the rail
     exactly, so what meets the viewport edge is always the leading edge of the
     next card, exactly one hold-off wide — a deliberate peek at width n+1
     rather than an arbitrary remainder.

     RESERVE n GAPS, NOT n − 1. The rail lays out card gap card gap card gap
     card: there is a third gap BEFORE the fourth card. Reserving two consumes
     the whole sliver and the next card starts exactly on the glass. This is the
     single easiest thing to get wrong here.

     A clamp() cannot do this: whatever is left over after the last whole card
     becomes the visible remainder, and that remainder is arbitrary — at 1600 it
     came out around 90% of a card, which reads as a stretched card rather than
     as a peek at the next one.

     testimonials.js MEASURES the card pitch from the first two cards rather
     than assuming a fixed step, so a fluid width needs no JS change. */
  width: calc((100% - 3 * var(--t-gap) - var(--t-hold)) / 3);
  scroll-snap-align: start;
  /* min-height, never height: the longest quote sets the row height and the
     others match it through the flex row's default align-items: stretch. */
  min-height: clamp(300px, min(22cqi, 35.2vh), 440px);
  padding: clamp(30.6px, min(2.5cqi, 4vh), 46.08px) clamp(28.9px, min(2.36cqi, 3.78vh), 43.52px);
  display: flex; flex-direction: column;
  background: var(--char-ink);
  transition: background-color .35s ease;
}
/* THE COUNT SWITCH. Held at three-up, the card reaches 635px on a 2560 monitor
   against 467px at 1920 — visibly bloated. Four-up dips it to 379px at 2100 and
   back to 470px by 2560, so it never leaves the range it already occupies below
   the switch.

   Consequence to accept: with only four testimonials, four-up means every card
   is on screen above 2100px and the rail stops scrolling — there is no sliver at
   those widths. Add a fifth testimonial and the sliver returns on its own with
   no change here. */
@container (min-width: 2100px) {
  .ts-card { width: calc((100% - 4 * var(--t-gap) - var(--t-hold)) / 4); }
}
.ts-card__meta {
  display: flex; justify-content: space-between;
  font-family: var(--hud); font-weight: 400;
  font-size: clamp(9.6px, min(0.67cqi, 1.07vh), 12.29px); letter-spacing: .18em;
  line-height: 1;
  color: var(--red-dark);
  margin-bottom: clamp(22.1px, min(1.81cqi, 2.89vh), 33.28px);
}
.ts-card__stars { letter-spacing: .14em; }
.ts-card__quote {
  margin: 0;
  font-family: var(--body); font-weight: 400;
  font-size: clamp(14.28px, min(1.17cqi, 1.87vh), 24.06px); line-height: 1.6;
  color: var(--on-dark);
}
/* The first card carries the lead treatment. The active card takes a
   DISPLAY-weight quote and the idle ones a body-weight one — that difference in
   type, not just in colour, is what makes the active card read as active. */
.ts-card--lead .ts-card__quote {
  font-family: var(--display); font-weight: 800;
  font-size: clamp(19.81px, min(1.62cqi, 2.59vh), 31.62px); line-height: 1.24;
  letter-spacing: -.01em;
}
/* margin-top: auto pins the attribution to the bottom of whichever card is
   tallest, so all four line up along it. */
.ts-card__foot {
  display: flex; align-items: center; gap: clamp(11.9px, min(0.97cqi, 1.56vh), 17.92px);
  margin-top: auto;
  padding-top: clamp(23.8px, min(1.94cqi, 3.11vh), 35.84px);
  border-top: 1px solid rgba(244, 242, 237, .10);
}
.ts-card__avatar {
  width: clamp(40.8px, min(3.33cqi, 5.33vh), 61.44px);
  height: clamp(40.8px, min(3.33cqi, 5.33vh), 61.44px);
  flex: none;
  /* SQUARE, NOT CIRCULAR, and deliberately so — the hard-cornered crop matches
     the rectangular geometry the rest of the page is built on. Stated as an
     explicit 0 rather than by omission because handoff-1 specified 50% and this
     reverses it: the old value is still written down in two places
     (`design_handoff_testimonials/README.md`, and the mobile handoff's README),
     so someone reading those will be tempted to put it back. Do not. */
  border-radius: 0;
  object-fit: cover;
  /* Sources are already black-and-white; this keeps a future colour headshot
     matching the rest without re-editing the file. Applies on the active card
     too — the red ground does not get a colour portrait. */
  filter: grayscale(1);
}
.ts-card__name {
  font-family: var(--display); font-weight: 800;
  font-size: clamp(12.5px, min(1.02cqi, 1.64vh), 18.82px); line-height: 1.2;
  color: var(--on-dark);
}
.ts-card__role {
  font-family: var(--hud); font-weight: 400;
  font-size: clamp(7.6px, min(0.62cqi, 1vh), 11.44px); line-height: 1.4;
  letter-spacing: .11em; text-transform: uppercase;
  /* Idle cards only — .ts-card.is-active overrides to on-red secondary below. */
  color: var(--ts-ink-2);
  margin-top: clamp(3.4px, min(0.28cqi, 0.44vh), 5.12px);
}

/* The leftmost visible card. testimonials.js owns the class. */
/* background: declared once in main.css, with the p3 uplift beside it. */
.ts-card.is-active .ts-card__meta { color: rgba(255, 255, 255, .9); }
.ts-card.is-active .ts-card__foot { border-top-color: rgba(255, 255, 255, .28); }
.ts-card.is-active .ts-card__role { color: rgba(255, 255, 255, .78); }


/* --- section footer ------------------------------------------------------- */

/* Outside the grid, full width, last child of the section — and asymmetric like
   everything else here: gutter on the left, hold-off on the right. */
.ts__foot {
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: 24px;
  padding: clamp(30.6px, min(2.5cqi, 4vh), 46.08px) var(--t-hold) 0 var(--t-gutter);
  font-family: var(--hud); font-weight: 400;
  font-size: clamp(10.56px, min(0.73cqi, 1.17vh), 13.52px);
  letter-spacing: .16em; line-height: 1.8;
  color: var(--ts-ink-3);
}
/* margin-left rather than justify-content: with the left cell hidden this row
   has ONE child, and space-between then parks it at the start. The auto margin
   holds it right either way, so restoring the left cell needs no second edit. */
.ts__next { color: var(--red-dark); margin-left: auto; text-align: right; }

/* REMOVED ON ZSOLT'S INSTRUCTION: the figure label and the headline restatement
   — "FIG. 08 / — DON'T TAKE MY WORD FOR IT" — that used to sit at the left end
   of this row. Only "NEXT / CONTACT ↓" is left.

   Hidden rather than deleted: the markup is frozen, so the two spans and the
   <br> between them are still in the DOM, and ts.figA / ts.figB are still in
   copy.js. Deleting this one rule brings the whole block back. */
.ts__foot > span:not(.ts__next) { display: none; }


/* --- scroll reveal --------------------------------------------------------
   Fires once when the section comes into view. Named data-ts-rv because the
   hero already owns data-rv for its entrance animations. */

[data-ts-rv] {
  transition: opacity .8s cubic-bezier(.22, .7, .24, 1),
              transform .8s cubic-bezier(.22, .7, .24, 1);
}
.ts.is-hidden [data-ts-rv] { opacity: 0; transform: translateY(30px); }
.ts.is-hidden [data-ts-rv="rail"] { transform: translateX(-24px); }

@media (prefers-reduced-motion: reduce) {
  [data-ts-rv] { transition: none; }
  .ts.is-hidden [data-ts-rv] { opacity: 1; transform: none; }
  .ts__strip { scroll-behavior: auto; }
}


/* Mobile only — the counter carries a swipe hint below 768px. */
.ts__swipe { display: none; }
