/* ============================================================================
   PledgePay — depth & motion layer
   ----------------------------------------------------------------------------
   Loaded AFTER style.css and deliberately kept in its own file: everything here
   is presentational, so the whole treatment can be dropped by removing one
   <link> without touching the 59KB base stylesheet.

   The "3D" is CSS transforms and layered blur, not WebGL. A Three.js scene
   would cost ~150KB of JS plus a canvas repaint every frame — on the mid-range
   Android this site is mostly viewed on, that is the difference between a hero
   that feels expensive and one that stutters. Transforms are composited on the
   GPU and cost effectively nothing.

   Every motion rule is switched off under prefers-reduced-motion at the bottom.
   ========================================================================== */

/* ---------- shared easing + depth tokens ---------- */
:root {
  --hx-ease: cubic-bezier(.22, 1, .36, 1);
  --hx-depth-1: 0 2px 6px rgba(20,8,43,.06), 0 8px 24px rgba(20,8,43,.08);
  --hx-depth-2: 0 4px 12px rgba(20,8,43,.08), 0 18px 48px rgba(20,8,43,.12);
  --hx-depth-3: 0 8px 20px rgba(20,8,43,.10), 0 40px 90px rgba(20,8,43,.20);
  --hx-glass: rgba(255,255,255,.72);
}

/* ============================ HERO STAGE ============================ */

.hero {
  position: relative;
  overflow: hidden;
  /* The stage. Everything inside can now sit at a real Z depth. */
  perspective: 1500px;
  perspective-origin: 60% 40%;
}
.hero > .container { position: relative; z-index: 2; }

/* ---------- aurora: three blurred orbs drifting at different rates ----------
   Different sizes and durations mean they never sync up, so the background
   never reads as a loop. */
.hx-aurora {
  position: absolute; inset: -25% -12% -18%;
  z-index: 0; pointer-events: none;
}
.hx-orb {
  position: absolute; border-radius: 50%;
  filter: blur(64px);
  will-change: transform;
}
.hx-orb--a {
  width: 42vw; height: 42vw; max-width: 620px; max-height: 620px;
  top: -6%; left: -8%;
  background: radial-gradient(circle at 35% 35%, rgba(157,78,221,.55), rgba(157,78,221,0) 68%);
  animation: hxDriftA 22s var(--hx-ease) infinite;
}
.hx-orb--b {
  width: 34vw; height: 34vw; max-width: 500px; max-height: 500px;
  top: 22%; right: -6%;
  background: radial-gradient(circle at 60% 40%, rgba(199,125,255,.50), rgba(199,125,255,0) 70%);
  animation: hxDriftB 27s var(--hx-ease) infinite;
}
.hx-orb--c {
  width: 30vw; height: 30vw; max-width: 430px; max-height: 430px;
  bottom: -14%; left: 32%;
  background: radial-gradient(circle at 50% 50%, rgba(90,24,154,.38), rgba(90,24,154,0) 70%);
  animation: hxDriftC 19s var(--hx-ease) infinite;
}
@keyframes hxDriftA {
  0%,100% { transform: translate3d(0,0,0) scale(1); }
  50%     { transform: translate3d(6%, 8%, 0) scale(1.12); }
}
@keyframes hxDriftB {
  0%,100% { transform: translate3d(0,0,0) scale(1.05); }
  50%     { transform: translate3d(-7%, -6%, 0) scale(.92); }
}
@keyframes hxDriftC {
  0%,100% { transform: translate3d(0,0,0) scale(.95); }
  50%     { transform: translate3d(4%, -9%, 0) scale(1.1); }
}

/* ---------- perspective floor: grounds the scene in space ---------- */
.hx-floor {
  position: absolute; left: -20%; right: -20%; bottom: -6%;
  height: 46%; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(rgba(123,44,191,.13) 1px, transparent 1px),
    linear-gradient(90deg, rgba(123,44,191,.13) 1px, transparent 1px);
  background-size: 62px 62px;
  transform: rotateX(70deg);
  transform-origin: bottom center;
  /* Fades toward the horizon so it reads as distance, not wallpaper. */
  -webkit-mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.45) 38%, transparent 80%);
          mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.45) 38%, transparent 80%);
}

/* ---------- fine grain: stops the large gradients from banding ---------- */
.hx-grain {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  opacity: .5; mix-blend-mode: overlay;
}

