/* ─── dither ─────────────────────────────────────────────────────────────────
   Shading is DITHERED, never a smooth dim: a Bayer mask opens gaps in the
   picture and whatever is behind shows through, one whole level at a time.
   Arrival is the same idea run through noise. Ported from WaveLoop
   (web/src/transitions.css there) under plain names; the two sites share the
   masks, so a thing that folds or arrives on either does it the same way.

   Two families below. The SHADE masks are an ordered Bayer grid, for a thing
   already on screen that is darkening or lifting — a leaf rotating away, a
   page falling behind an iris. The DITHER-IN masks are a noise field, for a
   thing that is appearing; those are generated, see the block at the foot.

   Every mask is an alpha image: the cells it draws stay, the rest is cut.
   Nothing here draws a stroke, and nothing here names a colour but the one
   token that says what belongs behind a shaded thing.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  /* ordered dither, 4×4 Bayer in 2px cells: as a mask, --dither-k hides k
     of every 16 cells (over --dither-shade, that darkens them). The cells go
     in Bayer rank order, so every level holds the one before it: a run of
     levels only ever adds cells, never swaps them */
  --dither-1: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' shape-rendering='crispEdges'%3E%3Cpath d='M2 0h2v2h-2zM4 0h2v2h-2zM6 0h2v2h-2zM0 2h2v2h-2zM2 2h2v2h-2zM4 2h2v2h-2zM6 2h2v2h-2zM0 4h2v2h-2zM2 4h2v2h-2zM4 4h2v2h-2zM6 4h2v2h-2zM0 6h2v2h-2zM2 6h2v2h-2zM4 6h2v2h-2zM6 6h2v2h-2z'/%3E%3C/svg%3E");
  --dither-3: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' shape-rendering='crispEdges'%3E%3Cpath d='M2 0h2v2h-2zM6 0h2v2h-2zM0 2h2v2h-2zM2 2h2v2h-2zM4 2h2v2h-2zM6 2h2v2h-2zM0 4h2v2h-2zM2 4h2v2h-2zM6 4h2v2h-2zM0 6h2v2h-2zM2 6h2v2h-2zM4 6h2v2h-2zM6 6h2v2h-2z'/%3E%3C/svg%3E");
  --dither-4: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' shape-rendering='crispEdges'%3E%3Cpath d='M2 0h2v2h-2zM6 0h2v2h-2zM0 2h2v2h-2zM2 2h2v2h-2zM4 2h2v2h-2zM6 2h2v2h-2zM2 4h2v2h-2zM6 4h2v2h-2zM0 6h2v2h-2zM2 6h2v2h-2zM4 6h2v2h-2zM6 6h2v2h-2z'/%3E%3C/svg%3E");
  --dither-5: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' shape-rendering='crispEdges'%3E%3Cpath d='M2 0h2v2h-2zM6 0h2v2h-2zM0 2h2v2h-2zM4 2h2v2h-2zM6 2h2v2h-2zM2 4h2v2h-2zM6 4h2v2h-2zM0 6h2v2h-2zM2 6h2v2h-2zM4 6h2v2h-2zM6 6h2v2h-2z'/%3E%3C/svg%3E");
  --dither-6: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' shape-rendering='crispEdges'%3E%3Cpath d='M2 0h2v2h-2zM6 0h2v2h-2zM0 2h2v2h-2zM4 2h2v2h-2zM6 2h2v2h-2zM2 4h2v2h-2zM6 4h2v2h-2zM0 6h2v2h-2zM2 6h2v2h-2zM4 6h2v2h-2z'/%3E%3C/svg%3E");
  --dither-7: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' shape-rendering='crispEdges'%3E%3Cpath d='M2 0h2v2h-2zM6 0h2v2h-2zM0 2h2v2h-2zM4 2h2v2h-2zM2 4h2v2h-2zM6 4h2v2h-2zM0 6h2v2h-2zM2 6h2v2h-2zM4 6h2v2h-2z'/%3E%3C/svg%3E");
  --dither-8: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' shape-rendering='crispEdges'%3E%3Cpath d='M2 0h2v2h-2zM6 0h2v2h-2zM0 2h2v2h-2zM4 2h2v2h-2zM2 4h2v2h-2zM6 4h2v2h-2zM0 6h2v2h-2zM4 6h2v2h-2z'/%3E%3C/svg%3E");
  --dither-10: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' shape-rendering='crispEdges'%3E%3Cpath d='M6 0h2v2h-2zM0 2h2v2h-2zM4 2h2v2h-2zM2 4h2v2h-2zM0 6h2v2h-2zM4 6h2v2h-2z'/%3E%3C/svg%3E");
  /* what belongs behind a shaded thing: the look's own drop-shadow ink, so a
     light look shades to a faint stain and a dark one to near-black */
  --dither-shade: var(--shade-7);
}

