/* © 2026 William C. Chesher. All Rights Reserved.
   glitch - the base skin and the student door

   THE TOKENS ARE SET BY THE SERVER, per theme, on :root (app.py::page). Nothing here
   hardcodes a colour; a theme swap is a token swap and no rule below has to know.

   THE MOTION CONTRACT, in one place so no surface can quietly invent its own curve:
     --ease  cubic-bezier(.2,0,.2,1)   the house default, 24 uses in sfhsbta site.css
     --enter cubic-bezier(.16,1,.3,1)  entrances only
     --fast  140ms                     the house default duration, 20 uses
   NOTHING BOUNCES. No overshoot easing anywhere in these files. The audience is fifteen,
   and an overshoot is the fastest way to make a tool read as built for eight-year-olds. */

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: "Barlow", "Barlow Fallback", system-ui, -apple-system, sans-serif;
  /* The browser chrome bar appearing mid-question must not resize the layout under a
     thumb that is already moving toward a button (3.6). */
  min-height: 100dvh;
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3 { font-family: var(--display); font-weight: 400; letter-spacing: -.01em; }

/* The mono face is the INSTRUMENT, never the voice: numbers, codes and labels only. */
.meta, .hint, .step, code, .bnum, .brank { font-family: "JetBrains Mono", ui-monospace, monospace; }
.meta, .hint { color: var(--muted); font-size: 13px; letter-spacing: .02em; }
/* /pc: this browser's tag, the four characters the teacher screen and the room log print */
.pctag { margin: 20px 0 4px; font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 16px; color: var(--muted); }
.pctag b { color: inherit; font-size: 22px; letter-spacing: .14em; filter: brightness(1.6); }

/* ---- ASCII ornaments (the subtle festoon) --------------------------------------------------
   The ascii identity carried through ordinary content -- a neon rule, glitch list bullets, a
   mono `>_` section eyebrow -- CSS-only so it costs nothing and needs no JS on the phone. The
   fuller motif rules and dynamic ornaments live in ornaments.js. */
hr, .glitch-hr { border: 0; height: 2px; margin: 22px 0;
  background: repeating-linear-gradient(90deg, var(--accent) 0 9px, transparent 9px 15px); opacity: .55; }
.glitch-list { list-style: none; padding-left: 0; }
.glitch-list > li { position: relative; padding-left: 1.5em; }
.glitch-list > li::before { content: "\25b8"; position: absolute; left: 0; color: var(--accent);
  font-family: "JetBrains Mono", ui-monospace, monospace; }
.sectional { font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 12px;
  letter-spacing: .18em; text-transform: uppercase; color: var(--muted);
  display: flex; align-items: center; gap: 8px; margin: 0 0 10px; }
.sectional::before { content: ">_"; color: var(--accent); }
/* a full ascii motif rule -- ornaments.js fills .glitch-rule with a MOTIF string */
.glitch-rule { font-family: "JetBrains Mono", ui-monospace, monospace; color: var(--muted);
  letter-spacing: .04em; text-align: center; overflow: hidden; white-space: nowrap; margin: 20px 0; opacity: .7; }

.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

/* The wordmark. Lowercase always -- glitch is a lowercase product name -- set in the display
   face and wearing the brand's RGB tear as a STATIC chromatic split (cyan left, magenta right)
   so it reads as the glitch with no animation to gate behind reduced-motion. Token-driven: the
   cyan is the theme's second answer colour and the magenta its accent, so a skin swap recolours
   the split rather than breaking it. */