/* ============================ HERO CONTENT ============================ */

/* Headline: brand gradient with a slow shine passing across it. */
.hero__title {
  background: linear-gradient(100deg, var(--ink) 0%, var(--ink) 34%, var(--brand) 50%, var(--ink) 66%, var(--ink) 100%);
  background-size: 260% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  animation: hxShine 9s linear infinite;
}
@keyframes hxShine { 0% { background-position: 150% 0; } 100% { background-position: -50% 0; } }
/* The sub-line is its own span and should stay solid, not inherit the sweep. */
.hero__title-sub {
  -webkit-text-fill-color: initial; color: var(--body);
  background: none;
}

/* ---------- staggered heading reveal ----------
   Word-level, not character-level. Splitting to characters looks busier but
   makes a screen reader announce the heading letter by letter, and this is the
   page's h1 — the one piece of text most worth keeping intact.

   The words carry no colour of their own: .hero__title paints the gradient and
   clips it to the text, and these spans inherit the transparent fill, so the
   shine sweeps across the whole headline exactly as it did before the split.
   ------------------------------------------------------------------------- */
/* Once the heading is split, the gradient has to move onto the words.

   `background-clip: text` clips to the text of the element that carries it,
   and an inline-block descendant is painted as its own box — so the words
   inherited `-webkit-text-fill-color: transparent` while inheriting no
   gradient to show through it, and the h1 rendered as nothing at all. Each
   word now carries the sweep itself, with a negative delay per word so the
   highlight still travels along the line instead of flashing on all five at
   once. */
.hero__title.is-staggered {
  background: none;
  -webkit-text-fill-color: initial;
  color: var(--ink);
  animation: none;
}
.hero__title.is-staggered .hx-word {
  background: linear-gradient(100deg, var(--ink) 0%, var(--ink) 34%, var(--brand) 50%, var(--ink) 66%, var(--ink) 100%);
  background-size: 260% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  display: inline-block;
  /* A keyframe animation, deliberately, not a transition flipped by a second
     class. A class toggle needs JS to land a frame later, and when that frame
     never comes — a throttled or non-compositing renderer — the heading stays
     at opacity 0 and the h1 is simply gone. An animation starts the moment the
     span exists and `forwards` holds the end state, so the worst case is that
     it plays instantly rather than that it never plays. */
  opacity: 0;
  animation:
    hxWordIn .62s var(--hx-ease) forwards,
    hxShine 9s linear infinite;
  animation-delay: calc(var(--w) * 68ms), calc(var(--w) * -700ms);
}
.hero__title.is-staggered .hero__title-sub {
  opacity: 0;
  animation: hxWordIn .55s var(--hx-ease) forwards;
  animation-delay: calc(var(--w) * 68ms);
}
@keyframes hxWordIn {
  from { opacity: 0; transform: translateY(.42em) rotate(1.4deg); }
  to   { opacity: 1; transform: none; }
}

/* A reveal animation starts from opacity 0, so anything that renders the page
   without ever running the animation timeline would print the h1 blank. Print
   is the realistic case; it costs two lines to rule out. */
