/* ==========================================================================
   Testimonial page — Figma "Testimonial" 9569:9161 (1920) / "Mobile" 9733:5914 (402)
   Every rule is namespaced `tst-` so nothing leaks into assets/css/style.css.
   Reference values are kept verbatim from Figma (including the odd 23.99px /
   29.988px numbers) so the build stays a 1:1 translation of the comp.
   ========================================================================== */

.tst-page {
  /* Colour styles — 7SearchPPC - Color Styles */
  --tst-blue: #3b82f6;
  --tst-blue-hover: #2563eb;
  --tst-black: #1e1e1e;
  --tst-grey: #6b7280;
  --tst-light-grey: #d2d2d2;
  --tst-yellow: #facc15;
  --tst-success: #10b981;
  --tst-white: #fff;
  --tst-chat-in: #f2f5f9;
  --tst-track: #ededed;
  --tst-error: #d92d20;

  --tst-head: 'Be Vietnam Pro', sans-serif;
  --tst-body: 'Inter', sans-serif;

  --tst-container: 1520px;
  --tst-gutter: 200px;

  background: var(--tst-white);
  color: var(--tst-black);
  overflow-x: clip;
}

.tst-page *,
.tst-page *::before,
.tst-page *::after { box-sizing: border-box; }

.tst-page p { margin: 0; }
.tst-page h1,
.tst-page h2,
.tst-page h3 { margin: 0; font-weight: 500; }
.tst-page img { max-width: 100%; }

.tst-container {
  width: min(var(--tst-container), 100% - 80px);
  margin-inline: auto;
}

/* --- shared atoms ------------------------------------------------------- */

/* Line breaks the comp only applies on the 402px frame. */
.tst-br-sm { display: none; }

.tst-eyebrow {
  font-family: var(--tst-body);
  font-size: 14px;
  font-weight: 400;
  line-height: 1.21;
  color: var(--tst-blue);
  margin: 0;
}

.tst-h2 {
  font-family: var(--tst-head);
  font-weight: 500;
  font-size: 36px;
  line-height: 1.28;
  letter-spacing: -0.18px;
  color: var(--tst-black);
}

.tst-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 19.992px 23.99px;
  border-radius: 29.988px;
  border: 1px solid transparent;
  font-family: var(--tst-head);
  font-weight: 500;
  font-size: 23.99px;
  line-height: 1;
  letter-spacing: -0.12px;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .25s ease, color .25s ease, border-color .25s ease, transform .25s ease;
}
.tst-btn:focus-visible { outline: 2px solid var(--tst-black); outline-offset: 3px; }

.tst-btn--solid { background: var(--tst-blue); border-color: var(--tst-blue); color: var(--tst-white); }
.tst-btn--solid:hover { background: var(--tst-blue-hover); border-color: var(--tst-blue-hover); color: var(--tst-white); transform: translateY(-2px); }

.tst-btn--ghost { background: var(--tst-white); border-color: var(--tst-blue); color: var(--tst-blue); }
.tst-btn--ghost:hover { background: var(--tst-blue); color: var(--tst-white); transform: translateY(-2px); }

.tst-btn--light { background: transparent; border-color: var(--tst-white); color: var(--tst-white); }
.tst-btn--light:hover { background: var(--tst-white); color: var(--tst-black); transform: translateY(-2px); }

/* ==========================================================================
   1. HERO — node 9689:6265 / 9689:6280
   ========================================================================== */

.tst-hero {
  position: relative;
  padding: 70px 0 150px;
  overflow: hidden;
}

.tst-hero__grid {
  position: absolute;
  top: -102px;
  left: 50%;
  width: 1920px;
  height: 1080px;
  transform: translateX(-50%);
  pointer-events: none;
  user-select: none;
}

.tst-hero__inner {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 40px;
}

.tst-hero__copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 39.984px;
  width: 590px;
  flex: 0 0 auto;
}

.tst-hero__eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--tst-body);
  font-size: 14px;
  line-height: 1.21;
  color: var(--tst-grey);
}
.tst-hero__eyebrow img { width: 12px; height: 12px; display: block; }

