/* ============================================================================
   MEMORIZE MEDICINE — SHARED APPLICATION STYLESHEET  ("Canvas")
   ----------------------------------------------------------------------------
   PASS 2. This file is the 164 rules that were duplicated verbatim inside all
   fifteen inline <style> blocks — 16.9 KB per page, ~253 KB across the repo,
   with fifteen chances to drift on every edit. They now live here once, and
   they have been conformed to Canvas rather than merely re-tinted.

   LOAD ORDER, and why it matters:

       assets/app.css      <- this file: tokens, base, shared components
       <style> ... </style><- what remains inline is page-bespoke only
       assets/canvas.css   <- the thin layer that must outrank page-bespoke CSS

   That is exactly the order the shared block occupied before it was extracted,
   so no cascade relationship changed. Proof: a computed-style snapshot of 17
   properties on every element of all fifteen pages in both themes, before and
   after the move, differed on zero elements.

   CSP NOTE. Moving these rules out does NOT let 'unsafe-inline' come out of
   style-src, and REDESIGN-PASS1.md §5.2 was wrong to imply it would. That
   directive also governs style="..." attributes, of which 762 remain (750 in
   markup, 12 in app.js templates). See REDESIGN-PASS2.md §6 for the count and
   the route. What this file does buy is one cached stylesheet instead of
   fifteen re-parsed copies, and one place to edit.

   NO !important. Not one, anywhere in this file. Where something needed to win
   it was given the specificity to win.
   ========================================================================== */

/* ============================ 1. TYPEFACES ============================== */
/* Self-hosted, latin subset, ~104 KB total. The originals were @import'ed from
   fonts.googleapis.com, which style-src 'self' blocks outright — every page
   had been silently falling back to system fonts in production. */

