/* ============================================================
   UNIQorn — ink dissolve
   assets/ink-dissolve.css + assets/ink-dissolve.js

   「其他的会像水墨般化开」 — the cards that were not picked do not fade
   out. They lose their edge first and their substance second, and what is
   left at the end is a wide, faint, soft-edged trace: ink in water.

   One number
   ----------
   `--dissolve-p` lives on the container and runs 0 -> 1. **Its default is
   1, the finished state**, so anything that never runs the script — no JS,
   print, a browser that drops the file — is already at the end of the
   animation. This is the same rule `--draw-p` (draw.css) and `--read-p`
   follow; it is the site's grammar for "the animation is decoration, the
   result is the page".

   Each card derives its own progress from that one number and its own
   stagger position `--ink-d`, so the fan is one clock read 77 times
   rather than 77 clocks.

   What moves, and in what order
   -----------------------------
   Three composited properties per card. No `width`, `height`, `top`,
   `left` or `margin` is touched — the deck places the cards with its own
   `transform`, and this file writes only the independent `scale`,
   `rotate` and `translate` properties, which *compose with* that
   transform instead of replacing it. (The composed order is translate,
   rotate, scale, then transform, so a card grows about its own centre
   and is then placed on the arc: the arc does not move.)

   The order of the two signals is the whole effect. `filter: blur()`
   — the edge going — rises linearly with p; `opacity` — the substance
   going — falls on p³. At p = .5 the card is already soft and still 87%
   present; at p = .9 it is a wide, faint, soft-edged trace. A fade does
   the opposite, and a fade is not what was asked for.

   Colour
   ------
   None added. The card back is indigo on white and stays indigo on
   white; the blur is what makes it read as ink, and blur does not
   introduce a hue. `--ink-veil` is the one tone this file can paint with
   if a surface needs it, and it is the indigo thinned.
   ============================================================ */

/* ---------- the one number, and the dials it drives ---------- */

.ink-dissolve {
  /* 0 = every card present, 1 = every card gone. Default 1: finished. */
  --dissolve-p: 1;

  /* How long one card's own melt takes, and how far the starts are spread
     across the set. stagger 0 = all 77 at once, .45 = the last card starts
     45% of the way through the first card's melt. */
  --ink-dur: 1400ms;
  --ink-stagger: .45;

  /* The melt itself: how far the edge softens, how much the card blooms
     outward, and the tone if a surface ever needs one (indigo, thinned). */
  --ink-blur: 10px;
  --ink-bloom: .45;
  --ink-veil: rgba(30, 58, 95, .22);
}

/* ---------- one card ---------- */

.ink-dissolve .ink-item {
  /*
   * Local progress. `--ink-d` is this card's place in the wave, 0..1,
   * written once by the script; `--ink-stagger` is the same number the
   * container holds. The span is what is left of the clock after the
   * starts are spread, and it is floored so a stagger of 1 cannot divide
   * by zero.
   */
  --ink-span: max(.001, calc(1 - var(--ink-stagger)));
  --ink-p: clamp(0, calc((var(--dissolve-p) - var(--ink-d, 0) * var(--ink-stagger)) / var(--ink-span)), 1);

  /* The substance goes on a cubic, the edge on a straight line. */
  --ink-fade: calc(var(--ink-p) * var(--ink-p) * var(--ink-p));

  opacity: calc(1 - var(--ink-fade));
  filter: blur(calc(var(--ink-p) * var(--ink-blur)));

  /* The bloom. Per-card drift (`--ink-x`, `--ink-y`, `--ink-rot`,
     `--ink-grow`) is written by the script from the card's index, so the
     spread is uneven the way a liquid is uneven — and deterministic, so
     the same pick always dissolves the same way. */
  scale: calc(1 + (var(--ink-bloom) + var(--ink-grow, 0)) * var(--ink-p));
  rotate: calc(var(--ink-rot, 0deg) * var(--ink-p));
  translate: calc(var(--ink-x, 0px) * var(--ink-p)) calc(var(--ink-y, 0px) * var(--ink-p));

  /* The class only exists while the melt is running, so this is a
     temporary promotion, not 77 permanent layers. */
  will-change: opacity, filter, scale, translate, rotate;
}

