/* ═══════════════════════════════════════════════════════════════════════════
   catalog-loader.css — the branded loading moment for the price catalog.

   THE IDEA: "ruling up the book". The catalog is a price book, so the loader is
   the book's own register being ruled — a stack of hairlines drawn left to
   right, each one laid down in ice-blue and then cooling to a settled grey
   border. Hot to cold is the Airflow idea applied to time rather than
   temperature: live, then settled. No spinner, no gradient, no glow.

   WHY THIS AND NOT A SKELETON: the skeleton is the quiet answer and it is
   already half-built (renderCatalogSkeleton). The user asked for the branded
   moment instead. This file is that, held to the same floor: flat surfaces,
   hairline borders, one accent, and a real figure — the item count — instead of
   the word "Loading".

   TOKENS: authored against the GLOBAL --app-* / --sp-* names only, never the
   canon's --accent / --card / --t-* aliases, because those exist solely under
   :root.books-v4. One stylesheet therefore serves the canon shell and the
   legacy books.css shell, in both themes, with no duplicate ruleset.

   MOTION: one authored moment. The rule-draw is a clip-path reveal, not a
   transform, so the hairline genuinely wipes rather than stretching from a
   squashed 1px. Everything is off under prefers-reduced-motion, where the
   register is simply shown fully ruled.
   ═══════════════════════════════════════════════════════════════════════════ */

/* The overlay covers the CANVAS, not the chrome: .topbar/.books-bar stays live
   above it so the tabs, search and back are reachable while the catalog loads.
   A loader that traps you on the page it is blocking is a worse bug than a slow
   page, and this is the one deviation from "cover everything". */
.cl-load {
  position: fixed;
  /* Overwritten from JS with the rail's measured height. The default here is a
     safety net for the frame before that runs, not a spacing decision — hence a
     raw px rather than a --sp-* token. */
  --cl-top: 64px;
  inset: var(--cl-top) 0 0 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: var(--sp-6);
  background: var(--app-bg);
  /* Flat. The overlay sits at the page's own background colour so it reads as
     the page not having arrived yet, rather than as a sheet laid over it. */
}

/* Lifting out. Opacity plus a short rise, exponential ease-out from an
   already-visible default. The real grid is painted underneath BEFORE this
   runs, so the eye reads one continuous surface resolving. */
.cl-load {
  opacity: 1;
  transform: translateY(0);
  transition: opacity .26s cubic-bezier(.2, .8, .25, 1),
              transform .26s cubic-bezier(.2, .8, .25, 1),
              visibility 0s linear .26s;
}
.cl-load.is-out {
  opacity: 0;
  transform: translateY(-4px);
  visibility: hidden;
  pointer-events: none;
}

.cl-inner {
  width: 100%;
  max-width: 520px;
}

/* ── The signature ─────────────────────────────────────────────────────────
   The real product mark, not an invented splash logo: same lockup as
   .books-brand in the rail, one step larger. Lowercase-compound is a brand
   rule, so it is set as written and never title-cased. */
.cl-mark {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2_5);
  font-size: 19px;
  letter-spacing: -.02em;
  color: var(--app-text);
  white-space: nowrap;
}
.cl-mark img { display: block; border-radius: 6px; }
.cl-mark b { font-weight: 800; }

/* ── The figure ────────────────────────────────────────────────────────────
   A real number beats the word "Loading". Inter, because Sora has no tabular
   numerals and a count that ticks up must not reflow its own digits. */
.cl-fig {
  margin: var(--sp-5) 0 var(--sp-1);
  font-family: 'Inter', system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  font-size: 26px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -.02em;
  color: var(--app-text);
}
/* Before the first page lands there is no count to show, so the line carries a
   plain verb instead and swaps to the figure the moment rows exist. Sora here
   because it is words, not a figure. */
.cl-fig.is-word {
  font-family: 'Sora', system-ui, -apple-system, sans-serif;
  font-size: 20px;
  font-weight: 600;
}
.cl-sub {
  margin: 0;
  font-size: 13px;
  /* --app-muted, the obvious pick, measures 4.26:1 on the light theme's page
     background — under the 4.5:1 floor for 13px copy. tokens.css already flags
     that token as low on light (see --placeholder). --app-text-2 clears AA in
     both themes, so the subtitle uses it rather than shipping one theme legible
     and the other not. */
  color: var(--app-text-2);
}

/* Announced, never seen. books-canon.css declares this class too, but ONLY
   under :root.books-v4 — so with the canon flag off the status text rendered
   as visible body copy in the middle of the loader. Declared here unscoped so
   the loader behaves the same in both shells. */