@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:300 700;
  font-display:swap;src:url('./fonts/spacegrotesk-var.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
  U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
  U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Source Sans 3';font-style:normal;font-weight:200 900;
  font-display:swap;src:url('./fonts/sourcesans3-var.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
  U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
  U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Source Sans 3';font-style:italic;font-weight:200 900;
  font-display:swap;src:url('./fonts/sourcesans3-var-italic.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
  U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
  U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;
  font-display:swap;src:url('./fonts/ibmplexmono-400.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
  U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
  U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;
  font-display:swap;src:url('./fonts/ibmplexmono-500.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
  U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
  U+2215,U+FEFF,U+FFFD}

/* ============================== 2. TOKENS =============================== */
/* The app's existing variable NAMES are kept and given Canvas VALUES, so the
   ~695 lines of page-bespoke CSS re-tint themselves without being touched.
   `:root` carries dark (it is what applies when the theme attribute is absent);
   `[data-theme="light"]` carries paper, and every page ships with it. */

:root{
  /* --- dark: now the opt-in --- */
  --orange:#FFA24D; --orange-hi:#FF751F;
  --blue:#5CABE5;   --blue-hi:#8CC5F0;
  --green:#6FB79C;  --green-hi:#8CCBB3;
  --ink:#F1F4F8;

  --bg:#141920; --bg2:#1B212A;
  --glass:#1B212A; --glass2:#212833;
  --stroke:#333C49; --stroke-hi:#5CABE5;
  --text:#F1F4F8; --text2:#B7C0CB; --text3:#96A0AE;

  --danger:#E8757C; --amber:#FFBA48;

  --paper:#141920; --surface:#1B212A; --surface-2:#212833; --surface-3:#2A323E;
  --line:#333C49; --line-2:#252D38;
  --brand:#5CABE5; --brand-ink:#8CC5F0; --brand-soft:#12283A; --brand-on:#08243C;
  --accent:#FF9040; --accent-ink:#FFB273; --accent-on:#2A1305; --accent-soft:#2A1B0E;
  --jade:#6FB79C; --jade-soft:#12291F; --jade-on:#04140F;
  --crimson:#E8757C; --crimson-soft:#2C1518; --crimson-on:#2A0E10;
  --amber-soft:#2A2110; --amber-on:#241800;

  /* Radii. Kept at the Pass 1 values by decision — Canvas proper runs at 2px,
     these are deliberately softer. Changing these two lines re-rounds the
     entire application. */
  --r-obj:10px; --r-btn:8px; --r-sm:7px; --r-tag:999px;

  /* Type roles */
  --fd:'Space Grotesk',system-ui,-apple-system,'Segoe UI',sans-serif;
  --fb:'Source Sans 3',system-ui,-apple-system,'Segoe UI',Arial,sans-serif;
  --fm:'IBM Plex Mono',ui-monospace,'SF Mono',Menlo,Consolas,monospace;

  /* The border does the work now; the 50px drop shadows are gone. */
  --shadow:0 1px 2px rgba(0,0,0,.28);

  /* Static fallbacks, then the color-mix refinement. Safari below 16.2 and
     older Android WebViews — which is a real share of this app's audience —
     drop any declaration containing color-mix(). Without the plain value
     first, the dot grid, every hatch pattern and several badge borders
     silently vanish on exactly those devices. */
  --dot:rgba(241,244,248,.16);
  --dot:color-mix(in srgb,var(--text) 16%,transparent);
  --pat-c:rgba(241,244,248,.30);
  --pat-c:color-mix(in srgb,var(--text) 30%,transparent);
  --edge-brand:rgba(92,171,229,.40);
  --edge-brand:color-mix(in srgb,var(--brand) 40%,transparent);
  --edge-accent:rgba(255,144,64,.40);
  --edge-accent:color-mix(in srgb,var(--accent) 40%,transparent);
  --edge-jade:rgba(111,183,156,.40);
  --edge-jade:color-mix(in srgb,var(--jade) 40%,transparent);
  --edge-crimson:rgba(232,117,124,.40);
  --edge-crimson:color-mix(in srgb,var(--crimson) 40%,transparent);
  --edge-amber:rgba(255,186,72,.40);
  --edge-amber:color-mix(in srgb,var(--amber) 40%,transparent);
  --veil:rgba(20,25,32,.55);
  --veil:color-mix(in srgb,var(--paper) 55%,transparent);
}

[data-theme="light"]{
  /* --- paper: the default --- */
  --orange:#B45309; --orange-hi:#C2570B;
  --blue:#2570B7;   --blue-hi:#1D5397;
  --green:#376F5C;  --green-hi:#2B5748;
  --ink:#293039;

  --bg:#F6F7FA; --bg2:#FFFFFF;
  --glass:#FFFFFF; --glass2:#FBFCFE;
  --stroke:#E4E8EE; --stroke-hi:#2570B7;
  --text:#293039; --text2:#4F5763; --text3:#5C6570;

  --danger:#B93B42; --amber:#8A6008;

  --paper:#F6F7FA; --surface:#FFFFFF; --surface-2:#FBFCFE; --surface-3:#E9F0F8;
  --line:#E4E8EE; --line-2:#EDF0F5;
  --brand:#2570B7; --brand-ink:#1D5397; --brand-soft:#E7F4FF; --brand-on:#FFFFFF;
  --accent:#B45309; --accent-ink:#8F4207; --accent-on:#FFFFFF; --accent-soft:#FFF3DE;
  --jade:#376F5C; --jade-soft:#E6F1ED; --jade-on:#FFFFFF;
  --crimson:#B93B42; --crimson-soft:#FBE9EA; --crimson-on:#FFFFFF;
  --amber-soft:#FFF3DE; --amber-on:#FFFFFF;

  --shadow:0 1px 2px rgba(41,48,57,.06);

  --dot:rgba(41,48,57,.15);
  --dot:color-mix(in srgb,var(--text) 15%,transparent);
  --pat-c:rgba(41,48,57,.30);
  --pat-c:color-mix(in srgb,var(--text) 30%,transparent);
  --edge-brand:rgba(37,112,183,.40);
  --edge-brand:color-mix(in srgb,var(--brand) 40%,transparent);
  --edge-accent:rgba(180,83,9,.40);
  --edge-accent:color-mix(in srgb,var(--accent) 40%,transparent);
  --edge-jade:rgba(55,111,92,.40);
  --edge-jade:color-mix(in srgb,var(--jade) 40%,transparent);
  --edge-crimson:rgba(185,59,66,.40);
  --edge-crimson:color-mix(in srgb,var(--crimson) 40%,transparent);
  --edge-amber:rgba(138,96,8,.40);
  --edge-amber:color-mix(in srgb,var(--amber) 40%,transparent);
  --veil:rgba(246,247,250,.72);
  --veil:color-mix(in srgb,var(--paper) 72%,transparent);
}

/* Six hatch patterns code the six specialties and the four course formats.
   The PATTERN is the information and the colour is decoration, so the coding
   survives greyscale printing, a monochrome e-reader and every form of colour
   blindness. This replaces the old scheme, where the only difference between
   "Cardiology" and "Neurology" was a hue. */
:root{
  --pat-hatch:repeating-linear-gradient(45deg,var(--pat-c) 0 1.5px,transparent 1.5px 8px);
  --pat-cross:repeating-linear-gradient(45deg,var(--pat-c) 0 1.1px,transparent 1.1px 9px),
              repeating-linear-gradient(-45deg,var(--pat-c) 0 1.1px,transparent 1.1px 9px);
  --pat-dot:radial-gradient(circle at 50% 50%,var(--pat-c) 1.9px,transparent 2px) 0 0/9px 9px;
  --pat-grid:repeating-linear-gradient(0deg,var(--pat-c) 0 1px,transparent 1px 9px),
             repeating-linear-gradient(90deg,var(--pat-c) 0 1px,transparent 1px 9px);
  --pat-rule:repeating-linear-gradient(0deg,var(--pat-c) 0 1.5px,transparent 1.5px 7px);
  --pat-stip:radial-gradient(circle at 28% 30%,var(--pat-c) 1.7px,transparent 1.8px) 0 0/12px 12px,
             radial-gradient(circle at 74% 70%,var(--pat-c) 1.1px,transparent 1.2px) 0 0/12px 12px;
}

/* ============================== 3. BASE ================================= */

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}

/* The dot grid is the substrate. Objects rest on it.
   The 0.3s background transition that used to live here is gone: it produced a
   visible smear across the whole page on every theme toggle, and it made
   automated colour auditing lie — the first Pass 2 contrast run sampled
   mid-transition and reported 180 failures that did not exist. */
body{
  min-height:100vh;
  color:var(--text);
  font-family:var(--fb);
  font-size:15px; line-height:1.58; letter-spacing:-.002em;
  -webkit-font-smoothing:antialiased;
  background-color:var(--paper);
  background-image:radial-gradient(circle at 1px 1px,var(--dot) 1.2px,transparent 1.3px);
  background-size:22px 22px;
}

/* Form controls do not inherit colour from the page in any browser -- they
   take a UA default of near-black. That was invisible on the dark theme
   wherever a bespoke button set a background but no colour, which is how
   flashcards' and tutor's card-type picker rendered its label at 1.39:1.
   Normalising here fixes it once for every control in the app. */
button,input,select,textarea{font-family:inherit;color:inherit}

h1,h2,h3,h4{font-family:var(--fd);font-weight:600;letter-spacing:-.011em;line-height:1.12}
a{color:var(--brand);text-decoration:none}
a:hover{color:var(--brand-ink);text-decoration:underline;text-underline-offset:2px}
[data-theme="light"] a{color:var(--brand)}
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:var(--r-sm)}
::selection{background:var(--brand);color:var(--brand-on)}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.001ms;animation-iteration-count:1;transition-duration:.001ms}}

