/* ============================================================
   10-testimonials — cinematic quote carousel + opposite-direction
   vertical review marquee ("Proof Wall"). Scoped under
   .sec-10-testimonials
   ============================================================ */

/* --- Carousel --- */
.sec-10-testimonials__carousel {
  max-width: 48rem;
  margin: 0 auto clamp(3.5rem, 6vw, 5rem);
  text-align: center;
}

.sec-10-testimonials__viewport {
  position: relative;
  overflow: hidden;
  min-height: 15rem;
}

.sec-10-testimonials__track {
  position: relative;
  display: block;
}

.sec-10-testimonials__slide {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  transform: translateX(24px);
  transition: opacity 600ms var(--ease-out), transform 600ms var(--ease-out);
  pointer-events: none;
}

.sec-10-testimonials__slide.is-active {
  position: relative;
  opacity: 1;
  transform: translateX(0);
  pointer-events: auto;
}

.sec-10-testimonials__slide.is-prev {
  transform: translateX(-24px);
}

.sec-10-testimonials__glyph {
  font-family: var(--font-quote);
  font-size: clamp(3.5rem, 4vw, 5.5rem);
  line-height: 0.6;
  color: var(--accent);
  opacity: 0.85;
}

.sec-10-testimonials__quote {
  font-family: var(--font-quote);
  font-style: italic;
  font-size: clamp(1.4rem, 1.1rem + 1.2vw, 2rem);
  line-height: 1.35;
  margin: 0.5rem 0 1.5rem;
  max-width: 42ch;
}

.sec-10-testimonials__stars {
  color: var(--ember);
  letter-spacing: 0.15em;
  margin-bottom: 0.6rem;
  font-size: 0.95rem;
}

.sec-10-testimonials__attr {
  color: var(--text-on-ink-dim);
  font-size: 0.9rem;
}

.sec-10-testimonials__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  margin-top: 2rem;
}

.sec-10-testimonials__arrow {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  border: 1px solid hsl(0 0% 100% / 0.2);
  background: transparent;
  color: var(--text-on-ink);
  font-size: 1.1rem;
  cursor: pointer;
  transition: background 250ms var(--ease-out), border-color 250ms var(--ease-out), transform 250ms var(--ease-spring);
}

.sec-10-testimonials__arrow:hover {
  background: hsl(0 0% 100% / 0.1);
  border-color: var(--accent);
  transform: translateY(-2px);
}

.sec-10-testimonials__dots {
  display: flex;
  gap: 0.5rem;
}

.sec-10-testimonials__dot {
  position: relative;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  border: none;
  background: hsl(0 0% 100% / 0.25);
  cursor: pointer;
  padding: 0;
  transition: background 250ms var(--ease-out), transform 250ms var(--ease-out);
}

/* Larger invisible tap target around the small visual dot, without affecting flex gap */
.sec-10-testimonials__dot::before {
  content: "";
  position: absolute;
  inset: -12px;
}

.sec-10-testimonials__dot.is-active {
  background: var(--accent);
  transform: scale(1.3);
}

/* --- Review wall, static variant ---
   This client has six real reviews in total. The two-column marquee below needs twelve
   unique reviews (six disjoint per column) to loop without repeating a reviewer or opening
   a void, so the wall ships static and shortened to three cards instead of padded with
   invented ones. Three across, no fixed height, no mask, no animation. The selector is
   doubled up so this wins over the untouched .sec-10-testimonials__wall block below it. */
.sec-10-testimonials__wall.sec-10-testimonials__wall--static {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  height: auto;
  overflow: visible;
  -webkit-mask-image: none;
          mask-image: none;
}
/* The 2-line clamp below exists so a card keeps a fixed height inside the moving
   marquee. The static wall has no such constraint, so reviews can be quoted at
   something close to their real length instead of being paraphrased down to fit. */
.sec-10-testimonials__wall--static .sec-10-testimonials__snippet {
  -webkit-line-clamp: 9;
}

