/* Homepage motion layer - loaded on /test only, after home.css.
   Everything here is additive: home.css owns layout and color, this file
   owns movement. Nothing is hidden unless .tph-motion is on <html>, which
   an inline head script only sets when JS is running and the visitor has
   not asked for reduced motion. No JS / reduced motion => static page. */

/* ---------- Organic blob morph ----------
   The 47% 53% 61% 39% / 45% 41% 59% 55% shape is a brand motif that shows up
   on the hero blob, the dark panel blob and the testimonial portrait. Morphing
   all three on the same slow cycle reads as one living system. */

@keyframes tph-blob-morph {
  0%,
  100% {
    border-radius: 47% 53% 61% 39% / 45% 41% 59% 55%;
  }
  25% {
    border-radius: 58% 42% 44% 56% / 52% 56% 44% 48%;
  }
  50% {
    border-radius: 39% 61% 52% 48% / 61% 38% 62% 39%;
  }
  75% {
    border-radius: 52% 48% 38% 62% / 41% 57% 43% 59%;
  }
}

.tph-hero-blob,
.tph-quote-photo {
  animation: tph-blob-morph 22s ease-in-out infinite;
  will-change: border-radius;
}

.tph-gives-blob {
  animation: tph-blob-morph 26s ease-in-out -8s infinite;
  will-change: border-radius;
}

/* Slow drift on the emerald blob inside the dark panel, layered on top of
   the morph via a wrapper-free approach: the morph owns border-radius, the
   float owns transform, so the two never fight. */
@keyframes tph-blob-float {
  0%,
  100% {
    transform: translate3d(0, 0, 0);
  }
  50% {
    transform: translate3d(-3%, -5%, 0);
  }
}

.tph-gives-blob {
  animation:
    tph-blob-morph 26s ease-in-out -8s infinite,
    tph-blob-float 19s ease-in-out infinite;
}

/* ---------- Hero: load-in + three-plane parallax ---------- */

/* Scroll-driven planes. home-motion.js writes --tph-hero-p as a unitless
   count of pixels scrolled past the top of the page, clamped to the hero's
   own height; the transforms below turn that one number into three depths.
   Unitless so it can be multiplied by px on one line and deg on the next.
   Transform only - never top/margin - so this stays on the compositor. */

.tph-hero-blob {
  transform: translate3d(0, calc(var(--tph-hero-p, 0) * 0.16px), 0);
}

.tph-hero-shot,
.tph-hero-chat {
  transform: translate3d(0, calc(var(--tph-hero-p, 0) * 0.05px), 0);
}

.tph-hero-doodle {
  transform: translate3d(0, calc(var(--tph-hero-p, 0) * -0.11px), 0)
    rotate(calc(var(--tph-hero-p, 0) * 0.012deg));
}

/* The blob morph animates border-radius and the parallax animates transform.
   A CSS animation on the same element would overwrite the transform, so the
   hero blob's movement is expressed only through the custom property above. */

.tph-motion .tph-hero-copy > *,
.tph-motion .tph-hero-visual {
  opacity: 0;
}