/* ========================= 4. THE ICON SYSTEM =========================== */
/* Pass 2 replaced the emoji icon set with drawn two-tone SVG. Emoji were the
   wrong tool for four reasons that all bite this particular product:
     - they render in the vendor's own colours, so they ignore the theme
       entirely and cannot be made to meet a contrast target;
     - they are drawn differently on every platform, so 🧾 is a receipt on iOS
       and something else on Windows;
     - many are simply missing on older Android, where they fall back to a
       tofu box;
     - screen readers announce them ("card index dividers"), which turns a
       decorative glyph into spoken noise in the middle of a label.
   The replacements take `currentColor`, so one icon works on both themes, on
   a tinted plate, and inside a filled button. They are aria-hidden, so the
   label beside them is what gets announced. */

.ic{
  width:1.15em; height:1.15em; flex:none; display:inline-block;
  vertical-align:-.19em; overflow:visible;
}
.ic .ib{fill:currentColor;opacity:.20}
.ic .is{fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
/* On a filled surface the 20% body reads as mud; lift it and thin the line. */
.btn-primary .ic .ib,.btn-accent .ic .ib,.btn-success .ic .ib,.btn-danger .ic .ib{opacity:.30}
.stat-icon .ic{width:20px;height:20px}
.nav-icon .ic{width:17px;height:17px}

/* ========================== 5. ATMOSPHERE =============================== */
/* Was three blurred gradient orbs plus a masked dot field. Canvas keeps the
   dots only, un-masked and even, so the grid reads as a substrate rather than
   as weather. */

.atmos{
  position:fixed; inset:0; z-index:-1; overflow:hidden;
  background-color:var(--paper);
  background-image:radial-gradient(circle at 1px 1px,var(--dot) 1.2px,transparent 1.3px);
  background-size:22px 22px;
}
.atmos::before{content:none}
.orb{display:none}

/* ======================= 6. OBJECTS ON THE CANVAS ======================= */
/* Glass is gone. An object is a flat surface with a hairline edge, resting on
   the grid. No blur, no inner highlight, no 50px drop shadow. */

.glass{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-obj);
  box-shadow:var(--shadow);
  position:relative;
}
.glass::before{content:none}

/* .lift — SELECTION FEEDBACK, AND WHO IS ALLOWED TO HAVE IT.
   Pass 1 gave every .lift the corner-handle treatment of an object editor.
   That was wrong, and not for the reason recorded in its own §5.1: the problem
   is not that the elements are large, it is that 26 of the 27 are not
   clickable. A blue outline and four drag handles is the strongest promise of
   interactivity an interface can make, and a dashboard stat tile cannot keep
   it — you hover it, it offers to be picked up, you click, nothing happens.
   So the handles are now scoped to things that genuinely respond to a click,
   detected structurally rather than by class name:
       a.lift, button.lift, [role="button"].lift, [tabindex].lift, .lift a…
   Everything else keeps a quiet edge change and nothing more. */
.lift{transition:border-color .14s,outline-color .14s;position:relative}
.lift:hover{transform:none;box-shadow:var(--shadow);border-color:var(--text3)}

a.lift:hover,button.lift:hover,
.lift[role="button"]:hover,.lift[tabindex]:hover,label.lift:hover{
  border-color:var(--text); outline:1.5px solid var(--brand); outline-offset:4px;
}
a.lift::after,button.lift::after,
.lift[role="button"]::after,.lift[tabindex]::after,label.lift::after{
  content:""; position:absolute; inset:-5px; pointer-events:none; opacity:0;
  transition:opacity .14s; border-radius:2px;
  background:
    linear-gradient(var(--brand),var(--brand)) left  top    /7px 7px no-repeat,
    linear-gradient(var(--brand),var(--brand)) right top    /7px 7px no-repeat,
    linear-gradient(var(--brand),var(--brand)) left  bottom /7px 7px no-repeat,
    linear-gradient(var(--brand),var(--brand)) right bottom /7px 7px no-repeat;
}
a.lift:hover::after,button.lift:hover::after,
.lift[role="button"]:hover::after,.lift[tabindex]:hover::after,label.lift:hover::after{opacity:1}

