/* <Language> for <name> — stylesheet
   System fonts only, so the page works with no network connection. */

/* ---------------------------------------------------------------- tokens */
:root {
  --red: #ba0c2f;          /* Norwegian flag red */
  --red-soft: #e8536f;
  --blue: #00205b;         /* Norwegian flag blue */
  --blue-soft: #3d6bc4;
  --green: #008c45;        /* Italian flag green */
  --gold: #a87c00;         /* German flag gold, darkened to stay readable on light */
  --orange: #d1610a;       /* Spanish flag red warmed toward its gold stripe */
  --crimson: #c8102e;      /* Dannebrog red */
  /* The streak fire — the one colour no course owns. Dark enough that white
     sits on it legibly, since the filled calendar days are exactly that. */
  --flame: #d24a06;

  --bg: #f4f6fb;
  --bg-tint: #e9edf7;
  --surface: #ffffff;
  --surface-2: #f7f8fc;
  --border: #dde2ee;
  --border-strong: #c3cbdf;

  --text: #182034;
  --text-soft: #5b6580;
  --text-faint: #8b94ab;

  --good: #0f8a5f;
  --good-bg: #e4f6ee;
  --bad: #c22c3d;
  --bad-bg: #fdeaec;

  /* The admin mark. A real yellow rather than --gold, which is darkened for
     use as *text* and reads brown as a fill; this one is only ever a fill,
     so it stays bright and carries dark ink. That pairing is why it needs no
     light/dark variants worth speaking of: yellow is a light colour in both
     themes, so the ink on it is dark in both. */
  --admin: #f2c015;
  --admin-ink: #3a2b00;

  /* --------------------------------------------- the korepetycje scrapbook

     Paper, tape, grass and stones. These were literal colours until this side
     gained a light/dark button of its own; they are tokens now for one
     reason, which is that it is the only way the dark version is a short list
     of substitutions rather than a second copy of every rule.

     Warm rather than grey all the way through, in both themes: this is a
     sheet of paper on a field, and the moment the paper picks up the app's
     blue-grey surface it stops reading as paper. */
  --paper: #fffdf5;
  --paper-ink: #463c28;
  --paper-dot: rgba(150, 124, 82, .17);
  --paper-dot-soft: rgba(150, 124, 82, .09);
  --paper-stitch: rgba(150, 124, 82, .38);
  --paper-shadow: rgba(92, 71, 38, .14);
  --tape: rgba(109, 111, 248, .32);
  --tape-soft: rgba(109, 111, 248, .15);
  --tape-edge: rgba(255, 255, 255, .55);

  --stone-top: #ffffff;
  --stone-bottom: #f4fbf1;
  --stone-edge: #8cc784;
  --stone-rim: #a9d7a1;
  --stone-ink: #2f5d2b;
  --stone-shadow: rgba(47, 93, 43, .18);
  --stone-done-top: #5cb85c;
  --stone-done-bottom: #479f47;
  --stone-done-edge: #3d8b3d;
  --stone-done-ink: #ffffff;
  --stone-shut-top: #eef1ec;
  --stone-shut-bottom: #e3e8e0;
  --stone-shut-edge: #c6cfc2;
  --stone-shut-ink: #97a294;

  /* The course colour made safe to carry text: darkened so a white label on
     a solid fill (Start, Check, Next) keeps its contrast, and so the accent
     itself clears 4.5:1 as small text — gold and orange miss that floor
     undarkened. Dark mode skips the darkening: there the label is dark and
     the fill wants to stay bright. Large accent text (the stat figures) uses
     the raw colour, which only owes 3:1. */
  --accent: color-mix(in srgb, var(--course-accent) 82%, #000);
  --accent-contrast: #ffffff;

  /* The sign-in screen belongs to no course, so it doesn't borrow a course
     colour — it stays red whichever language was last open. Darkened on the
     same principle as --accent so a white label clears 4.5:1. */
  --auth-accent: color-mix(in srgb, var(--red) 82%, #000);

  /* Flag colour of the course being studied — Norwegian blue by default,
     overridden per course below. Resolved lazily, so it follows the light
     and dark values of the colour it points at.

     The lifted --blue-soft rather than the flag's own #00205b: that navy is
     so dark it reads as grey at tint strength, and the solid fill darkens it
     further into a near-black button. */
  --course-accent: var(--blue-soft);

  /* The mascot's colour. It follows the course the same way --course-accent
     does, but it cannot simply *be* --course-accent: those five are tuned to
     carry text on the page background, and the chameleon is a solid animal on
     a blue-violet tile holding a pale blue globe, so it has to clear both.
     --blue-soft vanishes into that tile, and --gold is dark enough to read
     brown as a fill. These are the same five flag colours pushed to where
     they still separate from the backdrop and from the globe.

     Unlike the tokens above it takes no dark-mode variant: the tile the
     chameleon sits on is part of the artwork and is the same in both themes,
     so the colour that works on it works everywhere.

     This one defaults to the brand green and names Norwegian explicitly,
     where --course-accent above defaults *to* Norwegian. The sign-in screen
     belongs to no course and sets no data-course, and the green is the
     colour assets/favicon.svg already ships in — so the tab icon on that
     screen is the brand animal, and repainting it there changes nothing. */
  --logo-accent: #22c55e;

  /* One height for everything sitting in the topbar row — the icon buttons and
     the Translations select alike. Sharing a token is what keeps them level;
     centring alone lines up their middles but leaves the edges ragged. */
  --control-h: 38px;

  /* The tinted-button recipe, in one place: a translucent wash of the course
     colour over the card surface, plus an edge tinted the same way, at rest
     and under the cursor. Used by the home menu cards and by the korepetycje
     class and subject buttons, so all three colour themselves the same way
     from whatever --course-accent is in play. */
  --accent-wash: color-mix(in srgb, var(--course-accent) 20%, transparent);
  --accent-wash-strong: color-mix(in srgb, var(--course-accent) 30%, transparent);
  --accent-edge: color-mix(in srgb, var(--course-accent) 38%, var(--border));
  --accent-edge-strong: color-mix(in srgb, var(--course-accent) 60%, var(--border));

  --radius: 14px;
  --radius-lg: 22px;
  --shadow: 0 1px 2px rgba(16, 24, 48, .06), 0 8px 24px rgba(16, 24, 48, .06);
  --shadow-lift: 0 2px 4px rgba(16, 24, 48, .08), 0 16px 40px rgba(16, 24, 48, .12);

  --maxw: 940px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  color-scheme: light; /* keeps scrollbars and native controls in step with the theme */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #0f1218;
    --bg-tint: #161b25;
    --surface: #1a1f2b;
    --surface-2: #212736;
    --border: #2c3444;
    --border-strong: #3d4759;

    --text: #eaeef7;
    --text-soft: #a3adc2;
    --text-faint: #78829a;

    --red: #ff5c76;
    --red-soft: #ff8095;
    --blue: #7fa5f0;
    --blue-soft: #9dbaf5;
    --green: #3ecf7e;
    --gold: #ffce3a;
    --orange: #ff9448;
    --crimson: #ff6b7f;
    --flame: #ff922b;

    --good: #4ade9b;
    --good-bg: #10281f;
    --bad: #ff7a8a;
    --bad-bg: #2b1519;

    --admin: #ffd23f;
    --admin-ink: #332600;

    /* The scrapbook after dark. Paper goes to a warm brown-black rather than
       the app's surface, and the meadow to a field at dusk — the point is
       that it still reads as paper and grass, not as the light version with
       the lamp turned off. */
    --paper: #262019;
    --paper-ink: #ece2d2;
    --paper-dot: rgba(214, 186, 140, .13);
    --paper-dot-soft: rgba(214, 186, 140, .07);
    --paper-stitch: rgba(214, 186, 140, .3);
    --paper-shadow: rgba(0, 0, 0, .5);
    --tape: rgba(146, 148, 255, .34);
    --tape-soft: rgba(146, 148, 255, .17);
    --tape-edge: rgba(255, 255, 255, .14);

    --stone-top: #2d3d2e;
    --stone-bottom: #233225;
    --stone-edge: #52804d;
    --stone-rim: #31492c;
    --stone-ink: #c2e0bb;
    --stone-shadow: rgba(0, 0, 0, .5);
    --stone-done-top: #3f8f45;
    --stone-done-bottom: #327538;
    --stone-done-edge: #67b066;
    --stone-done-ink: #f0fbee;
    --stone-shut-top: #232a25;
    --stone-shut-bottom: #1c231e;
    --stone-shut-edge: #35402f;
    --stone-shut-ink: #6b7765;

    --accent: var(--course-accent);
    --auth-accent: var(--red);
    --accent-contrast: #12161f;
    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .35);
    --shadow-lift: 0 2px 4px rgba(0, 0, 0, .45), 0 16px 40px rgba(0, 0, 0, .5);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0f1218;
  --bg-tint: #161b25;
  --surface: #1a1f2b;
  --surface-2: #212736;
  --border: #2c3444;
  --border-strong: #3d4759;

  --text: #eaeef7;
  --text-soft: #a3adc2;
  --text-faint: #78829a;

  --red: #ff5c76;
  --red-soft: #ff8095;
  --blue: #7fa5f0;
  --blue-soft: #9dbaf5;
  --green: #3ecf7e;
  --gold: #ffce3a;
  --orange: #ff9448;
  --crimson: #ff6b7f;
  --flame: #ff922b;

  --good: #4ade9b;
  --good-bg: #10281f;
  --bad: #ff7a8a;
  --bad-bg: #2b1519;

  --admin: #ffd23f;
  --admin-ink: #332600;

  /* Kept in step with the prefers-color-scheme block above. */
  --paper: #262019;
  --paper-ink: #ece2d2;
  --paper-dot: rgba(214, 186, 140, .13);
  --paper-dot-soft: rgba(214, 186, 140, .07);
  --paper-stitch: rgba(214, 186, 140, .3);
  --paper-shadow: rgba(0, 0, 0, .5);
  --tape: rgba(146, 148, 255, .34);
  --tape-soft: rgba(146, 148, 255, .17);
  --tape-edge: rgba(255, 255, 255, .14);

  --stone-top: #2d3d2e;
  --stone-bottom: #233225;
  --stone-edge: #52804d;
  --stone-rim: #31492c;
  --stone-ink: #c2e0bb;
  --stone-shadow: rgba(0, 0, 0, .5);
  --stone-done-top: #3f8f45;
  --stone-done-bottom: #327538;
  --stone-done-edge: #67b066;
  --stone-done-ink: #f0fbee;
  --stone-shut-top: #232a25;
  --stone-shut-bottom: #1c231e;
  --stone-shut-edge: #35402f;
  --stone-shut-ink: #6b7765;

  --accent: var(--course-accent);
  --auth-accent: var(--red);
  --accent-contrast: #12161f;
  --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .35);
  --shadow-lift: 0 2px 4px rgba(0, 0, 0, .45), 0 16px 40px rgba(0, 0, 0, .5);
}

