/* ===========================================================================
 * A-Kiosk — the look.
 *
 * This is the design system. It sits on the tokens in index.html and owns
 * everything the product feels like: depth, weight, rhythm, motion.
 *
 * The palette comes from the logo rather than being invented: gold on
 * near-black, one accent temperature, everything else a neutral in service
 * of it.
 *
 * Three rules run through all of it:
 *   1. Depth is light, not lines. Surfaces lift by brightening and casting
 *      shadow, the way real things do. Borders are a last resort.
 *   2. Motion carries meaning — something moves because it arrived, because
 *      it can be pressed, or because it is alive. Never as decoration.
 *   3. Anyone who has asked their system to stop animating gets a still
 *      interface. Motion that triggers migraine is not a style choice.
 * =========================================================================== */

:root {
  --g-1: color-mix(in srgb, var(--gold) 12%, transparent);
  --g-2: color-mix(in srgb, var(--gold) 26%, transparent);
  --g-3: color-mix(in srgb, var(--gold) 48%, transparent);
  --g-glow: color-mix(in srgb, var(--gold) 34%, transparent);

  --lift-1: 0 1px 2px rgba(0,0,0,.16);
  --lift-2: 0 4px 14px -6px rgba(0,0,0,.28);
  --lift-3: 0 14px 34px -16px rgba(0,0,0,.42);
  --lift-gold: 0 12px 30px -16px var(--g-glow);

  --r-sm: 9px; --r-md: 14px; --r-lg: 20px; --r-pill: 999px;
  --ease: cubic-bezier(.22,.9,.3,1);
  --ease-out: cubic-bezier(.16,1,.3,1);
}
html[data-theme="dark"] {
  --lift-1: 0 1px 2px rgba(0,0,0,.5);
  --lift-2: 0 6px 18px -8px rgba(0,0,0,.72);
  --lift-3: 0 18px 40px -18px rgba(0,0,0,.85);
}

/* A faint gold horizon, so the page is never flat. */
body {
  background:
    radial-gradient(1100px 520px at 50% -8%, var(--g-1), transparent 62%),
    var(--p-bg);
  background-attachment: fixed;
}

/* ===========================================================================
 * KEYFRAMES
 * =========================================================================== */
@keyframes ak-rise  { from { opacity:0; transform:translateY(12px) } to { opacity:1; transform:none } }
@keyframes ak-fade  { from { opacity:0 } to { opacity:1 } }
@keyframes ak-pop {
  0%   { opacity:0; transform:scale(.955) translateY(10px) }
  62%  { transform:scale(1.014) translateY(0) }
  100% { opacity:1; transform:scale(1) }
}
@keyframes ak-sheen { from { background-position:-190% 0 } to { background-position:190% 0 } }
@keyframes ak-land {
  0%   { background: var(--g-3); box-shadow: inset 3px 0 0 var(--gold) }
  100% { background: transparent; box-shadow: inset 3px 0 0 transparent }
}
@keyframes ak-breathe { 0%,100% { transform:scale(1); opacity:.86 } 50% { transform:scale(1.09); opacity:1 } }
@keyframes ak-ring {
  0%   { box-shadow:0 0 0 0 color-mix(in srgb, var(--ok) 60%, transparent) }
  70%  { box-shadow:0 0 0 10px transparent }
  100% { box-shadow:0 0 0 0 transparent }
}
@keyframes ak-spin  { to { transform:rotate(360deg) } }
@keyframes ak-count { from { opacity:0; transform:translateY(8px) } to { opacity:1; transform:none } }

/* Whorl breathes, drifts and rotates slightly off-rhythm, so he reads as a
   character rather than an image being bounced up and down. */
@keyframes ak-whorl-idle {
  0%,100% { transform: translateY(0)    rotate(0deg)    scale(1) }
  28%     { transform: translateY(-8px) rotate(-2deg)   scale(1.015) }
  55%     { transform: translateY(-3px) rotate(1.5deg)  scale(1) }
  78%     { transform: translateY(-7px) rotate(-1deg)   scale(1.01) }
}
@keyframes ak-whorl-wave {
  0%,100% { transform: translateY(0)     rotate(-3deg) }
  20%     { transform: translateY(-11px) rotate(4deg) }
  40%     { transform: translateY(-4px)  rotate(-4deg) }
  60%     { transform: translateY(-10px) rotate(3.5deg) }
  80%     { transform: translateY(-2px)  rotate(-2deg) }
}
@keyframes ak-whorl-glow {
  0%,100% { opacity:.34; transform:translateX(-50%) scale(1) }
  50%     { opacity:.6;  transform:translateX(-50%) scale(1.16) }
}

@keyframes ak-flicker {
  0%,100% { transform:scaleY(1)    translateY(0)    skewX(0deg);    opacity:1 }
  22%     { transform:scaleY(1.09) translateY(-2px) skewX(1.6deg);  opacity:.9 }
  48%     { transform:scaleY(.94)  translateY(1px)  skewX(-1.2deg); opacity:1 }
  74%     { transform:scaleY(1.05) translateY(-1px) skewX(.8deg);   opacity:.95 }
}
@keyframes ak-ember {
  0%   { opacity:0;  transform:translate(0,0) scale(.45) }
  22%  { opacity:.95 }
  100% { opacity:0;  transform:translate(var(--dx,4px),-30px) scale(.1) }
}

/* ===========================================================================
 * CHROME
 * =========================================================================== */
.apphead, .tabs {
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  border-bottom: 1px solid color-mix(in srgb, var(--p-line) 70%, transparent);
}
.apphead { background: color-mix(in srgb, var(--p-sf) 74%, transparent); box-shadow: var(--lift-1) }
.tabs    { background: color-mix(in srgb, var(--p-sf) 78%, transparent) }

.applogo { transition: transform .3s var(--ease) }
.applogo:hover { transform: scale(1.04) }

.tab { position: relative; border-bottom: 0 !important; transition: color .2s }
.tab::after {
  content:''; position:absolute; left:10px; right:10px; bottom:0; height:2px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
  border-radius:2px; transform: scaleX(0);
  transition: transform .3s var(--ease-out);
}
.tab.on::after    { transform: scaleX(1) }
.tab:hover::after { transform: scaleX(.5) }
.tab.on { color: var(--p-ink); text-shadow: 0 0 18px var(--g-2) }

/* ===========================================================================
 * SURFACES
 * =========================================================================== */
.card {
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--p-sf), var(--p-sf2));
  border: 1px solid color-mix(in srgb, var(--p-line) 80%, transparent);
  box-shadow: var(--lift-1);
  animation: ak-pop .42s var(--ease) both;
  transition: transform .24s var(--ease), box-shadow .3s, border-color .24s;
  will-change: transform;
}
.grid > *:nth-child(1){animation-delay:.03s} .grid > *:nth-child(2){animation-delay:.07s}
.grid > *:nth-child(3){animation-delay:.11s} .grid > *:nth-child(4){animation-delay:.15s}
.grid > *:nth-child(5){animation-delay:.19s} .grid > *:nth-child(6){animation-delay:.23s}

button.card, .card.click { cursor: pointer }
button.card:hover, .card.click:hover {
  transform: translateY(-4px);
  border-color: var(--g-3);
  box-shadow: var(--lift-3), var(--lift-gold);
}
button.card:active { transform: translateY(-1px) scale(.994) }

/* Metrics are the loudest thing on a screen, so they carry the gold edge. */
.grid.g3 > .card, .metric { position: relative; overflow: hidden }
.grid.g3 > .card::before, .metric::before {
  content:''; position:absolute; left:0; top:0; bottom:0; width:3px;
  background: linear-gradient(180deg, var(--gold2), var(--gold)); opacity:.85;
}
.num { animation: ak-count .45s var(--ease) both; text-shadow: 0 0 30px var(--g-2) }

/* ===========================================================================
 * CONTROLS
 * =========================================================================== */
.btn {
  position: relative; overflow: hidden; border-radius: var(--r-sm);
  box-shadow: var(--lift-1);
  transition: transform .16s var(--ease), box-shadow .22s, background .2s,
              border-color .2s, color .2s;
}
.btn:hover:not(:disabled)  { transform: translateY(-2px); box-shadow: var(--lift-2) }
.btn:active:not(:disabled) { transform: translateY(0) scale(.97) }

.btn.pri {
  background: linear-gradient(140deg, var(--gold2), var(--gold));
  border-color: transparent; color: #1A1206;
  box-shadow: var(--lift-2), 0 8px 22px -12px var(--g-glow);
}
.btn.pri:hover:not(:disabled) { box-shadow: var(--lift-3), 0 14px 30px -12px var(--g-glow) }
.btn.pri::after {
  content:''; position:absolute; inset:0; pointer-events:none;
  background: linear-gradient(100deg, transparent 32%, rgba(255,255,255,.5) 50%, transparent 68%);
  background-size:220% 100%; background-position:-190% 0;
}
.btn.pri:hover:not(:disabled)::after { animation: ak-sheen .8s ease-out }