/* ========================== 7. TYPE EFFECTS ============================= */
/* The gradient-text treatment survives, on the Canvas palette. It is the one
   place a second hue is allowed, because it carries the brand line. */

.grad-text,[data-theme="light"] .grad-text{
  background:linear-gradient(100deg,var(--blue) 0%,var(--blue-hi) 48%,var(--green) 100%);
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
}
.grad-orange,[data-theme="light"] .grad-orange{
  background:linear-gradient(100deg,var(--accent),var(--accent-ink));
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
}
.eyebrow{
  font-family:var(--fm); font-size:10.5px; font-weight:500; letter-spacing:.17em;
  text-transform:uppercase; color:var(--text3);
}

/* =========================== 8. APP SHELL ============================== */

.app{display:flex;height:100vh;overflow:hidden}
.sidebar{
  width:248px; flex-shrink:0; display:flex; flex-direction:column;
  padding:18px 0 14px; background:var(--surface);
  border-right:1px solid var(--line);
}
.sidebar-logo{padding:2px 18px 16px;border-bottom:1px solid var(--line);margin-bottom:12px}
.sidebar-logo img{width:100%;max-width:176px;filter:none}
[data-theme="light"] .sidebar-logo img{filter:none}
/* The theme swap for the two mark files lives in canvas.css, not here. It has
   to outrank per-page rules like `.pnav-logo img{display:block}`, which score
   (0,1,1) against these classes' (0,1,0). Pass 1 solved that with !important;
   putting the rules in the layer that loads last solves it with the cascade.
   Leaving them here was a real regression -- in dark, BOTH marks rendered and
   the light one, being later in the DOM, was the one you saw. */

.nav-label{
  font-family:var(--fm); font-size:10px; font-weight:500; color:var(--text3);
  letter-spacing:.14em; text-transform:uppercase; padding:14px 22px 6px;
}
.nav-item{
  display:flex; align-items:center; gap:10px; margin:1px 10px; padding:8px 11px;
  border-radius:var(--r-sm); color:var(--text2); font-size:13.5px; font-weight:600;
  cursor:pointer; transition:background .15s,color .15s; position:relative;
  border:1px solid transparent; background:none; width:calc(100% - 20px);
  text-align:left; font-family:var(--fb);
}
.nav-item:hover{background:var(--surface-2);color:var(--text)}
.nav-item.active,[data-theme="light"] .nav-item.active{
  background:var(--brand-soft); border-color:transparent;
  color:var(--brand-ink); box-shadow:none;
}
/* The trace: one motif, reused. It marks the active nav item and nothing else
   in the rail, so "you are here" is never ambiguous. */
.nav-item.active::before{
  content:''; position:absolute; left:-10px; top:20%; bottom:20%; width:3px;
  border-radius:0 2px 2px 0; background:var(--brand); box-shadow:none;
}
.nav-icon{width:19px;display:flex;align-items:center;justify-content:center;color:var(--text3)}
.nav-item:hover .nav-icon{color:var(--text2)}
.nav-item.active .nav-icon{color:var(--brand)}
.nav-badge{
  margin-left:auto; font-family:var(--fm); font-size:10px; font-weight:500;
  color:var(--accent-on); background:var(--accent);
  border-radius:var(--r-tag); padding:2px 8px; box-shadow:none;
}
.sidebar-footer{
  margin-top:auto; padding:13px 18px 4px; border-top:1px solid var(--line);
  display:flex; align-items:center; gap:10px;
}
.avatar,.avatar.orange{
  width:36px; height:36px; border-radius:50%; flex-shrink:0;
  display:flex; align-items:center; justify-content:center;
  font-family:var(--fm); font-weight:500; font-size:13px;
  color:var(--text); background:var(--surface-3);
  border:1px solid var(--line); box-shadow:none;
}
.user-name{font-size:13.5px;font-weight:600}
.user-role{font-size:11.5px;color:var(--text3)}

.main{flex:1;overflow-y:auto;display:flex;flex-direction:column}
.topbar{
  display:flex; align-items:center; gap:12px; padding:13px 26px;
  position:sticky; top:0; z-index:20; flex-shrink:0;
  background:var(--paper); border-bottom:1px solid var(--line);
}
.topbar-title{font-family:var(--fd);font-size:16px;font-weight:600;letter-spacing:-.012em}
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:8px}
.search-bar{
  display:flex; align-items:center; gap:8px; background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r-sm);
  padding:7px 12px; width:230px; transition:border-color .15s; color:var(--text3);
}
.search-bar:focus-within{border-color:var(--brand)}
.search-bar input{
  background:none; border:none; outline:none; color:var(--text);
  font-family:var(--fb); font-size:13.5px; width:100%;
}
.search-bar input::placeholder{color:var(--text3)}
.icon-btn{
  /* flex:none matters. These sit in a flex row that runs out of width on a
     phone, and without it the button is squeezed to 21px -- below the 24px
     minimum target and visibly ovoid. */
  flex:none;
  width:34px; height:34px; border-radius:var(--r-sm); cursor:pointer;
  background:var(--surface); border:1px solid var(--line);
  display:flex; align-items:center; justify-content:center; font-size:15px;
  transition:border-color .15s,color .15s; position:relative; color:var(--text2);
}
.icon-btn:hover{border-color:var(--text3);color:var(--text);transform:none}
.notif-dot{
  position:absolute; top:6px; right:6px; width:7px; height:7px;
  border-radius:2px; background:var(--accent); box-shadow:none;
  border:1.5px solid var(--surface);
}
.content{padding:24px 26px 44px;flex:1}