/* ---------------------------------------------------------------- base */
* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  color: var(--text);
  background:
    radial-gradient(1100px 520px at 12% -12%, var(--bg-tint), transparent 60%),
    radial-gradient(900px 460px at 92% 0%, var(--bg-tint), transparent 55%),
    var(--bg);
  background-attachment: fixed;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { line-height: 1.2; margin: 0; letter-spacing: -.02em; }

button, input, select { font: inherit; color: inherit; }

button { cursor: pointer; }

[hidden] { display: none !important; }

:focus-visible {
  outline: 2px solid var(--blue-soft);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ---------------------------------------------------------------- topbar */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px max(16px, calc((100vw - var(--maxw)) / 2));
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  backdrop-filter: saturate(1.6) blur(12px);
  border-bottom: 1px solid var(--border);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: none;
  border: 0;
  padding: 6px 8px;
  margin-left: -8px;
  border-radius: 10px;
  color: inherit;
}

.brand:hover { background: var(--surface-2); }
.brand-flag { font-size: 20px; }
.brand-text { font-weight: 650; letter-spacing: -.01em; }
.brand-text em { font-style: normal; font-weight: 400; color: var(--text-soft); }
.brand-caret { font-size: 11px; color: var(--text-soft); margin-left: -2px; }
.brand[aria-expanded="true"] { background: var(--surface-2); }
/* Off the home screen the brand goes home instead of opening the picker —
   no menu, so no caret promising one. */
.brand.is-home-link .brand-caret { display: none; }

/* ---------------------------------------------------- course picker menu */
/* flex, not just a positioned block, so the brand inside is a proper flex item
   that can be told to shrink on a narrow screen — see the topbar rules in the
   620px block. The menu is absolutely positioned, so it sits outside all this. */
.course-picker { position: relative; display: flex; }

.course-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: -8px;
  z-index: 30;
  min-width: 230px;
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .18);
}

.course-menu[hidden] { display: none; }

.course-option {
  display: grid;
  grid-template-columns: 22px 1fr 16px;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  background: none;
  border: 0;
  border-radius: 8px;
  color: inherit;
  font-size: 14px;
  text-align: left;
}

.course-option:hover { background: var(--surface-2); }
.course-flag { font-size: 18px; }

/* The way out of the language app entirely, at the foot of the picker. Ruled
   off because it isn't a sixth course -- it leaves the set above it. */
.course-leave {
  grid-template-columns: 22px 1fr;
  margin-top: 4px;
  padding-top: 11px;
  border-top: 1px solid var(--border);
  border-radius: 0 0 8px 8px;
  color: var(--text-soft);
}
.course-option.is-current .course-label { font-weight: 650; }
.course-tick { color: var(--blue); opacity: 0; font-size: 12px; }
.course-option.is-current .course-tick { opacity: 1; }

.topbar-actions { display: flex; align-items: center; gap: 10px; }

.field-label { font-size: 12px; color: var(--text-soft); }

select, input[type="search"], input[type="text"], input[type="email"], input[type="password"] {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 7px 10px;
  color: var(--text);
}

select { cursor: pointer; }

/* ---------------------------------------------------------------- auth */
.field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 14px; }
.field input { width: 100%; padding: 10px 12px; }

/* Everything clickable on the sign-in screen is red rather than the course
   colour: both the solid buttons and the links that swap between the forms. */
#view-auth .primary-btn {
  background: var(--auth-accent);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--red) 30%, transparent);
}

#view-auth .link-btn { color: var(--auth-accent); }

.auth-card { max-width: 380px; margin: 0 auto; }

/* The "check your spam folder" note — quieter than the instruction above it,
   but the search string has to stay readable enough to retype. */
.auth-hint {
  margin: 0 0 16px;
  padding: 11px 13px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 13px;
  color: var(--text-soft);
}

.auth-hint code {
  padding: 1px 5px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  border-radius: 5px;
  font-size: 12px;
  color: var(--text);
  white-space: nowrap;
}
.auth-form .primary-btn { width: 100%; margin-top: 4px; }
.auth-switch { margin: 14px 0 0; font-size: 13px; color: var(--text-soft); text-align: center; }

/* The korepetycje question on the sign-up form. A fieldset, so the two radios
   read as one question rather than two loose controls -- and boxed off, since
   it's the only part of this form that isn't an account detail. */
.signup-ask {
  margin: 0 0 14px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-2);
}

.signup-ask legend {
  padding: 0 4px;
  font-size: 13px;
  font-weight: 600;
}

.signup-ask-options { display: flex; gap: 10px; margin: 8px 0 10px; }

/* Whole-pill targets rather than a radio with a label beside it: on a phone
   the dot alone is a small thing to hit accurately. */
.signup-ask-option {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 14px;
  cursor: pointer;
}

.signup-ask-option:has(input:checked) {
  border-color: var(--auth-accent);
  background: color-mix(in srgb, var(--auth-accent) 10%, var(--surface));
  font-weight: 600;
}

.signup-ask .auth-hint { margin: 0; }

.icon-btn {
  width: var(--control-h);
  height: var(--control-h);
  display: grid;
  place-items: center;
  font-size: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  transition: transform .12s ease, background .12s ease;
}

.icon-btn:hover { background: var(--surface-2); transform: translateY(-1px); }
.icon-btn.is-on { color: var(--course-accent); border-color: var(--course-accent); }

/* Interface language: a two-letter code rather than a glyph, so it needs to
   sit a size down from the icons it lines up with. */
.lang-btn { font-size: 12px; font-weight: 700; letter-spacing: .04em; }

/* The one filled button in the topbar, and the only yellow thing in the app.
   Solid rather than outlined because it isn't a setting like the two beside
   it -- it goes somewhere, and it goes somewhere almost nobody can. */
.admin-btn {
  background: var(--admin);
  border-color: color-mix(in srgb, var(--admin) 72%, #000);
  color: var(--admin-ink);
  font-weight: 800;
  font-size: 15px;
  letter-spacing: .02em;
}

.admin-btn:hover { background: color-mix(in srgb, var(--admin) 86%, #fff); }

/* Holds the profile button and the badge that overlaps its edge. Sized by
   the button inside it, so it stays level with the rest of the row. */
.avatar-slot { position: relative; display: inline-flex; }

/* The same yellow again, saying whose account this is. It sits over the
   picture's bottom-right, and the surface-coloured ring is what keeps it
   legible there — over a photograph, a badge with no ring reads as part of
   the image. */
.admin-mark {
  position: absolute;
  right: -3px;
  bottom: -3px;
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--admin);
  color: var(--admin-ink);
  font-size: 10px;
  font-weight: 800;
  line-height: 1;
  box-shadow: 0 0 0 2px var(--surface);
  pointer-events: none; /* the button underneath owns the whole target */
}

/* ---------------------------------------------------------------- layout */
#main {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 28px max(16px, 4vw) 80px;
}

.view { animation: rise .28s ease both; }

@keyframes rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* ---------------------------------------------------------------- home */
.hero { padding: 36px 0 30px; text-align: center; }

.hero-hello {
  margin: 0 0 10px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  /* --accent, not the raw course colour: at 15px this is small text, and the
     lighter accents (gold, orange) miss the 4.5:1 floor undarkened. */
  color: var(--accent);
}

/* The greeting — and the menu cards under it — take the flag colour of the
   course being studied: Norwegian red (the default), Italian green,
   German gold, Spanish orange, Danish crimson. Spain flies red *and* gold,
   so its accent sits between the two, clear of the German gold; Denmark and
   Norway both fly red, so the Dannebrog crimson is warmed to tell them
   apart. */
html[data-course="italian"] { --course-accent: var(--green); }
html[data-course="german"] { --course-accent: var(--gold); }
html[data-course="spanish"] { --course-accent: var(--orange); }
html[data-course="danish"] { --course-accent: var(--crimson); }

/* The same five courses, in the logo's own register — see --logo-accent. */
html[data-course="norwegian"] { --logo-accent: #1d4ed8; }
html[data-course="italian"] { --logo-accent: #00b359; }
html[data-course="german"] { --logo-accent: #e8a900; }
html[data-course="spanish"] { --logo-accent: #f2760f; }
html[data-course="danish"] { --logo-accent: #e02840; }

/* The korepetycje side isn't a language and flies no flag, so it drops the
   course colour for the app's own indigo — the one already in the manifest's
   theme-color. After the five rules above deliberately: it has to win over
   whichever course was last open, and both are one attribute deep. */
html[data-side="korepetycje"] {
  --course-accent: #6d6ff8;
  --logo-accent: #6d6ff8;
}

.hero h1 {
  font-size: clamp(30px, 5.6vw, 46px);
  font-weight: 700;
}

.hero-sub { margin: 12px 0 0; color: var(--text-soft); }

.menu {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px;
}

.menu-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  text-align: left;
  padding: 24px 22px 22px;
  /* Washed in the course colour, so the cards echo the greeting above them
     without swamping the text. */
  background: linear-gradient(var(--accent-wash) 0 100%), var(--surface);
  border: 1px solid var(--accent-edge);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}

.menu-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lift);
  background: linear-gradient(var(--accent-wash-strong) 0 100%), var(--surface);
  border-color: var(--accent-edge-strong);
}

.menu-icon { font-size: 30px; line-height: 1; margin-bottom: 6px; }
.menu-title { font-size: 20px; font-weight: 650; }
.menu-desc { font-size: 14px; color: var(--text-soft); }

/* ------------------------------------------------------------ which side?

   Two cards, one question. Deliberately not the .menu grid: that one holds
   five or six activities and reflows to fit them, while this is a fork in the
   road with exactly two ways on, so the pair stays side by side until the
   screen is too narrow to hold them. */
.choose-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
  max-width: 640px;
  margin: 10px auto 0;
}

.choose-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 30px 22px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}

.choose-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lift);
  border-color: var(--border-strong);
}

.choose-card strong { font-size: 20px; font-weight: 650; }
.choose-card em { font-style: normal; font-size: 14px; color: var(--text-soft); }

/* The five flags, stacked tight enough to read as one mark rather than as a
   row of five separate ones. */
.choose-flags { font-size: 26px; line-height: 1; letter-spacing: -2px; }

/* ------------------------------------------------------------ korepetycje

   The class question, and the answer it becomes. Both are centred under the
   greeting, so the one replaces the other in place rather than shifting the
   page around when it's answered. */
.kor-ask, .kor-chosen { text-align: center; }