.tst-hero__title {
  font-family: var(--tst-head);
  font-weight: 500;
  font-size: 48px;
  line-height: 61px;
  letter-spacing: -0.24px;
  color: var(--tst-black);
}
.tst-hero__title em { font-style: normal; color: var(--tst-blue); }

/* The cycling verb. The slot is held at the width of the longest word — set
   from JS, which measures the real glyphs at the current size — so "With Us"
   keeps its place instead of sliding left and right on every character, and the
   line cannot rewrap mid-word on a narrow screen. */
.tst-type {
  display: inline-block;
  white-space: nowrap;
  text-align: left;
}

/* The caret only exists while the script is driving the word; without JS the
   heading is a plain sentence and a blinking bar would be a lie. `steps(1)`
   snaps rather than fades, the way a terminal cursor does.
   The reduced-motion block in testimonial-responsive.css already freezes this
   animation, and testimonial.js does not start typing there either.

   It sits in the flow after the word and is aligned to the baseline. It used to
   be absolutely positioned against the word span, and because that span is an
   inline box its containing block is derived from the glyphs inside it — so the
   caret climbed and dropped as the letters under it changed between capitals,
   x-height letters and nothing at all. Aligned to the baseline instead, its box
   is decided by the font and the em size alone, so it holds one height and one
   position for every letter of every word.

   The few pixels it occupies are part of the reserved slot (see
   testimonial.js), so the line cannot rewrap because of it. */
.tst-type.is-typing::after {
  content: '';
  display: inline-block;
  width: .055em;
  height: .78em;
  margin-left: .06em;
  vertical-align: baseline;
  background: currentColor;
  animation: tst-caret 1s steps(1) infinite;
}
@keyframes tst-caret {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

/* Read by assistive tech in place of the half-typed word, which is why it is
   moved off-screen rather than hidden — display:none would take it out of the
   accessibility tree along with the rest. */
.tst-type__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.tst-hero__actions { display: flex; gap: 23.99px; }

.tst-hero__review-link {
  display: inline-flex;
  align-items: flex-start;
  justify-content: center;
  gap: 12px;
  padding-bottom: 4px;
  border-bottom: 1px solid #000;
  font-family: var(--tst-body);
  font-size: 23.99px;
  line-height: 1.21;
  color: var(--tst-black);
  text-decoration: none;
  transition: gap .25s ease, color .25s ease;
}
.tst-hero__review-link img { width: 24px; height: 24px; display: block; }
.tst-hero__review-link:hover { color: var(--tst-black); gap: 18px; }


/* ==========================================================================
   2. COMPLIMENTS — node 9672:9362
   ========================================================================== */

.tst-compliments { padding-bottom: 184px; }

.tst-sec-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  text-align: center;
}
.tst-sec-head--wall { gap: 36px; }
.tst-sec-head__stack { display: flex; flex-direction: column; align-items: center; gap: 24px; }
.tst-sec-head__sub {
  font-family: var(--tst-body);
  font-size: 24px;
  line-height: 1.21;
  color: var(--tst-grey);
}

.tst-slider {
  display: flex;
  align-items: stretch;
  margin-top: 80px;
  border: 1px solid var(--tst-black);
  border-radius: 80px;
  overflow: hidden;
  /* The carousel is dragged sideways to change compliment. pan-y hands the
     horizontal gesture to the script while the browser keeps vertical
     scrolling, so a swipe never has to fight the page. */
  touch-action: pan-y;
  cursor: grab;
}
.tst-slider.is-dragging {
  cursor: grabbing;
  user-select: none;
  -webkit-user-select: none;
}
/* Native image/video dragging would hijack the gesture on desktop. */
.tst-slider img,
.tst-slider video { -webkit-user-drag: none; user-drag: none; }

.tst-slider__media {
  position: relative;
  flex: 0 0 480px;
  height: 600px;
  display: flex;
  align-items: flex-end;
  padding: 40px;
}
.tst-slider__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
  background: var(--tst-black);
  display: block;
}
.tst-slider__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 53.417%, rgba(0, 0, 0, .2) 100%);
  pointer-events: none;
}