@media (max-width: 820px) {
  .sec-10-testimonials__wall.sec-10-testimonials__wall--static {
    grid-template-columns: 1fr;
  }
}

/* --- Review wall ---
   Opposite-direction vertical marquee. This was frozen to a static grid at one point
   because clients read the moving cards as "cut off mid-review". It is animated again,
   rebuilt so it does not reproduce that complaint:
     1. the viewport is tall enough that whole reviews are always readable, never slivers
     2. the top and bottom edges are a gradient mask, so partials fade rather than hard-cut
     3. it pauses on hover and on focus-within, so anyone can stop it and read
     4. mobile and reduced-motion fall back to the plain static grid
   DO NOT shorten the wall or thin the review count without redoing the maths below. Both
   are load-bearing, and getting either wrong brings the "truncated" complaint straight back. */
.sec-10-testimonials__wall {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-gap);
  align-items: start;
  /* Sizing is driven by the loop maths, not by eye. With card height C, gap G and pitch
     P = C + G, the number of cards guaranteed FULLY inside the clear zone at EVERY phase
     of the scroll is floor((clear - C) / P). For three full cards, clear >= 3P + C.
     With C=188 and G=20 => P=208, so the clear zone must be >= 812px. The fade bands are a
     fixed 120px each (not a %, so they cannot grow with the section and eat the clear
     zone), hence a minimum height of ~812 + 240.
     The two columns carry DISJOINT sets of 6 reviews (12 unique in total). Both halves of
     that matter:
       - disjoint, because rotating ONE shared list by half only separates the columns at
         t=0; they run at different speeds (54s vs 62s) and drift back into sync, so the
         same reviewer appears in both columns.
       - 6 per column, because a set must be TALLER than the window or a void opens at an
         edge on every loop. 6 cards = 1248px against a ~1060px window. 4 would be 832px.
     Two sets per column and translateY(-50%) = exactly one set, so the loop has no seam. */
  height: clamp(1060px, 108vh, 1180px);
  overflow: hidden;
  position: relative;
  /* 120px is deep relative to a ~188px card, so a partial fades across most of its body
     instead of showing crisp text and then vanishing at a hard edge. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 120px, #000 calc(100% - 120px), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 120px, #000 calc(100% - 120px), transparent 100%);
}

.sec-10-testimonials__col {
  height: 100%;
  overflow: hidden;
}

.sec-10-testimonials__col-track {
  display: flex;
  flex-direction: column;
  gap: 0;
  will-change: transform;
}

.sec-10-testimonials__col-set {
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* The set carries its own TRAILING gap and the track has gap:0. If the gap lived on the
     track instead, the seam between set 1 and set 2 would be one gap short and the loop
     would visibly jump every cycle. */
  padding-bottom: 20px;
}

.sec-10-testimonials [data-marquee-dup] { display: flex; }

.sec-10-testimonials__col[data-dir="up"] .sec-10-testimonials__col-track {
  animation: sec-10-marquee-up 54s linear infinite;
}
.sec-10-testimonials__col[data-dir="down"] .sec-10-testimonials__col-track {
  animation: sec-10-marquee-down 62s linear infinite;
}

/* Anyone can stop it to actually read a review */
.sec-10-testimonials__wall:hover .sec-10-testimonials__col-track,
.sec-10-testimonials__wall:focus-within .sec-10-testimonials__col-track {
  animation-play-state: paused;
}

/* --- Mobile: static grid ---
   A narrow screen makes moving text materially harder to read, and the mask would eat a
   larger share of a small viewport. Below 820px this reverts to the plain grid: full
   height, no mask, no animation, duplicate sets hidden. */
