/* ============================================================
   assets/css/theme.css — MISSION CONTROL design system
   ============================================================

   The single source of truth for the visual identity: brand tokens, the
   self-hosted fonts, the cosmic background, and the primitives every surface
   shares (buttons, inputs, cards, pills, chips, flash messages).

   Loaded alongside exactly one surface stylesheet on every page:
     auth.css        login / registration
     participant.css participant AND staff (both are phone-first, by design)
     admin.css       desktop organiser dashboard
     display.css     the projector board

   Change a colour here and it changes everywhere. Don't redefine these tokens
   in the surface files.
   ============================================================ */

/* ------------------------------------------------------------
   Fonts — self-hosted, latin subsets only.
   NOT Google Fonts: CLAUDE.md forbids CDN dependencies for anything the live
   event relies on, and campus wifi / captive portals block font CDNs often
   enough to matter. Same reasoning that vendored jsQR. Pulled with
   `npm pack @fontsource/...`; all three families are SIL OFL (licences beside
   the .woff2 files). font-display:swap so text is never invisible while
   loading.
   ------------------------------------------------------------ */
@font-face {
    font-family: 'Orbitron';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('../fonts/orbitron-latin-700-normal.woff2') format('woff2');
}
@font-face {
    font-family: 'Orbitron';
    font-style: normal;
    font-weight: 900;
    font-display: swap;
    src: url('../fonts/orbitron-latin-900-normal.woff2') format('woff2');
}
@font-face {
    font-family: 'Space Grotesk';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('../fonts/space-grotesk-latin-500-normal.woff2') format('woff2');
}
@font-face {
    font-family: 'Space Grotesk';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('../fonts/space-grotesk-latin-700-normal.woff2') format('woff2');
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('../fonts/inter-latin-400-normal.woff2') format('woff2');
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('../fonts/inter-latin-600-normal.woff2') format('woff2');
}

/* ------------------------------------------------------------
   Tokens
   ------------------------------------------------------------ */