.kor-ask-label { margin: 0 0 18px; font-size: 18px; color: var(--text-soft); }

/* A pyramid: two classes on top, three below, both rows centred on the same
   axis so the shape reads however wide the screen is. */
.kor-classes { display: flex; flex-direction: column; align-items: center; gap: 18px; }

.kor-class-row { display: flex; justify-content: center; gap: 18px; }

/* Big square targets — this is the whole screen's question, and the answer is
   a single digit, so there is no reason to make it small. Sized in vw between
   two limits, so three of them still fit across a narrow phone. */
.kor-class {
  width: clamp(96px, 27vw, 132px);
  height: clamp(96px, 27vw, 132px);
  /* Centred both ways, and line-height: 1 so the digit sits on the middle of
     the square rather than on the middle of its line box. */
  display: grid;
  place-items: center;
  line-height: 1;
  /* Same wash as the home menu cards — here it draws on the indigo this side
     flies instead of a course's flag colour. */
  background: linear-gradient(var(--accent-wash) 0 100%), var(--surface);
  border: 1px solid var(--accent-edge);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  font-size: clamp(40px, 11vw, 56px);
  font-weight: 650;
  color: var(--text);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}

.kor-class:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lift);
  background: linear-gradient(var(--accent-wash-strong) 0 100%), var(--surface);
  border-color: var(--accent-edge-strong);
}

/* The answer: one line, at display size, and no number repeating it above.

   Blue at full strength, in this side's own indigo rather than a flat blue —
   --accent, so it is the darkened variant that clears the contrast floor at
   any size, not only as display type. */
.kor-chosen-line {
  margin: 0;
  font-size: clamp(30px, 6.4vw, 54px);
  font-weight: 800;
  line-height: 1.15;
  color: var(--accent);
}

/* "You're not in class 6." Sits under whichever row was clicked, centred with
   the rest of the screen. Bordered rather than shouted in red on its own: it
   is a correction, not an error — the button worked, it just isn't theirs. */
.kor-wrong {
  max-width: 480px;
  margin: 18px auto 0;
  padding: 11px 15px;
  background: var(--bad-bg);
  border: 1px solid color-mix(in srgb, var(--bad) 35%, transparent);
  border-radius: 12px;
  font-size: 14px;
  color: var(--bad);
}

/* Their own class and courses, under where they stand on the profile screen. */
.tutoring-plan { margin: 6px 0 0; font-size: 14px; font-weight: 600; }

/* The subjects and the note, side by side. `stretch` is the whole point of
   the grid here: it is what makes the sheet exactly as tall as the three
   buttons beside it, whichever of the two turns out to be taller. */
.kor-desk {
  display: grid;
  grid-template-columns: minmax(150px, 210px) 1fr;
  align-items: stretch;
  gap: 22px;
  max-width: 780px;
  margin: 34px auto 0;
  text-align: left;
}

.kor-subjects { display: flex; flex-direction: column; gap: 12px; }

/* flex: 1 shares the column's height between the three, so they end level
   with the bottom of the sheet rather than leaving a gap under the last one. */
.kor-subject {
  flex: 1;
  min-height: 62px;
  padding: 14px 18px;
  background: linear-gradient(var(--accent-wash) 0 100%), var(--surface);
  border: 1px solid var(--accent-edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  font-size: 17px;
  font-weight: 600;
  color: var(--text);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}

.kor-subject:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lift);
  background: linear-gradient(var(--accent-wash-strong) 0 100%), var(--surface);
  border-color: var(--accent-edge-strong);
}

/* --accent, not the raw indigo: white on the undarkened colour lands around
   3.6:1, and this is 17px semi-bold rather than large text. */
/* ------------------------------------------------------------- the path

   One course in one class: thirty weeks as stepping stones across a chain of
   five small islands, six weeks to an island, drifting down the page.

   Each island takes the colours of the part of the year it falls in, so how
   far through the course a class is can be seen before a number is read. The
   seasons are set on the island and read by everything inside it, which is
   what keeps this to one palette per island rather than five sets of rules. */
.path {
  max-width: 620px;
  margin: 8px auto 0;
  padding: 8px 0 30px;
}

.island {
  position: relative;
  /* Deep at the foot: the island's bottom edge curves away, and the last row
     of stones sat on the curve rather than on the island. */
  padding: 22px 26px 38px;
  border-radius: 46% 46% 40% 40% / 16% 16% 26% 26%;
  background: linear-gradient(var(--isle-top), var(--isle-bottom));
  box-shadow:
    inset 0 2px 0 var(--isle-sheen),
    0 16px 30px -12px var(--isle-shadow);
  /* Every other island sits a little off-centre, so the chain wanders rather
     than falling in a straight line. */
  transition: opacity .3s ease, filter .3s ease;
}

.island:nth-child(4n + 1) { margin-left: 6%; margin-right: -6%; }
.island:nth-child(4n + 3) { margin-left: -6%; margin-right: 6%; }

/* Autumn through to spring, in five steps — one hue each, and the island
   built out of it by recipe. Written this way so the night version is a
   different *recipe* rather than a second set of five palettes: the hues
   below are the season and don't change with the lamp, only what is mixed
   into them does. */