/*
 * The end state. `opacity: 0` is invisible but still focusable and still
 * hit-testable, and these are buttons — so the script takes them out of
 * the tree as well once the last frame is painted.
 */
.ink-dissolve .ink-item.is-ink-gone {
  visibility: hidden;
  pointer-events: none;
  will-change: auto;
}

/* ============================================================
   The wash — one full-screen layer, when an exit is pressed.

   Same vocabulary as assets/page-transition.css: a mask whose reach is
   measured, the same house curve, the same clock, and the press point
   pinned by the same `calc(50% + offset)` reasoning. The difference is
   direction. A page transition *reveals* the new page through a growing
   disc of ink; this one *takes the screen away* with a growing disc, so
   the layer has to be transparent where the ink has reached and solid
   where it has not.

   That inversion is why this is a gradient and not the baked bitmap, and
   it was measured rather than assumed. Two things were tried and both
   failed in Chromium 153:

   1. XOR-ing img/ink-bloom.webp against a solid layer with
      `mask-composite`, so the site's own wet rim survived. It does not
      composite: six spellings were rendered side by side — `add,
      exclude`, `add, subtract`, an explicit `mask-mode: alpha, alpha`,
      both layer orders, two gradients with no image at all, and the
      legacy `-webkit-mask-composite: destination-out` — and every one of
      them came out as a plain `add`. The second layer only ever landed on
      top of the first.

   2. Growing the mask with `mask-size`, the page transition's own
      mechanism. That cannot express a hole at all. A single mask layer
      hides the element where its alpha is 0, so `mask-size: 0` hides
      *everything* — the first frame of the strip came out blank, and the
      sixth came out blank too, for the opposite reason. The page
      transition gets away with it because its mask starts at nothing and
      the *old* page is still underneath; here there is nothing
      underneath but the layer's own absence.

   What is left is one radial gradient, held at the element's own size so
   it covers the whole layer, with the hole's radius driven by `--ink-p`.
   The stops are not invented: they copy the alpha profile measured off
   ink-bloom.webp and quoted in page-transition.css — flat out to 0.45 of
   the radius, then falling to nothing — inverted, and with the ramp
   stretched wide on purpose. A narrow ramp is the hard-edged circle the
   cover already rejected; a wide one thins instead of cutting.
   ============================================================ */

.ink-wash {
  /* 0 = the screen is whole, 1 = the ink has taken all of it. Default 1,
     the finished state, for the same reason --dissolve-p defaults to 1. */
  --ink-p: 1;
  /* The final radius of the *solid* ink, in the element's own pixels.
     Overwritten by the script with a measured value; the fallback is the
     worst case for a square window, sqrt(2) × 100vmax. */
  --ink-r: 141vmax;

  -webkit-mask-image: radial-gradient(circle at var(--ink-cx, 50%) var(--ink-cy, 50%),
            transparent 0 calc(var(--ink-r) * var(--ink-p) * .55),
            rgba(0, 0, 0, .4) calc(var(--ink-r) * var(--ink-p) * .78),
            rgba(0, 0, 0, .86) calc(var(--ink-r) * var(--ink-p) * .93),
            #000 calc(var(--ink-r) * var(--ink-p)));
          mask-image: radial-gradient(circle at var(--ink-cx, 50%) var(--ink-cy, 50%),
            transparent 0 calc(var(--ink-r) * var(--ink-p) * .55),
            rgba(0, 0, 0, .4) calc(var(--ink-r) * var(--ink-p) * .78),
            rgba(0, 0, 0, .86) calc(var(--ink-r) * var(--ink-p) * .93),
            #000 calc(var(--ink-r) * var(--ink-p)));
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

/* Same end state, same reason: masked out is not the same as gone. */
.ink-wash.is-ink-gone {
  visibility: hidden;
  pointer-events: none;
}

/* ---------- reduced motion ---------- */

/*
 * The end state, without the getting there. The script does not start the
 * melt at all and resolves at once; this block is here for the case where
 * a melt is already running when the preference is turned on — the item
 * jumps to the finished state rather than continuing.
 *
 * foundation.css already clamps every animation to .001ms under this
 * preference, which is what makes the wash land here too.
 */
@media (prefers-reduced-motion: reduce) {
  .ink-dissolve .ink-item {
    --ink-p: 1;
    will-change: auto;
  }
  .ink-wash {
    --ink-p: 1;
  }
}