:root {
    /* Brand palette — from the Mission Control brand sheet, verbatim. */
    --navy:   #0B1C3B;
    --violet: #7A4BA8;
    --lilac:  #D58BE8;
    --sky:    #6EC9E8;
    --gold:   #F6D48F;

    /* Button-safe variant of --sky. The brand sky is gorgeous as an accent on
       dark ground (9:1) but only 1.9:1 behind white text — so a violet→sky
       gradient button is unreadable at its right-hand end. This deeper tone
       reads as the same colour family and clears AA for normal text (4.7:1).
       Use --sky for text/accents, --sky-deep for anything white sits on. */
    --sky-deep: #2B7BA3;

    /* Surface ramp derived from the navy. */
    --bg-deep:   #060E22;
    --bg:        #0B1C3B;
    --surface:   #12244A;
    --surface-2: #0D1B38;   /* inset: inputs, stat wells */
    --surface-3: #172C57;   /* raised: hover, chips */

    --border:        rgba(214, 139, 232, 0.14);
    --border-strong: rgba(214, 139, 232, 0.32);

    --text:  #E8ECF8;
    --muted: #93A3C6;
    --dim:   #64749C;

    --positive: #5BD6A4;
    --negative: #FF6B8A;
    --warning:  var(--gold);

    /* Legacy aliases — earlier stages' CSS and a few inline styles still use
       these names. Kept so nothing silently loses its colour. */
    --accent:   var(--violet);
    --card-bg:  var(--surface);
    --error:    var(--negative);
    --success:  var(--positive);

    --grad-primary: linear-gradient(135deg, var(--violet), var(--sky-deep));
    --grad-title:   linear-gradient(100deg, #FFFFFF 0%, var(--lilac) 52%, var(--sky) 100%);
    --glow-violet:  0 0 32px rgba(122, 75, 168, 0.35);
    --glow-lilac:   0 0 28px rgba(213, 139, 232, 0.28);

    --font-display: 'Orbitron', 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
    --font-head:    'Space Grotesk', 'Inter', ui-sans-serif, system-ui, sans-serif;
    --font-body:    'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

    --r-sm: 10px;
    --r-md: 14px;
    --r-lg: 20px;
    --r-pill: 999px;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    color: var(--text);
    font-family: var(--font-body);
    font-size: 15px;
    line-height: 1.5;
    background-color: var(--bg-deep);
    -webkit-font-smoothing: antialiased;
}

/* ------------------------------------------------------------
   Cosmic background — pure CSS, no markup and no image files.

   Two fixed pseudo-elements on <body>: one paints the starfield, the other the
   planets and orbit rings. position:fixed matters — the participant pages
   scroll under a fixed bottom nav, and a scrolling background would tile and
   shimmer. Everything sits at z-index:-1 so real content is never covered.
   ------------------------------------------------------------ */
body::before,
body::after {
    content: '';
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: -1;
}

/* Ground + nebula + starfield.
   Stars are TILED, not placed at percentages: a fixed handful of percentage
   dots looks empty on a 2500px monitor, whereas a repeating tile keeps the
   density constant at any screen size. Three tiles at different periods and
   brightnesses give depth without any of them reading as a grid.
   The nebula blooms are kept weak and off-centre on purpose — an earlier
   version used a single 0.42-alpha violet ellipse across the whole viewport
   and it washed the navy out to flat purple, hiding the stars entirely. */
body::before {
    background-color: #050B1A;
    background-image:
        /* --- near stars: small, dense, tile every 240px --- */
        radial-gradient(1.6px 1.6px at 34px 52px,   rgba(255,255,255,0.95), transparent),
        radial-gradient(1.3px 1.3px at 158px 121px, rgba(255,255,255,0.75), transparent),
        radial-gradient(1.1px 1.1px at 96px 199px,  rgba(255,255,255,0.60), transparent),
        radial-gradient(1.4px 1.4px at 211px 38px,  rgba(255,255,255,0.80), transparent),
        radial-gradient(1.0px 1.0px at 15px 168px,  rgba(255,255,255,0.55), transparent),
        /* --- mid stars: tile every 420px, faintly blue --- */
        radial-gradient(1.8px 1.8px at 268px 96px,  rgba(214,236,255,0.85), transparent),
        radial-gradient(1.2px 1.2px at 62px 300px,  rgba(255,255,255,0.55), transparent),
        radial-gradient(1.5px 1.5px at 340px 240px, rgba(255,255,255,0.70), transparent),
        radial-gradient(1.1px 1.1px at 190px 396px, rgba(255,255,255,0.45), transparent),
        /* --- far/bright: sparse, tile every 680px, one gold --- */
        radial-gradient(2.3px 2.3px at 120px 90px,  rgba(255,255,255,0.95), transparent),
        radial-gradient(2.0px 2.0px at 470px 380px, rgba(246,212,143,0.80), transparent),
        radial-gradient(1.9px 1.9px at 610px 150px, rgba(190,220,255,0.75), transparent),
        /* --- distant comet streaks, barely there --- */
        linear-gradient(114deg, transparent 49.75%, rgba(255,255,255,0.055) 49.9%, transparent 50.05%),
        linear-gradient(104deg, transparent 49.8%,  rgba(213,139,232,0.05) 49.92%, transparent 50.04%),
        /* --- nebula blooms: weak, off-centre, so the ground stays navy --- */
        radial-gradient(ellipse 62% 42% at 20% 6%,   rgba(122, 75, 168, 0.30), transparent 66%),
        radial-gradient(ellipse 52% 34% at 86% 16%,  rgba(110, 201, 232, 0.11), transparent 62%),
        radial-gradient(ellipse 70% 44% at 58% 104%, rgba(122, 75, 168, 0.20), transparent 68%),
        /* --- the ground itself --- */
        linear-gradient(178deg, #0B1C3B 0%, #081327 46%, #050B1A 100%);
    background-size:
        240px 240px, 240px 240px, 240px 240px, 240px 240px, 240px 240px,
        420px 420px, 420px 420px, 420px 420px, 420px 420px,
        680px 680px, 680px 680px, 680px 680px,
        760px 760px, 540px 540px,
        cover, cover, cover,
        cover;
}

/* Planets. Violet upper-right, sky lower-left, as in the concept.
   Layer order is top-first, so each planet is: specular highlight, then the
   sphere, then its ring, then an atmospheric halo underneath.

   !! EVERY GRADIENT HERE MUST END ON A TRANSPARENT STOP !!
   A gradient whose final stop is opaque extends that colour across the WHOLE
   element, not just its ending shape. An earlier version ended the violet
   planet on `#3A1F5C 100%`, which flooded the entire viewport with flat purple
   and painted straight over the starfield in body::before. The `99% -> 100%`
   pairs below give a crisp, anti-aliased limb and then stop. */
body::after {
    background-image:
        /* -- violet planet, upper right -- */
        radial-gradient(circle 22px at calc(100% - 132px) 86px,
            rgba(255,255,255,0.22), rgba(255,255,255,0) 70%),
        radial-gradient(circle 46px at calc(100% - 118px) 100px,
            #A96AD8 0%, #7B45AC 52%, #4A2670 82%, #2E1748 99%, rgba(46,23,72,0) 100%),
        /* tilted ring behind it */
        radial-gradient(ellipse 96px 26px at calc(100% - 118px) 106px,
            rgba(246,212,143,0) 62%, rgba(246,212,143,0.30) 64%,
            rgba(246,212,143,0.30) 71%, rgba(246,212,143,0) 73%),
        radial-gradient(circle 92px at calc(100% - 118px) 100px,
            rgba(169,106,216,0.16), rgba(169,106,216,0) 72%),
        /* -- sky planet, lower left -- */
        radial-gradient(circle 10px at 60px calc(100% - 196px),
            rgba(255,255,255,0.28), rgba(255,255,255,0) 70%),
        radial-gradient(circle 26px at 68px calc(100% - 188px),
            #8FDDF2 0%, #46A6CC 55%, #24698D 85%, #15455F 99%, rgba(21,69,95,0) 100%),
        radial-gradient(circle 58px at 68px calc(100% - 188px),
            rgba(110,201,232,0.14), rgba(110,201,232,0) 72%);
    background-repeat: no-repeat;
}

/* Faint orbit rings, drawn as huge thin-bordered circles behind everything. */
.orbit-field {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: -1;
    overflow: hidden;
}
.orbit-field::before,
.orbit-field::after {
    content: '';
    position: absolute;
    border: 1px solid rgba(213, 139, 232, 0.11);
    border-radius: 50%;
}
.orbit-field::before {
    width: 130vmax; height: 130vmax;
    top: -55vmax; right: -50vmax;
    transform: rotate(-12deg);
}
.orbit-field::after {
    width: 90vmax; height: 90vmax;
    bottom: -48vmax; left: -38vmax;
    border-color: rgba(110, 201, 232, 0.06);
}

/* ------------------------------------------------------------
   Typography primitives
   ------------------------------------------------------------ */
h1, h2, h3 { font-family: var(--font-head); font-weight: 700; letter-spacing: 0.01em; }
h1 { margin-top: 0; }

.display {
    font-family: var(--font-display);
    font-weight: 900;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* Gradient wordmark. Falls back to lilac where background-clip:text is absent. */
.gradient-text {
    color: var(--lilac);
    background: var(--grad-title);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

/* The small letterspaced caps used above every section in the concept. */
.eyebrow {
    font-family: var(--font-head);
    font-size: 0.68rem;
    font-weight: 500;
    letter-spacing: 0.34em;
    text-transform: uppercase;
    color: var(--muted);
    margin: 0;
}

.muted, .subtitle { color: var(--muted); }
.center { text-align: center; }
.back-link { color: var(--sky); text-decoration: none; }
.back-link:hover { color: var(--lilac); }

/* ------------------------------------------------------------
   Shared primitives
   ------------------------------------------------------------ */
.panel {
    background: linear-gradient(180deg, rgba(23, 44, 87, 0.72), rgba(18, 36, 74, 0.72));
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.32);
    backdrop-filter: blur(6px);
}

/* Section label with the left accent bar from the concept. */
.section-label {
    display: flex;
    align-items: center;
    gap: 10px;
    font-family: var(--font-head);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--muted);
    margin: 24px 0 10px;
}
.section-label::before {
    content: '';
    width: 3px;
    height: 1em;
    border-radius: 2px;
    background: var(--sky);
    flex: none;
}
/* The participant/staff pages centre some of these; don't force the bar off-centre. */
.section-label.center { justify-content: center; }

input, select, textarea {
    font-family: var(--font-body);
    font-size: 1rem;
    color: var(--text);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    padding: 12px 14px;
    transition: border-color 0.15s, box-shadow 0.15s;
}
input::placeholder { color: var(--dim); }
input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--border-strong);
    box-shadow: 0 0 0 3px rgba(213, 139, 232, 0.16);
}

button, .button {
    font-family: var(--font-head);
    font-weight: 700;
    font-size: 0.9rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #fff;
    background: var(--grad-primary);
    border: none;
    border-radius: var(--r-sm);
    padding: 13px 20px;
    cursor: pointer;
    text-decoration: none;
    text-align: center;
    transition: filter 0.15s, transform 0.05s;
}
button:hover, .button:hover { filter: brightness(1.12); }
button:active, .button:active { transform: translateY(1px); }

/* Text-only button used inside table rows. */
.link-button {
    background: none;
    color: var(--sky);
    padding: 4px 8px;
    font-weight: 600;
    font-size: 0.78rem;
    letter-spacing: 0.04em;
}
.link-button:hover { background: none; filter: none; color: var(--lilac); }
.link-button.danger { color: var(--negative); }
.link-button.danger:hover { color: #ff97ad; }

/* Status pill (CLEAR / MODERATE / LIVE / FINAL). */
.pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-head);
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    padding: 4px 11px;
    border-radius: var(--r-pill);
    border: 1px solid var(--border);
    color: var(--muted);
    background: rgba(255, 255, 255, 0.04);
}
.pill.good { color: var(--positive); border-color: rgba(91, 214, 164, 0.4); }
.pill.warn { color: var(--gold);     border-color: rgba(246, 212, 143, 0.4); }
.pill.bad  { color: var(--negative); border-color: rgba(255, 107, 138, 0.4); }

/* Small labelled token, e.g. a team name at a station. */
.chip {
    display: inline-block;
    font-size: 0.72rem;
    font-weight: 600;
    padding: 3px 10px;
    border-radius: var(--r-pill);
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--border);
    color: var(--text);
}