.island[data-season="earlyAutumn"] { --isle-hue: #e0a83c; }
.island[data-season="lateAutumn"]  { --isle-hue: #d2703c; }
.island[data-season="winter"]      { --isle-hue: #6fa3d8; }
.island[data-season="earlySpring"] { --isle-hue: #6fbb5c; }
.island[data-season="spring"]      { --isle-hue: #b57bc4; }

/* Daylight: sand and shallow water, the hue thinned into white. */
.island {
  --isle-top: color-mix(in srgb, var(--isle-hue) 24%, #fff);
  --isle-bottom: color-mix(in srgb, var(--isle-hue) 52%, #fff);
  --isle-sheen: rgba(255, 255, 255, .55);
  --isle-shadow: color-mix(in srgb, var(--isle-hue) 55%, #000);
  --isle-ink: color-mix(in srgb, var(--isle-hue) 72%, #000);
}

/* After dark the same islands, lit from a long way off: the hue sunk into
   the night rather than laid over it, and the label lifted out of it. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .island {
    --isle-top: color-mix(in srgb, var(--isle-hue) 26%, #11151b);
    --isle-bottom: color-mix(in srgb, var(--isle-hue) 13%, #11151b);
    --isle-sheen: rgba(255, 255, 255, .07);
    --isle-shadow: rgba(0, 0, 0, .6);
    --isle-ink: color-mix(in srgb, var(--isle-hue) 62%, #fff);
  }
}

:root[data-theme="dark"] .island {
  --isle-top: color-mix(in srgb, var(--isle-hue) 26%, #11151b);
  --isle-bottom: color-mix(in srgb, var(--isle-hue) 13%, #11151b);
  --isle-sheen: rgba(255, 255, 255, .07);
  --isle-shadow: rgba(0, 0, 0, .6);
  --isle-ink: color-mix(in srgb, var(--isle-hue) 62%, #fff);
}

/* Which weeks this island holds. Small and set into its own colour, so it
   labels the island without competing with the stones. */
.island-label {
  margin: 0 0 14px;
  text-align: center;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--isle-ink);
  opacity: .75;
}

/* Two plants per island, sat on its shoulders. Decorative and unclickable,
   so they can never take a tap meant for a week. */
.island-tree {
  position: absolute;
  top: -14px;
  font-size: 26px;
  line-height: 1;
  pointer-events: none;
  user-select: none;
}

.island-tree-a { left: 12px; transform: rotate(-8deg); }
.island-tree-b { right: 12px; transform: rotate(9deg); }

/* Six stones to an island: three and three, so each row is a short hop
   rather than a long line of numbers. */
.island-stones {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  justify-items: center;
}

/* The open water between one island and the next, with the three little
   stones of the crossing in it. */
.crossing {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 12px;
  height: 54px;
}

.crossing span {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--stone-edge);
  opacity: .5;
}

.crossing span:nth-child(2) { transform: translateY(-7px); opacity: .65; }

.stone {
  width: 100%;
  max-width: 74px;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: linear-gradient(var(--stone-top), var(--stone-bottom));
  border: 2px solid var(--stone-edge);
  /* A thin rim below the stone, so it sits on the island rather than
     floating over it. */
  box-shadow: 0 3px 0 var(--stone-rim), 0 8px 16px var(--stone-shadow);
  font-size: 19px;
  font-weight: 700;
  color: var(--stone-ink);
  transition: transform .14s ease, box-shadow .14s ease, opacity .3s ease;
}

.stone:hover {
  transform: translateY(-3px);
  box-shadow: 0 5px 0 var(--stone-rim), 0 12px 22px var(--stone-shadow);
}

/* The week the class is on. The one stone that should catch the eye first,
   so it is larger, ringed, and gently breathing. */
.stone.is-here {
  transform: scale(1.12);
  border-color: var(--stone-done-edge);
  box-shadow:
    0 3px 0 var(--stone-rim),
    0 0 0 5px color-mix(in srgb, var(--stone-done-edge) 30%, transparent),
    0 10px 20px var(--stone-shadow);
  animation: stone-here 2.4s ease-in-out infinite;
}

.stone.is-here:hover { transform: scale(1.12) translateY(-3px); }

@keyframes stone-here {
  0%, 100% { box-shadow: 0 3px 0 var(--stone-rim), 0 0 0 5px color-mix(in srgb, var(--stone-done-edge) 30%, transparent), 0 10px 20px var(--stone-shadow); }
  50%      { box-shadow: 0 3px 0 var(--stone-rim), 0 0 0 10px color-mix(in srgb, var(--stone-done-edge) 12%, transparent), 0 10px 20px var(--stone-shadow); }
}

/* Nobody with reduced motion asked for a pulsing circle. The ring stays; only
   the breathing goes. */
@media (prefers-reduced-motion: reduce) {
  .stone.is-here { animation: none; }
}

/* Finished. Waiting on the exercises, but drawn and ready. */
.stone.is-done {
  background: linear-gradient(var(--stone-done-top), var(--stone-done-bottom));
  border-color: var(--stone-done-edge);
  color: var(--stone-done-ink);
}

/* Not reached yet. No padlock drawn on it — the mist and the empty face say
   it, and the date it opens is on the stone's own tooltip. */
.stone.is-locked {
  background: linear-gradient(var(--stone-shut-top), var(--stone-shut-bottom));
  border-color: var(--stone-shut-edge);
  border-style: dashed;
  box-shadow: none;
  color: var(--stone-shut-ink);
  cursor: default;
}

.stone.is-locked:hover { transform: none; box-shadow: none; }

/* The mist. Weeks just past the one the class is on are dimmed; further ahead
   they fade into the cloud, so the path ends in weather rather than in a wall
   of grey circles. Never applied to anything already open, however far back. */
.stone[data-mist="1"] { opacity: .62; }
.stone[data-mist="2"] { opacity: .38; filter: blur(.6px); }
.stone[data-mist="3"] { opacity: .2; filter: blur(1.4px); }

.island[data-mist="1"] { opacity: .75; }
.island[data-mist="2"] { opacity: .5; filter: blur(1px); }
.island[data-mist="3"] { opacity: .28; filter: blur(2.5px); }

/* One week, opened. The same sheet of paper as the class note, because it is
   the same idea — a page waiting to be written on. */
.week-sheet { max-width: 720px; margin: 8px auto 0; }

@media (max-width: 640px) {
  .path { padding-bottom: 20px; }
  .island { padding: 18px 16px 20px; }
  .island-stones { gap: 10px; }
  .stone { font-size: 16px; }
}

/* The note: one sheet of scrapbook paper, taped up, with whatever Paulina
   has written for this class on it.

   Its own colours rather than the app's surface tokens — paper is paper —
   but tokens all the same, so it has a night version. See the scrapbook
   block in :root. */
.kor-note {
  position: relative;
  min-height: 340px;
  /* Clear of the stitched border, which is inset 12px. */
  padding: 34px 32px;
  border-radius: 4px;
  background-color: var(--paper);
  /* A faint dotted wash, offset in two passes so it reads as texture rather
     than as a grid to write on. */
  background-image:
    radial-gradient(circle at 1px 1px, var(--paper-dot) 1px, transparent 0),
    radial-gradient(circle at 1px 1px, var(--paper-dot-soft) 1px, transparent 0);
  background-size: 22px 22px, 22px 22px;
  background-position: 0 0, 11px 11px;
  box-shadow:
    0 1px 2px var(--paper-shadow),
    0 16px 34px var(--paper-shadow);
  /* Placed by hand rather than laid out by a machine. Small enough not to
     look like a mistake. */
  transform: rotate(-.5deg);

  /* Written by hand in a textarea and drawn with textContent, so the blank
     line somebody put between two paragraphs is part of what they wrote and
     has to survive to the page. break-word so a pasted URL wraps instead of
     running off the sheet. */
  white-space: pre-wrap;
  overflow-wrap: break-word;
  color: var(--paper-ink);
  font-size: 16px;
  line-height: 1.65;
}

/* The stitched border scrapbook pages are trimmed with. */
.kor-note::before {
  content: '';
  position: absolute;
  inset: 12px;
  border: 1px dashed var(--paper-stitch);
  border-radius: 2px;
  pointer-events: none;
}

/* A strip of washi tape holding it to the page. */
.kor-note::after {
  content: '';
  position: absolute;
  top: -13px;
  left: 50%;
  width: 116px;
  height: 27px;
  transform: translateX(-50%) rotate(-2.5deg);
  background: repeating-linear-gradient(
    45deg,
    var(--tape) 0 7px,
    var(--tape-soft) 7px 14px
  );
  border-left: 1px solid var(--tape-edge);
  border-right: 1px solid var(--tape-edge);
  box-shadow: 0 1px 2px var(--paper-shadow);
}

/* Two columns won't fit on a phone. The subjects become a row of pills over
   the sheet rather than a stack above it, so the note keeps the height. */
@media (max-width: 640px) {
  /* Wider than it needs to be for the pills alone: the tape overhangs the top
     of the sheet, and a 16px gap left it grazing the button above. */
  .kor-desk { grid-template-columns: 1fr; gap: 26px; }
  .kor-subjects { flex-direction: row; }
  .kor-subject { min-height: 0; padding: 12px 10px; font-size: 15px; text-align: center; }
}

/* ------------------------------------------------------- home dashboard */

/* The figures and today's word share a row above the menu, and fall into one
   column as soon as two of them stop fitting. */
.home-top {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
  gap: 16px;
  margin-bottom: 20px;
}

.home-dash {
  display: grid;
  gap: 16px;
  /* Whichever card is shorter grows to match its neighbour; pushing the goal
     bar to the bottom keeps the two baselines from drifting apart. */
  align-content: space-between;
  padding: 18px 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

.stat-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  text-align: center;
}

/* Hairlines between the figures, not three separate boxes — a row of cards
   directly above the menu cards would read as two competing grids. */
.stat + .stat { border-left: 1px solid var(--border); }

.stat strong {
  display: block;
  font-size: clamp(24px, 4.4vw, 32px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -.02em;
  color: var(--course-accent);
  font-variant-numeric: tabular-nums;
}

.stat span { font-size: 13px; color: var(--text-soft); }

.home-goal { display: flex; align-items: center; gap: 14px; }

.home-goal-bar {
  flex: 1;
  height: 8px;
  background: var(--bg-tint);
  border-radius: 999px;
  overflow: hidden;
}

.home-goal-bar span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--course-accent) 60%, transparent), var(--course-accent));
  transition: width .3s ease;
}

.home-goal-text {
  margin: 0;
  font-size: 13px;
  color: var(--text-soft);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Goal met: the bar leaves the course colour behind for the same green the
   quiz marks a right answer with, so "done" reads the same everywhere. */
.home-goal.is-done .home-goal-text { color: var(--good); font-weight: 600; }

.home-goal.is-done .home-goal-bar span {
  background: linear-gradient(90deg, color-mix(in srgb, var(--good) 55%, transparent), var(--good));
}

/* The nowrap figure and the bar stop fitting side by side on a narrow phone. */
@media (max-width: 420px) {
  .home-goal { flex-direction: column; align-items: stretch; gap: 8px; }
  .home-goal-text { text-align: center; }
}

/* --------------------------------------------------------- word of the day */
.wotd {
  padding: 20px 22px;
  background:
    linear-gradient(color-mix(in srgb, var(--course-accent) 12%, transparent) 0 100%),
    var(--surface);
  border: 1px solid color-mix(in srgb, var(--course-accent) 30%, var(--border));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

.wotd-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.wotd-kicker {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent); /* 12px — same contrast reasoning as .hero-hello */
}

.wotd-actions { display: flex; gap: 8px; }

.wotd-word {
  margin: 10px 0 0;
  font-size: clamp(24px, 4.6vw, 34px);
  font-weight: 650;
  letter-spacing: -.02em;
  text-wrap: balance;
}

.wotd-gloss { margin: 4px 0 0; font-size: 16px; color: var(--text-soft); }

.wotd-example {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid color-mix(in srgb, var(--course-accent) 20%, var(--border));
  font-size: 14px;
}

.wotd-sentence { margin: 0; font-weight: 600; }
.wotd-sentence-gloss { margin: 4px 0 0; color: var(--text-soft); font-style: italic; }

/* ---------------------------------------------------------------- streak */
#view-home { position: relative; }

.streak { position: absolute; top: 0; right: 0; z-index: 20; }

.streak-badge {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px 6px 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: var(--shadow);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}

.streak-badge:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lift);
  border-color: color-mix(in srgb, var(--flame) 45%, var(--border));
}

/* The ring is a conic gradient of the day's progress; the disc on top of it
   leaves only a rim showing, and the flame sits in the middle of that. */
.streak-ring {
  position: relative;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: conic-gradient(var(--flame) var(--progress, 0%), var(--border) 0);
}

.streak-ring::before {
  content: '';
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--surface);
}

/* Grey until the day's goal is met, so "have I practised today?" is answered
   at a glance, from across the room. */
.streak-flame {
  position: relative;
  font-size: 19px;
  line-height: 1;
  filter: grayscale(1);
  opacity: .5;
  transition: filter .3s ease, opacity .3s ease, transform .3s ease;
}

.streak-count {
  font-size: 17px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
}

.streak-badge.is-lit .streak-flame { filter: none; opacity: 1; transform: scale(1.08); }
.streak-badge.is-lit .streak-count { color: var(--flame); }

.streak-panel {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: min(320px, calc(100vw - 32px));
  padding: 16px 18px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  text-align: left;
  animation: rise .16s ease;
}

.streak-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
}

.streak-head h2 { font-size: 16px; font-weight: 650; }

.streak-figures { display: flex; gap: 10px; margin-bottom: 14px; }

.streak-figure {
  flex: 1;
  padding: 10px 12px;
  background: var(--surface-2);
  border-radius: var(--radius);
  line-height: 1.25;
}

.streak-figure strong { font-size: 26px; font-weight: 700; color: var(--flame); }
.streak-figure span { font-size: 13px; color: var(--text-soft); margin-left: 4px; }
.streak-figure em {
  display: block;
  font-style: normal;
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-top: 2px;
}

.streak-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}

.streak-bar span {
  display: block;
  height: 100%;
  background: var(--flame);
  border-radius: 999px;
  transition: width .3s ease;
}

.streak-progress p { margin: 7px 0 14px; font-size: 13px; color: var(--text-soft); }

.streak-cal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
  font-size: 13px;
  font-weight: 650;
}

.streak-cal-head .icon-btn { width: 26px; height: 26px; font-size: 15px; }
.streak-cal-head .icon-btn[disabled] { opacity: .3; pointer-events: none; }

.streak-calendar {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 3px;
  text-align: center;
}

.cal-wd {
  font-size: 10px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding-bottom: 2px;
}

.cal-day {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  border-radius: 50%;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-soft);
}

.cal-day.is-blank { visibility: hidden; }
.cal-day.is-future { color: var(--text-faint); opacity: .45; }

/* A day that reached the goal is filled; one that was practised but fell
   short keeps a rim, so a near miss still shows as effort. */
.cal-day.is-done {
  background: var(--flame);
  /* White in light, near-black in dark — the dark theme's flame is a bright
     amber that white text disappears into. */
  color: var(--accent-contrast);
  font-weight: 650;
}

.cal-day.is-partial {
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--flame) 50%, transparent);
  color: var(--text);
}

.cal-day.is-today { outline: 2px solid var(--text-soft); outline-offset: -2px; }
.cal-day.is-today.is-done { outline-color: var(--text); }

.streak-hint {
  margin: 12px 0 0;
  font-size: 12px;
  color: var(--text-faint);
  line-height: 1.4;
}

/* ---------------------------------------------------------------- page heads */
.page-head { margin-bottom: 22px; }
.page-head h1 { font-size: clamp(24px, 4vw, 32px); margin-top: 6px; }
.page-sub { margin: 6px 0 0; color: var(--text-soft); }

.page-head.compact {
  display: flex;
  align-items: center;
  gap: 18px;
}

.back-btn {
  background: none;
  border: 0;
  padding: 4px 0;
  color: var(--text-soft);
  font-size: 14px;
  white-space: nowrap;
}

.back-btn:hover { color: var(--text); }