.input { border-radius: var(--r-sm); transition: border-color .2s, box-shadow .25s, background .2s }
.input:focus {
  border-color: var(--gold);
  box-shadow: 0 0 0 3px var(--g-1), inset 0 1px 2px rgba(0,0,0,.1);
  background: var(--p-sf);
}
.field > .plbl { transition: color .2s }
.field:focus-within > .plbl { color: var(--gold-ink) }

.iconbtn { border-radius: var(--r-sm);
           transition: transform .2s var(--ease), background .2s, color .2s }
.iconbtn:hover  { transform: translateY(-2px) }
.iconbtn:active { transform: scale(.94) }
.iconbtn.theming{ animation: ak-spin .55s var(--ease) }
.iconbtn .badge { animation: ak-breathe 2.3s ease-in-out infinite;
                  box-shadow: 0 0 12px color-mix(in srgb, var(--rose) 60%, transparent) }

.seg { border-radius: var(--r-sm) }
.seg button { border-radius: 7px; transition: all .24s var(--ease) }
.seg button.on { background: var(--p-sf); box-shadow: var(--lift-2);
                 color: var(--p-ink); transform: translateY(-1px) }

/* ===========================================================================
 * PILLS
 * =========================================================================== */
.pill { border-radius: var(--r-pill); transition: transform .18s }
.pill:hover { transform: translateY(-1px) }
.pill.ok   { background: color-mix(in srgb, var(--ok) 12%, transparent) }
.pill.bad  { background: color-mix(in srgb, var(--rose) 12%, transparent) }
.pill.warn { background: color-mix(in srgb, var(--amber) 12%, transparent) }
.pill.gold { background: var(--g-1) }
.pill.live { position: relative; padding-left: 18px }
.pill.live::before {
  content:''; position:absolute; left:7px; top:50%; margin-top:-3px;
  width:6px; height:6px; border-radius:50%; background: var(--ok);
  animation: ak-ring 1.9s ease-out infinite;
}

/* ===========================================================================
 * TABLES
 * =========================================================================== */
table.tbl th {
  position: sticky; top: 0; z-index: 1;
  background: color-mix(in srgb, var(--p-sf) 94%, transparent);
  backdrop-filter: blur(8px);
}
table.tbl tbody tr { animation: ak-fade .34s ease-out both }
table.tbl tbody tr:nth-child(1){animation-delay:.01s} table.tbl tbody tr:nth-child(2){animation-delay:.035s}
table.tbl tbody tr:nth-child(3){animation-delay:.06s} table.tbl tbody tr:nth-child(4){animation-delay:.085s}
table.tbl tbody tr:nth-child(5){animation-delay:.11s} table.tbl tbody tr:nth-child(6){animation-delay:.135s}
table.tbl tbody tr:nth-child(7){animation-delay:.16s} table.tbl tbody tr:nth-child(8){animation-delay:.185s}
table.tbl tr.click td { transition: background .18s, box-shadow .18s }
table.tbl tr.click:hover td { background: var(--g-1) }
table.tbl tr.click:hover td:first-child { box-shadow: inset 3px 0 0 var(--gold) }

/* A tap that has just landed from a kiosk. */
tr.ak-new td { animation: ak-land 2s var(--ease-out) both }

/* ===========================================================================
 * WHORL
 * =========================================================================== */
.whorl-wrap { position: relative; display: inline-block }
.whorl-wrap::after {
  content:''; position:absolute; left:50%; bottom:-6%;
  width:76%; height:22%; border-radius:50%;
  background: radial-gradient(ellipse, var(--g-glow), transparent 70%);
  transform: translateX(-50%);
  animation: ak-whorl-glow 3.6s ease-in-out infinite;
  pointer-events:none;
}
.whorl {
  animation: ak-whorl-idle 4.2s ease-in-out infinite;
  transform-origin: 50% 92%;
  filter: drop-shadow(0 10px 18px rgba(0,0,0,.28));
}
.whorl.wave  { animation: ak-whorl-wave 2.6s ease-in-out infinite }
.whorl:hover { animation-duration: 1.1s }   /* he perks up when noticed */

/* ===========================================================================
 * FLAME
 *
 * Durations are deliberately non-harmonic — 2.6 / 1.9 / 1.35 / 0.85 — so the
 * layers never realign and the loop stays invisible. Each leans by a different
 * amount, which is what makes it look like fire rather than a pulsing shape.
 * =========================================================================== */
@keyframes fl-lick-1 {
  0%,100% { transform: scaleY(1)    scaleX(1)    translateX(0)     skewX(0deg) }
  25%     { transform: scaleY(1.11) scaleX(.95)  translateX(-2.2px) skewX(-3.5deg) }
  50%     { transform: scaleY(.93)  scaleX(1.05) translateX(1.4px)  skewX(2deg) }
  75%     { transform: scaleY(1.07) scaleX(.97)  translateX(2px)    skewX(3deg) }
}
@keyframes fl-lick-2 {
  0%,100% { transform: scaleY(1)    translateX(0)    skewX(0deg) }
  30%     { transform: scaleY(1.14) translateX(1.8px) skewX(2.8deg) }
  60%     { transform: scaleY(.9)   translateX(-1.6px) skewX(-2.4deg) }
  85%     { transform: scaleY(1.06) translateX(.8px)  skewX(1.4deg) }
}
@keyframes fl-lick-3 {
  0%,100% { transform: scaleY(1)    translateX(0)     skewX(0deg);  opacity:1 }
  22%     { transform: scaleY(1.18) translateX(-1.2px) skewX(-2deg); opacity:.94 }
  55%     { transform: scaleY(.88)  translateX(1.4px)  skewX(2.6deg);opacity:1 }
  80%     { transform: scaleY(1.1)  translateX(.6px)   skewX(1deg);  opacity:.97 }
}
@keyframes fl-heart {
  0%,100% { transform: scaleY(1)    scaleX(1);   opacity:.92 }
  35%     { transform: scaleY(1.22) scaleX(.9);  opacity:1 }
  70%     { transform: scaleY(.85)  scaleX(1.08);opacity:.85 }
}
@keyframes fl-haze {
  0%,100% { opacity:.5;  transform: scale(1) }
  50%     { opacity:.78; transform: scale(1.1) }
}
@keyframes fl-spark {
  0%   { opacity:0;   transform: translate(0,0) scale(.4) }
  18%  { opacity:1 }
  100% { opacity:0;   transform: translate(var(--dx,4px), -34px) scale(.08) }
}

.flame-box {
  position: relative; display: grid; place-items: end center;
  /* the glow under it grows with the streak */
  filter: drop-shadow(0 0 calc(6px + var(--heat,0) * 20px)
          color-mix(in srgb, var(--flame2) calc(28% + var(--heat,0) * 42%), transparent));
}
.flame { overflow: visible; transform-origin: 50% 100% }
.flame .fl-l1 { animation: fl-lick-1 2.6s  ease-in-out infinite;       transform-origin:50% 100% }
.flame .fl-l2 { animation: fl-lick-2 1.9s  ease-in-out .18s infinite;  transform-origin:50% 100% }
.flame .fl-l3 { animation: fl-lick-3 1.35s ease-in-out .34s infinite;  transform-origin:50% 100% }
.flame .fl-l4 { animation: fl-heart  .85s  ease-in-out .12s infinite;  transform-origin:50% 100% }
.flame .fl-haze { animation: fl-haze 2.2s ease-in-out infinite; transform-origin:50% 72% }
.flame .fl-sp {
  animation: fl-spark 2.1s ease-out infinite;
  animation-delay: var(--d, 0s);
}
.flame .sp1 { animation-duration: 2.6s } .flame .sp2 { animation-duration: 1.7s }
.flame .sp3 { animation-duration: 2.95s }

.flame-box.cold          { filter: grayscale(1) opacity(.3) }
.flame-box.cold *        { animation: none !important }

/* ---------------------------------------------------------------------------
 * THE MASTER FLAME — the top tier, and the only one that is hard to reach.
 *
 * It is not the same flame in different paint. The second heart (fl-l5) beats
 * against the white one on a different period, the halo breathes slowly around
 * the whole thing, and the drop shadow is thrown further and tinted by the
 * tier's THIRD colour rather than its second — so it reads as fire with
 * something else burning inside it.
 * ------------------------------------------------------------------------- */
.flame-master .fl-l5   { animation: fl-master-heart 1.15s ease-in-out .3s infinite;
                         transform-origin: 50% 100% }
.flame-master .fl-halo { animation: fl-master-halo 3.4s ease-in-out infinite;
                         transform-origin: 50% 68% }