/* ── a fold ───────────────────────────────────────────────────────────────
   For an element rotating away and back — a leaf on its hinge, a sheet on its
   rod. Down lands a whole level at a time from clear to ten cells cut; up is
   the same run in reverse. A mask image cannot blend, and the steps are the
   look. Pair either with the element's own transform transition at the same
   duration, on step-end, and the shade lands with the fold:

     animation: dither-shade-down 0.42s step-end both;

   Put mask-size 8px 8px on the element (the tile) — the keyframes only name
   the image. */
@keyframes dither-shade-down {
    0% { -webkit-mask-image: none; mask-image: none; }
   16% { -webkit-mask-image: var(--dither-1); mask-image: var(--dither-1); }
   33% { -webkit-mask-image: var(--dither-3); mask-image: var(--dither-3); }
   50% { -webkit-mask-image: var(--dither-5); mask-image: var(--dither-5); }
   66% { -webkit-mask-image: var(--dither-7); mask-image: var(--dither-7); }
   83% { -webkit-mask-image: var(--dither-8); mask-image: var(--dither-8); }
  100% { -webkit-mask-image: var(--dither-10); mask-image: var(--dither-10); }
}
@keyframes dither-shade-up {
    0% { -webkit-mask-image: var(--dither-10); mask-image: var(--dither-10); }
   16% { -webkit-mask-image: var(--dither-8); mask-image: var(--dither-8); }
   33% { -webkit-mask-image: var(--dither-7); mask-image: var(--dither-7); }
   50% { -webkit-mask-image: var(--dither-5); mask-image: var(--dither-5); }
   66% { -webkit-mask-image: var(--dither-3); mask-image: var(--dither-3); }
   83% { -webkit-mask-image: var(--dither-1); mask-image: var(--dither-1); }
  100% { -webkit-mask-image: none; mask-image: none; }
}

/* ── dither in: how things arrive ─────────────────────────────────────────
   Nothing that appears fades up. It DITHERS in: cells of it switch on out of
   a noise field — clumps condensing first, ragged sparkling edges, the last
   holes closing — until it is solid. Pixels arriving, not a glow coming up.
   Every level lands whole (step-end); the steps are the look. (Not the Bayer
   grid the shading uses: in order, that came in too evenly.)

   Put .dither-in on anything that appears (a label, a gem, a sheet's
   parchment). It is a mask, so text, canvas and images all take it. Its own
   variables: --dither-in-delay (when it starts; it is invisible until then)
   and --dither-in-time (default 360ms). It plays once, when the class lands:
   a thing that should arrive again toggles the class off and on across a
   state change, or keys the node. Reduced motion: there at once.
   The masks and the keyframes below are generated. ── */
