/* Pack and Kin Studio — the motion layer for the public pages.
   Loaded after public.css / public-home.css by any page that wants life.

   THREE RULES THIS FILE IS BUILT ON
   ---------------------------------
   1. CONTENT IS VISIBLE BY DEFAULT. Nothing here starts at opacity:0 outside an
      @supports block that has already proved the browser can finish the
      animation. A reveal that never fires must leave the page readable, not
      blank — Firefox has no scroll-driven animations, and a page that hides its
      own copy waiting for a timeline that will never resolve is a blank page for
      a third of the web.
   2. NO JAVASCRIPT. Scroll reveals use animation-timeline: view(), not an
      IntersectionObserver, because config/test_csp_nonce_coverage.py holds
      inline script at a hard zero and every scripted alternative fails silently.
   3. prefers-reduced-motion IS HONOURED AT THE BOTTOM, for everything, in one
      place. Motion sickness and vestibular disorders are not an edge case, and
      an ambient loop that cannot be switched off is the worst offender.

   Durations are deliberately unfashionable-slow on ambient loops (12-30s) and
   fast on interaction (120-260ms). Ambient motion should be noticed only if you
   look for it; interaction motion should feel instant. */

/* ===========================================================================
   KEYFRAMES
   =========================================================================== */

@keyframes pk-rise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
@keyframes pk-rise-sm {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: none; }
}
@keyframes pk-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes pk-slide-left {
  from { opacity: 0; transform: translateX(-22px); }
  to   { opacity: 1; transform: none; }
}
@keyframes pk-pop {
  0%   { opacity: 0; transform: scale(.82); }
  62%  { opacity: 1; transform: scale(1.045); }
  100% { opacity: 1; transform: scale(1); }
}

/* Ambient: the hero's warm light shifting, very slowly. */
@keyframes pk-aurora {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(2.5%, -2%, 0) scale(1.08); }
}

/* Ambient: a parcel bobbing. */
@keyframes pk-float {
  0%, 100% { transform: translateY(0) rotate(-3deg); }
  50%      { transform: translateY(-14px) rotate(3deg); }
}

/* A parcel running the dotted route line. */
@keyframes pk-route-draw {
  from { stroke-dashoffset: 340; }
  to   { stroke-dashoffset: 0; }
}
@keyframes pk-route-run {
  from { offset-distance: 0%; }
  to   { offset-distance: 100%; }
}

/* The soft ring that breathes out of the winning rate. */
@keyframes pk-ping {
  0%   { box-shadow: 0 0 0 0 rgba(31, 122, 76, .30); }
  70%  { box-shadow: 0 0 0 14px rgba(31, 122, 76, 0); }
  100% { box-shadow: 0 0 0 0 rgba(31, 122, 76, 0); }
}

/* A light sweeping across the vignette, like a screen refreshing. */
@keyframes pk-sheen {
  0%   { transform: translateX(-130%) skewX(-18deg); }
  100% { transform: translateX(240%) skewX(-18deg); }
}

/* Status dot. */
@keyframes pk-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: .35; }
}

/* The caret in the "typed" address field. */
@keyframes pk-caret {
  0%, 45%   { opacity: 1; }
  55%, 100% { opacity: 0; }
}

/* ===========================================================================
   ON-LOAD CHOREOGRAPHY (hero only)
   The hero is above the fold, so it animates on load rather than on scroll.
   Every element is visible by default; the animation only replays the arrival.
   =========================================================================== */

.pk-hero__flag  { animation: pk-rise-sm .5s .05s both ease-out; }
.pk-hero h1     { animation: pk-rise .62s .12s both cubic-bezier(.22,.68,.32,1); }
.pk-hero__lede  { animation: pk-rise .62s .22s both cubic-bezier(.22,.68,.32,1); }
.pk-hero .pk-btnrow { animation: pk-rise .62s .32s both cubic-bezier(.22,.68,.32,1); }
.pk-trust       { animation: pk-rise .62s .42s both cubic-bezier(.22,.68,.32,1); }
.pk-hero__art   { animation: pk-pop .8s .3s both cubic-bezier(.22,.72,.3,1); }