.brand {
  font-size: clamp(38px, 9vw, 72px); margin: 0 0 24px; letter-spacing: -.01em; line-height: 1;
  color: var(--ink);
  text-shadow: -.016em 0 var(--a2, #05d9e8);   /* a whisper of cyan, not a heavy split -- the brand lockup reads clean */
}
/* the bang -- the brand is "glitch!" across the board (operator, 2026-09-22). Magenta, and it
   inherits the wordmark's chromatic split. The common-noun "glitch" (a quiz) keeps no bang. */
.bang { color: var(--accent); }

/* glitch's brand lockup on the door -- its OWN glitchy colour logo: the glitched-tiles MARK (the
   four answer tiles with the void torn through them), the wordmark, and the pedagogy as tagline.
   (The g-glyph in /static is the academy's, not glitch's.) The mark's fills are the answer-position
   tokens, so a skin swap recolours it; the lockup runs wordmark-left / mark-right at EVERY width. */
/* One horizontal composition at all sizes, matching the wide brand lockup: the wordmark with the
   tagline directly UNDER it on the left, the tiles mark on the right. It scales down together on a
   phone rather than reflowing, so the mark is always fully in frame -- never a cut-off peek. */
.lockup { display: flex; align-items: center; gap: clamp(16px, 4vw, 40px); margin: 0 0 32px; }
.lock-text { flex: 1 1 auto; min-width: 0; }
/* The wordmark is the loud element -- big, near-clean, the way the brand lockup is drawn. */
.lockup .brand { margin: 0; font-size: clamp(49px, 12.2vw, 86px); }
/* The tiles mark is large and CRISP -- no glow; the reference lockup sits flat on the void. */
.lockmark { flex: 0 0 auto; display: block; width: clamp(120px, 34vw, 204px); height: auto; }
/* The tagline sits UNDER the wordmark, quiet and grey (not the accent), and small enough to stay
   on one line where the door is wide -- it wraps only on a phone. */
.lock-tag { margin: 10px 0 0; font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: clamp(12px, 2.3vw, 13px); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted, #a0788c); line-height: 1.4; }
.lock-tag b { font-weight: 600; color: var(--ink, #fff0f7); }
/* The wordmark arrives as a beautiful error: the chromatic split shudders once, hard, and settles
   into its resting cyan/magenta tear -- 100% equals the static .brand shadow so it lands exactly on
   the normal state, and the global reduced-motion clamp below snaps straight to it. */
.door .brand { animation: brand-glitch-in 520ms steps(2, end) 1 both; }
@keyframes brand-glitch-in {
  0%   { transform: translate(-3px, 1px); text-shadow: -.09em -.02em var(--a2, #05d9e8), .07em .02em var(--accent, #ff2e97); }
  30%  { transform: translate(3px, -1px);  text-shadow: .07em 0 var(--a2, #05d9e8), -.06em 0 var(--accent, #ff2e97); }
  60%  { transform: translate(-2px, 0);    text-shadow: -.05em 0 var(--wrong, #ff4d6d), .05em 0 var(--a2, #05d9e8); }
  100% { transform: none;                  text-shadow: -.016em 0 var(--a2, #05d9e8); }
}

/* THE MINI HERO on the join door (Maestro, 2026-10-19: "Make a minihero for this page"). The lockup sits on a
   lit stage rather than the bare void: the three tile colours as soft light pooled behind the mark (the mark
   itself stays crisp -- the light is the stage's, not the logo's), faint scan lines for the broadcast, and a
   pink-cyan-purple seam along the foot. It stays compact on purpose: at 390px the code box and Enter remain
   on screen. On arrival one scan bar sweeps it, in step with the wordmark's tear; reduced motion skips it. */
.minihero { position: relative; isolation: isolate; overflow: hidden; margin: 0 0 28px;
  padding: clamp(18px, 4vw, 28px) clamp(16px, 4vw, 28px) clamp(20px, 4.4vw, 30px);
  border: 1px solid rgba(255,255,255,.08); border-radius: 16px;
  background:
    radial-gradient(closest-side circle at 78% 34%, color-mix(in srgb, var(--a1, #ff2e97) 30%, transparent), transparent),
    radial-gradient(closest-side circle at 94% 26%, color-mix(in srgb, var(--a2, #05d9e8) 22%, transparent), transparent),
    radial-gradient(closest-side circle at 92% 80%, color-mix(in srgb, var(--a4, #c47cff) 24%, transparent), transparent),
    linear-gradient(160deg, rgba(255,255,255,.035), rgba(255,255,255,0) 60%); }
.minihero::before { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: repeating-linear-gradient(0deg, rgba(255,255,255,.035) 0 1px, transparent 1px 4px); }
.minihero::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; pointer-events: none;
  background: linear-gradient(90deg, var(--a1, #ff2e97), var(--a2, #05d9e8) 55%, var(--a4, #c47cff)); }
.minihero .lockup { margin: 0; gap: clamp(14px, 3vw, 28px); }
.minihero .lockmark { width: clamp(104px, 28vw, 168px); }
.minihero .lock-tag { letter-spacing: .06em; text-wrap: balance; }
/* the arrival sweep: one bright scan bar passes top to bottom, once */
.minihero .lockup::after { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 18%; pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(255,255,255,.07) 70%, color-mix(in srgb, var(--a2, #05d9e8) 45%, transparent) 96%, transparent);
  opacity: 0; animation: minihero-sweep 900ms var(--enter, cubic-bezier(.16,1,.3,1)) 160ms 1 both; }
@keyframes minihero-sweep {
  0%   { opacity: 1; transform: translateY(-100%); }
  85%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(560%); }
}
@media (prefers-reduced-motion: reduce) { .minihero .lockup::after { content: none; } }

.door {
  min-height: 100dvh;
  display: flex; flex-direction: column; justify-content: center;
  /* 16px side gutter, generous and uneven vertically -- nothing is centred in a box it
     does not fill. */
  padding: 32px 16px 15vh;
  max-width: 680px; margin: 0 auto;
}

.joinform { display: flex; flex-direction: column; gap: 10px; }
/* the privacy notice: a student reads it on a phone, so it is plain text at reading size */
.privacynote { margin-top: 18px; text-align: center; }
/* THE DOOR'S OWN LINKS READ ON THE DOOR (audit 2026-10-12, second pass): the privacy link under Enter and the
   way back off the notice took the browser's default link blue and its thick underline -- dark blue on the
   door's near-black. Muted like the hint they sit in, a hairline underline so they still read as links, the
   accent on hover. */
.privacynote a, .privacy .hint a, .door .hint a { color: var(--muted); text-decoration-thickness: 1px; text-underline-offset: 3px; }
.privacynote a:hover, .privacy .hint a:hover, .door .hint a:hover { color: var(--accent); }
.privacy h2 { margin: 28px 0 8px; font-size: 20px; }
.privacy p, .privacy li { font-size: 17px; line-height: 1.55; }
.privacy ul { padding-left: 20px; margin: 0; }
.privacy li { margin: 6px 0; }
.copyright a { color: inherit; }
label { font-size: 14px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }

input[type="text"], input[type="password"], input[type="number"], input:not([type]), textarea, select {
  font: inherit; font-size: 20px;
  background: rgba(255,255,255,.05);
  color: var(--ink);
  border: 1px solid rgba(255,255,255,.14);   /* a hairline, not a box */
  border-radius: 10px;
  padding: 14px 16px;
  width: 100%;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
input:focus-visible, textarea:focus-visible, select:focus-visible, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
#code { font-family: "JetBrains Mono", monospace; font-size: 32px; letter-spacing: .22em; text-align: center; }

button, .big {
  font: inherit; font-weight: 600; font-size: 16px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--surface);
  border: 0; border-radius: 10px;
  /* one control tall (app.py --control-h), like every button on every glitch page */
  padding: 0 20px; min-height: var(--control-h);
  cursor: pointer; text-decoration: none; text-align: center;
  transition: transform var(--fast) var(--ease), opacity var(--fast) var(--ease);
}
button:active { transform: scale(.985); }        /* weight, not springiness */
button:disabled { opacity: .35; cursor: default; }
button.quiet { background: transparent; color: var(--muted); border: 1px solid rgba(255,255,255,.14); }
button.danger { color: var(--wrong); }

.bad { color: var(--wrong); font-weight: 600; }

/* A BEAUTIFUL ERROR AT THE DOOR (ux audit 2026-10-12: one small red line, no next step, no personality,
   in a game called "beautiful errors"). A card on a hairline of the wrong colour: a mono tag that reads
   like a terminal ("err · no signal"), the line that says what happened, glitching in ONCE with the
   wordmark's chromatic shudder and settling, then the next step in plain ink. Quiet after the first
   half second -- a student is about to retype, and nothing keeps moving while they do. Reduced motion
   arrives settled (the clamp below). */
.doorerr { margin: 0 0 18px; padding: 12px 14px 12px 16px; border-radius: 10px;
  border-left: 3px solid var(--wrong);
  background: color-mix(in srgb, var(--wrong) 9%, transparent); }
.errtag { margin: 0 0 4px; font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 12px;
  letter-spacing: .14em; text-transform: lowercase; color: var(--wrong); opacity: .85; }
.errtag::before { content: ">_ "; color: var(--a2, #05d9e8); }
.errline { margin: 0; font-size: 20px; line-height: 1.25;
  animation: err-glitch-in 420ms steps(2, end) 1 both; }
@keyframes err-glitch-in {
  0%   { transform: translate(-3px, 0); text-shadow: -.08em 0 var(--a2, #05d9e8), .06em 0 var(--accent, #ff2e97); }
  40%  { transform: translate(2px, 0);  text-shadow: .06em 0 var(--a2, #05d9e8), -.05em 0 var(--accent, #ff2e97); }
  100% { transform: none; text-shadow: none; }
}
.errnext { margin: 6px 0 0; font-size: 15px; line-height: 1.45; color: var(--ink); }
.good { color: var(--correct); font-weight: 600; }

/* ---- the avatar grid ---- */
.avatars { border: 0; padding: 0; margin: 8px 0 0; }
.avatars legend { font-size: 14px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
/* AN EVEN GRID, EDGE TO EDGE (second audit 2026-10-12, lobbyform-360 / l-idform-390): inline-blocks
   wrapped four to a row against the left edge with a ragged gap on the right, under a full-width name
   field and Join button. Four columns that share the width on a phone, six on anything wider. */
.avatars { display: grid; grid-template-columns: repeat(4, 1fr); justify-items: center; row-gap: 4px; }
@media (min-width: 520px) { .avatars { grid-template-columns: repeat(6, 1fr); } }
.av { display: inline-block; }
.av input { position: absolute; opacity: 0; }
.av span {
  display: block; padding: 4px; border-radius: 12px;
  border: 1px solid transparent;
  transition: transform var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.av input:checked + span { border-color: var(--accent); transform: scale(1.06); }
.av input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  /* A REAL reduced path, not a disabled one: state still changes, it just arrives rather
     than travels. Vestibular sensitivity is common and this is a room of children. */
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
}

/* ---- team mode: one device, one team ---- */
.named { display: flex; gap: 8px; align-items: stretch; }
.named input { flex: 1; }
.reroll { flex: none; font-size: 14px; padding: 0 14px; min-height: 0;
  background: transparent; color: var(--muted); border: 1px solid rgba(255,255,255,.16); }
.reroll:hover { color: var(--accent); border-color: var(--accent); }
.members { border: 0; padding: 0; margin: 4px 0 0; display: grid; gap: 8px; }
.members legend { font-size: 14px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); }
.members legend .hint { text-transform: none; letter-spacing: 0; margin-left: 6px; }
.member { font-size: 17px; padding: 11px 14px; }

/* The nice 404: the door's own shape, a big quiet number, the way back. */
.notfound-code { font-family: "JetBrains Mono", monospace; font-size: 64px; letter-spacing: .12em;
  color: var(--muted); margin: 0; line-height: 1; opacity: .6; }
.notfound-why { max-width: 36ch; line-height: 1.6; font-size: 15px; }
.notfound-why a { color: var(--accent); }
.notfound-path { margin: 0; }
.notfound-path code { font-size: 12px; color: var(--muted); }
/* the actions start where the words start (audit 2026-10-12, second pass): the door is left-aligned, and the
   two buttons sat centred under a left-aligned sentence, a seam on every error page */
.notfound-acts { display: flex; gap: 10px; flex-wrap: wrap; justify-content: flex-start; margin-top: 8px; }
.notfound-tag { margin-top: 10px; }
.notfound-acts .btn { display: inline-flex; align-items: center; min-height: var(--control-h); padding: 0 18px; border-radius: 999px;
  text-decoration: none; border: 1px solid rgba(255,255,255,.2); color: var(--ink); font-size: 15px; }
.notfound-acts .btn.go { background: var(--accent); color: var(--surface); border-color: var(--accent); }

/* The glitch's title on the join page, under the brand (operator, 2026-09-25). */
.door .gtitle { margin: -4px 0 8px; font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 14px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--accent); text-wrap: balance; }

/* ---- /feedback: two choices, one box, one button (Maestro, 2026-10-19) ----
   The door's own shape and fields; the kind is a pair of radios drawn as one segmented choice, so it
   works with a keyboard and a screen reader the way a radio does, and with no script at all. */
.feedback-page .brand { margin-bottom: 12px; }
.fb-kinds { border: 0; padding: 0; margin: 0 0 6px; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.fb-kind { position: relative; display: block; text-transform: none; letter-spacing: 0; color: var(--ink); font-size: 16px; }
.fb-kind input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.fb-kind span { display: flex; align-items: center; justify-content: center; text-align: center;
  min-height: var(--control-h); padding: 8px 12px; border-radius: 10px; font-weight: 600;
  border: 1px solid rgba(255,255,255,.14); background: rgba(255,255,255,.05); color: var(--muted);
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease), color var(--fast) var(--ease); }
.fb-kind input:checked + span { border-color: var(--accent); color: var(--ink);
  background: color-mix(in srgb, var(--accent) 16%, transparent); }
.fb-kind input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.feedback-page textarea { font-size: 17px; line-height: 1.45; resize: vertical; min-height: 140px; }
.feedback-page .fb-optional { text-transform: none; letter-spacing: 0; }
.feedback-page .bad { margin: 0; }
.feedback-page .fb-thumb { display: block; max-width: 220px; max-height: 140px; border-radius: 8px; border: 1px solid rgba(255,255,255,.14); }
.feedback-page .fb-thumb[hidden], .feedback-page .fb-shot-off[hidden] { display: none; }
.fb-mine { margin-top: 20px; text-align: left; }
.fb-mine summary { cursor: pointer; color: var(--muted, #9aa); font-size: 14px; }
.fb-mine ol { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 8px; }
.fb-mine li { border: 1px solid rgba(255,255,255,.14); border-radius: 8px; padding: 10px 12px; }
.fb-mine-head { display: flex; gap: 8px; margin: 0; font-size: 13px; color: var(--muted, #9aa); }
.fb-mine-status { margin-left: auto; }
.fb-mine-status.good { color: var(--accent); }
.fb-mine-words { margin: 4px 0 0; overflow-wrap: anywhere; }
.fb-mine-reply { margin: 6px 0 0; padding-left: 10px; border-left: 3px solid var(--accent); }
.fb-done .fb-thanks { font-family: var(--display); font-size: 28px; margin: 0 0 8px; }
