/* ============================================================================
   approved/015 — the P loader
   ----------------------------------------------------------------------------
   Two surfaces, one technique: WelcomePage (the full 4s sequence, then breathe
   forever) and BrandSplash (breathe only, from frame one).

   THE TECHNIQUE, and why it is the whole point:
   The light IS the mark's own pixels. Every layer is the SAME PNG; the lit
   layers are brightened copies revealed through a moving CSS gradient mask. So
   the mosaic tiles light individually and exactly, we never enumerate them, and
   a future re-export of the mark carries the animation unchanged. Nothing here
   redraws, warps, or approximates the letterform — it cannot, because there is
   only one letterform in the file.

   MARKUP CONTRACT — everything is scoped under `.pl-mark`, so this file cannot
   leak. The cost of that safety: the class must be present or NOTHING applies.

     <div class="pl-mark pl-welcome pl-run" style="--pl-w:118px">
       <div class="pl-dark"></div>
       <div class="pl-lit"></div>
       <div class="pl-lit2"></div>
       <div class="pl-front"></div>
       <div class="pl-bub"></div>
     </div>

     <div class="pl-mark pl-loading pl-run" style="--pl-w:172px">
       <div class="pl-dark"></div>
       <div class="pl-lit2"></div>
     </div>

   Loading needs only two layers because it has no wave — see `.pl-loading`.
   Layer ORDER is load-bearing: dark, lit, lit2, front, bub. They stack, and a
   reorder changes which brightness wins.

   THE ASSET IS NOT p-glow.png. It is `assets/brand/piing-P.png` — the trimmed
   mark from approved/012. See SPEC.md; feeding the old padded asset to these
   sizes draws a mark 37% too small inside an opaque halo.
   ========================================================================== */

.pl-mark {
  position: relative;
  width: var(--pl-w);
  /* the trimmed asset is 437x512. NOT 1/1 — the shipped <img> forces a square
     box, which is exactly what made a "172px" mark draw a 109px letterform. */
  height: calc(var(--pl-w) * 1.1716);

  /* --pl-g is 1% of the mark's width. EVERY glow radius below is expressed in
     it, never in raw px. A fixed-px halo is scale-coupled the wrong way round:
     a 12px resting glow is 16% of a 75px mark but only 7% of a 172px one — so
     the smaller the mark, the hazier it reads. The factors are normalised to a
     109px mark. Do not substitute px values here: the mark ships at 118px on
     welcome and 172px on loading, and a fixed radius cannot serve both. */
  --pl-g: calc(var(--pl-w) / 100);

  /* SPEC 015 "fix that one URL" → DESIGN-1 JOB 7.1 (015 r2.6): the art is served by name from public/brand — the same
     URL Wordmark draws and index.html preloads, so the layers share one download. --pl-art stays the PNG master for
     any reader; the use site below writes the two declarations (boot-splash.css:26), WebP by type over the PNG */
  --pl-art: url("/brand/piing-P.png");
}

.pl-mark > div {
  position: absolute;
  inset: 0;
  /* 015 r2.6, verbatim from boot-splash.css:26: the PNG first, then image-set() with the WebP by type — a browser that
     can't pick an image by type keeps the first line, the same pixels */
  background-image: var(--pl-art);
  background-image: image-set(url("/brand/piing-P.webp") type("image/webp"), url("/brand/piing-P.png") type("image/png"));
  background-size: 100% 100%;
  background-repeat: no-repeat;
}
.pl-mark > div:not(.pl-dark) {
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  pointer-events: none;
}

/* the unlit mosaic. Not black — the tiles must still read as tiles, so this is
   a dimmed, desaturated, contrast-boosted copy rather than a silhouette. */
.pl-dark {
  filter: brightness(0.3) saturate(0.42) contrast(1.5);
}

/* the three wave layers share one oversized mask that travels corner to corner.
   240% so the gradient's soft edge is wider than the mark at every size. */
.pl-lit,
.pl-lit2,
.pl-front {
  -webkit-mask-size: 240% 240%;
  mask-size: 240% 240%;
  -webkit-mask-position: 100% 0%;
  mask-position: 100% 0%;
}

/* the speech bubble's single beat. An ellipse mask over the bubble's own
   position in the artwork — 47%/40% is measured off the asset, not guessed. */
.pl-bub {
  filter: brightness(1.62) saturate(0.72);
  opacity: 0;
  -webkit-mask-image: radial-gradient(
    ellipse 33% 26% at 47% 40%,
    #000 0%,
    #000 46%,
    rgba(0, 0, 0, 0.42) 70%,
    transparent 92%
  );
  mask-image: radial-gradient(
    ellipse 33% 26% at 47% 40%,
    #000 0%,
    #000 46%,
    rgba(0, 0, 0, 0.42) 70%,
    transparent 92%
  );
}