/* ---------------------------------------------------------------- panels */
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 20px;
  margin-bottom: 16px;
  box-shadow: var(--shadow);
}

.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.panel-head h2 { font-size: 15px; font-weight: 650; text-transform: uppercase; letter-spacing: .06em; color: var(--text-soft); }
.panel-head-actions { display: flex; gap: 12px; }

.link-btn {
  background: none;
  border: 0;
  padding: 0;
  font-size: 13px;
  color: var(--blue);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---------------------------------------------------------------- chips */
.chips { display: flex; flex-wrap: wrap; gap: 10px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  background: var(--surface-2);
  border: 1.5px solid var(--border);
  border-radius: 999px;
  font-size: 14px;
  transition: all .14s ease;
}

.chip:hover { border-color: var(--border-strong); }

.chip.is-active {
  background: color-mix(in srgb, var(--course-accent) 12%, var(--surface));
  border-color: var(--course-accent);
  color: var(--accent); /* 14px label — see .hero-hello */
  font-weight: 600;
}

.chip-count {
  font-size: 12px;
  padding: 1px 7px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  color: var(--text-soft);
}

.chip.is-active .chip-count { background: color-mix(in srgb, var(--course-accent) 18%, transparent); color: inherit; }

/* An unfinished deck. Same pill as a category chip so the two panels read as
   one family, but it carries two controls — the label resumes, the × throws
   the deck away — so the padding moves onto the buttons inside it. */
.continue-chip {
  gap: 0;
  padding: 0;
  overflow: hidden;
  background: color-mix(in srgb, var(--course-accent) 12%, var(--surface));
  border-color: var(--course-accent);
  color: var(--accent);
  font-weight: 600;
}

.continue-chip:hover { border-color: var(--course-accent); }

.continue-go,
.continue-drop {
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
}

.continue-go {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 8px 9px 14px;
}

.continue-chip .chip-count {
  background: color-mix(in srgb, var(--course-accent) 18%, transparent);
  color: inherit;
  font-variant-numeric: tabular-nums;
}

.continue-drop {
  padding: 9px 13px 9px 5px;
  color: var(--text-faint);
  font-size: 16px;
  line-height: 1;
}

.continue-drop:hover { color: var(--bad); }

/* ------------------------------------------------------- stats link (home) */

/* Sits between the stat tiles and the goal bar: the tiles say where things
   stand, this opens the page that says how they got there. */
.stats-link {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  margin: 14px 0 0;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--course-accent) 8%, var(--surface-2));
  border: 1px solid color-mix(in srgb, var(--course-accent) 24%, var(--border));
  border-radius: 12px;
  color: var(--text-soft);
  font-size: 14px;
  text-align: left;
  transition: all .14s ease;
}

.stats-link:hover {
  color: var(--text);
  border-color: color-mix(in srgb, var(--course-accent) 50%, var(--border));
}

.stats-link-icon { font-size: 16px; }
.stats-link-caret { margin-left: auto; color: var(--text-faint); }
.stats-link:hover .stats-link-caret { color: var(--accent); }

/* ---------------------------------------------------------------- stats page */
.stats-overview { display: flex; flex-wrap: wrap; align-items: center; gap: 26px; }

.stats-learned { flex: 1 1 240px; }
.stats-learned > strong { font-size: 40px; font-weight: 700; color: var(--course-accent); letter-spacing: -.03em; }

.stats-learned-bar {
  height: 9px;
  margin: 8px 0 6px;
  background: var(--bg-tint);
  border-radius: 999px;
  overflow: hidden;
}

.stats-learned-bar span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--course-accent), color-mix(in srgb, var(--course-accent) 55%, var(--surface)));
  transition: width .4s ease;
}

.stats-learned-text { margin: 0; font-size: 13px; color: var(--text-soft); }

.stats-figures {
  flex: 1 1 260px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: 14px;
}

