/* ─── ZENTOR LOADER MARKS ─────────────────────────────────────────────────────
   The single source for the five loading marks. Linked from <head> so it is
   parsed before the bundle, which is what lets the boot script in index.html
   paint a mark on a refresh without waiting for React, and lets React reuse the
   exact same rules once it mounts.

   This file used to exist twice: once here (as JavaScript strings inside the
   boot script) and once in styles/theme.jsx, because theme.jsx injects its CSS
   from JavaScript and so has nothing to offer a script that runs before it.
   Two copies of five marks is a drift waiting to happen.

   Colours come in as tokens rather than being written twice, because the two
   callers know different things. The boot script only knows the dark flag it
   reads from localStorage; theme.jsx knows the real palette. Both set the
   --zl-* tokens below, and the defaults here cover the case where neither has
   run yet.

   Geometry, timing and the module pattern live only here. The markup that uses
   them is in components/ZentorLoader.jsx and, for the boot paint, in
   index.html. */

/* Boot defaults. These are what the mark uses on a refresh, before any
   palette exists, so every one has to read on a light AND a dark ground: a
   neutral grey rather than dark ink, and a blue that holds on both. theme.jsx
   injects its <style> later in the document and redefines these same tokens
   from the real palette, so the moment React mounts the mark picks up the
   proper colours and follows the dark-mode toggle. Later wins, same
   specificity: no inline styles, nothing to fight over. */
:root {
  --zl-accent:  #2E6BF0;
  --zl-green:   #16A34A;
  --zl-muted:   rgba(127, 140, 160, 0.30);
  --zl-hair:    rgba(127, 140, 160, 0.40);
  --zl-border:  rgba(127, 140, 160, 0.35);
}

/* QR ASSEMBLE — the front door. Modules land in a diagonal cascade, hold,
   then clear. The three finder squares are set by the markup, not here. */
@keyframes qrPop {
  0%   { transform: scale(0.2); opacity: 0; }
  14%  { transform: scale(1);   opacity: 1; }
  66%  { transform: scale(1);   opacity: 1; }
  80%  { transform: scale(0.2); opacity: 0; }
  100% { transform: scale(0.2); opacity: 0; }
}
.zqr { display: grid; grid-template-columns: repeat(9, 1fr); gap: 3px; width: 116px; height: 116px; }
.zqr i {
  background: var(--zl-muted);
  border-radius: 2px;
  animation: qrPop 2.6s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
.zqr i.on { background: var(--zl-accent); }

/* REGISTER ROLL — registrar and admin. The paper register Zentor replaces. */
@keyframes zregRow {
  0%   { opacity: 0; transform: translateX(-9px); }
  12%  { opacity: 1; transform: translateX(0); }
  82%  { opacity: 1; transform: translateX(0); }
  94%  { opacity: 0; transform: translateX(0); }
  100% { opacity: 0; transform: translateX(-9px); }
}
@keyframes zregTick {
  0%, 22%   { stroke-dashoffset: 14; }
  38%, 88%  { stroke-dashoffset: 0; }
  96%, 100% { stroke-dashoffset: 14; }
}
.zreg {
  width: 190px; border: 1px solid var(--zl-border); border-radius: 8px;
  padding: 12px 13px; display: flex; flex-direction: column; gap: 9px;
}
.zreg .rule { height: 7px; border-radius: 3px; width: 54%; background: var(--zl-accent); opacity: 0.25; }
.zreg .row { display: flex; align-items: center; gap: 9px; animation: zregRow 3s ease-out infinite; }
.zreg .nm { height: 7px; border-radius: 3px; flex: 1; background: var(--zl-muted); }
.zreg .bx {
  width: 14px; height: 14px; border-radius: 4px; flex-shrink: 0;
  border: 1.5px solid var(--zl-hair); display: grid; place-items: center;
}
.zreg .bx svg { width: 9px; height: 9px; display: block; }
.zreg .bx path {
  fill: none; stroke: var(--zl-green); stroke-width: 2.6;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 14; stroke-dashoffset: 14;
  animation: zregTick 3s ease-out infinite;
}

/* SCAN SWEEP — students. The viewfinder they already stare at. */
@keyframes zscanBeam {
  0%   { top: 12px;  opacity: 0; }
  12%  { opacity: 1; }
  50%  { top: 112px; opacity: 1; }
  88%  { opacity: 1; }
  100% { top: 12px;  opacity: 0; }
}
.zscan { position: relative; width: 128px; height: 128px; }
.zscan .bk { position: absolute; width: 26px; height: 26px; border: 2.5px solid var(--zl-accent); border-radius: 3px; }
.zscan .tl { top: 0; left: 0;  border-right: 0; border-bottom: 0; border-top-left-radius: 8px; }
.zscan .tr { top: 0; right: 0; border-left: 0;  border-bottom: 0; border-top-right-radius: 8px; }
.zscan .bl { bottom: 0; left: 0;  border-right: 0; border-top: 0; border-bottom-left-radius: 8px; }
.zscan .br { bottom: 0; right: 0; border-left: 0;  border-top: 0; border-bottom-right-radius: 8px; }
.zscan .fd { position: absolute; inset: 17px; display: grid; grid-template-columns: repeat(6, 1fr); gap: 4px; opacity: 0.4; }
.zscan .fd i { background: var(--zl-muted); border-radius: 1.5px; }
.zscan .bm {
  position: absolute; left: 8px; right: 8px; height: 2px; border-radius: 2px;
  background: var(--zl-accent); box-shadow: 0 0 12px 2px var(--zl-accent);
  animation: zscanBeam 2.1s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

/* PRESENCE RING — parents. The percentage they open the app to check. */
@keyframes zringArc {
  0%   { stroke-dashoffset: 314; }
  45%  { stroke-dashoffset: 66; }
  70%  { stroke-dashoffset: 66; }
  100% { stroke-dashoffset: 314; }
}
.zring { transform: rotate(-90deg); }
.zring circle { fill: none; stroke-width: 9; stroke-linecap: round; }
.zring .tk { stroke: var(--zl-muted); }
.zring .ac { stroke: var(--zl-accent); stroke-dasharray: 314; animation: zringArc 2.4s cubic-bezier(0.65, 0, 0.35, 1) infinite; }

/* ROLL CALL — lecturers. A hall checking in. */
@keyframes zrollIn {
  0%, 8%    { background: var(--zl-muted); transform: scale(1); }
  16%       { background: var(--zl-green); transform: scale(1.42); }
  24%, 74%  { background: var(--zl-green); transform: scale(1); }
  88%, 100% { background: var(--zl-muted); transform: scale(1); }
}
.zroll { display: grid; grid-template-columns: repeat(8, 1fr); gap: 9px; width: 176px; }
.zroll i { aspect-ratio: 1; border-radius: 50%; background: var(--zl-muted); animation: zrollIn 2.8s ease-in-out infinite; }

/* Every mark resolves to a still, COMPLETE frame. Not a blank one: qrPop and
   zscanBeam both end on an empty keyframe, so simply letting an animation run
   to its end (what the usual blanket reduced-motion reset does) would hide the
   loader from exactly the readers who asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .zqr i        { animation: none; opacity: 1; transform: none; }
  .zreg .row    { animation: none; opacity: 1; transform: none; }
  .zreg .bx path { animation: none; stroke-dashoffset: 0; }
  .zscan .bm    { animation: none; top: 62px; }
  .zring .ac    { animation: none; stroke-dashoffset: 66; }
  .zroll i      { animation: none; }
}