/* The hero's warm light, drifting. Sits behind everything, never interactive. */
.pk-hero { position: relative; overflow: hidden; isolation: isolate; }
.pk-hero__glow {
  position: absolute; inset: -22% -12%; z-index: -1; pointer-events: none;
  background:
    radial-gradient(ellipse 46% 52% at 18% 12%, rgba(254, 167, 36, .30) 0, transparent 68%),
    radial-gradient(ellipse 40% 46% at 86% 4%,  rgba(225, 50, 43, .16) 0, transparent 66%),
    radial-gradient(ellipse 44% 40% at 62% 96%, rgba(42, 196, 230, .16) 0, transparent 70%);
  animation: pk-aurora 26s ease-in-out infinite;
  will-change: transform;
}

/* Decorative parcels drifting in the hero. aria-hidden in the markup. */
.pk-drift { position: absolute; z-index: -1; pointer-events: none; opacity: .5; color: var(--pk-gold); }
.pk-drift svg { display: block; }
.pk-drift--a { top: 14%;  left: 4%;   width: 44px; animation: pk-float 11s ease-in-out infinite; }
.pk-drift--b { top: 64%;  left: 46%;  width: 30px; color: var(--pk-red);  opacity: .32; animation: pk-float 14s ease-in-out -3s infinite; }
.pk-drift--c { top: 30%;  right: 2%;  width: 36px; color: var(--pk-sky);  opacity: .38; animation: pk-float 17s ease-in-out -7s infinite; }

/* ===========================================================================
   SCROLL REVEALS
   Progressive enhancement, gated on @supports. A browser without scroll-driven
   animations gets the finished state and no animation at all — which is the
   whole point: the copy is never hidden behind a feature test.
   =========================================================================== */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .pk-reveal {
      animation: pk-rise .8s linear both;
      animation-timeline: view();
      animation-range: entry 2% cover 26%;
    }
    .pk-reveal--fade {
      animation-name: pk-fade;
    }
    .pk-reveal--left {
      animation-name: pk-slide-left;
    }
    /* Stagger children of a grid as the grid scrolls in. */
    .pk-stagger > * {
      animation: pk-rise .8s linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 24%;
    }
    .pk-stagger > :nth-child(2) { animation-range: entry 0% cover 28%; }
    .pk-stagger > :nth-child(3) { animation-range: entry 0% cover 32%; }
    .pk-stagger > :nth-child(4) { animation-range: entry 0% cover 36%; }
    .pk-stagger > :nth-child(5) { animation-range: entry 0% cover 40%; }
    .pk-stagger > :nth-child(6) { animation-range: entry 0% cover 44%; }
  }
}

/* ===========================================================================
   VIGNETTE LIFE
   The UI vignettes are the page's proof, so they behave like software rather
   than like pictures of software: rows land, the winner is chosen, a sheen
   passes. All of it loops slowly enough to read as "live", not as a carousel.
   =========================================================================== */

.pk-ui { position: relative; }

/* The sheen. A sibling overlay so it never repaints the card's contents. */
.pk-ui--sheen::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, transparent 32%, rgba(255,255,255,.62) 50%, transparent 68%);
  transform: translateX(-130%) skewX(-18deg);
  animation: pk-sheen 7.5s ease-in-out 1.6s infinite;
}

/* Rate rows land one after another, then the winner is marked. */
.pk-rate { animation: pk-rise-sm .5s both ease-out; }
.pk-rate:nth-of-type(1) { animation-delay: .78s; }
.pk-rate:nth-of-type(2) { animation-delay: .92s; }
.pk-rate:nth-of-type(3) { animation-delay: 1.06s; }
.pk-rate--best { animation: pk-pop .55s 1.2s both cubic-bezier(.22,.72,.3,1), pk-ping 3.4s 1.9s ease-out infinite; }
.pk-rate__flag { animation: pk-fade .4s 1.62s both; }

/* The destination field, typed in. A caret, not a typewriter: a width
   animation on real text reflows and re-wraps, which looks broken at 320px. */
.pk-caret {
  display: inline-block; width: 1.5px; height: 1em; margin-left: 1px;
  background: var(--pk-navy); vertical-align: -.14em;
  animation: pk-caret 1.15s step-end infinite;
}