.stats-figure { text-align: center; }
.stats-figure strong { display: block; font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
.stats-figure span { font-size: 12px; color: var(--text-soft); }

/* Half a year of activity: one column per week, one row per weekday, so the
   cells flow down each column. Scrolls sideways rather than shrinking the
   squares away to nothing on a phone. */
.heatmap-wrap { overflow-x: auto; padding-bottom: 4px; }

.heatmap {
  display: grid;
  grid-template-rows: repeat(7, 13px);
  grid-auto-flow: column;
  grid-auto-columns: 13px;
  gap: 3px;
  min-width: min-content;
}

.heat-day { border-radius: 3px; background: var(--bg-tint); }
.heat-1 { background: color-mix(in srgb, var(--flame) 28%, var(--bg-tint)); }
.heat-2 { background: color-mix(in srgb, var(--flame) 52%, var(--bg-tint)); }
.heat-3 { background: color-mix(in srgb, var(--flame) 76%, var(--bg-tint)); }
.heat-4 { background: var(--flame); }

.heat-legend {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 3px;
  margin-top: 10px;
}

.heat-legend .heat-day { width: 13px; height: 13px; }
.heat-legend-label { font-size: 12px; color: var(--text-faint); }
.heat-legend-label:first-child { margin-right: 4px; }
.heat-legend-label:last-child { margin-left: 4px; }

/* One grid for the whole list rather than a grid per row: the count column is
   auto-width, so a row sizing itself would set its own bar length and the
   bars would start and stop at a different place on every line. The rows
   dissolve into it with display:contents, which is what lets all three
   columns line up down the list. */
.stats-cats {
  display: grid;
  grid-template-columns: minmax(90px, 1fr) 2fr auto;
  align-items: center;
  gap: 9px 12px;
  font-size: 13px;
}

.stats-cat { display: contents; }

.stats-cat-name { color: var(--text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.stats-cat-bar {
  height: 7px;
  background: var(--bg-tint);
  border-radius: 999px;
  overflow: hidden;
}

.stats-cat-bar span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--course-accent);
  transition: width .4s ease;
}

.stats-cat-count { color: var(--text-faint); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ------------------------------------------------------ listening / gender */

/* The audio is the question, so the replay button is the prompt — sized to
   look like the thing you're meant to press. */
.listen-replay {
  width: 92px;
  height: 92px;
  margin: 6px auto 4px;
  display: grid;
  place-items: center;
  font-size: 36px;
  background: color-mix(in srgb, var(--course-accent) 12%, var(--surface));
  border: 1.5px solid color-mix(in srgb, var(--course-accent) 40%, var(--border));
  border-radius: 50%;
  transition: transform .14s ease, background .14s ease;
}

.listen-replay:hover { transform: scale(1.04); background: color-mix(in srgb, var(--course-accent) 20%, var(--surface)); }
.listen-replay:active { transform: scale(.97); }

.gender-gloss { margin: 2px 0 0; color: var(--text-soft); font-size: 15px; }

/* A handful of short articles, not full answers — centred and roomy rather
   than stretched across the same grid the word choices use. */
.gender-choices { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }

.choice-article {
  min-width: 108px;
  text-align: center;
  font-size: 19px;
  font-weight: 650;
}

/* ---------------------------------------------------------------- options */
.options { display: flex; flex-wrap: wrap; gap: 22px; }
.option-group { display: flex; flex-direction: column; gap: 8px; }
.option-label { font-size: 13px; color: var(--text-soft); }

.segmented {
  display: inline-flex;
  padding: 3px;
  gap: 3px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
}

.segmented button {
  border: 0;
  background: none;
  padding: 7px 13px;
  border-radius: 9px;
  font-size: 14px;
  color: var(--text-soft);
  transition: all .14s ease;
}

.segmented button:hover { color: var(--text); }

/* For the copy riding in the word list's toolbar, which shares a line with a
   search box and a select rather than standing under a caption of its own. */
.segmented-sm button { padding: 5px 10px; font-size: 13px; }

.segmented button.is-active {
  background: var(--surface);
  color: var(--text);
  font-weight: 600;
  box-shadow: 0 1px 3px rgba(16, 24, 48, .12);
}

/* ---------------------------------------------------------------- buttons */
.primary-btn {
  padding: 12px 26px;
  background: var(--accent);
  color: var(--accent-contrast);
  border: 0;
  border-radius: 12px;
  font-size: 15px;
  font-weight: 600;
  box-shadow: 0 4px 14px color-mix(in srgb, var(--course-accent) 30%, transparent);
  transition: transform .14s ease, filter .14s ease;
}

.primary-btn:hover { transform: translateY(-1px); filter: brightness(1.06); }
.primary-btn:disabled { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }

.ghost-btn {
  padding: 10px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  font-size: 14px;
  color: var(--text-soft);
  transition: all .14s ease;
}

.ghost-btn:hover { color: var(--text); border-color: var(--border-strong); }

/* Outlined rather than filled: ending the session is a deliberate act, but
   not the thing this page is for, so it shouldn't outweigh Save. */
.danger-btn {
  padding: 10px 18px;
  background: var(--surface);
  border: 1.5px solid color-mix(in srgb, var(--bad) 45%, transparent);
  border-radius: 12px;
  font-size: 14px;
  font-weight: 600;
  color: var(--bad);
  white-space: nowrap;
  transition: all .14s ease;
}

.danger-btn:hover { background: var(--bad-bg); border-color: var(--bad); }

/* Filled, for the button that actually does the destructive thing — the
   outlined pair above only open the question. Text takes the surface colour
   rather than a literal white, so it stays readable against the paler red
   the dark theme uses. */
.danger-btn.is-solid { background: var(--bad); border-color: var(--bad); color: var(--surface); }
.danger-btn.is-solid:hover { background: color-mix(in srgb, var(--bad) 85%, #000); }

/* The two ways out, under the last panel — no panel of their own, since a
   pair of buttons doesn't need a card around it. They wrap rather than
   shrink, so neither label breaks on a narrow phone. */
.signout-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 4px; }

/* The confirmation, in the warning colour so it can't be mistaken for one
   more settings panel. */
.reset-confirm {
  max-width: 520px;
  margin: 14px auto 0;
  padding: 18px 20px;
  background: var(--bad-bg);
  border: 1px solid color-mix(in srgb, var(--bad) 40%, transparent);
  border-radius: var(--radius-lg);
}

.reset-confirm h3 { margin: 0 0 6px; font-size: 17px; color: var(--text); }
.reset-what { margin: 0; font-size: 14px; line-height: 1.55; color: var(--text-soft); }

/* The whole row is the label, so the wording is as clickable as the box. */
.reset-option {
  display: flex;
  gap: 10px;
  margin: 16px 0;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}

.reset-option input { margin-top: 3px; flex: none; accent-color: var(--bad); }
.reset-option span { display: flex; flex-direction: column; gap: 4px; }
.reset-option strong { font-size: 14px; font-weight: 600; color: var(--text); }
.reset-option em { font-size: 12.5px; line-height: 1.5; font-style: normal; color: var(--text-faint); }

/* Keeping the progress sits first and last in the tab order's reach: the
   safe choice is the easy one to hit. */
.reset-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
.ghost-btn.small { padding: 7px 12px; font-size: 13px; }

.start-bar {
  position: sticky;
  bottom: 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
}

.start-count { margin: 0; font-size: 14px; color: var(--text-soft); }

/* ---------------------------------------------------------------- progress */
.progress { flex: 1; display: flex; align-items: center; gap: 12px; }

.progress-bar {
  flex: 1;
  height: 7px;
  background: var(--bg-tint);
  border-radius: 999px;
  overflow: hidden;
}

.progress-bar span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  /* Fades in from the track rather than stepping to a fixed second colour, so
     the bar works for any course accent without needing a paired -soft tone. */
  background: linear-gradient(90deg, color-mix(in srgb, var(--course-accent) 60%, transparent), var(--course-accent));
  transition: width .3s ease;
}

.progress-text { margin: 0; font-size: 13px; color: var(--text-soft); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- flashcards */
.card-stage { position: relative; display: flex; justify-content: center; }

.flashcard {
  width: 100%;
  max-width: 620px;
  perspective: 1400px;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.flashcard-inner {
  position: relative;
  min-height: 360px;
  transform-style: preserve-3d;
  transition: transform .55s cubic-bezier(.4, .2, .2, 1);
}

.flashcard.is-flipped .flashcard-inner { transform: rotateY(180deg); }

.flashcard-face {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 32px 34px;
  overflow-y: auto;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  /* Wordy backs scroll; keep that scrollbar discreet instead of a bright slab. */
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

.flashcard-face::-webkit-scrollbar { width: 8px; }
.flashcard-face::-webkit-scrollbar-track { background: transparent; }
.flashcard-face::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: 999px;
}

.flashcard-back {
  transform: rotateY(180deg);
  background: linear-gradient(165deg, var(--surface), var(--surface-2));
}

.card-kicker {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.card-word {
  margin: 0;
  font-size: clamp(26px, 5.2vw, 40px);
  font-weight: 650;
  letter-spacing: -.02em;
  text-wrap: balance;
}

.flashcard-front .card-word { color: var(--blue); }

.card-hint {
  position: absolute;
  bottom: 18px;
  margin: 0;
  font-size: 12px;
  color: var(--text-faint);
}

.card-note {
  margin: 4px 0 0;
  max-width: 46ch;
  font-size: 14px;
  color: var(--text-soft);
}

.card-example {
  margin-top: 10px;
  padding: 12px 16px;
  max-width: 46ch;
  background: color-mix(in srgb, var(--blue) 7%, transparent);
  border-radius: 12px;
  font-size: 14px;
}

.example-target { margin: 0; font-weight: 600; }
.example-gloss { margin: 4px 0 0; color: var(--text-soft); font-style: italic; }
/* A word can carry several examples — separate the pairs. */
.card-example p + .example-target { margin-top: 10px; }

.card-side-actions {
  position: absolute;
  top: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

@media (max-width: 760px) {
  .card-side-actions { position: static; flex-direction: row; justify-content: center; margin-top: 12px; }
  .card-stage { flex-direction: column; align-items: center; }
}

.card-controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin-top: 22px;
}

.rate-btn {
  padding: 10px 20px;
  border-radius: 12px;
  border: 1.5px solid;
  font-size: 14px;
  font-weight: 600;
  background: var(--surface);
  transition: all .14s ease;
}

.rate-again { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 45%, transparent); }
.rate-again:hover { background: var(--bad-bg); }
.rate-known { color: var(--good); border-color: color-mix(in srgb, var(--good) 45%, transparent); }
.rate-known:hover { background: var(--good-bg); }

.keyhint { margin: 16px 0 0; text-align: center; font-size: 12px; color: var(--text-faint); }

/* ---------------------------------------------------------------- quiz */
.quiz-stage {
  position: relative;
  padding: 30px 26px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  text-align: center;
}

.quiz-prompt-label {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.quiz-prompt {
  margin: 0 0 6px;
  font-size: clamp(24px, 4.6vw, 36px);
  font-weight: 650;
  letter-spacing: -.02em;
  color: var(--blue);
  text-wrap: balance;
}

.quiz-speak { position: absolute; top: 22px; right: 22px; }

.quiz-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px;
  margin-top: 26px;
}

.choice {
  padding: 16px 18px;
  background: var(--surface-2);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  font-size: 15px;
  text-align: left;
  transition: all .14s ease;
}

.choice:hover:not(:disabled) { border-color: var(--blue-soft); transform: translateY(-2px); }
.choice:disabled { cursor: default; }
.choice.is-correct { background: var(--good-bg); border-color: var(--good); color: var(--good); font-weight: 600; }
.choice.is-wrong { background: var(--bad-bg); border-color: var(--bad); color: var(--bad); }
.choice.is-dim { opacity: .45; }

.quiz-typing { display: flex; gap: 10px; justify-content: center; margin-top: 26px; flex-wrap: wrap; }
.quiz-typing input { flex: 1; min-width: 220px; max-width: 380px; padding: 12px 14px; font-size: 16px; }

/* Full sentences read better smaller and left-aligned than the short,
   centred word/phrase prompts quiz.js uses this same class for. */
.quiz-prompt.sentence-prompt {
  font-size: clamp(18px, 3vw, 25px);
  line-height: 1.4;
  text-align: left;
}

.sentence-typing input { max-width: 520px; }

.quiz-feedback {
  margin-top: 20px;
  padding: 14px 18px;
  border-radius: var(--radius);
  font-size: 15px;
}

.quiz-feedback.good { background: var(--good-bg); color: var(--good); }
.quiz-feedback.bad { background: var(--bad-bg); color: var(--bad); }
.quiz-feedback .fb-answer { font-weight: 650; }
.quiz-feedback .fb-note { display: block; margin-top: 6px; font-size: 13px; color: var(--text-soft); }

#quiz-next { margin-top: 20px; }

/* ---------------------------------------------------------------- results */
.result {
  margin-top: 24px;
  padding: 32px 26px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

.result h2 { font-size: 26px; }
.score { margin: 12px 0 0; font-size: 18px; color: var(--text-soft); }
.score strong { color: var(--text); font-size: 26px; }

.result-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 24px; }

.review { margin-top: 24px; text-align: left; }
.review h3 { font-size: 13px; text-transform: uppercase; letter-spacing: .07em; color: var(--text-soft); margin-bottom: 10px; }

.review-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  padding: 10px 14px;
  border-radius: 10px;
  background: var(--surface-2);
  margin-bottom: 8px;
  font-size: 14px;
}

.review-row .rw-target { font-weight: 650; }
.review-row .rw-right { color: var(--good); }
.review-row .rw-yours { color: var(--bad); text-decoration: line-through; }

/* ---------------------------------------------------------------- word list */
.list-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}

.list-toolbar input[type="search"] { flex: 1; min-width: 220px; padding: 10px 14px; }

.checkbox { display: inline-flex; align-items: center; gap: 7px; font-size: 14px; color: var(--text-soft); cursor: pointer; }

.table-wrap {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  overflow-x: auto;
}

.word-table { width: 100%; border-collapse: collapse; font-size: 14px; }

.word-table th {
  position: sticky;
  top: 57px;
  text-align: left;
  padding: 12px 14px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  font-weight: 650;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-soft);
  white-space: nowrap;
}

.word-table td { padding: 11px 14px; border-bottom: 1px solid var(--border); vertical-align: top; }
.word-table tr:last-child td { border-bottom: 0; }
.word-table tbody tr:hover { background: var(--surface-2); }

.word-table .cell-target { font-weight: 600; color: var(--blue); white-space: nowrap; }
.word-table .col-star { width: 42px; }
.word-table .col-cat { white-space: nowrap; }
.word-table .cell-cat { color: var(--text-soft); font-size: 13px; white-space: nowrap; }
.word-table .cell-note { color: var(--text-soft); font-size: 13px; min-width: 220px; }
.word-table .cell-note em { display: block; margin-top: 3px; color: var(--text-faint); font-style: normal; }

/* ------------------------------------------------- how learned a word is

   Four steps, from untouched to learned, painted as a wash across the row
   and spelled out exactly by the dots beside it. The wash is what makes the
   table scannable; the dots are what make it readable, since the palest
   shade is easy to miss on a phone and colour alone shouldn't carry the
   meaning anyway.

   Mixed into the row rather than set as a flat colour so the tint sits on
   the surface underneath it and works unchanged in both themes. */
/* --tint is set per row by js/wordlist.js, worked out from the word's step
   over LEARNED_AT. One rule covers every level, so adding levels needs no
   stylesheet change; an untouched row has no --tint and falls back to 0%,
   which leaves the surface underneath showing through. */
.word-table tbody tr td { background: color-mix(in srgb, var(--flame) var(--tint, 0%), transparent); }

/* Hover has to beat the tint, or a learned row looks unresponsive. */
.word-table tbody tr:hover td { background: color-mix(in srgb, var(--flame) var(--tint-hover, 0%), var(--surface-2)); }

.word-table .col-strength { width: 78px; }
.word-table .cell-strength { white-space: nowrap; }

.strength { display: inline-flex; gap: 4px; align-items: center; }

.strength .dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--flame) 45%, transparent);
}

.strength .dot.is-on { background: var(--flame); box-shadow: none; }

/* -------------------------------------------------------------- legend */
.strength-legend {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin: 0 0 14px;
  font-size: 12px;
  color: var(--text-faint);
}

/* The chip's own patch of the shade it selects, so the filter doubles as the
   legend for the rows. */
.level-swatch {
  width: 16px;
  height: 16px;
  border-radius: 5px;
  border: 1px solid var(--border-strong);
}

/* The row classes paint `td`s — as bare spans these need the same mixes
   applied directly. */
.level-swatch { background: color-mix(in srgb, var(--flame) var(--tint, 0%), var(--surface)); }

.progress-filter { margin-bottom: 12px; }

.row-btn {
  background: none;
  border: 0;
  padding: 2px 5px;
  font-size: 15px;
  color: var(--text-faint);
  border-radius: 6px;
}

.row-btn:hover { color: var(--text); background: var(--bg-tint); }
.row-btn.is-on { color: var(--course-accent); }

.empty-note { padding: 28px; text-align: center; color: var(--text-soft); margin: 0; }

/* The language setting decides which translation columns are worth showing. */
#view-list.hide-english .cell-en,
#view-list.hide-english #th-english,
#view-list.hide-polish .cell-pl,
#view-list.hide-polish #th-polish { display: none; }