.flame-master.lit {
  filter: drop-shadow(0 0 calc(10px + var(--heat,0) * 26px)
          color-mix(in srgb, var(--flame3) 62%, transparent))
          drop-shadow(0 0 calc(4px + var(--heat,0) * 10px)
          color-mix(in srgb, var(--flame1) 55%, transparent));
}
.flame-master .fl-sp { animation-duration: 1.5s }

@keyframes fl-master-heart {
  0%,100% { transform: scaleY(1)    scaleX(1)    translateY(0) }
  40%     { transform: scaleY(1.22) scaleX(.86)  translateY(-2px) }
  70%     { transform: scaleY(.92)  scaleX(1.12) translateY(.6px) }
}
@keyframes fl-master-halo {
  0%,100% { transform: scale(1);    opacity: .30 }
  50%     { transform: scale(1.09); opacity: .62 }
}

@media (prefers-reduced-motion: reduce) {
  .flame-master .fl-l5, .flame-master .fl-halo { animation: none }
}

/* ===========================================================================
 * MODAL, LOADING, SIGN-IN
 * =========================================================================== */
.ak-scrim { animation: ak-fade .2s ease-out both }
.ak-scrim > .card {
  animation: ak-pop .32s var(--ease) both;
  border-radius: var(--r-lg); box-shadow: var(--lift-3);
}
.empty .plbl {
  background: linear-gradient(90deg, var(--p-ink3) 25%, var(--gold) 50%, var(--p-ink3) 75%);
  background-size: 220% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: ak-sheen 1.6s linear infinite;
}
.body > .wrap { animation: ak-rise .38s var(--ease) both }
.crumbs a { transition: color .18s }
.crumbs a:hover { color: var(--gold-ink) }

.authwrap::before {
  content:''; position:fixed; inset:0; pointer-events:none;
  background: radial-gradient(760px 420px at 50% 12%, var(--g-1), transparent 68%);
}
.authbox { border-radius: var(--r-lg); box-shadow: var(--lift-3);
           animation: ak-pop .5s var(--ease) both .08s }
.authcard > div:first-child { animation: ak-rise .55s var(--ease) both }

/* ===========================================================================
 * PHONE — the primary case, not a fallback.
 * =========================================================================== */
@media (max-width: 700px) {
  body { font-size: 15px }
  .wrap { max-width: 100% }
  .body { padding: 13px 12px 34px }

  .apphead { padding: 9px 11px; gap: 7px }
  .who b { font-size: 12.5px; max-width: 32vw;
           overflow:hidden; text-overflow:ellipsis; white-space:nowrap }

  .btn     { padding: 12px 16px; font-size: 14px }
  .btn.sm  { padding: 9px 13px;  font-size: 13px }
  .iconbtn { width: 38px; height: 38px }
  .input   { padding: 12px; font-size: 16px }   /* 16px keeps iOS from zooming */

  .tabs { padding: 0 6px; scroll-snap-type: x proximity }
  .tab  { padding: 13px 12px; font-size: 13.5px; scroll-snap-align: center }

  .grid.g2, .grid.g3 { grid-template-columns: repeat(auto-fit, minmax(142px,1fr)) }
  .card { border-radius: 12px }

  table.tbl { font-size: 13px }
  table.tbl th, table.tbl td { padding: 11px 8px }

  .authwrap { padding: 16px; align-items: flex-start; padding-top: 30px }
  .authbox  { padding: 20px }
}
@media (max-width: 380px) {
  .grid.g2, .grid.g3 { grid-template-columns: 1fr 1fr }
  .num { font-size: 21px !important }
}
@media (min-width: 1100px) { .body { padding: 26px } }

/* ===========================================================================
 * Respect the system setting.
 * =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ===========================================================================
 * PAGE FURNITURE — hero, sections, and the layout each screen is built on.
 * =========================================================================== */
.hero { margin-bottom: 20px; display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap;
        animation: ak-rise .42s var(--ease) both; }
.hero-txt { min-width: 0; flex: 1 }
.hero-eyebrow { margin-bottom: 6px; color: var(--gold-ink); opacity: .9 }
.hero-title {
  font-size: clamp(21px, 3.4vw, 30px); line-height: 1.08; letter-spacing: -.03em;
  background: linear-gradient(120deg, var(--p-ink) 30%, var(--gold-ink));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero-sub { margin-top: 7px; font-size: 13.5px; color: var(--p-ink2); max-width: 62ch; line-height: 1.55 }
.hero-right { display: flex; gap: 8px; align-items: center }

.sect { margin-top: 24px; animation: ak-rise .42s var(--ease) both }
.sect-head { display: flex; align-items: flex-end; gap: 10px; margin-bottom: 11px }
.sect-title { font-size: 15px; letter-spacing: -.015em }

/* --- the hero row: ring beside metrics ------------------------------------ */
.hero-grid { display: grid; grid-template-columns: minmax(230px, .82fr) 2fr; gap: 13px; align-items: stretch }
.hero-ring { display: grid; place-items: center; padding: 20px }
.hero-metrics { display: grid; grid-template-columns: 1fr 1fr; gap: 13px }

.ring-wrap { position: relative; display: inline-grid; place-items: center }
.ring-mid  { position: absolute; inset: 0; display: grid; place-content: center;
             text-align: center; padding: 8px }
.ring circle:last-of-type { will-change: stroke-dashoffset }

/* --- metric card ---------------------------------------------------------- */
.metric-card {
  position: relative; overflow: hidden; padding: 15px 16px;
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--p-sf), var(--p-sf2));
  border: 1px solid color-mix(in srgb, var(--p-line) 80%, transparent);
  box-shadow: var(--lift-1);
  animation: ak-pop .42s var(--ease) both;
  transition: transform .24s var(--ease), box-shadow .3s, border-color .24s;
}
.metric-card::before {
  content:''; position:absolute; left:0; top:0; bottom:0; width:3px;
  background: linear-gradient(180deg, var(--gold2), var(--gold)); opacity:.8;
}
.metric-card:hover { transform: translateY(-3px); box-shadow: var(--lift-2), var(--lift-gold);
                     border-color: var(--g-3) }
.metric-top { display:flex; align-items:center; gap:8px }
.metric-icon{ margin-left:auto; opacity:.5; font-size:14px }
.metric-val { font-size: 30px; line-height: 1.05; margin-top: 7px }
.metric-sub { font-size: 11.5px; margin-top: 4px }

/* --- ranked bars ---------------------------------------------------------- */
.bars { display: grid; gap: 3px }
.bar-row {
  display: grid; grid-template-columns: 84px 1fr 52px; align-items: center; gap: 11px;
  padding: 8px 6px; border-radius: 9px; text-align: left; width: 100%;
  transition: background .18s;
}
.bar-row:hover { background: var(--g-1) }
.bar-label { font-size: 12.5px; font-weight: 600; white-space: nowrap;
             overflow: hidden; text-overflow: ellipsis }
.bar-track { height: 9px; border-radius: 9px; background: var(--p-line); overflow: hidden }
.bar-fill  { display: block; height: 100%; border-radius: 9px;
             transform-origin: left; animation: ak-bar .85s var(--ease-out) both }
.bar-val   { font-size: 13px; text-align: right }
@keyframes ak-bar { from { transform: scaleX(0) } to { transform: scaleX(1) } }

/* --- avatars + people ----------------------------------------------------- */
.avatar { display: inline-grid; place-items: center; border-radius: 50%;
          color: #fff; font-weight: 700; letter-spacing: .02em; flex: none;
          font-family: var(--disp) }
.people-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(258px,1fr)); gap: 11px }
.person-card { display: flex; align-items: center; gap: 11px; padding: 12px 13px }
.person-name { font-weight: 600; font-size: 13.5px; white-space: nowrap;
               overflow: hidden; text-overflow: ellipsis }

/* --- sparkline ------------------------------------------------------------ */
.spark { display: block }
.spark-line { stroke-dasharray: 260; stroke-dashoffset: 260;
              animation: ak-draw 1.1s var(--ease-out) forwards }
@keyframes ak-draw { to { stroke-dashoffset: 0 } }

/* --- thin progress track -------------------------------------------------- */
.mini-track { height: 6px; border-radius: 6px; background: var(--p-line); overflow: hidden }
.mini-fill  { display: block; height: 100%; border-radius: 6px; background: var(--gold);
              transform-origin: left; animation: ak-bar .9s var(--ease-out) both }

/* --- empty states --------------------------------------------------------- */
.blank { text-align: center; padding: 46px 22px; animation: ak-pop .45s var(--ease) both }
.blank.sm { padding: 30px 18px }
.blank-whorl { display: inline-block; margin-bottom: 12px }
.blank-title { font-size: 16px; letter-spacing: -.015em }
.blank-body  { margin-top: 7px; font-size: 13px; color: var(--p-ink2);
               max-width: 44ch; margin-left: auto; margin-right: auto; line-height: 1.6 }