@media print {
  .hero__title .hx-word,
  .hero__title .hero__title-sub {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
}


/* ---------- the phone plane, tilted into the stage ---------- */
.hero__visual {
  transform-style: preserve-3d;
  will-change: transform;
}
.hero__photo {
  transform: rotateY(-13deg) rotateX(6deg) rotateZ(-1.2deg);
  transform-origin: 60% 50%;
  border-radius: 22px;
  box-shadow:
    0 6px 16px rgba(20,8,43,.10),
    0 26px 50px rgba(20,8,43,.16),
    0 60px 120px rgba(90,24,154,.24);
  transition: transform 1.1s var(--hx-ease), box-shadow 1.1s var(--hx-ease);
}
/* A soft mirror of the plane, so it sits on the floor rather than floating. */
.hero__visual::after {
  content: ""; position: absolute;
  left: 6%; right: 6%; bottom: -7%; height: 90px;
  background: radial-gradient(ellipse at 50% 0%, rgba(90,24,154,.30), rgba(90,24,154,0) 72%);
  filter: blur(20px);
  z-index: -1;
  transform: translateZ(-60px);
}
/* Existing decorative blob, pushed behind the plane so it reads as depth. */
.hero__blob { transform: translateZ(-140px) scale(1.12); opacity: .85; }

/* ---------- float cards: glass, each on its own Z plane ---------- */
.float-card {
  background: var(--hx-glass);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
          backdrop-filter: blur(16px) saturate(150%);
  border: 1px solid rgba(255,255,255,.85);
  box-shadow: var(--hx-depth-2);
  will-change: transform;
}
/* The note was overlapping the avatars because .avatars is a flex child with
   no flex-shrink: once the card ran out of room the avatar row collapsed and the
   text ran straight over it. Pinning the row fixes it at every width. */
.avatars { flex: 0 0 auto; }
.float-card__note { min-width: 0; }

.float-card--stat { transform: translateZ(90px); }
.float-card--team { transform: translateZ(130px); }
@media (min-width: 981px) {
  /* Pulled out into the gutters so the cards frame the phones instead of
     covering the screenshots, which are the actual product. */
  .float-card--team { bottom: -2%; left: -16%; max-width: 268px; padding: 15px 18px; }
  .float-card--stat { top: 2%;  right: -8%; max-width: 226px; padding: 15px 18px; }
}

/* ---------- buttons: lift and sheen ---------- */
.btn--primary {
  position: relative; overflow: hidden;
  box-shadow: var(--shadow-brand);
  transition: transform .3s var(--hx-ease), box-shadow .3s var(--hx-ease);
}
.btn--primary::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 35%, rgba(255,255,255,.38) 50%, transparent 65%);
  transform: translateX(-120%);
  transition: transform .75s var(--hx-ease);
}
.btn--primary:hover { transform: translateY(-2px); box-shadow: 0 18px 40px rgba(123,44,191,.42); }
.btn--primary:hover::after { transform: translateX(120%); }
.btn--light {
  transition: transform .3s var(--hx-ease), box-shadow .3s var(--hx-ease);
}
.btn--light:hover { transform: translateY(-2px); box-shadow: var(--hx-depth-2); }

/* ======================= DEPTH ACROSS THE PAGE ======================= */

/* Cards lift toward the pointer. The rotation is kept small on purpose — a
   tighter perspective exaggerates it into a gimmick. */
[data-tilt] { transform-style: preserve-3d; will-change: transform; }

.service, .product, .post, .stat-card, .badge-card {
  transition: transform .4s var(--hx-ease), box-shadow .4s var(--hx-ease);
}
.service:hover, .post:hover, .stat-card:hover, .badge-card:hover {
  transform: translateY(-6px);
  box-shadow: var(--hx-depth-3);
}
/* .product sits inside a draggable carousel — lift it, but no tilt, so the
   pointer handler never fights the track transform. */
.product:hover { transform: translateY(-5px); box-shadow: var(--hx-depth-3); }

/* Section seams: a hairline of brand light instead of a hard edge. */
.section--paper, .section--dark { position: relative; }
.section--paper::before, .section--dark::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(157,78,221,.35), transparent);
}

/* ============================ RESPONSIVE ============================ */

@media (max-width: 980px) {
  /* No pointer to parallax from, and blur is the most expensive thing on a
     mid-range phone — so the orbs shrink and the plane flattens out. */
  .hero__photo { transform: none; box-shadow: var(--hx-depth-2); }
  .hx-orb { filter: blur(48px); }
  .hx-orb--c { display: none; }
  .hx-floor { display: none; }
  .float-card--stat, .float-card--team { transform: none; }
}
@media (max-width: 620px) {
  .hx-aurora { inset: -10% -20% 0; }
  .hx-grain { display: none; }
}

/* ============================ MOTION OPT-OUT ============================ */

@media (prefers-reduced-motion: reduce) {
  .hx-orb { animation: none; }
  .hero__title {
    animation: none; background: none;
    -webkit-text-fill-color: initial; color: var(--ink);
  }
  .hero__title .hx-word,
  .hero__title .hero__title-sub {
    opacity: 1; transform: none; transition: none;
  }
  .hero__photo,
  .float-card,
  .service, .product, .post, .stat-card, .badge-card,
  .btn--primary, .btn--light { transition: none; }
  .btn--primary::after { display: none; }
}