/* ---------------------------------------------------------------- status toast */
.app-status {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 50;
  max-width: min(560px, 92vw);
  padding: 12px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-lift);
  font-size: 14px;
  color: var(--text-soft);
}

.app-status.is-error { border-color: var(--bad); color: var(--bad); }

/* ---------------------------------------------------------------- celebration */
.celebrate {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 20px;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(3px);
}

.celebrate-card {
  position: relative;
  z-index: 1;
  max-width: 380px;
  padding: 30px 32px 26px;
  text-align: center;
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--flame) 35%, var(--border));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  animation: rise .24s ease;
}

.celebrate-card h2 { font-size: 24px; font-weight: 700; margin-bottom: 8px; }
.celebrate-card p { color: var(--text-soft); margin-bottom: 20px; }

.celebrate-flame {
  display: block;
  font-size: 46px;
  line-height: 1;
  margin-bottom: 12px;
  animation: flame-pop .5s ease;
}

/* Milestone days get the bigger flame — an ordinary finished day stays calm. */
.celebrate.is-milestone .celebrate-flame { font-size: 62px; }

@keyframes flame-pop {
  0%   { transform: scale(.4); opacity: 0; }
  60%  { transform: scale(1.15); opacity: 1; }
  100% { transform: scale(1); }
}

/* Confetti is one absolutely-positioned span per piece, placed and timed by
   js/app.js; falling paper is what "reduce motion" is asking about, so that
   preference skips the pieces entirely rather than freezing them mid-air. */
.confetti { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }

.confetti span {
  position: absolute;
  top: -16px;
  width: 8px;
  height: 14px;
  border-radius: 2px;
  opacity: 0;
  animation-name: confetti-fall;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}

@keyframes confetti-fall {
  0%   { opacity: 1; transform: translateY(0) rotate(0deg); }
  100% { opacity: 0; transform: translateY(102vh) rotate(680deg); }
}

/* ---------------------------------------------------------------- motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

/* ---------------------------------------------------------------- avatars */
/* One component, three sizes, used by the topbar button, the friends list
   and the leaderboard. With no picture it falls back to initials on a
   colour derived from the name, so every friend is still tellable apart. */
.avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  overflow: hidden;
  background: hsl(var(--avatar-hue, 220) 45% 88%);
  color: hsl(var(--avatar-hue, 220) 55% 26%);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .02em;
  user-select: none;
}

/* The initials circle is generated from a hue, so its light and dark
   treatments are two lightness pairs rather than two token sets. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .avatar {
    background: hsl(var(--avatar-hue, 220) 32% 30%);
    color: hsl(var(--avatar-hue, 220) 60% 86%);
  }
}

:root[data-theme="dark"] .avatar {
  background: hsl(var(--avatar-hue, 220) 32% 30%);
  color: hsl(var(--avatar-hue, 220) 60% 86%);
}

.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

.avatar-lg { width: 96px; height: 96px; font-size: 32px; }

/* The topbar button is the avatar, so it drops the icon-btn's square frame. */
.avatar-btn {
  padding: 0;
  border-radius: 50%;
  overflow: hidden;
  border-color: var(--border-strong);
}

.avatar-btn .avatar { width: 100%; height: 100%; }

/* ---------------------------------------------------------------- profile */
.profile-identity {
  display: flex;
  gap: 24px;
  align-items: flex-start;
  flex-wrap: wrap;
}

.profile-photo { display: flex; flex-direction: column; align-items: center; gap: 6px; }

.profile-photo-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 0;
  background: none;
  border: 0;
  color: inherit;
}

.profile-photo-btn .avatar { transition: filter .16s ease; }
.profile-photo-btn:hover .avatar { filter: brightness(.85); }

.profile-photo-hint { font-size: 12px; color: var(--text-soft); }
.profile-photo-btn:hover .profile-photo-hint { color: var(--course-accent); }

.profile-fields { flex: 1 1 260px; }
.profile-fields .field { max-width: 340px; }

.profile-email { margin: 0 0 14px; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.profile-email strong { font-weight: 600; }
.profile-email em { font-style: normal; font-size: 12px; color: var(--text-faint); }

.profile-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

.profile-figure {
  padding: 12px 14px;
  background: var(--surface-2);
  border-radius: var(--radius);
  line-height: 1.3;
}

.profile-figure strong { display: block; font-size: 22px; font-weight: 700; }
.profile-figure span { font-size: 12px; color: var(--text-soft); }

.form-note { margin: 10px 0 0; font-size: 13px; color: var(--text-soft); }
.form-note.is-good { color: var(--good); }
.form-note.is-bad { color: var(--bad); }

/* ---------------------------------------------------------------- friends */
/* Red as asked for. Worth knowing that in this palette red is otherwise the
   error colour, so it shouts a little louder than the action deserves.

   Its own red rather than --bad: the dark theme lifts --bad to a pale pink
   so error *text* stays readable on a dark background, and white on that
   pink is barely legible. A solid button wants the opposite treatment, so
   this stays deep in both themes and carries white throughout. */
.add-friends-btn {
  width: 100%;
  padding: 16px 24px;
  background: #c22c3d;
  color: #fff;
  border: 0;
  border-radius: var(--radius);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: .01em;
  box-shadow: 0 4px 16px rgba(194, 44, 61, .34);
  transition: transform .14s ease, filter .14s ease;
}

.add-friends-btn:hover { transform: translateY(-1px); filter: brightness(1.1); }
.add-friends-btn[aria-expanded="true"] { filter: brightness(.92); }

.add-friend-form {
  display: flex;
  gap: 12px;
  align-items: flex-end;
  flex-wrap: wrap;
  margin-top: 16px;
}

.add-friend-form .field { flex: 1 1 240px; margin-bottom: 0; }

.friend-subhead {
  font-size: 12px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-soft);
  margin: 20px 0 8px;
}

.friend-list { list-style: none; margin: 10px 0 0; padding: 0; }

.friend-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--border);
}

.friend-row:first-child { border-top: 0; }

.friend-who { flex: 1; min-width: 0; line-height: 1.3; }
.friend-who strong { display: block; font-weight: 600; }
.friend-who em {
  font-style: normal;
  font-size: 12px;
  color: var(--text-faint);
  overflow-wrap: anywhere;
}

.friend-actions { display: flex; align-items: center; gap: 8px; flex: none; }
.friend-streak { font-size: 14px; font-weight: 650; color: var(--flame); }
.friend-pending { font-size: 12px; color: var(--text-faint); font-style: italic; }

/* A request needs answering, so it gets the weight an ordinary row doesn't. */
.friend-row.is-request { background: color-mix(in srgb, var(--course-accent) 7%, transparent); }
.friend-row.is-pending { opacity: .65; }

.small-btn { padding: 6px 12px; font-size: 13px; }
.icon-btn.small-btn { width: 30px; height: 30px; padding: 0; font-size: 13px; }

.board-link { margin: 18px 0 0; }

/* ------------------------------------------------------------ leaderboard */
.board { list-style: none; margin: 0; padding: 0; }

.board-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 12px;
  border-radius: var(--radius);
  border-top: 1px solid var(--border);
}

.board-row:first-child { border-top: 0; }

.board-row.is-me {
  background: color-mix(in srgb, var(--course-accent) 10%, transparent);
  border-top-color: transparent;
}

.board-rank {
  flex: none;
  width: 34px;
  text-align: center;
  font-size: 19px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text-soft);
}

/* The dash standing in for a place nobody has earned yet — quiet, so a row
   of them reads as "not started" rather than as a column of results. */
.board-row.is-unranked .board-rank { color: var(--text-faint); font-weight: 400; }

.board-name { flex: 1; min-width: 0; font-weight: 600; }
.board-name em {
  font-style: normal;
  font-weight: 400;
  font-size: 12px;
  color: var(--text-faint);
  margin-left: 6px;
}

.board-figures { display: flex; gap: 18px; flex: none; }

.board-figure { text-align: right; line-height: 1.25; }
.board-figure strong { display: block; font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums; }
.board-figure em {
  font-style: normal;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-faint);
}

/* ------------------------------------------------------------------ admin */
/* Rows are buttons, since the whole row opens the person. Styled to sit flush
   in the panel rather than to look like a control -- the pointer and the hover
   tint are what say it's clickable. */
.admin-people { display: flex; flex-direction: column; }

/* background:none and border:0 before the separator, or the browser's own
   button chrome -- a grey face and a border on all four sides -- boxes every
   row in and the list stops reading as a list. */
.admin-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 4px;
  background: none;
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 8px;
  text-align: left;
  transition: background .12s ease;
}

.admin-row:first-child { border-top: 0; }
.admin-row:hover { background: var(--surface-2); }

.admin-who { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.admin-who strong { display: flex; align-items: center; gap: 6px; font-size: 15px; font-weight: 650; }
.admin-who em {
  font-style: normal;
  font-size: 12px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The same yellow as the topbar mark, so "admin" reads as one idea across
   the app. Small caps rather than a letter here: there is room for the word. */
.admin-tag {
  flex: none;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--admin);
  color: var(--admin-ink);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.admin-row-figures { display: flex; gap: 16px; flex: none; }
.admin-figure { text-align: center; }
.admin-figure strong { display: block; font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums; }
.admin-figure em {
  font-style: normal;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-faint);
}

/* ------------------------------------------------------- admin requests

   The korepetycje queue above the overview. Rows, not buttons: unlike the
   People list below, nothing here opens -- the two buttons on the right are
   the whole point of the row. */
.admin-requests { list-style: none; margin: 8px 0 0; padding: 0; }
.admin-requests + .admin-request-kicker { margin-top: 22px; }

/* Answered and out of the queue, but still here in case the answer was
   wrong -- so it's drawn as a footnote to the list above, not a second list
   of equal weight. */
.admin-requests.is-quiet { opacity: .7; }

.admin-request {
  display: flex;
  align-items: center;
  /* Wraps so the timetable form, which asks for the row's whole width, drops
     onto its own line underneath rather than crushing the name beside it. */
  flex-wrap: wrap;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--border);
}

.admin-request:first-child { border-top: 0; }

.admin-request-kicker {
  margin: 0;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-faint);
}

.admin-request-when {
  flex: none;
  font-size: 12px;
  color: var(--text-soft);
  text-align: right;
}

/* Never verified their email, so they cannot log in at all yet — worth
   seeing before deciding anything about the account. */
.admin-request-when em {
  display: block;
  font-style: normal;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--bad);
}

.admin-request-actions { display: flex; align-items: center; gap: 8px; flex: none; }

/* The timetable form, inside whichever row is being answered. flex-basis:100%
   drops it onto its own line under that row rather than squeezing in beside
   the name. */
.admin-plan {
  flex: 0 0 100%;
  margin-top: 12px;
  padding: 14px 16px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
}