.flash-success, .flash-error {
    border-radius: var(--r-md);
    padding: 11px 15px;
    font-size: 0.9rem;
    border: 1px solid;
}
.flash-success {
    background: rgba(91, 214, 164, 0.11);
    border-color: rgba(91, 214, 164, 0.45);
    color: #A9EBCF;
}
.flash-error {
    background: rgba(255, 107, 138, 0.11);
    border-color: rgba(255, 107, 138, 0.45);
    color: #FFAFBF;
}

/* ------------------------------------------------------------
   Mission patch — the circular emblem from the brand sheet.
   Used on the auth screens; sized with --patch-size.
   ------------------------------------------------------------ */
.mission-patch {
    --patch-size: 92px;
    width: var(--patch-size);
    height: var(--patch-size);
    margin: 0 auto 18px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    font-size: calc(var(--patch-size) * 0.38);
    line-height: 1;
    background:
        radial-gradient(circle at 50% 32%, rgba(213, 139, 232, 0.30), transparent 62%),
        radial-gradient(circle at 50% 50%, #1A3363 0%, #0C1E42 100%);
    border: 2px solid rgba(246, 212, 143, 0.55);
    box-shadow:
        0 0 0 6px rgba(11, 28, 59, 0.85),
        0 0 0 7px rgba(122, 75, 168, 0.45),
        var(--glow-violet);
}

/* Rule — label — rule, as under the wordmark in the concept. */
.rule-label {
    display: flex;
    align-items: center;
    gap: 12px;
    justify-content: center;
    font-family: var(--font-head);
    font-size: 0.66rem;
    font-weight: 500;
    letter-spacing: 0.34em;
    text-transform: uppercase;
    color: var(--gold);
    margin: 10px 0 6px;
}
.rule-label::before, .rule-label::after {
    content: '';
    height: 1px;
    width: 46px;
    background: linear-gradient(90deg, transparent, rgba(246, 212, 143, 0.55));
}
.rule-label::after { background: linear-gradient(90deg, rgba(246, 212, 143, 0.55), transparent); }

/* ------------------------------------------------------------
   Motion — one shared pulse, and an off switch that covers the lot.
   ------------------------------------------------------------ */
@keyframes mc-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
    }
}

