 /* ─── Tokens ─────────────────────────────────────── */
 :root {
     --bg: #0a0b11;
     --surface: #10121c;
     --surface2: #161927;
     --border: rgba(255, 255, 255, .07);
     --accent: #7c3aed;
     --accent2: #06b6d4;
     --text: #e8eaf0;
     --muted: #5a6278;
     --muted2: #7b8399;
     --danger: #ef4444;
     --radius: 14px;
 }

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

 body {
     font-family: 'DM Sans', sans-serif;
     background: var(--bg);
     color: var(--text);
     min-height: 100vh;
     display: flex;
     align-items: center;
     justify-content: center;
     padding: 1.5rem 1rem;
     position: relative;
     overflow: hidden;
 }

 /* ─── Ambient layers ──────────────────────────────── */
 body::before {
     content: '';
     position: fixed;
     inset: 0;
     background:
         radial-gradient(ellipse 70% 55% at 50% -5%, rgba(124, 58, 237, .22) 0%, transparent 55%),
         radial-gradient(ellipse 45% 35% at 85% 90%, rgba(6, 182, 212, .12) 0%, transparent 50%),
         radial-gradient(ellipse 30% 25% at 10% 80%, rgba(124, 58, 237, .07) 0%, transparent 50%);
     pointer-events: none;
 }

 /* subtle grid lines */
 body::after {
     content: '';
     position: fixed;
     inset: 0;
     background-image:
         linear-gradient(rgba(255, 255, 255, .025) 1px, transparent 1px),
         linear-gradient(90deg, rgba(255, 255, 255, .025) 1px, transparent 1px);
     background-size: 48px 48px;
     pointer-events: none;
     mask-image: radial-gradient(ellipse 80% 80% at 50% 50%, black 30%, transparent 80%);
 }

 /* ─── Layout ──────────────────────────────────────── */
 .login-wrap {
     position: relative;
     z-index: 1;
     width: 100%;
     max-width: 420px;
     animation: fadeUp .4s ease both;
 }

 @keyframes fadeUp {
     from {
         opacity: 0;
         transform: translateY(16px);
     }

     to {
         opacity: 1;
         transform: none;
     }
 }

 /* ─── Brand ───────────────────────────────────────── */
 .brand {
     display: flex;
     flex-direction: column;
     align-items: center;
     gap: 12px;
     margin-bottom: 2.25rem;
     text-align: center;
 }

 /* Shared by login.php (70px) and recover.php (42px) — see .nav-brand img
    in global.css for why the radius is a percentage. */
 .brand img {
     border-radius: 22%;
 }

 .brand-icon {
     width: 58px;
     height: 58px;
     background: linear-gradient(135deg, var(--accent), var(--accent2));
     border-radius: 16px;
     display: grid;
     place-items: center;
     font-size: 1.5rem;
     color: #fff;
     box-shadow:
         0 0 0 1px rgba(124, 58, 237, .4),
         0 0 40px rgba(124, 58, 237, .3),
         0 0 80px rgba(124, 58, 237, .12);
 }

 .brand-name {
     font-family: 'Syne', sans-serif;
     font-size: 1.75rem;
     font-weight: 800;
     letter-spacing: -.04em;
     background: linear-gradient(135deg, #e8eaf0 40%, #a78bfa);
     -webkit-background-clip: text;
     -webkit-text-fill-color: transparent;
     background-clip: text;
     line-height: 1;
 }

 .brand-tagline {
     font-size: .78rem;
     color: var(--muted);
     letter-spacing: .04em;
     text-transform: uppercase;
     font-weight: 500;
 }

 /* ─── Card ────────────────────────────────────────── */
 .login-card {
     background: var(--surface);
     border: 1px solid var(--border);
     border-radius: 20px;
     padding: 2rem 2rem 1.75rem;
     box-shadow:
         0 4px 24px rgba(0, 0, 0, .5),
         0 0 0 1px rgba(255, 255, 255, .04) inset;
     position: relative;
     overflow: hidden;
 }

 /* top glow strip */
 .login-card::before {
     content: '';
     position: absolute;
     top: 0;
     left: 10%;
     right: 10%;
     height: 1px;
     background: linear-gradient(90deg, transparent, rgba(124, 58, 237, .5), rgba(6, 182, 212, .4), transparent);
 }

 .card-head {
     margin-bottom: 1.75rem;
 }

 .card-head h2 {
     font-family: 'Syne', sans-serif;
     font-size: 1.15rem;
     font-weight: 700;
     letter-spacing: -.02em;
     color: var(--text);
 }

 .card-head p {
     font-size: .8rem;
     color: var(--muted);
     margin-top: 4px;
     line-height: 1.5;
 }

 /* ─── Fields ──────────────────────────────────────── */
 .field {
     margin-bottom: 1rem;
 }

 .field label {
     display: block;
     font-size: .73rem;
     color: var(--muted2);
     margin-bottom: 6px;
     font-weight: 500;
     letter-spacing: .03em;
     text-transform: uppercase;
 }

 .field-wrap {
     position: relative;
 }

 .field-wrap .fi {
     position: absolute;
     left: 13px;
     top: 50%;
     transform: translateY(-50%);
     color: var(--muted);
     font-size: .9rem;
     pointer-events: none;
     transition: color .2s;
 }

 .field-wrap:focus-within .fi {
     color: #a78bfa;
 }

 .field input {
     width: 100%;
     padding: 11px 14px 11px 38px;
     background: var(--surface2);
     border: 1px solid var(--border);
     border-radius: 10px;
     color: var(--text);
     font-family: 'DM Sans', sans-serif;
     font-size: .875rem;
     outline: none;
     transition: border-color .2s, box-shadow .2s;
 }

 .field input:focus {
     border-color: var(--accent);
     box-shadow: 0 0 0 3px rgba(124, 58, 237, .15);
 }

 .field input::placeholder {
     color: var(--muted);
 }

 .field input[type="password"],
 .field input[type="text"].pass-shown {
     padding-right: 42px;
 }

 .toggle-pass {
     position: absolute;
     right: 11px;
     top: 50%;
     transform: translateY(-50%);
     background: none;
     border: none;
     color: var(--muted);
     cursor: pointer;
     font-size: .88rem;
     padding: 5px;
     line-height: 1;
     transition: color .15s;
 }

 .toggle-pass:hover {
     color: var(--text);
 }

 /* ─── Error ───────────────────────────────────────── */
 .error-banner {
     display: flex;
     align-items: center;
     gap: 9px;
     background: rgba(239, 68, 68, .09);
     border: 1px solid rgba(239, 68, 68, .22);
     border-radius: 9px;
     padding: 10px 14px;
     color: #f87171;
     font-size: .8rem;
     margin-bottom: 1.25rem;
     animation: shakeIn .35s ease;
 }

 @keyframes shakeIn {
     0% {
         transform: translateX(-6px);
         opacity: 0;
     }

     50% {
         transform: translateX(4px);
     }

     100% {
         transform: none;
         opacity: 1;
     }
 }

 /* ─── Submit ──────────────────────────────────────── */
 .btn-login {
     width: 100%;
     margin-top: 1.5rem;
     padding: 12px;
     background: linear-gradient(135deg, var(--accent), #8b5cf6);
     color: #fff;
     border: none;
     border-radius: 10px;
     font-family: 'DM Sans', sans-serif;
     font-size: .9rem;
     font-weight: 600;
     cursor: pointer;
     display: flex;
     align-items: center;
     justify-content: center;
     gap: 8px;
     box-shadow: 0 4px 20px rgba(124, 58, 237, .35);
     transition: all .2s;
     letter-spacing: .01em;
 }

 .btn-login:hover {
     transform: translateY(-1px);
     box-shadow: 0 8px 30px rgba(124, 58, 237, .45);
 }

 .btn-login:active {
     transform: scale(.985);
 }

 /* ─── Hint box ────────────────────────────────────── */
 .hint-box {
     margin-top: 1.25rem;
     display: flex;
     align-items: center;
     gap: 10px;
     padding: 10px 14px;
     background: rgba(6, 182, 212, .06);
     border: 1px solid rgba(6, 182, 212, .13);
     border-radius: 9px;
     font-size: .76rem;
     color: var(--muted);
     line-height: 1.55;
 }

 .hint-box i {
     color: rgba(6, 182, 212, .6);
     font-size: .85rem;
     flex-shrink: 0;
 }

 .hint-box code {
     font-family: 'JetBrains Mono', monospace;
     font-size: .76rem;
     color: #22d3ee;
 }

 /* ─── Theme toggle ───────────────────────────────── */
 .theme-toggle {
     width: 34px;
     height: 34px;
     background: var(--surface);
     border: 1px solid var(--border);
     border-radius: 9px;
     color: var(--muted2);
     cursor: pointer;
     display: grid;
     place-items: center;
     font-size: .9rem;
     transition: all .18s;
 }

 .theme-toggle:hover {
     border-color: rgba(124, 58, 237, .5);
     color: #a78bfa;
 }

 /* ─── Light mode overrides ───────────────────────── */
 /* login.php and recover.php are the two pages that do NOT load global.css,
    which is where the preload-light rules live — so the class their <head>
    script sets did nothing here and a light-mode user got a dark flash on
    every load, the exact thing that script exists to stop.

    Scoped to .auth-page, i.e. login.php alone: recover.php sets the class
    but has no theme toggle, so it always renders with the dark tokens.
    Painting its background light would leave near-white text on grey. */
 html.preload-light body.auth-page {
     background: #f4f5f7;
 }

 body.light-mode {
     --bg: #f4f5f7;
     --surface: #ffffff;
     --surface2: #f0f1f5;
     --border: rgba(0, 0, 0, .09);
     --text: #1a1c26;
     --muted: #8a90a2;
     --muted2: #6b7280;
 }

 body.light-mode::before {
     background:
         radial-gradient(ellipse 70% 55% at 50% -5%, rgba(124, 58, 237, .10) 0%, transparent 55%),
         radial-gradient(ellipse 40% 32% at 85% 90%, rgba(6, 182, 212, .07) 0%, transparent 50%);
 }

 body.light-mode::after {
     background-image:
         linear-gradient(rgba(0, 0, 0, .04) 1px, transparent 1px),
         linear-gradient(90deg, rgba(0, 0, 0, .04) 1px, transparent 1px);
 }

 body.light-mode .login-card {
     box-shadow: 0 4px 24px rgba(0, 0, 0, .10);
 }

 /* #f87171 is tuned for the dark card. On the light ground it falls under
    the contrast a refused sign-in needs — and on the split layout the
    banner sits on the page itself, with no card behind it to help. */
 body.light-mode .error-banner {
     color: #b91c1c;
     background: rgba(220, 38, 38, .07);
     border-color: rgba(220, 38, 38, .25);
 }

 @media (max-width: 480px) {
     body {
         align-items: flex-start;
         padding-top: 2.5rem;
     }

     .brand {
         margin-bottom: 1.75rem;
     }

     .brand-icon {
         width: 50px;
         height: 50px;
         font-size: 1.25rem;
     }

     .brand-name {
         font-size: 1.5rem;
     }

     .login-card {
         padding: 1.5rem 1.25rem 1.5rem;
     }
 }

 /* ═══════════════════════════════════════════════════════════════
    SPLIT SIGN-IN LAYOUT — login.php only

    recover.php shares this stylesheet and keeps the centred .login-card,
    so nothing above this line changes and every rule below is scoped to
    .auth-page / .auth-*. The field, error-banner and button styles are
    reused as-is by both layouts.
    ═══════════════════════════════════════════════════════════════ */

 /* body is flex-centred for the card layout; the split fills the viewport
    instead. overflow must go back to auto — the panel stacks above the form
    on a narrow screen, and hidden would clip the form off the bottom. */
 body.auth-page {
     display: block;
     padding: 0;
     overflow: auto;
 }

 .auth-split {
     position: relative;
     /* lifts the grid above the fixed ambient layers painted on <body> */
     z-index: 1;
     display: grid;
     grid-template-columns: 1.02fr 1fr;
     min-height: 100vh;
 }

 /* ─── Brand panel ─────────────────────────────────── */
 /* The panel keeps its deep ground in light mode too, so it redeclares the
    colour tokens locally. Anything reused inside it — .brand-name, the stat
    chips — then reads against THIS surface instead of the page's, which is
    what stops light mode from putting grey-on-violet text in here. */
 .auth-panel {
     --text: #f2f0fb;
     --muted: rgba(242, 240, 251, .62);
     --muted2: rgba(242, 240, 251, .48);
     --border: rgba(255, 255, 255, .16);

     position: relative;
     overflow: hidden;
     display: flex;
     flex-direction: column;
     justify-content: center;
     padding: 3.25rem 3.5rem;
     color: var(--text);
     background:
         radial-gradient(ellipse 78% 58% at 18% 8%, rgba(124, 58, 237, .62) 0%, transparent 62%),
         radial-gradient(ellipse 62% 52% at 92% 88%, rgba(6, 182, 212, .34) 0%, transparent 58%),
         linear-gradient(158deg, #1b1140 0%, #0c0e1a 72%);
 }

 /* Same grid the body wears, masked toward the lockup so it fades out
    before it reaches the form. */
 .auth-panel::after {
     content: '';
     position: absolute;
     inset: 0;
     pointer-events: none;
     background-image:
         linear-gradient(rgba(255, 255, 255, .055) 1px, transparent 1px),
         linear-gradient(90deg, rgba(255, 255, 255, .055) 1px, transparent 1px);
     background-size: 44px 44px;
     -webkit-mask-image: radial-gradient(ellipse 85% 70% at 25% 20%, #000 10%, transparent 78%);
     mask-image: radial-gradient(ellipse 85% 70% at 25% 20%, #000 10%, transparent 78%);
 }

 .auth-panel>* {
     position: relative;
     z-index: 1;
 }

 .auth-lock {
     display: flex;
     align-items: center;
     gap: 16px;
 }

 /* The logo carries most of the size here, and the wordmark stays under the
    headline below it. A mark reads by area, so a big one anchors the panel
    without competing; type that size would just be a second headline. */
 /* Percentage radius, same reasoning as .brand img above. */
 .auth-lock img {
     width: 70px;
     height: 70px;
     border-radius: 22%;
     flex-shrink: 0;
 }

 .auth-lock .brand-name {
     font-size: 1.7rem;
 }

 .auth-school {
     font-size: .82rem;
     color: var(--muted);
     margin-top: 4px;
 }

 /* The headline is Branding's tagline, so its length is not ours to
    control — hence the clamp and the character-based max-width.

    The auto margins here and on .auth-features are what balance the panel:
    the lockup stays pinned under the top padding, and the free space splits
    evenly above the headline and below the list, centring that pair in
    whatever is left. Pushing the last block to the very bottom instead left
    a void down the middle of the panel. */
 .auth-pitch {
     font-family: 'Syne', sans-serif;
     font-weight: 700;
     font-size: clamp(1.6rem, 2.2vw, 2rem);
     line-height: 1.15;
     letter-spacing: -.025em;
     margin-top: auto;
     padding-top: 2.75rem;
     max-width: 20ch;
     text-wrap: balance;
 }

 .auth-features {
     margin-bottom: auto;
     padding-top: 2.25rem;
     max-width: 30rem;
 }

 .auth-feat-list {
     list-style: none;
     display: flex;
     flex-direction: column;
     gap: 1.15rem;
 }

 /* The extra copies only exist for the narrow-screen ticker. */
 .auth-feat-clone {
     display: none;
 }

 .auth-features li {
     display: flex;
     align-items: flex-start;
     gap: 14px;
 }

 /* The icon element is the tile itself — the glyph is a ::before, so it
    centres inside the grid without another wrapper. */
 .auth-features li>i {
     flex-shrink: 0;
     display: grid;
     place-items: center;
     width: 34px;
     height: 34px;
     border-radius: 10px;
     background: rgba(255, 255, 255, .07);
     border: 1px solid var(--border);
     font-size: .95rem;
     color: #c4b5fd;
 }

 .auth-features b {
     display: block;
     font-size: .92rem;
     font-weight: 600;
     line-height: 1.35;
 }

 .auth-features span {
     display: block;
     font-size: .78rem;
     color: var(--muted);
     line-height: 1.5;
 }

 /* ─── Form side ───────────────────────────────────── */
 .auth-form {
     position: relative;
     display: flex;
     align-items: center;
     justify-content: center;
     padding: 3.25rem 2.5rem;
 }

 .auth-form-inner {
     width: 100%;
     max-width: 370px;
     animation: fadeUp .4s ease both;
 }

 /* Above .auth-panel > *, which is raised to 1 to clear the panel's grid
    overlay. On a narrow screen the toggle sits over the band, and .auth-lock
    is a full-width block — the logo is on the left but the element still
    covers the corner, so at the same level it swallows the tap. */
 .auth-theme {
     position: absolute;
     top: 1.5rem;
     right: 1.75rem;
     z-index: 2;
 }

 .auth-kicker {
     font-family: 'JetBrains Mono', monospace;
     font-size: .625rem;
     letter-spacing: .2em;
     text-transform: uppercase;
     color: var(--muted);
     margin-bottom: .8rem;
 }

 /* The head sits on the page ground here rather than inside a card, so it
    carries the weight the card's border used to. */
 .auth-form .card-head {
     margin-bottom: 1.5rem;
 }

 .auth-form .card-head h2 {
     font-size: 1.5rem;
 }

 .auth-form .card-head p {
     font-size: .84rem;
 }

 .auth-credit {
     margin-top: 1.9rem;
     padding-top: 1.1rem;
     border-top: 1px solid var(--border);
     display: flex;
     flex-wrap: wrap;
     align-items: center;
     gap: 6px 14px;
     font-size: .72rem;
     color: var(--muted);
 }

 .auth-credit span {
     display: inline-flex;
     align-items: center;
     gap: 6px;
 }

 .auth-credit strong {
     color: var(--muted2);
     font-weight: 500;
 }

 .auth-credit .bi-lightning-charge-fill {
     color: var(--accent);
 }

 /* In the split the fields sit directly on the page ground instead of on a
    card, and in light mode --surface2 is a grey barely off the background —
    the field would lose its edge, so the fill goes white on this layout. */
 body.light-mode .auth-form {
     --surface2: #ffffff;
 }

 /* ─── Split responsive ────────────────────────────── */
 /* The panel does not stack — it collapses to a brand band. A tall visual
    panel above a login form is scrolling nobody performs, and the form has
    to start where a thumb already is. */
 @media (max-width: 900px) {
     .auth-split {
         grid-template-columns: 1fr;
         /* auto / 1fr, not the default even stretch. Two rows sharing a
            100vh minimum split it between them, which grew a dead block of
            gradient under the lockup and left the form pinned to the top of
            its own row with the same void underneath. */
         grid-template-rows: auto 1fr;
     }

     .auth-panel {
         justify-content: flex-start;
         padding: 1.5rem 1.25rem;
     }

     /* Nearly the desktop size on a fraction of the width, and the wordmark
        is actually the larger of the two: the headline is hidden here, so
        there is nothing for it to compete with and it becomes the only
        branding a phone gets. */
     .auth-lock img {
         width: 66px;
         height: 66px;
     }

     .auth-lock .brand-name {
         font-size: 1.75rem;
     }

     .auth-school {
         font-size: .84rem;
     }

     /* The tagline is the one thing that goes: it is the tallest block here
        and the chips below say more in less room. */
     .auth-pitch {
         display: none;
     }

     /* Same markup, read as a ticker — icon and title only, sliding on its
        own. The band was flat gradient before this, and it fills it without
        pushing the form out of thumb reach. It bleeds past the panel padding
        so the chips run edge to edge, and the mask fades them out at both
        sides instead of letting them stop against a hard cut. */
     .auth-features {
         display: flex;
         max-width: none;
         margin-bottom: 0;
         margin-inline: -1.25rem;
         padding-top: 1.1rem;
         overflow: hidden;
         -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
         mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
     }

     .auth-feat-clone {
         display: flex;
     }

     /* Every copy slides one full copy-width, so copy 2 lands exactly where
        copy 1 began and the restart is invisible. The trailing padding is
        what carries the gap between copies — a gap on the flex container
        would sit outside the width the transform is measured against, and
        the loop would jump by exactly that much. */
     .auth-feat-list {
         flex: none;
         flex-direction: row;
         gap: 8px;
         padding-right: 8px;
         animation: ffTicker 24s linear infinite;
     }

     /* Hovering is not how a phone reads this, but a mouse on a small window
        is, and a strip that stops when pointed at is easier to read. */
     .auth-features:hover .auth-feat-list {
         animation-play-state: paused;
     }

     @keyframes ffTicker {
         to {
             transform: translateX(-100%);
         }
     }

     .auth-features li {
         flex: none;
         align-items: center;
         gap: 8px;
         background: rgba(255, 255, 255, .05);
         border: 1px solid var(--border);
         border-radius: 999px;
         padding: 6px 13px 6px 7px;
     }

     .auth-features li>i {
         width: 22px;
         height: 22px;
         border: 0;
         background: none;
         font-size: .8rem;
     }

     .auth-features b {
         font-size: .72rem;
         font-weight: 500;
         white-space: nowrap;
     }

     .auth-features span {
         display: none;
     }

     /* Static on purpose: it hands the toggle's containing block back to
        .auth-split, so the button lands in the band's corner instead of
        floating loose over the top of the form. */
     .auth-form {
         position: static;
         padding: 2.25rem 1.5rem 2.75rem;
     }

     /* 34px is a fine mouse target and a poor thumb one, and this is the
        only control in the band. */
     .auth-theme {
         top: 1.15rem;
         right: 1.25rem;
         width: 40px;
         height: 40px;
     }
 }

 /* One step down from the band above, not back to the desktop size — a small
    phone has less width, not less need for the brand. */
 @media (max-width: 480px) {
     .auth-lock img {
         width: 56px;
         height: 56px;
     }

     .auth-lock .brand-name {
         font-size: 1.5rem;
     }

     .auth-school {
         font-size: .78rem;
     }

     .auth-form .card-head h2 {
         font-size: 1.35rem;
     }
 }

 @media (prefers-reduced-motion: reduce) {

     .login-wrap,
     .auth-form-inner,
     .error-banner,
     .auth-feat-list {
         animation: none;
     }
 }

 /* A strip that moves on its own is the exact thing this setting asks us to
    stop, so the ticker becomes a row the reader moves themselves. The clones
    have to go with it — they are duplicate text the moment nothing slides. */
 @media (max-width: 900px) and (prefers-reduced-motion: reduce) {
     .auth-feat-clone {
         display: none;
     }

     .auth-features {
         overflow-x: auto;
         scrollbar-width: none;
     }

     .auth-features::-webkit-scrollbar {
         display: none;
     }
 }