/* Gooki storefront: what a picture looks like before it has arrived.
 *
 * Every cover on the shelf is rendered by the origin on request, so a cold one
 * costs a few hundred ms even with the render cached — long enough that a shelf
 * of empty white squares reads as a broken page rather than a loading one.
 *
 * Two layers, and both are progressive:
 *
 *   1. A paper-toned skeleton painted as the IMAGE'S OWN background. An <img>
 *      shows its background until its bitmap paints over it, so this needs no
 *      wrapper element and no script — it is what the visitor sees in the gap
 *      between the HTML arriving and image-loading.js running.
 *   2. A turning mark on the container the image already sits in, matched with
 *      :has() so nothing in the DOM has to move. Only on containers big enough
 *      to hold one: a 52px category cover gets the skeleton by itself.
 *
 * The script's only job is to END those states, which is the safe direction for
 * it to fail in — a missing or broken image-loading.js leaves a page that
 * shimmers, never one that hides its pictures. Nothing here sets opacity to 0
 * for that reason: the reveal animates up from a dim state, so an image can
 * never be invisible waiting for a class that does not come.
 */

:root {
  /* The two paper tones the rest of the storefront is built from, so a loading
     tile reads as this book's paper rather than as a grey web placeholder. */
  --gk-skel-quiet: var(--gk2-paper-2, #f3eee2);
  --gk-skel-lit: var(--gk2-paper, #fdfbf5);
  --gk-skel-sweep: 1.5s;
  --gk-spin-size: clamp(18px, 22%, 26px);
}

/* --------------------------------------------------------------- skeleton -- */

/* `.gk-img` is added by the script, so an image it never claimed is styled
   exactly as it was before this file existed. */
img.gk-img {
  /* The sweep is drawn in the image's own box; `no-repeat` keeps the lit band
     from tiling into a second highlight on a wide spread. */
  background-repeat: no-repeat;
  background-position: 100% 0;
  background-size: 300% 100%;
}

img.gk-img.is-pending {
  background-image: linear-gradient(
    100deg,
    var(--gk-skel-quiet) 34%,
    var(--gk-skel-lit) 50%,
    var(--gk-skel-quiet) 66%
  );
  animation: gk-skel-sweep var(--gk-skel-sweep) ease-in-out infinite;
}

/* An image that never arrives must stop asking. The alt text is what is left,
   so the tile keeps a quiet paper ground for it to sit on. */
img.gk-img.is-failed {
  background-image: none;
  background-color: var(--gk-skel-quiet);
  animation: none;
}

/* Drop the ground the moment the picture owns the box: a transparent PNG would
   otherwise keep the paper tone behind it for good. */
img.gk-img.is-loaded {
  background-image: none;
  animation: none;
}

/* The reveal, only for an image that was actually waited for. One already in
   the browser's cache is marked loaded in the same pass it is claimed and never
   gets this class, so a warm page does not fade in on every navigation. */
img.gk-img.is-revealed {
  animation: gk-img-arrive 340ms var(--gk2-ease, cubic-bezier(0.22, 1, 0.36, 1)) both;
}

@keyframes gk-skel-sweep {
  from { background-position: 100% 0; }
  to { background-position: -100% 0; }
}

@keyframes gk-img-arrive {
  from { opacity: 0.3; }
  to { opacity: 1; }
}

/* ---------------------------------------------------------------- spinner -- */

/* The containers that are big enough for a mark. Each already exists and each
   already clips its image; only `position` is added — and only where nothing
   has set one. `:where()` gives this rule zero specificity, so a container
   the shelf lays out with `position: absolute` (the catalogue's cover and
   peek, the home page's fan and morph) keeps it. Written as a plain class
   list this file loaded last and turned those absolute layers into flowing
   blocks: every catalogue cover dropped under its own peek and the card's
   words ran into the row below. A relative container is all the `::after`
   below needs; an absolute one is positioned already. */
:where(.alive-shelf__book,
.alive-book__cover,
.alive-book__peek,
.alive-fan,
.alive-morph,
.alive-step__panel,
.alive-family__frame,
.alive-page__sheet,
.book-page__cover-figure,
.book-reader__stage,
[data-gk-spinner]) {
  position: relative;
}

.alive-shelf__book:has(> img.gk-img.is-pending)::after,
.alive-book__cover:has(> img.gk-img.is-pending)::after,
.alive-book__peek:has(> img.gk-img.is-pending)::after,
.alive-fan:has(> img.gk-img.is-pending)::after,
.alive-morph:has(> img.gk-img.is-pending)::after,
.alive-step__panel:has(> img.gk-img.is-pending)::after,
.alive-family__frame:has(> img.gk-img.is-pending)::after,
.alive-page__sheet:has(> img.gk-img.is-pending)::after,
.book-page__cover-figure:has(> img.gk-img.is-pending)::after,
.book-reader__stage:has(> img.gk-img.is-pending)::after,
[data-gk-spinner]:has(> img.gk-img.is-pending)::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--gk-spin-size);
  height: var(--gk-spin-size);
  max-width: 26px;
  max-height: 26px;
  margin: calc(var(--gk-spin-size) / -2) 0 0 calc(var(--gk-spin-size) / -2);
  border-radius: 50%;
  /* Two thirds of a ring in the page's own ink, the last third in the accent —
     the turn is legible without a second colour stop. */
  border: 2px solid rgb(var(--gk2-ink-rgb, 34 48 74) / 0.16);
  border-top-color: var(--gk2-coral, #d95d45);
  animation: gk-spin 760ms linear infinite;
  pointer-events: none;
  z-index: 2;
}

@keyframes gk-spin {
  to { transform: rotate(360deg); }
}

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

/* A visitor who has asked for less movement still needs to be told the picture
   is on its way — so the skeleton and the ring stay and only the motion goes. */
@media (prefers-reduced-motion: reduce) {
  img.gk-img.is-pending {
    background-image: none;
    background-color: var(--gk-skel-quiet);
    animation: none;
  }

  img.gk-img.is-revealed {
    animation: none;
  }

  .alive-shelf__book:has(> img.gk-img.is-pending)::after,
  .alive-book__cover:has(> img.gk-img.is-pending)::after,
  .alive-book__peek:has(> img.gk-img.is-pending)::after,
  .alive-fan:has(> img.gk-img.is-pending)::after,
  .alive-morph:has(> img.gk-img.is-pending)::after,
  .alive-step__panel:has(> img.gk-img.is-pending)::after,
  .alive-family__frame:has(> img.gk-img.is-pending)::after,
  .alive-page__sheet:has(> img.gk-img.is-pending)::after,
  .book-page__cover-figure:has(> img.gk-img.is-pending)::after,
  .book-reader__stage:has(> img.gk-img.is-pending)::after,
  [data-gk-spinner]:has(> img.gk-img.is-pending)::after {
    animation: none;
    border-top-color: var(--gk2-coral, #d95d45);
  }
}