/* ---------- whole product card is the click target ----------
   .product already carried cursor:pointer, so the card advertised itself as
   clickable while only the small CTA actually worked.

   A stretched-link ::after was the first attempt. Its geometry came out exactly
   right (360x400 at 0,0) but it still lost to .product__veil at the card
   corners: the card stacks photo/veil/body inside an isolation:isolate context,
   and an overlay nested in the body could not be got reliably above the veil
   across it. Rather than escalate z-indexes, the click is delegated in
   hero3d.js — one handler on the carousel, which also survives product-cards.js
   replacing the track markup.

   The real CTA link is left alone, so keyboard and screen-reader users still
   get a genuine focusable link with real text. The delegation only adds the
   pointer convenience on top of it. */
/* The CTA reacts to the whole card, now that the whole card is the target. */
.product:hover .product__cta { background: var(--card-accent); }
/* Keyboard users get a ring on the card, not just on the small button. */
.product:focus-within { outline: 2px solid var(--brand-300); outline-offset: 3px; }

/* ---------- single article (blog-post.html) ---------- */
.bp-head { padding: 56px 0 26px; }
.bp-back {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: .875rem; font-weight: 700; color: var(--brand);
  margin-bottom: 22px;
}
.bp-back svg { transform: rotate(-135deg); }   /* the shared arrow, pointed back */
.bp-back:hover { text-decoration: underline; }
.bp-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  font-size: .8125rem; color: var(--muted); margin-bottom: 14px;
}
.bp-head h1 {
  font-size: clamp(1.75rem, 4vw, 2.75rem); line-height: 1.15;
  letter-spacing: -.02em; max-width: 22ch; margin-bottom: 14px;
}
.bp-lead {
  font-size: 1.0625rem; line-height: 1.7; color: var(--body); max-width: 62ch;
}
.bp-figure {
  margin: 12px 0 34px; border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: var(--hx-depth-2);
}
.bp-figure img { display: block; width: 100%; height: auto; }
/* A measure of about 68 characters — long enough to read, short enough to
   track back to the next line without losing your place. */
.bp-body { max-width: 68ch; margin: 0 auto 72px; }
.bp-body p { font-size: 1.0625rem; line-height: 1.8; color: var(--body); margin-bottom: 1.25em; }
.bp-missing { padding: 90px 0; text-align: center; }
.bp-missing h1 { font-size: 1.75rem; margin-bottom: 10px; }
.bp-missing p { color: var(--muted); margin-bottom: 22px; }

/* ---------- blog index (blog.html) ---------- */
.blog-hero { padding-bottom: 0; }
.blog-hero h1 { font-size: clamp(1.9rem, 4vw, 2.9rem); letter-spacing: -.02em; }
.blog-list { display: grid; gap: 22px; }
.blog-list__note { color: var(--muted); text-align: center; padding: 40px 0; }

/* Picture beside the words on a wide screen, stacked on a narrow one. The
   140px column keeps every row the same height regardless of summary length. */
.bi {
  display: grid; grid-template-columns: 300px 1fr; gap: 26px;
  align-items: center;
  background: var(--white); border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: var(--hx-depth-1);
  transition: transform .35s var(--hx-ease), box-shadow .35s var(--hx-ease);
}
.bi:hover { transform: translateY(-4px); box-shadow: var(--hx-depth-3); }
.bi__media { display: block; height: 100%; min-height: 200px; overflow: hidden; }
.bi__media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .5s var(--hx-ease);
}
.bi:hover .bi__media img { transform: scale(1.04); }
.bi__body { padding: 26px 28px 26px 0; }
.bi__meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 9px;
  font-size: .8125rem; color: var(--muted); margin-bottom: 10px;
}
.bi__body h2 { font-size: 1.3rem; line-height: 1.3; letter-spacing: -.01em; margin-bottom: 10px; }
.bi__body h2 a { color: var(--ink); }
.bi__body h2 a:hover { color: var(--brand); }
.bi__excerpt {
  font-size: .9375rem; line-height: 1.7; color: var(--body);
  margin-bottom: 14px; max-width: 68ch;
}
/* An article with no picture should not leave a 300px hole. */
.bi:not(:has(.bi__media)) { grid-template-columns: 1fr; }
.bi:not(:has(.bi__media)) .bi__body { padding: 26px 28px; }

@media (max-width: 820px) {
  .bi { grid-template-columns: 1fr; gap: 0; }
  .bi__media { min-height: 200px; max-height: 240px; }
  .bi__body { padding: 22px 22px 24px; }
}