.tst-glass {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 24px;
  border-radius: 40px;
  background: rgba(30, 30, 30, .6);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
/* Both lines are <p>, and style.css sets a colour on bare `p`. A direct rule
   beats an inherited one no matter how specific the ancestor is, so the white
   has to be declared on the elements themselves — setting it only on the
   wrapper leaves the name and role rendering black over the video. */
.tst-glass__text { flex: 1 1 0; min-width: 0; color: var(--tst-white); }
.tst-glass__name {
  font-family: var(--tst-head);
  font-weight: 600;
  font-size: 18px;
  line-height: 1.28;
  letter-spacing: -0.09px;
  color: var(--tst-white);
}
.tst-glass__role {
  margin-top: 12px;
  font-family: var(--tst-body);
  font-size: 14px;
  line-height: 1.21;
  text-transform: capitalize;
  color: var(--tst-white);
}
.tst-glass__play {
  flex: 0 0 auto;
  width: 72px;
  height: 72px;
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
  transition: transform .25s ease;
}
.tst-glass__play img { width: 100%; height: 100%; display: block; }
.tst-glass__play:hover { transform: scale(1.08); }
/* The Iconsax badge is stroked rather than filled, and at 72px the comp's
   1.5px stroke would come out at 4.5 — twice the weight of the pause twin it
   alternates with. Scaling the art down inside the same 72px box holds the
   button's footprint while bringing the stroke back to the pause bars' 2px. */
.tst-glass__play .tst-icon svg { transform: scale(.66); }
.tst-glass__play:focus-visible { outline: 2px solid var(--tst-white); outline-offset: 3px; border-radius: 50%; }

/* Pause twin of the Figma play badge — same circle as the button, two bars
   instead of the triangle. Only shown while the clip is running. The bars are
   sized in % of the button so they stay in proportion at every breakpoint
   instead of needing an override each time the badge is resized. */
.tst-glass__pause {
  display: none;
  width: 100%;
  height: 100%;
  border: 2px solid var(--tst-white);
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  gap: 10%;
}
.tst-glass__pause::before,
.tst-glass__pause::after {
  content: '';
  width: 8%;
  height: 32%;
  border-radius: 2px;
  background: var(--tst-white);
}
.tst-glass__play.is-playing img,
.tst-glass__play.is-playing .tst-icon { display: none; }
.tst-glass__play.is-playing .tst-glass__pause { display: flex; }

/* ---------------------------------------------------------------- icons
   Iconsax play badge, mounted by testimonial-icons.js. The host is laid over
   the static <img> at the same size rather than replacing it in flow, so the
   button never changes footprint and the fallback stays measurable. `is-lottie`
   is set by the script the moment an animation exists — until then the <img>
   is what shows. The art ships in Iconsax's default near-black, and both
   badges sit on video, so the stroke is repainted white here instead of
   forking the JSON. */
.tst-icon {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.tst-icon svg { display: block; width: 100%; height: 100%; overflow: visible; }
.tst-icon svg path { stroke: var(--tst-white); }

.is-lottie { position: relative; }
.is-lottie > img { visibility: hidden; }
.is-lottie .tst-icon { display: block; }

.tst-slider__body {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 66px 56px 36px;
}

.tst-transcript {
  position: relative;
  height: 397px;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, .32) 0%, rgba(0, 0, 0, .72) 16%, #000 38%, #000 58%, rgba(0, 0, 0, .68) 80%, rgba(0, 0, 0, .22) 100%);
  mask-image: linear-gradient(180deg, rgba(0, 0, 0, .32) 0%, rgba(0, 0, 0, .72) 16%, #000 38%, #000 58%, rgba(0, 0, 0, .68) 80%, rgba(0, 0, 0, .22) 100%);
}
.tst-transcript__slide { display: none; }
.tst-transcript__slide.is-active { display: block; animation: tst-fade .45s ease both; }
@keyframes tst-fade { from { opacity: 0; } to { opacity: 1; } }

/* While the clip runs the block is positioned so the line being spoken stays
   in the middle of the window — it starts pushed down, so the opening line
   lands on the centre, then slides upward as the speech moves on. At rest the
   transform is zero, which is the comp's static composition. */
.tst-transcript__slide {
  will-change: transform;
  transition: transform .55s cubic-bezier(.16, 1, .3, 1);
}

.tst-transcript .tst-cue {
  font-family: var(--tst-body);
  font-size: 24px;
  line-height: 29px;
  color: var(--tst-light-grey);
  margin-bottom: 29px;
  transition: font-size .35s ease, line-height .35s ease, color .35s ease;
}
.tst-transcript .tst-cue:last-child { margin-bottom: 0; }

/* The line currently being spoken: larger, black, with the words already said
   turned blue. Frozen at one moment this is exactly the Figma "peak" line. */
.tst-transcript .tst-cue.is-active {
  font-size: 32px;
  line-height: 39px;
  color: var(--tst-black);
}
.tst-cue__w { transition: color .2s ease; }
.tst-transcript .tst-cue.is-active .tst-cue__w.is-spoken {
  color: var(--tst-blue);
  font-weight: 500;
}

.tst-dots { display: flex; align-items: center; gap: 12px; }
/* The bar stays the comp's 6px, but the button itself is padded out to a real
   touch target and the colour is painted on the content box only — so it looks
   identical and is no longer a 6px-tall thing to hit. */
.tst-dots button {
  width: 36px;
  height: 6px;
  padding: 12px 0;
  border: 0;
  border-radius: 3px;
  background: var(--tst-track);
  background-clip: content-box;
  box-sizing: content-box;
  cursor: pointer;
  transition: background-color .3s ease, width .3s ease;
}
/* background-color, not the `background` shorthand — the shorthand resets
   background-clip to border-box and paints the touch padding as a solid block. */
.tst-dots button.is-active { background-color: var(--tst-blue); }
.tst-dots button:hover { background-color: #cfd6de; }
.tst-dots button.is-active:hover { background-color: var(--tst-blue-hover); }

/* ==========================================================================
   3. WALL OF FAME — node 9603:6492
   ========================================================================== */

.tst-wall { padding-bottom: 200px; }

.tst-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 40px;
  margin-top: 80px;
  align-items: start;
}

/* text review card */
.tst-card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 500px;
  padding: 36px;
  border: 1px solid var(--tst-light-grey);
  border-radius: 40px;
  background: var(--tst-white);
  transition: border-color .3s ease, transform .3s ease, box-shadow .3s ease;
  /* Anchors the stretched link below. On phones .tst-grid already makes every
     card absolute, which anchors it just the same. */
  position: relative;
}
.tst-card:hover {
  border-color: var(--tst-black);
  transform: translateY(-4px);
  box-shadow: 0 18px 40px rgba(30, 30, 30, .08);
}

.tst-card__top { display: flex; flex-direction: column; gap: 36px; }
.tst-card__meta { display: flex; align-items: center; justify-content: space-between; }
.tst-card__source {
  font-family: var(--tst-body);
  font-size: 18px;
  line-height: 1.21;
  color: var(--tst-blue);
}
/* The label is a link out to the original review — underlined only on hover so
   the resting card still reads as the comp draws it. */
a.tst-card__source {
  text-decoration: none;
  transition: color .2s ease;
}

/* The whole card opens that review, not just the label. The link is stretched
   over the card with a pseudo-element rather than wrapping the <article> in an
   <a>: wrapping would nest this link inside another one, and it would change
   the element the wall script measures and deals out. This way the markup is
   untouched, there is still exactly one link carrying one accessible name, and
   middle-click, right-click and ctrl-click all behave the way a real link does.

   Hovering anywhere on the card is hovering the link, so the label picks up its
   own hover state alongside the card's lift — the two existing effects now fire
   together instead of only over the label. */
a.tst-card__source::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}
a.tst-card__source:hover,
a.tst-card__source:focus-visible {
  color: var(--tst-blue-hover);
  text-decoration: underline;
}
a.tst-card__source:focus-visible {
  outline: 2px solid var(--tst-black);
  outline-offset: 3px;
  border-radius: 4px;
}
.tst-stars--lg { width: 114px; height: 18px; display: block; flex: 0 0 auto; }
/* The card is a fixed 500px so every tile in a row lines up; the longest
   reviews run well past that, so the quote is clamped to the lines that fit
   between the meta row and the author block. */