/* --- student dashboard ---------------------------------------------------- */
.stu-hero { display: grid; grid-template-columns: 1.15fr .9fr 1fr; gap: 13px; margin-bottom: 4px }
.stu-streak { display: flex; align-items: center; gap: 15px; padding: 18px }
.stu-flame  { flex: none }
.stu-streak-n { font-size: 40px; line-height: 1; display: flex; align-items: baseline; gap: 7px }
.stu-streak-u { font-size: 13px; font-weight: 500; color: var(--p-ink3);
                font-family: var(--body); letter-spacing: 0 }
.stu-ring  { display: grid; place-items: center; padding: 16px }
.stu-marks { padding: 18px }
.stu-of    { font-size: 17px; color: var(--p-ink3); font-weight: 500 }

/* --- course cards --------------------------------------------------------- */
.course-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(252px,1fr)); gap: 12px }
.course-card { text-align: left; padding: 15px 16px; display: grid; gap: 9px }
.course-top  { display: flex; align-items: center; gap: 9px }
.course-code { font-family: var(--disp); font-weight: 700; font-size: 15.5px;
               letter-spacing: -.02em; margin-right: auto }
.course-title{ font-size: 12.5px; color: var(--p-ink2); white-space: nowrap;
               overflow: hidden; text-overflow: ellipsis; margin-top: -4px }

/* Who takes the course, under its title. Ellipsised rather than wrapped: a
   card in a grid must keep one height whether a course has one lecturer or
   three, or the whole row of cards jumps. */