.tph-motion .tph-hero-copy > * {
  transform: translate3d(0, 18px, 0);
  transition:
    opacity 620ms cubic-bezier(0.22, 0.61, 0.36, 1),
    transform 620ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.tph-motion .tph-hero-visual {
  transition: opacity 900ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.tph-motion .tph-hero.is-ready .tph-hero-copy > *,
.tph-motion .tph-hero.is-ready .tph-hero-visual {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

.tph-hero-copy > *:nth-child(1) { transition-delay: 60ms; }
.tph-hero-copy > *:nth-child(2) { transition-delay: 150ms; }
.tph-hero-copy > *:nth-child(3) { transition-delay: 240ms; }
.tph-hero-copy > *:nth-child(4) { transition-delay: 330ms; }
.tph-hero-copy > *:nth-child(5) { transition-delay: 420ms; }

.tph-hero-visual { transition-delay: 240ms; }

/* ---------- Hero: the chat, live instead of a screenshot ----------
   A DOM rebuild of anna-interview-chat.png that plays one exchange on load.
   Every size below is in cqw against the card's own width, taken from the
   988px-wide source image, so the card scales exactly like the PNG it
   replaces at every breakpoint. Colours are sampled from the same file. */

.tph-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.tph-hero-chat {
  display: none;
  position: relative;
  container-type: inline-size;
  /* Slightly tighter than the PNG's 988/1112. The message area is bottom
     anchored, so all slack collects above Anna's bubble; this leaves just
     enough headroom for it to rise into place without a dead band at rest. */
  aspect-ratio: 988 / 1071;
  flex-direction: column;
  border-radius: var(--tp-radius-lg);
  background: #fdfdfb;
  box-shadow: var(--tp-shadow-raised);
  overflow: hidden;
  font-family: var(--tp-font-sans);
}

.tph-motion .tph-hero-chat {
  display: flex;
}

/* The screenshot carried the visual's intrinsic width; the card has only an
   aspect ratio. Below 992px home.css gives .tph-hero-visual auto side margins,
   which makes it a shrink-to-fit grid item - with no intrinsic width left to
   shrink to, it would collapse to zero. An explicit width restores it, and
   the existing max-width still caps and centres it. */
.tph-motion .tph-hero-visual {
  width: 100%;
}

/* The screenshot and the live card occupy the same slot; only one is ever
   displayed, and both take the hero's mid-depth parallax. */
.tph-motion .tph-hero-shot {
  display: none;
}

/* --- title bar --- */

.tph-hero-chat-bar {
  display: flex;
  align-items: center;
  gap: 2.4cqw;
  padding: 5.8cqw 5.7cqw 0;
  flex: none;
}

.tph-hero-chat-icon {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 8.3cqw;
  height: 8.3cqw;
  border: 0.1cqw solid #e4e6e1;
  border-radius: 50%;
  color: #4a5044;
  font-size: 3cqw;
  line-height: 1;
}

.tph-hero-chat-icon-more {
  margin-left: auto;
}

.tph-hero-chat-id {
  display: flex;
  flex-direction: column;
  gap: 0.4cqw;
  min-width: 0;
}

.tph-hero-chat-who {
  font-size: 2.55cqw;
  font-weight: 700;
  line-height: 1.2;
  color: #22281f;
}

.tph-hero-chat-status {
  display: inline-flex;
  align-items: center;
  gap: 1.1cqw;
  font-size: 2.15cqw;
  line-height: 1.2;
  color: #7a806f;
}

.tph-hero-chat-dot {
  width: 1.35cqw;
  height: 1.35cqw;
  border-radius: 50%;
  background: #2e8b62;
  flex: none;
  animation: tph-dot-pulse 2.6s ease-in-out infinite;
}

/* --- message area ---
   Bottom-anchored, like every chat client: messages stack up from the input
   and the earlier one rides upward as the reply grows. The card's fixed
   aspect ratio means that motion happens inside a box that never resizes. */

.tph-hero-chat-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 2.9cqw;
  padding: 4cqw 5.7cqw 0;
  overflow: hidden;
}

/* A row that has not started yet is out of the layout, so the conversation
   genuinely stacks up from the composer: each arrival pushes the previous
   message toward the top of the card. The card's fixed aspect ratio and the
   ghost text mean the final state is reached exactly, with nothing clipped. */
.tph-hero-chat-row {
  display: none;
  align-items: flex-start;
  gap: 2cqw;
}

.tph-hero-chat-row.is-typing,
.tph-hero-chat-row.is-said {
  display: flex;
}

.tph-hero-chat-row-cand {
  justify-content: flex-end;
}

.tph-hero-chat-avatar {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 5.7cqw;
  height: 5.7cqw;
  border-radius: 50%;
  background: #e7e2d4;
  color: #4a4a3e;
  font-size: 2.25cqw;
  font-weight: 600;
}

.tph-hero-chat-bubble {
  position: relative;
  padding: 3.5cqw 3.6cqw;
  border-radius: 2.1cqw;
  font-size: 2.65cqw;
  line-height: 1.54;
  color: #33382e;
  opacity: 0;
  transform: translate3d(0, 1.4cqw, 0);
  transition:
    opacity 420ms ease-out,
    transform 420ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.tph-hero-chat-row.is-said .tph-hero-chat-bubble {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

.tph-hero-chat-bubble p {
  margin: 0;
}

.tph-hero-chat-bubble p + p {
  margin-top: 1.6em;
}

.tph-hero-chat-bubble-anna {
  background: #f8f4ea;
  max-width: 80.2cqw;
}

.tph-hero-chat-bubble-cand {
  background: #e2e9df;
  width: 64.4cqw;
}

.tph-hero-chat-ghost {
  visibility: hidden;
}

.tph-hero-chat-typed {
  position: absolute;
  top: 3.5cqw;
  left: 3.6cqw;
  right: 3.6cqw;
}

.tph-hero-chat-caret {
  display: inline-block;
  width: 0.22cqw;
  height: 1.02em;
  background: var(--tp-emerald);
  vertical-align: -0.15em;
  margin-left: 0.1cqw;
  animation: tph-caret 1.05s step-end infinite;
}

@keyframes tph-caret {
  0%,
  50% {
    opacity: 1;
  }
  51%,
  100% {
    opacity: 0;
  }
}

/* Typing pill, sized to the card like everything else. It sits in the flow
   where the bubble will be so the row does not jump when the two swap. */
.tph-hero-chat-dots {
  display: none;
  align-items: center;
  gap: 0.9cqw;
  padding: 2.4cqw 3cqw;
  border-radius: 2.1cqw;
  background: #f1efe9;
}

.tph-hero-chat-row.is-typing .tph-hero-chat-dots {
  display: inline-flex;
}

.tph-hero-chat-dots span {
  width: 1.15cqw;
  height: 1.15cqw;
  border-radius: 50%;
  background: #a2a89b;
  animation: tph-blip 1.1s ease-in-out infinite;
}

.tph-hero-chat-dots span:nth-child(2) { animation-delay: 0.18s; }
.tph-hero-chat-dots span:nth-child(3) { animation-delay: 0.36s; }

/* While a row is typing its bubble is out of the layout entirely, so the
   pill - not the empty bubble box - is what reserves space. */
.tph-hero-chat-row.is-typing .tph-hero-chat-bubble {
  display: none;
}

/* --- composer --- */

.tph-hero-chat-foot {
  display: flex;
  align-items: center;
  gap: 2.4cqw;
  padding: 4cqw 5.7cqw 5.7cqw;
  flex: none;
}

.tph-hero-chat-input {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  height: 9.1cqw;
  padding: 0 4cqw;
  border: 0.1cqw solid #e4e6e1;
  border-radius: 999px;
  font-size: 2.45cqw;
  color: #a2a89b;
}

.tph-hero-chat-send {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 8.9cqw;
  height: 8.9cqw;
  border-radius: 50%;
  background: #2e6b57;
  color: #fff;
  font-size: 3.4cqw;
  line-height: 1;
}

/* The "always on" pulse on eyebrow dots - small, and it earns its keep by
   echoing Anna's status indicator. */
@keyframes tph-dot-pulse {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.75;
  }
  50% {
    transform: scale(1.3);
    opacity: 1;
  }
}

.tph-hero .tph-eyebrow-dot,
.tph-trust-chat-name .tph-eyebrow-dot {
  animation: tph-dot-pulse 2.6s ease-in-out infinite;
}

/* ---------- Quote marquee: two rows, opposing ----------
   The single track in home.css becomes two. The lower row runs the same
   keyframe in reverse and takes noticeably longer to cross, so the rows
   separate into two planes instead of reading as one striped block -
   parallax without a scroll listener. */

/* Each row now carries half the quotes, so a track is half as wide and the
   120s in home.css would crawl. These durations restore roughly the original
   pixel speed on the top row and run the bottom one slower. */
.tph-loved-marquee-track {
  animation-duration: 62s;
}

.tph-loved-marquee-track-alt {
  margin-top: 14px;
  animation-duration: 84s;
  animation-direction: reverse;
}

@media (max-width: 991px) {
  .tph-loved-marquee-track {
    animation-duration: 52s;
  }

  .tph-loved-marquee-track-alt {
    animation-duration: 70s;
  }
}

/* ---------- Stat counters ---------- */

/* Counting numbers must not reflow the row as digits are added. */
.tph-loved-num {
  font-variant-numeric: tabular-nums;
}

.tph-motion .tph-loved-stat {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition:
    opacity 520ms ease-out,
    transform 520ms cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: calc(var(--i, 0) * 110ms);
}

.tph-motion .tph-loved-stats.is-inview .tph-loved-stat {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* The zero doesn't count - nothing to count to. It lands last with a punch,
   which is the point of the stat. */
@keyframes tph-punch {
  0% {
    transform: scale(0.7);
    opacity: 0;
  }
  60% {
    transform: scale(1.12);
    opacity: 1;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.tph-motion .tph-loved-num[data-tph-punch] {
  display: inline-block;
  opacity: 0;
}

.tph-motion .tph-loved-num[data-tph-punch].is-punched {
  animation: tph-punch 640ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

/* ---------- Anna's chat: bubbles that arrive ---------- */

/* Each bubble reserves its final box from the start, so nothing below it
   shifts while the conversation plays. During its "typing" beat the bubble's
   own surface is invisible and a small pill sits in its top corner; on
   resolve the pill fades and the surface + copy arrive together. */

.tph-trust-bubble {
  transition:
    background-color 340ms ease-out,
    box-shadow 340ms ease-out,
    opacity 480ms ease-out,
    transform 480ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.tph-motion .tph-trust-bubble {
  opacity: 0;
  transform: translate3d(-18px, 10px, 0);
}

.tph-motion .tph-trust-bubble-offset {
  transform: translate3d(18px, 10px, 0);
}

.tph-motion .tph-trust-bubble.is-typing,
.tph-motion .tph-trust-bubble.is-said {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* Typing beat: hide the bubble's surface and its copy, show the pill. */
.tph-motion .tph-trust-bubble.is-typing {
  background: transparent;
  box-shadow: none;
}

.tph-trust-bubble-body {
  transition:
    opacity 380ms ease-out,
    transform 380ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Hidden from the start, not just during .is-typing - otherwise the copy
   fades out as the bubble fades in and you see a ghost of the text behind
   the typing pill. */
.tph-motion .tph-trust-bubble-body {
  opacity: 0;
  transform: translate3d(0, 6px, 0);
}

.tph-motion .tph-trust-bubble.is-said .tph-trust-bubble-body {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

.tph-trust-typing {
  position: absolute;
  top: 14px;
  left: 16px;
  display: none;
  align-items: center;
  gap: 5px;
  padding: 11px 14px;
  border-radius: 16px 16px 16px 5px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(30, 77, 64, 0.08);
  opacity: 0;
  transition: opacity 240ms ease-out;
  pointer-events: none;
}

.tph-trust-bubble-offset .tph-trust-typing {
  left: auto;
  right: 16px;
  border-radius: 16px 16px 5px 16px;
}

.tph-motion .tph-trust-typing {
  display: inline-flex;
}

.tph-motion .tph-trust-bubble.is-typing .tph-trust-typing {
  opacity: 1;
}

.tph-trust-typing span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--tp-pewter);
}

@keyframes tph-blip {
  0%,
  80%,
  100% {
    transform: translateY(0);
    opacity: 0.5;
  }
  40% {
    transform: translateY(-4px);
    opacity: 1;
  }
}

.tph-trust-typing span:nth-child(1) { animation: tph-blip 1.1s ease-in-out infinite; }
.tph-trust-typing span:nth-child(2) { animation: tph-blip 1.1s ease-in-out 0.18s infinite; }
.tph-trust-typing span:nth-child(3) { animation: tph-blip 1.1s ease-in-out 0.36s infinite; }

/* ---------- Closing CTA diamond ---------- */

.tph-cta {
  position: relative;
  overflow: hidden;
}

/* Soft emerald wash behind the mark, drifting slowly so the flattest
   section on the page has some life in it. */
.tph-cta::before {
  content: "";
  position: absolute;
  top: -140px;
  left: 50%;
  width: 620px;
  height: 620px;
  margin-left: -310px;
  background: radial-gradient(circle, rgba(108, 197, 162, 0.22), rgba(108, 197, 162, 0) 68%);
  pointer-events: none;
  animation: tph-cta-wash 16s ease-in-out infinite;
}

@keyframes tph-cta-wash {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
    opacity: 0.85;
  }
  50% {
    transform: translate3d(0, 26px, 0) scale(1.08);
    opacity: 1;
  }
}

.tph-cta-mark {
  position: relative;
}

@keyframes tph-mark-in {
  0% {
    opacity: 0;
    transform: scale(0.72) rotate(-9deg);
  }
  100% {
    opacity: 1;
    transform: scale(1) rotate(0deg);
  }
}

@keyframes tph-mark-float {
  0%,
  100% {
    transform: translate3d(0, 0, 0) rotate(-1.5deg);
  }
  50% {
    transform: translate3d(0, -10px, 0) rotate(1.5deg);
  }
}

.tph-motion .tph-cta-mark {
  opacity: 0;
}

.tph-motion .tph-cta-mark.is-inview {
  animation:
    tph-mark-in 760ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards,
    tph-mark-float 6.5s ease-in-out 760ms infinite;
}

/* ---------- Reduced motion ----------
   .tph-motion is never applied when the visitor asks for reduced motion, so
   the reveal states above are already inert. This block stops the ambient
   loops that run without it. */

@media (prefers-reduced-motion: reduce) {
  .tph-hero-blob,
  .tph-gives-blob,
  .tph-quote-photo,
  .tph-cta::before,
  .tph-cta-mark,
  .tph-hero .tph-eyebrow-dot,
  .tph-trust-chat-name .tph-eyebrow-dot,
  .tph-trust-typing span,
  .tph-hero-chat-dot,
  .tph-hero-chat-dots span,
  .tph-hero-chat-caret {
    animation: none;
  }

  .tph-hero-blob,
  .tph-hero-shot,
  .tph-hero-chat,
  .tph-hero-doodle {
    transform: none;
  }

  .tph-trust-typing {
    display: none;
  }

  /* Without .tph-motion the screenshot is what renders; the live card stays
     out of the layout entirely. */
  .tph-hero-chat {
    display: none;
  }
}