.admin-plan-label {
  margin: 0 0 8px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-faint);
}

.admin-plan-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }

/* Real radios and checkboxes, with the whole pill as the target. */
.plan-choice {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 7px 13px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 14px;
  cursor: pointer;
}

.plan-choice:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  font-weight: 600;
}

.admin-plan-actions { display: flex; gap: 8px; }

/* ------------------------------------------------- admin: course dates

   One date per class and course. Grouped by class, three courses to a row,
   so the grid reads the way the timetable does. */
.admin-course-group { padding: 14px 0; border-top: 1px solid var(--border); }
.admin-course-group:first-child { border-top: 0; padding-top: 4px; }

.admin-course-rows {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
  margin-top: 10px;
}

.admin-course { display: flex; flex-direction: column; gap: 5px; }

.admin-course span {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-faint);
}

.admin-course input {
  width: 100%;
  padding: 8px 10px;
  font-size: 14px;
}

.admin-course-state { font-size: 12px; color: var(--text-faint); }
.admin-course-state.is-good { color: var(--good); }
.admin-course-state.is-bad { color: var(--bad); }

/* Which class somebody is in, beside their name in the People list. Reads as
   a sibling of the admin tag, in the app's own colour rather than that
   yellow -- being a student is not being an admin. */
.class-tag {
  flex: none;
  padding: 1px 7px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--blue-soft) 22%, transparent);
  color: var(--blue);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
}

/* Where the flag is read — and taken back — once it has been granted, since
   an approved account has left the queue by then. */
.admin-tutoring-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}

.admin-tutoring-row > span:first-child {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-faint);
}

.admin-tutoring-row { flex-wrap: wrap; }
.admin-tutoring-row strong { flex: 1; font-weight: 600; }
/* The class and courses under the status word. */
.admin-tutoring-row strong em {
  display: block;
  font-style: normal;
  font-weight: 500;
  font-size: 12px;
  color: var(--text-soft);
}

.admin-seen { flex: none; text-align: right; min-width: 96px; }
.admin-seen strong { display: block; font-size: 13px; font-weight: 600; }
.admin-seen em { font-style: normal; font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); }

/* ------------------------------------------------------------ admin detail */
.admin-detail-head .back-btn { margin-bottom: 14px; }

.admin-identity { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; }
.admin-identity-who { flex: 1 1 220px; min-width: 0; }
.admin-identity-who h2 { display: flex; align-items: center; gap: 8px; font-size: 22px; margin: 0 0 2px; }
.admin-identity-who p { margin: 0 0 10px; color: var(--text-soft); font-size: 13px; word-break: break-all; }
.admin-self { color: var(--text-faint) !important; font-style: italic; }

/* The account's dates and counts. Auto-fit rather than a fixed column count
   so the five of them reflow instead of squeezing. */
.admin-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
}

/* Label to the top of the cell, value to the bottom. The grid stretches every
   cell to the tallest in the row, so without this a label that wraps to two
   lines ("Last synced" in Polish does) drops its own value a line below the
   four beside it. */
.admin-fact { display: flex; flex-direction: column; justify-content: space-between; gap: 3px; }

.admin-fact span {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-faint);
}

.admin-fact strong { font-size: 14px; font-weight: 600; }

/* --------------------------------------------------------- activity chart */
/* Words practised a day over a fortnight. One series, one hue — the flame the
   streak already uses for this number — so there is no legend to draw: the
   panel heading says what is plotted. */
.admin-chart { margin-top: 4px; }

.admin-plot {
  position: relative;
  height: 148px;
  border-bottom: 1px solid var(--border);
}

/* Columns are capped at 24px and spread, so a fortnight on a wide screen is a
   row of thin marks rather than fourteen slabs. */
.admin-cols {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2px;
}

.admin-col {
  flex: 1 1 0;
  max-width: 24px;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

/* Always in the flow, labelled or not, so every column shares one baseline
   and the peak's number can't shift its own bar down relative to the rest. */
.admin-col-cap {
  flex: none;
  height: 15px;
  line-height: 15px;
  font-size: 10px;
  font-weight: 650;
  text-align: center;
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
}

/* Rounded at the data end, square on the baseline it grows from. Days under
   the goal take a lighter step of the same hue, so the fill states what the
   line states — no second colour needed to say "this day counted". */
.admin-col-fill {
  flex: none;
  min-height: 2px;
  border-radius: 4px 4px 0 0;
  background: color-mix(in srgb, var(--flame) 32%, var(--bg-tint));
}

.admin-col-fill.is-met { background: var(--flame); }

.admin-goal {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 1px solid var(--border-strong);
  pointer-events: none;
}

.admin-goal em {
  position: absolute;
  right: 0;
  top: -15px;
  font-style: normal;
  font-size: 10px;
  color: var(--text-faint);
  background: var(--surface); /* so the label isn't read through a column behind it */
  padding: 0 3px;
}

.admin-axis {
  display: flex;
  justify-content: space-between;
  margin-top: 6px;
  font-size: 10px;
  color: var(--text-faint);
}

/* "+3 still learning", riding the learned count at the tip of each bar. */
.stats-cat-count em { font-style: normal; color: var(--text-faint); font-size: 11px; }

/* ------------------------------------------------------------ class notes */
/* The three notes, side by side while there is room for them. Deliberately
   plain form controls rather than a mock-up of the paper they land on: this
   is the writing end, and a textarea that behaves like a textarea is easier
   to write in than one dressed as a scrapbook page. */
.admin-notes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px;
  margin-top: 4px;
}

.admin-note-edit { display: flex; flex-direction: column; gap: 8px; }

.admin-note-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.admin-note-head strong { font-size: 14px; font-weight: 650; }
.admin-note-head em { font-style: normal; font-size: 11px; color: var(--text-faint); }

.admin-note-body {
  width: 100%;
  min-height: 132px;
  padding: 11px 13px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 14px;
  line-height: 1.55;
  /* Down but not across: a note wraps, and a textarea that can be dragged
     wider than its column breaks the grid it sits in. */
  resize: vertical;
}

.admin-note-body:focus { outline: 2px solid var(--course-accent); outline-offset: 1px; border-color: transparent; }

.admin-note-actions { display: flex; align-items: center; gap: 10px; }

/* Beside its own Save button rather than in the screen's status line, so
   three notes sharing a panel each say their own piece. */
.admin-note-state { font-size: 12px; color: var(--text-faint); }
.admin-note-state.is-good { color: var(--good); }
.admin-note-state.is-bad { color: var(--bad); }

/* -------------------------------------------------------- sign-in history */
.admin-logins { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }

.admin-logins li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 4px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}

.admin-logins li:first-child { border-top: 0; }
.admin-logins strong { font-weight: 500; font-variant-numeric: tabular-nums; }
.admin-logins em { font-style: normal; color: var(--text-faint); }

@media (max-width: 620px) {
  /* Smaller controls, further apart. Both halves matter: shrinking them is
     what buys the room for the gap, and the gap is what stops a thumb landing
     on the theme toggle when it meant the profile. Every .icon-btn follows,
     not just the topbar's, so the panels stay in proportion with it. */
  :root { --control-h: 32px; }

  .topbar { padding: 8px 14px; }

  /* Down to three controls: Log out moved to the profile page and the
     Translations picker to the settings panels. Letting the brand truncate
     stops a long course-plus-name label pushing the icons off the edge. */
  .topbar-actions { gap: 9px; }
  .icon-btn { font-size: 15px; }
  .lang-btn { font-size: 11px; }
  /* A size down as well, which trims its width — the label above it is
     hidden here, so the select is carrying the row's whole text weight. */
  .topbar-actions select { padding: 5px 8px; font-size: 13px; }
  /* The brand gives way, the controls never do. Every link in the chain —
     picker, button, text — needs min-width:0, since a flex item refuses by
     default to shrink below its own content: without it a long "for <name>"
     ran straight under the controls instead of ending in an ellipsis. */
  .topbar-actions { flex: none; }
  .course-picker, .brand, .brand-text { min-width: 0; }
  .brand-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* On the home screen the button doubles as the course switcher. Closed,
     "for <name>" is just spending width the flag + course name already
     needed; it earns its place back the moment the picker is actually open. */
  .brand:not(.is-home-link) .brand-text em { display: none; }
  .brand:not(.is-home-link)[aria-expanded="true"] .brand-text em { display: inline; }
  .card-controls .ghost-btn { flex: 1; }
  .start-bar { flex-direction: column; align-items: stretch; text-align: center; }
  .word-table .col-note { display: none; }
  .word-table .cell-note { display: none; }

  /* Too narrow to park the flame beside the greeting without it landing on
     the heading, so it moves above the hero and the panel centres under it. */
  .streak { position: relative; top: auto; right: auto; display: flex; justify-content: center; }
  .streak-panel { left: 50%; right: auto; transform: translateX(-50%); }

  /* Three figures won't sit beside a name at this width, so the row becomes
     two lines with the figures spread under the name. */
  .board-row { flex-wrap: wrap; }
  .board-name { flex: 1 1 auto; }
  .board-figures { flex: 1 0 100%; justify-content: space-between; padding-left: 48px; }
  .board-figure { text-align: left; }

  /* The sort control can't share a line with its heading here -- left beside
     it, it pushes the panel wider than the screen and the whole page scrolls
     sideways. Dropped onto its own full-width line instead. */
  #view-leaderboard .panel-head { flex-wrap: wrap; }
  #board-sort { display: flex; width: 100%; }
  #board-sort button { flex: 1; }

  .add-friend-form .field { flex-basis: 100%; }
  .add-friend-form .primary-btn { width: 100%; }

  /* Same problem as the leaderboard's sort control: four buttons beside the
     heading push the panel wider than the screen. Onto its own line, and the
     labels share the width evenly. */
  #view-admin .panel-head { flex-wrap: wrap; }
  #admin-sort { display: flex; width: 100%; }
  #admin-sort button { flex: 1; }

  /* A name, three figures and a timestamp won't sit on one line here. The
     name keeps the top row with the picture; the figures and "last seen"
     drop underneath, aligned to the same edge the name starts at. */
  .admin-row { flex-wrap: wrap; }
  .admin-who { flex: 1 1 auto; }
  .admin-row-figures { flex: 1 0 100%; justify-content: space-between; padding-left: 50px; }
  .admin-figure { text-align: left; }
  .admin-seen { flex: 1 0 100%; text-align: left; padding-left: 50px; min-width: 0; }
  .admin-seen strong { display: inline; }
  .admin-seen em { display: inline; margin-left: 5px; }

  /* Fourteen columns at 24px each need more room than a phone has, so the
     cap comes off and they share whatever is there. */
  .admin-col { max-width: none; }
}