.tst-card__quote {
  font-family: var(--tst-body);
  font-size: 16px;
  line-height: 24px;
  color: var(--tst-grey);
  display: -webkit-box;
  -webkit-line-clamp: 10;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.tst-card__bottom { display: flex; flex-direction: column; gap: 36px; }
.tst-card__rule { height: 1px; background: var(--tst-light-grey); }
.tst-person { display: flex; align-items: center; gap: 18px; }
/* avatar.png is pre-cropped to the window the comp shows (node 9603:6227 draws
   the source poster at 146.67% x 183.33%, offset -26.71% / -61.67%), so the
   frame only needs a straight cover fit. */
.tst-person__pic {
  flex: 0 0 auto;
  width: 60px;
  height: 60px;
  border-radius: 8px;
  overflow: hidden;
  display: block;
}
.tst-person__pic img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Most reviewers publish no profile photo, so the tile falls back to their
   initials in the same 60px frame the photo would have filled. */
.tst-person__pic--initials {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--tst-blue);
  color: var(--tst-white);
  font-family: var(--tst-body);
  font-weight: 700;
  font-size: 20px;
  line-height: 1;
  letter-spacing: .02em;
}
.tst-person__name {
  font-family: var(--tst-body);
  font-weight: 700;
  font-size: 16px;
  line-height: 1.21;
  color: var(--tst-black);
  text-transform: capitalize;
}
.tst-person__role {
  margin-top: 8px;
  font-family: var(--tst-body);
  font-size: 14px;
  line-height: 1.21;
  color: var(--tst-grey);
}