/* ============================ 9. BUTTONS =============================== */
/* Flat, one weight of border, no gradient, no shine sweep, no lift.

   EVERY VARIANT IS WRITTEN `.btn.btn-x`, NOT `.btn-x`, and that is not a
   stylistic choice. The base stylesheet carries `[data-theme="light"] a{color:
   var(--blue)}`, which scores (0,1,1) and beats a single-class `.btn-accent
   {color:#fff}` at (0,1,0). Every CTA that is an <a> — which is most of them —
   rendered its label in link-blue on an orange fill, about 1.02:1, invisible.
   `.btn.btn-x` scores (0,2,0) and wins cleanly, with no !important anywhere. */

.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  padding:9px 16px; border-radius:var(--r-btn);
  border:1px solid var(--line); background:var(--surface); color:var(--text);
  font-family:var(--fb); font-size:14px; font-weight:600;
  cursor:pointer; position:relative; overflow:visible; box-shadow:none;
  transition:background .15s,border-color .15s,color .15s,transform .1s;
}
.btn::after{content:none}
.btn:hover{transform:none;border-color:var(--text3);box-shadow:none}
.btn:active{transform:translateY(1px)}

.btn.btn-primary{background:var(--brand);border-color:var(--brand);color:var(--brand-on);font-weight:700}
.btn.btn-primary:hover{background:var(--brand-ink);border-color:var(--brand-ink);color:var(--brand-on)}
.btn.btn-accent{background:var(--accent);border-color:var(--accent);color:var(--accent-on);font-weight:700}
.btn.btn-accent:hover{background:var(--accent-ink);border-color:var(--accent-ink);color:var(--accent-on)}
.btn.btn-success{background:var(--jade);border-color:var(--jade);color:var(--jade-on);font-weight:600}
.btn.btn-success:hover{background:var(--jade);border-color:var(--text);color:var(--jade-on)}
.btn.btn-danger{background:var(--crimson);border-color:var(--crimson);color:var(--crimson-on);font-weight:600}
.btn.btn-danger:hover{background:var(--crimson);border-color:var(--text);color:var(--crimson-on)}
.btn.btn-ghost{background:transparent;border-color:var(--line);color:var(--text2)}
.btn.btn-ghost:hover{background:var(--surface-2);color:var(--text);border-color:var(--text3)}
.btn-lg{padding:12px 22px;font-size:15px;border-radius:var(--r-btn)}
.btn-block{width:100%}
.btn:disabled{opacity:.55;cursor:not-allowed}

/* ========================= 10. STATS AND CARDS ========================== */
/* The figure leads, in mono, tabular. The icon plate carries a hatch pattern
   instead of a coloured gradient — see the note on the patterns above. */

.stat-card{padding:18px;overflow:hidden}
.stat-card .spark{display:none}
.stat-icon{
  width:40px; height:40px; border-radius:var(--r-sm); font-size:18px;
  display:flex; align-items:center; justify-content:center; margin-bottom:12px;
  box-shadow:none; background-color:var(--surface-3); border:1px solid var(--line);
}
.si-blue  {background-image:var(--pat-hatch); color:var(--brand)}
.si-orange{background-image:var(--pat-stip);  color:var(--accent)}
.si-green {background-image:var(--pat-cross); color:var(--jade)}
.si-gray  {background-image:var(--pat-rule);  color:var(--text2)}
[data-theme="light"] .si-blue{color:var(--brand)}
[data-theme="light"] .si-orange{color:var(--accent)}
[data-theme="light"] .si-green{color:var(--jade)}
.stat-val{font-family:var(--fm);font-size:28px;font-weight:500;letter-spacing:-.035em;font-variant-numeric:tabular-nums}
.stat-lbl{font-size:12.5px;color:var(--text2);margin-top:3px;font-weight:400}
.stat-note{font-size:11.5px;font-weight:600;margin-top:8px}
.up,[data-theme="light"] .up{color:var(--jade)}
.down{color:var(--crimson)}
.card-title{font-family:var(--fd);font-size:15px;font-weight:600;letter-spacing:-.012em;margin-bottom:13px}

/* ========================== 11. PROGRESS =============================== */
/* An instrument, not a decoration: square ends, no shimmer, no glow. */

.progress,[data-theme="light"] .progress{
  height:6px; border-radius:0; background:var(--surface-3);
  overflow:hidden; position:relative;
}
.progress-fill{height:100%;border-radius:0;background:var(--brand);position:relative;overflow:hidden;box-shadow:none}
.progress-fill::after{content:none}