.cl-load .nd-sr-only {
  position: absolute; width: var(--sp-px); height: var(--sp-px);
  padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ── The register ──────────────────────────────────────────────────────────
   Seven rows in the catalog's own rhythm: a lead slot for the icon, the item
   name, a supplier, a price. Widths are varied rather than uniform — a column
   of equal blocks reads as a rendering fault, a ragged one reads as data on
   its way. */
.cl-book {
  margin-top: var(--sp-6);
  border-top: 1px solid var(--app-border);
}
.cl-row {
  position: relative;
  display: grid;
  grid-template-columns: 18px 1fr 64px 52px;
  align-items: center;
  gap: var(--sp-3);
  height: 38px;
}
/* The rule itself. Drawn by clip-path so it wipes in at full 1px height, and
   coloured by the same keyframe so the ice-blue head cools to a border behind
   itself. This element IS the animation; the row has no bottom border. */
.cl-row::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  background: var(--app-border);
  clip-path: inset(0 100% 0 0);
  animation: cl-rule 2.4s cubic-bezier(.2, .8, .25, 1) infinite;
  animation-delay: var(--cl-d, 0s);
}
.cl-sq,
.cl-bar {
  /* Derived from the page's own two ends rather than picked per theme:
     --app-sunken is only ~1.1:1 against --app-bg on dark, where the register
     all but vanished, while on light the same token reads fine. Mixing the
     text colour into the background gives the SAME perceptual lift in both
     themes from one declaration. */
  background: color-mix(in srgb, var(--app-text) 10%, var(--app-bg));
  opacity: 0;
  animation: cl-fill 2.4s cubic-bezier(.2, .8, .25, 1) infinite;
  animation-delay: var(--cl-d, 0s);
}
.cl-sq {
  width: 18px;
  height: 18px;
  border-radius: 4px;
}
.cl-bar {
  height: 9px;
  border-radius: 4px;
}
.cl-bar.cl-r { justify-self: end; }

/* The wave. 90ms apart, so the block reads as one surface being ruled rather
   than seven independent flashes — the same reasoning as the canon's staggered
   KPI skeleton bars. */
.cl-row:nth-child(1) { --cl-d: 0s; }
.cl-row:nth-child(2) { --cl-d: .09s; }
.cl-row:nth-child(3) { --cl-d: .18s; }
.cl-row:nth-child(4) { --cl-d: .27s; }
.cl-row:nth-child(5) { --cl-d: .36s; }
.cl-row:nth-child(6) { --cl-d: .45s; }
.cl-row:nth-child(7) { --cl-d: .54s; }

/* The cycle: drawn in ice-blue from the left, cooled to a hairline, held, then
   wiped away to the right. The exit clips from the LEADING edge, not back to
   the start — an earlier cut reset clip-path to `inset(0 100% 0 0)` at 100%,
   which made every rule blink out of existence at the loop point instead of
   leaving the way it arrived. The page keeps turning in one direction. */
@keyframes cl-rule {
  0%   { clip-path: inset(0 100% 0 0); background: var(--app-accent); }
  22%  { clip-path: inset(0 0 0 0);    background: var(--app-accent); }
  45%  { clip-path: inset(0 0 0 0);    background: var(--app-border); }
  82%  { clip-path: inset(0 0 0 0);    background: var(--app-border); }
  100% { clip-path: inset(0 0 0 100%); background: var(--app-border); }
}
@keyframes cl-fill {
  0%   { opacity: 0; transform: translateY(3px); }
  22%  { opacity: 1; transform: translateY(0); }
  82%  { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(0); }
}

/* ── Reduced motion ────────────────────────────────────────────────────────
   No draw, no wave, no loop: the register is simply shown, fully ruled. The
   count still updates, because that is data arriving rather than decoration.
   Matches the canon's own treatment, which stops the skeleton pulse and keeps
   every state transition. */
@media (prefers-reduced-motion: reduce) {
  .cl-row::after {
    animation: none;
    clip-path: none;
    background: var(--app-border);
  }
  .cl-sq,
  .cl-bar {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .cl-load { transition: opacity .01s linear, visibility 0s linear .01s; }
  .cl-load.is-out { transform: none; }
}

/* ── Phone ─────────────────────────────────────────────────────────────────
   The rail is shorter, so the overlay starts higher. The supplier column is the
   one that carries least at 380px, so it goes and the name takes the room. */
@media (max-width: 640px) {
  .cl-load { --cl-top: 56px; padding: var(--sp-5); }
  .cl-fig { font-size: 23px; }
  .cl-row { grid-template-columns: 18px 1fr 48px; }
  .cl-row .cl-sup { display: none; }
}