/* Sale lines in the register vignette. */
.pk-line { animation: pk-slide-left .48s both ease-out; }
.pk-line:nth-of-type(1) { animation-delay: .10s; }
.pk-line:nth-of-type(2) { animation-delay: .22s; }
.pk-line:nth-of-type(3) { animation-delay: .34s; }
.pk-line--sub   { animation-delay: .46s; }
.pk-line--total { animation: pk-pop .5s .58s both cubic-bezier(.22,.72,.3,1); }

/* Mailbox wall fills in. */
.pk-box { animation: pk-pop .42s both cubic-bezier(.22,.72,.3,1); }
.pk-box:nth-child(1) { animation-delay: .06s; }
.pk-box:nth-child(2) { animation-delay: .12s; }
.pk-box:nth-child(3) { animation-delay: .18s; }
.pk-box:nth-child(4) { animation-delay: .24s; }
.pk-box:nth-child(5) { animation-delay: .30s; }
.pk-box:nth-child(6) { animation-delay: .36s; }
.pk-box:nth-child(7) { animation-delay: .42s; }
.pk-box:nth-child(8) { animation-delay: .48s; }
.pk-box--due { position: relative; }
.pk-box--due::after {
  content: ""; position: absolute; top: 7px; right: 7px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--pk-red);
  animation: pk-blink 2s ease-in-out infinite;
}

/* A live-looking status pip for the vignette title bars. */
.pk-ui__live { display: inline-flex; align-items: center; gap: 6px; }
.pk-ui__live::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: #6EE7A8; animation: pk-blink 2.2s ease-in-out infinite;
}

/* ===========================================================================
   THE ROUTE GRAPHIC
   An SVG dotted line that draws itself, with a parcel running along it. Used as
   the divider between the story rows. offset-path is well supported; where it
   is not, the parcel simply sits at the start and the line still draws.
   =========================================================================== */

.pk-route { display: block; width: 100%; height: 58px; overflow: visible; color: var(--pk-line); }
.pk-route__line {
  fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round;
  stroke-dasharray: 3 9;
}
.pk-route__pin { fill: var(--pk-gold); }
.pk-route__parcel { fill: var(--pk-red); }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .pk-route__line {
      stroke-dasharray: 340;
      animation: pk-route-draw 1s linear both;
      animation-timeline: view();
      animation-range: entry 10% cover 42%;
    }
    .pk-route__parcel {
      offset-path: path("M 8 40 C 200 40, 240 8, 420 8 S 700 40, 892 40");
      offset-rotate: 0deg;
      animation: pk-route-run 1s linear both;
      animation-timeline: view();
      animation-range: entry 10% cover 42%;
    }
  }
}

/* ===========================================================================
   INTERACTION
   =========================================================================== */

.pk-card--link .pk-card__go,
.pk-doors .card .pk-card__go { transition: transform .18s ease; }
.pk-card--link:hover .pk-card__go,
.pk-doors .card:hover .pk-card__go { transform: translateX(4px); }

.pk-card--link .pk-card__icon,
.pk-doors .card .pk-card__icon { transition: transform .22s cubic-bezier(.22,.72,.3,1); }
.pk-card--link:hover .pk-card__icon,
.pk-doors .card:hover .pk-card__icon { transform: translateY(-2px) rotate(-4deg) scale(1.06); }

/* Button shine. */
.pk-btn { position: relative; overflow: hidden; }
.pk-btn::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 36%, rgba(255,255,255,.34) 50%, transparent 64%);
  transform: translateX(-120%) skewX(-16deg);
  transition: transform .5s ease;
}
.pk-btn:hover::after { transform: translateX(180%) skewX(-16deg); }

.pk-carrier { transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease; }
.pk-carrier:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: var(--pk-gold); }

.pk-faq details[open] .pk-faq__a { animation: pk-rise-sm .28s both ease-out; }

/* ===========================================================================
   LOTTIE MOUNTS
   A .pk-lottie holds a CSS/SVG fallback as its child. The loader
   (static/brand/public-motion.js) replaces that child ONLY once the player and
   the JSON have both loaded, so a blocked script, a 404 on the JSON or a user
   on a slow line keeps the fallback rather than an empty box. The mount carries
   its own aspect-ratio so nothing reflows when the swap happens.
   =========================================================================== */