.dither-in {
  -webkit-mask-size: var(--dither-in-tile);
  mask-size: var(--dither-in-tile);
  animation: dither-in var(--dither-in-time, 360ms) step-end var(--dither-in-delay, 0ms) both;
}
@media (prefers-reduced-motion: reduce) {
  .dither-in { animation: none; }
}
/* ── dither-in masks: GENERATED by scripts/dither-in-masks.mjs — change the script, then run it ── */
/* 32×32 cells of 2px on a 64px tile; 12 levels, seed 24301 */
:root {
  --dither-in-tile: 64px;
  --dither-in-1: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAABFSURBVHjaY2AYBaNgJIEPVBCgHPBTKvCBgeE/pQJUCYsPcOd9oFiAAnfwf0BjkiNAhViiQVIbHCl2ANM6DaJmFAyOIhUARqMu4VlfSgwAAAAASUVORK5CYII=");
  --dither-in-2: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAACRSURBVHjazdXRDYAwCATQ2+D235INMFqs5v68opH4YV8MKYgVAAISBqAxjx2hSwMk+BPwgprLAACpjTGAmtSAN4bk+ykdtWlxTyH3izzfVTqAYFXH+hYcqPYQcaw8mK0+8hvAC8atA7exi90MmJuS48WAYsYC8F6fCauRy9ByeEQ0QMfhkQ3Q909ZLyn1gQewASDwofVbOFYEAAAAAElFTkSuQmCC");
  --dither-in-3: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAADLSURBVHjapdXrDcMgDATg2+D23/I2cGUMAfrPR5qK8EWysHkEAIS6VtsGsRppugEVK/+04RpNdg04s1yR20DgGQByjWy+bAOvalmQj1FqQ40s3kB3lR0Qh+yoBlRpjvQsYDCnClyT34YAx51TProGIFhV4nzvgMYvI0qz5F3YZwA0Vq8BjG+yq84OjNS4WjkwA0as48WAmR73CjRAB4zYBtzbhx7kItlzZQHPE6nG2AfF1Xdgfyge4EzOBv4d2w7g24w+5EPo2I9N+AExelPoTJ1qOwAAAABJRU5ErkJggg==");
  --dither-in-4: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAADxSURBVHjanZXREQJBDELpgP67pAOcJBtdPxfPj/M5gwdhI2AIAEiIdROAgyU3jwBaUyDZv5AArXSLUwmA4P6gdsgIsG55nDkDJeXJ6ft4r6ANEr/XOyg7xkxJIShzpH2pvoMezQ4sA7Iq5q0cEzCXSf2hJ2Czrv6qE08A2G+RVX0nAJ2ve2J0Blx61Ziy11EFQNho1O0NACvfWQKtHYG2tidn2CuYusk1K8lIAMab5zz2UngHo3Q2Sk0tALset8cJIO5DrAj4bCSudAAoX1shA5iocUXzCrjrfpMOgOcf71irDr+DaZ2+zxeBMx1tbd/BB6ELEc+72UnNAAAAAElFTkSuQmCC");
  --dither-in-5: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAAEFSURBVHjanZXBEcQwDALpgP67pANuwM79zSeONzMOkpAMGgIAkqIkDEDMFpLddQEukkiCMDbQI7OLPC0gUnjWBMgFkHl1kwR4AlHjEyH+8l6BBZ0P/biAhOMr7Fb9GcAw3UonTZyATOXYT9oAbNH6siwugKAM22qeJ2DbNG+i6QmAZjrRlkeQF8j3B5pAHowqM+FpA8gUSWpOKw6APsU+8ynHDyDz6BRsBvVKxlpHk7GALHGvOhLqvWfATFa2eer8Bajx9ORGuAC0/4jj28p7BslwVcUsJ85nkOhuE+uOqWfA2qXzDEwvL+DU+X/dLMC9dQ6o6QbQSvXOiYujcQPn1rq2fQc/tuoEclVolVYAAAAASUVORK5CYII=");
  --dither-in-6: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAAEJSURBVHjanZXZDcQwEELpgP67pANWMNkCTD4i50XCnguDhgSApikZAxCRR7KDtQBbJmySIIwJVCon7Om0AAgmlSOBUR4AmQRF7P4tACJtNU9KfAuwpFOGssUAaKbOFU2yFiDDedRKR/0dUKav4tkCC7ADkmUlUC4gGe7ZpNZ7Aa4q+e+6CYghUMU3kAXcSeoGC2h+laCS7v8gvwJK+OqeAZqAqXMmfqLPgJ8TZIhb8wHU0fK6fpvAOZLc1wgY0YwNP5cdgGsGLXcjXACalX42ugW0W1NtQ4lxAfBdNQktBV8A0y46V7OuYZ6BLsSu7/Z5BvWB3hofHQDS+c1N5Lt+BrrY6o9n9M/gB21t+Ko6FDRtAAAAAElFTkSuQmCC");
  --dither-in-7: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAAEESURBVHjanZXBDUMxDELZgP23ZAMqcNt7uFT5r5KFiU1AQxJg07SMAYgQZMsmSC2gR9hmvs0JyKL6A9BagAyTNiWQ5AJItTQAxKMFSPQ1CIiaQLyJOdEVaQtgWqNFECm8AEVUpEVWx+cdxN92l6qqsGfggric6dUKTpuckfEIfOLu0iZw/rA7hBG4+5Pl+50HoH6idVcQgzpuyYMVUJdMXAGTBLgwyd8L6P5dWxW5gARRvNEfv4MuDxMrgLSCWIOW1zdxX0ETFnkj2HhagDL3SYKwnhZwNqW13tgAyEskVGPz8R34OmqLd9fPIPnaZ+JXdACdfFiXCZnkd5C9uUenc6cBfAAUNxAoPglAgwAAAABJRU5ErkJggg==");
  --dither-in-8: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAADtSURBVHjapdXRjcMwDANQbsD9t+QGPIhScN9mU6BwXgE3kiwFNCXRNj3fKEC0IOdC1u+Q1QCJ2bUCzWcke6sBzb70hAjSbGBuJiJwbjvQFx0gURUkQ5xigQLdAAOTJMIAGpC/RxujG2BSbDm/duAtk7V/wBL20MnMg7Vwl/wz6L8b3gHO8detK5glb+ceJjV7ckqYxkmRepAPmT6qYAZBwoJ9g+0RsLPVujgbSH5481q/gJJoZUq9w4UIXTc2sMXG9mAJ8u0KSQYbSG6E0JSsgnuaOzxoIId1X3l5gTUwNcLXxSn5O+ibJHvuVMAfuLb7tF4sZo8AAAAASUVORK5CYII=");
  --dither-in-9: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAADBSURBVHjaxdVbCoQwEETR3kHtf5d3BzV0HjrkszIwImKOUCR2R8u2oa+ybFcGc9wDmRDsFdpnDCsTp6A5BElWBPJa0XwQwT4AQth5UOrwBDwBo3JRCeBd6jY5gfVmGLXmCjp63Iewj870JfjswgA4mz+Ao+n+AL15VufE4LfGF6AfwLP9EuCIS2BOa/S/7oArGHG1/hQhzGL3Gsc3+gJwqT8tRKBnD3ehRAzsOVEJvFuYMbMEukbFV+sF8GTOEQF8AP1MxCcIzXWAAAAAAElFTkSuQmCC");
  --dither-in-10: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAACISURBVHja7dS7EcAwCANQNtD+W2oD5YLzuVBKLuOOV+h8GFOSRKoPJJULq+7ChUcQwJVJhUABLkDvrVx4QlerM6jRIAeAs4pAvC2ADk7h7DJiGENngOYUOqAfdgNy4AYoRYDJBqwNiwj4xvpQzIEqMIP+w70LfODdajKDXvQWgGP4Dfi8Ey04AGDZaAosnULjAAAAAElFTkSuQmCC");
  --dither-in-11: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMA/1uRIrUAAABQSURBVHja7dQxCgAwCAPA/iD//2V+YKk4lU6NU1Ac5CYRyYqrlgegARr2gA6kDjVqgGwNGAGIcKwBfH59wBbYACb3YKWaAhkmKsxbPoFfsAEdddQW+vu84QAAAABJRU5ErkJggg==");
}
@keyframes dither-in {
       0% { -webkit-mask-image: linear-gradient(transparent 0 0); mask-image: linear-gradient(transparent 0 0); }
    8.33% { -webkit-mask-image: var(--dither-in-1); mask-image: var(--dither-in-1); }
   16.67% { -webkit-mask-image: var(--dither-in-2); mask-image: var(--dither-in-2); }
      25% { -webkit-mask-image: var(--dither-in-3); mask-image: var(--dither-in-3); }
   33.33% { -webkit-mask-image: var(--dither-in-4); mask-image: var(--dither-in-4); }
   41.67% { -webkit-mask-image: var(--dither-in-5); mask-image: var(--dither-in-5); }
      50% { -webkit-mask-image: var(--dither-in-6); mask-image: var(--dither-in-6); }
   58.33% { -webkit-mask-image: var(--dither-in-7); mask-image: var(--dither-in-7); }
   66.67% { -webkit-mask-image: var(--dither-in-8); mask-image: var(--dither-in-8); }
      75% { -webkit-mask-image: var(--dither-in-9); mask-image: var(--dither-in-9); }
   83.33% { -webkit-mask-image: var(--dither-in-10); mask-image: var(--dither-in-10); }
   91.67% { -webkit-mask-image: var(--dither-in-11); mask-image: var(--dither-in-11); }
     100% { -webkit-mask-image: none; mask-image: none; }
}
/* ── end of the generated dither-in masks ── */