/* ------------------------------------------------------------
   Brand marks — see includes/brand.php.
   Both render only when the image file is actually present, so
   these rules are inert until the artwork is saved.
   ------------------------------------------------------------ */

/* The orientation emblem inside the circular mission patch. The patch keeps
   its glow ring and gradient; the artwork sits on top of it, inset so the
   ring reads as a border around the mark rather than a crop of it. */
.mission-patch.has-logo {
    /* Larger than the emoji patch it replaces: the emblem carries fine detail
       (the orbit ring, the small planets) that reads as noise below ~110px. */
    --patch-size: 124px;
    /* The supplied emblem is already a circular badge with its own rings, so
       the patch's own plate would fight it — drop to a soft halo only. */
    background: radial-gradient(circle at 50% 45%, rgba(213, 139, 232, 0.22), transparent 68%);
    border-color: transparent;
    box-shadow: none;
    overflow: visible;
}
.mission-patch-logo {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
    /* Lifts the mark off the deep-navy ground; matches the violet glow used
       on the wordmark so the two read as one lockup. */
    filter: drop-shadow(0 0 14px rgba(213, 139, 232, 0.45));
}

/* The university lockup, pinned in a corner. Sized in vw with a cap so it
   stays legible on a phone and doesn't dominate a projector. */
