/* Registered so the hero's one-shot intro can animate them. Everything
   below the hero writes these per frame from scroll instead.
   inherits MUST stay true: the mist, trigger and tumbling body are SVG
   children of the element the value is set on, and read it by inheritance. */
@property --fx-p { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --fx-d { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --fx-h { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --fx-s { syntax: '<number>'; inherits: true; initial-value: 0; }

/* ==========================================================================
   Pal's Landing Blocks — scroll critters + the treatment sequence
   Every critter's whole life is bolted to scroll position: it crawls in, the
   gloved hand arrives, sprays, and the critter drops. Scroll back up and it
   all reverses. Nothing animates on its own except the leg tick.
   Decorative: aria-hidden, pointer-events:none, behind content.
   ========================================================================== */

.pals-sec {
  --fx-ink: rgba(7, 8, 10, .30);
  --fx-web: rgba(7, 8, 10, .13);
  --fx-thread: rgba(7, 8, 10, .22);
  --fx-glove: #C7F268;
  --fx-glove-line: #07080A;
  --fx-can: #1B1E22;
  --fx-mist: rgba(7, 8, 10, .30);
  --fx-ease: cubic-bezier(0.23, 1, 0.32, 1);
}
/* Dark sections: critters go lime. */
.pals-hero, .pals-social, .pals-footer {
  --fx-ink: rgba(199, 242, 104, .40);
  --fx-web: rgba(199, 242, 104, .30);
  --fx-thread: rgba(199, 242, 104, .42);
  --fx-mist: rgba(199, 242, 104, .50);
}
/* Lime band: ink critters, cream glove so it doesn't vanish. */
.pals-ctaband {
  --fx-ink: rgba(7, 8, 10, .34);
  --fx-web: rgba(7, 8, 10, .16);
  --fx-thread: rgba(7, 8, 10, .28);
  --fx-glove: #F4EFEC;
  --fx-mist: rgba(7, 8, 10, .34);
}

.pals-fx {
  position: absolute;
  pointer-events: none;
  z-index: 0;
  will-change: transform;
}

/* Critters and the hand travel past the section edge by design — clip there
   so the page body never gains a horizontal scrollbar. `clip` over `hidden`
   because `hidden` would silently make the section a scroll container. */
.pals-sec[data-pals-fx] { overflow-x: hidden; }
@supports (overflow-x: clip) {
  .pals-sec[data-pals-fx] { overflow-x: clip; }
}
/* Content always sits above the critters. */
.pals-sec > .pals-wrap { position: relative; z-index: 1; }

/* The hero paints a dark gradient through ::after, which is the last child
   and so was covering the critters. Order it explicitly:
   overlay (0) < critters (1) < copy (2). */
.pals-hero::after { z-index: 0; }
.pals-hero > .pals-fx { z-index: 1; }
.pals-hero > .pals-wrap { z-index: 2; }

/* ---------- Webs: drawn once on entry ---------- */
.pals-fx-web { width: clamp(110px, 15vw, 230px); height: auto; }
.pals-fx-web--tr { top: 0; right: 0; }
.pals-fx-web--tl { top: 0; left: 0; transform: scaleX(-1); }
.pals-fx-web--br { bottom: 0; right: 0; transform: scaleY(-1); }
.pals-fx-web--bl { bottom: 0; left: 0; transform: scale(-1, -1); }

.pals-fx-web path {
  fill: none;
  stroke: var(--fx-web);
  stroke-width: 1.15;
  stroke-linecap: round;
  stroke-dasharray: var(--len, 400);
  stroke-dashoffset: var(--len, 400);
  transition: stroke-dashoffset 900ms var(--fx-ease);
}
.pals-fx-in .pals-fx-web path { stroke-dashoffset: 0; }
.pals-fx-in .pals-fx-web .ring { transition-delay: 260ms; }

/* ---------- Photographic critters ----------
   These are real photos, so they no longer recolour per section. A soft
   drop shadow is what stops them looking pasted on. */
.pals-fx-spider img,
.pals-fx-crawler img {
  display: block;
  width: 100%;
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
  filter: drop-shadow(0 3px 6px rgba(7, 8, 10, .45));
}
/* On the near-black hero a dark shadow is invisible — lift them instead. */
.pals-hero .pals-fx-spider img,
.pals-hero .pals-fx-crawler img,
.pals-footer .pals-fx-crawler img,
.pals-social .pals-fx-crawler img {
  filter: drop-shadow(0 0 10px rgba(0, 0, 0, .7)) brightness(1.06);
}

/* ---------- Hero: spiders hanging on threads ----------
   --fx-p  pays out the thread     --fx-d  the thread snaps and it drops    */
.pals-fx-spider {
  top: 0;
  right: var(--x, 6%);
  display: flex; flex-direction: column; align-items: center;
  transform: translate3d(0,
    calc(var(--fx-p, 0) * var(--drop, 240px) - var(--drop, 240px)
         + var(--fx-d, 0) * var(--fall, 420px)), 0);
}
.pals-fx-spider__thread {
  width: 1px;
  height: var(--drop, 240px);
  background: linear-gradient(to bottom, transparent, var(--fx-thread));
  /* snaps well before the spider has finished falling */
  opacity: calc(1 - var(--fx-d, 0) * 5);
}
.pals-fx-spider img {
  width: calc(clamp(58px, 7vw, 104px) * var(--s, 1));
  margin-top: -2px;
  /* only the body tumbles — the thread stays vertical */
  transform: rotate(calc(var(--fx-d, 0) * var(--tumble, 200deg)));
  opacity: calc(1 - var(--fx-d, 0) * 1.35);
}

/* ---------- Ground crawlers ----------
   --fx-p  crawl        --fx-d  knocked over / fled
   Travel first, then the drop, then the flip.                             */
.pals-fx-crawler {
  left: 0;
  width: var(--w, 92px);
  height: auto;
  transform:
    translate3d(
      calc(var(--start, -70px) + var(--fx-p, 0) * var(--travel, 100vw)
           + var(--fx-d, 0) * var(--flee, 0px)),
      calc(var(--fx-d, 0) * 10px),
      0)
    scaleX(var(--dir, 1))
    rotate(calc(var(--fx-d, 0) * var(--flip, 0deg)));
  opacity: calc(1 - var(--fx-d, 0) * var(--fade, .6));
}
.pals-fx-crawler--top { top: 18px; }
.pals-fx-crawler--bottom { bottom: 18px; }
.pals-fx-crawler--mid { top: 42%; }

/* A photo has no separable legs to tick, so the whole body bobs instead.
   It sits on the <img>, leaving the travel transform on the parent alone. */
.pals-fx-live .pals-fx-crawler[data-alive="1"] img {
  animation: pals-bob 260ms ease-in-out infinite alternate;
  transform-origin: 50% 80%;
}
@keyframes pals-bob {
  from { transform: translateY(0) rotate(-1.1deg) }
  to   { transform: translateY(1.6px) rotate(1.1deg) }
}

/* ---------- The gloved hand ----------
   --fx-h  slides in     --fx-s  squeezes the trigger and mists            */
.pals-fx-hand {
  width: clamp(130px, 16vw, 240px);
  height: auto;
  transform:
    translate3d(calc((1 - var(--fx-h, 0)) * var(--enter, 130%)), 0, 0)
    scaleX(var(--hdir, 1))
    rotate(calc(var(--fx-s, 0) * -5deg));
}
.pals-fx-hand--right { right: 0; }
.pals-fx-hand--left  { left: 0; }

.pals-fx-hand svg { width: 100%; height: auto; display: block; }
.pals-fx-hand .glove  { fill: var(--fx-glove); stroke: var(--fx-glove-line); stroke-width: 3; stroke-linejoin: round; }
.pals-fx-hand .cuff   { fill: var(--fx-glove-line); }
.pals-fx-hand .can    { fill: var(--fx-can); }
.pals-fx-hand .can-hi { fill: var(--fx-glove); }
.pals-fx-hand .line   { fill: none; stroke: var(--fx-glove-line); stroke-width: 2.4; stroke-linecap: round; }
.pals-fx-hand .badge-ring { fill: none; stroke: var(--fx-glove-line); stroke-width: 2.6; }

/* Trigger squeezes as the spray fires. */
.pals-fx-hand .trigger {
  transform: translateX(calc(var(--fx-s, 0) * 2.5px));
  transform-box: fill-box;
}

/* Mist: three puffs staggered across the spray, each blooming and thinning. */
.pals-fx-hand .mist > * {
  fill: var(--fx-mist);
  transform-box: fill-box;
  transform-origin: 100% 50%;
}
.pals-fx-hand .mist .p1 {
  opacity: calc(min(var(--fx-s, 0) * 4, 1) * (1 - var(--fx-s, 0)) * 1.6);
  transform: scale(calc(.3 + var(--fx-s, 0) * 1.5));
}
.pals-fx-hand .mist .p2 {
  opacity: calc(min(max(var(--fx-s, 0) - .12, 0) * 4, 1) * (1 - var(--fx-s, 0)) * 1.5);
  transform: scale(calc(.2 + var(--fx-s, 0) * 2));
}
.pals-fx-hand .mist .p3 {
  opacity: calc(min(max(var(--fx-s, 0) - .26, 0) * 4, 1) * (1 - var(--fx-s, 0)) * 1.3);
  transform: scale(calc(.1 + var(--fx-s, 0) * 2.6));
}

/* ---------- Never over the form ---------- */
.pals-quote .pals-fx { display: none; }

/* ---------- Reduced motion: show the scene, don't move it ---------- */
@media (prefers-reduced-motion: reduce) {
  .pals-hero.pals-fx-play .pals-fx-spider,
  .pals-hero.pals-fx-play .pals-fx-hand,
  .pals-hero.pals-fx-play .pals-fx-spray { animation: none; }
  .pals-fx-spray { display: none; }
  .pals-fx-spider { --fx-p: 1; --fx-d: 0; }
  .pals-fx-crawler { transform: scaleX(var(--dir, 1)); left: 10%; opacity: 1; }
  .pals-fx-hand { display: none; }
  .pals-fx-spider { transform: none; }
  .pals-fx-spider__thread { height: 70px; }
  .pals-fx-live .pals-fx-crawler img { animation: none; }
  .pals-fx-web path { transition: opacity 300ms ease; stroke-dashoffset: 0; opacity: 0; }
  .pals-fx-in .pals-fx-web path { opacity: 1; }
}


/* ---------- Hero sequence ----------
   The hero sits at the top of the page with almost no scroll runway, so it
   runs on its own clock instead of on scroll. One cycle is 7.6s: roughly
   3.6s of action, then 4s of stillness before it comes round again. The
   rest gap is the point — a hero that never stops moving competes with the
   headline for the whole visit. It also pauses whenever the hero scrolls
   out of view, so it costs nothing while someone reads further down.       */

@keyframes pals-hero-spider {
  0%     { --fx-p: 0; --fx-d: 0; animation-timing-function: linear }
  4%     { --fx-p: 0; --fx-d: 0; animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1) }
  18%    { --fx-p: 1; --fx-d: 0; animation-timing-function: linear }
  30%    { --fx-p: 1; --fx-d: 0; animation-timing-function: cubic-bezier(0.55, 0, 0.85, 0.35) }
  43%    { --fx-p: 1; --fx-d: 1; animation-timing-function: linear }
  46.9%  { --fx-p: 1; --fx-d: 1 }
  47%    { --fx-p: 0; --fx-d: 1 }   /* retracted while invisible */
  50%    { --fx-p: 0; --fx-d: 0 }   /* reset, ready to go again */
  100%   { --fx-p: 0; --fx-d: 0 }
}

@keyframes pals-hero-hand {
  0%    { --fx-h: 0; --fx-s: 0; animation-timing-function: linear }
  17%   { --fx-h: 0; --fx-s: 0; animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1) }
  26%   { --fx-h: 1; --fx-s: 0; animation-timing-function: ease-out }
  36%   { --fx-h: 1; --fx-s: 1; animation-timing-function: linear }
  43%   { --fx-h: 1; --fx-s: 1; animation-timing-function: cubic-bezier(0.55, 0, 0.85, 0.35) }
  52%   { --fx-h: 0; --fx-s: 1 }
  52.1% { --fx-h: 0; --fx-s: 0 }
  100%  { --fx-h: 0; --fx-s: 0 }
}

.pals-hero.pals-fx-play .pals-fx-spider {
  animation: pals-hero-spider 7600ms infinite both;
  animation-delay: calc(var(--i, 0) * 130ms);
}
.pals-hero.pals-fx-play .pals-fx-hand,
.pals-hero.pals-fx-play .pals-fx-spray {
  animation: pals-hero-hand 7600ms infinite both;
}
/* Paused while the hero is off screen — no work for a page nobody is on. */
.pals-hero.pals-fx-play:not(.pals-fx-live) .pals-fx-spider,
.pals-hero.pals-fx-play:not(.pals-fx-live) .pals-fx-hand,
.pals-hero.pals-fx-play:not(.pals-fx-live) .pals-fx-spray { animation-play-state: paused; }

/* hero_loop="no" — run it once and leave it. */
.pals-hero.pals-fx-once .pals-fx-spider,
.pals-hero.pals-fx-once .pals-fx-hand,
.pals-hero.pals-fx-once .pals-fx-spray { animation-iteration-count: 1; }

/* ---------- The spray fan ----------
   The compact mist inside the sprayer artwork can't reach three spiders
   spread across 200px and hanging at three different heights, and an SVG
   can't paint outside its own box. So the hero gets a real cone, sized in
   JS to the cluster it has to cover.                                       */
.pals-fx-spray {
  right: var(--sx, 180px);
  width: var(--sw, 340px);
  height: var(--sh, 320px);
  /* Density runs ALONG the cone, not out from a point — a radial anchored at
     the nozzle fades out exactly where the fan is widest. */
  background: linear-gradient(to left,
              var(--fx-mist) 0%, var(--fx-mist) 16%, transparent 86%);
  clip-path: polygon(100% 45%, 100% 55%, 0% 96%, 0% 4%);
  transform-origin: 100% 50%;
  transform: scale(calc(.34 + min(var(--fx-s, 0) * 2.4, 1) * .78));
  opacity: calc(min(var(--fx-s, 0) * 5, 1) * (1 - var(--fx-s, 0)) * 1.05);
  filter: blur(18px);
}

/* No @property support: the intro can't animate, so just hang the spiders
   in place and drop the sprayer rather than showing an empty corner. */
.pals-hero.pals-fx-static .pals-fx-spider { --fx-p: 1; --fx-d: 0; }
.pals-hero.pals-fx-static .pals-fx-hand,
.pals-hero.pals-fx-static .pals-fx-spray { display: none; }

/* Hero swarm thins out as the headline column grows into the right side. */
@media (max-width: 1180px) { .pals-fx-spider[data-n="2"] { display: none; } }
@media (max-width: 1000px) { .pals-fx-spider[data-n="1"] { display: none; } }

/* ---------- Phones: same sequence, scaled down ---------- */
@media (max-width: 700px) {
  .pals-fx-crawler { width: calc(var(--w, 92px) * .62); }
  .pals-fx-hand { width: clamp(104px, 32vw, 140px); }
  /* No clear column beside a phone headline — one spider hangs in the empty
     top-right corner and the sprayer sits this one out. */
  .pals-hero .pals-fx-hand, .pals-fx-spray { display: none; }
  .pals-fx-spider img { width: calc(52px * var(--s, 1)); }
  .pals-fx-web { width: clamp(90px, 26vw, 130px); }
}