/* video review card — same 500px box as .tst-card so the two card types sit
   flush in every row; the media pane takes whatever the footer leaves. */
.tst-vcard {
  display: flex;
  flex-direction: column;
  height: 500px;
  border: 1px solid var(--tst-light-grey);
  border-radius: 40px;
  overflow: hidden;
  transition: border-color .3s ease, transform .3s ease, box-shadow .3s ease;
}
.tst-vcard:hover {
  border-color: var(--tst-black);
  transform: translateY(-4px);
  box-shadow: 0 18px 40px rgba(30, 30, 30, .08);
}

.tst-vcard__media {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: flex-end;
  padding: 24px;
  overflow: hidden;
  background: var(--tst-black);
}
/* Blurred fill for the two side columns the contained clip leaves empty. It is
   the poster frame, scaled past the edges so the blur has no visible seam, and
   dimmed so the speaker stays the brightest thing in the card. */
.tst-vcard__fill {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
  filter: blur(28px) brightness(.45) saturate(1.15);
  transform: scale(1.25);
  pointer-events: none;
  -webkit-user-drag: none;
  user-drag: none;
}
/* The clip plays in place here rather than in a popup, so the video itself is
   the media pane — the poster attribute carries the still until playback.
   `contain` keeps the 9:16 source whole and centred in this near-square pane:
   `cover` cropped ~40% of the frame off the bottom and cut the speaker's face
   in half. Transparent background so the blurred fill above shows through the
   letterbox. */
.tst-vcard__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center center;
  background: transparent;
  display: block;
}