.uni-lockup-fixed {
    position: fixed;
    top: 16px;
    left: 16px;
    z-index: 5;
    pointer-events: none;
}
.uni-lockup img {
    display: block;
    height: auto;

    /* SIZE IS DELIBERATELY SMALL — the source file is only 283x71px. A 2x
       display (most phones and laptops) needs 2 device pixels per CSS pixel,
       so anything wider than ~140px here is upscaled and goes soft. Capping at
       140 keeps it sharp everywhere. If you can export the lockup at 2-3x
       (roughly 850x210), raise this cap and it will simply look bigger and
       still be crisp — the size is limited by the artwork, not by the layout. */
    width: clamp(112px, 14vw, 140px);

    /* No background and no glow — the mark sits directly on the starfield.
       NOTE: the supplied lockup has near-black wordmarks, which is low contrast
       against the navy ground. The fix for that is a reversed (white) export of
       the logo from the university brand kit, dropped in over
       assets/images/university-logo.png — not a treatment applied here. */
}

/* In-flow variant for the projector footer and the printed banner, where a
   fixed element would either overlap the board or repeat on every page. */
.uni-lockup-inline {
    position: static;
    pointer-events: auto;
}
.uni-lockup-inline img {
    width: clamp(104px, 11vw, 132px);
}

/* Inside the admin navigation bar, sized to the row rather than the viewport
   so it reads as a site logo beside the links. */
.uni-lockup-nav {
    position: static;
    pointer-events: auto;
    margin-right: 6px;
    flex: 0 0 auto;
}
.uni-lockup-nav img { width: 104px; }

/* Top-left of the page content, on the participant and staff surfaces. In flow
   rather than pinned: .p-shell is a centred 480px column, so a viewport-pinned
   mark would sit far off to its left on a wide screen and overlap the header on
   a narrow one. Sitting inside the column keeps it aligned with the content at
   every width. */
.uni-lockup-head {
    position: static;
    pointer-events: auto;
    display: block;
    margin: 0 0 16px;
}
.uni-lockup-head img { width: 116px; }

/* On a phone the lockup would sit on top of the mission patch, so move it to
   the bottom of the page and shrink it. */
/* Phones: the pinned variant only. This MUST stay scoped to .uni-lockup-fixed —
   applied to the bare class it also transformed the in-flow variants, which is
   what pushed the participant/staff logo off the left of the screen. */
@media (max-width: 560px) {
    .uni-lockup-fixed {
        top: auto;
        bottom: 12px;
        left: 50%;
        transform: translateX(-50%);
    }
    .uni-lockup-fixed img { width: 124px; }
    .uni-lockup-head img  { width: 104px; }
}

/* Printed banners go on white stock. The white halo exists to lift dark text
   off a dark ground — on paper it would just fringe the logo, so drop it. */
@media print {
    .uni-lockup img { filter: none; }
}