/* ======================= 12. BADGES AND CHIPS ========================== */
/* Status never rides on colour alone — every badge keeps its word, and the
   border gives a second, non-colour cue at a glance. */

.badge,a.badge{
  display:inline-flex; align-items:center; gap:5px;
  font-family:var(--fm); font-size:10.5px; font-weight:500;
  letter-spacing:.09em; text-transform:uppercase;
  border-radius:var(--r-tag); padding:3px 10px;
  border:1px solid var(--line); background:var(--surface); color:var(--text2);
}
.bg-blue  {background:var(--brand-soft);   color:var(--brand-ink); border:1px solid var(--edge-brand)}
.bg-orange{background:var(--accent-soft);  color:var(--accent);    border:1px solid var(--edge-accent)}
.bg-green {background:var(--jade-soft);    color:var(--jade);      border:1px solid var(--edge-jade)}
.bg-red   {background:var(--crimson-soft); color:var(--crimson);   border:1px solid var(--edge-crimson)}
.bg-amber {background:var(--amber-soft);   color:var(--amber);     border:1px solid var(--edge-amber)}
.bg-gray  {background:var(--surface-2);    color:var(--text2);     border:1px solid var(--line)}
[data-theme="light"] .bg-blue{color:var(--brand-ink)}
[data-theme="light"] .bg-orange{color:var(--accent)}
[data-theme="light"] .bg-green{color:var(--jade)}
[data-theme="light"] .bg-red{color:var(--crimson)}
[data-theme="light"] .bg-amber{color:var(--amber)}

/* ============================= 13. TABLE =============================== */

.table{width:100%;border-collapse:collapse;font-size:14px}
.table th{
  text-align:left; font-family:var(--fm); font-size:9.5px; font-weight:500;
  text-transform:uppercase; letter-spacing:.14em; color:var(--text3);
  padding:10px 15px; border-bottom:1px solid var(--line);
}
.table td{padding:12px 15px;border-bottom:1px solid var(--line-2);vertical-align:middle}
.table tr:last-child td{border-bottom:none}
.table tbody tr{transition:background .14s}
.table tbody tr:hover{background:var(--surface-2)}
.mono{font-family:var(--fm);font-size:12.5px;font-variant-numeric:tabular-nums}

/* ============================= 14. FORMS =============================== */

.field{margin-bottom:16px}
.field label{
  display:block; font-family:var(--fm); font-size:10px; font-weight:500;
  color:var(--text3); margin-bottom:6px; letter-spacing:.14em; text-transform:uppercase;
}
.input,.select{
  width:100%; padding:10px 13px; border-radius:var(--r-sm);
  background:var(--surface); border:1px solid var(--line); color:var(--text);
  font-family:var(--fb); font-size:14px; transition:border-color .15s,box-shadow .15s;
}
.input:focus,.select:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-soft)}
.input::placeholder{color:var(--text3)}
.select option{background:var(--surface);color:var(--text)}
.hint{font-size:12.5px;color:var(--text3);margin-top:6px}

/* ============================= 15. CHARTS ============================== */
/* Bars are readings. Flat fill, square top, no glow, no grow animation that
   makes a figure look like it is still settling when it is not. */

.bars{display:flex;align-items:flex-end;gap:12px;height:175px;padding-top:12px}
.bar-col{flex:1;display:flex;flex-direction:column;align-items:center;gap:7px;height:100%;justify-content:flex-end}
.bar{
  width:100%; max-width:44px; border-radius:1px; background:var(--brand);
  min-height:4px; transition:background .15s; box-shadow:none;
}
.bar:hover{filter:none;transform:none;background:var(--brand-ink)}
.bar.orange{background:var(--accent);box-shadow:none}
.bar.orange:hover{background:var(--accent-ink)}
.bar-lbl{font-family:var(--fm);font-size:10px;color:var(--text3);font-weight:500;letter-spacing:.06em}
.bar-val{font-family:var(--fm);font-size:11.5px;color:var(--text2);font-weight:500;font-variant-numeric:tabular-nums}

/* Department rows. `.dept-row`/`.dept-bar`/`.dept-fill` shipped in all fifteen
   inline stylesheets and were referenced by no markup anywhere in the repo —
   dead weight, ~370 bytes × 15. They are kept here once, conformed, because
   they are cheap now and the admin department screen may yet want them. */
.dept-row{display:flex;align-items:center;gap:11px;padding:8px 0;font-size:13.5px;font-weight:600}
.dept-bar{flex:1;height:6px;border-radius:0;background:var(--surface-3);overflow:hidden}
[data-theme="light"] .dept-bar{background:var(--surface-3)}
.dept-fill{height:100%;border-radius:0;background:var(--brand)}

/* ===================== 16. DEPARTMENT / FORMAT MARKERS ================== */
/* These replace an inline style that interpolated a colour straight out of the
   database into a style attribute:

       '<span class="badge" style="background:' + E(d.color) + '22;color:'
                                                + E(d.color) + '">'

   Memorize.esc() is an HTML escaper. It stops the value breaking OUT of the
   attribute, but it does nothing about CSS syntax INSIDE it, and style-src
   still carries 'unsafe-inline'. A departments.color of
   `red;position:fixed;inset:0;z-index:99999` was accepted and applied. The
   column is admin-writable rather than student-writable, so the severity is
   low, but it is an injection sink and it is now gone: the marker is a class,
   chosen deterministically from the department id, and nothing from the
   database reaches a stylesheet. */