.course-who{ font-size: 11.5px; color: var(--p-ink3, var(--p-ink2)); margin-top: 3px;
             white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.course-who-none{ opacity: .65; font-style: italic; }
.course-foot { display: flex; align-items: baseline; gap: 8px;
               font-size: 11.5px; color: var(--p-ink3) }
.course-foot span:last-child { margin-left: auto }

/* --- phone ---------------------------------------------------------------- */
@media (max-width: 860px) {
  .hero-grid { grid-template-columns: 1fr }
  .stu-hero  { grid-template-columns: 1fr }
}
@media (max-width: 700px) {
  .hero { margin-bottom: 15px }
  .hero-title { font-size: 23px }
  .hero-sub { font-size: 13px }
  .sect { margin-top: 19px }
  .hero-metrics { grid-template-columns: 1fr 1fr; gap: 10px }
  .metric-card { padding: 13px 14px }
  .metric-val  { font-size: 25px }
  .bar-row { grid-template-columns: 66px 1fr 44px; gap: 9px }
  .people-grid, .course-grid { grid-template-columns: 1fr }
  .stu-streak { padding: 15px; gap: 12px }
  .stu-streak-n { font-size: 33px }
  .blank { padding: 34px 16px }
}

/* Courses you did not choose and cannot drop read differently from ones you
   act on: no hover lift, muted rail, so the eye skips straight to decisions. */
.auto-card { opacity: .92 }
.auto-card::before { content:''; position:absolute; left:0; top:0; bottom:0; width:3px;
                     background: var(--ok); opacity:.55 }
.course-card { position: relative; overflow: hidden }
.carry-card::before { content:''; position:absolute; left:0; top:0; bottom:0; width:3px;
                      background: var(--amber) }

/* ===========================================================================
 * LEADERBOARD
 * =========================================================================== */
.scope-bar { display:flex; gap:5px; padding:4px; margin-bottom:15px; border-radius:12px;
             background: var(--p-sf2); border:1px solid var(--p-line);
             overflow-x:auto; scrollbar-width:none }
.scope-bar::-webkit-scrollbar { display:none }
.scope-btn { flex:1; min-width:92px; padding:9px 12px; border-radius:9px; font-size:12.5px;
             font-weight:550; color:var(--p-ink3); white-space:nowrap;
             transition: all .24s var(--ease) }
.scope-btn:hover { color: var(--p-ink) }
.scope-btn.on { background: var(--p-sf); color: var(--p-ink);
                box-shadow: var(--lift-2); transform: translateY(-1px) }

/* --- podium: second, first, third — first raised, as a podium actually is -- */
/* The podium's flame FLOATS above each plinth, so it needs room above the row
   and it must be out of flow. It used to be `height:0` inside the pod's grid —
   but <Flame size=62> renders 62 x 83px, so eighty-three pixels of fire were
   dropped into a zero-height track and landed straight on top of the avatar,
   the name and the number. That is what "mushed together" was: not a spacing
   value to nudge, a box with no height containing something tall.
   Absolute + a top pad on the podium is the fix; the pods keep their own
   padding for the avatar, and nothing overlaps anything. */
.podium { display:grid; grid-template-columns:1fr 1.16fr 1fr; gap:11px; align-items:end;
          margin-bottom:16px; padding-top:60px }
.pod { position:relative; display:grid; justify-items:center; gap:5px; padding:26px 10px 0;
       border-radius:var(--r-md) var(--r-md) 0 0; text-align:center;
       background: linear-gradient(180deg, var(--p-sf), var(--p-sf2));
       border:1px solid var(--p-line); border-bottom:0;
       animation: ak-pop .5s var(--ease) both }
.pod-1 { animation-delay:.1s } .pod-2 { animation-delay:.02s } .pod-3 { animation-delay:.18s }
.pod-1 { border-color: var(--g-3); box-shadow: var(--lift-2), 0 0 34px -14px var(--g-glow) }
.pod-flame { position:absolute; left:50%; top:0; transform:translate(-50%,-70%);
             line-height:0; pointer-events:none }
.pod-name  { font-size:12.5px; font-weight:600; margin-top:4px; max-width:100%;
             overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
/* SAME SIZE on all three. First place is distinguished by colour, by its
   border glow, by a bigger flame and by standing highest — not by being set in
   a larger face. Every pixel of difference in the CONTENT is a pixel added to
   gold's plinth, because the pods are bottom-aligned, and that is what made the
   steps 38px then 8px instead of even. */
.pod-streak{ font-size:28px; line-height:1 }
.pod-1 .pod-streak { color: var(--gold-ink) }
.pod-base  { width:100%; margin-top:9px; display:grid; place-items:center;
             border-radius:3px 3px 0 0; font-family:var(--disp); font-weight:700 }
/* The plinths carry ALL the stepping, in even 24px increments, so the three
   tops rise evenly. 52/36/28 put silver and bronze 8px apart, which read as a
   tie for second. */
.base-1 { height:72px; background:linear-gradient(180deg,var(--gold2),var(--gold)); color:#1A1206 }
.base-2 { height:48px; background:linear-gradient(180deg,#C9CBD1,#9BA0A8); color:#1B1D21 }
.base-3 { height:24px; background:linear-gradient(180deg,#D9A277,#B77C4E); color:#241608 }
.pod-rank { font-size:22px; text-shadow:0 1px 0 rgba(255,255,255,.35) }
.pod-me   { outline:2px solid var(--gold); outline-offset:-2px }

/* --- rows ------------------------------------------------------------------ */
.board-tbl .rank-cell { width:52px }
.rank-badge { display:inline-grid; place-items:center; width:26px; height:26px;
              border-radius:50%; font-family:var(--disp); font-weight:700; font-size:12.5px;
              background: var(--p-line); color: var(--p-ink2) }
.rank-badge.r1 { background:linear-gradient(140deg,var(--gold2),var(--gold)); color:#1A1206 }
.rank-badge.r2 { background:linear-gradient(140deg,#D3D5DA,#A2A7AF); color:#1B1D21 }
.rank-badge.r3 { background:linear-gradient(140deg,#DFAE85,#B77C4E); color:#241608 }
.streak-cell { display:inline-flex; align-items:center; gap:5px }
.streak-cell .flame-box { margin-bottom:-4px }

.row-me td { background: var(--g-1) }
.row-me td:first-child { box-shadow: inset 3px 0 0 var(--gold) }
@keyframes me-flash { 0%,100% { background: var(--g-1) } 40% { background: var(--g-3) } }
.me-flash td { animation: me-flash 1.1s ease-out 2 }

.optin-card { display:flex; align-items:center; gap:14px; padding:15px 16px; margin-bottom:15px;
              border-color: var(--g-3) }
.optin-flame { flex:none; display:grid; place-items:end }
.me-strip { display:flex; align-items:center; gap:12px; padding:13px 15px; margin-top:11px }

@media (max-width:700px) {
  .podium { grid-template-columns:1fr 1.14fr 1fr; gap:7px }
  .pod { padding:22px 6px 0 }
  .pod-streak { font-size:23px }
  .pod-name { font-size:11px }
  .base-1 { height:54px } .base-2 { height:36px } .base-3 { height:18px }
  .optin-card { flex-wrap:wrap }
  .board-tbl th:nth-child(5), .board-tbl td:nth-child(5) { display:none }
}

/* ===========================================================================
 * ENROLMENT GATE — the screen between registering and being countable.
 * =========================================================================== */
.gate { max-width: 560px; margin: 12px auto 40px; text-align: center;
        animation: ak-rise .45s var(--ease) both }
.gate-whorl { display:inline-block; margin-bottom: 14px }
.gate-title { font-size: clamp(22px, 4vw, 30px); letter-spacing: -.03em; line-height: 1.1;
              background: linear-gradient(120deg, var(--p-ink) 30%, var(--gold-ink));
              -webkit-background-clip: text; background-clip: text; color: transparent }
.gate-sub { margin-top: 10px; font-size: 14px; color: var(--p-ink2); line-height: 1.6 }

.gate-id { margin: 22px auto 0; max-width: 340px; padding: 20px;
           border-color: var(--g-3);
           box-shadow: var(--lift-2), 0 0 40px -18px var(--g-glow) }
.gate-id-num {
  font-family: var(--mono); font-size: clamp(34px, 8vw, 46px); font-weight: 500;
  letter-spacing: .16em; line-height: 1.15; margin: 8px 0 6px;
  background: linear-gradient(140deg, var(--gold2), var(--gold));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

.gate-progress { margin-top: 20px }
.gate-dots { display: flex; gap: 8px; justify-content: center }
.gate-dot {
  width: 15px; height: 20px; border-radius: 7px 7px 5px 5px;
  background: var(--p-line); border: 1px solid var(--p-line);
  animation: ak-pop .4s var(--ease) both;
}
.gate-dot.on {
  background: linear-gradient(180deg, var(--gold2), var(--gold));
  border-color: var(--gold);
  box-shadow: 0 0 14px -2px var(--g-glow);
}

.gate-steps { text-align: left; margin: 24px auto 0; max-width: 460px; padding-left: 20px;
              display: grid; gap: 11px; font-size: 13.5px; color: var(--p-ink2); line-height: 1.6 }
.gate-steps li::marker { color: var(--gold); font-weight: 700; font-family: var(--disp) }
.gate-steps b { color: var(--p-ink); font-weight: 600 }

.gate-foot { margin-top: 22px; font-size: 12px; color: var(--p-ink3);
             line-height: 1.6; max-width: 440px; margin-left: auto; margin-right: auto }

@media (max-width: 700px) {
  .gate { margin-top: 4px }
  .gate-steps { font-size: 13px }
}

/* Granting an excuse is the single most consequential thing a lecturer does on
   a student's record, so the way in must not be a secret. */
.excuse-hint { margin-top: 11px; padding: 9px 11px; border-radius: 9px;
               background: var(--g-1); border: 1px dashed var(--g-3);
               font-size: 11.5px; color: var(--p-ink2); line-height: 1.5 }
.excuse-hint b { color: var(--gold-ink); font-family: var(--mono) }

/* Sessions page: how a class actually starts. */
.kiosk-note { margin-bottom: 13px; border-color: var(--g-3);
              background: linear-gradient(180deg, var(--p-sf), var(--p-sf2)) }

/* Finger-count settings: the number, then that many fingerprint marks, so the
   figure is legible at a glance rather than needing to be read. */
.finger-val  { display:flex; align-items:center; gap:13px }
.finger-val .num { font-size:38px; line-height:1 }
.finger-dots { display:flex; gap:5px; flex-wrap:wrap }

/* Attention drill — collapsible course header inside a bucket */
.drill-head{display:flex;align-items:center;width:100%;padding:12px 15px;
  background:none;border:0;cursor:pointer;font:inherit;color:inherit;text-align:left}
.drill-head:hover{background:var(--hover,rgba(0,0,0,.03))}

/* Live class banner — student home */
.live-wrap{display:flex;flex-direction:column;gap:9px;margin:0 0 18px}
.live-class{display:flex;align-items:center;gap:12px;padding:14px 16px;
  border-left:3px solid var(--amber);animation:liveIn .45s cubic-bezier(.2,.8,.2,1) both}
.live-class.is-marked{border-left-color:var(--ok)}
.live-dot{width:9px;height:9px;border-radius:50%;background:var(--amber);
  flex:0 0 auto;animation:livePulse 1.8s ease-in-out infinite}
.live-dot.ok{background:var(--ok);animation:none}
@keyframes livePulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.35;transform:scale(.82)}}
@keyframes liveIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}

/* ===========================================================================
 * MR. WHORL — load screens
 *
 * The ridges draw themselves like a print being read, he bobs, and he blinks
 * off-beat. The three ridge durations are deliberately non-harmonic so the
 * loop never visibly restarts — the same trick the streak flame uses.
 * ========================================================================= */
.wload-wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:12px;padding:38px 16px}
.whorl-screen{position:fixed;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:16px;z-index:60;
  background:var(--p-bg,#0A0A0C)}
.whorl-label{opacity:.72;animation:wlabel 1.9s ease-in-out infinite}
@keyframes wlabel{0%,100%{opacity:.4}50%{opacity:.85}}

.whorl-load{display:block;overflow:visible}
.whorl-bob{transform-origin:32px 32px;animation:wbob 2.4s ease-in-out infinite}
@keyframes wbob{0%,100%{transform:translateY(0) rotate(0deg)}
  50%{transform:translateY(-3px) rotate(-2.5deg)}}

/* Each ridge traces, holds, then fades back — offset so they cascade. */
.wr{stroke-dasharray:140;stroke-dashoffset:140}
.wr1{animation:wtrace 2.6s cubic-bezier(.5,0,.3,1) infinite}
.wr2{animation:wtrace 1.9s cubic-bezier(.5,0,.3,1) infinite .18s}
.wr3{animation:wtrace 1.35s cubic-bezier(.5,0,.3,1) infinite .32s}
@keyframes wtrace{
  0%{stroke-dashoffset:140;opacity:.25}
  45%{stroke-dashoffset:0;opacity:.95}
  75%{stroke-dashoffset:0;opacity:.95}
  100%{stroke-dashoffset:-140;opacity:.25}}

/* A blink: a fast vertical squash, rarely, so it reads as alive not twitchy. */
.whorl-eyes{transform-origin:32px 30px;animation:wblink 4.7s ease-in-out infinite}
@keyframes wblink{0%,92%,100%{transform:scaleY(1)}95%{transform:scaleY(.12)}}

@media (prefers-reduced-motion: reduce){
  .whorl-bob,.wr1,.wr2,.wr3,.whorl-eyes,.whorl-label{animation:none}
  .wr{stroke-dashoffset:0;opacity:.9}
}

/* ===========================================================================
 * MR. WHORL LOADER v30 — the real artwork, animated around.
 * Supersedes the drawn-SVG loader; .whorl-load rules above are now unused.
 * ========================================================================= */
.wload-mark{position:relative;display:inline-grid;place-items:center;flex:0 0 auto}
.wload-img{position:relative;z-index:2;display:block;width:100%;height:100%;
  object-fit:contain;animation:wmBob 2.6s ease-in-out infinite;
  filter:drop-shadow(0 6px 14px rgba(0,0,0,.22))}
@keyframes wmBob{0%,100%{transform:translateY(0) rotate(0deg)}
  50%{transform:translateY(-5px) rotate(-3deg)}}

/* glow breathing underneath him */
.wload-glow{position:absolute;inset:-14%;z-index:1;border-radius:50%;
  background:radial-gradient(circle at 50% 50%,
    color-mix(in srgb,var(--gold) 55%,transparent), transparent 68%);
  filter:blur(12px);animation:wmGlow 3.1s ease-in-out infinite}
@keyframes wmGlow{0%,100%{opacity:.35;transform:scale(.94)}
  50%{opacity:.75;transform:scale(1.06)}}

/* a scan line sweeping him, like a print being read */
.wload-scan{position:absolute;inset:0;z-index:3;border-radius:50%;
  overflow:hidden;pointer-events:none;
  -webkit-mask:radial-gradient(circle at 50% 50%,#000 62%,transparent 64%);
          mask:radial-gradient(circle at 50% 50%,#000 62%,transparent 64%)}
.wload-scan::after{content:"";position:absolute;left:-10%;right:-10%;height:16%;
  background:linear-gradient(180deg,transparent,
    color-mix(in srgb,var(--gold2) 85%,transparent),transparent);
  opacity:.75;animation:wmScan 2.2s cubic-bezier(.55,0,.45,1) infinite}
@keyframes wmScan{0%{top:-18%}100%{top:104%}}

@media (prefers-reduced-motion: reduce){
  .wload-img,.wload-glow,.wload-scan::after{animation:none}
  .wload-scan{display:none}
}

/* ===========================================================================
 * v32 — responsiveness safety nets
 * ========================================================================= */
.ak-scrim{-webkit-overflow-scrolling:touch}

/* ---------------------------------------------------------------------------
   MODAL REGIONS — header and footer pinned, only the middle scrolls.
   Structure comes from Modal() in core.js; the sizing lives here.

   The cap is dvh (see the note in core.js): 100% of a fixed scrim is the
   large viewport on mobile, so a full-height dialog hides its own bottom
   behind the browser's URL bar. svh is the conservative fallback for engines
   without dvh.
   ------------------------------------------------------------------------- */
.ak-modal{ max-height: calc(100svh - 32px); max-height: calc(100dvh - 32px); }
.ak-modal-head{
  display:flex; align-items:center; gap:8px; flex:none;
  padding:14px 15px 11px; border-bottom:1px solid var(--p-line);
  background:var(--p-sf);
}
.ak-modal-body{
  flex:1 1 auto; min-height:0;              /* without this it will not shrink */
  overflow-y:auto; overscroll-behavior:contain;
  padding:14px 15px;
}
.ak-modal-foot{
  flex:none; display:flex; gap:8px; justify-content:flex-end;
  padding:11px 15px; border-top:1px solid var(--p-line); background:var(--p-sf);
}

/* Most dialogs predate the `footer` prop and keep their Cancel/Confirm row as
   the last thing in their children. Rather than rewrite seventeen call sites
   — and rather than leave those buttons scrolling away, which is the actual
   complaint — pin that trailing row where it sits. Matching on "last child
   that contains buttons" is deliberately narrow: a dialog whose content
   merely ends in a paragraph is untouched. Engines without :has() still get
   the scrolling fix above, so the buttons remain reachable either way. */
.ak-modal-body > div:last-child:has(> .btn){
  position:sticky; bottom:0; z-index:1;
  margin-left:-15px; margin-right:-15px; margin-bottom:-14px;
  padding:11px 15px; background:var(--p-sf);
  border-top:1px solid var(--p-line);
}

/* Any table inside a card scrolls sideways instead of stretching the page.
   Without this a wide report table widens the whole layout and pushes
   controls off-screen. */
.card > .tbl, .card table.tbl{width:100%}
.card:has(> table.tbl){overflow-x:auto}

/* Settings and other stacked-card admin screens on narrow viewports */
@media (max-width: 560px){
  .ak-scrim{padding:10px}
  .ak-modal{max-width:100% !important}
  .tbl th, .tbl td{padding:9px 10px;font-size:12.5px}
  .hero-metrics{grid-template-columns:1fr 1fr}
}

/* ===========================================================================
 * SEARCH BAR — shared by every list in the app (SearchBox in core.js).
 * =========================================================================== */
.searchbar { display:flex; align-items:center; gap:11px; margin-bottom:13px; flex-wrap:wrap }
.searchbox { position:relative; flex:1; min-width:200px; max-width:460px }
.searchbox .input { padding-left:32px; padding-right:30px; margin:0 }
.searchbox-i {
  position:absolute; left:11px; top:50%; transform:translateY(-50%);
  font-size:15px; color:var(--p-ink3); pointer-events:none; line-height:1;
}
.searchbox-x {
  position:absolute; right:8px; top:50%; transform:translateY(-50%);
  width:20px; height:20px; border-radius:50%; display:grid; place-items:center;
  font-size:10px; color:var(--p-ink3); background:var(--p-sf2); border:0; cursor:pointer;
}
.searchbox-x:hover { color:var(--p-ink); background:var(--p-line) }
/* The search input must never inherit a field's bottom margin — the bar is
   already spaced, and the doubled gap made every list look misaligned. */
.searchbar .field { margin-bottom:0 }

/* ===========================================================================
 * SHARED MOTION — applies in every theme, including the ones that add their
 * own on top. Kept to transform/opacity so none of it costs layout or paint.
 * =========================================================================== */
@keyframes ak-slide-in { from { opacity:0; transform:translate3d(-8px,0,0) } to { opacity:1; transform:none } }
@keyframes ak-pulse-soft { 0%,100% { opacity:1 } 50% { opacity:.55 } }

/* Rows arriving in a table stagger very slightly. On a register that has just
   loaded this reads as the list filling in; on an empty one, nothing. */
.tbl tbody tr { animation: ak-slide-in .3s var(--ease-out) both }
.tbl tbody tr:nth-child(1){animation-delay:.01s} .tbl tbody tr:nth-child(2){animation-delay:.03s}
.tbl tbody tr:nth-child(3){animation-delay:.05s} .tbl tbody tr:nth-child(4){animation-delay:.07s}
.tbl tbody tr:nth-child(5){animation-delay:.09s} .tbl tbody tr:nth-child(6){animation-delay:.11s}
.tbl tbody tr:nth-child(7){animation-delay:.13s} .tbl tbody tr:nth-child(n+8){animation-delay:.15s}

/* Pills and chips settle in rather than blink on. */
.pill { animation: ak-fade .28s var(--ease-out) both }

/* Anything still loading breathes, so a slow network looks busy not frozen. */
.spinner, .loading { animation: ak-pulse-soft 1.4s ease-in-out infinite }

/* Buttons acknowledge the press even when the action is instant. */
.btn:active:not(:disabled) { transform: scale(.975) }

/* The search box grows very slightly when focused — it is the one control on
   a list page that the whole page responds to. */
.searchbox .input { transition: box-shadow .22s var(--ease-out), transform .22s var(--ease-out) }
.searchbox .input:focus { transform: scale(1.006) }

@media (prefers-reduced-motion: reduce) {
  .tbl tbody tr, .pill, .spinner, .loading { animation: none !important }
  .btn:active:not(:disabled), .searchbox .input:focus { transform: none !important }
}

/* ===========================================================================
 * WHORL, STAGED — poses for in-app notifications (WhorlPose in ui.js).
 * The artwork is never redrawn; each pose is a frame, a motion and a prop.
 * =========================================================================== */
.wpose { position:relative; display:inline-block; flex:none }
/* Carries mirror + scale so the wp-* keyframes, which own `transform` on the
   image itself, cannot discard them. See WhorlPose() in ui.js. */
.wpose-tf { display:block; width:100%; height:100%; transform-origin:50% 85% }

/* The aura is what separates two poses at 28px, where a tilt of three degrees
   and a small emoji are both invisible. Warm for good news, red for a broken
   streak, cool for the sober ones, none for the flat ones. */
.wpose-aura-gold, .wpose-aura-hot, .wpose-aura-cool, .wpose-aura-bad { border-radius:50% }
.wpose-aura-gold::before, .wpose-aura-hot::before,
.wpose-aura-cool::before, .wpose-aura-bad::before {
  content:''; position:absolute; inset:-14%; border-radius:50%; z-index:0;
  pointer-events:none;
}
.wpose-aura-gold::before { background:radial-gradient(circle,rgba(224,168,46,.28),transparent 68%) }
.wpose-aura-hot::before  { background:radial-gradient(circle,rgba(255,122,26,.34),transparent 68%);
                           animation:wp-pulse 1.9s ease-in-out infinite }
.wpose-aura-cool::before { background:radial-gradient(circle,rgba(125,155,190,.22),transparent 68%) }
.wpose-aura-bad::before  { background:radial-gradient(circle,rgba(255,92,92,.24),transparent 68%) }
@keyframes wp-pulse { 0%,100%{opacity:.75} 50%{opacity:1} }
/* Above the aura, or he sits behind his own glow. The prop gets its z-index
   on its own rule below — setting it here would also have set position:relative
   on it, which only works by cascade order and breaks the moment these rules
   are reordered. */
.wpose-tf { position:relative; z-index:1 }
.wpose-img { display:block; width:100%; height:100%; object-fit:contain;
             transform-origin:50% 85%; will-change:transform }
.wpose-prop { position:absolute; z-index:2; font-size:15px; line-height:1; pointer-events:none;
              filter:drop-shadow(0 1px 2px rgba(0,0,0,.4)) }
.wpose-prop-top   { top:-6px;  left:50%; transform:translateX(-50%); animation:wp-bob 2.1s ease-in-out infinite }
.wpose-prop-right { right:-7px; bottom:2px; animation:wp-bob 2.6s ease-in-out infinite }

@keyframes wp-bob    { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-3px)} }
@keyframes wp-dash   { 0%,100%{transform:rotate(-8deg) translateX(0)} 50%{transform:rotate(-11deg) translateX(3px)} }
@keyframes wp-hop    { 0%,100%{transform:translateY(0) scale(1)} 45%{transform:translateY(-6px) scale(1.04)} }
@keyframes wp-lean   { 0%,100%{transform:rotate(6deg)} 50%{transform:rotate(9deg)} }
@keyframes wp-peek   { 0%,100%{transform:rotate(-10deg) translateX(0)} 50%{transform:rotate(-7deg) translateX(2px)} }
@keyframes wp-wave   { 0%,100%{transform:rotate(-4deg)} 50%{transform:rotate(7deg)} }
@keyframes wp-breathe{ 0%,100%{transform:scale(1)} 50%{transform:scale(1.035)} }
@keyframes wp-tap    { 0%,92%,100%{transform:rotate(0)} 96%{transform:rotate(-5deg)} }
@keyframes wp-sink   { 0%,100%{transform:rotate(8deg) translateY(0)} 50%{transform:rotate(9deg) translateY(2px)} }
@keyframes wp-glint  { 0%,100%{filter:none} 50%{filter:brightness(1.25) drop-shadow(0 0 6px var(--g-glow))} }
@keyframes wp-still  { 0%,100%{transform:none} }

.wpose-img.wp-dash   { animation:wp-dash 1.1s ease-in-out infinite }
.wpose-img.wp-hop    { animation:wp-hop 1.5s ease-in-out infinite }
.wpose-img.wp-lean   { animation:wp-lean 2s ease-in-out infinite }
.wpose-img.wp-peek   { animation:wp-peek 2.4s ease-in-out infinite }
.wpose-img.wp-wave   { animation:wp-wave 1.6s ease-in-out infinite }
.wpose-img.wp-breathe{ animation:wp-breathe 3.2s ease-in-out infinite }
.wpose-img.wp-tap    { animation:wp-tap 2.8s ease-in-out infinite }
.wpose-img.wp-sink   { animation:wp-sink 3.4s ease-in-out infinite; opacity:.9 }
.wpose-img.wp-glint  { animation:wp-glint 2.2s ease-in-out infinite }

/* A notification row that carries a pose gets room for him. */
.notif { display:flex; gap:12px; align-items:flex-start }
.notif-body { min-width:0; flex:1 }
/* A NOTIFICATION IS READ IN PLACE, NOT SOMEWHERE ELSE.
   A relayed message can be several paragraphs; showing all of it makes the
   inbox unscannable, and truncating it with nowhere to go makes the message
   unreadable. Collapsed to three lines, expanded on click. -webkit-line-clamp
   is the only cross-browser way to clamp by LINES rather than by height, so it
   works whatever the font size ends up being. */
.notif-text { font-size:12.5px; color:var(--p-ink2); margin-top:3px; white-space:pre-wrap }
.notif-text.clamped {
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3;
  overflow:hidden;
}
.notif-more { font-size:11.5px; color:var(--gold); margin-top:4px; font-weight:600 }
/* tone=plain rows are deliberately flat: no mascot, no tint, no flourish. */
.notif.plain { border-left:3px solid var(--p-line); padding-left:11px }

@media (prefers-reduced-motion: reduce) {
  .wpose-img, .wpose-prop, .wpose-aura-hot::before { animation:none !important }
}

/* Report grouping + selection */
.rpt-group td { background: color-mix(in srgb, var(--gold) 10%, transparent);
                border-top: 1.5px solid var(--gold); font-size: 12.5px }
.rpt-on td    { background: color-mix(in srgb, var(--gold) 7%, transparent) }

/* The active tab must stay legible when the strip is scrolled — a faint
   underline on a scrolling strip is easy to lose, so the current tab also
   carries a tint and full-strength ink. */
.tab.on { position: relative }
.tabs { scroll-behavior: smooth; scroll-padding-inline: 16px }


/* Stay-signed-in, on the sign-in card. A 44px row rather than a bare
   checkbox: this is the one control on the page a phone user has to hit
   accurately, and WCAG 2.5.5 asks for 44px. */
.stayin{ display:flex; align-items:center; gap:9px; min-height:44px;
         margin:2px 0 6px; cursor:pointer; user-select:none;
         font-size:12.5px; color:var(--p-ink2) }
.stayin input{ width:17px; height:17px; accent-color:var(--gold); cursor:pointer; flex:none }
.stayin:hover span{ color:var(--p-ink) }


/* ---------------------------------------------------------------------------
 * TABLES ON A PHONE
 *
 * Nineteen of the twenty-seven tables sit inside an overflow-x container.
 * The other eight did not, and a wide table with no scroll context pushes the
 * whole PAGE sideways — every screen then scrolls left-right, which is the
 * thing that makes an app feel unusable on a phone rather than merely tight.
 *
 * Wrapping eight more call sites by hand fixes today and not tomorrow, so the
 * scroll context is given to .tbl itself: display:block makes the table its
 * own scroller, and min-width keeps the columns from collapsing into unreadable
 * slivers. Where a wrapper already exists this changes nothing — the inner
 * scroller simply never overflows.
 * ------------------------------------------------------------------------- */
/* A table cannot scroll itself: making .tbl display:block turns thead and
   tbody into two independent tables, which compute their column widths
   separately and misalign the header from its own cells. The scroll context
   has to be a WRAPPER, so the eight call sites that lacked one were given one
   rather than solved in CSS. This just stops a table from forcing the page
   wider than the phone. */
.tblwrap{ overflow-x:auto; -webkit-overflow-scrolling:touch; max-width:100% }
@media (max-width: 720px){
  .tbl{ min-width: 520px }        /* readable columns; the wrapper scrolls */
}
}

/* Nothing may make the BODY scroll sideways. If a single element overruns, the
   whole app feels broken rather than that one element. */
html, body{ overflow-x: hidden; max-width: 100%; }

@media (max-width: 720px){
  /* Filter rows and toolbars wrap instead of forcing a minimum width. */
  .grid.g2, .grid.g3{ grid-template-columns: 1fr; }
  .wrap{ padding-left: 12px; padding-right: 12px; }
  /* Cards and heroes lose their generous desktop padding. */
  .card{ padding: 13px; }
  .stu-hero{ grid-template-columns: 1fr; }
  .course-grid{ grid-template-columns: 1fr; }
  /* A 44px tap target everywhere, per WCAG 2.5.5 — on a phone these are
     thumbs, not a mouse. */
  .btn, .iconbtn, .seg button{ min-height: 40px; }
  .btn.sm{ min-height: 36px; }
  /* The header stops competing with the content for space. */
  .apphead{ gap: 6px; padding-left: 10px; padding-right: 10px; }
  .apphead .who span{ display:none }
}

/* ---------------------------------------------------------------------------
 * THE STREAK TIMELINE — the classes behind the number (StreakDetail).
 *
 * Scrolls inside itself rather than making the modal grow: sixty classes is a
 * long list, and a dialog taller than the viewport puts its own close button
 * off-screen. Every colour is a token that is already defined for both themes,
 * because a var() nothing defines voids the whole declaration — that is no
 * colour at all, not the wrong one.
 * ------------------------------------------------------------------------ */
.tl { max-height: 46vh; overflow-y: auto; margin-top: 4px;
      border-top: 1px solid var(--p-line); }
.tl-row { display: flex; align-items: flex-start; gap: 10px; padding: 9px 2px;
          border-bottom: 1px solid var(--p-line); }
.tl-row:last-child { border-bottom: 0 }
.tl-dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 5px; flex: 0 0 8px;
          background: var(--p-ink3) }
.tl-dot.ok   { background: var(--ok) }
.tl-dot.warn { background: var(--amber) }
/* --rose, NOT --bad. There is no --bad token in this palette and a var()
   nothing defines voids the whole declaration — so this would be no colour
   at all rather than the wrong one. Checked against the palette in
   index.html before shipping, which is the only way to catch it: CSS
   fails silently. */
.tl-dot.bad  { background: var(--rose) }
/* The one class that ended the run, marked in the margin rather than by colour
   alone — the dot is already carrying present/excused/missed. */
.tl-broke { border-left: 2px solid var(--rose); padding-left: 8px;
            background: color-mix(in srgb, var(--rose) 7%, transparent) }

/* ---------------------------------------------------------------------------
 * SWITCHES AND SEGMENTED CHOICES — Settings
 *
 * A boolean setting used to render as the literal word "true" in a 20px
 * numeral, and changing it meant opening a modal and typing the opposite word.
 * A setting that is one of four modes rendered the same way. Both are now what
 * they always were: a switch, and a choice.
 * ------------------------------------------------------------------------- */
.swx{position:relative;display:inline-flex;align-items:center;
     width:46px;height:26px;flex:none;cursor:pointer;vertical-align:middle}
.swx input{position:absolute;inset:0;opacity:0;width:100%;height:100%;margin:0;cursor:pointer}
.swx i{position:absolute;inset:0;border-radius:999px;background:var(--p-line);
       transition:background .18s ease}
.swx i::after{content:"";position:absolute;top:3px;left:3px;width:20px;height:20px;
              border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.34);
              transition:transform .18s ease}
.swx input:checked + i{background:var(--gold)}
.swx input:checked + i::after{transform:translateX(20px)}
.swx input:focus-visible + i{outline:2px solid var(--gold);outline-offset:2px}
.swx input:disabled + i{opacity:.5;cursor:not-allowed}

.seg{display:inline-flex;border:1px solid var(--p-line);border-radius:8px;overflow:hidden;
     flex-wrap:wrap}
.seg button{border:0;background:none;color:var(--p-ink2);padding:7px 13px;
            font:inherit;font-size:12px;cursor:pointer;transition:background .14s,color .14s}
.seg button + button{border-left:1px solid var(--p-line)}
.seg button:hover:not(.on){color:var(--gold)}
.seg button.on{background:var(--gold);color:#1A1508;font-weight:650}
.seg button:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}

.setrow{display:flex;align-items:center;gap:13px;flex-wrap:wrap}
.setrow .grow{margin-left:auto}

@media (prefers-reduced-motion:reduce){
  .swx i, .swx i::after, .seg button{transition:none}
}

/* A table cell holding a row of controls. The controls appear conditionally, so
   per-button margins left every row starting at a different offset and nothing
   lined up down the column. One flex row, right-aligned, spaced with gap. */
.rowacts{ text-align:right; white-space:nowrap }
.rowacts > div{ display:flex; gap:6px; justify-content:flex-end; align-items:center;
                flex-wrap:nowrap }

/* ===========================================================================
 * ACADEMIC CALENDAR — pages-calendar.js
 *
 * An agenda read top to bottom: a month, then its dates. The date block is a
 * fixed-width column with tabular figures so ranges line up down the page, and
 * a date's KIND is a coloured dot plus a word — never colour alone.
 * =========================================================================== */
.cal-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:flex-end}
.cal-notice{display:flex;gap:10px;align-items:center;margin-bottom:12px;
            border-color:color-mix(in srgb,var(--gold) 45%,transparent);font-size:13px}
.cal-notice.bad{border-color:color-mix(in srgb,var(--rose) 45%,transparent)}
.cal-note{margin-bottom:14px}
.cal-next{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));margin-bottom:16px}
.cal-next-card{display:flex;gap:14px;align-items:flex-start}
.cal-next-card.is-now{border-color:color-mix(in srgb,var(--gold) 50%,transparent);background:var(--g-1)}
.cal-next-big{flex:none;min-width:46px;text-align:center}
.cal-next-n{font-family:var(--disp);font-weight:700;font-size:28px;letter-spacing:-.03em;line-height:1;
            color:var(--p-ink);font-variant-numeric:tabular-nums}
.cal-next-u{font-size:10.5px;color:var(--p-ink3);margin-top:3px}
.cal-next-empty{margin-bottom:16px;font-size:12.5px;color:var(--p-ink2)}
.cal-title{font-weight:600;font-size:13.5px;line-height:1.35;overflow-wrap:anywhere}
.cal-sub{font-size:12px;color:var(--p-ink2);margin-top:3px}
.cal-kind{display:inline-flex;align-items:center;gap:6px;color:var(--p-ink2);font-size:11.5px}
.cal-kind i{width:8px;height:8px;border-radius:50%;flex:none;display:inline-block}
.cal-agenda{margin-bottom:22px}
.cal-earlier{margin-bottom:10px}
.cal-today{display:flex;align-items:center;gap:10px;margin:4px 0 14px;font-family:var(--mono);font-size:10px;
           letter-spacing:.14em;text-transform:uppercase;color:var(--gold-ink)}
.cal-today::before,.cal-today::after{content:"";height:1px;background:var(--g-3);flex:1}
.cal-today.in-list{margin:0;padding:8px 14px;border-top:1px solid var(--p-line)}
.cal-month{margin-bottom:18px}
.cal-month-h{font-family:var(--mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
             color:var(--p-ink3);margin:0 0 8px 2px}
.cal-list{background:var(--p-sf);border:1px solid var(--p-line);border-radius:12px;overflow:hidden}
.cal-row{display:grid;grid-template-columns:104px minmax(0,1fr) auto;gap:14px;align-items:start;
         padding:12px 14px;border-top:1px solid var(--p-line)}
.cal-list > :first-child, .cal-list > :first-child > .cal-row:first-child{border-top:0}
.cal-row.past{opacity:.55}
.cal-row.now{background:var(--g-1)}
.cal-d{font-family:var(--disp);font-weight:700;font-size:16px;letter-spacing:-.02em;color:var(--p-ink);
       font-variant-numeric:tabular-nums;line-height:1.2}
.cal-wd{font-size:11px;color:var(--p-ink3);margin-top:2px;white-space:nowrap}
.cal-d.x{font-size:14px}
.cal-d.x span{white-space:nowrap}
.cal-meta{display:flex;flex-wrap:wrap;gap:4px 12px;font-size:11.5px;color:var(--p-ink3);margin-top:5px;align-items:center}
.cal-notes{font-size:12px;color:var(--p-ink2);margin-top:5px;line-height:1.5}
.cal-status{font-size:11.5px;color:var(--p-ink3);white-space:nowrap;text-align:right;padding-top:2px}
.cal-meta-line{font-size:11.5px;margin:-8px 0 22px 2px}

/* the editor: one row per date, the fields in a line on a wide screen */
.cal-edit-list{background:var(--p-sf);border:1px solid var(--p-line);border-radius:12px;overflow:hidden}
.cal-edit-row{display:grid;grid-template-columns:30px minmax(0,2fr) 142px 142px minmax(140px,1fr) minmax(150px,1fr) 34px;
              gap:8px 10px;align-items:end;padding:11px 12px;border-top:1px solid var(--p-line)}
.cal-edit-row:first-child{border-top:0}
.cal-edit-row .field{margin:0}
.cal-edit-row .input{padding:8px 10px;font-size:13px}
.cal-edit-row.new{box-shadow:inset 3px 0 0 var(--ok)}
.cal-edit-row.changed{box-shadow:inset 3px 0 0 var(--gold)}
.cal-edit-row.bad{box-shadow:inset 3px 0 0 var(--rose);background:color-mix(in srgb,var(--rose) 5%,transparent)}
.cal-edit-n{align-self:center;font-family:var(--mono);font-size:11px;color:var(--p-ink3);
            display:flex;flex-direction:column;gap:4px;align-items:flex-start}
.cal-edit-n .pill{padding:1px 6px;font-size:9.5px}
.cal-edit-x{align-self:center}
.cal-edit-note{grid-column:2 / -2}
.cal-add-note{font-size:11.5px;color:var(--p-ink3);padding:0;margin-top:-2px}
.cal-add-note:hover{color:var(--gold-ink)}
.cal-edit-err{grid-column:2 / -1;font-size:12px;color:var(--rose)}
.cal-edit-foot{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:10px 0 14px}
.cal-removed{margin-bottom:14px}
.cal-removed-row{display:flex;gap:10px;align-items:center;margin-top:8px;font-size:13px}
.cal-publish-bar{position:sticky;bottom:0;display:flex;gap:10px;align-items:center;flex-wrap:wrap;
                 padding:12px 14px calc(12px + env(safe-area-inset-bottom,0px));margin:0 -2px;
                 background:var(--p-sf);border:1px solid var(--p-line);border-radius:12px;box-shadow:var(--lift-2);z-index:5}
.cal-review-group{margin-bottom:12px}
.cal-review-group ul{margin:6px 0 0;padding-left:18px;font-size:12.5px;line-height:1.6}
.cal-review-who{margin-top:6px;font-size:13px;border-color:color-mix(in srgb,var(--gold) 45%,transparent)}
.cal-review-who.quiet{border-color:var(--p-line);color:var(--p-ink2)}
.cal-import-box{font-family:var(--mono);font-size:12px;line-height:1.5;resize:vertical}
.cal-skipped{margin-top:12px;font-size:12px}
.cal-skipped summary{cursor:pointer;color:var(--p-ink2)}
.cal-skipped ul{margin:8px 0 0;padding-left:18px;line-height:1.6}

/* the student dashboard card */
.cal-teaser{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:13px}
.cal-teaser-line{margin-top:6px;font-size:13px;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap}
.cal-teaser-when{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--gold-ink)}

@media (max-width:760px){
  /* the page's actions go UNDER the title on a phone; side by side, the title
     was squeezed into a column one word wide */
  .cal-page .hero{flex-direction:column;align-items:stretch}
  .cal-page .hero-right, .cal-actions{justify-content:flex-start}
  .cal-row{grid-template-columns:76px minmax(0,1fr);gap:10px}
  .cal-status{grid-column:2;text-align:left;padding-top:0}
  .cal-d{font-size:14px}
  .cal-edit-row{grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:start}
  /* number and remove button share the first line; without grid-row the
     button is auto-placed after the last field, alone on a row of its own */
  .cal-edit-n{grid-column:1 / 2;grid-row:1;flex-direction:row;align-items:center}
  .cal-edit-x{grid-column:2 / 3;grid-row:1;justify-self:end}
  .cal-edit-title, .cal-edit-note, .cal-edit-err{grid-column:1 / -1}
}