.tst-vcard__pill {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: 20px;
  border-radius: 20px;
  background: rgba(30, 30, 30, .6);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
.tst-vcard__info { flex: 1 1 0; min-width: 0; }
.tst-vcard__row { display: flex; align-items: center; gap: 12px; }
.tst-vcard__name {
  font-family: var(--tst-body);
  font-weight: 700;
  font-size: 16px;
  line-height: 1.21;
  color: var(--tst-white);
  white-space: nowrap;
}
.tst-stars--sm { width: 104px; height: 16px; display: block; flex: 0 0 auto; }
.tst-vcard__role {
  margin-top: 8px;
  font-family: var(--tst-body);
  font-size: 14px;
  line-height: 1.21;
  color: var(--tst-light-grey);
}
.tst-vcard__play {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  transition: transform .25s ease;
}
.tst-vcard__play img { width: 100%; height: 100%; display: block; }
.tst-vcard__play:hover { transform: scale(1.12); }

/* Pause glyph drawn in % of the button, matching .tst-glass__pause, so it holds
   its proportions at every breakpoint without a per-size override. */
.tst-vcard__pause {
  display: none;
  width: 100%;
  height: 100%;
  border: 2px solid var(--tst-white);
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  gap: 10%;
}
.tst-vcard__pause::before,
.tst-vcard__pause::after {
  content: '';
  width: 8%;
  height: 32%;
  border-radius: 2px;
  background: var(--tst-white);
}
.tst-vcard__play.is-playing img,
.tst-vcard__play.is-playing .tst-icon { display: none; }
.tst-vcard__play.is-playing .tst-vcard__pause { display: flex; }
/* Same stroke-weight correction as the hero badge — see .tst-glass__play. At
   36px the comp's 1.5px lands at 2.25 against the pause twin's 2px border. */
.tst-vcard__play .tst-icon svg { transform: scale(.9); }

/* While the clip runs the pill would sit over the speaker, so it fades out and
   stops catching clicks until playback pauses or ends. */
.tst-vcard.is-playing .tst-vcard__pill {
  opacity: 0;
  pointer-events: none;
}
.tst-vcard__pill { transition: opacity .25s ease; }
.tst-vcard.is-playing:hover .tst-vcard__pill,
.tst-vcard.is-playing:focus-within .tst-vcard__pill {
  opacity: 1;
  pointer-events: auto;
}

.tst-vcard__foot {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 20px;
  background: var(--tst-blue);
}
.tst-vcard__foot img { width: 11.403px; height: 8.744px; margin-top: 7px; flex: 0 0 auto; }
.tst-vcard__foot p {
  font-family: var(--tst-body);
  font-size: 16px;
  line-height: 24px;
  color: var(--tst-white);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Cards past the current page stay in the DOM (so no-JS shows the full wall)
   and are pulled out of layout by the wall script. */
.tst-grid > .is-wall-hidden { display: none; }

.tst-wall__more { display: flex; justify-content: center; margin-top: 40px; }
.tst-wall__more .tst-btn { cursor: pointer; }
.tst-wall__more [hidden] { display: none; }

/* ==========================================================================
   4. FEEDBACK — node 9635:5531
   ========================================================================== */

.tst-feedback { padding-bottom: 200px; }

.tst-feedback__panel {
  display: flex;
  align-items: flex-end;
  gap: 71px;
  padding: 80px;
  border-radius: 80px;
  background: var(--tst-blue);
  overflow: hidden;
}

.tst-feedback__left {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 71px;
}

.tst-feedback__title {
  font-family: var(--tst-head);
  font-weight: 600;
  font-size: 90px;
  line-height: 100px;
  letter-spacing: -0.48px;
  color: var(--tst-white);
}

/* speech bubbles — node 9730:5648
   The cluster scales as one unit off `--k`, so a breakpoint only has to set that
   one number instead of restating three positions. Each bubble still rotates
   about its own centre (as in the comp), so what gets multiplied by --k is the
   bubble's *centre*, and `left`/`top` back the half-size off again. At --k: 1
   every value collapses to the Figma number exactly — 131.793 - 122.263 = 9.53. */
.tst-bubbles {
  --k: 1;
  position: relative;
  width: calc(563px * var(--k));
  height: calc(308.29px * var(--k));
}

.tst-bubble {
  position: absolute;
  width: 255.406px;
  height: 120.259px;
  transform-origin: 50% 50%;
}
.tst-bubble--1 {
  width: 244.526px;
  height: 122.126px;
  left: calc(131.793px * var(--k) - 122.263px);
  top:  calc(84.013px  * var(--k) - 61.063px);
  transform: rotate(-10.93deg) scale(var(--k));
}
.tst-bubble--2 {
  left: calc(430.803px * var(--k) - 127.703px);
  top:  calc(81.2895px * var(--k) - 60.1295px);
  transform: rotate(4.71deg) scale(var(--k));
}
.tst-bubble--3 {
  left: calc(273.033px  * var(--k) - 127.703px);
  top:  calc(236.0695px * var(--k) - 60.1295px);
  transform: scale(var(--k));
}

.tst-bubble__shadow {
  position: absolute;
  left: -42.32px;
  top: -30.22px;
  width: calc(100% + 84.64px);
  height: calc(100% + 82.86px);
  pointer-events: none;
}
.tst-bubble__shape { position: absolute; inset: 0; width: 100%; height: 100%; }

.tst-bubble__body {
  position: absolute;
  inset: 0 20px 12px 24.73px;
  display: flex;
  align-items: center;
  gap: 18px;
}
.tst-bubble__icon { flex: 0 0 auto; width: 70.113px; height: 70.113px; display: block; }
.tst-bubble__label {
  font-family: var(--tst-head);
  font-weight: 500;
  font-size: 20.406px;
  line-height: 1.28;
  letter-spacing: -0.102px;
  color: var(--tst-black);
  white-space: nowrap;
}

/* form card */
.tst-form {
  flex: 0 0 583px;
  display: flex;
  flex-direction: column;
  gap: 30px;
  padding: 40px;
  border-radius: 40px;
  background: var(--tst-white);
}

.tst-rate { display: flex; flex-direction: column; align-items: center; gap: 15px; position: relative;}
.tst-rate__title {
  font-family: var(--tst-head);
  font-weight: 500;
  font-size: 16px;
  line-height: 1.28;
  color: var(--tst-black);
}
.tst-rate__group { display: flex; flex-direction: column; gap: 12px; }
.tst-rate__stars { display: flex; align-items: center; gap: 16px; }

.tst-rate__btn {
  width: 56px;
  height: 56px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: rgba(107, 114, 128, .08);
  cursor: pointer;
  transition: background-color .2s ease;
}
.tst-rate__btn svg { width: 28px; height: 28px; display: block; }
.tst-rate__btn svg path {
  fill: none;
  stroke: var(--tst-light-grey);
  transition: fill .2s ease, stroke .2s ease;
}
.tst-rate__btn.is-on svg path { fill: var(--tst-yellow); stroke: var(--tst-yellow); }
.tst-rate__btn:hover { background: rgba(107, 114, 128, .14); }
.tst-rate__btn:focus-visible { outline: 2px solid var(--tst-blue); outline-offset: 2px; }

.tst-rate__scale {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: var(--tst-head);
  font-size: 14px;
  line-height: 1.28;
  color: var(--tst-grey);
}

.tst-fields { display: flex; flex-direction: column; gap: 30px; position: relative;}
.tst-field { display: flex; flex-direction: column; gap: 0px;  position: relative;}
.tst-field label {
  font-family: var(--tst-head);
  font-weight: 400;
  font-size: 14px;
  line-height: 1.28;
  text-transform: capitalize;
  color: var(--tst-black);
}
.tst-field input,
.tst-field textarea {
  width: 100%;
  padding: 15px 18px;
  border: 1px solid var(--tst-black);
  border-radius: 0;
  background: var(--tst-white);
  font-family: var(--tst-head);
  font-size: 16px;
  line-height: 1.28;
  color: var(--tst-black);
  transition: border-color .2s ease, box-shadow .2s ease;
}
.tst-field textarea { resize: vertical; min-height: 68px; }
.tst-field input::placeholder,
.tst-field textarea::placeholder { font-style: italic; color: var(--tst-grey); }
.tst-field input:focus,
.tst-field textarea:focus {
  outline: none;
  border-color: var(--tst-blue);
  box-shadow: 0 0 0 3px rgba(59, 130, 246, .16);
}

.tst-field input.is-invalid,
.tst-field textarea.is-invalid { border-color: var(--tst-error); }
.tst-field input.is-invalid:focus,
.tst-field textarea.is-invalid:focus { box-shadow: 0 0 0 3px rgba(217, 45, 32, .16); }

/* Validation line under a field. It is empty until something is wrong, and
   collapses to nothing when empty so the 40px field rhythm is unchanged in the
   default state — only a field with a real message grows. */
.tst-field__msg {
  font-family: var(--tst-body);
  font-size: 13px;
  line-height: 1.4;
  color: var(--tst-error);
      position: absolute;
    left: 0px;
    bottom: -20px;
}
.tst-field__msg:empty { display: none; }

/* The rating message hangs under the centred star row, so it centres too. */
#tstRatingMsg { text-align: center; }

.tst-field--captcha { gap: 12px; }
/* Cloudflare's default 300 x 65 box, left-aligned in its row — the same widget
   the traffic-estimator, demo, CPA-goal and register forms show, so the captcha
   reads the same wherever it turns up on the site. min-height reserves the space
   so the form does not jump while the widget loads.

   --tst-cap-fit is 1 unless the row is narrower than the widget's 300px floor,
   in which case testimonial.php sets the fraction it has to shrink by (phones,
   where the panel and the form card have both taken padding out of the row). It
   scales rather than reshapes, so the widget stays the one on every other page.
   The host's height is set alongside it, since a transform leaves the original
   height behind in the layout. */
.tst-field--captcha .cf-turnstile {
  width: 300px;
  /* Holds the row open before the widget arrives and if the script never gets to
     measure it. Once it has, testimonial.php sets the exact height inline. */
  min-height: calc(72px * var(--tst-cap-fit, 1));
  transform: scale(var(--tst-cap-fit, 1));
  transform-origin: left top;
}

/* Matches `.ate-modal-submit` — the submit on the traffic-estimator form inside
   index.php (assets/css/style.css). Same pill, same 16px Be Vietnam Pro
   SemiBold, and the same disabled treatment: the button stays blue and dims to
   .65 rather than turning grey, so it reads as "not yet" instead of "broken".
   That replaces the comp's grey #F3F4F5 / #D2D2D2 idle state, which is why
   `.is-ready` no longer carries any colour of its own. */
.tst-submit {
  width: 100%;
  margin-top: 0px;
  padding: 16px;
  border: 0;
  border-radius: 999px;
  background: var(--tst-blue);
  color: var(--tst-white);
  font-family: var(--tst-head);
  font-weight: 600;
  font-size: 16px;
  line-height: 1.5;
  cursor: pointer;
  transition: background-color .2s ease, opacity .2s ease;
}
.tst-submit:hover:not(:disabled) { background: #2f74e6; }
/* Disabled now means "verify the captcha first" far more often than it means
   "a submission is running", and a spinner cursor on a button that is waiting
   for the reader to act reads as the page being busy. `progress` is kept for
   the one case that really is busy. */
.tst-submit:disabled { opacity: .65; cursor: not-allowed; }
.tst-submit.is-busy { cursor: progress; }
.tst-submit:focus-visible { outline: 2px solid var(--tst-blue-hover); outline-offset: 3px; }

.tst-rate #tstRatingMsg {
        position: absolute !important;
    width: 100%;
}

/* ==========================================================================
   5. COMMUNITY CTA — node 9650:5659
   ========================================================================== */

.tst-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 40px;
  padding: 180px var(--tst-gutter);
  border-radius: 80px 80px 0 0;
  border-bottom: 1px solid var(--tst-grey);
  background: linear-gradient(180deg, #2563eb 0%, #1e1e1e 100%);
}
.tst-cta__title {
  font-family: var(--tst-head);
  font-weight: 400;
  font-size: 36px;
  line-height: 46px;
  letter-spacing: -0.18px;
  color: var(--tst-white);
}
.tst-cta__actions { display: flex; gap: 23.99px; flex: 0 0 auto; }