@keyframes pl-wipe {
  0% {
    opacity: 1;
    -webkit-mask-position: 100% 0%;
    mask-position: 100% 0%;
  }
  100% {
    opacity: 1;
    -webkit-mask-position: 0% 100%;
    mask-position: 0% 100%;
  }
}
@keyframes pl-front {
  0% {
    opacity: 1;
    -webkit-mask-position: 100% 0%;
    mask-position: 100% 0%;
  }
  88% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    -webkit-mask-position: 0% 100%;
    mask-position: 0% 100%;
  }
}
@keyframes pl-pop {
  0% {
    opacity: 0;
  }
  38% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

/* THE RESTING PULSE — 2.8s, and BOTH surfaces run this identical curve, so the
   app has one heartbeat rather than two that nearly match. Two close-but-
   different pulses read as a bug the moment anyone sees them back to back. */
@keyframes pl-breathe {
  0%,
  100% {
    filter: drop-shadow(0 0 calc(var(--pl-g) * 11) rgba(157, 92, 255, 0.42));
  }
  50% {
    filter: drop-shadow(0 0 calc(var(--pl-g) * 20.2) rgba(157, 92, 255, 0.72));
  }
}

/* ============================================================================
   WELCOME — the full sequence at full speed, then the breathe, forever
   ============================================================================
   THE ANIMATION LIST IS ADDITIVE AND THAT IS LOAD-BEARING. Listing only
   `pl-breathe` here would REPLACE the outline choreography and silently delete
   the 3s flare. During 0–4s `pl-breathe` is still in its delay and contributes
   nothing; at 4s it becomes active and, being later in the list, takes the
   filter over from outline's `forwards` fill. Do not split these into two
   rules and do not reorder them.
*/
.pl-welcome.pl-run {
  animation: pl-outline 4s linear forwards, pl-breathe 2.8s ease-in-out 4s infinite;
}

/* the neon keyline: a faint swell, then the strike at 81% (3.24s), then settle.
   The strike is white, not violet — a real tube flashes hotter than its colour. */
@keyframes pl-outline {
  0% {
    filter: drop-shadow(0 0 calc(var(--pl-g) * 2.8) rgba(201, 166, 255, 0.26));
  }
  25% {
    filter: drop-shadow(0 0 calc(var(--pl-g) * 6.4) rgba(201, 166, 255, 0.5));
  }
  70% {
    filter: drop-shadow(0 0 calc(var(--pl-g) * 8.3) rgba(157, 92, 255, 0.52));
  }
  75% {
    filter: drop-shadow(0 0 calc(var(--pl-g) * 9.2) rgba(157, 92, 255, 0.6));
  }
  81% {
    filter: drop-shadow(0 0 calc(var(--pl-g) * 23.9) rgba(255, 255, 255, 0.92));
  }
  87% {
    filter: drop-shadow(0 0 calc(var(--pl-g) * 11.9) rgba(157, 92, 255, 0.62));
  }
  100% {
    filter: drop-shadow(0 0 calc(var(--pl-g) * 11) rgba(157, 92, 255, 0.42));
  }
}

/* pass 1 — the tiles come up to two-thirds, on the diagonal */
.pl-welcome .pl-lit {
  filter: brightness(0.6) saturate(0.72);
  opacity: 0;
  -webkit-mask-image: linear-gradient(48deg, #000 0 56%, transparent 68%);
  mask-image: linear-gradient(48deg, #000 0 56%, transparent 68%);
}
.pl-welcome.pl-run .pl-lit {
  animation: pl-wipe 1.15s linear 1s forwards;
}

/* pass 2 — full colour, chasing the first */
.pl-welcome .pl-lit2 {
  filter: none;
  opacity: 0;
  -webkit-mask-image: linear-gradient(48deg, #000 0 56%, transparent 66%);
  mask-image: linear-gradient(48deg, #000 0 56%, transparent 66%);
}
.pl-welcome.pl-run .pl-lit2 {
  animation: pl-wipe 1.18s linear 1.82s forwards;
}

/* the crest — a narrow hot band riding the leading edge of pass 2, which is
   what makes the wave read as light travelling rather than as a fade */
.pl-welcome .pl-front {
  filter: brightness(1.85) saturate(0.5);
  opacity: 0;
  -webkit-mask-image: linear-gradient(
    48deg,
    transparent 50%,
    #000 56%,
    #000 60%,
    transparent 66%
  );
  mask-image: linear-gradient(48deg, transparent 50%, #000 56%, #000 60%, transparent 66%);
}
.pl-welcome.pl-run .pl-front {
  animation: pl-front 1.18s linear 1.82s forwards;
}

.pl-welcome.pl-run .pl-bub {
  animation: pl-pop 0.62s cubic-bezier(0.22, 0.9, 0.3, 1) 3.34s forwards;
}

/* ============================================================================
   LOADING — the breathe only, from frame one
   ============================================================================
   NO SEQUENCE HERE, deliberately. Auth resolves well inside 4s, so a wave that
   never completes is worse than no wave: the user would watch the mark light
   halfway and then get yanked to the app. The mark is fully lit from the first
   frame (`.pl-lit2` revealed, mask removed) and only the glow moves.

   This is why `.pl-loading` needs just two layers. Rendering all five is
   harmless but pointless.
*/
.pl-loading .pl-lit2 {
  filter: none;
  opacity: 1;
  -webkit-mask-image: none;
  mask-image: none;
}
.pl-loading.pl-run {
  animation: pl-breathe 2.8s ease-in-out infinite;
}

/* ============================================================================
   THE PERIOD — the mint presence dot
   ============================================================================ */

/* WELCOME: the dot arrives WITH the breathe, at 4s. The sequence finishing IS
   the sign-on, so the dot is the last beat of it rather than a separate event. */
.pl-period-welcome {
  color: #55477e;
}
.pl-run .pl-period-welcome {
  animation: pl-period-on 0.5s ease-out 4s forwards;
}
@keyframes pl-period-on {
  to {
    color: #4dd6c4;
    text-shadow: 0 0 0.3em rgba(77, 214, 196, 0.9);
  }
}

/* LOADING: mint from frame one, pulsing on the SAME 2.8s clock as the mark —
   one heartbeat shared by mark and period, not two drifting ones.
   ⚠ This SUPERSEDES a ratified rule. Mock 26 rules the splash period dim
   #55477E until ready, then a blink to mint. Founder-ruled otherwise; see
   SPEC.md. Do not "restore" the dim state without a ruling.

   The glow is in `em`, so it tracks font-size the way --pl-g tracks the mark.
   At a fixed 13px it would be 33% of a 40px word and 20% of a 64px one. */
.pl-period-loading {
  color: #4dd6c4;
}
.pl-run .pl-period-loading {
  animation: pl-dot-pulse 2.8s ease-in-out infinite;
}
@keyframes pl-dot-pulse {
  0%,
  100% {
    opacity: 0.6;
    text-shadow: 0 0 0.15em rgba(77, 214, 196, 0.45);
  }
  50% {
    opacity: 1;
    text-shadow: 0 0 0.325em rgba(77, 214, 196, 0.95);
  }
}

/* ============================================================================
   REDUCED MOTION — the finished mark and a steady mint dot, immediately
   ============================================================================
   Same DESTINATION for everyone. The choreography is removed, never the
   outcome. A splash is the worst possible place to serve a dim, half-lit logo
   to someone who asked for less motion — do not "simplify" this into a shorter
   sequence or a faded-in mark.
*/
@media (prefers-reduced-motion: reduce) {
  .pl-mark,
  .pl-mark > div,
  .pl-period-welcome,
  .pl-period-loading {
    animation: none !important;
  }
  .pl-dark {
    filter: none !important;
  }
  .pl-lit,
  .pl-front,
  .pl-bub {
    opacity: 0 !important;
  }
  .pl-lit2 {
    opacity: 1 !important;
    -webkit-mask-image: none !important;
    mask-image: none !important;
  }
  .pl-mark {
    filter: drop-shadow(0 0 calc(var(--pl-g) * 14.7) rgba(157, 92, 255, 0.5)) !important;
  }
  .pl-period-welcome,
  .pl-period-loading {
    color: #4dd6c4 !important;
    text-shadow: 0 0 0.3em rgba(77, 214, 196, 0.9) !important;
  }
}

/* ============================================================================
   APP ADAPTATION (DW-1, 2026-08-31) — the drawn period dot
   ============================================================================
   design v1.2 §1: the app's wordmark period is a DRAWN dot, never a typed
   glyph (typed characters sit on font baselines and never center). The kit's
   .pl-period-* rules above animate `color` + `text-shadow` against the
   preview's typed <i>; the app's dot is an empty inline-block span, so the
   mirrors below bind the SAME timings and values (#55477E dim · #4dd6c4
   mint · 0.5s@4s arrival · 2.8s pulse · 0.3em/0.15em/0.325em glows, em so
   the glow tracks the wordmark size the way --pl-g tracks the mark) to
   background-color + box-shadow. Nothing else about the kit is changed.
*/
.pl-dot {
  display: inline-block;
  border-radius: 50%;
}
.pl-dot.pl-period-welcome {
  background-color: #55477e;
}
.pl-run .pl-dot.pl-period-welcome {
  animation: pl-dot-on 0.5s ease-out 4s forwards;
}
@keyframes pl-dot-on {
  to {
    background-color: #4dd6c4;
    box-shadow: 0 0 0.3em rgba(77, 214, 196, 0.9);
  }
}
.pl-dot.pl-period-loading {
  background-color: #4dd6c4;
}
.pl-run .pl-dot.pl-period-loading {
  animation: pl-dot-pulse-drawn 2.8s ease-in-out infinite;
}
@keyframes pl-dot-pulse-drawn {
  0%,
  100% {
    opacity: 0.6;
    box-shadow: 0 0 0.15em rgba(77, 214, 196, 0.45);
  }
  50% {
    opacity: 1;
    box-shadow: 0 0 0.325em rgba(77, 214, 196, 0.95);
  }
}
/* reduced motion — same DESTINATION as the kit's block: steady lit mint,
   immediately, on both surfaces. Never a dim dot. */
@media (prefers-reduced-motion: reduce) {
  .pl-dot.pl-period-welcome,
  .pl-dot.pl-period-loading {
    animation: none !important;
    opacity: 1 !important;
    background-color: #4dd6c4 !important;
    box-shadow: 0 0 0.3em rgba(77, 214, 196, 0.9) !important;
  }
}