.dmark{
  display:inline-block; width:11px; height:11px; flex:none;
  border:1px solid var(--text3); border-radius:2px;
  background-color:var(--surface-3); vertical-align:-1px;
}
.dp-1{background-image:var(--pat-hatch)}
.dp-2{background-image:var(--pat-cross)}
.dp-3{background-image:var(--pat-dot)}
.dp-4{background-image:var(--pat-grid)}
.dp-5{background-image:var(--pat-rule)}
.dp-6{background-image:var(--pat-stip)}
.badge .dmark{width:9px;height:9px;margin-inline-end:1px}
.di.dp-1,.di.dp-2,.di.dp-3,.di.dp-4,.di.dp-5,.di.dp-6{
  background-color:var(--surface-3); color:var(--text2); border:1px solid var(--line);
}

/* Course-format tiles. `t-blue`/`t-orange`/`t-green` come out of app.js's
   CATEGORY map and used to be three hues; they are three patterns now. */
.t-blue,.t-orange,.t-green{background-color:var(--surface-3);color:var(--text2)}
.t-blue{background-image:var(--pat-hatch)}
.t-orange{background-image:var(--pat-stip)}
.t-green{background-image:var(--pat-cross)}

/* ============================ 17. UTILITY ============================== */

.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.flex{display:flex;align-items:center;gap:10px}
.flex-between{display:flex;align-items:center;justify-content:space-between;gap:10px}
.mt-8{margin-top:8px}.mt-16{margin-top:16px}.mb-8{margin-bottom:8px}.mb-16{margin-bottom:16px}.mb-24{margin-bottom:24px}
.text2{color:var(--text2)}.text3{color:var(--text3)}.small{font-size:12.5px}
.pad{padding:18px}
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.fu{animation:fadeUp .38s cubic-bezier(.2,.7,.3,1) both}
.d1{animation-delay:.05s}.d2{animation-delay:.10s}.d3{animation-delay:.15s}.d4{animation-delay:.20s}.d5{animation-delay:.25s}
.menu-btn{display:none}
.sidebar-close{display:none;width:32px;height:32px;font-size:13px;flex-shrink:0}
.nav-backdrop{
  position:fixed; inset:0; background:rgba(20,25,32,.55); z-index:45;
  opacity:0; pointer-events:none; transition:opacity .22s;
}
.nav-backdrop.show{opacity:1;pointer-events:auto}
@media(max-width:1000px){.grid-4{grid-template-columns:1fr 1fr}}
@media(max-width:760px){
  .sidebar{position:fixed;inset:0 auto 0 0;z-index:50;transform:translateX(-100%);transition:transform .22s ease;background:var(--surface)}
  .sidebar.open{transform:none;box-shadow:0 0 0 1px var(--line)}
  .menu-btn{display:flex}.sidebar-close{display:flex}.search-bar{display:none}
  .grid-2,.grid-4{grid-template-columns:1fr}.content{padding:18px 16px 34px}
}

/* ========================= 18. THE LOGO LOCKUP ========================== */
/* The mark is a retention curve: two accent rises are the reviews, two ink
   decays are the forgetting between them. The second decay lands higher than
   the first, because each review buys a longer interval and leaves you above
   where you started. It also reads as a rising M. Shipped as SVG, which is
   why 32 base64 PNG lockups left the HTML in Pass 1. */

.lock{display:inline-flex;align-items:center;gap:10px;text-decoration:none}
.lock:hover{text-decoration:none}
.lock-mark{width:34px;height:29px;flex:none;position:relative}
.lock-mark img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}
.lock-name{
  font-family:var(--fd); font-weight:600; font-size:19px;
  letter-spacing:-.02em; white-space:nowrap; color:var(--text); line-height:1.1;
}
.lock-name b{color:var(--brand);font-weight:600}
.sidebar-logo .lock-mark{width:30px;height:26px}
.sidebar-logo .lock-name{font-size:17px}

/* ============== 19. INSTALL BANNER (app-store style, top of page) ======= */

.pwa-bar{
  display:flex; align-items:center; gap:12px;
  padding:10px 16px; background:var(--surface);
  border-bottom:1px solid var(--line); position:relative; z-index:60;
}
.pwa-bar[hidden]{display:none}
.pwa-bar-mark{width:34px;height:34px;flex:none;border-radius:var(--r-sm);overflow:hidden}
.pwa-bar-mark img{width:100%;height:100%;object-fit:cover;display:block}
.pwa-bar-txt{min-width:0;line-height:1.3}
.pwa-bar-txt b{display:block;font-family:var(--fd);font-size:14.5px;font-weight:600;color:var(--text)}
.pwa-bar-txt span{display:block;font-size:12.5px;color:var(--text2)}
.pwa-bar-get{
  margin-inline-start:auto; flex:none;
  background:var(--brand); border:1px solid var(--brand); color:var(--brand-on);
  font-family:var(--fb); font-weight:700; font-size:13px;
  padding:6px 16px; border-radius:var(--r-tag); cursor:pointer;
}
.pwa-bar-get:hover{background:var(--brand-ink);border-color:var(--brand-ink)}
.pwa-bar-x{
  flex:none; width:28px; height:28px; border-radius:50%;
  display:grid; place-items:center; color:var(--text3);
  background:transparent; border:0; cursor:pointer; line-height:1;
}
.pwa-bar-x:hover{background:var(--surface-3);color:var(--text)}
@media (display-mode:standalone){.pwa-bar{display:none}}
@media (max-width:520px){
  .pwa-bar{padding:9px 12px;gap:9px}
  .pwa-bar-txt span{display:none}
}