@media (max-width: 820px) {
  .sec-10-testimonials__wall {
    grid-template-columns: 1fr;
    height: auto;
    overflow: visible;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .sec-10-testimonials__col {
    height: auto;
    overflow: visible;
    display: flex;
    flex-direction: column;
    gap: var(--sp-gap);
  }
  .sec-10-testimonials__col-track { animation: none !important; transform: none !important; }
  .sec-10-testimonials__col-set { padding-bottom: 0; gap: var(--sp-gap); }
  .sec-10-testimonials [data-marquee-dup] { display: none; }
  /* Both columns stay visible and simply stack. The columns hold DISJOINT reviews, so
     hiding either one drops half of them from every phone. */
  /* The mobile column is static, so height is free: stop truncating mid-sentence. */
  .sec-10-testimonials__snippet { -webkit-line-clamp: 6; }
}

/* --- Reduced motion: static grid, and the height/mask must go too, or the wall keeps a
   fixed tall box with most of its cards masked out and unreachable. --- */
@media (prefers-reduced-motion: reduce) {
  .sec-10-testimonials__wall {
    height: auto;
    overflow: visible;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .sec-10-testimonials__col { height: auto; overflow: visible; }
  .sec-10-testimonials__col-track { animation: none !important; transform: none !important; }
  .sec-10-testimonials__col-set { padding-bottom: 0; }
  .sec-10-testimonials [data-marquee-dup] { display: none; }
}
/* core.js also sets html.reduced-motion; mirror it so the toggle works without a reload. */
html.reduced-motion .sec-10-testimonials__wall {
  height: auto;
  overflow: visible;
  -webkit-mask-image: none;
          mask-image: none;
}
html.reduced-motion .sec-10-testimonials__col { height: auto; overflow: visible; }
html.reduced-motion .sec-10-testimonials__col-track { animation: none !important; transform: none !important; }
html.reduced-motion .sec-10-testimonials__col-set { padding-bottom: 0; }
html.reduced-motion .sec-10-testimonials [data-marquee-dup] { display: none; }

@keyframes sec-10-marquee-up {
  from { transform: translateY(0); }
  to { transform: translateY(-50%); }
}

@keyframes sec-10-marquee-down {
  from { transform: translateY(-50%); }
  to { transform: translateY(0); }
}

.sec-10-testimonials__review {
  background: #fbfbf9;
  color: var(--text-on-bone);
  border-radius: var(--radius-card);
  padding: 1.6rem 1.65rem;
  box-shadow: var(--shadow-soft);
}

.sec-10-testimonials__review-head {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  margin-bottom: 0.5rem;
}

.sec-10-testimonials__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.9rem;
  flex-shrink: 0;
}

.sec-10-testimonials__review-name {
  font-weight: 600;
  font-size: 0.9rem;
}

.sec-10-testimonials__review .sec-10-testimonials__stars {
  color: var(--ember);
  font-size: 0.8rem;
  margin-bottom: 0.4rem;
}

.sec-10-testimonials__snippet {
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--text-on-bone);
  margin-bottom: 0.55rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.sec-10-testimonials__via {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.75rem;
  color: var(--text-on-bone-dim);
}

/* --- Google lockup --- */
.sec-10-testimonials__lockup {
  margin-top: clamp(2rem, 4vw, 3rem);
  text-align: center;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 1rem 1.5rem;
}

.sec-10-testimonials__lockup-rating {
  font-weight: 600;
  color: var(--text-on-ink);
}

.sec-10-testimonials__lockup-link {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 200ms var(--ease-out);
}

.sec-10-testimonials__lockup-link:hover {
  border-color: currentColor;
}

/* Mobile stacking for the wall lives with the wall rules above, not here: it used to also
   carry `.__col--b { display: none }`, which silently dropped half the reviews from every
   phone once the two columns held DISJOINT sets. */

/* --- Reduced motion: no auto-slide feel --- */
@media (prefers-reduced-motion: reduce) {
  .sec-10-testimonials__slide { transition: opacity 200ms linear; transform: none; }
}

html.reduced-motion .sec-10-testimonials__slide { transition: opacity 200ms linear; transform: none; }