.pk-lottie { position: relative; display: block; width: 100%; }
.pk-lottie > svg,
.pk-lottie > .pk-lottie__fallback { display: block; width: 100%; height: auto; }
.pk-lottie[data-lottie-loaded="1"] > .pk-lottie__fallback { display: none; }

/* ===========================================================================
   REDUCED MOTION — one switch, everything off.
   =========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .pk-hero__flag, .pk-hero h1, .pk-hero__lede, .pk-hero .pk-btnrow,
  .pk-trust, .pk-hero__art, .pk-hero__glow, .pk-drift,
  .pk-rate, .pk-rate--best, .pk-rate__flag,
  .pk-line, .pk-line--total, .pk-box, .pk-box--due::after,
  .pk-ui__live::before, .pk-caret, .pk-reveal, .pk-stagger > *,
  .pk-route__line, .pk-route__parcel, .pk-faq details[open] .pk-faq__a {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    stroke-dashoffset: 0 !important;
    box-shadow: none;
  }
  .pk-ui--sheen::after { display: none; }
  .pk-btn::after { display: none; }
  .pk-drift { display: none; }
}

/* ===========================================================================
   THE THREE STEP ICONS (the Lottie fallbacks)
   These are the artwork that ships. They are animated here rather than left as
   flat placeholders precisely because they may never be replaced — a fallback
   nobody notices is a fallback that worked.

   SVG needs `transform-box: fill-box` for a percentage transform-origin to mean
   anything; without it the origin is the SVG viewport corner and the shape flies
   off screen. That is the one non-obvious line in this block.
   =========================================================================== */

.pk-step { --pk-step-ease: cubic-bezier(.22,.72,.3,1); }
.pk-step * { transform-box: fill-box; }

/* 1 — the shop opens: the awning unfurls, then the sign lights. */
@keyframes pk-awning {
  0%, 8%    { transform: scaleY(.06); }
  26%, 82%  { transform: scaleY(1); }
  100%      { transform: scaleY(.06); }
}
@keyframes pk-openlight {
  0%, 24%  { fill: #C9C2B4; }
  32%, 84% { fill: #1F7A4C; }
  100%     { fill: #C9C2B4; }
}
.pk-step__awning {
  transform-origin: 50% 0%;
  animation: pk-awning 7s var(--pk-step-ease) infinite;
}
.pk-step__sign { animation: pk-openlight 7s linear infinite; }

/* 2 — funds land in the wallet. Two coins, offset, on a loop that rests. */
@keyframes pk-drop {
  0%        { transform: translateY(-14px); opacity: 0; }
  16%       { opacity: 1; }
  38%, 100% { transform: translateY(34px); opacity: 0; }
}
@keyframes pk-wallet-nudge {
  0%, 34%, 100% { transform: translateY(0); }
  42%           { transform: translateY(2.5px); }
}
.pk-step__coin { animation: pk-drop 3.6s var(--pk-step-ease) infinite; }
.pk-step__coin--2 { animation-delay: .5s; }
.pk-step__wallet { animation: pk-wallet-nudge 3.6s var(--pk-step-ease) infinite; }

/* 3 — the receipt prints. Clipped by the printer body above it, so it reads as
   feeding out rather than sliding around. */
@keyframes pk-print {
  0%, 6%    { transform: translateY(-42px); }
  46%, 88%  { transform: translateY(0); }
  100%      { transform: translateY(-42px); }
}
.pk-step__paper {
  animation: pk-print 6s var(--pk-step-ease) infinite;
  clip-path: inset(-2px -20px -20px -20px);
}

@media (prefers-reduced-motion: reduce) {
  .pk-step__awning, .pk-step__sign, .pk-step__coin,
  .pk-step__wallet, .pk-step__paper {
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }
  .pk-step__sign { fill: #1F7A4C; }
}

/* ===========================================================================
   SMALL SCREENS
   The drifting parcels are decorative and positioned as a percentage of the
   hero, so as the hero narrows they land ON the headline rather than beside it
   — measured at 375px, one sat directly over "Free forever." Ambient decoration
   that collides with the one line the page is selling is worse than no
   decoration, and there is no arrangement of three floating boxes that is safe
   at every width. Drop them instead; the aurora still carries the warmth.
   =========================================================================== */

@media (max-width: 860px) {
  .pk-drift { display: none; }
}