/* ================= 20. DOWNLOAD SECTION (landing page) ================== */

.getapp{text-align:center;padding:40px 28px}
.getapp h2{font-family:var(--fd);font-size:clamp(1.5rem,3vw,2rem);font-weight:600;margin-bottom:8px}
.getapp p{color:var(--text2);max-width:52ch;margin:0 auto 22px}
.getapp-row{display:flex;gap:12px;flex-wrap:wrap;justify-content:center}
.store-btn{
  display:inline-flex; align-items:center; gap:10px;
  padding:11px 20px; min-width:186px;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-btn);
  color:var(--text); cursor:pointer; text-align:left;
  font-family:var(--fb); transition:border-color .15s,background .15s;
}
.store-btn:hover{border-color:var(--text3);background:var(--surface-2)}
.store-btn .sb-ico{font-size:20px;line-height:1;flex:none;color:var(--text2);display:flex}
.store-btn .sb-txt{line-height:1.25}
.store-btn .sb-txt small{
  display:block; font-family:var(--fm); font-size:9.5px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--text3);
}
.store-btn .sb-txt b{display:block;font-size:14.5px;font-weight:600}
.store-btn.is-live{border-color:var(--brand)}
.store-btn.is-live:hover{border-color:var(--brand-ink);background:var(--brand-soft)}
.getapp-note{margin-top:16px;font-size:12.5px;color:var(--text3)}

/* ============================ 21. FOOTER =============================== */

.foot{border-top:1px solid var(--line)}
.foot .fcol b{
  font-family:var(--fm); font-size:10.5px; letter-spacing:.14em;
  text-transform:uppercase; font-weight:500; color:var(--text3);
}
.foot .fcol a{color:var(--text2)}
.foot .fcol a:hover{color:var(--text)}
.foot-bottom{color:var(--text3);border-top:1px solid var(--line-2)}

/* ========================== 22. ACCESSIBILITY ========================== */
/* prefers-reduced-motion is handled in §3 by shortening durations rather than
   setting `animation:none`, which is safer: several panels in this app are
   revealed by an animation's end state, and killing the animation outright can
   leave them at opacity 0. */

@media (prefers-contrast:more){
  :root,[data-theme="light"]{--line:currentColor;--line-2:currentColor}
  .btn,.glass,.store-btn,.badge{border-width:1.5px}
  .ic .ib{opacity:.34}
}

/* ======================= 20. PRINT (Pass 3) ============================
   POLICY, set by the owner: flashcards and questions are protected content and
   must not print. Lecture PDFs may print, WITH the student's own annotations
   and notes — those are their work, they asked for them to be kept.

   READ THIS BEFORE RELYING ON IT. A print stylesheet is FRICTION, NOT
   PROTECTION. `@media print{display:none}` stops an honest student pressing
   Ctrl+P. It does nothing against a screenshot, "Save as PDF" with CSS
   disabled, reader mode, devtools, or a phone camera. Anyone determined has the
   content inside a minute. If real protection is wanted, it is server-side —
   per-student watermarking, short-lived signed URLs, rendering that never
   delivers a full page at once — and it is not a design pass.

   Nothing here uses !important. The suppression selectors are specific enough
   to win on their own, and the ban still holds.
   ===================================================================== */
@media print {

  /* --- shared: chrome never prints ------------------------------------ */
  .atmos, .toast, .nav, .topbar, .sidebar, .lnav, .lmain > .ltop,
  .theme-toggle, .themebtn, .m-modal-ov, .demo-badge,
  nav, aside, footer .nav-links {
    display: none;
  }
  html body {
    background: #fff;
    color: #000;
  }
  /* Scrolling panes must unwrap or their contents are guillotined at the box
     edge — the single most common way a screen print loses half its content. */
  html body .scroll, html body .pane, html body .lstage, html body .tbl-wrap {
    overflow: visible;
    max-height: none;
  }
  a[href]::after { content: ''; }        /* no URL footnotes on paper */

  /* --- protected content: refuse to print ---------------------------- */
  /* One page-filling notice replaces the body content. Done with a wrapper
     rule rather than `* {display:none}` so the notice itself survives. */
  html body.noprint > * { display: none; }
  html body.noprint::before {
    content: 'This material is protected and is not available to print. Your lecture PDFs can be printed from the lecture page.';
    display: block;
    font: 15px/1.6 Georgia, 'Times New Roman', serif;
    max-width: 46em;
    margin: 4em auto;
    text-align: center;
  }
}
