/* THE LESSON PAGES' LOOK. (Build 147.2.) The app and the lesson pages are one
   school, and from 147 a lesson opens inside the app — so they share the
   lesson template's navy ink (#10233B) and terracotta, Playfair Display for
   titles and Be Vietnam Pro for text, a face drawn for Vietnamese diacritics.

   THE BACKGROUNDS ARE CLAUDE'S. (Build 147.3.) 147.2 also took the lesson
   template's cream paper (#F4F3EF) and a navy night for dark mode. Khoa asked
   for the backgrounds of Claude's own interface instead, measured off its
   screen pixel by pixel: page #FCFCFB, soft fills #F6F6F4 and #EDECE8, cards
   white; in dark, page #151515, inset #111111, raised #20201F. */
:root{
  color-scheme:light;
  --paper:#FCFCFB;--panel:#F6F6F4;--card:#FFFFFF;--card-2:#F6F6F4;
  /* Secondary text was drifting toward decoration. These sit at the contrast
     of body copy you would actually read for an hour, not at the contrast of
     a caption — navy-grey now, off the lesson template's ink. */
  --ink:#10233B;--ink-2:#3C4A5B;--ink-3:#566272;
  --line:#E6E4DF;--line-2:#CFCCC5;
  --accent:#D97757;--accent-deep:#B85C3E;--accent-wash:#F6E7DF;
  --accent-text:#A85234;
  --on-accent:#FFFFFF;
  --done:#7C8F63;--done-wash:#E8EDE0;--wrong:#B4553A;--wrong-wash:#F8E5DF;
  --err-ink:#A33A22;
  /* the Vocabulary lab's stars and combo (build 181) */
  --gold:#B8860B;--gold-wash:#F7EDD6;
  --sunk:#EDECE8;--track:#EDECE8;--glass:rgba(246,246,244,.6);
  /* The landing nav, now that it actually stays on screen. It was --glass, an
     inset fill meant for code blocks: over a page that is MOVING underneath it,
     a bar you can read the h1 through is worse than no bar at all. Solid, like
     the app's own topbar — same building. */
  --nav-bg:#FCFCFB;
  /* One soft, wide shadow, reused everywhere a card sits on the page. */
  --shadow:0 1px 2px rgba(16,35,59,.05),0 6px 18px rgba(16,35,59,.06);
  --hero:#FAFAF8;--locked-bg:#F6F6F4;--soon-bg:#F9F9F7;
  --lock:#A3A199;--r:12px;
  /* Level themes. Each is an edge, a pale label and a tinted wash, the same
     three-part shape the reference uses for its cards. Blue, amber and mauve
     were chosen over the reference's green and peach because green would read
     as "correct" and peach as "current" — both already mean something here. */
  --lv1:#4E5C9E;--lv1-text:#3A4478;--lv1-wash:#EAECF8;   /* Basic        */
  /* chart colours (build 251, dataviz reference palette, checked for colour
     blindness: adjacent pairs pass in both themes): one per class, in order */
  --cat1:#2a78d6;--cat2:#eb6834;--cat3:#1baf7a;--cat4:#eda100;--cat5:#e87ba4;--cat6:#008300;--cat7:#6250d6;--cat8:#e34948;
  --lv2:#7A7B42;--lv2-text:#55561F;--lv2-wash:#F0F0DC;   /* Intermediate */
  --lv3:#96628F;--lv3-text:#6A3A63;--lv3-wash:#F6E9F4;   /* Advanced     */
  /* The lesson template's three faces (147.2). The notes further down about
     IBM Plex Mono having no Vietnamese subset describe the face this replaced;
     JetBrains Mono has one, and the fallbacks they added are harmless. */
  --serif:'Playfair Display',Georgia,serif;--sans:'Be Vietnam Pro',system-ui,sans-serif;--mono:'JetBrains Mono',monospace;
  /* A face for READING, not for titles. (Build 155.) Playfair Display is a
     headline face and is only loaded at 600, so a practice passage set in it
     was a whole paragraph of semibold display type — fine for six words, hard
     going for two hundred. Source Serif 4 is a book face (the legal pages
     already use it), loaded at 400 for the passage alone. Its ink is a step
     softer than --ink in dark, where near-white body text glares. */
  --read:'Source Serif 4',Georgia,serif;--read-ink:#1F2A37;
}
/* Keeps the reference's neutral greys, its orange and its navy bloom, but
   backs off the two things that make a landing page tiring to sit inside for
   an hour: the ground is lifted off pure black and the text is pulled off
   pure white. White on black runs at 21:1, where the letterforms bleed and
   the eye keeps refocusing. Reading text now sits at 13.6:1 — the same band
   as Claude, Notion and VS Code, all of which people stare at all day.
   Secondary text lands near 10:1 and captions near 6:1, so the hierarchy is
   carried by three clear steps rather than by one blinding one. */
:root[data-theme="dark"]{
  color-scheme:dark;
  /* CLAUDE'S DARK. (Build 147.3.) Measured off Claude's own dark interface:
     page #151515, the inset sidebar #111111, raised surfaces #20201F — so
     elevation runs in both directions from the page. (147.2 had tried a navy
     night here; Khoa preferred this.) Text is warm off-white with the old
     contrast steps: near-white for reading, then two clear steps down, the
     last still clearing AA on a card. */
  --paper:#151515;--panel:#111111;--card:#20201F;--card-2:#1A1A19;
  --ink:#F5F4EF;--ink-2:#C3C2B7;--ink-3:#8F8D86;
  --line:#2A2A28;--line-2:#3B3B39;
  /* The selected/active wash has to be LIGHTER than whatever it replaces, or
     selecting a card reads as a hole rather than a highlight. #2E1A13 sat at
     0.82x the luminance of --card and did exactly that. It also ran at chroma
     27, double the level washes and level with --wrong-wash, so "selected"
     and "error" looked alike. This sits at 1.52x --card and chroma 17. */
  --accent:#D97757;--accent-deep:#E89070;--accent-wash:#352A24;
  --accent-text:#EC8F6E;
  --on-accent:#151515;
  --done:#8FB06E;--done-wash:#1D2718;--wrong:#E58270;--wrong-wash:#301A16;
  --gold:#E2B24C;--gold-wash:#33291A;
  --err-ink:#EC7E7E;
  --lv1:#7280C4;--lv1-text:#D4DAFA;--lv1-wash:#2A2C36;
  --cat1:#3987e5;--cat2:#d95926;--cat3:#199e70;--cat4:#c98500;--cat5:#d55181;--cat6:#008300;--cat7:#9085e9;--cat8:#e66767;
  --lv2:#A89C4E;--lv2-text:#F2F2CE;--lv2-wash:#34342A;
  --lv3:#AC77A5;--lv3-text:#F0CCEB;--lv3-wash:#332A32;
  --sunk:#0E0E0E;--track:#2A2A29;--glass:rgba(255,255,255,.04);--nav-bg:#151515;
  --shadow:0 1px 2px rgba(0,0,0,.4),0 6px 18px rgba(0,0,0,.3);
  --hero:#191918;--locked-bg:#121212;--soon-bg:#1B1B1A;
  --lock:#75746F;
  --read-ink:#DAD8D0;
}
*{box-sizing:border-box;}
html,body{margin:0;padding:0;height:100%;}
/* A DOUBLE TAP DOES NOT ZOOM THE APP (build 224.1). Khoa, three iPhone
   screenshots the day 224 went live: the thin line "is still there" (now
   through "6 of 24 sessions published"), "a scrolling bar on the right of
   mobile view", and "I can now drag the app page this way" (the whole page,
   header and all, pulled sideways).

   One cause for the three. In his screenshots the right edge of everything
   is where it should be and the left edge is 12 points too far left: the
   page is 3.3% bigger than the screen, and showing its right end. That is
   Safari's double-tap zoom: two quick taps on the tab row (224's own check
   was "press each of the three tabs") and it enlarges the page until that
   row fills the width. A zoomed page can be dragged every way, shows its
   scroll bar while it is dragged, and at 1.033× the joins between the
   pieces a phone paints a page in no longer land on whole pixels, which
   is the thin line.

   `manipulation` keeps scrolling and the two-finger pinch (anyone who
   needs bigger text can still zoom) and drops only the double tap, here
   and in everything inside, because the browser takes the strictest value
   between the finger and the root. A lesson page is its own document in a
   frame and keeps its own behaviour.

   Not seen in Chrome on a computer, which has no double-tap zoom; the
   phone is the only check. A phone that is already zoomed stays zoomed
   until it is pinched back or the tab is closed. */
html{touch-action:manipulation;}
body{background:var(--paper);color:var(--ink);font-family:var(--sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;}
#root{height:100vh;height:100dvh;}
button{font-family:inherit;font-size:inherit;cursor:pointer;border:none;background:none;color:inherit;}
button:focus-visible,input:focus-visible,textarea:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
/* Thin scrollbars everywhere (147.3; they were 9px). Chrome, Edge and Safari
   read the -webkit- rules; Firefox reads `scrollbar-width`. Chrome would read
   that one too and then ignore the -webkit- ones, so it is given only to a
   browser that does not know them. */
::-webkit-scrollbar{width:5px;height:5px;}
::-webkit-scrollbar-thumb{background:var(--line-2);border-radius:5px;}
::-webkit-scrollbar-thumb:hover{background:var(--ink-3);}
::-webkit-scrollbar-track{background:transparent;}
@supports not selector(::-webkit-scrollbar){
  *{scrollbar-width:thin;scrollbar-color:var(--line-2) transparent;}
}
/* On a phone the column has no bar of its own (build 224.1, Khoa: "it's
   weird when having a scrolling bar on the right of mobile view"). The thin
   bar above is for a mouse; a browser that honours it on a touch screen
   (Chrome on Android does) keeps a grey strip down the right edge of the one
   column a phone shows, where a phone's own scroll mark comes and goes by
   itself. Only for a finger and only at phone width: a narrow window on a
   computer keeps its bar. */
@media (max-width:900px) and (pointer:coarse){
  .cols{scrollbar-width:none;}
  .cols::-webkit-scrollbar{display:none;}
}
.app{display:flex;flex-direction:column;height:100vh;height:100dvh;overflow:hidden;}

.topbar{display:flex;align-items:center;gap:16px;padding:0 18px;height:54px;flex:0 0 auto;border-bottom:1px solid var(--line);background:var(--paper);}
.brand{display:flex;align-items:baseline;gap:9px;}
/* The mark and the wordmark are a button — the logo is where everyone reaches
   for "home", and a logo that does nothing is a dead spot in the one corner
   every visitor tries first. The course title beside it is NOT part of it:
   under 900px `.brand` is the full width of the row, and a button that wide
   turns an idle tap anywhere along the bar into leaving the lesson.
   The global button reset already strips border, background, colour and font,
   so only the pad is left — and the negative margin cancels it, so the row
   measures exactly as it did and the 9px gap to the course title survives. */
.brand-home{display:flex;align-items:baseline;gap:9px;line-height:1.5;padding:5px 7px;margin:-5px -7px;border-radius:9px;text-align:left;}
.brand-home:hover{background:var(--sunk);}
.brand-logo{flex:0 0 auto;align-self:center;display:block;width:30px;height:30px;object-fit:contain;}
.brand-mark{width:22px;height:22px;border-radius:6px;background:var(--accent);display:inline-flex;align-items:center;justify-content:center;align-self:center;color:var(--on-accent);font-family:var(--serif);font-size:13px;font-weight:600;}
.brand-name{font-family:var(--serif);font-size:17px;font-weight:600;letter-spacing:-.01em;}
.brand-sub{font-size:12px;color:var(--ink-3);}
.topbar-spacer{flex:1;}
.tb-break{display:none;}   /* the phone header's second row starts here (212) */
.stat{display:flex;align-items:center;gap:6px;font-size:12.5px;color:var(--ink-2);}
.stat b{font-family:var(--mono);font-size:13px;font-weight:500;color:var(--ink);}
.stat-div{width:1px;height:16px;background:var(--line);}
/* ---- shelf: view switch + filters ---- */
/* One row since build 147.1, the chips scrolling sideways with a fade at the
   right edge. Build 185, Khoa: every chip shows — they wrap onto a second row
   when the column is narrow (All · three levels · three tracks), and the
   Cards/List switch stays at the top right. See the note in LibraryColumn. */
/* build 216: the search box, over the chips, the full width of the column */
.shelf-q{position:relative;margin-bottom:10px;}
/* 254.7 (Khoa: "slightly smaller, it seems big compared to the rest"): 32px, the chips' size of type */
.shelf-q .field{padding-left:30px;padding-right:32px;border-radius:18px;height:32px;}
/* smaller type on a computer only: a phone keeps 16px, or Safari zooms in on focus (pointer:coarse rule below) */
@media (pointer:fine){.shelf-q .field{font-size:13.5px;}}
.shelf-q .field::-webkit-search-cancel-button{display:none;}   /* our own ✕, one look everywhere */
.shelf-q-i{position:absolute;left:12px;top:50%;transform:translateY(-52%);font-size:17px;line-height:1;
  color:var(--ink-3);pointer-events:none;}
.shelf-q-x{position:absolute;right:6px;top:50%;transform:translateY(-50%);width:26px;height:26px;
  border:0;border-radius:50%;background:transparent;color:var(--ink-3);font-size:12px;cursor:pointer;}
.shelf-q-x:hover{background:var(--card-2);color:var(--ink);}
.shelf-bar{display:flex;align-items:flex-start;gap:8px;margin-bottom:12px;}
/* "Sign in to keep your progress" sat on the heading under it with no gap. */
.shelf-bar + .hint{margin-bottom:16px;}
.vswitch{display:flex;gap:3px;background:var(--panel);padding:3px;border-radius:8px;}
.vswitch button{flex:1;padding:5px 8px;border-radius:6px;font-size:12px;color:var(--ink-3);}
.vswitch button.on{background:var(--card);color:var(--ink);box-shadow:0 1px 2px rgba(0,0,0,.06);}
/* The Cards/List switch on the shelf is two icons (147.1). Scoped to the shelf:
   `.vswitch` is also the sign-in box's "Sign in / Create account" and its
   learner/teacher switch, and 147.1 first wrote this unscoped — which shrank
   those to 28px and stacked their words letter-width. */
.shelf-bar .vswitch{flex:0 0 auto;gap:2px;}
.shelf-bar .vswitch button{flex:none;width:28px;height:24px;padding:0;font-size:13px;line-height:1;}
.filters{flex:1 1 auto;min-width:0;display:flex;flex-wrap:wrap;gap:5px;}
.fchip{padding:3px 9px;border-radius:20px;border:1px solid var(--line-2);
  font-size:11.5px;color:var(--ink-3);background:var(--paper);white-space:nowrap;}
.fchip:hover{border-color:var(--ink-3);color:var(--ink-2);}
.fchip.on{border-color:var(--accent);background:var(--accent-wash);color:var(--accent-text);}

/* ---- card view ---- */
/* 250px was the floor from the day this grid was written, when the Course
   column was a third of the window and the number never had to hold anything
   up. Dragged wide it does: at 830px the grid took three tracks of 265, and at
   265 a card wraps its own TAG ROW onto a second line — the one line in the
   card that is a single object, and the line that tells you at a glance what
   level a course is. The action row is `flex-wrap:wrap` and is built to break;
   the tags are not.

   290 is where the tag row holds at both languages' worst case, so it is the
   width the grid may not go under. Two of them fill the column at every setting
   the drag handle allows, and the third track — the one that caused this — can
   no longer happen.

   `min(290px,100%)` and not a bare 290px: a MINIMUM track wider than the
   container does not shrink, it overflows — and the Course column's own floor
   is 270px, so a bare 290 pushed every card past the right-hand edge at the
   narrowest setting the handle allows and on a 320px phone. The one width the
   floor may never beat is the width there is. (Build 139.2.) */
.kan{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(290px,100%),1fr));gap:10px;}
/* A CARD ON ITS OWN TAKES THE WHOLE ROW. (Build 139.2.)
   The Course column is draggable, so this grid is one column wide at 430px and
   two at 640px — and a collapsed section holds exactly one card. Left in a cell,
   that card sat at half the column with nothing beside it and the deck's sheets,
   which are the card's own width by construction, ran the full width underneath
   it. Spanning the row is what makes the sheets line up again, and it is the
   honest drawing of the state: one card face up, and everything else behind it.
   `:only-child` and not a class, because the condition IS "there is one card
   here" and the grid is the only thing that knows the row is wider than it. */
.kan>.kcard:only-child{grid-column:1/-1;}
.kcard{border:1px solid var(--line-2);border-radius:12px;background:var(--card);overflow:hidden;
  transition:border-color .15s;}
.kcard:hover{border-color:var(--ink-3);}
.kcard.on{border-color:var(--accent);}
/* THE COURSE YOU HAVE PICKED, WASHED OVER — AT EVERY WIDTH.
   Everything else in the app that is chosen says so by being FILLED, not by
   being outlined: the pace tile in the Plan, the paywall panel in the Journey,
   the active tab, every chip that is on. The shelf was the one place a
   selection was a 1px edge and nothing else — a different language for the same
   idea, in the column where the choice is actually made.

   116.3 gave the card this wash on a phone only, on the argument that side by
   side the two columns changing is confirmation enough. That was the wrong
   argument. The columns confirm what you picked; they do not mark WHICH card in
   a list of eleven it was, and the three columns should not each say "this one"
   in a different way. (Build 116.5.)

   `::after` over the card rather than a background on it, so the rows inside
   keep their own fills; `inset:0` so it covers the action row too;
   `pointer-events:none` so the card underneath is entirely clickable. The
   accent at 7% reads in both themes and leaves the text alone. */
.kcard,.trow{position:relative;}
.kcard.on::after,.trow.on::after{content:'';position:absolute;inset:0;
  background:var(--accent);opacity:.07;pointer-events:none;}
/* THE COURSE YOU HAVE JUST SWITCHED ON, RINGED FOR A SECOND AND A HALF.
   (Build 121.) It has moved from the Course library up into My learning, past
   whatever the reader was looking at, so it needs to say "here I am" once and
   then stop — a permanent mark would be a second thing on the shelf claiming to
   be the selection, and `.on` is already that.

   `::before` and not `::after`, because `::after` is the selection wash above
   and the card carries both at once now: activating also selects. `inset:0`
   rather than a ring outside the card, because `.kcard` is `overflow:hidden`
   and anything at a negative inset would simply be clipped away.

   Opacity only. A `box-shadow` fading to `transparent` interpolates through
   rgba(0,0,0,0) and leaves a grey halo on the way out, and a colour animation
   here would be the one thing on this shelf that changes with the theme. */
@keyframes shelfFlash{0%{opacity:0}12%{opacity:1}70%{opacity:1}100%{opacity:0}}
.kcard.flash::before,.trow.flash::before{content:'';position:absolute;inset:0;
  border:2px solid var(--accent);border-radius:inherit;pointer-events:none;
  animation:shelfFlash 1.5s ease-out 1;}
@media (prefers-reduced-motion:reduce){
  .kcard.flash::before,.trow.flash::before{animation:none;opacity:1;}}
.kcard-head{display:flex;gap:11px;padding:11px 12px;text-align:left;width:100%;}
.kcard-act{display:flex;align-items:center;gap:6px;flex-wrap:wrap;
  padding:8px 12px;border-top:1px solid var(--line);background:var(--paper);}
.kcard-new{display:flex;gap:6px;padding:8px 12px;border-top:1px solid var(--line);}
.kcard-new .field{flex:1;min-width:0;}
.kcard-kids{padding:2px 12px 9px;border-top:1px solid var(--line);}
/* The two questions a join can come back with — a code for another course, or
   a class you are already in — are answered on the card, under the field they
   were typed into. `.fb` carries a `margin-top` because it is normally a
   paragraph in a column; inside a card it is a panel, so it takes the card's
   own gutter instead. (Build 118.) */
.kcard .fb,.tnode .fb{margin:0 12px 10px;}
/* The link out to the course's page on the school site. It is an <a>, not a
   button — middle-click, long-press and "copy link" are the whole point of a
   link, and a button that calls window.open has none of them — so it is given
   the button's box back by hand. (Build 118.) */
a.cls-btn{display:inline-flex;align-items:center;gap:4px;text-decoration:none;}
a.cls-btn.detail{color:var(--ink-3);}
a.cls-btn.detail:hover{color:var(--accent-text);border-color:var(--accent);}
/* ---- shelf sections ---- */
.sec+.sec{margin-top:18px;}
.sec-h{display:flex;align-items:baseline;gap:8px;margin:0 0 9px;}
.sec-h .eyebrow{flex:0 0 auto;}
.sec-rule{flex:1;height:1px;background:var(--line);}
.sec-n{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);}
/* build 221: the fold, at the end of the heading line — small, quiet, a link */
.sec-more{flex:0 0 auto;display:inline-flex;align-items:center;gap:4px;margin:-3px -6px -3px 0;padding:3px 6px;
  border:0;border-radius:6px;background:none;font:inherit;font-size:12px;color:var(--accent-text);cursor:pointer;
  align-self:center;white-space:nowrap;}
.sec-more:hover{background:var(--accent-wash);}
.sec-more:focus-visible{outline:2px solid var(--accent);outline-offset:1px;}
.sec-more i{font-style:normal;font-size:10px;}
.fold{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;
  padding:9px 12px;border:1px dashed var(--line-2);border-radius:10px;background:transparent;
  font-size:12.5px;color:var(--ink-3);transition:border-color .15s,color .15s;}
.fold:hover{border-color:var(--accent);color:var(--accent-text);}
.fold i{font-style:normal;font-family:var(--mono);font-size:11px;}
.sec-empty{font-size:12px;color:var(--ink-3);padding:2px 0 4px;}
/* ---- the card stack (build 128) ----
   One card face up and the rest peeking out below it as narrowing sheets, the
   way a phone stacks notifications. The sheets are INSIDE the button, so the
   whole deck is the target and the card above keeps its own Add / Remove /
   Join buttons to itself.

   `margin-top:-1px` on the first sheet lands it on the card's own bottom
   border rather than a pixel under it, so the two read as one object stacked
   rather than as a card with a bar beneath. Seven pixels of each sheet show:
   enough to be a deck, not enough to be mistaken for a row. */
.kstack-peek{display:block;width:100%;padding:0;background:none;}
.kstack-peek .pk{display:block;margin-left:auto;margin-right:auto;
  border:1px solid var(--line-2);border-top:none;
  border-radius:0 0 12px 12px;transition:border-color .15s;}
/* The two sheets are DIFFERENT FILLS, not one fill at two widths. Stacked
   vertically they are seven pixels apart, and seven pixels of the same colour
   with a hairline between them reads as one thick border rather than as two
   cards. `--sunk` is the app's own "further back" surface — the tab row uses
   it against `--paper` for exactly this reason (112.1). */
.kstack-peek .pk1{width:calc(100% - 16px);height:8px;margin-top:-1px;
  background:var(--card);}
.kstack-peek .pk2{width:calc(100% - 38px);height:7px;background:var(--sunk);}
.kstack-peek:hover .pk{border-color:var(--accent);}
.kstack-lab{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;
  padding:9px 12px;margin-top:9px;border:1px dashed var(--line-2);border-radius:10px;
  font-size:12.5px;color:var(--ink-3);transition:border-color .15s,color .15s;}
.kstack-peek:hover .kstack-lab{border-color:var(--accent);color:var(--accent-text);}
.kstack-lab i{font-style:normal;font-family:var(--mono);font-size:11px;}
/* ---- admin dashboard ---- */
.dash{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--line);
  border:1px solid var(--line);border-radius:10px;overflow:hidden;}
.dash-cell{background:var(--card);padding:10px 8px;text-align:center;}
.dash-cell b{display:block;font-family:var(--serif);font-size:20px;font-weight:600;line-height:1.15;}
.dash-cell span{font-size:10.5px;color:var(--ink-3);}
.dash-cell.warn b{color:var(--accent-text);}
.dash-pick{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  padding:7px 9px;border:1px solid var(--line);border-radius:8px;background:var(--card);
  font-size:12.5px;margin-bottom:6px;}
.dash-pick:hover{border-color:var(--accent);}
.dash-pick.on{border-color:var(--accent);background:var(--paper);}
.dash-pick b{font-family:var(--mono);font-size:10.5px;color:var(--accent-text);flex:0 0 auto;}
/* the pick list's search (278.2): a row of the list's shape; empty, the open
   course is its placeholder, in ink, so it still reads as the course picked */
.dash-find{display:flex;align-items:center;gap:8px;width:100%;padding:0 9px;border:1px solid var(--line);
  border-radius:8px;background:var(--card);margin-bottom:6px;cursor:text;}
.dash-find.on{border-color:var(--accent);background:var(--paper);}
.dash-find:focus-within{border-color:var(--accent);}
.dash-find b{font-family:var(--mono);font-size:10.5px;color:var(--accent-text);flex:0 0 auto;}
.dash-find input{flex:1;min-width:0;padding:7px 0;border:0;background:transparent;outline:none;
  font:inherit;font-size:12.5px;color:var(--ink);}
.dash-find.on input::placeholder{color:var(--ink);opacity:1;}
.dash-find input::-webkit-search-cancel-button{cursor:pointer;}
.dash-find-i{color:var(--ink-3);font-size:14px;flex:0 0 auto;}
@media (pointer:coarse){.dash-find input{font-size:16px;}}
/* the admin's folds (269.9): a heading with its show / hide at the right */
.adm-h{display:flex;align-items:baseline;justify-content:space-between;gap:8px;}
.adm-fold{display:inline-block;margin:6px 0 0;padding:2px 0;border:0;background:none;font:inherit;font-size:12px;color:var(--accent-text);cursor:pointer;}
.adm-h .adm-fold{margin:0;}
.adm-fold:hover{text-decoration:underline;}
/* Quiet on purpose: it is there to explain the order of the list, not to be
   read down it. */
.dash-lvl{font-family:var(--mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);flex:0 0 auto;}
/* ---- the admin board's drill-down and money view (build 120.1) ----
   A count is a button now, so it has to look like one WITHOUT changing the
   height of the row it sits in: the grid is nine cells of a fixed size and a
   border would move every one of them. The affordance is the hover fill and
   the cursor, which is the same answer build 112 gave the roadmap days. */
button.dash-cell{width:100%;cursor:pointer;transition:background .12s;}
button.dash-cell:hover{background:var(--panel);}
/* THE RING GOES INSIDE. (Build 127.1.)
   The global focus style is `outline:2px solid var(--accent);outline-offset:2px`,
   and the grid's cells are separated by ONE pixel — so an outline two pixels
   outside a tile is painted across the tiles beside it, and what you see is an
   orange rectangle that belongs to no cell, straddling three. It appears at
   exactly the wrong moment, too: pressing a tile leaves it focused, the panel
   opens over it, and closing with Escape is a keyboard interaction — which is
   what flips Chrome's `:focus-visible` heuristic on for the element that still
   has focus. So the ring arrived only after you had used the thing, which is
   the definition of a mark that looks like a fault.
   An inset ring cannot leave the cell it belongs to. Keyboard focus is still
   visible, and it is still two accent pixels; it is simply drawn on the inside
   of the border rather than on top of the neighbours. */
button.dash-cell:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--accent);}
button.dash-cell.on{background:var(--accent-wash);}
button.dash-cell.on b{color:var(--accent-text);}
/* THE ROW THAT IS NOT A COUNT. (Build 127.)
   Money, Sign-ups and SePay are the same tile as the nine above them — same
   box, same number, same press — because they answer the same kind of
   question and a door that looks different from the doors beside it reads as
   a different kind of thing. The only difference is the rule along the top,
   which says: below this line the number is money and time rather than
   people. */
.dash-cell.grp{border-top:2px solid var(--accent-wash);}
.dash-cell .dash-em{color:var(--ink-3);}
/* The board while it is counting: the same twelve cells, dimmed, and not
   pressable — there is nothing behind them yet. (Build 140.) */
.dash-wait{opacity:.55;}
.dash-wait b{animation:dashPulse 1.4s ease-in-out infinite;}
@keyframes dashPulse{0%,100%{opacity:.45;}50%{opacity:.9;}}
/* Small enough that 1.990.000 ₫ fits in a third of a 320px column without
   the tile growing taller than the eight above it. */
.dash-cell b.dash-money{font-size:15px;letter-spacing:-.01em;}
/* WORK, NOT A COUNT. (Build 129.)
   The grid is always twelve (127) and a deletion request is not a thirteenth
   number about the business — it is a job with a clock on it, and it is only
   there some of the time. So it sits ABOVE the grid, in the error ink, and it
   is drawn at all only when there is something in it. Full width, because a
   task nobody notices is a task nobody does. */
.dash-task{display:flex;align-items:center;gap:9px;width:100%;text-align:left;
  border:1px solid var(--err-ink);background:var(--wrong-wash);color:var(--err-ink);
  border-radius:9px;padding:9px 12px;font-size:12.5px;line-height:1.4;margin-bottom:10px;}
.dash-task b{font-family:var(--serif);font-size:17px;font-weight:600;}
.dash-task span{flex:1;min-width:0;}
.dash-task:hover{background:var(--card);}
/* The number a door already knows, on the door. It replaces 134's quiet second
   band (134.1): a typo nobody has to answer never earned a band of its own, and
   what the band was actually carrying — how many, how many droppable — is four
   words that fit on the button. In the ink of a count, never the error ink: the
   band above is an alarm and this is a figure. */
.door-n{margin-left:7px;padding:1px 6px;border-radius:999px;
  border:1px solid var(--line);background:var(--sunk);color:var(--ink-3);
  font-family:var(--mono);font-size:10.5px;}
/* The mark inside an account row. `.dmail` is not a flex row, so the tag needs
   its own space either side of it or it grows out of the display name. */
.unc-mark{margin-left:7px;vertical-align:1px;}
/* One row of the deletion queue. It is not a `.drow`: a `.drow` is a button
   that opens the grant-and-seats actions, and none of those mean anything for
   an account on its way out. */
.del-row{border-top:1px solid var(--line);padding:8px 0;font-size:12.5px;}
.del-row:first-child{border-top:none;}
.del-top{display:flex;align-items:baseline;gap:8px;}
.del-top .dmail{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.del-why{font-size:11.5px;color:var(--ink-2);margin-top:3px;font-style:italic;}
.del-when{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);margin-top:2px;}
.del-act{display:flex;gap:6px;margin-top:6px;flex-wrap:wrap;}
.cls-tag.bad{border-color:var(--err-ink);color:var(--err-ink);background:var(--wrong-wash);}
.drow{display:flex;align-items:baseline;gap:8px;padding:6px 0;font-size:12.5px;
  border-top:1px solid var(--line);width:100%;text-align:left;}
.drow:first-child{border-top:none;}
.drow .dmail{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.drow .ddet{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);flex:0 0 auto;}
button.drow:hover .dmail{color:var(--accent-text);}
.dbar{display:flex;align-items:flex-end;gap:3px;height:54px;margin:8px 0 4px;}
.dbar i{flex:1;background:var(--accent-wash);border-radius:2px 2px 0 0;min-height:2px;
  border-bottom:2px solid var(--accent);}
.dmoney{display:flex;gap:14px;flex-wrap:wrap;font-size:12.5px;margin-top:6px;}
.dmoney b{font-family:var(--serif);font-size:17px;font-weight:600;display:block;}
/* ---- the drill-down (build 127, windowed in 127.1) ----
   IT IS A WINDOW, NOT A SCREEN. 127 made it full screen on the learner
   report's argument — a teacher READS the report, so it should be the only
   thing on the page. This is not that: an admin presses a tile, looks at
   eleven rows and goes back to the grid, and full screen made every one of
   those glances a departure from the board. Khoa tried it and said so.

   A window keeps the board visible behind the dim, so the thing you pressed
   is still on screen and where you are is never in doubt — and the dim is the
   same rgba the sign-in scrim and the tour use, because a scrim is a scrim.
   Under 640px it goes back to filling the screen: a phone has no room for a
   window with a page around it. */
/* `.scrim.apop-scrim`, TWO CLASSES, ON PURPOSE. `.scrim` is declared six
   hundred lines below this and sets `background:var(--paper)`; at equal
   specificity the later rule wins, so a single-class `.apop-scrim` was
   silently overruled and the dim came out as solid paper — near-white in
   light, near-black in dark, and the board invisible behind it either way.
   The same trap the print block fell into in 116.2, in a different room. */
.scrim.apop-scrim{background:rgba(20,20,19,.55);align-items:center;
  justify-content:center;padding:28px;}
.apop{width:min(880px,100%);height:min(660px,100%);background:var(--paper);
  display:flex;flex-direction:column;overflow:hidden;border-radius:14px;
  border:1px solid var(--line-2);box-shadow:0 18px 50px rgba(0,0,0,.35);
  animation:rise .22s cubic-bezier(.2,.8,.2,1);}
@media (max-width:640px){
  .scrim.apop-scrim{padding:0;}
  .apop{width:100%;height:100%;border-radius:0;border:none;box-shadow:none;}
  /* a phone's window never pans sideways as a whole (269.9): a wide table
     scrolls inside its own box (`.cw-tblw`, the attendance grid) */
  .apop-body{overflow-x:hidden;}
}
.apop-head{padding:12px 16px;border-bottom:1px solid var(--line);display:flex;align-items:center;
  gap:12px;background:var(--card);flex:0 0 auto;position:sticky;top:0;z-index:4;}
.apop:focus{outline:none;}          /* it is focused to move the ring off the tile, not to draw one */
.apop-t{font-family:var(--serif);font-size:16px;font-weight:600;line-height:1.2;}
.apop-s{font-size:11.5px;color:var(--ink-3);margin-top:1px;}
.apop-body{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:16px 20px 40px;}
.apop-in{max-width:760px;margin:0 auto;width:100%;}
/* The search box and the filters sit ABOVE the scroller's content but inside
   it, and they are the first thing under the header, so on a list of two
   hundred they are one flick away rather than at the far end. */
.apop-tools{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin:0 0 10px;}
.apop-tools .field{flex:1 1 220px;min-width:0;margin:0;}
.apop-chips{display:flex;gap:5px;flex-wrap:wrap;}
/* THE BOARD'S BAR (278.4): the search with Export at its right, then one line
   of small labelled pickers, all one height (34px), wrapping as whole pickers */
.adm-bar{display:flex;flex-direction:column;gap:8px;margin:0 0 12px;}
.adm-bar-top{display:flex;gap:8px;align-items:center;}
.adm-bar-top .field{flex:1 1 auto;min-width:0;margin:0;height:38px;padding:0 12px;}
.adm-csv{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;height:38px;padding:0 14px;
  border:1px solid var(--line-2);border-radius:10px;background:transparent;color:var(--ink);
  font:inherit;font-size:12.5px;font-weight:500;white-space:nowrap;cursor:pointer;}
.adm-csv span{font-family:var(--mono);font-size:11px;color:var(--ink-3);}
.adm-csv:hover:not(:disabled){border-color:var(--accent);color:var(--accent-text);}
.adm-csv:disabled{opacity:.45;cursor:default;}
.apop-tools .adm-csv{margin-left:auto;height:30px;}
.adm-bar-f{display:flex;flex-wrap:wrap;gap:6px;align-items:center;}
.adm-pick{display:inline-flex;align-items:center;height:32px;padding:0 4px 0 10px;gap:4px;
  border:1px solid var(--line-2);border-radius:999px;font-size:12px;color:var(--ink-3);white-space:nowrap;}
.adm-pick>span{font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;}
.adm-pick select,.adm-pick input{height:28px;border:0;background:transparent;outline:none;
  font:inherit;font-size:12.5px;color:var(--ink);cursor:pointer;padding:0 4px;}
.adm-pick select option{background:var(--card);color:var(--ink);}
.adm-pick.on{border-color:var(--accent);background:var(--accent-wash);}
.adm-pick.on select,.adm-pick.on input{color:var(--accent-text);}
.adm-pick:focus-within{border-color:var(--accent);}
.adm-dates input{width:118px;}
.adm-dates i{font-style:normal;color:var(--ink-3);}
.adm-dates input::-webkit-calendar-picker-indicator{opacity:.55;cursor:pointer;}
:root[data-theme="dark"] .adm-dates input{color-scheme:dark;}
.adm-clear{height:32px;padding:0 12px;border:0;border-radius:999px;background:transparent;
  color:var(--accent-text);font:inherit;font-size:12px;cursor:pointer;}
.adm-clear:hover{background:var(--accent-wash);}
@media (max-width:640px){.adm-bar-top{flex-wrap:wrap;}.adm-bar-top .adm-csv{flex:1 1 100%;justify-content:center;}}
@media (pointer:coarse){.adm-bar-top .field,.adm-pick select,.adm-pick input{font-size:16px;}}
.apop-sum{font-size:12px;color:var(--ink-2);margin:0 0 10px;line-height:1.6;}
.apop-sum b{font-family:var(--serif);font-size:14px;}
/* ---- the folder tree (build 127.1) ----
   Two of these lists are one thing repeated: `courses in use` said the same
   course down six rows, and `classes` said the same teacher's email down
   three. A list whose first column repeats is a list you read the second
   column of — so the repeated half becomes the folder and the rest hangs
   under it, and the folder shuts.

   The chevron is its OWN button, beside the row rather than around it. In
   `classes` the folder IS an account and pressing it has to be able to open
   the actions the way every other row does; one control cannot both fold and
   act, and nesting a button inside a button is not markup. */
.apop-node{display:flex;align-items:center;gap:0;border-top:1px solid var(--line);}
.apop-node:first-child{border-top:none;}
.apop-fold{flex:0 0 24px;height:100%;align-self:stretch;color:var(--ink-3);font-size:9px;
  display:flex;align-items:center;justify-content:center;border-radius:6px;}
.apop-fold:hover{background:var(--panel);color:var(--ink);}
.apop-node .drow{border-top:none;flex:1;min-width:0;}
/* The folder's own label, where the folder is not an account. */
.apop-dir{flex:1;min-width:0;display:flex;align-items:baseline;gap:8px;font-size:12.5px;
  padding:6px 0;text-align:left;}
.apop-dir b{font-family:var(--mono);font-size:11px;color:var(--accent-text);
  text-transform:uppercase;letter-spacing:.04em;flex:0 0 auto;}
.apop-dir span{color:var(--ink-3);font-size:11.5px;}
/* What hangs under a folder. Indented to the chevron's width so the tree is
   read by its left edge, and quiet, because the folder above it is the
   heading. */
.apop-kid{padding:4px 0 4px 24px;font-size:12px;color:var(--ink-2);
  display:flex;align-items:baseline;gap:8px;}
.apop-kid .k-det{font-family:var(--mono);font-size:11px;color:var(--ink-3);}
.apop-kid.k-row{padding-left:0;}
.apop-kid.k-row .drow{padding-left:24px;}
/* ---- billing, the side a learner and a teacher see (build 125) ---- */
.bill-row{display:flex;align-items:baseline;gap:8px;padding:7px 0;font-size:12.5px;
  border-top:1px solid var(--line);}
.bill-row:first-child{border-top:none;}
.bill-row .bl-n{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);flex:0 0 auto;}
.bill-row .bl-w{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.bill-row .bl-a{font-family:var(--mono);font-size:12px;flex:0 0 auto;}
.pay-grid{display:flex;gap:14px;flex-wrap:wrap;align-items:flex-start;margin-top:10px;}
.pay-qr{flex:0 0 auto;width:180px;height:180px;border:1px solid var(--line);border-radius:10px;
  background:#fff;object-fit:contain;}
.pay-facts{flex:1;min-width:200px;}
.pay-f{display:flex;align-items:baseline;gap:8px;padding:5px 0;border-top:1px solid var(--line);}
.pay-f:first-child{border-top:none;}
.pay-f span{font-size:11px;color:var(--ink-3);flex:0 0 82px;}
.pay-f b{font-family:var(--mono);font-size:13px;font-weight:600;flex:1;min-width:0;word-break:break-all;}
/* The code is the one thing on this panel somebody may have to read down a
   telephone, so it is the one thing set larger than the label beside it. */
.pay-code b{font-size:17px;letter-spacing:.06em;color:var(--accent-text);}
.pay-copy{flex:0 0 auto;padding:3px 8px;border-radius:6px;border:1px solid var(--line-2);
  font-size:11px;color:var(--ink-2);background:var(--card);}
.pay-copy:hover{border-color:var(--accent);color:var(--accent-text);}
.pay-wait{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--ink-3);margin-top:12px;}
.pay-dot{width:7px;height:7px;border-radius:50%;background:var(--accent);
  animation:payPulse 1.4s ease-in-out infinite;}
@keyframes payPulse{0%,100%{opacity:.25;}50%{opacity:1;}}
.pay-ok{border:1px solid var(--done);background:var(--done-wash);border-radius:10px;
  padding:10px 12px;font-size:13px;color:var(--done);margin-top:10px;}
.kls{display:flex;align-items:center;gap:7px;padding:6px 0;font-size:12.5px;}
.kls+.kls{border-top:1px solid var(--line);}
.kls-name{font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
/* THE NAME AS A CONTROL (build 140).
   Everything about the label is unchanged — same weight, same colour, same
   truncation — because the row must read the same whether or not you can
   rename. What is added is a ✎ that stands off to the side and an underline
   on hover, so the label says it is pressable without becoming a button that
   takes a button's space. A `<button>` does not inherit `line-height`: set it
   back, or this one row sits 5px taller than every other class row. */
.kls-edit{display:inline-flex;align-items:baseline;gap:5px;line-height:inherit;
  min-width:0;color:inherit;font:inherit;font-weight:500;text-align:left;}
.kls-edit:hover .kls-pen,.kls-edit:focus-visible .kls-pen{opacity:1;}
/* The label is its own element rather than a bare text node, because the
   ellipsis and the hover underline both need something to be applied TO. */
.kls-lbl{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.kls-edit:hover .kls-lbl{text-decoration:underline;text-underline-offset:2px;}
.kls-pen{flex:0 0 auto;font-size:10.5px;color:var(--ink-3);opacity:.5;transition:opacity .15s;}
/* The field takes the room the name and the code had: renaming is the one
   thing happening on this row while it is open. */
.kls-ren{gap:6px;}
.kls-in{flex:1;min-width:0;font-size:12.5px;padding:5px 8px;}
.kls-kid{padding-left:14px;color:var(--ink-2);}
/* ---- ONE RIGHT-HAND COLUMN (build 141) ----
   Every row on this card used to end wherever its own text ran out: a bar at
   one x, a bar at another, a button at a third, and three names of three
   different lengths deciding all of it. That is most of what reads as mess in
   a column 340px wide — nothing is crooked, but nothing lines up either.

   So the marks at the end of a row are ONE item, pushed right by an auto
   margin, with the percentage on a fixed width and tabular figures. The bars
   then share an x-position down the whole roster whatever the names do, and
   the ⋯ shares another. `flex:0 0 auto` for 116.2's reason — nothing in here
   may be squeezed; the row wraps instead. */
.kls-end{margin-left:auto;display:flex;align-items:center;gap:7px;flex:0 0 auto;}
.kls-end .kls-pct{width:31px;text-align:right;font-variant-numeric:tabular-nums;}
/* What a copy did, said under the row that did it. One line, in the caption
   ink: it is a receipt, not a warning. */
.kls-said{padding:0 0 6px 14px;font-size:11px;color:var(--ink-3);line-height:1.45;}
/* A departure is news, not a roster entry. The rule down its left edge is the
   same mark `.fb` uses for "read this bit" — enough to catch the eye scanning a
   column of classes, not so much that a class somebody left looks broken. The
   name is dimmed because the person is gone; the tag beside it is not. */
.kls-gone{border-left:2px solid var(--accent);padding-left:12px;margin-top:4px;
  background:var(--accent-wash);border-radius:0 7px 7px 0;}
.kls-gone+.kls-gone{border-top:none;}
.kls-gone .kls-name{color:var(--ink-2);}
.kls-when{font-family:var(--mono);font-size:10px;color:var(--ink-3);white-space:nowrap;}
.cls-tag.on{border-color:var(--done);color:var(--done);background:var(--done-wash);}
/* Membership, stated on the course itself so it survives a quick scan. */
.jchip{display:inline-flex;align-items:center;gap:5px;margin-top:5px;padding:2px 8px;
  border-radius:20px;font-size:11px;white-space:nowrap;border:1px solid var(--line-2);
  color:var(--ink-3);}
.jchip::before{content:'';width:5px;height:5px;border-radius:50%;background:currentColor;}
.jchip.in{border-color:var(--done);color:var(--done);background:var(--done-wash);}
.jchip.wait{border-color:var(--accent);color:var(--accent-text);background:var(--accent-wash);}
.trow .jchip{margin-top:0;flex:0 0 auto;}

/* ---- list view ---- */
.tree-list{display:flex;flex-direction:column;}
.tnode{border-bottom:1px solid var(--line);padding:2px 0;}
.trow{display:flex;align-items:center;gap:8px;padding:9px 0;}
.trow.on .trow-name{color:var(--accent-text);}
.trow-main{display:flex;align-items:flex-start;gap:7px;flex:1;min-width:0;text-align:left;}
.tglyph{color:var(--ink-3);font-size:11px;flex:0 0 10px;line-height:19px;}
/* Two lines (build 147.1): code and name, then what the course is. */
.trow-txt{display:flex;flex-direction:column;gap:3px;flex:1;min-width:0;}
.trow-l1{display:flex;align-items:baseline;gap:7px;min-width:0;}
.trow-l2{display:flex;align-items:center;flex-wrap:wrap;gap:4px 8px;min-width:0;}
.trow-l1 .ccode{flex:0 0 auto;}
.trow-name{font-size:13.5px;font-weight:500;min-width:0;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;}
.trow-meta{font-size:11.5px;color:var(--ink-3);white-space:nowrap;}
.trow-l2 .cls-tag{font-size:9.5px;padding:1px 6px;}
.tkid{padding-left:24px;border-left:1px solid var(--line-2);margin-left:5px;}
/* ---- the invitation link, on the teacher's class row (build 118) ---- */
.invite{display:flex;flex-wrap:wrap;align-items:center;gap:7px;padding:0 0 8px;}
/* The URL wants the whole width it can get and the button wants none of it,
   so the field grows and the button does not — and `min-width:0` because a
   flex item will not shrink below its content otherwise, and a URL has no
   spaces to wrap at. Written as width+min-width rather than a flex-basis, the
   rule build 116.2 paid for. */
.invite-u{flex:1 1 220px;min-width:0;font-family:var(--mono);font-size:11.5px;
  padding:6px 9px;color:var(--ink-2);}
.invite-n{flex:1 1 100%;font-size:11.5px;color:var(--ink-3);line-height:1.45;}

/* Classes */
.cls{border:1px solid var(--line-2);border-radius:11px;background:var(--card);
  padding:12px 13px;margin-bottom:9px;}
.cls-top{display:flex;align-items:baseline;gap:8px;}
.cls-name{font-family:var(--serif);font-size:14px;font-weight:600;}
.cls-course{font-size:11.5px;color:var(--ink-3);}
/* THE CODE STOPS SHOUTING (build 141).
   It was 14px letter-spaced mono in a dashed accent box with an accent wash —
   the loudest thing on the whole card, for six characters that are read once a
   term and never pressed. The accent is a claim about what to do next, and on
   this row that is `Invite link`. So the code keeps its mono, because it is a
   string somebody reads aloud character by character, and gives the colour
   back. `.ok` is the same box in the accent for a second and a half, which is
   how it says a copy actually happened. */
.cls-code{font-family:var(--mono);font-size:11px;letter-spacing:.1em;
  padding:2px 7px;border-radius:6px;border:1px solid var(--line-2);
  color:var(--ink-3);background:var(--sunk);white-space:nowrap;flex:0 0 auto;}
.cls-code.ok{border-color:var(--accent);color:var(--accent-text);background:var(--accent-wash);}
.cls-row{display:flex;align-items:center;gap:8px;padding:6px 0;
  border-top:1px solid var(--line);font-size:12.5px;}
.cls-row b{font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.cls-tag{font-family:var(--mono);font-size:10px;letter-spacing:.05em;text-transform:uppercase;
  padding:2px 7px;border-radius:20px;border:1px solid var(--line-2);color:var(--ink-3);}
.cls-tag.wait{border-color:var(--accent);color:var(--accent-text);background:var(--accent-wash);}
.cls-btn{padding:4px 9px;border-radius:7px;border:1px solid var(--line-2);
  font-size:11.5px;color:var(--ink-2);background:var(--paper);}
.cls-btn:hover{border-color:var(--accent);color:var(--accent-text);}
.cls-btn.go{background:var(--accent);color:#fff;border-color:var(--accent);}
.cls-btn .spin{width:9px;height:9px;margin-right:6px;}
/* The Add of the course open in Learn and Practice (build 182): bigger, a
   ring that pulses three times to be found, then stays to be remembered.
   The pulse reaches 9px and no further: `.kcard` is overflow:hidden and its
   action row has 8px of padding, so a wider one is cut off at the edge. */
.cls-btn.go.cls-add-hi{padding:6px 16px;font-size:14px;font-weight:600;
  box-shadow:0 0 0 3px var(--accent-wash),0 0 0 4px var(--accent);animation:addHi 1.4s ease-out 3;}
@keyframes addHi{0%{box-shadow:0 0 0 3px var(--accent-wash),0 0 0 4px var(--accent),0 0 0 4px rgba(217,119,87,.55);}
  100%{box-shadow:0 0 0 3px var(--accent-wash),0 0 0 4px var(--accent),0 0 0 9px rgba(217,119,87,0);}}
@media (prefers-reduced-motion:reduce){.cls-btn.go.cls-add-hi{animation:none;}}
.code-in{font-family:var(--mono);font-size:17px;letter-spacing:.18em;text-transform:uppercase;
  text-align:center;}

/* ---- the invitation screen (build 118) ----
   The whole page, not an overlay on one: it is the first thing a stranger
   following a link from Zalo ever sees, and there is nothing behind it worth
   showing them. `dvh` and `align-items:flex-start` with `margin:auto` on the
   panel, which is `.auth-scrim`'s rule and for its reason — a panel taller
   than the window must pin to the top rather than centre, because you cannot
   scroll up past the start of a scroll box. */
.inv-page{position:fixed;inset:0;z-index:60;background:var(--paper);
  height:100vh;height:100dvh;overflow-y:auto;display:flex;
  align-items:flex-start;justify-content:center;padding:20px;
  animation:fade .18s ease-out;}
.inv{margin:auto;width:100%;max-width:400px;}
.inv-brand{display:flex;align-items:center;gap:9px;margin-bottom:18px;
  font-family:var(--serif);font-size:15px;font-weight:600;}
.inv-card{display:flex;gap:13px;padding:13px;border:1px solid var(--line-2);
  border-radius:15px;background:var(--card);box-shadow:var(--shadow);}
.inv-c{min-width:0;display:flex;flex-direction:column;gap:6px;}
.inv-eyebrow{font-family:var(--mono);font-size:10px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-3);}
.inv-t{font-family:var(--serif);font-size:18px;font-weight:600;line-height:1.2;}
/* The code, still shown. The link is the easy road and the code is the one
   that survives a link being mangled by a chat app — a learner who can read
   six characters can always get in the other way. */
.inv-code{align-self:flex-start;font-family:var(--mono);font-size:13px;letter-spacing:.16em;
  padding:3px 9px;border-radius:7px;border:1px dashed var(--accent);
  color:var(--accent-text);background:var(--accent-wash);}
.inv-s{font-size:13px;line-height:1.55;color:var(--ink-2);margin:16px 0 14px;}
.inv-not{display:block;width:100%;margin-top:14px;padding:6px;font-size:12px;
  color:var(--ink-3);text-align:center;}
.inv-not:hover{color:var(--ink-2);}
.inv .cthumb-wrap{flex:0 0 auto;}
/* align-items:flex-start plus margin:auto on the box, NOT align-items:center.
   A centred flex item taller than its container overflows equally in both
   directions and the top — where the close button lives — becomes unreachable,
   because you cannot scroll up past the start of a scroll box. margin:auto
   centres a short dialog and pins a tall one to the top instead. dvh, not vh,
   so the phone's collapsing address bar is not counted twice. */
.auth-scrim{position:fixed;inset:0;z-index:70;background:rgba(20,20,19,.55);
  display:flex;align-items:flex-start;justify-content:center;padding:20px;
  height:100vh;height:100dvh;overflow-y:auto;overscroll-behavior:contain;
  animation:fade .15s ease-out;}
.auth-box{position:relative;margin:auto;width:100%;max-width:380px;background:var(--card);
  border:1px solid var(--line-2);border-radius:14px;padding:24px 22px 22px;
  box-shadow:0 14px 44px rgba(0,0,0,.28);animation:rise .18s cubic-bezier(.2,.8,.2,1);}
/* The plans dialog is two tall cards. On a phone they stack, and stacked they
   are taller than the screen — so the dialog itself is capped and the cards
   scroll inside it. The heading and the ✕ stay put while you read. */
.auth-box.is-plans{display:flex;flex-direction:column;max-height:calc(100dvh - 40px);
  padding-bottom:0;overflow:hidden;}
.auth-box.is-plans .lp-prices{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  margin:14px -4px 0;padding:4px 4px 22px;}   /* room for the best card's ring */
/* The billing panel scrolls inside the same capped box, so it inherits
   `padding-bottom:0` — and unlike `.lp-prices` above, nothing put the space
   back. The title had 24px over it and the last invoice row sat on the border.
   The scroller carries it, not the box, because the box is what the cap and
   the fade are measured from. Same number at both ends, at both widths. */
.bill-scroll{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  padding:12px 0 24px;}
/* A card cut off flush at the bottom edge looks like the dialog ends there.
   Fading it says there is more below without spending a row on a scrollbar. */
.auth-box.is-plans:after{content:'';position:absolute;left:1px;right:1px;bottom:0;height:26px;
  border-radius:0 0 13px 13px;pointer-events:none;
  background:linear-gradient(to top,var(--card),rgba(0,0,0,0));}
.auth-t{font-family:var(--serif);font-size:19px;font-weight:600;}
.gbtn{display:flex;align-items:center;justify-content:center;gap:9px;width:100%;
  padding:10px 14px;border-radius:9px;border:1px solid var(--line-2);background:var(--paper);
  font-size:13.5px;font-weight:500;color:var(--ink);}
.gbtn:hover{border-color:var(--ink-3);background:var(--panel);}
.gbtn[disabled]{opacity:.55;cursor:default;}
.gmark{width:19px;height:19px;border-radius:50%;background:var(--card);
  border:1px solid var(--line-2);display:flex;align-items:center;justify-content:center;
  font-family:var(--serif);font-size:12.5px;font-weight:600;color:var(--accent-text);}
.auth-or{display:flex;align-items:center;gap:9px;margin:13px 0;
  font-size:11.5px;color:var(--ink-3);}
.auth-or i{flex:1;height:1px;background:var(--line);}
.auth-alt{display:flex;flex-wrap:wrap;align-items:center;gap:7px;justify-content:center;
  margin-top:13px;font-size:12px;color:var(--ink-3);}
.auth-alt button{color:var(--accent-text);text-decoration:underline;text-underline-offset:2px;}
.auth-alt button:hover{color:var(--accent-deep);}
.auth-s{font-size:13px;color:var(--ink-2);line-height:1.5;margin-top:6px;}
/* Account menu. The top bar was collecting one control per feature; they live
   in here now, which is also where the account rows land once auth exists. */
.acct{position:relative;}
.menu{position:absolute;top:38px;right:0;z-index:60;width:262px;padding:6px;
  background:var(--card);border:1px solid var(--line-2);border-radius:12px;
  box-shadow:0 10px 32px rgba(0,0,0,.16);animation:menuIn .13s ease-out;}
@keyframes menuIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.menu-who{padding:9px 11px 10px;}
.menu-name{font-family:var(--serif);font-size:14px;font-weight:600;line-height:1.2;}
.menu-mail{font-size:11.5px;color:var(--ink-3);margin-top:2px;word-break:break-all;}
.menu-plan{display:inline-block;margin-top:7px;font-family:var(--mono);font-size:10px;
  letter-spacing:.06em;text-transform:uppercase;padding:2px 8px;border-radius:20px;
  border:1px solid var(--line-2);color:var(--ink-3);}
.menu-plan.pro{border-color:var(--accent);color:var(--accent-text);background:var(--accent-wash);}
.menu-sync{font-size:11.5px;color:var(--ink-3);margin-top:8px;line-height:1.45;}
.menu-sep{height:1px;background:var(--line);margin:5px 3px;}
.menu-item{display:flex;align-items:center;gap:10px;width:100%;text-align:left;
  padding:8px 11px;border-radius:8px;font-size:13px;color:var(--ink);text-decoration:none;}
.menu-item:hover{background:var(--panel);}
.menu-item[disabled]{color:var(--ink-3);cursor:default;}
.menu-item[disabled]:hover{background:none;}
.menu-ico{flex:0 0 16px;text-align:center;color:var(--ink-3);font-size:13px;}
.menu-val{margin-left:auto;font-size:11.5px;color:var(--ink-3);}
.menu-note{padding:2px 11px 9px 37px;font-size:11.5px;color:var(--ink-3);line-height:1.45;}
/* The one row in this menu that destroys something (build 129). It is the last
   item, under a rule, in the error ink — the same colour `.err` and `.col-err`
   use, so the app has ONE colour meaning "this is the bad one" rather than a
   second red invented for a menu. It is not a filled red button: the row does
   not delete anything, it opens the panel that explains what deleting costs. */
.menu-item.danger{color:var(--err-ink);}
.menu-item.danger:hover{background:var(--wrong-wash);}
.menu-item.danger .menu-ico{color:var(--err-ink);}
.menu-seg{display:flex;gap:3px;margin:2px 11px 6px 37px;}
.menu-seg button{flex:1;padding:4px 6px;border-radius:6px;border:1px solid var(--line-2);
  font-size:11.5px;color:var(--ink-3);background:var(--paper);}
.menu-seg button.on{border-color:var(--accent);background:var(--accent-wash);color:var(--accent-text);}
/* ---- ⋯ , THE ROW MENU (build 141) ----
   The panel is `.menu` and the rows are `.menu-item` — the account menu's own,
   not a second menu to keep looking alike. All this adds is where the panel is
   put: `is-at` swaps the account menu's `absolute · top:38 · right:0` for the
   fixed coordinates `Dots` measures, because this one is portalled out of a
   card that clips.

   The button is 24×22 and borderless until it is wanted. A control that is
   one of eight on a row has to be loud enough to find; a control that is the
   ONLY other thing on its row does not, and drawing a box round a ⋯ on every
   row would put back the stripe of outlines this build exists to remove. */
.dots{display:inline-flex;flex:0 0 auto;}
.dots-b{width:24px;height:22px;border-radius:6px;flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;
  font-size:15px;line-height:1;color:var(--ink-3);border:1px solid transparent;}
.dots-b:hover,.dots-b[aria-expanded="true"]{color:var(--accent-text);
  border-color:var(--line-2);background:var(--paper);}
.menu.is-at{position:fixed;top:0;left:0;right:auto;width:214px;}
/* Drawn, measured, then placed — so it is never painted in the wrong corner
   for a frame on the way. `visibility` and not `display`, because a menu with
   no layout has no height to measure. */
.menu.is-placing{visibility:hidden;}
.menu.is-at .menu-item{font-size:12.5px;padding:7px 10px;}
.avatar{width:28px;height:28px;border-radius:50%;background:var(--panel);border:1px solid var(--line-2);font-size:11.5px;font-weight:600;color:var(--ink-2);display:flex;align-items:center;justify-content:center;}
/* ---- the bell (build 130) ----
   Beside the avatar and shaped like it: same row, same height, same hover, so
   the two read as one control group rather than as a button somebody added.
   The count sits ON the bell rather than beside it — a badge that takes its own
   width makes the topbar reflow the moment it appears, and this topbar wraps at
   390px already. */
/* 28px, which is the avatar's size to the pixel. Anything taller makes the
   topbar taller — and at 390 that row has already wrapped, so two pixels there
   move every column on the page down two pixels. Measured, not assumed: the
   first build of this was 30px and the harness caught the shift. */
.bell{position:relative;width:28px;height:28px;border-radius:9px;color:var(--ink-2);
  display:flex;align-items:center;justify-content:center;}
.bell:hover{background:var(--panel);color:var(--ink);}
.bell-dot{position:absolute;top:-1px;right:-2px;min-width:15px;height:15px;padding:0 3px;
  border-radius:20px;background:var(--accent);color:var(--on-accent);
  font-family:var(--mono);font-size:9.5px;line-height:15px;text-align:center;
  font-style:normal;border:1.5px solid var(--paper);}
/* Wider than the account menu because these are sentences, not labels — and
   capped, because a panel that runs off the bottom of a phone is a list you
   cannot reach the end of. */
.menu.is-notes{width:min(330px,calc(100vw - 24px));max-height:min(460px,70vh);
  overflow-y:auto;overscroll-behavior:contain;}
.note-head{display:flex;align-items:center;gap:8px;padding-bottom:8px;}
.note-head b{flex:1;font-family:var(--serif);font-size:15px;font-weight:600;}
.note-sec{font-family:var(--mono);font-size:9.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-3);padding:8px 11px 4px;}
.note-row{display:flex;gap:8px;align-items:flex-start;width:100%;text-align:left;
  padding:7px 11px;font-size:12.5px;line-height:1.45;color:var(--ink-2);
  border-top:1px solid var(--line);}
.note-row>span:first-of-type{flex:1;min-width:0;}
.note-row b{color:var(--ink);font-weight:600;}
button.note-row:hover{background:var(--panel);}
/* EVERY ROW CARRIES THE SAME MARK IN THE SAME PLACE, and an already-read one
   carries an empty one. Dropping the dot on read rows would shift their text
   four pixels left and break the column the eye is scanning — 116.1's rule
   about the roster, in a narrower box. */
.note-row::before{content:'';flex:0 0 auto;width:6px;height:6px;border-radius:50%;
  margin-top:6px;background:transparent;}
.note-row.unread::before,.note-row.is-now::before{background:var(--accent);}
.note-when{font-family:var(--mono);font-size:10px;color:var(--ink-3);flex:0 0 auto;
  padding-top:1px;}
.note-x{flex:0 0 auto;width:20px;height:20px;margin:-1px -4px 0 0;border-radius:6px;
  color:var(--ink-3);font-size:12px;line-height:1;}
.note-x:hover{background:var(--track);color:var(--ink);}
/* build 217: a row that leads somewhere says so with a › and a hand cursor;
   a NOW row's text is its own button, beside its ✕ */
.note-row.has-go{cursor:pointer;}
.note-go{flex:1;min-width:0;display:flex;gap:6px;align-items:flex-start;text-align:left;
  border:0;background:none;padding:0;font:inherit;color:inherit;cursor:pointer;}
.note-go>span:first-child{flex:1;min-width:0;}
.note-go:hover>span:first-child{color:var(--ink);text-decoration:underline;text-underline-offset:3px;}
.note-arrow{color:var(--accent-text);font-family:var(--sans);font-size:14px;line-height:1;}
/* the class the bell sent you to, glowing for three seconds */
.kls.kls-hi{border-radius:10px;animation:klsHi 3s ease-out;}
@keyframes klsHi{0%,35%{box-shadow:0 0 0 2px var(--accent);background:var(--accent-wash);}
  100%{box-shadow:0 0 0 2px transparent;background:transparent;}}
@media (prefers-reduced-motion:reduce){.kls.kls-hi{animation:none;box-shadow:0 0 0 2px var(--accent);}}

.cols{flex:1;display:flex;min-height:0;}
.col{display:flex;flex-direction:column;flex:0 0 auto;min-width:0;min-height:0;overflow:hidden;}
/* The default widths, until the first drag. Builds 149 and 154 made them
   three equal columns (152 had tried a narrower Course hub, and Khoa went back
   to equal). Build 185, Khoa: the Course hub narrower, Learn and Practice equal
   — weights .85 : 1 : 1, so about 30% : 35% : 35%. Build 189, Khoa: 30 : 40 : 30
   (Course hub : Learn : Practice), weights 3 : 4 : 3. The hub keeps room for
   its card at a narrow window. */
.col.col-eq{flex:3 1 0;}
.col.col-learn.col-eq{flex:4 1 0;}
.col.col-source.col-eq{flex:3 1 0;min-width:270px;}
.handle{flex:0 0 5px;cursor:col-resize;background:var(--line);}
.handle:hover{background:var(--accent);}
.col-head{flex:0 0 auto;padding:14px 16px 10px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:8px;}
.col-title{font-family:var(--serif);font-size:15px;font-weight:600;}
.col-body{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:14px 16px 56px;}
.eyebrow{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);}

.tabs{display:flex;gap:4px;background:var(--sunk);padding:3px;border-radius:8px;margin-bottom:12px;}
.tab{flex:1;padding:6px 8px;border-radius:6px;font-size:12.5px;font-weight:500;color:var(--ink-2);text-align:center;}
.tab.on{background:var(--card);color:var(--ink);box-shadow:0 1px 2px rgba(0,0,0,.05);}
.field{width:100%;padding:9px 11px;border:1px solid var(--line-2);border-radius:8px;background:var(--card);font-family:var(--sans);font-size:13px;color:var(--ink);}
.field::placeholder{color:var(--ink-3);}
textarea.field{resize:vertical;min-height:96px;line-height:1.55;}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:9px 14px;border-radius:8px;font-size:13px;font-weight:500;background:var(--accent);color:var(--on-accent);transition:background .15s;}
.btn:hover{background:var(--accent-deep);}
.btn[disabled]{background:var(--lock);cursor:not-allowed;}
.btn-ghost{background:transparent;color:var(--ink-2);border:1px solid var(--line-2);}
.btn-ghost:hover{background:var(--track);color:var(--ink);}
.btn-full{width:100%;}
/* The one filled button in the app that is not the accent (build 129). It
   appears on exactly one screen — the second step of deleting an account — and
   it is filled rather than ghosted because by then it is the button being
   pressed on purpose. On the FIRST step the order is the other way round: the
   accent is on "Keep my account" and the delete is a ghost, because there the
   safe answer is the likely one and the filled button is where a thumb lands. */
/* `--paper` for the label, not white: `--err-ink` is a DARK red in the light
   theme and a LIGHT one in the dark theme, so white text is unreadable on it
   in one of the two. The page's own ground is the one colour guaranteed to
   contrast with it in both. */
.btn-danger{background:var(--err-ink);color:var(--paper);}
.btn-danger:hover{background:var(--err-ink);filter:brightness(1.08);}
/* The list of consequences. One fact per line, no bullets in a colour of their
   own: the reader is meant to get through all of them. */
.del-list{margin:0;padding:0;list-style:none;}
.del-list li{position:relative;padding:0 0 7px 15px;font-size:12.5px;line-height:1.5;
  color:var(--ink-2);}
.del-list li::before{content:'';position:absolute;left:2px;top:8px;width:4px;height:4px;
  border-radius:50%;background:var(--ink-3);}
.del-list li b{color:var(--ink);font-weight:600;}
.hint{font-size:12px;color:var(--ink-3);line-height:1.45;margin-top:10px;}
.err{font-size:12.5px;color:var(--err-ink);background:var(--wrong-wash);border-radius:8px;padding:8px 10px;margin-top:10px;}
/* ONE place a class or course failure is ever said. (Build 116.6.)
   It used to be printed by the column AND again inside every course card that
   had a class under it AND again inside every list row — one refusal, four
   copies of it down a single screen. This band is a flex sibling of `.col-bar`
   and of `.col-body`, ABOVE the scroller rather than inside it: on a desktop
   that is all it takes to stay put, with no `position:sticky` and so no new
   paint layer anywhere. `margin-top:0` because it is no longer a paragraph in
   a flow — it is a band across the top of a column. */
.col-err{flex:0 0 auto;display:flex;align-items:flex-start;gap:10px;
  margin:0;border-radius:0;border-bottom:1px solid var(--line);
  padding:9px 16px;line-height:1.45;}
.col-err span{flex:1 1 auto;min-width:0;}
.col-err button{flex:0 0 auto;width:22px;height:22px;margin:-2px -4px 0 0;
  border-radius:6px;color:var(--err-ink);font-size:13px;line-height:1;opacity:.75;}
.col-err button:hover{background:var(--wrong-wash);opacity:1;}
/* The same band, for the one piece of good news this column carries: the
   plan just became Pro. Same geometry as `.col-err` to the pixel — a column
   has ONE strip that means "something has changed", not two that have to be
   told apart — and the accent instead of the error colour, because being let
   into a class is the opposite of a refusal. (Build 118.) */
.col-note{flex:0 0 auto;display:flex;align-items:flex-start;gap:10px;
  margin:0;border-radius:0;border-bottom:1px solid var(--line);
  padding:9px 16px;line-height:1.45;font-size:12.5px;
  color:var(--accent-text);background:var(--accent-wash);}
.col-note span{flex:1 1 auto;min-width:0;}
.col-note button{flex:0 0 auto;width:22px;height:22px;margin:-2px -4px 0 0;
  border-radius:6px;color:var(--accent-text);font-size:13px;line-height:1;opacity:.75;}
.col-note button:hover{background:var(--accent-wash);opacity:1;}
/* THE SAME BAND, ONE LEVEL UP. (Build 129.)
   `.col-err` and `.col-note` are strips across the top of ONE column, because
   what they say is about that column. An account on its way out is not about a
   column: it is about every screen behind it. So this is a flex sibling of
   `.cols` inside `.app` — the same shape, the same geometry, one box further
   out — and it takes its height out of the columns rather than floating over
   them. It is rendered only for the two accounts that have something to be
   told, so for everybody else the app is the app it was yesterday, to the
   pixel. */
.acct-band{flex:0 0 auto;display:flex;align-items:flex-start;gap:10px;
  border-bottom:1px solid var(--line);padding:9px 16px;line-height:1.45;
  font-size:12.5px;color:var(--accent-text);background:var(--accent-wash);}
.acct-band.gone{color:var(--err-ink);background:var(--wrong-wash);}
.acct-band span{flex:1 1 auto;min-width:0;}
.acct-band .cls-btn{flex:0 0 auto;margin-top:-2px;}
/* A guest's band (build 148): quieter than a warning — it is an offer. */
.acct-band.guest-band{color:var(--ink);background:var(--card-2);align-items:center;}
.acct-band.guest-band .band-x{flex:0 0 auto;border:0;background:none;color:inherit;
  opacity:.55;cursor:pointer;font-size:15px;line-height:1;padding:2px 4px;}
.acct-band.guest-band .band-x:hover{opacity:1;}

.role{display:flex;gap:3px;background:var(--sunk);padding:3px;border-radius:8px;margin-bottom:12px;}
.role button{flex:1;padding:5px 8px;border-radius:6px;font-size:12px;font-weight:500;color:var(--ink-3);}
.role button.on{background:var(--card);color:var(--ink);box-shadow:0 1px 2px rgba(0,0,0,.05);}
.shelf{display:flex;flex-direction:column;gap:10px;}
.ccard{display:flex;gap:12px;padding:12px;border:1px solid var(--line-2);border-radius:16px;background:var(--card);box-shadow:var(--shadow);text-align:left;width:100%;transition:border-color .15s,transform .15s,box-shadow .15s;}
.ccard:hover{border-color:var(--accent);transform:translateY(-1px);}
.ccard.on{border-color:var(--accent);background:var(--accent-wash);}
/* The thumbnail carries three facts at a glance: which programme the course
   belongs to, its level, and the three-letter code used for its bucket folder. */
.cthumb-wrap{position:relative;flex:0 0 76px;width:76px;height:76px;border-radius:10px;
  overflow:hidden;border:1px solid var(--line);background:var(--panel);}
.cthumb{width:100%;height:100%;object-fit:cover;display:flex;align-items:center;justify-content:center;
  font-family:var(--serif);font-size:19px;font-weight:600;letter-spacing:.03em;color:var(--accent);}
.ctags{display:flex;flex-wrap:wrap;gap:5px;margin-top:5px;}
.ctag{font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;
  padding:2px 8px;border-radius:20px;border:1px solid var(--line-2);color:var(--ink-2);line-height:1.6;}
.ctag.lv1{border-color:var(--lv1);color:var(--lv1-text);background:var(--lv1-wash);}
.ctag.lv2{border-color:var(--lv2);color:var(--lv2-text);background:var(--lv2-wash);}
.ctag.lv3{border-color:var(--lv3);color:var(--lv3-text);background:var(--lv3-wash);}
.ccode{font-family:var(--mono);font-size:10px;letter-spacing:.06em;color:var(--accent-text);}
.cbody{flex:1;min-width:0;display:flex;flex-direction:column;align-items:stretch;gap:2px;}
.cname{display:block;font-family:var(--serif);font-size:14px;font-weight:600;line-height:1.25;}
.cmeta{display:block;font-size:11.5px;color:var(--ink-3);}
.cdesc{display:block;font-size:12px;color:var(--ink-2);line-height:1.4;margin-top:3px;}
.chip.draft{border-color:var(--ink-3);color:var(--ink-2);background:var(--panel);}
.role button{white-space:nowrap;}
.role-note{font-size:12px;color:var(--ink-2);background:var(--card);border:1px solid var(--line);border-left:2px solid var(--accent);border-radius:0 8px 8px 0;padding:10px 12px;margin-top:12px;line-height:1.5;}
.role-list{list-style:none;margin:8px 0 0;padding:0;}
.role-list li{position:relative;padding:4px 0 4px 15px;font-size:12.5px;color:var(--ink-2);}
.role-list li:before{content:'';position:absolute;left:2px;top:11px;width:5px;height:5px;border-radius:50%;background:var(--accent);}
.cbar{display:block;width:100%;height:5px;border-radius:5px;background:var(--track);overflow:hidden;margin-top:7px;}
.cbar i{display:block;height:100%;background:var(--accent);border-radius:5px;}
.chip{display:block;align-self:flex-start;font-size:10.5px;font-family:var(--mono);letter-spacing:.05em;text-transform:uppercase;padding:2px 7px;border-radius:20px;border:1px solid var(--line-2);color:var(--ink-3);margin-top:6px;}
.chip.go{border-color:var(--accent);color:var(--accent-deep);background:var(--accent-wash);}
.chip.fin{border-color:var(--done);color:var(--done);background:var(--done-wash);}
/* What the second tap does, said on the card that is waiting for it. It exists
   only where the two-tap walk exists — side by side the Plan is already on
   screen and there is nothing to promise. */
.cnext{display:none;}
.repo{font-family:var(--mono);font-size:11.5px;color:var(--ink-2);background:var(--glass);border:1px solid var(--line);border-radius:7px;padding:7px 9px;word-break:break-all;margin-bottom:10px;}
.covsum{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line);border:1px solid var(--line);border-radius:9px;overflow:hidden;margin:10px 0 4px;}
.covsum div{background:var(--panel);padding:8px 4px;text-align:center;}
.covsum b{display:block;font-family:var(--serif);font-size:16px;font-weight:600;line-height:1.2;}
.covsum span{font-size:10px;color:var(--ink-3);}
.cov-row{display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid var(--line);font-size:12px;}
.cov-row b{font-family:var(--mono);font-size:10.5px;font-weight:400;color:var(--ink-3);flex:0 0 18px;}
.cov-row span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink-2);}
.dots{display:flex;gap:3px;flex:0 0 auto;}
.dot{width:7px;height:7px;border-radius:2px;background:var(--line-2);}
.dot.on{background:var(--done);}
.dot.miss{background:var(--wrong-wash);border:1px solid var(--wrong);}
.legend{display:flex;gap:9px;font-size:10.5px;color:var(--ink-3);margin:8px 0 2px;font-family:var(--mono);}
.tree{font-family:var(--mono);font-size:11px;line-height:1.7;color:var(--ink-2);background:var(--glass);border:1px solid var(--line);border-radius:8px;padding:10px 11px;white-space:pre;overflow-x:auto;}
.ref-img{width:100%;border-radius:10px;border:1px solid var(--line);margin-bottom:16px;display:block;}
/* Read & notice puts the session picture above the text in the same frame
   as the listening picture (.sit-img, below), not at its full height, which
   pushed the text off the screen (build 185, Khoa). Discuss keeps the whole
   picture: there the picture is the activity. */
.ref-img.sit-img{margin-top:0;}
/* The session picture under a listening pane's situation line (156). One fixed
   frame for every course and every listening activity (157): the full width of
   the pane, so both edges line up with the audio bar above, and a height set
   by the screen rather than by the picture, so a tall photo never pushes the
   transcript out of view. `cover` fills the frame by trimming the picture's
   edges; the focus sits a little above centre, where faces usually are.
   TALLER (build 177, Khoa): 30% of the screen, at most 280px, cut too much
   of the picture. 45% of the screen, at most 420px, in every course; the
   transcript still starts above the fold on a laptop. Phones keep their own
   smaller frame below, where the pane sits on top of the questions. */
.sit-img{display:block;box-sizing:border-box;width:100%;height:clamp(160px,45vh,420px);
  object-fit:cover;object-position:center 35%;
  border-radius:10px;border:1px solid var(--line);margin:12px 0 14px;}
/* embedded video / segmented audio in the reference pane */
.embed{position:relative;width:100%;padding-top:56.25%;border-radius:10px;overflow:hidden;border:1px solid var(--line);background:#000;}
.embed iframe{position:absolute;inset:0;width:100%;height:100%;border:0;}
.seg-note{font-size:12px;color:var(--ink-2);margin-top:10px;line-height:1.45;}
.seg-list{margin-top:16px;border-top:1px solid var(--line);}
.seg-item{display:flex;gap:9px;padding:7px 0;border-bottom:1px solid var(--line);font-size:12px;color:var(--ink-3);}
.seg-item.on{color:var(--ink);font-weight:500;}
.seg-item b{font-family:var(--mono);font-size:10.5px;font-weight:400;flex:0 0 76px;color:var(--ink-3);}
.seg-item.on b{color:var(--accent);}
mark.tv{position:relative;background:var(--accent-wash);color:var(--ink);border-radius:3px;padding:1px 2px;box-shadow:inset 0 -2px 0 rgba(217,119,87,.45);}
mark.tv.has-gloss{cursor:help;}
mark.tv .tip{position:absolute;left:50%;bottom:100%;transform:translateX(-50%);margin-bottom:7px;
  min-width:110px;max-width:230px;background:var(--ink);color:var(--paper);font-family:var(--sans);
  font-size:12.5px;font-weight:500;line-height:1.4;text-align:center;padding:7px 10px;border-radius:8px;
  white-space:normal;opacity:0;visibility:hidden;transition:opacity .12s;z-index:30;
  box-shadow:0 4px 14px rgba(0,0,0,.22);}
mark.tv .tip i{display:block;font-style:normal;font-family:var(--mono);font-size:11px;opacity:.7;margin-top:3px;}
/* The gloss on a Basic course IS the Vietnamese — 132 drew it under the
   English meaning, 137 draws it instead of one. Its own face for the same
   reason `.card-vi` has one: the level decides this, not `.lang-vi`. */
mark.tv .tip .tip-vi-only{font-family:var(--sans);font-weight:500;}
mark.tv:hover .tip,mark.tv:focus .tip,mark.tv.on .tip{opacity:1;visibility:visible;}
.tv-head{display:flex;align-items:center;gap:8px;margin:22px 0 8px;}
.tv-head .eyebrow{flex:1;}
.tv-list{display:flex;flex-wrap:wrap;gap:6px;}
.tv-item{font-size:12.5px;padding:4px 10px;border-radius:20px;border:1px solid var(--line-2);background:var(--panel);color:var(--ink-2);}
.tv-item.hit{border-color:var(--accent);background:var(--accent-wash);color:var(--accent-deep);}
/* a step's own recording, pinned above the step: no rule under it either
   (build 231; the fade that replaces it is beside `.pin-media`) */
.p-pin{flex:0 0 auto;padding:6px 26px 7px;background:var(--paper);position:relative;z-index:1;}
/* THE RECORDING'S BAR, drawn by the app (build 227, RateAudio in
   50-widgets.js): play, time, a line to drag, length, speed, in one rounded
   bar the full width of its place. It was the browser's own player with the
   speed button beside it (build 205); an iPhone's player kept a "⋯" menu
   with a second speed inside. The line's filled part is `--p` (0 to 1), set
   on the input as the recording plays. */
.au-row{display:flex;align-items:center;gap:10px;margin:10px 0;padding:5px 6px 5px 5px;
  border:1px solid var(--line-2);border-radius:999px;background:var(--panel);}
.au-row audio{display:none;}
.p-pin .au-row,.pin-media .au-row{margin:0;}
.au-play{flex:none;width:34px;height:34px;border-radius:50%;background:var(--accent);color:var(--on-accent);
  display:flex;align-items:center;justify-content:center;}
.au-play:hover{background:var(--accent-deep);}
.au-play svg{width:17px;height:17px;fill:currentColor;}
.au-time{flex:none;min-width:30px;text-align:center;font-size:12.5px;color:var(--ink-2);font-variant-numeric:tabular-nums;}
.au-seek{-webkit-appearance:none;appearance:none;flex:1 1 auto;min-width:40px;width:auto;height:24px;margin:0;padding:0;
  border:0;background:transparent;cursor:pointer;touch-action:none;}
.au-seek:disabled{cursor:default;opacity:.6;}
.au-seek::-webkit-slider-runnable-track{height:5px;border-radius:3px;
  background:linear-gradient(to right,var(--accent) calc(var(--p,0)*100%),var(--line-2) calc(var(--p,0)*100%));}
.au-seek::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:15px;height:15px;margin-top:-5px;border-radius:50%;
  background:#fff;border:1px solid var(--line-2);box-shadow:0 1px 3px rgba(0,0,0,.3);}
.au-seek::-moz-range-track{height:5px;border-radius:3px;background:var(--line-2);}
.au-seek::-moz-range-progress{height:5px;border-radius:3px;background:var(--accent);}
.au-seek::-moz-range-thumb{width:15px;height:15px;border-radius:50%;background:#fff;border:1px solid var(--line-2);
  box-shadow:0 1px 3px rgba(0,0,0,.3);}
.au-speed{flex:none;font:inherit;font-size:12.5px;font-weight:600;padding:6px 10px;border-radius:999px;border:1px solid var(--line-2);
  background:var(--card);color:var(--ink);cursor:pointer;font-variant-numeric:tabular-nums;min-width:58px;}
.au-speed:hover{border-color:var(--accent);color:var(--accent-deep);}
.skel{font-family:var(--mono);font-size:15px;letter-spacing:.12em;color:var(--ink-3);text-align:center;}
.tts-bar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:10px 0 4px;}
.tts-bar .play-btn{padding:7px 13px;}
.rep-wrap{margin-top:14px;}
.rep-open{font-size:11.5px;color:var(--ink-3);border:1px solid var(--line-2);border-radius:20px;padding:4px 11px;}
.rep-open:hover{border-color:var(--accent);color:var(--accent-deep);}
.rep-box{border:1px solid var(--line-2);border-left:2px solid var(--accent);border-radius:0 9px 9px 0;background:var(--card);padding:11px 13px;margin-top:8px;}
.rep-box textarea{width:100%;min-height:62px;resize:vertical;border:1px solid var(--line-2);border-radius:7px;background:var(--paper);color:var(--ink);font-family:var(--sans);font-size:13px;padding:7px 9px;}
.rep-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:8px;}
.rep-tag{font-size:11.5px;padding:3px 9px;border-radius:20px;border:1px solid var(--line-2);color:var(--ink-3);}
.rep-tag.on{border-color:var(--accent);background:var(--accent-wash);color:var(--accent-deep);}
.gapin.maybe{border-color:var(--accent);color:var(--accent-deep);}
/* The revealed target is the correct answer, not an error, so it reads as
   ordinary text at full strength — a dimmed or tinted answer is one more
   thing to decode at exactly the moment the learner wants to read it
   cleanly. Only the part they missed takes a colour. */
.gapin.ans{border-color:var(--accent);color:var(--ink);}
.judge{border-top:1px solid var(--line);padding:8px 0;}
.judge:first-of-type{border-top:none;}
.judge-t{font-size:12.5px;color:var(--ink-2);}
.judge-b{display:flex;flex-wrap:wrap;gap:7px;margin-top:6px;}
.judge-b .btn{padding:4px 11px;font-size:12px;}
.was{display:inline-block;margin-left:6px;font-size:12.5px;color:var(--wrong);}
.was-tag{font-family:var(--mono);font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-3);margin-left:9px;margin-right:5px;}
.was .d-same{color:var(--ink-3);opacity:.7;text-decoration:none;}
/* Shared characters recede; only the delta is marked. */
.d-same{opacity:.45;}
.d-add{font-weight:600;}
/* Inside the revealed answer the shared part is plain ink and the missing
   part is the only thing coloured, so the eye lands on the gap itself
   rather than on the whole word. */
.gapin.ans .d-same{opacity:1;color:var(--ink);}
.gapin.ans .d-add{color:var(--wrong);font-weight:600;}
.d-cut{text-decoration:line-through;text-decoration-thickness:1.5px;}
span.gapin{cursor:default;}
.ing{display:inline-flex;flex-wrap:wrap;justify-content:center;align-items:baseline;gap:2px;color:var(--ink-2);}
.ing-sep{color:var(--accent);padding:0 8px;font-family:var(--mono);}
.tts-bar{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0;}
/* Admin's test mode in Practice, in the look of Learn's today card (269, Khoa:
   "for test mode card, update the format to match the left box's"): an accent
   edge washed down to the card, the eyebrow with its dot, a full-width
   picker, the activities as round buttons */
.admin{margin-top:14px;padding:16px;border:1px solid var(--accent);border-radius:14px;
  background:linear-gradient(180deg,var(--accent-wash),var(--card) 85%);}
.admin-t{display:flex;align-items:center;gap:7px;font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent-text);margin-bottom:10px;}
.admin-t::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--accent);flex:none;}
.admin-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center;}
.admin select{flex:1 1 100%;padding:8px 10px;border:1px solid var(--line-2);border-radius:10px;background:var(--card);font-family:var(--sans);font-size:13px;color:var(--ink);max-width:100%;}
.admin button{padding:7px 14px;border-radius:999px;border:1px solid var(--line-2);background:var(--card);font-size:13px;font-weight:500;color:var(--ink);}
.admin button:hover{border-color:var(--accent);color:var(--accent-text);}
/* its ✕ (278.2): test mode off, top right of the card */
.admin{position:relative;}
.admin .admin-x{position:absolute;top:8px;right:8px;width:28px;height:28px;padding:0;border:0;border-radius:50%;
  background:transparent;color:var(--ink-3);font-size:13px;line-height:28px;text-align:center;cursor:pointer;}
.admin .admin-x:hover{background:var(--accent-wash);color:var(--accent-text);}
.admin:has(.admin-x) .admin-t{padding-right:30px;}
.toggle{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--ink-2);padding:4px 9px;border:1px solid var(--line-2);border-radius:20px;}
.toggle.on{border-color:var(--accent);background:var(--accent-wash);color:var(--accent-deep);}

.pace-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;}
.pace{padding:11px 6px;border-radius:9px;border:1px solid var(--line-2);background:var(--card);text-align:center;}
.pace.on{border-color:var(--accent);background:var(--accent-wash);}
.pace-n{font-family:var(--serif);font-size:19px;font-weight:600;line-height:1.1;}
.pace-u{font-size:11px;color:var(--ink-3);}
.summary{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;margin-top:14px;}
.sum-cell{background:var(--paper);padding:10px 12px;}
.sum-v{font-family:var(--serif);font-size:17px;font-weight:600;line-height:1.2;}
.sum-l{font-size:11.5px;color:var(--ink-3);margin-top:1px;}
.bar{height:7px;border-radius:7px;background:var(--track);overflow:hidden;}
.bar-fill{height:100%;background:var(--accent);border-radius:7px;transition:width .4s cubic-bezier(.2,.8,.2,1);}
.bar-fill.full{background:var(--done);}
/* A day is a button now: it carries you to the stop it describes. Kept looking
   exactly as it did — the affordance is the hover edge and the cursor, not a
   chevron on forty rows. `width` and `text-align` are the two things a <button>
   would otherwise get wrong inside a column. */
.day{display:flex;gap:10px;padding:9px 0;border-bottom:1px solid var(--line);
  width:100%;text-align:left;font:inherit;}
button.day{border-left:0;border-right:0;border-top:0;}
button.day:hover .day-title{color:var(--accent-text);}
button.day:hover{background:var(--hero);}
.day-n{flex:0 0 34px;font-family:var(--mono);font-size:11px;color:var(--ink-3);padding-top:2px;}
.day.is-now .day-n{color:var(--accent-text);}
.day.is-done .day-n{color:var(--done);}
.day-body{flex:1;min-width:0;}
.day-title{font-size:12.5px;font-weight:500;margin-bottom:2px;}
.day.is-locked .day-title{color:var(--ink-3);font-weight:400;}
.day-acts{font-size:11.5px;color:var(--ink-3);}
.day-part+.day-part{margin-top:6px;padding-top:6px;border-top:1px dashed var(--line);}
.day-min{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);margin-top:5px;}
.day-check{flex:0 0 15px;height:15px;border-radius:50%;border:1.5px solid var(--line-2);margin-top:3px;}
.day.is-done .day-check{background:var(--done);border-color:var(--done);}
.day.is-now .day-check{border-color:var(--accent);border-style:dashed;}
.bank-row{display:flex;justify-content:space-between;font-size:12px;color:var(--ink-2);padding:5px 0;border-bottom:1px solid var(--line);}
.bank-row b{font-weight:500;color:var(--ink-2);}
.pips{display:inline-flex;gap:3px;}
.pip{width:6px;height:6px;border-radius:50%;background:var(--line-2);}
.pip.on{background:var(--done);}

.j-wrap{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:0 0 72px;}
.j-hero{padding:18px 22px 16px;border-bottom:1px solid var(--line);background:linear-gradient(180deg,var(--hero) 0%,var(--paper) 100%);}
/* No coloured bloom: the reference keeps its canvas plain and lets the
   orange be the only colour that appears. The hero is a quiet step up from
   the page, nothing more. */
.j-kicker{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--accent-text);}
.j-title{font-family:var(--serif);font-size:22px;font-weight:600;letter-spacing:-.015em;margin:4px 0 6px;}
.j-sub{font-size:13px;color:var(--ink-2);max-width:56ch;line-height:1.55;}
.j-for{font-size:12.5px;color:var(--ink-2);max-width:56ch;margin-top:7px;line-height:1.5;}
.j-for b{font-family:var(--mono);font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-3);display:block;margin-bottom:1px;}
.j-aims{margin:8px 0 0;padding-left:16px;max-width:56ch;}
.j-aims li{font-size:12.5px;color:var(--ink-2);line-height:1.5;padding:1px 0;}
.j-aims li::marker{color:var(--accent);}
.j-note{font-family:var(--mono);font-size:11px;color:var(--ink-3);margin-top:10px;}

/* ---- the Learn column (build 147) ----
   The course page, as the school site draws it: the course's own head, a tab
   row, then each stage as a bordered box of its sessions. Built only from the
   app's own tokens, so dark mode needs nothing of its own. */
.l-body{padding:0 0 56px;}
.l-tabs{display:flex;gap:4px;padding:10px 16px 0;border-bottom:1px solid var(--line);}
.l-tab{padding:8px 14px;font-size:13px;color:var(--ink-2);border:1px solid transparent;
  border-bottom:none;border-radius:8px 8px 0 0;margin-bottom:-1px;background:none;}
.l-tab.on{background:var(--paper);border-color:var(--line);color:var(--ink);font-weight:500;}
.l-stage{margin:14px 16px 0;border:1px solid var(--line);border-radius:var(--r);overflow:hidden;background:var(--card);}
.l-stage-h{display:flex;align-items:baseline;gap:10px;padding:11px 14px;background:var(--sunk);}
.l-stage-t{flex:1 1 auto;min-width:0;font-family:var(--serif);font-size:14.5px;font-weight:600;}
.l-stage-n{flex:0 0 auto;font-family:var(--mono);font-size:11px;color:var(--ink-3);}
.l-row{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;
  border:0;border-top:1px solid var(--line);background:none;text-align:left;font:inherit;font-size:13px;color:var(--ink);}
.l-row-n{flex:0 0 22px;font-family:var(--mono);font-size:11px;color:var(--ink-3);}
.l-row-t{flex:1 1 auto;min-width:0;line-height:1.4;}
.l-row-s{flex:0 0 auto;font-size:12px;color:var(--ink-3);}
button.l-row{cursor:pointer;}
button.l-row:hover{background:var(--accent-wash);}
.l-row.is-open .l-row-s{color:var(--accent);font-size:16px;line-height:1;}
.l-row.is-soon,.l-row.is-locked{color:var(--ink-3);}
.l-row.is-wait{color:var(--ink-2);}

/* The lesson, full screen. The page brings its own design; the app adds only
   the frame and a close button that is always there — a phone has no Esc. */
.scrim.lesson-scrim{z-index:60;background:var(--paper);}
.lesson-frame{flex:1 1 auto;width:100%;height:100%;border:0;background:#fff;}
.lesson-msg{margin:auto;padding:24px;text-align:center;color:var(--ink-2);font-size:14px;max-width:40ch;line-height:1.5;}
/* THE ✕ IS FOUND (build 172). Khoa: many learners never saw it — a dark
   grey disc on the lessons' own navy mastheads. It is the brand's terracotta
   now, with a thin white ring so it reads on navy and on white alike, and it
   says hello twice when a lesson opens (a soft ring spreading out from it),
   then keeps still. No movement for anyone who has asked for none. */
.lesson-x{position:fixed;top:12px;right:12px;z-index:61;width:38px;height:38px;border-radius:50%;
  border:2px solid rgba(255,255,255,.92);background:#D97757;color:#fff;font-size:15px;font-weight:700;line-height:1;
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  box-shadow:0 2px 12px rgba(217,119,87,.45),0 1px 3px rgba(0,0,0,.25);
  transition:background .15s ease,transform .15s ease;
  animation:xHello 1.4s ease-out .8s 2;}
.lesson-x:hover{background:#C4623F;transform:scale(1.07);}
.lesson-x:focus-visible{outline:3px solid #fff;outline-offset:2px;}
@keyframes xHello{
  0%{box-shadow:0 0 0 0 rgba(217,119,87,.65),0 2px 12px rgba(217,119,87,.45),0 1px 3px rgba(0,0,0,.25);}
  80%,100%{box-shadow:0 0 0 14px rgba(217,119,87,0),0 2px 12px rgba(217,119,87,.45),0 1px 3px rgba(0,0,0,.25);}}
@media (prefers-reduced-motion:reduce){.lesson-x{animation:none;}}
/* ---- build 170: a lesson arrives behind a cover ----
   The splash's own mark and bar, with the lesson's name, over the frame
   while the page is fetched and drawn; it fades once the page is in place
   (LessonViewer). `no-fade`: a lesson put back after a refresh is on screen
   from the first frame, so its scrim does not fade in over the columns. */
.scrim.no-fade{animation:none;}
.lesson-cover{position:absolute;inset:0;z-index:1;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:18px;padding:24px;text-align:center;background:var(--paper);
  opacity:1;transition:opacity .28s ease;}
.lesson-cover.off{opacity:0;pointer-events:none;}
.lesson-cover-txt{display:flex;flex-direction:column;align-items:center;gap:5px;max-width:min(440px,86vw);}
.lesson-cover-k{font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);}
.lesson-cover-t{font-family:var(--serif);font-size:19px;font-weight:600;letter-spacing:-.01em;line-height:1.3;color:var(--ink);}
/* The teacher's live submissions, from the lesson itself: a pill beside the
   ✕, in its colours. It turns the accent colour for a few seconds when
   somebody submits. On a phone it is the dot and the count.
   UNDER THE ✕ BELOW 900px (build 210). Khoa: on a phone it covered the
   lesson's audio bar. Below 900px the lesson has no side rail and its bar,
   docked at the top while the Dialogue is scrolled, runs the full width but
   for the 46px it leaves the ✕ (APP_PAGE_CSS, 88-submit.js) — the chip
   beside the ✕ sat on its time and its end. Stacked under the ✕ it starts
   below the docked bar (8px + 54px tall), so nothing of the player is hidden. */
.lesson-live{position:fixed;top:12px;right:58px;z-index:71;height:38px;display:flex;align-items:center;gap:8px;
  padding:0 14px 0 12px;border:0;border-radius:19px;background:rgba(20,20,19,.72);color:#fff;
  font:inherit;font-size:13px;font-weight:600;cursor:pointer;box-shadow:0 2px 10px rgba(0,0,0,.25);
  transition:background .25s ease;}
.lesson-live:hover{background:rgba(20,20,19,.9);}
.lesson-live b{font-weight:500;color:#C9D3E0;font-variant-numeric:tabular-nums;}
.lesson-live.fresh{background:var(--accent);}
.lesson-live.fresh b{color:#fff;}
@media (max-width:899px){.lesson-live{top:68px;right:12px;height:32px;padding:0 12px 0 10px;}}
@media (max-width:600px){.lesson-live-t{display:none;}}

/* ---- Learn › My plan (build 147.1): what was the Plan column ---- */
.l-plan{padding:14px 16px 8px;}
/* Today, as a card with a ring rather than a second progress bar. */
.l-today{display:flex;align-items:center;gap:14px;padding:12px 14px;
  border:1px solid var(--line);border-radius:var(--r);background:var(--card);}
.l-today.met{border-color:var(--done);background:var(--done-wash);}
.l-today-ring{flex:0 0 auto;display:block;}
.l-today-pct{font-family:var(--mono);font-size:11px;font-weight:600;fill:var(--ink-2);}
.l-today.met .l-today-pct{fill:var(--done);font-size:15px;}
.l-today-txt{min-width:0;}
.l-today-v{font-family:var(--serif);font-size:18px;font-weight:600;line-height:1.25;margin-top:2px;}
.l-today-s{font-size:12.5px;color:var(--ink-2);}
.l-today.met .l-today-s{color:var(--done);font-weight:500;}
/* Course information: under the course's description, the one way into its
   landing page. */
.l-about{display:inline-flex;align-items:center;gap:8px;margin-top:12px;padding:7px 12px 7px 8px;
  border:1px solid var(--line-2);border-radius:20px;background:var(--card);
  font-size:12.5px;font-weight:500;color:var(--ink);cursor:pointer;}
.l-about:hover{border-color:var(--accent);color:var(--accent-text);}
.l-about-i{width:18px;height:18px;border-radius:50%;background:var(--accent);color:var(--on-accent);
  display:inline-flex;align-items:center;justify-content:center;
  font-family:var(--serif);font-style:italic;font-weight:600;font-size:12px;}
/* Progress sits in a band of its own between the column header and the
   scroller, identical in both columns, so the two bars line up exactly and
   nothing can ever scroll through the space above them. */
.col-bar{flex:0 0 auto;height:52px;display:flex;flex-direction:column;justify-content:center;
  padding:0 16px;background:inherit;border-bottom:1px solid var(--line);}
.col-bar .role{margin-bottom:0;}
/* The refresh sits ON the band rather than in it. (Build 116.7.)
   Absolutely positioned, so the band stays the column box it has always been —
   `flex-direction:column;justify-content:center` — and nothing inside it is
   re-centred or re-measured by the arrival of a button. What the band's
   contents give up is width, and only when the button is actually there:
   `.has-refresh` is the class that pays for it, so a signed-out column and an
   admin's board are pixel for pixel what they were. */
.col-bar{position:relative;}
.col-bar.has-refresh .pinbar-l{padding-right:92px;}
.col-bar.has-refresh .role{margin-right:92px;}
.bar-refresh{position:absolute;right:16px;top:50%;transform:translateY(-50%);
  display:inline-flex;align-items:center;gap:6px;padding:5px 10px;border-radius:8px;
  border:1px solid var(--line-2);background:var(--paper);color:var(--ink-3);
  font-size:11.5px;line-height:1;white-space:nowrap;}
.bar-refresh:hover{border-color:var(--accent);color:var(--accent-text);background:var(--accent-wash);}
/* The glyph and the spinner are the same box, so pressing it cannot make the
   button change width under the thumb that is pressing it. */
.bar-refresh .spin{width:12px;height:12px;border-width:1.5px;}
.bar-refresh span[aria-hidden]{display:inline-block;width:12px;text-align:center;
  font-size:13px;line-height:1;}
.bar-refresh:disabled{opacity:1;}
.pinbar-l{display:flex;justify-content:space-between;gap:10px;font-family:var(--mono);font-size:10.5px;letter-spacing:.04em;color:var(--ink-3);margin-top:5px;}
.pinbar-l span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.pinbar-l span:last-child{flex:0 0 auto;}
.path{position:relative;padding:26px 22px 10px;}
/* The route: ONE stroke through every stop, drawn behind the rows and deaf to
   the mouse so it can never eat a click. How the curve is built — and why it
   is one path rather than a chain of arcs — is in JourneyColumn. */
.path-svg{position:absolute;inset:0;pointer-events:none;z-index:0;}
.row{position:relative;z-index:1;}
.row{position:relative;display:flex;flex-direction:column;align-items:flex-start;padding:9px 0;}
.node-wrap{display:flex;align-items:center;gap:11px;}
.node{position:relative;width:62px;height:62px;border-radius:50%;flex:0 0 auto;display:flex;align-items:center;justify-content:center;background:var(--card);border:1.5px solid var(--line-2);font-family:var(--mono);font-size:14px;color:var(--ink-3);transition:transform .18s cubic-bezier(.2,.8,.2,1);}
.node:hover{transform:translateY(-2px);}
.node.is-done{background:var(--done-wash);border-color:var(--done);color:var(--done);}
.node.is-now{border-color:var(--accent);border-width:2px;color:var(--accent-deep);box-shadow:0 0 0 5px var(--accent-wash);}
.node.is-locked{background:var(--locked-bg);border-color:var(--line);color:var(--lock);cursor:default;}
.node.is-locked:hover{transform:none;}
.node.is-soon{background:var(--soon-bg);border-color:var(--line-2);border-style:dashed;color:var(--ink-3);cursor:default;}
.node.is-soon:hover{transform:none;}
/* Not locked — bought. It keeps the accent so the eye reads it as the same
   route continuing, not as a wall. */
/* Shut for money, and shut for want of one press. They look the same on
   purpose: both are a stop the learner can open right now, one by paying and
   one by pressing Add, and both are a solid offer rather than a padlocked dead
   end. What tells them apart is the sentence beside them. (1058 is 123's;
   `.is-add` is 138's.) */
.node.is-pay,.node.is-add{background:var(--accent-wash);border-color:var(--accent);border-style:dashed;color:var(--accent-text);cursor:pointer;}
.pay-link{background:none;border:0;padding:0;font:inherit;color:var(--accent-text);cursor:pointer;text-align:left;text-decoration:underline;text-underline-offset:2px;}
.paywall{margin-top:12px;border:1px solid var(--accent);background:var(--accent-wash);border-radius:var(--r);padding:12px 13px;}
.paywall-t{font-family:var(--mono);font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--accent-text);}
.paywall-s{font-size:12.8px;line-height:1.5;color:var(--ink-2);margin:5px 0 9px;}
/* ---- the teacher's side of the paywall ----
   A learner meets the price at the point the road stops, so a sentence and a
   button are enough. A teacher never hits that point — they are shown the whole
   course on purpose — so their block has to say what is missing rather than
   where the wall is: seats, classes, sessions. Three short rows, not prose. */
.pw-why{list-style:none;margin:9px 0 11px;padding:0;display:flex;flex-direction:column;gap:5px;}
.pw-why li{font-size:12.5px;color:var(--ink-2);display:flex;gap:8px;align-items:flex-start;line-height:1.45;}
.pw-why i{font-style:normal;color:var(--accent-text);font-family:var(--mono);font-size:11px;flex:0 0 auto;padding-top:1px;min-width:16px;}
/* The price per learner, under the button rather than in it. A teacher does not
   buy a plan, they buy a class — but the plan price is what they are agreeing
   to, so it stays on the button and the per-head figure answers it underneath. */
.pw-foot{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);margin-top:7px;text-align:center;}
.pw-gauge{display:flex;align-items:center;gap:9px;margin:8px 0 2px;}
.pw-bar{flex:1;height:5px;border-radius:5px;background:var(--track);overflow:hidden;border:1px solid var(--line);}
.pw-bar i{display:block;height:100%;background:var(--accent);}
.pw-num{font-family:var(--mono);font-size:11px;color:var(--accent-text);white-space:nowrap;}
/* A control that cannot be used teaches nothing. This one still opens — it
   opens the price instead of the thing it names. */
.cls-btn.pro{border-color:var(--accent);color:var(--accent-text);background:var(--accent-wash);}
.seat-warn{display:flex;align-items:center;gap:7px;flex-wrap:wrap;padding:6px 0 2px;font-size:11.5px;color:var(--accent-text);}
.seat-warn button{color:var(--accent-text);text-decoration:underline;text-underline-offset:2px;padding:0;font-size:11.5px;}
/* ---- folding a course's classes ----
   A teacher who runs the same course every term collects a class per term, and
   the shelf stops being something you can browse. The stack folds. Folding is
   for tidiness and never for hiding work, so a class with somebody waiting to be
   let in stays on screen whatever the fold says. */
.cls-fold{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:6px 0 4px;border-bottom:1px solid var(--line);}
.cls-fold.kid{margin-left:5px;padding-left:24px;border-left:1px solid var(--line-2);}
.cls-fold-b{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:500;color:var(--ink);padding:0;}
.cls-fold-b:hover{color:var(--accent-text);}
.cls-fold-x{font-size:9px;color:var(--ink-3);width:9px;text-align:center;}
.cls-fold-n{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);}
.cls-fold-w{font-family:var(--mono);font-size:10px;letter-spacing:.05em;text-transform:uppercase;
  border:1px solid var(--accent);border-radius:20px;padding:1px 7px;color:var(--accent-text);background:var(--accent-wash);}
/* ---- the roster ----
   A learner in a class is a person with a name, and until build 116 they were
   eight characters of a uuid. The name is a button, because the row is now the
   way into that learner's report — and a button is the only thing that says so
   without a label taking up the width the name needs. */
.kls-who{font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  text-align:left;padding:1px 0;color:var(--ink);border-bottom:1px solid transparent;}
.kls-who:hover{color:var(--accent-text);border-bottom-color:var(--accent);}
/* No name yet — the account was made before display names, or deleted. The
   short id is not a name, so it does not get a name's weight. */
.kls-anon{font-family:var(--mono);font-size:11px;color:var(--ink-3);font-weight:400;}
/* Progress on the roster row: a number and a rule, both narrow enough to sit in
   a 390px column beside a name and two buttons. */
.kls-pct{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);flex:0 0 auto;}
/* WIDTH AND MIN-WIDTH, NOT A flex-basis. `flex:0 0 34px` lays the bar out
   correctly in every engine — but Safari does not count a flex-basis when it
   works out the INTRINSIC width of the flex container the bar sits inside. So
   `.cs-i` on the class row measured as its text alone, the summary row decided
   all of its items fitted on one line, and then the bar took its 54px anyway
   and the word it had been measured without ran straight underneath it. A real
   `min-width` is counted by every engine, so the row wraps when it should.
   (Build 116.2.) */
.kls-bar{flex:0 0 auto;width:34px;min-width:34px;height:4px;border-radius:4px;background:var(--track);border:1px solid var(--line);overflow:hidden;}
.kls-bar i{display:block;height:100%;background:var(--accent);}
.kls-bar.done i{background:var(--done);}
/* ---- the class row is the fold control ----
   It used to be a bare "2 ▾" beside the join code: a number with no noun, on a
   button that gave no sign of what it opened. It is now the summary itself —
   how many learners, how far along, who has not begun, who is waiting — and
   clicking anywhere along it opens or shuts the list. A disclosure control
   labelled by its own contents needs no separate label, and on a phone the
   target is the full width of the column rather than 34px of chevron.

   It is a <button>, so `line-height` is set back explicitly: Chrome's UA
   stylesheet puts the `font:` shorthand on buttons and that resets leading to
   normal, which is the same trap build 113.6 fell into with the wordmark. */
.cls-sum{display:flex;align-items:center;gap:2px 9px;flex-wrap:wrap;width:100%;
  padding:7px 0 7px 4px;text-align:left;line-height:1.5;
  font-size:11.5px;color:var(--ink-2);border-top:1px solid var(--line);}
.cls-sum:hover{color:var(--ink);}
.cls-sum:hover .kls-x{color:var(--accent-text);}
.cls-sum b{font-family:var(--serif);font-size:14px;font-weight:600;color:var(--ink);}
.kls-x{font-size:9px;color:var(--ink-3);width:9px;flex:0 0 auto;text-align:center;}
/* The separator belongs to the item AFTER it, not to the row. Written as a
   loose `<span>·</span>` between items it becomes a flex child of its own, and
   the moment the row wraps — which at 390px it does — the dot is left hanging
   at the end of the first line with nothing after it. Inside the item, it
   wraps where the item wraps. */
/* `flex:0 0 auto` — an item on this row may never be squeezed. It is the other
   half of the Safari fix above: nowrap text in a box narrower than itself does
   not clip or ellipsise, it simply draws over whatever is next to it. Nothing
   here is worth shrinking, so nothing here shrinks; the row wraps instead. */
.cs-i{display:inline-flex;align-items:baseline;gap:4px;white-space:nowrap;flex:0 0 auto;}
.cs-i+.cs-i::before{content:'\00b7';color:var(--ink-3);}
.cs-i.warn{color:var(--accent-text);}
.cs-i.wait{font-family:var(--mono);font-size:10px;letter-spacing:.05em;text-transform:uppercase;
  border:1px solid var(--accent);border-radius:20px;padding:1px 7px;
  color:var(--accent-text);background:var(--accent-wash);}
.cs-i.wait::before{content:none;}
/* The class's own average, on the class row and therefore visible whether the
   list is open or shut — which is what makes folding cost the teacher nothing. */
.kls-bar.wide{width:54px;min-width:54px;}

/* ---- the learner report ----
   A WINDOW, from build 131 — `.apop`'s, drawn by `AdminPop`. Its own box, its
   own header (`.report`, `.rep-head`, `.rep-title`, `.rep-sub`) and its own
   four lines in the print block all went with the change: there is one window
   in this app now, and a second implementation of it is a second thing to keep
   looking alike.

   `.rep-in` is all that is left of the frame, and it is 640 inside `.apop-in`'s
   760 on purpose. This is the one panel in the app that is sentences rather
   than rows, and a paragraph 760px wide is a paragraph nobody finishes. */
.rep-in{max-width:640px;margin:0 auto;width:100%;}
/* The headline figure. One number big enough to be the answer, with the bar
   under it doing the work of saying what it is a fraction of. */
.rep-lead{display:flex;align-items:flex-end;gap:14px;margin-bottom:10px;}
.rep-big{font-family:var(--serif);font-size:44px;font-weight:600;line-height:.95;color:var(--ink);}
.rep-lead>span{font-size:12.5px;color:var(--ink-3);padding-bottom:5px;line-height:1.4;}
/* WHO THIS IS ABOUT, INSIDE THE PICTURE.
   The header says it too, but on a desktop the header is a strip along the top
   of a 1440px window and the report is a 640px column in the middle of it — so
   a screenshot of the part anyone would actually crop, or a page sent to a
   printer, arrives with a percentage on it and no name. This block sits at the
   end of the headline row, right-aligned, and travels with the figures.
   It is not repeated on a phone: there the header is directly above the number,
   in the same narrow column, and saying it twice would cost a third of the
   first screen. */
/* WHO IT IS ABOUT — ON PAPER, AND NOWHERE ELSE FROM BUILD 131.
   116.2 put this block beside the big figure because the name was in a strip
   along the top of a 1440px window while the report was a 640px column in the
   middle of it: the part anybody would crop arrived with a percentage and no
   name. It was already hidden under 900px, "where the header is directly above
   this line" — and in a window the header is ALWAYS directly above this line.
   So the condition that justified it is now never true on screen, and what was
   left was the learner's name, class and course printed twice, eighty pixels
   apart. The print block turns it back on: a sheet of paper gets filed, cropped
   and stapled, and the header row is small. */
.rep-id{display:none;margin-left:auto;text-align:right;padding-bottom:4px;max-width:46%;min-width:0;}
.rep-id-n{font-family:var(--serif);font-size:15px;font-weight:600;line-height:1.2;color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.rep-id-m{font-size:11.5px;color:var(--ink-3);line-height:1.45;margin-top:2px;}
.rep-id-d{font-family:var(--mono);font-size:10px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--ink-3);margin-top:3px;}
.rep-bar{height:8px;border-radius:8px;background:var(--track);border:1px solid var(--line);overflow:hidden;margin-bottom:18px;}
.rep-bar i{display:block;height:100%;background:var(--accent);transition:width .3s ease-out;}
.rep-bar.done i{background:var(--done);}
.rep-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:1px;background:var(--line);
  border:1px solid var(--line);border-radius:var(--r);overflow:hidden;margin-bottom:20px;}
.rep-cell{background:var(--card);padding:11px 8px;text-align:center;}
.rep-cell b{display:block;font-family:var(--serif);font-size:19px;font-weight:600;line-height:1.15;}
.rep-cell span{font-size:10.5px;color:var(--ink-3);}
.rep-cell.warn b{color:var(--accent-text);}
/* Where they stopped. The single most useful line on the page, so it gets the
   shape the app already uses for "read this one thing". */
.rep-now{border:1px solid var(--line);border-left:2px solid var(--accent);border-radius:0 9px 9px 0;
  background:var(--card);padding:12px 14px;margin-bottom:20px;}
.rep-now-k{font-family:var(--mono);font-size:10px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3);}
.rep-now-t{font-family:var(--serif);font-size:15.5px;font-weight:600;margin-top:3px;line-height:1.3;}
.rep-now-s{font-size:12.5px;color:var(--ink-2);margin-top:3px;line-height:1.5;}
/* Stage by stage. A course is stages before it is activities, and "stuck in
   stage 2" is a thing a teacher can act on where "38%" is not. */
.rep-stage{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--line);}
.rep-stage:last-child{border-bottom:none;}
.rep-stage-n{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);flex:0 0 22px;}
.rep-stage-t{flex:1;min-width:0;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.rep-stage-b{flex:0 0 90px;height:5px;border-radius:5px;background:var(--track);border:1px solid var(--line);overflow:hidden;}
.rep-stage-b i{display:block;height:100%;background:var(--accent);}
.rep-stage-b.done i{background:var(--done);}
.rep-stage-p{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);flex:0 0 34px;text-align:right;}
.rep-stage.is-none .rep-stage-t{color:var(--ink-3);}
.day.is-soon .day-title{color:var(--ink-3);font-weight:400;}
.soon-tag{font-family:var(--mono);font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);border:1px solid var(--line-2);border-radius:20px;padding:1px 7px;margin-left:6px;}
.node.is-check{border-radius:16px;width:66px;height:52px;}
.node-ring{position:absolute;inset:-1.5px;transform:rotate(-90deg);}
.node-eyebrow{font-family:var(--mono);font-size:10px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3);}
.node-name{font-family:var(--serif);font-size:14.5px;font-weight:600;line-height:1.25;margin-top:1px;}
.node-meta{font-size:11.5px;color:var(--ink-3);margin-top:2px;}
.pulse{position:absolute;inset:-2px;border-radius:50%;border:2px solid var(--accent);animation:pulse 2.4s ease-out infinite;}
@keyframes pulse{0%{transform:scale(1);opacity:.55}70%{transform:scale(1.32);opacity:0}100%{opacity:0}}
.station{margin:12px 0 4px;border:1px solid var(--line-2);border-radius:var(--r);background:var(--card);overflow:hidden;box-shadow:var(--shadow);animation:open .22s ease-out;width:100%;max-width:520px;}
@keyframes open{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.station-head{padding:13px 15px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;gap:10px;}
.station-t{font-family:var(--serif);font-size:15px;font-weight:600;}
.station-s{font-size:12px;color:var(--ink-3);margin-top:2px;}
.act{display:flex;align-items:center;gap:11px;padding:10px 15px;border-bottom:1px solid var(--line);}
.act:last-child{border-bottom:none;}
.act-box{flex:0 0 19px;height:19px;border-radius:6px;border:1.5px solid var(--line-2);background:var(--panel);display:flex;align-items:center;justify-content:center;color:#fff;}
.act.is-done .act-box{background:var(--done);border-color:var(--done);}
.act.is-locked{opacity:.45;}
.act-body{flex:1;min-width:0;}
.act-t{font-size:13px;font-weight:500;}
.act.is-done .act-t{color:var(--ink-3);}
.act-k{font-size:11.5px;color:var(--ink-3);}
.act-m{font-family:var(--mono);font-size:11px;color:var(--ink-3);}
.act-go{padding:5px 11px;border-radius:7px;background:var(--accent);color:var(--on-accent);font-size:12px;font-weight:500;}
.act-go:hover{background:var(--accent-deep);}
.summit{margin:26px 0 0;display:flex;align-items:center;gap:12px;}
/* The end of the route opens the certificate (build 172). As a button it
   keeps the look of the node it was; finished, its badge fills in. */
button.summit{width:100%;padding:6px 8px 6px 0;border:0;background:none;font:inherit;color:inherit;text-align:left;
  cursor:pointer;border-radius:14px;}
button.summit:hover{background:var(--accent-wash);}
button.summit:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
.summit.is-earned .summit-badge{background:var(--accent);border-style:solid;color:#fff;
  box-shadow:0 0 0 4px var(--accent-wash);}
.summit.is-earned .node-meta{color:var(--accent-text);font-weight:600;}
.summit-sample{display:inline-block;margin-top:3px;padding:0;border:0;background:none;font:inherit;font-size:12px;cursor:pointer;
  color:var(--accent-text);text-decoration:underline;text-underline-offset:2px;text-align:left;}
.summit-sample:hover{color:var(--accent);}
.summit-sample:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px;}
.summit-lonly{margin-top:18px;width:100%;max-width:340px;}
/* The certificate itself, on screen: the page scaled to the panel's width. */
.scrim.cert-scrim{z-index:72;background:rgba(10,18,32,.55);align-items:flex-start;justify-content:center;
  overflow-y:auto;padding:24px 16px;}
.cert{width:min(980px,100%);margin:auto;background:var(--paper);border-radius:18px;padding:18px 20px 22px;
  box-shadow:0 24px 60px rgba(0,0,0,.3);}
.cert-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;}
.cert-t{font-family:var(--serif);font-size:22px;font-weight:600;margin-top:2px;}
.cert-x{position:static;flex:0 0 auto;animation:none;}
.cert-note{margin-top:12px;}
.cert-tools{display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;margin-top:14px;}
.cert-name{flex:1 1 260px;display:flex;flex-direction:column;gap:5px;font-size:12.5px;color:var(--ink-2);}
.cert-hint{margin-top:6px;}
.cert-who{font-family:var(--serif);font-size:20px;font-weight:600;color:var(--ink);}
.cert-view{position:relative;margin-top:14px;overflow:hidden;border-radius:6px;box-shadow:0 2px 14px rgba(0,0,0,.14);}
.cert-view iframe{position:absolute;top:0;left:0;border:0;transform-origin:0 0;background:#fff;}
/* Never squeezed (build 175): beside text that wraps, a flex item may shrink,
   and the circle became an oval. Its size is fixed both ways. */
.summit-badge{flex:0 0 62px;width:62px;height:62px;min-width:62px;box-sizing:border-box;aspect-ratio:1;border-radius:50%;background:var(--accent-wash);border:1.5px dashed var(--accent);display:flex;align-items:center;justify-content:center;font-size:22px;}
.summit .node-label{flex:1 1 auto;min-width:0;}
.empty{flex:1;min-height:0;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:40px 28px;color:var(--ink-3);gap:8px;}
.empty-t{font-family:var(--serif);font-size:16px;color:var(--ink-2);}
/* inline-block (build 179): an empty <span> left inline keeps no size and
   cannot turn, so inside a plain button (the Join beside a class code) it drew
   as one curved border, a ")". A flex parent had been hiding that. */
.spin{display:inline-block;vertical-align:-2px;flex:0 0 auto;width:14px;height:14px;border:2px solid transparent;border-top-color:currentColor;border-right-color:currentColor;border-radius:50%;opacity:.85;animation:sp .7s linear infinite;}
.spin.dark{border-color:var(--line-2);border-top-color:var(--accent);}
@keyframes sp{to{transform:rotate(360deg)}}

/* ---------- activity player ---------- */
.scrim{position:fixed;inset:0;height:100vh;height:100dvh;background:var(--paper);z-index:50;display:flex;animation:fade .18s ease-out;}
@keyframes fade{from{opacity:0}to{opacity:1}}
.player{width:100%;height:100%;background:var(--paper);display:flex;flex-direction:column;overflow:hidden;animation:rise .22s cubic-bezier(.2,.8,.2,1);}
@keyframes rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
.p-split{flex:1;display:flex;min-height:0;}
.p-ref{flex:0 0 58%;min-width:0;display:flex;flex-direction:column;background:var(--card);border-right:1px solid var(--line-2);}
.p-ref-head{padding:11px 20px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px;flex:0 0 auto;}
/* `scrollbar-gutter:stable` keeps the scrollbar's room reserved even while
   nothing scrolls yet, so the audio bar above does not shift by a few pixels
   the moment the picture loads and the pane starts to scroll (157). */
.p-ref-body{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:22px 26px 44px;
  scrollbar-gutter:stable;}
/* Its own band above the scroller, held as tight to the pane header as the
   native control allows. Nothing scrolls through it and nothing passes
   under it, because it is not inside the scrolling box at all.
   `--sbw` is the scroller's gutter, measured by gutterRef (60-player.js). It
   is added on the right so the bar ends exactly where the picture and
   transcript below it end. */
.pin-media{flex:0 0 auto;background:var(--card);padding:5px calc(26px + var(--sbw,0px)) 7px 26px;
  position:relative;z-index:1;}
/* NO RULES ROUND THE BAR (build 231, Khoa: "remove the two thin lines above
   and below the audio bar for better look"). The heading above lost its rule
   (`.no-rule`, set by the Player when a bar is pinned under it) and this band
   lost its own. What the lower rule was for, showing where the text scrolls
   away, is done by a short fade instead: the top 10px of the scroller go to
   the pane's colour, so a line sliding under the bar thins out and is not cut
   through the middle. It stops short of the scrollbar (`--sbw`). */
.p-ref-head.no-rule{border-bottom:none;}
.pin-media::after,.p-pin::after{content:"";position:absolute;left:0;right:var(--sbw,0px);top:100%;height:10px;
  background:linear-gradient(var(--card),transparent);pointer-events:none;}
.p-pin::after{right:0;background:linear-gradient(var(--paper),transparent);}
.pin-media .play-btn{margin-top:8px;}
.p-ref-body .passage{border:none;background:transparent;padding:0;font-size:17.5px;line-height:1.75;}
/* The reading pane's text, its target-language list and its hint, as one
   column. (Build 155.) The pane is 58% of the screen and can be dragged
   wider, and a line that runs the full width of it is 140-odd characters —
   the eye loses its place on the way back to the next line. Capped near 70
   characters and centred, the way a book page is; a pane narrower than that
   just fills, as before. */
.read-col{max-width:640px;margin:0 auto;}
.read-col .passage{font-family:var(--read);font-weight:400;color:var(--read-ink);
  line-height:1.8;font-optical-sizing:auto;
  /* build 210: the passage is a summary in paragraphs (a blank line between
     them in the Practice file); they ran together as one block before */
  white-space:pre-line;}
.read-col mark.tv{color:var(--ink);}
.p-handle{flex:0 0 5px;cursor:col-resize;background:var(--line);touch-action:none;}
.p-handle:hover{background:var(--accent);}
.p-main{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;position:relative;}
.p-inner{max-width:620px;margin:0 auto;width:100%;}
.p-head{padding:13px 16px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:12px;background:var(--card);}
.p-close{width:26px;height:26px;border-radius:7px;color:var(--ink-3);display:flex;align-items:center;justify-content:center;font-size:16px;}
.p-close:hover{background:var(--panel);color:var(--ink);}
.p-title{font-family:var(--serif);font-size:15px;font-weight:600;line-height:1.2;}
.p-sub{font-size:11.5px;color:var(--ink-3);}
.p-headtext{min-width:0;flex:1;}
.p-sub-m{display:none;}
.p-steps{display:flex;gap:4px;margin-left:auto;}
.step{width:22px;height:4px;border-radius:4px;background:var(--line-2);}
.step.on{background:var(--accent);}
.step.ok{background:var(--done);}
.step.no{background:var(--wrong);}
.p-body{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:26px 26px 24px;}
.p-foot{padding:12px 26px 20px;border-top:1px solid var(--line);}
.p-foot .p-inner{display:flex;align-items:center;gap:12px;}
.p-foot .grow{flex:1;}
/* build 210: the result is already saved when its screen is up; this says so */
.p-saved{font-size:12px;color:var(--done);font-weight:600;}
.p-back{padding:6px 11px;border-radius:7px;border:1px solid var(--line-2);color:var(--ink-2);font-size:12px;}
.p-back:hover{border-color:var(--accent);color:var(--accent-deep);}
.p-back[disabled]{opacity:.4;cursor:not-allowed;}
.recap-tag{float:right;font-family:var(--mono);font-size:9.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3);margin-left:10px;}
.recap{opacity:1;}
.recap input,.recap .opt,.recap .tok,.recap .mcell{pointer-events:none;}
.locked-note{font-family:var(--mono);font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3);border:1px solid var(--line-2);border-radius:20px;padding:3px 9px;display:inline-block;margin-bottom:10px;}
.oq{margin-bottom:14px;}
.oq-q{font-family:var(--read);font-optical-sizing:auto;font-size:15px;font-weight:400;line-height:1.45;margin-bottom:6px;}
.oq-model{font-size:12.5px;color:var(--ink-2);background:var(--card);border:1px solid var(--line);border-left:2px solid var(--done);border-radius:0 8px 8px 0;padding:8px 10px;margin-top:6px;}
.card-ipa{font-family:var(--mono);font-size:13px;color:var(--ink-3);margin-top:4px;}
.chunk-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px;padding:9px 0;border-bottom:1px solid var(--line);font-size:16px;}
/* Frames listed in a note: numbered, one per line, hanging indent. */
.note-pat{display:flex;gap:9px;align-items:baseline;padding:4px 0;line-height:1.55;}
.note-pat+.note-pat{border-top:1px solid var(--line);}
.note-pat b{font-family:var(--mono);font-size:12px;color:var(--accent);flex:0 0 auto;}
.chunk-fixed{color:var(--ink-3);}
/* Punctuation and closing marks sit against what precedes them. */
.chunk-fixed.hug{margin-left:-4px;}
.idea{display:flex;gap:9px;align-items:baseline;margin-bottom:5px;font-size:13px;}
.idea b{font-family:var(--mono);font-size:11px;color:var(--accent-text);flex:0 0 14px;}
.q-kind{font-family:var(--mono);font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--accent-text);}
.q-why{font-size:12px;color:var(--ink-2);margin-top:4px;}
.q{font-family:var(--read);font-optical-sizing:auto;font-size:17px;font-weight:400;line-height:1.45;margin:6px 0 14px;}
/* One line, in one language (build 137). A Basic course reads an instruction
   in Vietnamese and in nothing else; every level above it reads the same
   instruction in English and in nothing else. So the Vietnamese has no face of
   its own any more — it WEARS the face of the line it replaced (`.q`,
   `.q-kind`, `.q-why`, `.fb`, `.hint`, `.res-l`), and `.vi-line` puts right
   only the three things that are about the SCRIPT rather than about the role:
   IBM Plex Mono has no Vietnamese subset, an uppercase eyebrow set in
   Vietnamese capitals with tracking is close to unreadable, and both of those
   are sometimes set from an inline style — which is the one thing `!important`
   is for here. `.card-vi` and `.tip-vi` set their own face for the same
   reason. */
.vi-line{font-family:var(--sans)!important;text-transform:none!important;letter-spacing:normal!important;}
/* The question line is the one Vietnamese line that keeps its serif (build
   226): Source Serif 4 has every Vietnamese letter, which the mono and the
   uppercase eyebrow this rule was written for do not. Same face and size as
   the English question it stands in for. */
.q.vi-line{font-family:var(--read)!important;font-size:17px;line-height:1.5;}
.q-kind.vi-line{font-size:11px;}
.opt{display:block;width:100%;text-align:left;padding:11px 13px;border:1px solid var(--line-2);border-radius:9px;background:var(--card);margin-bottom:8px;font-size:13.5px;transition:border-color .12s,background .12s;}
.opt:hover{border-color:var(--ink-3);}
.opt.sel{border-color:var(--accent);background:var(--accent-wash);}
.opt.ok{border-color:var(--done);background:var(--done-wash);}
.opt.no{border-color:var(--wrong);background:var(--wrong-wash);}
.tok{display:inline-block;padding:4px 8px;margin:0 3px 6px 0;border:1px solid var(--line-2);border-radius:7px;background:var(--card);font-size:14px;line-height:1.4;}
.tok.sel{border-color:var(--accent);background:var(--accent-wash);}
.tok.ok{border-color:var(--done);background:var(--done-wash);}
.tok.no{border-color:var(--wrong);background:var(--wrong-wash);}
.passage{font-family:var(--serif);font-size:16px;line-height:1.62;background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:16px 18px;}
/* PRACTICE TEXT IS READ, NOT HEADLINED. (Build 198.) Playfair is loaded at
   600 only, so every box set in --serif — the lines to shadow, the examples,
   the gap sentence, the cue said to you, what the microphone heard — drew as
   a block of semibold display type, near-white in dark. Khoa (2026-10-05):
   "too bright and tired my eyes because of being bold". Build 155 had already
   moved the reading pane to --read for the same reason; this extends it to
   every text box in the player. What stays semibold is what is there to be
   picked out: a highlighted target word (`mark.tv`) and a `<b>`. Source
   Serif 4 is loaded at 400 and 600 for that.

   AND SO IS THE QUESTION (builds 225, 226). 198 kept two things in Playfair
   semibold on purpose: the step's question (`.q`) and the frame a note
   teaches (`.note-pat`). On the note card those two are everything on it, a
   three-line paragraph and up to five frames. Khoa, a red box round exactly
   that on ME2 S02: "use non-serif font and don't get them bold to avoid
   tiring learner eyes". 225 set the two in the sans. He saw it live: "clear
   but look ugly ... roll back the old font but don't get them bold, or
   suggest the best approach as long as it benefits the learners. then make
   it consistent through out similar function text of all courses".

   The old font cannot simply be made lighter: Playfair is loaded at 600 and
   700 only, and it is a headline face (hairline strokes, made for a few
   large words). The face for reading is already here: Source Serif 4 at 400
   (`--read`), what the passage and every other text box above has used
   since 155 and 198. So what a learner READS at the top of a step is that
   face too, in every step of every course: the question or instruction
   (`.q`), an open-answer step's questions (`.oq-q`), the note's frames
   (`.note-pat`). Sizes as they were. Playfair is left to what is a heading
   or a figure: the activity's title, the word on a card, the score. */
.passage,.chunk-row,.gapline,.tset-t,.heard{font-family:var(--read);font-weight:400;
  font-optical-sizing:auto;color:var(--read-ink);}
.heard{color:var(--ink-2);}
.passage b,.passage strong,.chunk-row b,.gapline b,.passage mark.tv{font-weight:600;color:var(--ink);}
.note-pat{font-family:var(--read);font-optical-sizing:auto;font-weight:400;color:var(--ink);}
.q.note-q{line-height:1.5;}
.timer{font-family:var(--mono);font-size:12px;color:var(--accent-text);margin-bottom:8px;}
.match-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
.mcell{padding:10px 12px;border:1px solid var(--line-2);border-radius:9px;background:var(--card);font-size:13px;text-align:left;min-height:44px;}
.mcell.sel{border-color:var(--accent);background:var(--accent-wash);}
.mcell.ok{border-color:var(--done);background:var(--done-wash);color:var(--ink-3);cursor:default;}
.mcell.no{border-color:var(--wrong);background:var(--wrong-wash);}
.gapline{font-size:17px;line-height:1.7;}
.gapin{display:inline-block;min-width:130px;border:none;border-bottom:2px solid var(--accent);background:transparent;font-family:var(--sans);font-size:15px;padding:1px 4px;color:var(--ink);text-align:center;}
.gapin.ok{border-color:var(--done);color:var(--done);}
.gapin.no{border-color:var(--wrong);color:var(--wrong);}
.gapin:focus,.gapin:focus-visible{outline:none;border-bottom-width:3px;}
.dline{display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--line);align-items:baseline;}
.dwho{flex:0 0 20px;font-family:var(--mono);font-size:11px;color:var(--accent-text);}
.dtext{font-size:14px;}
/* "Your turn" (build 214): the conversation as cards, the other character's
   on the card colour, yours on the accent wash; a speaker's name is a name
   here, not a one-letter label, so it gets room and wraps above the line */
.rp-thread{display:flex;flex-direction:column;gap:8px;margin-top:12px;}
.rp-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px;margin-top:8px;padding:10px 12px;
  border:1px solid var(--line);border-radius:12px;background:var(--card);}
.rp-thread .rp-line{margin-top:0;}
.rp-line.me{background:var(--accent-wash);border-color:transparent;}
.rp-line .dwho{flex:0 0 auto;font-size:11.5px;letter-spacing:.04em;}
.rp-line .dtext{flex:1 1 220px;font-family:var(--read);font-size:16px;line-height:1.5;color:var(--ink);}
.rp-play{flex:none;padding:2px 8px;border-radius:6px;font-size:11px;align-self:center;}
.dtext.hidden{color:transparent;background:linear-gradient(var(--line),var(--line));background-size:100% 9px;background-repeat:no-repeat;background-position:0 60%;border-radius:3px;}
.masked{color:transparent;background:linear-gradient(var(--line-2),var(--line-2));background-size:100% 11px;background-repeat:no-repeat;background-position:0 58%;border-radius:4px;user-select:none;}
/* A covered transcript line: the real words are there, just out of focus, so
   the learner can see how much is coming and open only what they need. */
.blurline{position:relative;flex:1;min-width:0;cursor:pointer;border-radius:5px;padding:1px 3px;margin:-1px -3px;}
.blurline .dtext{display:block;filter:blur(4.5px);opacity:.9;user-select:none;transition:filter .16s;}
.blurline:hover .dtext,.blurline:focus-visible .dtext{filter:blur(3px);}
.blurline .peek{position:absolute;inset:0;display:flex;align-items:center;justify-content:flex-start;
  font-family:var(--mono);font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--accent-deep);opacity:0;transition:opacity .16s;pointer-events:none;}
.blurline:hover .peek,.blurline:focus-visible .peek{opacity:.95;}
.tset-row{padding:11px 0;border-bottom:1px solid var(--line);}
.tset-cue{display:flex;gap:9px;align-items:baseline;margin-bottom:6px;}
.tset-n{font-family:var(--mono);font-size:11px;color:var(--ink-3);flex:0 0 18px;}
.tset-t{font-size:15px;flex:1;min-width:0;}
.tset-t small{display:block;font-family:var(--sans);font-size:11.5px;color:var(--ink-3);margin-top:2px;}
.tset-tag{font-family:var(--mono);font-size:10.5px;padding:1px 6px;border-radius:20px;border:1px solid var(--line-2);color:var(--ink-3);flex:0 0 auto;}
.tset-in{margin-left:27px;width:calc(100% - 27px);}
.tset-in.ok{border-color:var(--done);}
.tset-in.no{border-color:var(--wrong);}
.tset-model{margin-left:27px;}
.play-btn{display:inline-flex;align-items:center;gap:8px;padding:9px 15px;border-radius:30px;background:var(--card);border:1px solid var(--line-2);font-size:13px;font-weight:500;}
.play-btn:hover{border-color:var(--accent);color:var(--accent-deep);}
.play-btn.on{border-color:var(--accent);background:var(--accent-wash);color:var(--accent-deep);}
.mic{width:74px;height:74px;border-radius:50%;background:var(--accent);color:var(--on-accent);display:flex;align-items:center;justify-content:center;font-size:26px;margin:6px auto;box-shadow:0 4px 16px rgba(217,119,87,.35);}
/* WHILE IT RECORDS (build 227, MicBtn in 50-widgets.js): the button is red
   with a stop square, and the line under it says so, behind a red dot that
   blinks. Before that the line says how the button works. The red is one
   colour in both themes: it is the sign for "recording" everywhere. */
.mic.rec{background:#D93025;color:#fff;font-size:22px;box-shadow:0 4px 16px rgba(217,48,37,.35);animation:pulseMic 1.3s ease-out infinite;}
@keyframes pulseMic{0%{box-shadow:0 0 0 0 rgba(217,48,37,.5)}100%{box-shadow:0 0 0 22px rgba(217,48,37,0)}}
.mic-say{font-size:13.5px;line-height:1.45;text-align:center;color:var(--ink-2);margin:8px 0 10px;}
.mic-say.on{color:var(--ink);font-weight:600;}
.mic-say.on::before{content:"";display:inline-block;width:9px;height:9px;margin-right:7px;border-radius:50%;
  background:#D93025;animation:micDot 1s ease-in-out infinite;}
@keyframes micDot{50%{opacity:.25}}
@media (prefers-reduced-motion:reduce){.mic.rec,.mic-say.on::before{animation:none;}}
.heard{font-size:16px;text-align:center;margin-top:8px;min-height:24px;}
/* the microphone is being started and is not listening yet (236.5): the red
   button is there to stop it, but faint, and without its pulse */
.mic.rec.wait{opacity:.5;animation:none;}
/* 236.8: faint to full is one move, not a jump; and the waiting words come
   only when the start is slow enough to need them */
.mic{transition:opacity .25s ease;}
.mic-soon{animation:micSoon .25s ease .35s both;}
@keyframes micSoon{from{opacity:0;}to{opacity:1;}}
@media (prefers-reduced-motion:reduce){.mic{transition:none;}.mic-soon{animation-duration:.01s;}}
/* the browser's word for why the microphone gave nothing (236.4) */
.mic-why{text-align:center;font-family:var(--mono);font-size:10.5px;color:var(--ink-3);margin-top:4px;}
.musts{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0;}
.must{font-size:12px;padding:4px 9px;border-radius:20px;border:1px solid var(--line-2);color:var(--ink-3);background:var(--card);}
.must.on{border-color:var(--done);background:var(--done-wash);color:var(--done);}
.fb{border:1px solid var(--line);border-left:2px solid var(--accent);border-radius:0 9px 9px 0;background:var(--card);padding:12px 14px;font-size:13px;line-height:1.55;margin-top:12px;}
.fb b{font-weight:600;}
/* ---- the sign-up box's two typo checks (build 134) ----
   The suggestion is a link, not an alert: it is a guess, and a guess drawn in
   the error colour is read as a refusal. The confirm panel is the accent
   panel every other two-step in this app uses. */
.mail-near{display:block;width:100%;text-align:left;padding:7px 2px 0;font-size:12.5px;
  color:var(--accent-text);background:none;border:0;}
.mail-near:hover{text-decoration:underline;}
.mail-conf{margin-top:0;}
.mail-conf-a{font-family:var(--mono);font-size:15px;color:var(--ink);
  word-break:break-all;margin:8px 0 8px;}
.lang-vi .mail-conf-a{font-family:var(--sans);}
.mail-conf-b{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px;}
.mail-conf-b .btn{flex:1 1 auto;width:auto;min-width:130px;}
/* The second line of a negative pattern: both forms of "not", inside the same
   panel as the pattern itself but set back from it, because it is a note ABOUT
   the pattern and not another pattern. (Build 133.) */
.pat-ex{margin-top:6px;padding-top:6px;border-top:1px solid var(--line);font-size:14px;color:var(--ink-2);}
.pat-ex i{font-style:normal;font-weight:600;color:var(--ink);}
.pat-ex-vi{color:var(--ink-3);}
/* Hear it, flip it and Spot the mistake (build 223, 60-player.js).
   Laid out in groups (223.2): one even gap between groups, a tight one inside
   each, and no margin of its own on anything inside, so nothing stacks up. */
.snap{display:flex;flex-direction:column;gap:22px;}
.snap-top{display:flex;flex-direction:column;gap:10px;}
.snap-brief{display:flex;flex-direction:column;gap:4px;}
.snap-card{display:flex;flex-direction:column;gap:12px;}
.snap-act{display:flex;flex-direction:column;align-items:stretch;gap:12px;}
.snap :is(.q-kind,.q-why,.fb,.heard,.grade,.mic,.mic-say,.snap-how,.snap-ask,.snap-note,.fix-ok,.fix-no,.verdict){margin:0;}
.snap .grade{margin:0;}
.snap-act .mic{margin:2px auto 0;}
.snap-act .heard{min-height:0;text-align:center;}
.snap-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px;}
.snap-task{display:inline-block;background:var(--ink);color:var(--paper);font-weight:600;font-size:17px;line-height:1.3;padding:7px 14px;border-radius:20px;}
.snap-old{font-size:12px;font-weight:600;color:var(--lv1-text);background:var(--lv1-wash);border-radius:12px;padding:4px 10px;}
.snap-how{font-size:15px;line-height:1.5;color:var(--ink-2);}
.snap-ask{font-size:16px;line-height:1.45;font-weight:600;color:var(--ink);}
.snap-pat{font-family:var(--mono);font-size:12px;line-height:1.5;color:var(--ink-3);}
.snap-note{margin-top:8px !important;font-size:14px;line-height:1.5;color:var(--ink-3);}
.snap-cue{display:flex;align-items:center;justify-content:space-between;gap:12px;background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:16px 18px;}
.snap-t{font-family:var(--read);font-size:21px;line-height:1.4;color:var(--ink);}
.snap-hid{font-size:14px;line-height:1.5;color:var(--ink-3);}
.snap-vi{font-size:15px;line-height:1.5;color:var(--ink-2);}
.snap-time{height:6px;background:var(--track);border-radius:3px;overflow:hidden;}
.snap-time i{display:block;height:6px;width:0;background:var(--accent);border-radius:3px;}
.snap-time i.on{animation:snapT linear forwards;}
@keyframes snapT{from{width:0}to{width:100%}}
.snap-total{font-size:15px;color:var(--ink-2);}
.snap-total b{font-family:var(--serif);font-size:38px;line-height:1.1;color:var(--ink);margin-right:6px;}
.snap-tasks{display:grid;gap:12px;background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:14px 16px;}
.snap-row{display:grid;grid-template-columns:1fr auto;gap:5px 10px;font-size:14px;}
.snap-n{font-family:var(--mono);}
.snap-bar{grid-column:1 / -1;height:6px;background:var(--track);border-radius:3px;overflow:hidden;}
.snap-bar i{display:block;height:6px;background:var(--done);}
.fix-words{display:flex;flex-wrap:wrap;justify-content:center;gap:8px;background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:18px 14px;}
.fix-w{font-family:var(--read);font-size:20px;padding:8px 13px;min-height:44px;border-radius:8px;border:1px solid var(--line-2);background:var(--card);color:var(--ink);cursor:pointer;}
.fix-w:disabled{cursor:default;}
.fix-w.dim{border-color:transparent;color:var(--ink-2);}
.fix-w.bad{border:1.5px solid var(--wrong);background:var(--wrong-wash);color:var(--err-ink);text-decoration:line-through;}
.fix-w.miss{border:1px dashed var(--line-2);color:var(--ink-3);}
/* the four games of a video or interview session (build 235): SprintSet, TilesSet, TurnSet, DuelSet */
.gm-hud{display:flex;align-items:center;gap:14px;font-size:13px;color:var(--ink-3);font-variant-numeric:tabular-nums;}
.gm-hud b{font-size:16px;color:var(--ink);}
.gm-sp{flex:1;}
.gm-hearts{font-size:17px;letter-spacing:2px;color:var(--wrong);}
.gm-bar{height:6px;border-radius:99px;background:var(--line);overflow:hidden;margin-top:-12px;}
.gm-bar i{display:block;height:100%;background:var(--accent);transition:width .3s linear;}
.gm-opts{display:flex;flex-direction:column;gap:8px;}
.gm-opt{font-family:var(--read);font-size:18px;line-height:1.35;text-align:left;padding:12px 14px;min-height:48px;border-radius:10px;border:1px solid var(--line-2);background:var(--card);color:var(--ink);cursor:pointer;}
.gm-opt:disabled{cursor:default;}
.gm-opt.hit{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent);font-weight:600;}
.gm-opt.miss{border:1.5px solid var(--wrong);background:var(--wrong-wash);color:var(--err-ink);}
.gm-slot{display:flex;flex-wrap:wrap;align-content:flex-start;gap:8px;min-height:104px;padding:12px;border:1px dashed var(--line-2);border-radius:var(--r);background:var(--card);}
.gm-slot.ok{border-style:solid;border-color:var(--accent);}
.gm-slot.no{border-style:solid;border-color:var(--wrong);}
.gm-pool{display:flex;flex-wrap:wrap;gap:8px;}
.gm-tile{font-size:17px;padding:7px 11px;min-height:42px;}
.gm-fade{display:flex;flex-wrap:wrap;gap:6px 8px;align-items:baseline;font-family:var(--read);font-size:20px;line-height:1.5;color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:16px 16px;}
.gm-piece.gap i{display:inline-block;width:56px;margin-left:4px;border-bottom:2px solid var(--line-2);vertical-align:baseline;}
.gm-used{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:14px;color:var(--ink-2);}
.gm-chip{font-size:13px;font-weight:600;padding:3px 10px;border-radius:12px;border:1px solid var(--line-2);background:var(--card);color:var(--ink);}
@media (prefers-reduced-motion:reduce){.gm-bar i{transition:none;}}
.fix-ok,.fix-no{font-size:15px;line-height:1.45;font-weight:600;border-radius:9px;padding:9px 13px;}
.fix-ok{background:var(--done-wash);color:var(--ink);border-left:3px solid var(--done);}
.fix-no{background:var(--wrong-wash);color:var(--err-ink);}
/* THE VERDICT (build 241, `Verdict`, 50-widgets.js): right or not, said the
   same way in every Practice step at the moment of checking. Right is the
   wash of a finished thing with a green tick that pops ONCE, a third of a
   second; not right is the wrong answer's wash, still. The words are the
   page's ink on the wash, not green on paper: `--done` as a text colour is
   too faint to read. The tick is drawn in `--card`, so it is light on the
   green by day and dark on it at night. The little chip is "3 in a row",
   in the lab's gold. Nothing here moves for a learner who has asked for
   less motion (the rule for everything, further down). */
.verdict{display:flex;align-items:center;gap:9px;margin-top:12px;padding:9px 13px;border-radius:9px;font-size:15px;line-height:1.45;font-weight:600;}
.verdict.ok{background:var(--done-wash);color:var(--ink);}
.verdict.no{background:var(--wrong-wash);color:var(--err-ink);font-weight:500;}
.vd-mk{flex:0 0 auto;width:22px;height:22px;border-radius:50%;display:grid;place-items:center;font-size:13px;line-height:1;font-weight:700;}
.verdict.ok .vd-mk{background:var(--done);color:var(--card);animation:vdPop .36s cubic-bezier(.3,1.5,.5,1) both;}
.verdict.no .vd-mk{border:1.5px solid var(--wrong);color:var(--wrong);font-size:10px;}
.vd-t{min-width:0;}
.vd-run{display:inline-block;margin-left:9px;font-family:var(--mono);font-size:11px;font-weight:500;padding:1px 9px;border-radius:999px;border:1px solid var(--gold);background:var(--gold-wash);color:var(--gold);white-space:nowrap;vertical-align:1px;animation:g-pop .32s ease-out;}
@keyframes vdPop{0%{transform:scale(.3);opacity:0;}60%{transform:scale(1.2);opacity:1;}100%{transform:scale(1);opacity:1;}}
/* A RECORDING WITH EVERY WORD HEARD (build 245, `MicScore`): the lab's gold,
   a star that pops once the way the tick does, the words in the page's ink
   on the gold wash (gold as a text colour is too faint to read). */
.mic-perfect{display:flex;align-items:center;justify-content:center;gap:8px;width:fit-content;max-width:100%;margin:10px auto 0;padding:7px 14px;border-radius:999px;border:1px solid var(--gold);background:var(--gold-wash);color:var(--ink);font-size:14px;line-height:1.4;}
.mic-perfect .mp-star{flex:0 0 auto;display:grid;place-items:center;animation:vdPop .4s cubic-bezier(.3,1.5,.5,1) both;}
.mic-perfect .mp-t{min-width:0;}
@media (prefers-reduced-motion:reduce){.mic-perfect .mp-star{animation:none;}}
/* and the box that was right breathes out once: a ring that fades. Not in the
   locked review of an old step (`.recap`), where nothing has just happened. */
:is(.opt,.tok,.mcell).ok:not(.recap *){animation:okRing .5s ease-out 1;}
@keyframes okRing{0%{box-shadow:0 0 0 0 rgba(124,143,99,.5);}100%{box-shadow:0 0 0 8px rgba(124,143,99,0);}}
.pat-neg{margin-top:6px;padding-top:6px;border-top:1px solid var(--line);font-size:12.5px;color:var(--ink-2);}
/* The same panel, hanging off a roster row rather than off a class row, so it
   lines up under the name it is asking about. (Build 119.) */
.fb.kid{margin-left:14px;}
/* THE GUIDED TOUR (build 119).
   One layer, two elements. `.tour` is the whole window: it exists to take the
   clicks, so nothing the tour is pointing at can be pressed by accident while
   the tour is talking about it. `.tour-ring` draws the dim — the 9999px spread
   IS the darkening, so there is no second scrim to keep in step with it, and
   the lit area is simply the hole the shadow is cast around. A box-shadow is
   not hit-tested, which is why the ring itself can be pointer-events:none and
   the layer beneath it still catches everything.

   z-index 80: above the report and the player (50), above the invitation and
   the account menu (60), above the sign-in box (70) — a tour that a menu can
   cover is a tour pointing at nothing — and below the boot screen (9999),
   which must always win.

   The dim is the same rgba the sign-in scrim uses, in both themes on purpose:
   a scrim is a scrim, and one that changed colour with the theme would be the
   only overlay on the site that did. */
.tour{position:fixed;inset:0;z-index:80;}
.tour-ring{position:absolute;border-radius:11px;pointer-events:none;
  box-shadow:0 0 0 9999px rgba(20,20,19,.55);
  outline:2px solid var(--accent);outline-offset:2px;
  transition:top .2s ease,left .2s ease,width .2s ease,height .2s ease;}
.tour-pop{position:absolute;width:300px;background:var(--card);
  border:1px solid var(--line-2);border-radius:13px;padding:14px 15px;
  box-shadow:0 14px 38px rgba(0,0,0,.3);}
.tour-n{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--accent-deep);}
.tour-t{font-family:var(--serif);font-size:16px;font-weight:600;color:var(--ink);
  margin:5px 0 5px;line-height:1.3;}
.tour-b{font-size:13px;line-height:1.55;color:var(--ink-2);}
.tour-b b{font-weight:600;color:var(--ink);}
.tour-act{display:flex;align-items:center;gap:8px;margin-top:13px;}
.tour-dots{display:flex;gap:4px;flex:1 1 auto;}
.tour-dots i{width:5px;height:5px;border-radius:50%;background:var(--line-2);display:block;}
.tour-dots i.on{background:var(--accent);}
.tour-skip{font-size:12px;color:var(--ink-3);padding:4px 2px;}
.tour-skip:hover{color:var(--ink-2);}
.card3d{background:var(--card);border:1px solid var(--line-2);border-radius:16px;padding:34px 20px;text-align:center;box-shadow:var(--shadow);}
.card-w{font-family:var(--serif);font-size:26px;font-weight:600;}
.card-m{font-size:14px;color:var(--ink-2);margin-top:10px;}
.card-cap{color:var(--ink-3);font-size:12px;}
/* The Vietnamese of a taught term, under its English meaning, on every card
   face in the app — Intro, Recall and the spaced-recall sprint. In the accent
   rather than in --ink-3, because it is the one line on the card a learner
   looks for when the definition did not land, and a third grey line under two
   grey lines is a line nobody finds. `--sans` is set here rather than left to
   `.lang-vi`: the course's level decides this, not the app's language, so a
   Basic course read in English still has Vietnamese on the card and IBM Plex
   Mono still has no Vietnamese subset. (Build 132.) */
.card-vi{font-family:var(--sans);font-size:14px;color:var(--accent-deep);margin-top:5px;}
/* Progress through an Intro set. A dot is hollow until its card has been
   seen, filled once it has, ringed while you are on it — so walking back
   through the set never makes the row look like it has un-happened. */
.card-dots{display:flex;justify-content:center;gap:6px;margin-top:14px;}
.card-dots i{width:7px;height:7px;border-radius:50%;background:var(--line-2);display:block;}
.card-dots i.met{background:var(--done);}
.card-dots i.on{background:var(--accent);box-shadow:0 0 0 3px var(--accent-wash);}
.card-nav{display:flex;gap:10px;}
.card-nav .btn{flex:1;justify-content:center;}
.card-nav .btn:disabled{opacity:.4;cursor:default;}
.grade{display:flex;gap:8px;justify-content:center;margin-top:14px;}
.result{text-align:center;padding:14px 0 6px;}
/* what the big number is (236.1): it was read as "how much I finished" */
.res-k{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);margin-bottom:8px;}
.res-n{font-family:var(--serif);font-size:44px;font-weight:600;line-height:1;}
.res-l{font-size:13px;color:var(--ink-3);margin-top:4px;}
.res-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;margin:18px 0;}
.res-cell{background:var(--paper);padding:11px;text-align:center;}
.res-cell b{display:block;font-family:var(--serif);font-size:18px;font-weight:600;}
.res-cell span{font-size:11px;color:var(--ink-3);}
.added{font-size:12.5px;color:var(--ink-2);background:var(--card);border:1px solid var(--line);border-radius:9px;padding:11px 13px;text-align:left;}

/* ---- the Vocabulary lab as a game (build 181) ----
   Gold is the game's one colour: stars, the combo, a clock still inside the
   target. Everything else is the player's own furniture (.opt, .res-grid). */
.g-hud{display:flex;flex-wrap:wrap;gap:7px;align-items:center;margin:2px 0 12px;}
.g-pill{font-family:var(--mono);font-size:11.5px;border:1px solid var(--line-2);border-radius:999px;padding:2px 10px;color:var(--ink-2);background:var(--card);font-variant-numeric:tabular-nums;}
.g-clock.under{border-color:var(--gold);color:var(--gold);}
.g-clock.over{color:var(--ink-3);}
.g-combo{font-family:var(--mono);font-size:11.5px;font-weight:500;border-radius:999px;padding:2px 10px;background:var(--gold-wash);color:var(--gold);border:1px solid var(--gold);animation:g-pop .32s ease-out;}
.g-combo.lost{background:var(--wrong-wash);color:var(--wrong);border-color:var(--wrong);}
@keyframes g-pop{0%{transform:scale(1);}40%{transform:scale(1.2);}100%{transform:scale(1);}}
.g-cost{font-size:12px;color:var(--gold);}
.g-key{display:inline-block;min-width:18px;margin-right:9px;font-family:var(--mono);font-size:11px;color:var(--ink-3);}
.g-music{padding:5px 10px;border-radius:7px;font-size:12px;white-space:nowrap;}
.g-music.off{color:var(--ink-3);}
@media (max-width:600px){.g-music-t{display:none;}.g-music{padding:5px 9px;font-size:14px;}.g-music.off{text-decoration:line-through;}}
/* flip pairs */
.flipg{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;}
.fcard{position:relative;min-height:92px;border:0;padding:0;background:none;perspective:700px;}
.fc-in{position:absolute;inset:0;transition:transform .35s;transform-style:preserve-3d;}
.fcard.open .fc-in,.fcard.got .fc-in{transform:rotateY(180deg);}
.fc-face{position:absolute;inset:0;backface-visibility:hidden;-webkit-backface-visibility:hidden;border-radius:10px;border:1px solid var(--line-2);
  display:flex;align-items:center;justify-content:center;padding:8px;text-align:center;overflow:hidden;}
.fc-back{background:var(--sunk);}
.fc-back::after{content:"e2i";font-family:var(--serif);font-size:19px;color:var(--ink-3);opacity:.55;}
.fcard:hover .fc-back{border-color:var(--ink-3);}
.fc-front{transform:rotateY(180deg);background:var(--card);color:var(--ink);}
.fc-front.en{font-family:var(--serif);font-weight:600;font-size:14px;line-height:1.2;}
.fc-front.vi{font-size:12px;line-height:1.35;color:var(--ink-2);}
.fcard.open .fc-front{border-color:var(--accent);background:var(--accent-wash);}
.fcard.miss .fc-front{border-color:var(--wrong);background:var(--wrong-wash);}
.fcard.got .fc-front{border-color:var(--done);background:var(--done-wash);color:var(--ink-3);}
@media (max-width:520px){.flipg{grid-template-columns:repeat(3,minmax(0,1fr));}}
/* stars */
.g-star path{fill:none;stroke:var(--line-2);}
.g-star.on path{fill:var(--gold);stroke:var(--gold);}
.g-rowstars{display:inline-flex;gap:1px;vertical-align:-2px;margin-right:7px;}
.g-bigstars{display:flex;justify-content:center;gap:10px;}
.g-bs{opacity:.2;transform:scale(.6);}
.g-bs.lit{opacity:1;transform:scale(1);transition:transform .4s cubic-bezier(.3,1.6,.5,1),opacity .2s;}
.g-bs.unlit{opacity:1;transform:scale(1);transition:transform .3s;}
.g-head{font-family:var(--serif);font-size:19px;color:var(--ink);margin-top:8px;}
.g-best{font-family:var(--mono);font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--gold);margin-top:4px;}
.g-checks{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:6px;text-align:left;}
.g-checks li{display:grid;grid-template-columns:20px minmax(0,1fr);gap:8px;align-items:start;padding:8px 12px;border:1px solid var(--line);border-radius:9px;font-size:12.5px;color:var(--ink-2);}
.g-checks li.ok{background:var(--done-wash);border-color:var(--done);}
.g-mk{font-family:var(--mono);font-weight:600;color:var(--ink-3);}
.g-checks li.ok .g-mk{color:var(--done);}
.res-grid.g4{grid-template-columns:repeat(4,1fr);}
@media (max-width:520px){.res-grid.g4{grid-template-columns:1fr 1fr;}}
@media (prefers-reduced-motion:reduce){.fc-in,.g-bs,.g-combo{transition:none!important;animation:none!important;}}
.mobile-tabs{display:none;}
@media (max-width:900px){
  .topbar{height:auto;min-height:54px;flex-wrap:wrap;padding:8px 12px;gap:6px 10px;align-content:center;}
  .brand{flex:1 1 100%;}
  /* Same outer box, a taller finger target: the pad grows and the margin
     cancels it, so nothing on the row moves and a thumb has ~44px to land on
     without the button reaching across the whole width of the bar. */
  .brand-home{padding:9px 7px;margin:-9px -7px;}
  .brand-sub{font-size:11px;}
  .topbar-spacer{display:none;}
  .stat{font-size:12px;}
  .stat-div{height:12px;}
  /* `.avatar` is the only child of `.acct`, so pushing IT right moved nothing —
     the flex item that needs the margin is the wrapper. At 390px the row
     happened to end where it looked right anyway; at 320px the stats wrap and
     the account landed 40px from the LEFT, taking its menu off screen with it.
     The wrapper is also what the menu is positioned against, so this is the one
     element whose place decides where the menu opens. */
  .acct{margin-left:auto;}
  /* TWO ROWS, IN THIS ORDER (build 212). Khoa, a phone screenshot: "the
     header looks bad for both teacher and learner". It wrapped where it ran
     out of room: the brand alone, then the board chip, the numbers and the
     bell, then the avatar alone on a third row, with a divider left hanging
     after "due for review". The bell and the account are both `.acct`
     wrappers, so both took `margin-left:auto` above and pushed each other
     off. Now `order` puts the bell and the account on the brand's row (the
     brand shrinks, its course name ends in "…"), and `.tb-break`, a
     full-width line of no height, starts the numbers' row. The divider
     before the bell has nothing to divide here. */
  .brand{flex:1 1 0;min-width:0;}
  .brand-home{flex:none;}
  .brand-sub{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .topbar>.acct{order:1;margin-left:0;}
  .tb-break{display:block;order:2;flex:0 0 100%;height:0;}
  .topbar>.bd-chip,.topbar>.stat,.topbar>.stat-div{order:3;}
  .topbar>.stat-div.tail{display:none;}
  /* The 🏆 chip comes last, at the row's right end (213): it arrives a
     moment after the rest (the board is asked for once the classes are in),
     and first in the row it pushed every number sideways as it landed. */
  .topbar>.bd-chip{order:4;margin-left:auto;animation:fade .3s ease-out;}
  .handle{display:none;}
  .cols{display:block;overflow-y:auto;
    /* THE COLUMN STAYS PUT UNDER A THUMB (build 215). Khoa, an iPhone
       screenshot of the Course hub pulled ~100px down and to the right: "I
       feel like I can dragging the whole column when scrolling down".
       Measured at 390, 375, 340 and 320px as a teacher with a 7-learner
       class, in list view: nothing is wider than the screen. It is iOS's
       rubber band: `overflow-y:auto` turns the unset `overflow-x` into
       `auto` too, so this scroller could be pulled sideways as well as
       past its ends, and a slightly diagonal swipe dragged the column
       about. Now it scrolls one way only, and stops at its ends instead of
       stretching (Safari 16+; older iPhones keep the bounce, not the
       sideways drag). */
    overflow-x:hidden;overscroll-behavior:none;}
  .col{width:100%!important;height:auto;display:none;}
  /* Fill the scroller, and not a pixel more. `calc(100dvh - 104px)` was a guess
     at the height of the topbar and the tab row above it, and it guessed high:
     the column ran ~130px past the bottom of `.cols`, so every tab had that
     much empty paper under it that could still be scrolled into — a screenful
     of nothing after the last card. `100%` of a scroller that already has a
     definite height needs no arithmetic and cannot drift when the topbar wraps
     to two lines at 320px. (Build 116.3.) */
  .col.m-on{display:flex;min-height:100%;}
  /* NOTHING MAY LAND UNDER THE BAND.
     Tapping a card focuses its button, and a browser scrolls a newly focused
     element into view by itself — aligned to the top of the scroller, which on
     a phone is `.cols`, with `.col-bar` stuck across the top of it. The band is
     inside the scrollport, so as far as the browser is concerned the top of the
     scrollport is behind it: the card you had just chosen slid up under the
     band and had its first line cut off. Nothing in the app asked for that
     scroll, so nothing in the app could correct it afterwards.

     `scroll-padding-top` on the SCROLLER, not `scroll-margin-top` on the card:
     it insets the scrollport once, for every element the browser ever brings
     into view here — a card, a roadmap day, a field, anything focused by a
     keyboard — instead of listing the ones we happened to think of. 52px of
     band, 10px of air. */
  .cols{scroll-padding-top:62px;}
  .col-head{display:none;}
  .col{overflow:visible;}
  .col-body,.j-wrap{overflow:visible;}
  /* On a phone the whole page scrolls rather than each column, so the band
     has to pin itself. It also needs a solid colour of its own here: on
     desktop it inherits a background nothing scrolls behind. */
  .col-bar{position:sticky;top:0;z-index:6;background:var(--paper);
    /* Same layer promotion as `.lp-nav`, for the same reason: this is the
       other bar on this site that a phone scrolls a page underneath. */
    transform:translateZ(0);will-change:transform;
    -webkit-backface-visibility:hidden;backface-visibility:hidden;}
  /* And the same seam guard — see `.lp-nav::before`. The strip above this bar
     is above the top of `.cols`, which is the scroller and clips it, so at
     rest it is invisible here too. */
  /* 6px, not 24 (build 224.1). The thin line Khoa saw across the phone tabs
     sat 24px above the top of `.cols`, measured off his screenshot against
     the tab row's own border: this strip's top edge, which the scroller is
     meant to clip and, on a zoomed iPhone, did not quite. It showed only
     where it crossed a tab, because it is the paper's colour. At 6px its
     edge falls in the tab row's bottom padding, paper on paper, where a
     stray pixel cannot be seen; and `.cols` no longer bounces
     (`overscroll-behavior`, 215), so there is no gap above the bar left
     for a taller strip to cover. */
  .col-bar::before{content:'';position:absolute;left:0;right:0;bottom:100%;
    height:6px;background:var(--paper);pointer-events:none;}
  /* Here the column is not the scroller — `.cols` is — so the band would scroll
     away with the cards, which is exactly the thing that made the message get
     repeated down the page in the first place. It pins itself directly under
     `.col-bar`: 52px is that band's height, and z-index 5 keeps it beneath it
     rather than over it, so the two never overlap while both are stuck. */
  .col-err{position:sticky;top:52px;z-index:5;background:var(--wrong-wash);}
  /* The band is 358px of usable width here and the course title needs most of
     it, so the refresh keeps the glyph and drops the word. `aria-label` is what
     names it either way, so nothing is lost to a screen reader. The pad grows
     instead, because a thumb needs a target and this one has a 52px band to
     live inside. (Build 116.7.) */
  .col-bar.has-refresh .pinbar-l{padding-right:52px;}
  .col-bar.has-refresh .role{margin-right:52px;}
  .bar-refresh{padding:9px 10px;right:12px;}
  .bar-refresh-t{display:none;}
  /* ---- Course › Plan › Journey ----
     Three steps of one walk, not three separate buttons. Each one comes to a
     point on its right, and the next one is notched to receive it — so they
     read as a sequence with a direction rather than as a segmented control
     you could press in any order.

     NOTHING IS CUT TO SHAPE ANY MORE (build 224). Until then each step was a
     button cut to an arrow with `clip-path: polygon(…)`, the point at half
     its height. Khoa, an iPhone screenshot, dark mode: "there is a thin line
     striking through the 3 tabs". Read off the picture, it was one pixel of
     paper showing through the first and the last step at exactly half their
     height (`#171717` on `#0E0E0E`), which is the row where the cut shape's
     two halves meet. 224 took that for the whole cause. It was half of it:
     the line stayed, lower down, through the bar's own text (224.1). The
     page was ZOOMED IN by about 3% — a double tap on these tabs does that
     on an iPhone — and at a scale that is not a whole number every join on
     the page can open by a pixel. The zoom is what 224.1 stops
     (`touch-action` on `html`, at the top of this file); this redrawing
     stays because it takes away one join that could open.

     So each step is a plain rectangle now, with no seam to open, and the
     point is a separate small square turned 45° on its right edge (squeezed
     to --d deep), in the step's own fill. A second one in the paper's
     colour, 3px further on, sits between the point and the next step: that
     is the gap, and what cuts the next step's notch. The steps stack left
     over right (z-index), so a point lies on top of the step it points at;
     the points take no taps (`pointer-events:none`), which go to the step
     underneath as they did when the shape was cut.

     --d is the depth of the point, --h the height of a step (the point is
     sized from it: a 24px square turned 45° is 34px tall).

     No borders anywhere here; the separation is carried by the fill —
     `--sunk` against `--paper` in both themes. */
  .mobile-tabs{--d:11px;--h:34px;display:flex;gap:0;padding:8px 12px;
    border-bottom:1px solid var(--line);background:var(--paper);
    /* the points are turned squares: nothing of them may count as width (224.1) */
    overflow:hidden;}
  .mobile-tabs .tab{flex:1;min-width:0;position:relative;height:var(--h);border:0;border-radius:0;
    padding:0 2px 0 calc(var(--d) + 10px);box-shadow:none;
    background:var(--sunk);color:var(--ink-3);font-size:12.5px;font-weight:500;line-height:var(--h);}
  .mobile-tabs .tab:nth-child(1){z-index:3;padding-left:8px;}
  .mobile-tabs .tab:nth-child(2){z-index:2;}
  /* the last one is the destination, so it does not point onward at nothing */
  .mobile-tabs .tab:last-child{z-index:1;padding-right:8px;}
  .mobile-tabs .tab-t{display:block;position:relative;z-index:1;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .mobile-tabs .tab:not(:last-child)::before,
  .mobile-tabs .tab:not(:last-child)::after{content:'';position:absolute;top:50%;left:100%;
    width:24px;height:24px;margin:-12px 0 0 -12px;pointer-events:none;
    transform:scaleX(.648) rotate(45deg);}
  /* the gap first, then the point over it */
  .mobile-tabs .tab:not(:last-child)::before{background:var(--paper);margin-left:-9px;}
  .mobile-tabs .tab:not(:last-child)::after{background:inherit;}
  /* THE PICKED STEP IS A SOLID COLOUR (build 236). Khoa, a phone picture of
     the three tabs: "make the tab picked glowing or in different color … to
     make sure a learner can recognized it". It was the accent's pale wash
     with accent-coloured words, which beside two grey steps reads as one
     more pale step. Now it is filled with the accent itself, the colour of
     the app's own buttons, with their text colour on it and bolder words:
     the darker terracotta under white in light mode (white on the plain
     accent is too faint at this size), the lighter one under near-black in
     dark mode. The point on its right takes the fill (`background:inherit`,
     above), so the whole arrow is coloured. No glow: the row clips its
     overflow, and a shadow would be cut at the row's edge. */
  .mobile-tabs .tab.on{background:var(--accent-deep);color:var(--on-accent);font-weight:700;}
  /* The gap runs the step's whole height (236). The paper-coloured square
     that makes the gap narrows to nothing at the top and bottom of a step,
     as the point does, so for its first 3px a step showed its own fill in
     the two corners, left of the gap: nothing to see while every step was
     grey or nearly, four coloured specks beside a solid one. Those 3px are
     paper now (an inset shadow: no new box, nothing cut), so the gap is one
     band from top to bottom and a step begins after it. */
  .mobile-tabs .tab + .tab{box-shadow:inset 3px 0 0 var(--paper);}
  .mobile-tabs .tab:active{filter:brightness(.97);}
  .p-split{flex-direction:column;}
  .p-ref{flex-grow:0;flex-shrink:0;min-height:46px;max-height:none;border-right:none;border-bottom:none;}
  .p-ref-head{padding:9px 16px;}
  .p-ref-body{padding:14px 18px 20px;}
  .pin-media{padding:4px calc(18px + var(--sbw,0px)) 6px 18px;}
  .sit-img{height:clamp(110px,22vh,180px);}
  .p-ref-body .passage{font-size:16px;}
  .p-handle{display:block;flex:0 0 24px;cursor:row-resize;background:var(--panel);
    border-top:1px solid var(--line);border-bottom:1px solid var(--line);position:relative;touch-action:none;}
  .p-handle::after{content:'';position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:46px;height:4px;border-radius:4px;background:var(--line-2);}
  .p-handle:active::after{background:var(--accent);}
  /* Six lines of header on a 380px screen is five lines of video. Title on one
     line, the stop name alone underneath, and the step pills shrink to fit. */
  /* One column of cards on a phone, and the filters scroll sideways rather than
     stacking into four rows of chips. */
  .kan{grid-template-columns:1fr;}
  .kcard-head{padding:10px;}
  .cnext{display:block;align-self:flex-start;margin-top:7px;
    font-family:var(--mono);font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;
    color:var(--accent-text);}
  /* (`.trow-meta` used to be hidden here, when it shared one line with the
     name. It has a line of its own since 147.1, so it stays.) */
  /* The menu used to be `position:fixed; top:52px; left:10px; right:10px` here,
     which did two things wrong at once.

     It stretched edge to edge, so a seven-row menu became a full-width panel
     with every value stranded a screen away from its label — it stopped
     reading as a menu hanging off the avatar and started reading as a page.

     And 52px was the height of a topbar that no longer exists: on a phone the
     bar wraps to two rows, so a menu pinned at 52px opens ON TOP of the streak
     and XP line rather than below the bar. Anchoring it to `.acct`, which is
     what the desktop rule already does, tracks the bar at whatever height it
     ends up — no number to keep in sync.

     What a phone does genuinely need is kept: a width that fits the screen, and
     a ceiling with somewhere to scroll. A Pro teacher's menu carries seats, a
     gauge and an upgrade block; without the cap its last row sits below the
     fold with no way to reach it. */
  .menu{width:min(300px,calc(100vw - 20px));
    max-height:calc(100dvh - 108px);overflow-y:auto;overscroll-behavior:contain;
    -webkit-overflow-scrolling:touch;}
  .p-head{padding:8px 12px;gap:8px;}
  .p-title{font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .p-sub{display:none;}
  .p-sub-m{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .p-steps{flex:0 1 auto;min-width:0;max-width:38%;overflow:hidden;}
  .p-steps .step{width:12px;}
  .p-body{padding:18px 18px 8px;}
  .p-pin{padding:5px 18px 6px;}
  .p-foot{padding:10px 18px 14px;}
}
/* The smallest phones (320px): the header's numbers row still fits on one
   line (212). A block of its own, AFTER the phone block closes: build 212
   first put it in the middle of that block, closing it early, and every
   phone rule below the header (one column at a time, the tabs) stopped
   applying above 380px — a 390px phone got the desktop's three columns. */
@media (max-width:380px){
  .topbar{column-gap:7px;}
  .topbar .stat{font-size:11px;gap:4px;}
  .bd-chip{padding:0 8px;font-size:12px;}
}
/* ===================== landing =====================
   The page someone meets before they have an account. It borrows every token
   from the app above it — same paper, same ink, same orange — so signing up
   does not feel like walking into a different building. Nothing here is a
   second design system: it is the app's own vocabulary, given room. */
/* `overflow-x:hidden` here is what stopped the nav below from sticking, and it
   is worth writing down, because the CSS reads as though it should have worked.
   Any overflow value other than `visible` makes the element a scroll container,
   and a `position:sticky` child sticks to its NEAREST scroll container — not to
   the window. `.lp` has no height of its own, so it grows with its content and
   never scrolls: the nav was faithfully sticking to a box that never moved,
   which is indistinguishable from sticky being ignored. The document scrolled
   straight past it. `clip` clips the same overflow WITHOUT creating a scroll
   container, so the nearest scroller goes back to being the viewport. */
/* `--navh` is the height of the bar, which is out of flow — see `.lp-nav`. It
   is written here so the page has the right shape on the very first paint, and
   corrected from the real box by a layout effect in `Landing`, so a wrapped
   button or a longer word in another language can never leave a gap or a
   collision. The padding IS the bar; nothing else compensates for it. */
.lp{--navh:67px;padding-top:var(--navh);
  min-height:100vh;background:var(--paper);color:var(--ink);overflow-x:clip;}
/* Safari before 16 has no `clip`. There the containment moves up to the body,
   whose overflow propagates to the viewport — leaving the body itself a
   non-scroller, so sticky is untouched by the fallback either. */
@supports not (overflow:clip){
  .lp{overflow-x:visible;}
  body{overflow-x:hidden;}
}
.lp a{color:inherit;text-decoration:none;}
.lp-wrap{max-width:1080px;margin:0 auto;padding:0 24px;}
/* The bar gets a compositing layer of its own.

   Without one, iOS Safari paints the sticky bar into the same layer as the
   page it is covering. Through a momentum flick the two go out of step for a
   frame at a time and a sliver of the content ABOVE the current scroll
   position paints on top of the bar — orange fragments of the headline
   appearing inside the header, over and over, which is what "blinking"
   is. It is a paint fault, not a layout one: nothing has moved, the wrong
   layer is simply on top for one frame.

   `translateZ(0)` is what promotes it; `will-change:transform` tells Safari
   to keep it promoted rather than re-flattening it between scrolls, which is
   how the tear comes back after a pause. `backface-visibility:hidden` stops
   the layer being merged back into its parent — `.lp` is `overflow-x:clip`,
   and a clipping ancestor is exactly what invites that merge.

   A transform on a `position:sticky` element does not disturb the sticking:
   the offset is resolved first and the transform is applied to the result. */
/* FIXED, NOT STICKY — AND THAT IS THE END OF A THREE-BUILD ARGUMENT.
   113.9 promoted this bar onto a layer of its own to stop iOS painting the
   page through it. 113.10 then had to paint a strip above it, because a
   promoted layer is placed on whole device pixels while the page scrolls on
   fractional ones and a seam opened along its top edge. And now the third
   symptom of the same promotion: `will-change:transform` on a STICKY element
   takes it off WebKit's compositor path for sticking and puts it back on the
   main thread, so through a fast flick it lags the page and catches up — which
   is the bar bumping.

   Every one of those is a consequence of asking a sticky element to also be a
   layer. A FIXED bar is a layer by definition and is positioned by the
   compositor against the viewport: it is never in the content's layer, so
   there is nothing to tear; it never moves relative to the viewport, so there
   is nothing to round against; and it is never repositioned per frame by the
   main thread, so there is nothing to bump. All three hints come off with it.

   The one thing sticky gave for free was its own height in the flow. That is
   `--navh` on `.lp`, measured from the bar itself. */
.lp-nav{position:fixed;top:0;left:0;right:0;z-index:40;background:var(--nav-bg);
  border-bottom:1px solid var(--line);}
/* The hairline left over after 113.9, and why a layer hint cannot close it.

   Khoa's screenshot, scrolling the landing page on a phone: a line of type
   sliding along the very top edge of the header — about one device pixel of
   it, the bottoms of the letters only, the whole width of the bar. Measured
   off the screenshot it is a single row of pixels sitting exactly on the
   bar's top edge, not a fragment somewhere inside it.

   That is not the 113.9 tear, which was whole words painting over the middle
   of the bar. It is a rounding seam. The bar now has a compositing layer of
   its own — that was the point of 113.9 — and iOS places that layer at whole
   device pixels while the page underneath scrolls at fractional ones. Through
   a flick the two are a fraction of a pixel out of step, and the row of the
   page that should be hidden behind the bar's top edge is drawn just above
   it. Nothing is mispositioned; the layer and the page are simply rounded
   apart, which is why no amount of promoting the bar removes it.

   So the bar paints a strip of its own background ABOVE itself, and the seam
   opens onto that instead of onto the page. `.lp-nav` is the first thing in
   the document, so the strip is off the top of the page at rest and there is
   nothing it could ever cover; in a rubber-band overscroll it shows the same
   colour the page already shows there (`--nav-bg` and `--paper` are the same
   value in both themes). It is inside the bar's own layer, so it travels with
   it, wherever iOS decides to put it.

   116.4 KEPT IT, THOUGH THE SEAM IT WAS FOR CANNOT HAPPEN TO A FIXED BAR.
   It costs nothing — the strip is above the top of the viewport at rest and can
   never cover anything — and it still does one real job: through a rubber-band
   overscroll at the top of the page, a fixed bar stays glued to the viewport
   while the page pulls away beneath it, and the strip is what the gap opens
   onto. Same colour the page shows there anyway. */
.lp-nav::before{content:'';position:absolute;left:0;right:0;bottom:100%;
  height:24px;background:var(--nav-bg);pointer-events:none;}
/* Nothing is behind the bar at the top of the page, so it should not look like
   a bar yet. The shadow arrives with the first scroll and leaves again. */
.lp-nav.is-stuck{box-shadow:0 1px 0 var(--line),0 6px 20px rgba(0,0,0,.06);}
:root[data-theme="dark"] .lp-nav.is-stuck{box-shadow:0 1px 0 var(--line),0 6px 20px rgba(0,0,0,.35);}
/* Now that the bar stays, every in-page link would land its heading underneath
   it. Give anything the nav can jump to a margin the size of the bar. */
.lp [id]{scroll-margin-top:78px;}
.lp-nav-in{max-width:1080px;margin:0 auto;padding:12px 24px;display:flex;align-items:center;gap:14px;}
.lp-nav .brand-name{font-family:var(--serif);font-size:16px;font-weight:600;}
.lp-links{display:flex;gap:20px;margin-left:22px;font-size:13px;color:var(--ink-2);}
.lp-links a:hover{color:var(--accent-text);}
.lp-nav-act{margin-left:auto;display:flex;align-items:center;gap:8px;}
.lp-icobtn{width:32px;height:32px;border-radius:8px;border:1px solid var(--line-2);color:var(--ink-2);
  display:flex;align-items:center;justify-content:center;font-size:14px;background:transparent;}
.lp-icobtn:hover{background:var(--track);color:var(--ink);}
/* VI | EN. One pill with two halves rather than two buttons, so it reads as a
   choice between two states and not as two things you could both press. The
   inactive half is deliberately quiet: for most readers this control is
   already on the right setting and should not compete with "Create account". */
.lp-lang{display:flex;align-items:center;border:1px solid var(--line-2);border-radius:8px;
  overflow:hidden;height:32px;flex:0 0 auto;}
.lp-lang button{font-family:var(--mono);font-size:11px;letter-spacing:.06em;font-weight:500;
  padding:0 9px;height:100%;color:var(--ink-3);background:transparent;transition:background .15s,color .15s;}
.lp-lang button+button{border-left:1px solid var(--line-2);}
.lp-lang button:hover{color:var(--ink);background:var(--track);}
.lp-lang button.on{background:var(--accent-wash);color:var(--accent-text);}

.lp-hero{padding:72px 0 8px;display:grid;grid-template-columns:1.05fr .95fr;gap:48px;align-items:center;}
/* Google Fonts serves IBM Plex Mono without a Vietnamese subset. So a label
   like NGHỀ falls back glyph by glyph, and the tone mark arrives from another
   font at another size — on screen it lands BESIDE the vowel rather than over
   it, and the word looks misspelt. Inter carries Vietnamese properly.
   The small uppercase labels therefore change FACE on the Vietnamese page:
   same size, same tracking, same colour, so the design does not move — only
   the letters become ones the font actually has. Digits and the VI/EN pill
   stay monospaced, having nothing to lose — and so does the hero chip, which
   reads "Custom English · Real Impact" on both pages. */
.lang-vi .lp-kicker,.lang-vi .lp-mock-t,.lang-vi .lp-price-n{font-family:var(--sans);letter-spacing:.08em;}
/* THE SAME BUG, ACROSS THE WHOLE APP. (Build 119.1.)

   Build 113 found it on the landing page: Google Fonts serves IBM Plex Mono
   with no Vietnamese subset, so `NGHỀ` falls back glyph by glyph and the tone
   mark arrives from a different face at a different size — on screen it sits
   BESIDE the vowel instead of over it, and the word looks misspelt. Five
   landing classes were fixed and the app was left alone, because the app had
   no Vietnamese in it. It has now.

   Seventy-seven declarations use `var(--mono)`; these are the ones that can
   hold a Vietnamese word. Everything left out is Latin or digits by
   construction and reads better monospaced: a course code (`.ccode`), a join
   code (`.cls-code`, `.inv-code`, `.code-in`), an invitation URL
   (`.invite-u`), IPA (`.card-ipa`), a short uuid (`.kls-anon`), a percentage
   (`.kls-pct`), a timer, the build string, and the highlighted English word in
   a passage (`.tv` — though its gloss, `.tip`, is Vietnamese and is in).

   Only the FACE changes. Size, tracking and colour are left exactly as they
   are, so this cannot move a box; and every rule is scoped to `.lang-vi`, so
   the English app is untouched by construction rather than by inspection. */
.lang-vi .eyebrow,.lang-vi .chip,.lang-vi .ctag,.lang-vi .cls-tag,.lang-vi .cs-i,
.lang-vi .cls-fold-n,.lang-vi .cls-fold-w,.lang-vi .dash-lvl,.lang-vi .dwho,
.lang-vi .kls-when,.lang-vi .menu-plan,.lang-vi .pinbar-l,.lang-vi .q-kind,
.lang-vi .node,.lang-vi .node-eyebrow,.lang-vi .j-kicker,.lang-vi .j-note,
.lang-vi .j-for,.lang-vi .paywall-t,.lang-vi .pw-foot,.lang-vi .pw-why,
.lang-vi .legend,.lang-vi .locked-note,.lang-vi .soon-tag,.lang-vi .day-n,
.lang-vi .day-min,.lang-vi .stat,.lang-vi .act-m,.lang-vi .recap-tag,
.lang-vi .rep-id-d,.lang-vi .rep-now-k,.lang-vi .rep-stage-n,.lang-vi .rep-stage-p,.lang-vi .rep-l,.lang-vi .rep-sc,.lang-vi .rep-th,
.lang-vi .sec-n,.lang-vi .seg-item,.lang-vi .tour-n,.lang-vi .tset-n,
.lang-vi .tset-tag,.lang-vi .was-tag,.lang-vi .wait,.lang-vi .fold,
.lang-vi .idea,.lang-vi .tree,.lang-vi .cov-row,
.lang-vi .dash-pick,.lang-vi .inv-eyebrow,.lang-vi .tip,
/* `.cnext` — "Chạm lần nữa để xem lộ trình" — was missed by the sweep above
   and found by the check, not by reading the list again. It only exists under
   900px, which is exactly why it was easy to overlook. */
.lang-vi .cnext{font-family:var(--sans);}
.lp-eyebrow{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--accent-text);background:var(--accent-wash);border:1px solid var(--accent);
  border-radius:20px;padding:4px 11px;display:inline-block;}
.lp-h1{font-family:var(--serif);font-size:48px;line-height:1.1;letter-spacing:-.02em;
  font-weight:600;margin:18px 0 0;text-wrap:balance;}
.lp-h1 em{font-style:normal;color:var(--accent-deep);}
.lp-vi{font-family:var(--serif);font-size:19px;line-height:1.4;color:var(--ink-2);margin-top:14px;font-style:italic;}
.lp-lede{font-size:15.5px;line-height:1.62;color:var(--ink-2);margin-top:16px;max-width:52ch;}
.lp-cta{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:26px;}
.lp-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:12px 20px;
  border-radius:10px;font-size:14px;font-weight:500;background:var(--accent);color:var(--on-accent);
  white-space:nowrap;transition:transform .15s,background .15s;}
.lp-btn:hover{background:var(--accent-deep);transform:translateY(-1px);}
.lp-btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--line-2);}
.lp-btn.ghost:hover{background:var(--track);}
.lp-fine{font-size:12.5px;color:var(--ink-3);margin-top:12px;}
.lp-trust{display:flex;flex-wrap:wrap;gap:18px;margin-top:30px;font-size:12.5px;color:var(--ink-3);}
.lp-trust b{color:var(--ink);font-weight:600;}

/* The picture is the product: one stop on the route, opened. */
.lp-mock{background:var(--card);border:1px solid var(--line);border-radius:18px;
  box-shadow:0 18px 50px rgba(20,20,19,.10);padding:16px;position:relative;}
.lp-mock-bar{display:flex;align-items:center;gap:7px;padding-bottom:12px;border-bottom:1px solid var(--line);}
.lp-dot{width:9px;height:9px;border-radius:50%;background:var(--line-2);}
.lp-mock-t{font-family:var(--mono);font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3);margin-left:6px;}

.lp-sec{padding:70px 0 0;}
.lp-kicker{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);}
.lp-h2{font-family:var(--serif);font-size:31px;line-height:1.18;font-weight:600;margin:9px 0 0;letter-spacing:-.01em;}
.lp-sub{font-size:14.5px;line-height:1.6;color:var(--ink-2);margin-top:11px;max-width:60ch;}

.lp-seg{display:inline-flex;background:var(--sunk);border-radius:10px;padding:3px;margin-top:20px;}
.lp-seg button{padding:8px 18px;border-radius:8px;font-size:13.5px;color:var(--ink-2);background:transparent;}
.lp-seg button.on{background:var(--card);color:var(--ink);box-shadow:var(--shadow);font-weight:500;}
.lp-prices{display:grid;grid-template-columns:repeat(2,1fr);gap:18px;margin-top:24px;max-width:760px;}
.lp-price{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:24px;
  display:flex;flex-direction:column;box-shadow:var(--shadow);}
.lp-price.best{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-wash),var(--shadow);}
.lp-price-n{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);}
.lp-price.best .lp-price-n{color:var(--accent-text);}
.lp-amt{font-family:var(--serif);font-size:34px;font-weight:600;margin-top:10px;letter-spacing:-.02em;}
.lp-per{font-size:13px;color:var(--ink-3);margin-top:2px;}
.lp-feat{list-style:none;margin:18px 0 22px;display:flex;flex-direction:column;gap:9px;}
.lp-feat li{position:relative;padding-left:22px;font-size:13.4px;line-height:1.5;color:var(--ink-2);}
.lp-feat li:before{content:'✓';position:absolute;left:0;top:1px;width:14px;text-align:center;
  color:var(--accent-text);font-size:12.5px;font-weight:600;}
.lp-feat li.no{color:var(--ink-3);}
.lp-feat li.no:before{content:'✕';color:var(--lock);font-size:11px;font-weight:400;}
.lp-price .lp-btn{margin-top:auto;width:100%;}
.lp-note{font-size:12.5px;color:var(--ink-3);margin-top:16px;}

.lp-faq{margin-top:26px;display:grid;grid-template-columns:repeat(2,1fr);gap:18px 34px;}
/* 263: the referral program on the landing page (LpAffiliate, 96-landing.js) */
.lp-aff-soon{margin-left:10px;padding:2px 9px;border-radius:99px;background:var(--accent-wash);color:var(--accent-text);letter-spacing:.04em;}
.lp-aff-two{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:26px;}
.lp-aff-c{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:20px;display:grid;gap:8px;align-content:start;min-width:0;}
.lp-aff-c h3{font-family:var(--serif);font-size:19px;font-weight:600;margin:0;}
.lp-aff-c p{font-size:13.4px;line-height:1.58;color:var(--ink-2);margin:0;}
.lp-aff-big{display:flex;gap:28px;flex-wrap:wrap;margin-top:4px;}
.lp-aff-big>div{display:grid;gap:2px;}
.lp-aff-big b{font-family:var(--serif);font-size:30px;line-height:1.1;color:var(--accent-text);font-variant-numeric:tabular-nums;}
.lp-aff-big span{font-size:12.5px;color:var(--ink-3);}
.lp-aff-c .lp-aff-who{font-size:12.5px;color:var(--ink-3);}
.lp-aff-tk{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:40px;}
.lp-aff-h3{font-family:var(--serif);font-size:23px;font-weight:600;margin:6px 0 0;line-height:1.2;}
.lp-aff-tiers{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-top:18px;}
.lp-aff-tiers>div{display:grid;gap:2px;padding:14px;border:1px solid var(--line);border-top:4px solid var(--tc);border-radius:12px;background:var(--card);min-width:0;}
.lp-aff-tiers .t0{--tc:#A0673A;}.lp-aff-tiers .t1{--tc:#7D8794;}.lp-aff-tiers .t2{--tc:var(--gold);}.lp-aff-tiers .t3{--tc:#3E7FB8;}
.lp-aff-tiers b{font-family:var(--serif);font-size:30px;line-height:1.1;color:var(--tc);}
.lp-aff-tiers span{font-size:12.5px;color:var(--ink-3);}
.lp-aff-tiers .lp-aff-tn{font-weight:600;font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink);}
.lp-aff-tiers>div.on{box-shadow:inset 0 0 0 2px var(--tc);}
.lp-aff-calc{display:grid;grid-template-columns:1.2fr 1fr;gap:16px;margin-top:18px;}
.lp-aff-in{display:grid;gap:14px;background:var(--card);border:1px solid var(--line);border-radius:14px;padding:20px;min-width:0;}
.lp-aff-sl{display:grid;gap:6px;font-size:13.4px;color:var(--ink-2);}
.lp-aff-slt{display:flex;justify-content:space-between;gap:10px;}
.lp-aff-slt b{color:var(--ink);font-variant-numeric:tabular-nums;}
.lp-aff-sl input[type=range]{width:100%;accent-color:var(--accent);}
.lp-aff-tot{display:grid;gap:6px;align-content:center;padding:22px;border-radius:14px;background:var(--ink);color:var(--paper);min-width:0;}
.lp-aff-tot>span{font-size:13px;opacity:.75;}
.lp-aff-tot>b{font-family:var(--serif);font-size:36px;line-height:1.1;font-variant-numeric:tabular-nums;overflow-wrap:anywhere;}
.lp-aff-tot>div{display:flex;justify-content:space-between;gap:10px;font-size:13px;opacity:.85;font-variant-numeric:tabular-nums;}
.lp-aff-tot small{font-size:11.5px;opacity:.7;margin-top:6px;line-height:1.5;}
.lp-aff-steps{list-style:none;margin:18px 0 0;padding:0;display:grid;grid-template-columns:repeat(3,1fr);gap:16px;counter-reset:aff;}
.lp-aff-steps li{display:grid;gap:4px;background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px;counter-increment:aff;}
.lp-aff-steps li::before{content:counter(aff);display:grid;place-items:center;width:26px;height:26px;border-radius:50%;background:var(--accent-wash);color:var(--accent-text);font-weight:700;font-size:13px;}
.lp-aff-steps b{font-family:var(--serif);font-size:16px;font-weight:600;}
.lp-aff-steps span{font-size:13.4px;line-height:1.55;color:var(--ink-2);}
.lp-faq h3{font-family:var(--serif);font-size:16px;font-weight:600;margin-bottom:5px;}
.lp-faq p{font-size:13.4px;line-height:1.58;color:var(--ink-2);}
.lp-final{margin:74px 0 0;background:var(--hero);border:1px solid var(--line);border-radius:20px;
  padding:44px;text-align:center;}
.lp-final .lp-h2{margin-bottom:8px;}
.lp-final .lp-cta{justify-content:center;}
.lp-foot{margin-top:56px;border-top:1px solid var(--line);padding:22px 0 40px;display:flex;
  flex-wrap:wrap;gap:14px;align-items:center;font-size:12.5px;color:var(--ink-3);}
.lp-foot a:hover{color:var(--accent-text);}
.lp-foot .sp{margin-left:auto;}
@media (max-width:600px){
  /* Two full-width buttons in a phone-width bar wrap into four lines, so the
     bar keeps one. SIGN IN, NOT SIGN UP (build 178, Khoa): a learner coming
     back on a phone found only "Tạo tài khoản" up here and no way into their
     account short of the foot of a long page. A newcomer has the page's own
     buttons ("Học thử miễn phí", the closing "Tạo tài khoản miễn phí"), so
     the bar's one button is Sign in, filled in the accent so it is seen. */
  .lp-nav-in{padding:10px 16px;gap:9px;}
  /* The bar is shorter here, so the space kept for it is too. This is only the
     first-paint value; the layout effect measures the real one. */
  .lp{--navh:54px;}
  .lp-nav .brand-name{font-size:14.5px;}
  .lp-nav-act{gap:6px;}
  .lp-nav-act .lp-btn{padding:9px 13px;font-size:13px;}
  .lp-nav-act .lp-nav-up-btn{display:none;}
  .lp-nav-act .lp-btn.lp-nav-in-btn{background:var(--accent);color:var(--on-accent);border-color:transparent;}
  .lp-nav-act .lp-btn.lp-nav-in-btn:hover{background:var(--accent-deep);}
  /* On a 390px bar the language pill has to keep its place — it is the one
     control that decides whether the page is readable at all — so it shrinks
     rather than disappearing, and the theme toggle is what gives up its
     border to make room. */
  .lp-lang{height:30px;}
  .lp-lang button{padding:0 7px;font-size:10.5px;}
  .lp-icobtn{width:30px;height:30px;border-color:transparent;}
  .lp-wrap{padding:0 18px;}
  .lp-eyebrow{font-size:10px;}
  .lp-h1{font-size:33px;}
  /* Dialogs on a phone. 20px of scrim padding either side of a 390px screen is
     10% of the width spent on nothing, and the plan cards are built for a
     column three times this wide — so the chrome comes in and the cards lose
     the padding they no longer need. */
  .auth-scrim{padding:12px;}
  .auth-box{padding:20px 16px 18px;}
  .auth-box.is-plans{max-height:calc(100dvh - 24px);}
  .bill-scroll{padding:12px 0 20px;}   /* the box's own top padding, here too */
  .auth-box.is-plans .lp-prices{gap:12px;}
  .auth-box.is-plans .lp-price{padding:18px 16px;}
  .auth-box.is-plans .lp-amt{font-size:29px;margin-top:8px;}
  .auth-box.is-plans .lp-feat{margin:14px 0 18px;gap:8px;}
  .auth-box.is-plans .lp-feat li{font-size:13px;}
  /* Side by side, Free-then-Pro reads left to right and Pro is the one your eye
     lands on last. Stacked, that order buries the thing being sold under a
     screenful of the plan you already have. Pro goes first on a phone. */
  .auth-box.is-plans .lp-price.best{order:-1;}
  /* Four stat cells across 390px is 90px each, which is not enough for a serif
     19px number and its label underneath. Two by two instead — and since 160
     there are five, so the first (Learn) takes the whole top row. */
  .rep-grid{grid-template-columns:repeat(2,1fr);}
  .rep-cell:first-child{grid-column:1/-1;}

  .rep-big{font-size:38px;}
  .rep-stage-b{flex:0 0 56px;}
}
@media (max-width:900px){

  

  

  

  

  .lp-hero{grid-template-columns:1fr;gap:34px;padding-top:44px;}
  .lp-h1{font-size:38px;}
  .lp-prices,.lp-faq{grid-template-columns:1fr;}

  .lp-links{display:none;}
  .lp-final{padding:30px 20px;}

  .lp-aff-two,.lp-aff-calc,.lp-aff-steps{grid-template-columns:1fr;}
  .lp-aff-tiers{grid-template-columns:repeat(2,minmax(0,1fr));}
}
/* ===================== the boot screen =====================
   Three CDN scripts, a font stylesheet, an in-browser Babel pass over 6,000
   lines, and then a round trip to Supabase to find out who is here: that is
   between half a second and several on a phone, and the page used to spend it
   showing whatever happened to be ready. Worse, the app's own three columns
   render while `authReady` is still false, so a signed-out visitor got a
   flash of the inside of the product before the landing page replaced it.

   So: paint one deliberate screen immediately, keep it up until the app knows
   which side of the door the visitor is on, then cross-fade. The markup sits
   in the document rather than in React — it has to be on screen before React
   exists — and it uses the theme variables, which the inline script above has
   already set on <html>, so the splash is never the wrong colour. */
#boot{position:fixed;inset:0;z-index:9999;background:var(--paper);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;
  opacity:1;transition:opacity .42s ease;}
#boot.off{opacity:0;pointer-events:none;}
.boot-mark{width:52px;height:52px;object-fit:contain;display:block;
  animation:bootBreathe 2.4s ease-in-out infinite;}
/* If the logo has not arrived from the bucket yet, the letter stands in, so the
   screen is never an empty rectangle on a cold cache. */
.boot-mark.gone{display:none;}
.boot-fallback{display:none;width:52px;height:52px;border-radius:14px;background:var(--accent);
  color:var(--on-accent);font-family:var(--serif);font-size:26px;font-weight:600;
  align-items:center;justify-content:center;animation:bootBreathe 2.4s ease-in-out infinite;}
.boot-mark.gone+.boot-fallback{display:flex;}
.boot-name{font-family:var(--serif);font-size:19px;font-weight:600;letter-spacing:-.01em;color:var(--ink);}
/* Indeterminate, because there is no honest percentage to report. It says
   "something is happening", which is the only claim we can actually make. */
.boot-bar{position:relative;width:132px;height:2px;border-radius:2px;background:var(--track);overflow:hidden;}
.boot-bar i{position:absolute;top:0;left:0;height:100%;width:40%;border-radius:2px;
  background:var(--accent);animation:bootSlide 1.25s ease-in-out infinite;}
/* Only after a wait long enough to worry about. A spinner that never explains
   itself is how a broken CDN looks exactly like a slow one. */
.boot-slow{font-size:12.5px;color:var(--ink-3);opacity:0;transition:opacity .4s ease;
  max-width:min(320px,80vw);text-align:center;line-height:1.5;}
#boot.slow .boot-slow{opacity:1;}
@keyframes bootBreathe{0%,100%{opacity:.55;transform:scale(.97);}50%{opacity:1;transform:scale(1);}}
@keyframes bootSlide{0%{left:-40%;}100%{left:100%;}}
/* The same bar, alone, for the moment between leaving the landing page and the
   second half of the app arriving — see `useStage2`. Usually never seen: the
   second half compiles while the landing page is being read. */
.stage-wait{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--paper);}
/* What the splash hands over to. One short fade so the app or the landing page
   arrives rather than appears. */
.boot-in{animation:bootIn .42s ease both;}
@keyframes bootIn{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:none;}}
/* THE LANDING PAGE FADES; IT DOES NOT RISE.
   An element running a transform animation is a containing block for its
   `position:fixed` descendants — and `animation-fill-mode: both` keeps it one
   long after the animation has ended, because the property is still
   animation-controlled. `.lp` carries `boot-in`, so the "fixed" header was
   fixed to the page rather than to the window and scrolled away with it,
   `overflow-x:clip` cutting it off on the way. Measured: the bar sat at -800
   after an 800px scroll with the class on, and at 0 with it off.

   Four pixels of rise are not worth a header. The app keeps them — nothing
   inside it is fixed to the window that is not already the size of it. */
.lp.boot-in{animation-name:bootFade;}
@keyframes bootFade{from{opacity:0;}to{opacity:1;}}

/* ---- the screen a crash gets instead of nothing (build 140) ----
   It uses the theme variables and nothing else, because the one thing that is
   certainly still working when this draws is the stylesheet. */
.boom{position:fixed;inset:0;z-index:9998;background:var(--paper);
  display:flex;align-items:center;justify-content:center;padding:20px;}
.boom-card{max-width:440px;text-align:center;display:flex;flex-direction:column;
  align-items:center;gap:12px;}
.boom-t{font-family:var(--serif);font-size:20px;font-weight:600;color:var(--ink);}
.boom-s{font-size:13px;line-height:1.6;color:var(--ink-2);}
.boom-e{font-size:11px;line-height:1.5;color:var(--ink-3);opacity:.7;
  word-break:break-word;max-width:100%;}

@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important;}}

/* ---- typing on a phone (build 135) ----
   iOS Safari zooms the page in whenever a control it is focusing sets its text
   below 16px, and it does not zoom back out afterwards. The layout is then
   wider than the window, so the right-hand end of every row — the Check
   button, the Join button, the answer field's own tail — is off screen, and a
   learner who has just tapped into a gap-fill is looking at a page that will
   not let them finish it. It reads as the app being stuck, and two of the ten
   reports in this build are that one behaviour seen twice.

   It is not our layout: it is the browser's accessibility behaviour, and the
   only two ways out of it are to ban pinch-zoom, which we will not do to
   anybody, or to set the text to 16px. So every control that takes typing is
   16px wherever the pointer is coarse, and the desktop keeps its own
   typography untouched.

   `.field.code-in` is written with both classes on purpose. `.code-in` sets
   17px at line 572 and `.field` sets 13px at line 749; they are one class each
   and the later one wins, so the invitation code has been rendering at 13px
   since it was written — the one field in the app most likely to be typed on a
   phone, and the one this build was shown a screenshot of. Two rules, one
   class each: the gotcha this project has already written down twice. */
@media (pointer:coarse){
  input,textarea,select,.field,.gapin{font-size:16px;}
  .rep-box textarea,.admin select{font-size:16px;}
}
.field.code-in{font-size:17px;}
/* A field that cannot be narrower than its own minimum pushes the row it is in
   past the margin. Every one of them may shrink; the line they sit on wraps. */
.gapin{max-width:100%;}
.gapline{overflow-wrap:break-word;}

/* ---- the mouth, in cross-section (build 135) ----
   A Basic learner is being asked to make a sound their own language does not
   have. "Tongue high and forward" is a sentence about a thing they cannot see;
   the picture is the explanation, and the words underneath it are the caption.
   Two per row on a desktop, one on a phone, and the whole block is skipped at
   Intermediate and above where the IPA alone is enough. */
.phon-row{display:flex;flex-wrap:wrap;gap:12px;margin:12px 0 4px;}
/* 300px, not 210px (136): an annotated drawing needs the room, and two cards
   squeezed to 210 put the six labels at 7px. Above ~600px of column the pair
   still stands side by side; below it they stack full width, which is more
   diagram than either of them had before. */
.phon{flex:1 1 300px;min-width:0;border:1px solid var(--line);border-radius:var(--r);
  background:var(--card);padding:12px 14px 11px;}
.phon-h{display:flex;align-items:baseline;gap:9px;margin-bottom:8px;}
.phon-sym{font-family:var(--mono);font-size:19px;color:var(--accent-text);letter-spacing:.02em;}
.phon-len{font-family:var(--mono);font-size:10px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--ink-3);}
/* The cross-section is as wide as the card it is in (136): six labels sit in
   its margins, and they are only labels if they can be read. `width:100%` on
   the svg with a viewBox and no width attribute is what makes the drawing —
   and the type inside it, which scales with the user units — grow with the
   card instead of standing at 150px in the middle of it. */
.mouth-svg{display:block;width:100%;height:auto;}
.mouth-lead{fill:none;stroke:var(--line-2);stroke-width:1;}
.mouth-lab{font-family:var(--sans);font-size:8.6px;fill:var(--ink-3);}
.mouth-lab.is-key{fill:var(--accent-text);font-weight:600;}
.mouth-dot{fill:var(--accent);}
.phon-lips{margin-top:10px;border-top:1px solid var(--line);padding-top:9px;}
.phon-lips-h{font-family:var(--sans);font-size:10px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink-3);}
.phon-lips-b{display:flex;align-items:center;gap:13px;margin-top:7px;}
.phon-lips-b svg{display:block;flex:0 0 auto;}
.phon-lips-b b{display:block;font-size:13px;font-weight:600;color:var(--ink-2);}
.phon-lips-b span{display:block;font-size:12.5px;color:var(--ink-3);margin-top:2px;}
.mouth-hard{fill:none;stroke:var(--ink-3);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.mouth-soft{fill:none;stroke:var(--line-2);stroke-width:1.6;stroke-linecap:round;}
.mouth-teeth{fill:var(--card);stroke:var(--ink-3);stroke-width:1.3;}
.mouth-lips{fill:none;stroke:var(--ink-2);stroke-width:2.4;stroke-linecap:round;}
.mouth-tongue{fill:var(--accent-wash);stroke:var(--accent);stroke-width:1.8;stroke-linejoin:round;}
.mouth-move{fill:none;stroke:var(--accent);stroke-width:1.5;stroke-dasharray:3.5 3;opacity:.75;}
.mouth-spot{fill:var(--accent);}
.mouth-lip{fill:var(--accent-wash);stroke:var(--ink-2);stroke-width:1.6;}
.mouth-hole{fill:var(--ink);opacity:.85;}
.phon-fig{margin:0;text-align:center;}
.phon-fig figcaption{font-family:var(--sans);font-size:10px;letter-spacing:.04em;
  text-transform:uppercase;color:var(--ink-3);margin-top:2px;}
.phon-key{font-family:var(--sans);font-size:11.5px;color:var(--ink-3);margin-top:5px;}
.phon-lines{margin-top:9px;}
.phon-line{display:flex;gap:7px;font-size:12.5px;line-height:1.5;color:var(--ink-2);padding:2px 0;}
/* var(--sans), NOT var(--mono): "chỗ" and "lưỡi" are the two labels here and
   IBM Plex Mono has no Vietnamese subset, so the tone marks arrive from a
   different font at a different size. The rule this project already wrote down
   for `.lang-vi`, met again in a place that is Vietnamese at every app
   language because the COURSE's level put it there. */
.phon-line b{font-family:var(--sans);font-size:10px;letter-spacing:.06em;text-transform:uppercase;
  font-weight:600;color:var(--ink-3);flex:0 0 40px;padding-top:2px;}
.phon-vi{font-size:12.5px;line-height:1.5;color:var(--accent-deep);margin-top:7px;
  border-top:1px solid var(--line);padding-top:7px;}

/* ---- a rule line that carries more than one mapping (build 135) ----
   "i, y → /ɪ/ ee, ea, ie, e_e → /iː/" is two rules printed as one run, and the
   place they meet — the answer of the first sitting against the question of
   the second — is exactly where the eye needs a gap and does not get one. */
.pat-groups{display:flex;flex-wrap:wrap;justify-content:center;align-items:baseline;
  gap:10px 30px;}
.pat-g{display:inline-flex;align-items:baseline;gap:9px;white-space:nowrap;}
.pat-g .pat-ar{color:var(--accent);font-family:var(--mono);}
@media (max-width:560px){.pat-groups{flex-direction:column;align-items:center;gap:8px;}
  .pat-g+.pat-g{border-top:1px solid var(--line);padding-top:8px;}}

/* ---- the learner report on paper ----
   LAST IN THE FILE ON PURPOSE. Ctrl-P over a full-screen overlay prints a
   blank sheet unless it is told otherwise — `.scrim` is `position:fixed` and
   clipped to one viewport, so the printer is handed one screen of it and
   nothing else. Here the overlay becomes an ordinary block that flows over as
   many pages as it needs, and the app behind it is not printed at all.

   It has to sit below every rule it overrides: `.app>*{display:none}` and the
   `display:flex` on `.cols` and `.mobile-tabs` have the same specificity, and
   at equal specificity the later rule wins. Written halfway up the stylesheet
   this block silently printed the shelf on page 1 and the report on page 2.

   Backgrounds are asked for explicitly because browsers drop them on paper by
   default, which would turn every progress bar into an empty outline — the one
   mark on the page a teacher is printing it for. */
@media print{
  html,body{background:#fff;height:auto;overflow:visible;}
  .app{display:block;height:auto;overflow:visible;}
  .app>*{display:none;}
  /* Build 131 took four lines out of here — `.app>.scrim`, `.report`,
     `.rep-head` and `.rep-body`. The learner report is `.apop` now, portalled
     to `body`, so the block below covers it and these covered nothing but the
     activity player, which nobody prints. */
  .rep-in{max-width:none;}
  .rep-id{display:block;}                    /* who it is about, on every printout */
  .rep-bar,.rep-bar i,.rep-stage-b,.rep-stage-b i,.rep-grid,.rep-cell,.rep-now,.kls-bar,.kls-bar i,
  .rep-th,.rep-grp-h{
    -webkit-print-color-adjust:exact;print-color-adjust:exact;}
  .rep-now,.rep-grid,.rep-stage,.rep-tr{break-inside:avoid;}
  /* A stage's heading never ends a page alone (build 160). */
  .rep-grp-h{break-after:avoid;}
  .rep-print{display:none;}
  /* THE ADMIN DRILL-DOWN ON PAPER. (Build 127.)
     It is portalled to `body` rather than into `.app` — a `position:fixed`
     child of an element with a transform animation is not fixed to the window,
     and `.app` carries `boot-in` — so it is NOT covered by `.app>*` above and
     needs its own two lines or Ctrl-P over a list of two hundred accounts
     prints one screen of it. Same treatment, same reasons. */
  body>.scrim,body>.scrim.apop-scrim{display:block;position:static;height:auto;
    background:#fff;animation:none;padding:0;}
  .apop{width:auto;height:auto;overflow:visible;animation:none;
    border:none;border-radius:0;box-shadow:none;}
  .apop-head{position:static;}
  .apop-head .p-close{display:none;}
  .apop-body{overflow:visible;height:auto;padding:18px 16px 0;}
  .apop-in{max-width:none;}
  .apop-tools{display:none;}                 /* a search box is not a thing on paper */
  /* PAPER IS WHITE IN BOTH THEMES. (Build 160.) A teacher in dark mode who
     printed a report got the dark theme's near-white ink on white paper, and
     the stat boxes and table heads — which are asked to print their
     backgrounds, above — as black slabs. The window takes the light theme's
     colours for the printer, whatever the screen is set to. */
  :root[data-theme="dark"] .apop{color-scheme:light;
    --paper:#FCFCFB;--panel:#F6F6F4;--card:#FFFFFF;--card-2:#F6F6F4;
    --ink:#10233B;--ink-2:#3C4A5B;--ink-3:#566272;--line:#E6E4DF;--line-2:#CFCCC5;
    --accent:#D97757;--accent-deep:#B85C3E;--accent-wash:#F6E7DF;--accent-text:#A85234;
    --done:#7C8F63;--done-wash:#E8EDE0;--wrong:#B4553A;--sunk:#EDECE8;--track:#EDECE8;--read-ink:#1F2A37;}
  @page{margin:14mm;}
}

/* ===================== the header: a slim navy bar (builds 147.1, 147.2) =====================
   The TOP BAR wears the navy of the lesson pages' masthead (#061C37) with the
   same terracotta rule under it, in both themes: it is the brand, not the
   theme. 147.1 painted the column heads and their progress bands navy too —
   three stacked navy rows, a fifth of a laptop screen, and a slab that read
   like the top of a spreadsheet. 147.2 keeps only the one bar. Each column's
   name and its progress now sit on the page colour as one small block: the
   name in Playfair, a thin bar, a caption in the text face.

   COLOURED BY NAME, NOT BY RE-POINTING THE THEME. Redefining --paper, --ink
   and the rest on `.topbar` would reach the account menu and the bell's panel
   too, because both are drawn inside the top bar. So each piece of the bar is
   named here, and the menus keep the theme they had. */
:root{
  --hdr:#061C37;--hdr-2:#10304F;
  --hdr-ink:#FFFFFF;--hdr-ink-2:#C9D3E0;--hdr-ink-3:#93A3B8;
  --hdr-line:rgba(255,255,255,.12);--hdr-soft:rgba(255,255,255,.08);
}
/* The school site's own header (147.4): near-black at both edges, deep blue in
   the middle — sampled off english2impact.odoo.com, #00040C · #00122F ·
   #001A44 · #00122F · #00040D. */
.topbar{background:var(--hdr);
  background:linear-gradient(90deg,#00040C 0%,#00122F 30%,#001A44 50%,#00122F 72%,#00040D 100%);
  border-bottom:3px solid var(--accent);}
.topbar .brand-name{color:var(--hdr-ink);}
.topbar .brand-sub{color:var(--hdr-ink-3);}
.topbar .brand-home:hover{background:var(--hdr-soft);}
.topbar .stat{color:var(--hdr-ink-2);}
.topbar .stat b{color:var(--hdr-ink);}
.topbar .stat-div{background:var(--hdr-line);}
.topbar .bell{color:var(--hdr-ink-2);}
.topbar .bell:hover{background:var(--hdr-soft);color:var(--hdr-ink);}
.topbar .bell-dot{border-color:var(--hdr);}
.topbar .avatar{background:var(--hdr-2);border-color:rgba(255,255,255,.22);color:var(--hdr-ink);}

/* ---- build 211: the class board ----
   The strip takes the top bar's spacer: two slides (the top three; your
   place) that trade every seven seconds with a short rise, never a scroll.
   Too narrow for its label, it drops it; on a phone it is one chip on the
   stats row. The popup is the app's ordinary dialog (.auth-box). */
.bd-strip{flex:1;min-width:0;display:flex;align-items:center;justify-content:center;gap:12px;
  height:38px;padding:0 14px;border:0;border-radius:19px;background:none;cursor:pointer;
  font:inherit;font-size:13px;color:var(--hdr-ink-2);white-space:nowrap;overflow:hidden;}
.bd-strip:hover{background:var(--hdr-soft);}
.bd-strip:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
.bd-k{flex:none;font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--hdr-ink-3);}
.bd-slide{display:flex;gap:18px;min-width:0;overflow:hidden;animation:bdIn .45s ease-out;}
.bd-it{min-width:0;overflow:hidden;text-overflow:ellipsis;}
.bd-it b{font-weight:600;color:var(--hdr-ink);}
@keyframes bdIn{from{opacity:0;transform:translateY(5px);}to{opacity:1;transform:none;}}
@media (prefers-reduced-motion:reduce){.bd-slide{animation:none;}}
@media (max-width:1180px){.bd-k{display:none;}}
.bd-chip{display:none;}
@media (max-width:900px){
  .bd-strip{display:none;}
  .bd-chip{display:inline-flex;align-items:center;height:28px;padding:0 11px;border-radius:14px;
    border:1px solid var(--hdr-line);background:var(--hdr-soft);color:var(--hdr-ink);
    font:inherit;font-size:12.5px;font-weight:600;cursor:pointer;}
}
.bd-box{max-width:460px;}
.bd-sub{margin:6px 0 12px;font-size:13px;color:var(--ink-2);}
.bd-sub b{color:var(--ink);}
.bd-pick{font:inherit;font-size:13px;font-weight:600;color:var(--ink);background:var(--card);
  border:1px solid var(--line-2);border-radius:7px;padding:3px 6px;}
.bd-list{list-style:none;margin:0;padding:0;border:1px solid var(--line);border-radius:10px;
  max-height:min(46vh,420px);overflow-y:auto;overscroll-behavior:contain;}
.bd-row{display:grid;grid-template-columns:32px minmax(0,1fr) auto 64px;align-items:center;gap:8px;
  padding:9px 12px;border-top:1px solid var(--line);font-size:14px;}
.bd-list>li:first-child{border-top:0;}
.bd-row.me{background:var(--accent-wash);}
.bd-rk{text-align:center;font-family:var(--mono);font-size:13px;color:var(--ink-3);}
.bd-nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;color:var(--ink);}
.bd-nm em{font-style:normal;font-weight:400;color:var(--accent-text);}
.bd-st{font-size:12.5px;color:var(--ink-2);}
.bd-xp{text-align:right;font-family:var(--mono);font-size:13px;color:var(--ink);}
.bd-row.zero .bd-nm{font-weight:400;color:var(--ink-2);}
.bd-row.zero .bd-xp{color:var(--ink-3);}
.bd-gap{padding:7px 12px;border-top:1px solid var(--line);background:var(--paper);
  font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);}
.bd-how{margin-top:14px;font-size:12.5px;line-height:1.5;color:var(--ink-2);}
.bd-how b{color:var(--ink);}
.bd-how ul{margin:5px 0 0;padding-left:18px;}
.bd-how li{margin:2px 0;}
.bd-me{margin-top:14px;padding-top:12px;border-top:1px solid var(--line);}
.bd-lbl{font-size:12.5px;font-weight:600;color:var(--ink-2);}
.bd-name-row{display:flex;gap:8px;margin-top:6px;}
.bd-name-row .field{flex:1;min-width:0;}
.bd-hide{display:flex;align-items:flex-start;gap:8px;margin-top:10px;font-size:13px;color:var(--ink-2);cursor:pointer;}
.bd-hide input{margin-top:2px;accent-color:var(--accent);}

/* The column's name and its band: one block, no rule between them. */
.col-head{padding:14px 16px 0;border-bottom:0;}
.col-title{font-size:17px;font-weight:700;letter-spacing:-.005em;}
@media (min-width:901px){.col-bar{height:46px;}}   /* phones keep 52: `.col-err` and scroll-padding pin to it */
.col-bar .bar{height:4px;}
.pinbar-l{font-family:var(--sans);font-size:11.5px;letter-spacing:0;margin-top:6px;}

/* FEWER BOXES. Every block had a hairline round it, so nothing stood out.
   The stages of a course and the course cards lift off the page on a soft
   shadow instead; a border stays only where it means something — the course
   that is selected, the pace that is chosen. The small print that was set in
   the monospace face goes back to the text face: mono stays for the short
   capitalised labels (3 STAGES · 16 LESSONS), which is how the lesson pages
   use it. */
.l-stage{border:0;box-shadow:var(--shadow);}
/* A card's own head (--card-2): in dark, between the card and the page — set apart from its
   rows without sinking below the page the way the inset colour did. */
.l-stage-h{background:var(--card-2);border-bottom:1px solid var(--line);}
/* A button since build 162 (it takes Practice to the stage), drawn exactly
   as the heading it was: the browser's button face, border and centring off. */
button.l-stage-h{width:100%;border:0;border-bottom:1px solid var(--line);margin:0;font:inherit;color:inherit;
  text-align:left;cursor:pointer;}
button.l-stage-h:hover .l-stage-t{color:var(--accent-text);}
button.l-stage-h:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;}
/* Room above a Practice stop brought to the top of its column. */
.node{scroll-margin-top:18px;}
.l-stage-n,.j-note{font-family:var(--sans);font-size:12px;letter-spacing:0;}
.kcard{border-color:transparent;box-shadow:var(--shadow);}
.kcard:hover{border-color:var(--line-2);}
.kcard.on{border-color:var(--accent);}
.station{border-color:transparent;}
.summary{border:0;box-shadow:var(--shadow);}
.pace{border-color:transparent;box-shadow:var(--shadow);}
.pace.on{border-color:var(--accent);}
.l-today{border-color:transparent;box-shadow:var(--shadow);}
.l-today.met{border-color:var(--done);}

/* Playfair Display draws old-style figures by default, so "English Reflex 1"
   read "English Reflex ı" and the pace buttons' 30/60/90 bobbed up and down.
   Lining figures everywhere: numbers here are data, not decoration. */
body,button,input,select,textarea{font-variant-numeric:lining-nums;}   /* buttons get the browser's own font settings, not the body's */
/* The drag handles were solid 5px bars — a grey stripe in light mode, a dark
   gutter in dark. Still 5px wide to grab, but drawn as one hairline in the
   middle that turns terracotta under the pointer. */
.handle{background:linear-gradient(90deg,transparent 2px,var(--line) 2px,var(--line) 3px,transparent 3px);}
.handle:hover{background:var(--accent);}

.lp-pts{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:16px;}
.lp-pts li{display:flex;flex-direction:column;gap:3px;padding-left:18px;position:relative;}
.lp-pts li::before{content:'';position:absolute;left:0;top:8px;width:7px;height:7px;border-radius:50%;background:var(--accent);}
.lp-pts b{font-size:15px;font-weight:600;color:var(--ink);display:flex;align-items:center;flex-wrap:wrap;gap:8px;}
.lp-pts span{font-size:14px;color:var(--ink-2);line-height:1.55;}
.lp-soon{font-size:10.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  padding:2px 8px;border-radius:20px;border:1px solid var(--accent);color:var(--accent-text);background:var(--accent-wash);}

/* ---- build 148: sessions opened by a class's teacher ---- */
/* On the class row itself (148): "Now S01/24 − Open S02", then the ⋯. */
.kls-open{display:inline-flex;align-items:center;gap:5px;flex:0 0 auto;font-size:12px;color:var(--ink-2);}
.kls-open-t{font-family:var(--mono);font-size:11.5px;color:var(--ink-3);margin-right:2px;white-space:nowrap;}
.kls-open-b{padding:3px 9px;font-size:12px;white-space:nowrap;}
/* "Now S13/16" as a picker (build 172): the words stay, a native list lies
   over them, invisible. 16px so a phone does not zoom in on it. */
.kls-open-pick{position:relative;display:inline-flex;align-items:center;cursor:pointer;border-radius:6px;}
.kls-open-pick .kls-open-t i{font-style:normal;margin-left:3px;font-size:9px;opacity:.8;}
.kls-open-pick:hover .kls-open-t,.kls-open-pick:focus-within .kls-open-t{color:var(--accent-text);}
.kls-open-pick:focus-within{outline:2px solid var(--accent);outline-offset:2px;}
.kls-open-sel{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer;font-size:16px;border:0;}
.kls-open-sel:disabled{cursor:default;}
.kls-open .cls-btn:disabled{opacity:.4;cursor:default;}
.l-row.is-after,.l-row.is-teacher{color:var(--ink-3);}
.l-row.is-after .l-row-s,.l-row.is-teacher .l-row-s{font-size:11.5px;}

/* ---- build 148: learning vs practice in My plan; a teacher's My flow ---- */
.l-split{display:flex;flex-wrap:wrap;gap:6px 16px;margin-top:10px;font-size:12.5px;color:var(--ink-2);}
.l-split span{display:inline-flex;align-items:center;gap:6px;}
.l-dot{width:8px;height:8px;border-radius:50%;display:inline-block;}
.l-dot.learn{background:var(--lv1);}
.l-dot.prac{background:var(--accent);}
.l-flow-pick{width:100%;margin-bottom:14px;}
.l-flow-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px;
  font-size:12px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em;}
.l-flow-head b{font-family:var(--serif);font-size:18px;color:var(--ink);text-transform:none;letter-spacing:0;}
.l-flow{list-style:none;margin:0;padding:0;border-radius:var(--r);background:var(--card);box-shadow:var(--shadow);overflow:hidden;}
.l-flow li{display:flex;align-items:flex-start;gap:12px;padding:11px 14px;border-bottom:1px solid var(--line);}
.l-flow li:last-child{border-bottom:0;}
.l-flow-n{flex:0 0 22px;height:22px;border-radius:50%;background:var(--card-2);color:var(--ink-2);
  font-size:11.5px;font-weight:600;display:flex;align-items:center;justify-content:center;}
.l-flow-t{flex:1;min-width:0;font-size:13.5px;font-weight:500;display:flex;flex-direction:column;gap:3px;}
.l-flow-t em{font-style:normal;font-weight:400;font-size:11.5px;color:var(--ink-3);}
.l-flow-bar{display:block;height:3px;border-radius:3px;background:var(--track);overflow:hidden;margin-top:2px;}
.l-flow-bar i{display:block;height:100%;background:var(--accent);border-radius:3px;}
.l-flow-m{flex:0 0 auto;font-size:12.5px;color:var(--ink-2);padding-top:1px;}
.l-flow-msg{font-size:13px;color:var(--ink-3);padding:6px 0;}
.l-flow-go{margin-top:14px;}

/* ---- quieter class controls (148) ----
   One filled orange button per screen area is a call to action; three stacked
   (New class, Open S02, and Add beside them) was noise. Opening the next
   session and making a class are things a teacher does deliberately, now and
   then — they are outlined, terracotta text, no fill. The "1 class" count is
   a fact, not a badge: plain small text. */
.cls-btn.cls-new.go{background:transparent;color:var(--accent-text);border-color:var(--line-2);font-weight:500;}
.cls-btn.cls-new.go:hover{border-color:var(--accent);background:var(--accent-wash);}
.kls-open .cls-btn{background:transparent;border-color:var(--line);color:var(--ink-3);padding:2px 8px;font-size:11.5px;font-weight:500;}
.kls-open .cls-btn.go{color:var(--accent-text);border-color:var(--line-2);}
.kls-open .cls-btn:not(:disabled):hover{border-color:var(--accent);background:var(--accent-wash);color:var(--accent-text);}
.kls-open .cls-btn:disabled{opacity:.35;}
.cls-tag.on{font-family:var(--sans);font-size:12px;letter-spacing:0;text-transform:none;
  border-color:transparent;background:transparent;color:var(--ink-3);padding:0;}
.trow-l2 .cls-tag.on::before{content:'· ';}

/* ---- build 148: course information as a link; the invitation as a sheet ---- */
/* Above the invitation (z-index 60), so its "Course information" opens over it. */
.scrim.lesson-scrim{z-index:70;}
.lesson-x{z-index:71;}
.l-about-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:12px;}
.l-about-row .l-about{margin-top:0;text-decoration:none;}
.l-about-copy{padding:6px 11px;border-radius:20px;border:1px solid var(--line);background:transparent;
  font-size:12px;color:var(--ink-3);cursor:pointer;}
.l-about-copy:hover{border-color:var(--accent);color:var(--accent-text);}

.inv-page{background:radial-gradient(ellipse at top,var(--accent-wash),transparent 60%),var(--paper);}
.inv{max-width:440px;}
.inv-sheet{border-radius:18px;overflow:hidden;background:var(--card);
  box-shadow:0 2px 6px rgba(0,0,0,.06),0 24px 60px rgba(0,0,0,.14);}
.inv-top{display:flex;align-items:center;gap:9px;padding:12px 18px;color:#fff;
  font-family:var(--serif);font-size:16px;font-weight:700;
  background:linear-gradient(90deg,#00040C 0%,#00122F 30%,#001A44 50%,#00122F 72%,#00040D 100%);
  border-bottom:3px solid var(--accent);}
.inv-top .brand-logo{width:26px;height:26px;}
.inv-banner{position:relative;height:170px;background:var(--card-2);overflow:hidden;}
.inv-banner img{width:100%;height:100%;object-fit:cover;display:block;}
.inv-banner::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,transparent 45%,rgba(0,0,0,.55));}
.inv-banner .inv-eyebrow{position:absolute;left:18px;bottom:12px;z-index:1;color:#fff;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;}
.inv-body{padding:18px 20px 20px;}
.inv-body .inv-t{font-size:26px;font-weight:700;line-height:1.15;}
.inv-goal{font-size:14px;line-height:1.55;color:var(--ink-2);margin-top:8px;}
.inv-facts{font-size:12.5px;color:var(--ink-3);margin-top:10px;}
.inv-about{display:inline-flex;align-items:center;gap:8px;margin-top:14px;padding:7px 12px 7px 8px;
  border:1px solid var(--line-2);border-radius:20px;font-size:12.5px;font-weight:500;
  color:var(--ink);text-decoration:none;}
.inv-about:hover{border-color:var(--accent);color:var(--accent-text);}
.inv-ticket{display:flex;align-items:center;justify-content:space-between;margin-top:16px;
  padding:10px 14px;border:1.5px dashed var(--accent);border-radius:12px;background:var(--accent-wash);}
.inv-ticket span{font-size:12px;color:var(--accent-text);text-transform:uppercase;letter-spacing:.08em;font-weight:600;}
.inv-ticket b{font-family:var(--mono);font-size:18px;letter-spacing:.2em;color:var(--accent-text);}
.inv-body .inv-s{margin:16px 0 12px;}

/* ===================== the teacher's live submissions (build 149.3) =====================
   Full screen, because it is often on the classroom screen: the navy strip
   of the lesson pages on top, the class's learners down the left, and the
   lesson page itself filling the rest, with the chosen learner's answers. */
.scrim.live-scrim{z-index:70;background:var(--paper);display:block;overflow:hidden;}
.live{height:100%;display:flex;flex-direction:column;}
.live-head{position:relative;flex:0 0 auto;display:flex;align-items:center;flex-wrap:wrap;gap:10px 14px;
  padding:12px 64px 12px 20px;color:#fff;border-bottom:3px solid var(--accent);
  background:linear-gradient(90deg,#00040C 0%,#00122F 30%,#001A44 50%,#00122F 72%,#00040D 100%);}
.live-t{display:flex;align-items:baseline;gap:10px;min-width:0;flex:1 1 260px;}
.live-t b{font-family:var(--serif);font-size:20px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.live-c{font-size:13px;color:#C9D3E0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.live-dot{width:9px;height:9px;border-radius:50%;background:#E5484D;flex:0 0 auto;align-self:center;
  box-shadow:0 0 0 0 rgba(229,72,77,.6);animation:live-pulse 2s infinite;}
@keyframes live-pulse{70%{box-shadow:0 0 0 8px rgba(229,72,77,0)}100%{box-shadow:0 0 0 0 rgba(229,72,77,0)}}
@media (prefers-reduced-motion:reduce){.live-dot,.live-new{animation:none}}
.live-sel{width:auto;max-width:min(360px,100%);background:rgba(255,255,255,.08);color:#fff;border-color:rgba(255,255,255,.25);}
.live-sel option{color:#10233B;background:#fff;}
.live-n{font-size:13px;color:#C9D3E0;font-variant-numeric:tabular-nums;}
.live-x{position:absolute;top:10px;right:14px;}
/* Opened from the lesson (build 170): back to it, in words, where the ✕ was. */
.live-back{margin-left:auto;padding:7px 14px;border-radius:18px;border:1px solid rgba(255,255,255,.35);
  background:rgba(255,255,255,.08);color:#fff;font:inherit;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;}
.live-back:hover{background:rgba(255,255,255,.18);}
.live-note{flex:0 0 auto;margin:10px 16px 0;}
.live-empty{padding:40px 16px;text-align:center;color:var(--ink-3);font-size:15px;line-height:1.6;}
.live-main{flex:1 1 auto;min-height:0;display:flex;}
.live-list{flex:0 0 240px;overflow-y:auto;border-right:1px solid var(--line);background:var(--panel);padding:10px 8px;}
.live-p{display:block;width:100%;text-align:left;border:1px solid transparent;background:none;border-radius:10px;
  padding:9px 12px;margin:0 0 4px;cursor:pointer;font:inherit;color:var(--ink);}
.live-p:hover{background:var(--card-2);}
.live-p.on{background:var(--card);border-color:var(--accent);box-shadow:var(--shadow);}
.live-p-n{display:flex;align-items:center;gap:7px;font-weight:600;font-size:14.5px;overflow-wrap:anywhere;}
.live-p-s{display:block;font-size:12.5px;color:var(--ink-3);margin-top:2px;}
.live-p-s.got{color:var(--done);}
.live-new{width:8px;height:8px;border-radius:50%;background:var(--accent);flex:0 0 auto;animation:live-pulse 1.4s infinite;}
/* The list folds to a strip of initials (build 185): green once the learner
   has submitted, the pulse when they just did; the name is the tooltip. */
/* The folded state is `.live-folded`, not `.fold` (build 202): `.fold` is the
   app's dashed "show more" button (above), and its display:flex laid the
   initials side by side and pushed the » button out of sight, so the list
   could fold but never come back. The button is sticky so a long class keeps
   it in reach. */
.live-list{transition:flex-basis .18s ease;}
.live-fold{display:block;margin:0 0 8px auto;position:sticky;top:0;z-index:1;width:30px;height:28px;border-radius:8px;border:1px solid var(--line);
  background:var(--card);color:var(--ink-2);font:inherit;font-size:15px;line-height:1;cursor:pointer;}
.live-fold:hover{background:var(--card-2);color:var(--ink);}
.live-list.live-folded{flex-basis:64px;padding:10px 6px;}
.live-list.live-folded .live-fold{margin:0 auto 10px;}
.live-list.live-folded .live-p{padding:5px 0;text-align:center;}
.live-p-i{position:relative;display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:50%;
  background:var(--card-2);border:1px solid var(--line);font-weight:700;font-size:13px;letter-spacing:.02em;color:var(--ink-2);}
.live-p-i.got{background:var(--done-wash,rgba(46,125,91,.14));border-color:var(--done);color:var(--done);}
.live-p-i .live-new{position:absolute;top:-1px;right:-1px;}
.live-list.live-folded .live-p.on{background:none;box-shadow:none;border-color:transparent;}
.live-list.live-folded .live-p.on .live-p-i{box-shadow:0 0 0 2px var(--accent);}
.live-page{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;background:#fff;}
.live-page .live-empty{background:var(--paper);flex:1 1 auto;}
@media (max-width:760px){
  .live-head{padding:10px 60px 10px 16px;}
  .live-main{flex-direction:column;}
  .live-list{flex:0 0 auto;display:flex;gap:6px;overflow-x:auto;overflow-y:hidden;border-right:0;border-bottom:1px solid var(--line);padding:8px;}
  .live-p{flex:0 0 auto;width:auto;margin:0;padding:6px 12px;}
  /* on a phone the list is already one thin row: nothing to fold */
  .live-fold{display:none;}
  .live-list.live-folded{flex-basis:auto;padding:8px;}
}

/* ---- the learner report's "Session by session" (builds 160, 161) ----
   One table for the whole course: under each stage, every session with its
   Learn mark and its Practice bar, and since 161 how much of each was right.
   It replaced Practice's "Stage by stage" and the Learn answers (149, folded
   in 159) with the one page a teacher prints for a learner or their parents.
   Rows are grids rather than a <table> so a long session title can shrink
   and ellipsis where a table cell would push the bars off the right edge on
   a phone.

   `.rep-tr`, NOT `.rep-row`. 160 first called the rows `.rep-row`, a name
   the player's "report a problem" box (50-widgets.js) has had since long
   before, and this rule — later in the file — turned that box's row of tags
   into a four-column grid. */
.rep-tab{border:1px solid var(--line);border-radius:var(--r);overflow:hidden;margin-bottom:10px;}
.rep-tr{display:grid;grid-template-columns:24px minmax(0,1fr) 112px 40px 112px 40px;align-items:center;gap:10px;
  padding:7px 12px;border-top:1px solid var(--line);}
.rep-row-t{font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.rep-tr.is-none .rep-row-t{color:var(--ink-3);}
/* The column names, above everything: Learn and Practice over two columns
   each, then what each column is. */
.rep-th{border-top:none;background:var(--card-2);
  font-family:var(--mono);font-size:10px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3);}
.rep-th-1{padding:8px 12px 0;}
.rep-th-2{padding:2px 12px 6px;font-size:9.5px;letter-spacing:.04em;}
.rep-th-g{grid-column:span 2;color:var(--ink-2);border-bottom:1px solid var(--line-2);padding-bottom:3px;}
/* A stage: the row the old "Stage by stage" list was, set apart by its
   background so the sessions read as belonging to it. */
.rep-grp-h{background:var(--card);}
.rep-grp-h .rep-row-t{font-family:var(--serif);font-size:14px;font-weight:600;color:var(--ink);}
.rep-grp-h .rep-l{color:var(--ink-2);}
/* Learn: a bar with "3/5", the date once all are in (257.1); ✓ and a date
   where the lesson's size is not known; or a dash. */
.rep-l{font-family:var(--mono);font-size:11px;color:var(--ink-3);white-space:nowrap;}
.rep-l.got{color:var(--done);}
.rep-lp .rep-stage-b{min-width:18px;}
.rep-lp .rep-l{flex:0 0 auto;}
/* Practice: the stage bar at a row's size, and its percentage. */
.rep-p{display:flex;align-items:center;gap:8px;min-width:0;}
.rep-p .rep-stage-b{flex:1 1 auto;}
.rep-p .rep-stage-p{flex:0 0 34px;}
/* A share right (161): green solid, red to go over again, plain between. */
.rep-sc{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--ink-2);text-align:right;white-space:nowrap;}
.rep-sc.is-good{color:var(--done);}
.rep-sc.is-low{color:var(--wrong);}
.rep-sc.is-none{font-weight:400;color:var(--ink-3);}
/* the Practice "Right" cell with the voice's real figure under it (build 247) */
.rep-sc-2{display:flex;flex-direction:column;align-items:flex-end;gap:1px;line-height:1.2;}
.rep-said{font-family:var(--mono);font-size:9.5px;font-weight:600;color:var(--ink-3);white-space:nowrap;}
.rep-said.is-good{color:var(--done);}
.rep-said.is-low{color:var(--wrong);}
.rep-th .rep-sc{font-weight:400;color:inherit;}
/* …and the course-wide one, under a stat box's label. */
.rep-cell-s{display:block;font-style:normal;font-size:10.5px;color:var(--ink-3);margin-top:3px;}
.rep-cell-s .rep-sc{font-size:10.5px;}
.rep-print{display:flex;justify-content:flex-end;margin-top:14px;}
@media (max-width:560px){
  .rep-tr{grid-template-columns:18px minmax(0,1fr) 64px 32px 64px 32px;gap:6px;padding:7px 10px;}
  .rep-lp .rep-stage-b{display:none;}
  .rep-l,.rep-sc{font-size:10px;}
  .rep-p .rep-stage-p{display:none;}
}

/* A folded course's "N waiting" (build 151): its own size, not the card's width. */
.kcard-head .cbody > .cls-tag.wait{align-self:flex-start;margin-top:5px;}

/* ---- landing: the drawn app window, the loop and the tour (build 199) ----
   Every screen is a size container; all sizes under `.x-in` are `em` off the
   base below, so one drawing serves the small hero and the big tour. Colours are
   the page tokens, so a screen follows the theme with no second drawing. */
.lpx-frame{padding:0;overflow:hidden;}
.lpx-frame .lp-mock-bar{padding:11px 14px;}
.lpx-url{margin-left:8px;flex:1;max-width:60%;font-size:11.5px;color:var(--ink-3);background:var(--sunk);
  border:1px solid var(--line);border-radius:8px;padding:4px 12px;font-family:var(--sans);}

@keyframes lpxBob{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}

/* ===================== the class line and the class window (build 218) =====================
   The line: one button (dot, name, one line of facts, ›), then Open S.. and ⋯.
   The window: AdminPop's, with four numbers, three tabs and a learner table;
   the Activity column goes on a phone. */
.kls-line{gap:6px;padding:4px 0;}
.kls-win{flex:1;min-width:0;display:flex;align-items:center;gap:9px;padding:7px 8px;margin-left:-8px;
  border:0;border-radius:9px;background:transparent;text-align:left;font:inherit;color:inherit;cursor:pointer;}
.kls-win:hover{background:var(--accent-wash);}
.kls-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--accent);}
.kls-dot.calm{background:var(--line-2);}
.kls-line-t{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px;}
.kls-line-t .kls-name{font-weight:600;font-size:13.5px;color:var(--ink);}
.kls-line-s{font-size:12px;color:var(--ink-3);line-height:1.4;}
.kls-go{flex:none;color:var(--accent-text);font-size:18px;line-height:1;}
.cw{display:flex;flex-direction:column;gap:12px;}
.cw-stats{display:flex;flex-wrap:wrap;gap:8px;}
.cw-stat{flex:1 1 110px;background:var(--panel);border-radius:10px;padding:8px 12px;}
.cw-stat b{display:block;font-family:var(--mono);font-size:18px;font-weight:500;font-variant-numeric:tabular-nums;color:var(--ink);}
.cw-stat span{font-size:12px;color:var(--ink-3);}
.cw-stat.warn{background:var(--wrong-wash);}
.cw-stat.warn b{color:var(--accent-text);}
.cw-tabs{display:flex;gap:2px;border-bottom:1px solid var(--line);overflow-x:auto;}
.cw-tab{flex:none;display:flex;align-items:center;gap:6px;padding:9px 12px;border:0;border-bottom:2px solid transparent;
  background:none;font:inherit;font-size:13.5px;font-weight:500;color:var(--ink-3);cursor:pointer;}
.cw-tab[aria-selected="true"]{color:var(--ink);border-bottom-color:var(--accent);}
.cw-badge{font-family:var(--mono);font-size:11px;line-height:18px;padding:0 6px;border-radius:10px;background:var(--accent);color:var(--on-accent);}
.cw-badge.calm{background:var(--track);color:var(--ink-2);}
.cw-pane{min-height:120px;}
.cw-grp{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 8px;margin:14px 0 4px;}
.cw-pane>.cw-grp:first-child{margin-top:2px;}
.cw-grp h4{margin:0;font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);}
.cw-grp span{font-size:12px;color:var(--ink-3);}
.cw-fold{margin-left:auto;border:0;background:none;font:inherit;font-size:12.5px;color:var(--accent-text);cursor:pointer;}
.cw-tblw{overflow-x:auto;}
.cw-tbl{width:100%;border-collapse:collapse;font-size:13px;}
.cw-tbl th{text-align:left;padding:6px 8px;border-bottom:1px solid var(--line);font-family:var(--mono);font-size:10.5px;
  font-weight:500;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);}
.cw-tbl td{padding:8px;border-bottom:1px solid var(--line);vertical-align:middle;}
.cw-tbl tbody tr:hover td{background:var(--panel);}
.cw-who{min-width:0;}
.cw-who .kls-who,.cw-who .kls-name{display:inline;white-space:normal;font-size:13.5px;}
.cw-flag{display:inline-block;margin-left:6px;padding:1px 8px;border-radius:20px;font-size:11px;font-weight:500;white-space:nowrap;}
.cw-flag.wait{background:var(--accent-wash);color:var(--accent-text);}
.cw-flag.idle{background:var(--wrong-wash);color:var(--accent-text);}
.cw-flag.quiet{background:var(--track);color:var(--ink-2);}
.cw-prog{white-space:nowrap;}
.cw-prog .kls-bar{display:inline-block;vertical-align:middle;width:64px;min-width:64px;height:5px;}
.cw-prog .kls-pct{margin-left:6px;font-size:11.5px;}
.cw-none{font-size:12.5px;color:var(--ink-3);}
.cw-when{font-size:12.5px;color:var(--ink-3);white-space:nowrap;}
.cw-acts{text-align:right;white-space:nowrap;}
.cw-tbl .dots-b{opacity:0;}
.cw-tbl tr:hover .dots-b,.cw-tbl .dots-b:focus-visible,.cw-tbl .dots-b[aria-expanded="true"]{opacity:1;}
@media (hover:none){.cw-tbl .dots-b{opacity:1;}}
.cw-ask td{background:transparent!important;}
.cw-gone{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px;}
.cw-openrow{display:flex;flex-wrap:wrap;align-items:center;gap:10px;background:var(--panel);border-radius:10px;padding:10px 12px;font-size:13px;}
.cw-sel{width:auto;max-width:100%;}
.cw-sess{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:6px;margin-top:12px;}
.cw-s{display:flex;gap:8px;align-items:center;min-width:0;padding:6px 8px;border:1px solid var(--line);border-radius:8px;font-size:12.5px;color:var(--ink-2);}
.cw-s b{flex:none;font-family:var(--mono);font-weight:500;color:var(--ink-3);}
.cw-s span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.cw-s.on{border-color:var(--done);background:var(--done-wash);}
.cw-inv{display:flex;flex-direction:column;gap:18px;}
.cw-k{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-bottom:6px;}
.cw-code{font-family:var(--mono);font-size:28px;font-weight:500;letter-spacing:.2em;margin-bottom:6px;}
@media (max-width:640px){.cw-tbl .cw-when{display:none;}}
/* the two tables of the Learners tab line up: fixed columns, same widths */
.cw-tbl{table-layout:fixed;}
.cw-tbl th:nth-child(2){width:26%;}
.cw-tbl th:nth-child(3){width:22%;}
.cw-tbl th:nth-child(4){width:150px;}
.cw-tbl td.cw-when{overflow:hidden;text-overflow:ellipsis;}
.cw-who .kls-who{overflow:visible;text-overflow:clip;white-space:normal;word-break:break-word;text-align:left;}
.cw-acts .cls-btn{margin:2px 0 2px 4px;}
@media (max-width:640px){.cw-acts{white-space:normal;}.cw-acts .cls-btn{display:block;width:100%;margin:3px 0;}}
@media (max-width:640px){.cw-tbl th:nth-child(2){width:34%;}.cw-tbl th:nth-child(4){width:120px;}}

/* ===================== attendance (build 250, 91-attendance.js) =====================
   The class window's Điểm danh tab and a class learner's own line. The five
   states keep the colours they have everywhere else: present the done green,
   made up the gold, excused the Basic blue, absent the wrong red, not yet the
   track grey. Each also carries its own glyph (✓ B P ✕ ·), so none is told by
   colour alone. */
.att{display:grid;gap:12px;}
.att-head{display:flex;align-items:flex-start;gap:10px;flex-wrap:wrap;justify-content:space-between;background:var(--panel);border-radius:10px;padding:10px 12px;}
.att-head>div{display:grid;gap:2px;min-width:0;}
.att-head b{font-size:13.5px;font-weight:600;color:var(--ink);}
.att-head span{font-size:12.5px;color:var(--ink-2);}
/* 254.9: a roll's date and time as fields, in the heading's own type */
.att-dh{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap;}
.att-dh .att-dh-in{width:auto;height:32px;padding:2px 8px;font-weight:600;color:var(--ink);}
.att-head>.att-head-b{display:flex;gap:6px;flex-wrap:wrap;}
/* 254.5: ten class days at a time, and classes taught before the app */
.att-pg{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.att-d.pre button{border-bottom:2px dashed var(--line-2);border-radius:6px 6px 0 0;}
.att-pre-list{display:grid;gap:6px;max-height:340px;overflow-y:auto;}
.att-pre-r{display:grid;grid-template-columns:44px 1fr auto;gap:10px;align-items:center;font-size:13px;color:var(--ink-2);}
.att-pre-r span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
@media (max-width:520px){.att-pre-r{grid-template-columns:40px 1fr;}.att-pre-r input{grid-column:1/-1;}}
.att-gridw{overflow-x:auto;border:1px solid var(--line);border-radius:10px;}
.att-grid{border-collapse:collapse;font-size:13px;min-width:100%;}
.att-grid th,.att-grid td{border-bottom:1px solid var(--line);padding:5px 4px;text-align:center;white-space:nowrap;}
.att-grid tbody tr:last-child td{border-bottom:1px solid var(--line-2);}
.att-grid tfoot td{border-bottom:0;}
.att-grid .att-who{text-align:left;padding-left:10px;position:sticky;left:0;background:var(--card);z-index:1;max-width:160px;overflow:hidden;text-overflow:ellipsis;}
.att-grid thead .att-who{font-family:var(--mono);font-size:10.5px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);}
.att-d button{border:0;background:none;font:inherit;font-size:11.5px;color:var(--ink-2);display:inline-grid;justify-items:center;gap:1px;padding:3px 6px;border-radius:6px;cursor:pointer;font-variant-numeric:tabular-nums;}
.att-d button small{font-family:var(--mono);font-size:10px;color:var(--ink-3);}
.att-d button:hover{background:var(--panel);}
.att-d.now button{color:var(--accent-text);font-weight:600;}
.att-d.sel button{background:var(--accent-wash);}
.att-d.off button{text-decoration:line-through;opacity:.7;}
.att-c{min-width:34px;}
.att-g{display:inline-grid;place-items:center;width:26px;height:26px;border-radius:7px;border:0;font:inherit;font-size:12.5px;font-weight:700;position:relative;padding:0;}
button.att-g{cursor:pointer;}
button.att-g:hover{outline:2px solid var(--line-2);outline-offset:1px;}
.att-g.ok,.att-chip.ok{background:var(--done-wash);color:var(--done);}
.att-g.mk,.att-chip.mk{background:var(--gold-wash);color:var(--gold);}
.att-g.ex,.att-chip.ex{background:var(--lv1-wash);color:var(--lv1);}
.att-g.no,.att-chip.no{background:var(--wrong-wash);color:var(--wrong);}
.att-g.wait,.att-chip.wait{background:var(--track);color:var(--ink-3);}
.att-g.up,.att-chip.up{background:transparent;box-shadow:inset 0 0 0 1px var(--line-2);color:var(--ink-3);}
.att-g.off,.att-chip.off{background:var(--panel);color:var(--ink-3);}
.att-g.by::after{content:"";position:absolute;top:2px;right:2px;width:5px;height:5px;border-radius:50%;background:var(--accent);}
.att-g.sm{width:18px;height:18px;border-radius:5px;font-size:10.5px;font-style:normal;}
.att-na{color:var(--ink-3);}
.att-pct{font-family:var(--mono);font-variant-numeric:tabular-nums;font-size:12.5px;padding-right:10px!important;}
.att-pct small{display:block;font-size:10.5px;color:var(--ink-3);}
.att-n{font-family:var(--mono);font-size:11px;color:var(--ink-3);}
.att-day{border:1px solid var(--line);border-radius:12px;padding:12px;display:grid;gap:10px;background:var(--card);}
/* NOTHING HERE MAY BE WIDER THAN THE WINDOW (269.9, Khoa's iPhone picture of
   e2i-28's Attendance: the window ran off the right edge and the page slid
   sideways). A select is as wide as its longest option, and "Dạy bài" lists
   every session with its whole title; a grid's one column grows to its widest
   item, so the roll, then the tab, then the window's body were 120 px wider
   than a phone. The columns may now shrink (`minmax(0,1fr)`), a select stops
   at its row's width and cuts its text, and the session line under a day wraps. */
.att,.att-day{grid-template-columns:minmax(0,1fr);}
.att>*,.att-day>*{min-width:0;}
.att select.field{max-width:100%;text-overflow:ellipsis;}
.att-day-ctl label{min-width:0;max-width:100%;}
.att-day-h span{overflow-wrap:anywhere;}
.att-day-h{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.att-day-h>div{display:grid;gap:1px;min-width:0;}
.att-day-h b{font-size:14px;}
.att-day-h span{font-size:12.5px;color:var(--ink-2);}
.att-cnt{font-family:var(--mono);font-size:18px!important;font-weight:500;font-variant-numeric:tabular-nums;}
.att-live{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:600;color:var(--wrong);background:var(--wrong-wash);border-radius:20px;padding:2px 9px;}
.att-live i{width:7px;height:7px;border-radius:50%;background:var(--wrong);animation:attPulse 1.4s ease-in-out infinite;}
@keyframes attPulse{50%{opacity:.25;}}
@media (prefers-reduced-motion:reduce){.att-live i{animation:none;}}
.att-day-ctl{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:13px;color:var(--ink-2);}
.att-roll{display:grid;border:1px solid var(--line);border-radius:10px;overflow:hidden;}
.att-rl{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:6px 10px;align-items:center;padding:8px 10px;border-bottom:1px solid var(--line);}
.att-rl:last-child{border-bottom:0;}
.att-rl-n{display:grid;min-width:0;}
.att-rl-n b{font-size:13.5px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.att-rl-n span{font-size:12px;color:var(--ink-3);}
.att-chip{display:inline-block;font-size:11.5px;font-weight:600;padding:2px 9px;border-radius:20px;white-space:nowrap;}
.att-seg{display:inline-flex;border:1px solid var(--line-2);border-radius:8px;overflow:hidden;}
.att-seg button{border:0;border-right:1px solid var(--line-2);background:var(--card);font:inherit;font-size:12px;padding:3px 9px;cursor:pointer;color:var(--ink-2);}
.att-seg button:last-child{border-right:0;}
.att-seg button[aria-pressed="true"].p{background:var(--done-wash);color:var(--done);font-weight:600;}
.att-seg button[aria-pressed="true"].e{background:var(--lv1-wash);color:var(--lv1);font-weight:600;}
.att-seg button[aria-pressed="true"].a{background:var(--wrong-wash);color:var(--wrong);font-weight:600;}
@media (max-width:640px){.att-rl{grid-template-columns:minmax(0,1fr) auto;}.att-seg{grid-column:1/-1;justify-self:start;}}
.att-form{display:grid;gap:12px;background:var(--panel);border-radius:12px;padding:14px;}
.att-form h4{margin:0;font-size:15px;}
.att-why{margin:0;font-size:13px;color:var(--ink-2);line-height:1.5;}
.att-f-row{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;}
.att-f-row label{display:grid;gap:4px;font-size:12.5px;color:var(--ink-2);min-width:0;}
.att-f-row label.grow{flex:1 1 220px;}
.att-f-row .field{width:auto;max-width:100%;}
.att-f-row label.grow .field{width:100%;}
.att-f-l{font-size:12.5px;color:var(--ink-2);width:100%;}
.att-dows{display:flex;gap:6px;flex-wrap:wrap;}
.att-dows button{min-width:44px;padding:6px 10px;border-radius:8px;border:1px solid var(--line-2);background:var(--card);font:inherit;font-size:13px;font-weight:600;cursor:pointer;color:var(--ink-2);}
.att-dows button[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:var(--on-accent);}
.att-f-btns{display:flex;gap:8px;justify-content:flex-end;}
.att-me{margin:2px 0 8px;padding:8px 10px;border-radius:10px;background:var(--panel);display:grid;gap:4px;font-size:12.5px;}
.att-me-top{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.att-me-top b{font-size:12.5px;}
.att-me-top>span:not(.att-strip){color:var(--ink-2);}
.att-strip{display:inline-flex;gap:3px;margin-left:auto;}
.att-me-next{color:var(--ink-2);}
.att-me-list{display:grid;gap:4px;margin-top:4px;}
.att-me-list div{display:flex;justify-content:space-between;gap:8px;align-items:center;}

/* ===================== Lớp của tôi (build 251, 93-myclasses.js) =====================
   The teacher's page of every class: a full-screen sheet over the app, its own
   header with three tabs, then the dashboard. Charts: thin columns (24px at
   most, 4px rounded tops, 2px surface gaps), 2px lines with ringed end dots,
   hairline grid, one tooltip, a table under each. Class colours --cat1…8. */
.mc-open{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;margin-right:4px;border-radius:9px;
  border:1px solid var(--hdr-line);background:transparent;color:var(--hdr-ink-2);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;}
.mc-open:hover{background:var(--hdr-soft);color:var(--hdr-ink);}
@media (max-width:700px){.mc-open-t{display:none;}.mc-open{padding:0 8px;}}
.mc-line{display:flex;align-items:center;gap:8px;width:calc(100% - 20px);margin:6px 10px 12px;padding:8px 10px;border-radius:10px;
  border:1px solid var(--line);background:var(--card);font:inherit;font-size:12.5px;color:var(--ink-2);cursor:pointer;text-align:left;}
.mc-line:hover{border-color:var(--accent);}
.mc-line-k{font-weight:600;color:var(--accent-text);white-space:nowrap;}
/* one row (254.6, Khoa: "should be in one row, just click to see more"): what does not fit fades out at the right; 12px under the line, or on a phone the sticky course bar's 6px paper strip covers its bottom edge */
.mc-line-t{flex:1;min-width:0;display:flex;flex-wrap:nowrap;gap:5px;overflow:hidden;}
.mc-line-t.fade{-webkit-mask-image:linear-gradient(to right,#000 82%,transparent);mask-image:linear-gradient(to right,#000 82%,transparent);}
.mc-line-n{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
/* 254.5: each class of the day a chip in its colour; one already over is paler */
.mc-line-c{display:inline-flex;align-items:baseline;gap:5px;padding:2px 8px 2px 6px;border-radius:6px;border-left:4px solid var(--pc);
  background:var(--panel);color:var(--ink);white-space:nowrap;flex:none;}
.mc-line-c b{font-family:var(--mono);font-size:12px;font-weight:600;}
.mc-line-c.alt{border-left-style:double;border-left-width:5px;}
.mc-line-c.over{opacity:.6;}
.mc-scrim{flex-direction:column;}
.mc{display:flex;flex-direction:column;width:100%;height:100%;background:var(--paper);}
.mc-top{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:10px max(16px,env(safe-area-inset-right,0px)) 10px max(16px,env(safe-area-inset-left,0px));
  padding-top:calc(10px + env(safe-area-inset-top,0px));border-bottom:1px solid var(--line);background:var(--card);}
.mc-title{display:grid;gap:1px;}
.mc-title b{font-family:var(--serif);font-size:20px;}
.mc-title>span{font-size:12px;color:var(--ink-3);}
.mc-tabs{display:flex;gap:2px;overflow-x:auto;margin-left:auto;position:relative;scrollbar-width:none;}
/* 257.2: no scroll bar; an edge with more tabs behind it fades */
.mc-tabs::-webkit-scrollbar{display:none;}
.mc-tabs.fr{-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 44px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 44px),transparent);}
.mc-tabs.fl{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 44px),transparent);mask-image:linear-gradient(to left,#000 calc(100% - 44px),transparent);}
.mc-tabs.fl.fr{-webkit-mask-image:linear-gradient(to right,transparent,#000 44px,#000 calc(100% - 44px),transparent);mask-image:linear-gradient(to right,transparent,#000 44px,#000 calc(100% - 44px),transparent);}
.mc-tab{border:0;background:none;font:inherit;font-size:14px;padding:8px 12px;border-radius:8px;color:var(--ink-2);cursor:pointer;white-space:nowrap;}
.mc-tab[aria-selected="true"]{background:var(--accent);color:var(--on-accent);font-weight:600;}
.mc-x{position:static!important;}
/* on a phone the tabs take their own row and the ✕ stays top right (254.5) */
@media (max-width:700px){.mc-tabs{order:3;width:100%;margin-left:0;}.mc-x{margin-left:auto;}}
.mc-body{flex:1;overflow-y:auto;padding:16px max(16px,env(safe-area-inset-right,0px)) calc(32px + env(safe-area-inset-bottom,0px)) max(16px,env(safe-area-inset-left,0px));}
.mc-body>*{max-width:1240px;margin-left:auto;margin-right:auto;}
/* 264.5: the outline while the reads come in (`McSkel`), then the page fades
   in once (`.fresh`, taken off after 400 ms, so a tab opened later does not) */
.mc-sk{display:block;height:11px;border-radius:6px;background:var(--line);animation:mcSk 1.4s ease-in-out infinite;}
.mc-sk.big{height:26px;width:52%;margin:4px 0;}
.mc-sk.chip{height:28px;border-radius:20px;}
.mc-sk-card{align-content:start;}
@keyframes mcSk{0%,100%{opacity:.55;}50%{opacity:1;}}
.mc-body.fresh>*{animation:mcIn .22s ease-out;}
@keyframes mcIn{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:none;}}
@media (prefers-reduced-motion:reduce){.mc-sk,.mc-body.fresh>*{animation:none;}}
/* 258.2 (Khoa: "should we leave a little more margin for the tabs … except tab
   Calendar?"): on a computer 32px at the sides, Lịch lớp keeps 16 for its week */
@media (min-width:701px){.mc-body:not(.t-cal){padding-left:max(32px,env(safe-area-inset-left,0px));padding-right:max(32px,env(safe-area-inset-right,0px));}}
/* one column no wider than the page (254.7): a grid's own column grows to its widest
   child, and on a phone the learners' table pushed the toolbar off the screen */
.mc-ov,.mc-ln{display:grid;gap:14px;grid-template-columns:minmax(0,1fr);}
.mc-card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px;display:grid;gap:10px;min-width:0;align-content:start;}
.mc-card h3{margin:0;font-family:var(--serif);font-size:17px;}
.mc-setup{border-color:var(--accent);box-shadow:inset 3px 0 0 var(--accent);display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between;}
.mc-setup>div:first-child{display:grid;gap:2px;}
.mc-setup span{font-size:12.5px;color:var(--ink-2);}
.mc-chips{display:flex;gap:6px;flex-wrap:wrap;align-items:center;}
.mc-chip{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line-2);background:var(--card);border-radius:20px;padding:4px 12px;
  font:inherit;font-size:13px;color:var(--ink-2);cursor:pointer;}
.mc-chip[aria-pressed="true"]{background:var(--accent-wash);border-color:var(--accent);color:var(--accent-text);font-weight:600;}
.mc-dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px;vertical-align:1px;flex:none;background:var(--pc);}
/* a class past the eighth: its hue again, as a ring (254.5) */
.mc-dot.ring{background:transparent;box-shadow:inset 0 0 0 2.5px var(--pc);}
.mc-chip .mc-dot{margin-right:0;}
.mc-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;}
@media (max-width:1000px){.mc-kpis{grid-template-columns:repeat(3,minmax(0,1fr));}}
@media (max-width:560px){.mc-kpis{grid-template-columns:repeat(2,minmax(0,1fr));}}
.mc-kpi{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:12px 14px;display:grid;gap:2px;min-width:0;}
.mc-kpi span{font-size:12px;color:var(--ink-3);}
.mc-kpi b{font-family:var(--serif);font-size:28px;line-height:1.15;font-variant-numeric:tabular-nums;}
.mc-kpi small{font-size:12px;color:var(--ink-2);}
.mc-kpi.warn b{color:var(--wrong);}
.mc-dash{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:14px;align-items:start;}
.mc-main,.mc-side{display:grid;gap:14px;min-width:0;}
@media (max-width:1000px){.mc-dash{grid-template-columns:minmax(0,1fr);}.mc-side{order:-1;}}
.mc-ch{display:flex;gap:10px;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;}
.mc-ch>div:first-child{display:grid;gap:2px;}
.mc-ch span{font-size:12.5px;color:var(--ink-3);}
.mc-leg{display:flex;gap:12px;flex-wrap:wrap;font-size:12px;color:var(--ink-2);}
.mc-leg span{display:inline-flex;align-items:center;gap:5px;}
.mc-sw{display:inline-block;width:10px;height:10px;border-radius:3px;background:var(--sc);flex:none;}
.mc-sw.r{background:transparent;box-shadow:inset 0 0 0 2px var(--sc);}
.mc-sw.h{background:repeating-linear-gradient(45deg,var(--sc) 0 2px,transparent 2px 5px);box-shadow:inset 0 0 0 1px var(--sc);}
.mc-chart{width:100%;height:auto;display:block;overflow:visible;}
.mc-chart .gl{stroke:var(--line);stroke-width:1;}
.mc-chart .ax{fill:var(--ink-3);font-size:11px;font-variant-numeric:tabular-nums;}
.mc-chart .lbl{fill:var(--ink-2);font-size:11.5px;font-weight:600;}
.mc-chart .ln{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;}
.mc-chart .dot{stroke:var(--card);stroke-width:2;}
.mc-chart .hit{fill:transparent;outline:none;}
.mc-chart .hit:hover,.mc-chart .hit:focus-visible{fill:var(--ink);fill-opacity:.05;}
.mc-tip{position:fixed;z-index:200;background:var(--card);color:var(--ink);border:1px solid var(--line-2);border-radius:10px;
  box-shadow:0 6px 20px rgba(0,0,0,.16);padding:8px 10px;font-size:12.5px;display:grid;gap:3px;min-width:180px;pointer-events:none;}
.mc-tip div{display:flex;align-items:center;gap:6px;}
.mc-tip span{margin-left:auto;padding-left:12px;font-family:var(--mono);font-variant-numeric:tabular-nums;}
.mc-tv summary{cursor:pointer;font-size:12.5px;color:var(--accent-text);}
.mc-tv .cw-tblw{margin-top:8px;}
.mc-today{display:grid;gap:4px;padding:10px 0;border-top:1px solid var(--line);font-size:13px;}
.mc-today>span{color:var(--ink-3);font-size:12.5px;}
.mc-today-b{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
.mc-need{display:grid;gap:1px;text-align:left;border:1px solid var(--line);border-left:4px solid var(--pc);border-radius:9px;
  background:var(--card);padding:7px 10px;font:inherit;font-size:13px;cursor:pointer;color:var(--ink);}
.mc-need span{font-size:12px;color:var(--ink-3);}
.mc-need.nx span{color:var(--accent-text);}
/* 254.6: the controls and the day's list in a column on the left, the grid on the right */
.mc-cal-w{display:grid;grid-template-columns:270px minmax(0,1fr);grid-template-areas:"ctl cal";gap:14px;align-items:start;}
.mc-calc{grid-area:ctl;}.mc-cal{grid-area:cal;}
/* 254.7: the controls stay in view while the page scrolls (computer) */
@media (min-width:1001px){.mc-calc{position:sticky;top:0;}}
.mc-calc h3{margin:0;}
.mc-calc .mc-seg{justify-self:start;}
.mc-calc-hint{margin:0;font-size:12px;}
@media (max-width:1000px){.mc-cal-w{grid-template-columns:minmax(0,1fr);grid-template-areas:"ctl" "cal";}
  .mc-calc{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;}.mc-calc h3{white-space:nowrap;}
  .mc-calc .mc-mini,.mc-calc-hint{flex-basis:100%;}}
@media (max-width:640px){.mc-calc .mc-mini{display:none;}}
.mc-mini{display:grid;gap:6px;}
.mc-mini-h{display:flex;align-items:center;gap:4px;font-size:13px;}
.mc-mini-b{border:0;background:none;font:inherit;font-size:16px;width:28px;height:28px;border-radius:50%;cursor:pointer;color:var(--ink-2);}
.mc-mini-b:hover{background:var(--panel);}
.mc-mini-g{display:grid;grid-template-columns:repeat(7,1fr);gap:2px 0;text-align:center;}
.mc-mini-g>span{font-size:10.5px;color:var(--ink-3);padding-bottom:2px;}
.mc-mini-g button{position:relative;border:0;background:none;font:inherit;font-size:12px;height:30px;cursor:pointer;color:var(--ink);font-variant-numeric:tabular-nums;}
.mc-mini-g button.wk{background:var(--panel);}
.mc-mini-g button.out{color:var(--ink-3);}
.mc-mini-g button.now{color:var(--accent-text);font-weight:700;}
.mc-mini-g button[aria-pressed="true"]{box-shadow:inset 0 0 0 2px var(--accent);border-radius:50%;}
.mc-mini-g button i{position:absolute;left:50%;bottom:3px;width:4px;height:4px;margin-left:-2px;border-radius:50%;background:var(--ink-3);}
.mc-mini-g button:hover{background:var(--accent-wash);}
.mc-cal-h{display:flex;align-items:center;gap:10px;}
.mc-grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:4px;}
.mc-dow{font-size:11px;font-weight:600;color:var(--ink-3);text-align:center;padding:3px 0;}
.mc-cell{min-height:92px;border:1px solid var(--line);border-radius:8px;padding:4px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:pointer;background:var(--card);}
.mc-cell.out{opacity:.45;}
.mc-cell.sel{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent);}
.mc-dn{font-size:12px;font-weight:600;color:var(--ink-2);align-self:flex-start;padding:0 4px;}
.mc-cell.now .mc-dn{background:var(--accent);color:var(--on-accent);border-radius:20px;padding:0 7px;}
.mc-pill{display:block;width:100%;text-align:left;border:0;border-left:3px solid var(--pc);border-radius:5px;background:var(--panel);
  font:inherit;font-size:11px;line-height:1.3;padding:2px 5px;color:var(--ink);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.mc-pill span{margin-left:4px;font-family:var(--mono);color:var(--ink-3);}
.mc-pill.off{text-decoration:line-through;opacity:.6;}
.mc-pill.up{opacity:.8;}
.mc-pill.live{background:var(--wrong-wash);}
.mc-dots{display:none;gap:3px;flex-wrap:wrap;}
.mc-dots i{width:7px;height:7px;border-radius:50%;}
.mc-dots .mc-dot{margin:0;}
.mc-pill.alt,.mc-ag.alt,.mc-need.alt{border-left-style:double;border-left-width:5px;}
@media (max-width:640px){.mc-cell{min-height:50px;}.mc-cell .mc-pill{display:none;}.mc-dots{display:flex;}}
.mc-agenda{gap:8px;}
.mc-ag{display:grid;gap:2px;text-align:left;border:1px solid var(--line);border-left:4px solid var(--pc);border-radius:10px;background:var(--card);
  padding:8px 10px;font:inherit;font-size:13px;cursor:pointer;color:var(--ink);}
.mc-ag span{font-size:12.5px;color:var(--ink-2);}
/* 257.1: Xem theo Học viên · Lớp. By learner a block per learner, the name
   once over its rows, the block lit as one; by class a heading row per class */
.mc-by{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;color:var(--ink-3);}
.mc-by .mc-seg{height:34px;box-sizing:border-box;}
.mc-by .mc-seg button{padding:0 12px;}
.mc-lt td.mc-who{vertical-align:top;white-space:normal;min-width:130px;}
.mc-who-n{display:block;font-size:11.5px;color:var(--ink-3);margin-top:3px;}
.mc-lt.by-ln tbody.mc-grp tr:not(:last-child) td:not(.mc-who){border-bottom-style:dashed;}
.mc-grp-h td{background:var(--card-2);padding:10px 8px 8px;white-space:normal;}
.cw-tbl tbody tr.mc-grp-h:hover td{background:var(--card-2);}
.mc-grp-h .mc-cl b{font-weight:600;}
.mc-grp-c{margin-left:12px;font-size:12.5px;color:var(--ink-2);}
.mc-grp-c b{font-family:var(--mono);font-size:12px;margin-right:4px;}
.mc-grp-n{margin-left:12px;font-size:12px;color:var(--ink-3);}
.mc-course-b{display:inline;text-align:left;}
.mc-course-b:hover span{color:var(--accent-text);}
/* 264.6: the course is a link to its about page, in a new tab; no ↗ after it
   since 264.12 (Khoa: "remove all diagonal arrows, just highlighted when
   hovering": `.mc-a`) */
a.mc-course-b,a.mc-grp-c{color:inherit;text-decoration:none;}
a.mc-grp-c{color:var(--ink-2);}
/* a rename refused, under the name in the class window's header (264.6) */
.mc-ren-err{display:block;margin:6px 0 0;font-size:13px;font-weight:400;font-family:var(--sans);}
.mc-lt td{white-space:nowrap;}
.mc-sort{cursor:pointer;user-select:none;}
.mc-cl{border:0;background:none;font:inherit;font-size:13px;color:var(--ink);cursor:pointer;padding:0;display:inline-flex;align-items:center;}
.mc-att{display:inline-flex;align-items:center;gap:8px;}
.mc-att .kls-bar{display:inline-block;width:64px;height:5px;}
.mc-att .kls-bar.low i{background:var(--wrong);}
.mc-warn{color:var(--wrong);font-weight:600;}

/* 251.1: the class window's tables fix their column widths (they are a
   roster); on this page a table sizes to its content, and a class name wraps
   rather than running over the next column. */
.mc .cw-tbl{table-layout:auto;}
.mc .cw-tbl th{width:auto!important;}
.mc-t td.mc-clt{white-space:normal;min-width:110px;}
.mc-t td.mc-wrap{white-space:normal;min-width:180px;}
.mc-t td.mc-course{white-space:normal;min-width:170px;}

/* 251.2: each class day of the week its own time and length */
.att-slots{display:grid;gap:6px;}
.att-slot{display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;background:var(--card);border:1px solid var(--line);border-radius:10px;padding:8px 10px;}
.att-slot b{min-width:38px;font-size:13.5px;padding-bottom:8px;}
.att-slot label{display:grid;gap:4px;font-size:12.5px;color:var(--ink-2);}
.att-slot .field{width:auto;}
/* 258.3: when the session ends, beside its start and length; Lặp lại hằng tuần · Một buổi */
.att-ends{display:grid;gap:4px;font-size:12.5px;color:var(--ink-2);}
.att-ends b{min-width:0;padding:0;font-size:15px;font-variant-numeric:tabular-nums;color:var(--ink);line-height:36px;}
.att-rep{justify-self:start;}

/* ===================== fees (build 252, 95-fees.js) =====================
   The cycle as dots (filled = a session counted), the bill with its QR, the
   Học phí card on Tổng quan, Nhận học phí, the learner's own card. A QR is
   always black on white with its own quiet edge, in dark mode too, so a bank
   app reads it. */
.mc-kpis.six{grid-template-columns:repeat(6,minmax(0,1fr));}
@media (max-width:1100px){.mc-kpis.six{grid-template-columns:repeat(3,minmax(0,1fr));}}
@media (max-width:560px){.mc-kpis.six{grid-template-columns:repeat(2,minmax(0,1fr));}}
.mc-kpi.money b{font-family:var(--sans,inherit);font-size:clamp(17px,2vw,21px);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.fee-dots{display:inline-flex;gap:3px;align-items:center;flex-wrap:wrap;}
.fee-dots i{width:8px;height:8px;border-radius:2px;background:var(--panel);box-shadow:inset 0 0 0 1px var(--line-2);}
.fee-dots i.on{background:var(--accent);box-shadow:none;}
.fee-dots.big i{width:15px;height:15px;border-radius:4px;}
.fee-bar{display:inline-block;width:64px;height:6px;border-radius:6px;background:var(--panel);overflow:hidden;}
.fee-bar i{display:block;height:100%;background:var(--accent);}
.fee-bar.big{width:160px;height:10px;}
.fee-cell{display:inline-flex;align-items:center;gap:8px;border:1px solid transparent;background:none;border-radius:8px;padding:3px 6px;font:inherit;font-size:12.5px;color:var(--ink);cursor:pointer;}
.fee-cell:hover{border-color:var(--line-2);background:var(--card);}
.fee-link{border:0;background:none;padding:0;margin-left:6px;font:inherit;font-size:12.5px;color:var(--accent-text);text-decoration:underline;cursor:pointer;}
td>.fee-link:first-child{margin-left:0;}
.fee-form{display:grid;gap:12px;}
.fee-form>label{display:grid;gap:4px;font-size:12.5px;color:var(--ink-2);}
.fee-form .field{width:100%;max-width:420px;}
.fee-check{display:flex!important;gap:8px;align-items:center;font-size:13px!important;color:var(--ink)!important;}
.fee-check input{width:auto;}
.fee-ln,.fee-bill{display:grid;gap:14px;}
.fee-ln-top{display:flex;gap:10px;align-items:center;flex-wrap:wrap;}
.fee-days{display:grid;gap:5px;border:1px solid var(--line);border-radius:10px;padding:8px 10px;background:var(--card);}
.fee-days div{display:flex;justify-content:space-between;gap:8px;align-items:center;font-size:13px;}
.fee-bills td{white-space:nowrap;}
/* 277.3: "Huỷ hoá đơn" kept apart from the date (a reset elsewhere took its margin) */
.apop .fee-bills td .fee-link{margin-left:8px;}
button.fee-paidchip{border:0;cursor:pointer;font:inherit;font-size:11.5px;font-weight:600;}
.fee-billbox{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:16px;align-items:start;}
@media (max-width:620px){.fee-billbox{grid-template-columns:minmax(0,1fr);}}
.fee-msgl{display:grid;gap:4px;font-size:12.5px;color:var(--ink-2);min-width:0;}
.fee-msg{width:100%;min-height:170px;resize:vertical;font:inherit;font-size:13.5px;line-height:1.5;white-space:pre-wrap;}
.fee-qrcol{display:grid;gap:6px;justify-items:center;text-align:center;max-width:220px;font-size:12px;color:var(--ink-2);}
.fee-qr{display:block;border-radius:10px;border:1px solid var(--line);background:#fff;image-rendering:pixelated;max-width:100%;height:auto;}
.fee-qr-miss{display:grid;place-items:center;background:var(--panel);color:var(--ink-3);font-size:12px;padding:10px;box-sizing:border-box;}
.fee-today{gap:12px;}
.fee-g{display:grid;gap:2px;}
.fee-gh{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin-bottom:2px;}
.fee-row{display:flex;gap:10px;align-items:center;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--line);flex-wrap:wrap;}
.fee-g .fee-row:last-child{border-bottom:0;}
/* 257.2: the due date in the bill, and a bill past it */
.fee-due{display:flex;gap:4px 10px;align-items:center;flex-wrap:wrap;font-size:12.5px;color:var(--ink-2);grid-column:1;}
.fee-billbox>.fee-qrcol{grid-column:2;grid-row:1/span 2;}
/* 258.4: in the bill the caption is its own row, the QR level with the message box */
.fee-billbox.bill{row-gap:6px;}
.fee-billbox.bill>.fee-msg{grid-column:1;}
.fee-billbox.bill>.fee-qrcol{grid-row:2/span 2;}
@media (max-width:620px){.fee-billbox>.fee-qrcol,.fee-billbox.bill>.fee-qrcol{grid-column:1;grid-row:auto;}}
.fee-due .field{width:auto;padding:4px 8px;font-size:13px;}
.fee-due-s{color:var(--ink-3);}
.fee-due-s.late,.fee-row.late .fee-who span{color:var(--wrong);font-weight:600;}
.fee-who{display:grid;gap:1px;flex:1;min-width:0;text-align:left;border:0;background:none;padding:0;font:inherit;font-size:13px;color:var(--ink);cursor:pointer;}
.fee-who span,.fee-ask span{font-size:12px;color:var(--ink-3);}
.fee-ask>div:first-child{display:grid;gap:2px;flex:1 1 200px;min-width:0;font-size:13px;}
.fee-btns{display:flex;gap:6px;}
.fee-pay>div:first-child{display:grid;gap:2px;}
.fee-note{background:var(--done-wash);color:var(--done);border-radius:10px;padding:8px 12px;font-size:13px;margin-bottom:12px;
  display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;}
.fee-note>span{flex:1;min-width:200px;}
/* cancelling a bill (254.5): asked once more, in place */
.fee-cancel{flex-basis:100%;display:grid;gap:6px;background:var(--wrong-wash);color:var(--ink);border-radius:10px;padding:10px 12px;font-size:13px;margin:4px 0 8px;}
.fee-cancel span{color:var(--ink-2);line-height:1.5;}
.fee-cancel .fee-btns{justify-content:flex-end;}.cls-btn.fee-x{background:var(--wrong);border-color:var(--wrong);color:#fff;}
.fee-me{display:grid;gap:4px;margin-top:6px;padding-top:6px;border-top:1px solid var(--line);}
.fee-me-top{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.fee-me-top>span{color:var(--ink-2);}
.fee-me-s,.fee-me-rule{color:var(--ink-2);}
.fee-me-rule{font-size:11.5px;color:var(--ink-3);}
.fee-me-qr{justify-self:center;margin:6px 0;}

/* ===================== moving a class day, the payer's report (build 253) =====================
   A class still to come can be grabbed on the calendar; the date it is
   dragged over is ringed. A day moved away stays, struck through and dashed,
   saying where it went. The report is a sheet of paper on screen and in
   print: the buttons are not printed. */
.mc-pill.drag{cursor:grab;}
.mc-pill.drag:active{cursor:grabbing;}
.mc-pill.moved{background:transparent;border:1px dashed var(--line-2);border-left:3px dashed var(--pc);color:var(--ink-3);text-decoration:line-through;cursor:default;}
.mc-pill.moved span{text-decoration:none;display:inline-block;}
.mc-cell.over{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent);background:var(--accent-wash);}
.mc-ag-w{display:grid;gap:4px;}
.mc-ag-b{display:flex;gap:6px;flex-wrap:wrap;}
.mc-ag.moved{border-style:dashed;color:var(--ink-3);cursor:default;}
.mv-was{font-size:12px;color:var(--accent-text);}
.mv-was .fee-link{margin-left:8px;}
.mv-ways{display:grid;gap:8px;}
.mv-way{display:grid!important;grid-template-columns:auto minmax(0,1fr);gap:2px 10px;align-items:start;border:1px solid var(--line-2);border-radius:10px;
  padding:10px 12px;background:var(--card);cursor:pointer;color:var(--ink)!important;}
.mv-way input{grid-row:span 2;margin-top:3px;width:auto;}
.mv-way b{font-size:13.5px;}
.mv-way span{font-size:12.5px;color:var(--ink-2);}
.mv-way.on{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent);background:var(--accent-wash);}
.mv-way.no{opacity:.6;cursor:not-allowed;}
.rpt-page{max-width:720px;margin:0 auto;padding:24px 16px 48px;display:grid;gap:14px;align-content:start;background:var(--paper);min-height:100vh;box-sizing:border-box;}
.rpt{border:1px solid var(--line);border-radius:14px;background:var(--card);padding:18px;display:grid;gap:10px;align-content:start;}
.rpt-h{display:flex;justify-content:space-between;align-items:baseline;gap:10px;border-bottom:1px solid var(--line);padding-bottom:8px;}
.rpt-h span{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);font-weight:600;}
.rpt-brand{font-family:var(--serif);font-size:18px;}
.rpt-name{margin:4px 0 0;font-family:var(--serif);font-size:24px;}
.rpt-meta{font-size:13px;color:var(--ink-2);}
.rpt-tiles{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-top:4px;}
@media (max-width:560px){.rpt-tiles{grid-template-columns:repeat(2,minmax(0,1fr));}}
.rpt-tiles div{display:grid;background:var(--panel);border-radius:10px;padding:8px 10px;}
.rpt-tiles b{font-family:var(--serif);font-size:22px;font-variant-numeric:tabular-nums;}
.rpt-tiles span{font-size:12px;color:var(--ink-3);}
.rpt-days{display:grid;gap:5px;}
.rpt-days div{display:flex;justify-content:space-between;gap:10px;align-items:center;font-size:13px;border-bottom:1px solid var(--line);padding:4px 0;}
.rpt-days div:last-child{border-bottom:0;}
.rpt-note{background:var(--panel);border-radius:10px;padding:10px 12px;}
.rpt-note b{font-size:12.5px;}
.rpt-note p{margin:4px 0 0;white-space:pre-wrap;font-size:14px;line-height:1.5;}
.rpt-rule{margin:0;font-size:11.5px;color:var(--ink-3);}
.rpt-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;font-size:12.5px;color:var(--ink-3);}
.rpt-gone{border:1px solid var(--line);border-radius:14px;background:var(--card);padding:24px;display:grid;gap:8px;text-align:center;}
.rpt-gone h2{margin:0;font-family:var(--serif);font-size:20px;}
.rpt-gone p{margin:0;color:var(--ink-2);}
.rpt-link{display:grid;gap:8px;}
.rpt-link .field{width:100%;font-size:12.5px;}
@media print{
  .rpt-noprint{display:none!important;}
  .rpt-page{padding:0;min-height:0;}
  .rpt{border:0;padding:0;}
}

/* ===================== the teacher page, Hồ sơ của tôi (build 254) =====================
   254.1: the public page is a sheet like the class invitation (`.inv-sheet`):
   the navy strip with the logo and a terracotta line, a hero on the
   invitation's soft wash with the photo ringed in white, the booking button,
   then the courses as a grid of cover tiles (4 across, 2 on a phone), the
   video, learners' words. In Hồ sơ the form sits beside the same page drawn
   small, as its preview; on a phone, under. */
/* 258.2 (Khoa, a picture of his page: "improve it to be professional and
   attractive but still carry the brand theme. currently, the background color
   in light mode is not good"): the page around the sheet is a quiet grey of
   the paper, no peach glow; the strip and the hero are one navy head, the
   brand's own colour with a breath of terracotta in its corner, the photo in
   a white ring, the words light, the terracotta line under it; the booking
   button is a bar, its words on the left and an arrow on the right. The head
   is navy in dark mode too. */
.tp-page{min-height:100vh;box-sizing:border-box;padding:32px 16px 56px;
  background:color-mix(in srgb,var(--ink) 4%,var(--paper));display:grid;align-content:start;}
.tp-page>*{width:100%;max-width:800px;margin:0 auto;}
.tp{border-radius:20px;overflow:hidden;background:var(--card);border:1px solid var(--line);
  box-shadow:0 1px 3px rgba(0,18,47,.06),0 18px 48px rgba(0,18,47,.12);}
.tp-head{color:#F5F4EF;border-bottom:3px solid var(--accent);
  background:radial-gradient(circle at 96% -30%,rgba(217,119,87,.24),transparent 44%),
    radial-gradient(circle at -5% 125%,rgba(27,175,122,.13),transparent 42%),
    linear-gradient(135deg,#00122F 0%,#001A44 52%,#00081A 100%);}
.tp-top{display:flex;align-items:center;gap:9px;padding:16px 28px 0;color:#fff;font-family:var(--serif);font-size:15px;font-weight:700;}
.tp-top .brand-logo{width:24px;height:24px;}
.tp-hero{display:flex;gap:24px;align-items:center;padding:20px 28px 30px;}
.tp-av{width:124px;height:124px;border-radius:50%;overflow:hidden;flex:none;display:grid;place-items:center;
  border:4px solid #fff;box-shadow:0 10px 30px rgba(0,0,0,.35);
  background:linear-gradient(135deg,var(--accent),var(--cat3,#1baf7a));color:#fff;font-family:var(--serif);font-size:46px;font-weight:700;}
.tp-av img{width:100%;height:100%;object-fit:cover;}
.tp-av.sm{width:64px;height:64px;font-size:26px;border-width:2px;}
.tp-id{display:grid;gap:6px;min-width:0;}
.tp-eyebrow{font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#F2A88C;}
.tp-id h1{margin:0;font-family:var(--serif);font-size:34px;line-height:1.1;color:#fff;}
.tp-line{margin:0;font-size:15.5px;color:rgba(245,244,239,.8);line-height:1.45;}
.tp-years{justify-self:start;font-size:12px;font-weight:600;color:#FFD3C2;background:rgba(217,119,87,.2);
  border:1px solid rgba(242,168,140,.6);border-radius:20px;padding:3px 11px;margin-top:2px;}
/* 257.2: the years and the qualifications in one row of pills */
.tp-badges{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px;}
.tp-badges .tp-years{margin-top:0;}
.tp-qual{font-size:12px;font-weight:500;color:rgba(255,255,255,.9);background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.24);border-radius:20px;padding:3px 11px;}
.tp-body{padding:24px 28px 28px;display:grid;gap:22px;}
a.tp-book{display:flex;align-items:center;justify-content:space-between;gap:14px;text-align:left;text-decoration:none;padding:14px 16px 14px 22px;border-radius:14px;
  background:var(--accent);color:var(--on-accent,#fff);box-shadow:0 8px 22px rgba(217,119,87,.3);transition:transform .15s ease,box-shadow .15s ease;}
.tp-book-t{display:grid;gap:2px;}
a.tp-book .tp-book-t>span,a.tp-book.sm>span{font-size:17px;font-weight:600;}
a.tp-book small{font-size:12.5px;opacity:.9;}
a.tp-book i{font-style:normal;flex:none;width:40px;height:40px;border-radius:50%;display:grid;place-items:center;background:rgba(255,255,255,.2);font-size:19px;}
a.tp-book:hover{background:var(--accent-deep,var(--accent));transform:translateY(-1px);box-shadow:0 12px 26px rgba(217,119,87,.36);}
@media (prefers-reduced-motion:reduce){a.tp-book,a.tp-book:hover{transition:none;transform:none;}}
a.tp-book.sm{justify-content:center;padding:11px 16px;box-shadow:none;}
.tp-sec{display:grid;gap:10px;}
.tp-sec h2{margin:0;font-family:var(--serif);font-size:18px;display:flex;align-items:center;gap:8px;}
.tp-n{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--accent-text);background:var(--accent-wash);border-radius:20px;padding:1px 8px;}
.tp-cs{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;}
@media (max-width:640px){.tp-cs{grid-template-columns:repeat(2,minmax(0,1fr));}}
.tp-c{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--card);
  text-decoration:none;color:var(--ink);transition:transform .15s ease,box-shadow .15s ease,border-color .15s ease;}
.tp-c:hover{transform:translateY(-2px);border-color:var(--accent);box-shadow:0 6px 16px rgba(0,0,0,.1);}
@media (prefers-reduced-motion:reduce){.tp-c,.tp-c:hover{transition:none;transform:none;}}
.tp-c-img{aspect-ratio:16/10;background:var(--panel);overflow:hidden;display:grid;place-items:center;}
.tp-c-img img{width:100%;height:100%;object-fit:cover;display:block;}
.tp-c-ph{font-family:var(--serif);font-size:20px;font-weight:700;color:var(--ink-3);}
.tp-c-t{display:grid;gap:5px;padding:8px 10px 10px;align-content:start;flex:1;}
.tp-c-t b{font-size:13px;line-height:1.3;font-weight:600;}
.tp-lv{justify-self:start;font-size:10.5px;font-weight:600;border-radius:20px;padding:1px 8px;background:var(--panel);color:var(--ink-2);}
.tp-lv.lv1{background:var(--lv1-wash);color:var(--lv1);}
.tp-lv.lv2{background:var(--lv2-wash);color:var(--lv2);}
.tp-lv.lv3{background:var(--lv3-wash);color:var(--lv3);}
.tp-vid{position:relative;aspect-ratio:16/9;border-radius:12px;overflow:hidden;background:#000;}
.tp-vid iframe{position:absolute;inset:0;width:100%;height:100%;border:0;}
.tp-qs{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;}
.tp-q{margin:0;background:var(--panel);border-radius:12px;padding:12px 14px;border-left:3px solid var(--accent);}
.tp-q p{margin:0;font-size:14px;line-height:1.5;}
.tp-q cite{display:block;font-style:normal;font-size:12px;color:var(--ink-3);margin-top:4px;}
.tp-end{display:grid;gap:8px;border-top:1px dashed var(--line-2);padding-top:16px;}
.tp-end p{margin:0;font-size:14px;color:var(--ink-2);text-align:center;}
@media (max-width:520px){.tp-page{padding:16px 10px 40px;}.tp-top{padding:14px 16px 0;}
  .tp-hero{flex-direction:column;text-align:center;padding:16px 16px 24px;gap:16px;}.tp-id{justify-items:center;}
  .tp-years{justify-self:center;}.tp-badges{justify-content:center;}.tp-av{width:100px;height:100px;font-size:38px;}.tp-id h1{font-size:27px;}
  .tp-body{padding:18px 16px 22px;}a.tp-book{padding:12px 12px 12px 16px;}}
.pf-two{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,380px);gap:18px;align-items:start;}
@media (max-width:900px){.pf-two{grid-template-columns:minmax(0,1fr);}}
.pf-prev{display:grid;gap:6px;position:sticky;top:0;}
.pf-prev-in{border-radius:18px;max-height:680px;overflow:auto;}
.pf-prev-in .tp-cs{grid-template-columns:repeat(2,minmax(0,1fr));}
.pf-prev-in .tp-top{padding:12px 16px 0;}
.pf-prev-in .tp-hero{padding:12px 16px 18px;gap:14px;}
.pf-prev-in .tp-body{padding:16px 16px 18px;}
.pf-prev-in .tp-av{width:72px;height:72px;font-size:28px;}
.pf-prev-in .tp-id h1{font-size:21px;}
.pf-l{font-size:12.5px;color:var(--ink-2);}
.pf-slug{display:flex;align-items:center;gap:4px;max-width:420px;}
.pf-slug span{font-size:12.5px;color:var(--ink-3);white-space:nowrap;}
.pf-slug .field{flex:1;min-width:0;}
.pf-photo{display:flex;align-items:center;gap:12px;flex-wrap:wrap;}
.pf-up{cursor:pointer;}
.pf-q{display:grid;gap:6px;border:1px solid var(--line);border-radius:10px;padding:8px 10px;background:var(--card);}
.pf-q textarea{width:100%;resize:vertical;}
.pf-pub{font-weight:600;}
.pf-sl{list-style:none;margin:0;padding:0;display:flex;gap:14px;flex-wrap:wrap;font-size:13px;}
.pf-sl li{display:flex;align-items:center;gap:6px;color:var(--ink);}
.pf-sl li i{width:18px;height:18px;border-radius:50%;border:1.5px solid var(--line-2);display:grid;place-items:center;font-style:normal;font-size:11px;}
.pf-sl li.ok{color:var(--ink-3);text-decoration:line-through;}
.pf-sl li.ok i{background:var(--done-wash);border-color:var(--done);color:var(--done);}
a.pf-join{display:inline-block;justify-self:start;text-decoration:none;margin:4px 0;}

/* ===================== the week view, the column picker (build 254.2) =====================
   Lịch lớp's week: like a phone or Google calendar, the days as columns over
   an hour grid (48 px an hour, 34 on a phone since 264.1, `--hh`), each class a block in its class
   colour with white words, as tall as it lasts; past classes paler, a class
   in progress ringed, a class off dashed and struck through, a moved one
   dashed where it was. The grid scrolls sideways inside its card on a phone. */
.mc-seg{display:inline-flex;border:1px solid var(--line-2);border-radius:9px;overflow:hidden;}
.mc-seg button{border:0;border-right:1px solid var(--line-2);background:var(--card);font:inherit;font-size:13px;padding:5px 14px;cursor:pointer;color:var(--ink-2);}
.mc-seg button:last-child{border-right:0;}
.mc-seg button[aria-pressed="true"]{background:var(--accent);color:var(--on-accent);font-weight:600;}
.mc-cal-h{flex-wrap:wrap;}
.mc-cal-h h3{margin:0 4px;}
/* 254.7: on a computer the page scrolls the grid and the day headers stay at its top
   (sticky needs no scrolling box between them and the page); a phone keeps the
   grid's own box, scrolling both ways, its headers stuck inside it */
.mc-wk-w{overflow:auto;-webkit-overflow-scrolling:touch;max-height:max(360px,calc(100vh - 160px));}
/* the page's 16px top padding is inside where sticky holds, so the headers sit 16px higher, flush under the top bar */
@media (min-width:1001px){.mc-wk-w{overflow:visible;max-height:none;}.mc-wk-w .mc-wk-day,.mc-wk-w .mc-wk-corner{top:-16px;}}
/* 257.2 (Khoa, a phone picture of the week: "the calendar is stuck on mobile
   view"): the grid's own box inside the scrolling page took the swipe and
   held it, so on a phone and a tablet there is no box any more either: the page
   scrolls, the headers stay at its top, and on a phone the seven days fit the
   width (narrow hours, names that wrap, no second line in a block) */
@media (max-width:1000px){.mc-wk-w{overflow:visible;max-height:none;}.mc-wk-w .mc-wk-day,.mc-wk-w .mc-wk-corner{top:-16px;}}
@media (max-width:700px){.mc-wk-w{--mc-hw:30px;--mc-cw:0px;}.mc-wk-w .mc-wk{min-width:0;}
  .mc-wk-corner{font-size:0;}.mc-wk-hours span{font-size:9.5px;right:3px;}
  .mc-wk-day span{font-size:9.5px;letter-spacing:0;}.mc-wk-day b{font-size:14px;width:26px;height:26px;}
  .mc-wk-w:not(.one) .mc-wk-ev{padding:2px 3px;font-size:9.5px;line-height:1.2;border-radius:4px;}
  .mc-wk-w:not(.one) .mc-wk-ev b{white-space:normal;overflow-wrap:anywhere;}
  .mc-wk-w:not(.one) .mc-wk-ev span{display:none;}}
.mc-wk{display:grid;min-width:700px;}
.mc-wk-w.one .mc-wk{min-width:0;}
.mc-wk-w.one .mc-wk-ev{font-size:12.5px;padding:5px 10px;gap:1px;}
.mc-wk-w.one .mc-wk-ev span{white-space:normal;}
.mc-wk-none{position:absolute;top:8px;left:10px;margin:0;}
.mc-wk-corner{font-size:10.5px;color:var(--ink-3);align-self:stretch;display:flex;align-items:flex-end;justify-content:flex-end;padding:0 4px 6px 0;
  position:sticky;top:0;z-index:4;background:var(--card);border-bottom:1px solid var(--line-2);}
.mc-wk-day{border:0;background:none;font:inherit;cursor:pointer;display:grid;justify-items:center;gap:0;padding:3px 0 5px;color:var(--ink-2);
  border-bottom:1px solid var(--line-2);position:sticky;top:0;z-index:4;background:var(--card);}
.mc-wk-day span{font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink-3);}
.mc-wk-day b{font-size:18px;font-weight:500;width:32px;height:32px;border-radius:50%;display:grid;place-items:center;}
.mc-wk-day.now b{background:var(--accent);color:var(--on-accent);}
.mc-wk-day.sel:not(.now) b{box-shadow:inset 0 0 0 2px var(--accent);}
.mc-wk-hours{position:relative;}
.mc-wk-hours span{position:absolute;right:6px;transform:translateY(-50%);font-size:10.5px;color:var(--ink-3);white-space:nowrap;}
.mc-wk-hours span:first-child{transform:none;}
.mc-wk-col{position:relative;border-left:1px solid var(--line);
  background:repeating-linear-gradient(to bottom,var(--line) 0 1px,transparent 1px var(--hh));}
.mc-wk-col.now{background-color:var(--accent-wash);}
.mc-wk-col.over{box-shadow:inset 0 0 0 2px var(--accent);}
/* 254.10: an empty time can be pressed to make a class there */
.mc-wk-col.can-new{cursor:cell;}
/* 255: the class calendar card, and its link from Lịch lớp */
.pf-cal-url{font-size:12px;}
.pf-cal-go{justify-content:flex-start;flex-wrap:wrap;}
.pf-cal-how summary{cursor:pointer;font-size:13px;color:var(--ink-2);}
.pf-cal-how ol{margin:6px 0 0;padding-left:20px;display:grid;gap:4px;font-size:13px;color:var(--ink-2);line-height:1.5;}
.mc-gcal{margin-left:0;text-align:left;font-size:12.5px;}
/* 257: Học thử, the presentation, the invite */
/* 258.1 (Khoa: "optimize the left top card to be bottom aligned with the right one, then move up the pipeline"): the two cards end on one line, the parts in two columns, so the Pipeline comes up into the screen */
.dm-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;align-items:stretch;}
@media (max-width:860px){.dm-two{grid-template-columns:minmax(0,1fr);}}
.dm-eye{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);font-weight:600;}
/* 258.2 (Khoa: "shorten the text (refer to img 3 …) to fit the top half … Then,
   spend the bottom half for the pipeline block"): the presentation's parts are
   the demo page's own 30-minute bar, each part as long as its minutes, a
   press opens the presentation there; the tab fills the screen and the
   Pipeline takes what the two cards leave */
.mc-ov.dm-tab{display:flex;flex-direction:column;min-height:100%;}
.dm-agenda{display:grid;gap:8px;}
.dm-ag{display:flex;gap:6px;}
.dm-ag button{flex:1 1 0;min-width:86px;display:grid;gap:6px;border:0;background:none;padding:0;font:inherit;text-align:left;cursor:pointer;}
.dm-ag i{display:block;height:6px;border-radius:3px;background:var(--accent);}
.dm-ag .s1 i{background:color-mix(in srgb,var(--accent) 74%,var(--card));}
.dm-ag .s2 i{background:color-mix(in srgb,var(--accent) 52%,var(--card));}
.dm-ag .s3 i{background:color-mix(in srgb,var(--accent) 34%,var(--card));}
.dm-ag span{font-size:12.5px;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.dm-ag em{font-style:normal;font-weight:400;color:var(--ink-3);}
.dm-ag button:hover span{color:var(--accent-text);}
.cls-btn.dm-big{padding:9px 18px;font-size:15px;}
.dm-mine{display:flex;gap:10px;align-items:center;justify-content:space-between;flex-wrap:wrap;border:1px dashed var(--accent);border-radius:10px;padding:10px 12px;background:var(--accent-wash);}
.dm-mine b{overflow-wrap:anywhere;font-size:13px;}
.dm-nobook{display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:space-between;background:var(--wrong-wash);color:var(--ink);border-radius:10px;padding:10px 12px;font-size:13.5px;}
.dm-rchip{display:inline-block;font-family:var(--mono);font-size:11.5px;font-weight:600;background:var(--panel);border-radius:6px;padding:1px 7px;margin-right:4px;}
.dm-step{display:inline-flex;gap:4px;align-items:center;font-size:12.5px;}
.dm-step i{width:10px;height:10px;border-radius:50%;background:var(--panel);box-shadow:inset 0 0 0 1px var(--line-2);}
.dm-step i.on{background:var(--done);box-shadow:none;}
.dm-step span{margin-left:6px;color:var(--ink-2);}
/* 258: the Pipeline. Columns side by side, scrolling sideways in their own
   row (a board is wider than a phone), a card dragged between them; the won
   column green; "Chúc mừng!" at the foot of the screen, over nothing */
.dm-pipe{display:flex;flex-direction:column;gap:12px;flex:1 1 auto;}
.dm-pipe-h{display:flex;gap:10px 16px;align-items:flex-end;flex-wrap:wrap;}
.dm-pipe-h>div:first-child{flex:1 1 320px;display:grid;gap:2px;}
.dm-pipe-h h3{margin:0;}
.dm-pipe-k{display:flex;gap:16px;flex-wrap:wrap;font-size:12.5px;color:var(--ink-2);}
.dm-pipe-k b{color:var(--ink);}
.dm-pipe-k .won b{color:var(--done);}
/* 258.2 (Khoa: "remove the background color + outline, no radius at corner or
   you suggest me the best way"): a column is its name, its count, its fee
   line and its cards, with no box; it is tinted only while a card is dragged
   over it (the tint reaches a little past its edges, so nothing moves). The
   columns share the width (five fit a computer) and run to the screen's foot. */
.dm-board{display:flex;gap:20px;overflow-x:auto;padding:0 6px 6px;margin:0 -6px;align-items:stretch;flex:1 1 auto;min-height:300px;scrollbar-width:thin;}
.dm-col{flex:1 0 200px;min-width:0;display:flex;flex-direction:column;gap:8px;padding:2px 0 8px;background:none;border:0;border-radius:0;}
.dm-col.over{background:color-mix(in srgb,var(--accent) 7%,transparent);box-shadow:0 0 0 6px color-mix(in srgb,var(--accent) 7%,transparent);}
.dm-col.won.over{background:color-mix(in srgb,var(--done) 9%,transparent);box-shadow:0 0 0 6px color-mix(in srgb,var(--done) 9%,transparent);}
.dm-col-h{display:flex;align-items:center;gap:6px;}
.dm-col-n{flex:1 1 auto;border:0;background:none;font:inherit;font-size:15px;font-weight:600;color:var(--ink);text-align:left;cursor:pointer;padding:2px 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.dm-col-n:hover{color:var(--accent-text);}
.dm-won{color:var(--done);font-size:14px;}
.dm-col-c{font-size:11px;color:var(--ink-3);}
.dm-col-sum{display:flex;align-items:center;gap:8px;font-size:12.5px;}
.dm-col-sum .kls-bar{flex:1;display:block;height:5px;}
.dm-col.won .dm-col-sum .kls-bar i{background:var(--done);}
.dm-col-ed{display:grid;gap:8px;}
.dm-col-none{font-size:12.5px;color:var(--ink-3);padding:14px 0;text-align:center;}
.dm-addcol{flex:none;}
.dm-card{background:var(--card);border:1px solid var(--line-2);border-radius:10px;padding:9px 10px;display:grid;gap:4px;cursor:grab;box-shadow:0 1px 2px rgba(0,0,0,.05);}
.dm-card:hover{border-color:var(--ink-3);box-shadow:0 3px 10px rgba(0,0,0,.08);}
.dm-card.drag{opacity:.45;}
.dm-card-t{display:flex;gap:6px;align-items:flex-start;}
.dm-card-n{flex:1 1 auto;border:0;background:none;padding:0;font:inherit;font-size:14px;font-weight:600;color:var(--ink);text-align:left;cursor:pointer;min-width:0;overflow-wrap:anywhere;}
.dm-card-n:hover{color:var(--accent-text);}
/* where a card came from (258.2): a dot and a word at its foot, before the date */
.dm-src{white-space:nowrap;color:var(--ink-2);}
.dm-src::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:5px;vertical-align:1px;background:var(--ink-3);}
.dm-src.booking::before{background:var(--cat1);}
.dm-src.demo::before{background:var(--accent);}
.dm-card-f{font-size:13px;color:var(--ink-2);}
.dm-card-c,.dm-card-w{font-size:12px;color:var(--ink-3);overflow-wrap:anywhere;}
.dm-card-r .dm-rchip{font-size:10.5px;}
/* 259: the first bill on a card (waiting, paid, moved into the class fees), the nudge a day after the trial, the bill in the panel */
.dm-bill{justify-self:start;font-size:11.5px;font-weight:600;border-radius:7px;padding:1px 9px;background:var(--accent-wash);color:var(--accent-text);}
.dm-bill.ok{background:color-mix(in srgb,var(--done) 14%,transparent);color:var(--done);}
.dm-bill.moved{background:var(--panel);color:var(--ink-2);}
.dm-remind{justify-self:start;border:1px dashed var(--accent);background:none;border-radius:20px;padding:1px 9px;font:inherit;font-size:11.5px;color:var(--accent-text);cursor:pointer;}
.dm-remind:hover{background:var(--accent-wash);}
.dm-billbox{display:grid;gap:8px;font-size:13px;color:var(--ink-2);border:1px solid var(--line-2);border-radius:10px;padding:10px 12px;background:var(--card);}
.dm-billbox b{color:var(--ink);}
.att-f-row.dm-b-row{align-items:flex-start;}
.dm-b-row .field{height:40px;box-sizing:border-box;}
.dm-pop .mc-chips{flex-wrap:wrap;}
.dm-bks{display:grid;gap:6px;}
.dm-bk{display:flex;justify-content:space-between;gap:10px;align-items:center;width:100%;border:1px solid var(--line-2);background:var(--card);border-radius:9px;padding:8px 10px;cursor:pointer;font:inherit;font-size:13px;color:var(--ink);text-align:left;}
.dm-bk small{font-size:11.5px;color:var(--ink-3);white-space:nowrap;}
.dm-bk:hover:not(:disabled),.dm-bk.on{border-color:var(--accent);}
.dm-bk:disabled{opacity:.55;cursor:default;}
.dm-cheer{position:fixed;left:50%;bottom:calc(28px + env(safe-area-inset-bottom,0px));transform:translateX(-50%);z-index:400;pointer-events:none;}
.dm-cheer-in{position:relative;display:grid;gap:3px;text-align:center;min-width:300px;max-width:calc(100vw - 32px);padding:14px 22px;border-radius:16px;
  background:var(--card);border:1px solid var(--done);box-shadow:0 14px 40px rgba(0,0,0,.28);}
.dm-cheer b{font-family:var(--serif);font-size:22px;color:var(--done);}
.dm-cheer span{font-size:14px;color:var(--ink);}
.dm-cheer small{font-size:12px;color:var(--ink-3);}
.dm-bits{position:absolute;inset:0;pointer-events:none;}
.dm-bits i{position:absolute;left:50%;top:40%;width:7px;height:11px;border-radius:2px;background:var(--bc);opacity:0;}
@media (prefers-reduced-motion:no-preference){
  .dm-cheer-in{animation:dm-in .35s ease-out;}
  .dm-bits i{animation:dm-bit 1.3s ease-out forwards;}}
@keyframes dm-in{from{opacity:0;transform:translateY(10px) scale(.96);}to{opacity:1;transform:none;}}
@keyframes dm-bit{0%{transform:translate(0,0) rotate(0);opacity:1;}100%{transform:translate(var(--dx),var(--dy)) rotate(240deg);opacity:0;}}
@media (max-width:560px){.dm-col{flex:0 0 78vw;}}
.dm-step span.flag{color:var(--err-ink);}
.dm-acts{white-space:nowrap;}
.scrim.dm-pres{z-index:72;flex-direction:column;background:var(--paper);}
.dm-top{display:flex;align-items:center;gap:8px;flex-wrap:wrap;background:#00122F;color:#F5F4EF;padding:8px max(14px,env(safe-area-inset-right,0px)) 8px max(14px,env(safe-area-inset-left,0px));padding-top:calc(8px + env(safe-area-inset-top,0px));}
.dm-top b{font-family:var(--serif);font-size:16px;}
.dm-tb{border:1px solid rgba(255,255,255,.3);background:transparent;color:inherit;border-radius:8px;padding:5px 11px;font:inherit;font-size:13px;cursor:pointer;}
.dm-tb:hover{background:rgba(255,255,255,.1);}
.dm-frame{flex:1;width:100%;border:0;background:#fff;}
.dm-msg{flex:1;display:grid;place-items:center;padding:24px;text-align:center;}
.dm-inv-scrim{position:absolute;inset:0;background:rgba(0,12,30,.42);display:grid;place-items:center;padding:16px;z-index:3;}
.dm-inv{width:min(620px,100%);max-height:100%;overflow-y:auto;background:var(--paper);border-radius:14px;box-shadow:0 10px 40px rgba(0,0,0,.35);padding:18px;display:grid;gap:12px;}
.dm-inv-h{display:flex;align-items:center;gap:8px;}
.dm-inv-h h3{margin:0;font-family:var(--serif);font-size:19px;}
.dm-inv .p-close{position:static;}
.dm-lab{display:grid;gap:5px;font-size:13px;color:var(--ink-2);font-weight:500;}
.dm-route{margin:0;padding:0;list-style:none;display:grid;gap:8px;}
.dm-route li{display:grid;gap:8px;border:1px solid var(--line);border-radius:10px;padding:10px 12px;background:var(--card);}
.dm-route li.now{border-color:var(--accent);box-shadow:inset 3px 0 0 var(--accent);}
.dm-route li>div:first-child{display:grid;}
.dm-cls{display:grid;gap:8px;}
.dm-msgbox{width:100%;font-size:14px;line-height:1.5;resize:vertical;}
/* 256: bookings from Google: grey, outlined, read-only */
.mc-wk-ev.gb{background:var(--panel);color:var(--ink);box-shadow:inset 0 0 0 1.5px var(--ink-3);cursor:pointer;}
.mc-wk-ev.gb span{color:var(--ink-2);}
.mc-pill.gb{border-left-color:var(--ink-3);color:var(--ink-2);}
.mc-gb-line{display:flex;align-items:center;gap:6px;}
.mc-gb-line i{width:10px;height:10px;border-radius:3px;background:var(--panel);box-shadow:inset 0 0 0 1.5px var(--ink-3);flex:none;}
.mc-gb-line.err{color:var(--err-ink);}
/* 260.1: the description read into lines (mcGbLines) */
.mc-gb-desc .mc-gb-h{font-size:11.5px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin:2px 0 4px;}
.mc-gb-desc .mc-gb-gap{height:10px;}
.mc-gb-desc .mc-gb-qa{display:grid;gap:1px;padding:5px 0;border-top:1px solid var(--line);}
.mc-gb-desc .mc-gb-qa span{font-size:12.5px;color:var(--ink-3);}
.mc-gb-desc .mc-gb-qa b{font-weight:500;}
.mc-gb-desc .mc-gb-h+.mc-gb-qa{border-top:0;}
.mc-gb-desc{white-space:normal;font-size:13.5px;line-height:1.55;color:var(--ink);background:var(--panel);border-radius:10px;padding:10px 12px;max-height:50vh;overflow:auto;overflow-wrap:anywhere;}
.mc-gb-desc a{color:var(--accent-text);}
.nc-full{display:flex;align-items:center;gap:10px;flex-wrap:wrap;background:var(--gold-wash);border-radius:10px;padding:10px 12px;font-size:13px;}
.nc .att-form{padding:0;background:none;}
.mc-wk-ev{position:absolute;z-index:1;display:grid;align-content:start;gap:0;text-align:left;border:0;border-radius:6px;padding:3px 6px;
  background:var(--pc);color:#fff;font:inherit;font-size:11.5px;line-height:1.25;overflow:hidden;cursor:pointer;box-shadow:0 1px 2px rgba(0,0,0,.18);}
.mc-wk-ev b{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.mc-wk-ev span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:.95;}
.mc-wk-ev.past{opacity:.62;}
/* a short block: time and session on one line (254.5) */
.mc-wk-ev.tight{padding:2px 6px;}
/* a class past the eighth: its hue again, solid like every block, with light
   diagonal stripes (254.8; 254.5 drew it as an outline) */
.mc-wk-ev.alt{background:repeating-linear-gradient(135deg,rgba(255,255,255,.2) 0 6px,transparent 6px 13px),var(--pc);}
.mc-wk-ev.live{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--wrong);}
.mc-wk-ev.drag{cursor:grab;}
/* 276.3: ▶ Bắt đầu buổi học on today's block when its time comes (McWeek) */
.mc-wk-go{position:absolute;z-index:3;min-height:0;height:24px;padding:0 10px;font-size:12px;line-height:24px;white-space:nowrap;box-shadow:0 2px 8px rgba(0,0,0,.25);}
.mc-wk-go.tight{height:20px;line-height:20px;padding:0 7px;font-size:11px;}
.mc-wk-ev.off{background:var(--card);color:var(--ink-3);border:1.5px dashed var(--pc);text-decoration:line-through;box-shadow:none;}
.mc-wk-ev.moved{background:transparent;color:var(--ink-3);border:1.5px dashed var(--pc);box-shadow:none;cursor:default;z-index:0;}
.mc-wk-now{position:absolute;left:0;right:0;height:2px;background:var(--wrong);z-index:2;pointer-events:none;}
.mc-wk-now::before{content:"";position:absolute;left:-5px;top:-4px;width:10px;height:10px;border-radius:50%;background:var(--wrong);}
@media (prefers-reduced-motion:no-preference){.mc-wk-ev{transition:filter .12s;}.mc-wk-ev:hover{filter:brightness(1.08);}}
/* Lớp & học viên: the Course column and "Cột ▾" */
.mc-course b{font-family:var(--mono);font-size:12px;font-weight:600;margin-right:6px;}
/* 264.7: sessions deleted, each with Khôi phục, under the timetable line */
.att-gone{margin:0;}
.att-gone .err{display:block;margin:6px 0 0;}
.mc-course span{font-size:12.5px;color:var(--ink-2);}
.mc-cols{position:relative;}
.mc-cols>summary{list-style:none;cursor:pointer;}
.mc-cols>summary::-webkit-details-marker{display:none;}
.mc-cols-pop{position:absolute;right:0;top:calc(100% + 6px);z-index:20;background:var(--card);border:1px solid var(--line-2);border-radius:10px;
  box-shadow:0 8px 24px rgba(0,0,0,.14);padding:8px 12px;display:grid;gap:6px;min-width:190px;}
.mc-cols-pop label{display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;white-space:nowrap;}

/* ===================== 254.3: the money's period; classes with no timetable ===================== */
.mc-per{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
.mc-per-c{display:flex;gap:6px;align-items:center;}
.mc-per-c .field{width:auto;padding:4px 8px;font-size:13px;}
.mc-per-c span{color:var(--ink-3);}

/* 254.4: the day list beside the month stays in view while the month scrolls (a computer only; on a phone it sits under the calendar) */

/* ---- build 260: renaming a class from its window in Lịch lớp (93-myclasses.js, McRoll) ---- */
.mc-ren-t{display:inline-flex;align-items:center;gap:6px;}
.mc-ren-b{border:0;background:none;cursor:pointer;color:var(--ink-3);font-size:14px;line-height:1;padding:4px 6px;border-radius:6px;}
.mc-ren-b:hover,.mc-ren-b:focus-visible{color:var(--accent-text);background:var(--accent-wash);}
.mc-ren{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
.mc-ren .cls-btn{font-family:var(--sans);font-size:13px;padding:6px 12px;}
.mc-ren .field{margin:0;flex:1 1 200px;min-width:0;max-width:320px;font-family:var(--sans);font-size:14px;font-weight:500;padding:6px 10px;}

/* ---- build 260: learners' contacts (39-contacts.js) ---- */
/* the learner's own number, under their class in the hub and on the invitation */
.ct-me{margin:2px 0 8px;padding:8px 10px;border-radius:10px;background:var(--panel);display:grid;gap:6px;font-size:12.5px;}
.ct-me.line{display:flex;align-items:center;gap:8px;flex-wrap:wrap;color:var(--ink-2);}
.ct-me.line i{font-style:normal;color:var(--ink-3);}
/* 262: the referral program (84-affiliate.js) */
.aff{display:grid;gap:12px;font-size:13px;}
.aff-two{margin:0;padding:0;list-style:none;display:grid;gap:8px;}
.aff-two li{display:grid;gap:2px;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--card);}
.aff-two li span{color:var(--ink-2);font-size:12.5px;}
.aff-tabs{display:flex;gap:2px;border-bottom:1px solid var(--line);overflow-x:auto;}
.aff-tabs button{font:inherit;font-size:13px;background:none;border:0;border-bottom:2px solid transparent;padding:7px 10px;color:var(--ink-3);cursor:pointer;white-space:nowrap;}
.aff-tabs button.on{color:var(--ink);border-bottom-color:var(--accent);font-weight:600;}
.aff-join,.aff-soon,.aff-link-c{display:grid;gap:8px;}
.aff-k{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;}
@media (max-width:560px){.aff-k{grid-template-columns:repeat(2,minmax(0,1fr));}}
.aff-k>div{display:grid;gap:1px;padding:10px;border:1px solid var(--line);border-radius:10px;background:var(--card);min-width:0;}
.aff-k b{font-family:var(--serif);font-size:19px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere;}
.aff-k span{font-size:11.5px;color:var(--ink-3);}
.aff-link{display:flex;align-items:center;gap:8px;padding:6px 6px 6px 12px;border-radius:10px;background:var(--panel);min-width:0;}
.aff-link .mono{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12.5px;}
.aff-list{display:grid;gap:4px;}
.aff-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:7px 0;border-bottom:1px solid var(--line);}
.aff-row>span:first-child{flex:1;min-width:0;}
.aff-amt{font-variant-numeric:tabular-nums;font-weight:600;}
.aff-pill{font-size:11.5px;font-weight:600;padding:2px 9px;border-radius:99px;background:var(--panel);color:var(--ink-2);white-space:nowrap;}
.aff-pill.done{background:var(--done-wash);color:var(--done);}
.aff-pill.sent{background:var(--gold-wash);color:var(--gold);}
.aff-pill.no{background:var(--wrong-wash);color:var(--wrong);}
.aff-pill.none{background:none;color:var(--ink-3);font-weight:400;}
.aff-tiers{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;}
@media (max-width:560px){.aff-tiers{grid-template-columns:repeat(2,minmax(0,1fr));}}
.aff-tiers>div{display:grid;gap:1px;padding:10px;border:1px solid var(--line);border-top:3px solid var(--accent);border-radius:10px;background:var(--card);}
.aff-tiers b{font-family:var(--serif);font-size:22px;}
.aff-tiers small{color:var(--ink-3);}
.aff-bank label{display:grid;gap:4px;}
.aff-bank label.fee-check{display:flex;}
.aff-rw{display:grid;gap:2px;font-size:12px;color:var(--ink-2);margin:4px 0;}
.aff-paybox{display:flex;gap:14px;align-items:flex-start;flex-wrap:wrap;margin-top:6px;}
.aff-paynums{display:grid;gap:4px;font-size:13px;min-width:0;}
/* 264: class deals (84-affiliate.js) and the teacher page's button (38-tpage.js) */
.aff-deals{display:grid;gap:10px;margin-top:6px;}
.aff-ask{display:grid;gap:8px;padding:12px;border:1px dashed var(--accent);border-radius:10px;background:var(--accent-wash);}
/* the teachers to choose from (264.4): a card each, the tiers in one row */
.aff-tiers.sm>div{display:flex;align-items:baseline;gap:8px;padding:8px 12px;}
.aff-tiers.sm b{font-size:20px;}
.aff-l-h{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-top:6px;}
.aff-l-h h3{margin:0;font-family:var(--serif);font-size:18px;}
.aff-tcs{display:grid;gap:10px;}
.aff-tc{display:grid;gap:12px;padding:14px;border:1px solid var(--line);border-radius:12px;background:var(--card-2);}
.aff-tc.on{border-color:var(--accent);}
.aff-tc-top{display:flex;gap:14px;align-items:flex-start;}
.aff-tc-av{flex:0 0 52px;width:52px;height:52px;border-radius:50%;overflow:hidden;background:var(--accent);color:var(--on-accent);display:grid;place-items:center;font-family:var(--serif);font-size:21px;font-weight:700;}
.aff-tc-av img{width:100%;height:100%;object-fit:cover;}
.aff-tc-id{flex:1;min-width:0;display:grid;gap:4px;}
.aff-tc-n{display:flex;gap:10px;align-items:baseline;flex-wrap:wrap;}
.aff-tc-n b{font-size:15.5px;}
.aff-tc-n span{font-size:12.5px;color:var(--ink-3);}
.aff-tc-line{font-size:13.5px;color:var(--ink-2);line-height:1.5;}
.aff-tc-cs{display:flex;gap:6px;flex-wrap:wrap;margin-top:4px;}
.aff-tc-cs span{font-size:12px;padding:3px 9px;border-radius:99px;border:1px solid var(--line-2);color:var(--ink-2);}
.aff-tc-cs b{color:var(--ink);font-weight:600;}
.aff-tc-act{display:flex;flex-direction:column;gap:8px;align-items:flex-end;flex:0 0 auto;}
.aff-tc-act a{font-size:13px;padding:4px 2px;color:var(--accent-text);}
.aff-tc .aff-link{flex-wrap:wrap;}
.aff-tc .aff-link .mono{flex:1;min-width:0;overflow-wrap:anywhere;}
.aff-l-paste{justify-self:start;align-self:flex-start;margin:6px 0 0;padding:6px 0;text-decoration:underline;}
.aff-listed{display:flex;gap:10px;align-items:center;flex-wrap:wrap;padding:8px 12px;border-radius:10px;background:var(--done-wash);color:var(--done);font-size:13px;font-weight:500;}
.aff-listed .fee-link{margin-left:auto;}
@media (max-width:560px){.aff-tc-top{flex-wrap:wrap;}.aff-tc-act{flex-direction:row;align-items:center;flex-basis:100%;justify-content:space-between;}}
.aff-ask label{display:grid;gap:4px;font-size:12.5px;color:var(--ink-2);}
.aff-deal{display:grid;gap:6px;padding:12px;border:1px solid var(--line);border-radius:10px;background:var(--card);margin-top:8px;}
.aff-deal.s-active{border-left:3px solid var(--done);}
.aff-deal.s-countered,.aff-deal.s-asked{border-left:3px solid var(--accent);}
.aff-deal-h{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.aff-deal-h .aff-pill{margin-left:auto;}
.aff-msg{margin:0;font-family:var(--read);font-size:14px;color:var(--ink-2);}
.aff-rates-ed{display:grid;grid-template-columns:repeat(4,minmax(0,110px));gap:8px;}
.aff-rates-ed label{display:grid;gap:3px;font-size:12px;color:var(--ink-3);}
.aff-rates-ed span{display:flex;align-items:center;gap:4px;}
.aff-rates-ed input{width:100%;min-width:0;text-align:right;}
.aff-bankline{font-size:12.5px;color:var(--ink-2);}
.aff-t{display:grid;gap:14px;}
.aff-t-head{display:grid;gap:8px;}
.aff-switch b{font-size:15px;}
/* 277: the map, the pay window, the paid list, the referrer's commissions
   (84-affiliate.js). Inside Lớp của tôi only .mc-card is glass in Tối; every
   button and box here takes --card and stays solid (270's rule). */
.aff-views,.aff-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;}
.aff-views button,.aff-chips button{border:1px solid var(--line);background:var(--card);color:var(--ink-2);border-radius:99px;padding:6px 14px;font-size:13px;}
.aff-chips button{padding:5px 12px;}
.aff-views button.on,.aff-chips button[aria-pressed="true"]{background:var(--ink);border-color:var(--ink);color:var(--paper);font-weight:600;}
.aff-k>div.aff-k-owe{background:var(--accent-wash);border-color:color-mix(in srgb,var(--accent) 45%,transparent);}
.aff-k.three{grid-template-columns:repeat(3,minmax(0,1fr));}
.aff-mapw{display:grid;gap:10px;}
.aff-mapgrid{display:flex;flex-wrap:wrap;gap:14px;align-items:flex-start;}
.aff-map{flex:999 1 620px;min-width:0;display:grid;gap:14px;}
.aff-side{flex:1 1 280px;min-width:0;display:grid;gap:10px;align-content:start;}
.aff-legend{display:flex;flex-wrap:wrap;gap:4px 12px;align-items:center;font-size:12px;color:var(--ink-2);}
.aff-legend b{color:var(--ink);}
.aff-legend-k{display:inline-flex;gap:10px;margin-left:auto;}
.aff-legend-k i{display:inline-block;width:14px;height:6px;border-radius:3px;margin-right:4px;vertical-align:middle;background:var(--line-2);}
.aff-marks{display:flex;gap:3px;}
.aff-marks i{flex:1;display:block;height:6px;border-radius:3px;background:var(--line-2);}
.aff-marks.sm{width:60px;flex:none;}
.aff-legend-k i.d,.aff-marks i.d{background:var(--done);}
.aff-legend-k i.c,.aff-marks i.c{background:var(--accent);}
.aff-tree{display:grid;grid-template-columns:132px minmax(0,1fr);}
.aff-root{display:flex;align-items:center;position:relative;}
.aff-root::after{content:'';position:absolute;left:124px;right:0;top:50%;height:2px;background:var(--line-2);}
.aff-you{width:124px;display:grid;justify-items:center;gap:4px;padding:12px 8px;border:0;border-radius:16px;background:#0B1F3F;color:#fff;text-align:center;}
.aff-you small{font-size:11px;color:#B9C2D2;}
.aff-av{width:28px;height:28px;border-radius:50%;display:grid;place-items:center;flex:none;background:#0B1F3F;color:#fff;font-size:12px;font-weight:600;}
.aff-av.big{width:38px;height:38px;background:var(--accent);color:var(--on-accent);font-size:15px;}
.aff-branches{border-left:2px solid var(--line-2);display:grid;gap:14px;padding:4px 0;min-width:0;}
.aff-br{display:grid;grid-template-columns:20px 210px minmax(0,1fr);align-items:start;transition:opacity .2s;min-width:0;}
.aff-br::before{content:'';height:2px;background:var(--line-2);margin-top:38px;}
.aff-br.dim{opacity:.38;}
.aff-pt{text-align:left;display:grid;gap:3px;padding:10px 12px;border:1.5px solid var(--line);border-radius:14px;background:var(--card);color:var(--ink);min-width:0;}
.aff-pt.s-asked,.aff-pt.s-countered{border-style:dashed;border-color:var(--line-2);}
.aff-pt.s-ended,.aff-pt.s-declined,.aff-pt.s-withdrawn{opacity:.75;}
.aff-pt.on,.aff-p.on{border-color:var(--accent);background:var(--accent-wash);}
.aff-pt-h{display:flex;align-items:center;gap:8px;min-width:0;}
.aff-pt-h b{font-size:14.5px;overflow-wrap:anywhere;}
.aff-pt-l{font-size:12px;color:var(--ink-2);}
.aff-pt-owe{font-size:12px;font-weight:600;color:var(--accent-text);}
.aff-lv{display:grid;gap:6px;border-left:2px solid var(--line);margin-left:12px;min-width:0;}
.aff-lf{display:flex;align-items:center;min-width:0;}
.aff-lf::before{content:'';width:14px;height:2px;background:var(--line);flex:none;}
.aff-none{font-size:12.5px;color:var(--ink-3);padding:7px 10px;border:1px dashed var(--line-2);border-radius:10px;}
.aff-p{flex:1;min-width:0;display:grid;grid-template-columns:minmax(0,140px) 112px minmax(0,1fr) 96px;gap:10px;align-items:center;text-align:left;
  border:1px solid var(--line);background:var(--card);border-radius:12px;padding:7px 10px;color:var(--ink);}
.aff-p-n{display:grid;min-width:0;}
.aff-p-n b,.aff-p-n small,.aff-p-s{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.aff-p-n b{font-size:13px;}
.aff-p-n small{font-size:11px;color:var(--ink-3);}
.aff-p-s{font-size:12px;color:var(--ink-2);}
.aff-p.k-due .aff-p-s{color:var(--accent-text);font-weight:600;}
.aff-p.k-paid .aff-p-s{color:var(--done);}
.aff-p-a{font-size:12px;text-align:right;}
.aff-side h3{margin:0;font-family:var(--serif);font-size:20px;font-weight:600;}
.aff-side-k{font-size:12px;color:var(--ink-3);}
.aff-tiers.aff-side-t{grid-template-columns:repeat(4,minmax(0,1fr));gap:4px;}
.aff-tiers.aff-side-t>div{display:grid;justify-items:center;gap:0;padding:6px 4px;border-top-width:1px;}
.aff-tiers.aff-side-t b{font-size:16px;}
.aff-tiers.aff-side-t>div.on{border-color:var(--accent);background:var(--accent-wash);}
.aff-side-bank{display:grid;gap:2px;font-size:12.5px;color:var(--ink-2);background:var(--panel);border-radius:10px;padding:8px 10px;}
.aff-side-bank b{color:var(--ink);font-weight:500;overflow-wrap:anywhere;}
.aff-side-go{justify-self:stretch;justify-content:center;text-align:center;}
.aff-side-rw{font-size:12.5px;line-height:1.55;background:var(--accent-wash);border:1px solid color-mix(in srgb,var(--accent) 40%,transparent);border-radius:10px;padding:8px 10px;}
.aff-tl{list-style:none;margin:0;padding:0;display:grid;}
.aff-tl li{display:grid;grid-template-columns:14px minmax(0,1fr);gap:10px;position:relative;padding-bottom:10px;}
.aff-tl li>i{width:12px;height:12px;border-radius:50%;border:2px solid var(--line-2);background:var(--card);margin-top:3px;box-sizing:border-box;}
.aff-tl li.d>i{background:var(--done);border-color:var(--done);}
.aff-tl li.c>i{background:var(--accent);border-color:var(--accent);}
.aff-tl li:not(:last-child)::before{content:'';position:absolute;left:5px;top:16px;bottom:0;width:2px;background:var(--line);}
.aff-tl b{display:block;font-size:13px;}
.aff-tl li:not(.d):not(.c) b{color:var(--ink-3);}
.aff-tl small{font-size:12px;color:var(--ink-3);}
@media (max-width:760px){
  .aff-tree{grid-template-columns:1fr;gap:10px;}
  .aff-root::after{display:none;}
  .aff-you{width:100%;grid-auto-flow:column;justify-content:start;align-items:center;}
  .aff-branches{border-left:0;padding:0;}
  .aff-br{grid-template-columns:1fr;gap:6px;}
  .aff-br::before{display:none;}
  .aff-p{grid-template-columns:minmax(0,1fr) auto;gap:4px 10px;}
  .aff-p .aff-marks{grid-column:1 / -1;}
  .aff-legend-k{margin-left:0;}
}
.aff-pay{display:grid;gap:12px;}
.aff-pay-grid{display:grid;grid-template-columns:minmax(0,1fr) 220px;gap:18px;align-items:start;}
.aff-pay-l{display:grid;gap:10px;min-width:0;}
.aff-pay-r{display:grid;gap:8px;justify-items:center;}
.aff-pay-sum{display:grid;gap:4px;border-top:2px solid var(--ink);padding-top:8px;font-size:13.5px;}
.aff-pay-sum>div{display:flex;justify-content:space-between;gap:12px;}
.aff-pay-sum .net{font-size:15px;}
.aff-pay-sum .net b:last-child{color:var(--accent-text);}
.aff-pay-bank{display:grid;gap:2px;width:100%;box-sizing:border-box;font-size:12px;background:var(--panel);border-radius:10px;padding:8px 10px;overflow-wrap:anywhere;}
.aff-pay-go{grid-column:1 / -1;display:flex;flex-wrap:wrap;gap:12px;align-items:center;border-top:1px solid var(--line);padding-top:12px;}
.aff-pay-go .hint{flex:1;min-width:200px;}
.aff-pay-done{display:grid;gap:6px;padding:12px;border-radius:12px;background:var(--done-wash);}
@media (max-width:640px){.aff-pay-grid{grid-template-columns:1fr;}}
.aff-year{display:grid;gap:3px;}
.aff-year+.aff-year{margin-top:10px;}
.aff-mine{display:grid;gap:10px;}
.aff-mine-pay{display:grid;gap:6px;padding:12px;border-radius:12px;background:var(--accent-wash);border:1px solid color-mix(in srgb,var(--accent) 40%,transparent);}
.aff-mine-t{display:grid;gap:6px;padding:12px;border:1px solid var(--line);border-radius:10px;background:var(--card);}
.aff-prog{height:6px;border-radius:3px;background:var(--panel);overflow:hidden;}
.aff-prog i{display:block;height:6px;background:var(--accent);}
.aff-mine-steps{font-size:11.5px;}
/* 277.2: the referrer's diagram. "Bạn" at the top, a rail down to each
   teacher, a rail down to each person; the stubs are drawn by ::before so a
   card, a note or a person all hang off the rail the same way. */
.aff-vt{display:grid;gap:0;}
.aff-vt-root{display:flex;align-items:center;gap:10px;}
.aff-vt-id{display:grid;min-width:0;flex:1;}
.aff-vt-id b{overflow-wrap:anywhere;}
.aff-vt-id small{font-size:12px;color:var(--ink-3);}
.aff-vt-kids{margin-left:18px;border-left:2px solid var(--line-2);padding:12px 0 2px 16px;display:grid;gap:14px;}
.aff-vt-kids>*,.aff-vt-ppl>*{position:relative;}
.aff-vt-kids>*::before{content:'';position:absolute;left:-16px;top:22px;width:14px;height:2px;background:var(--line-2);}
.aff-vt-t{display:grid;gap:8px;padding:12px;border:1px solid var(--line);border-radius:14px;background:var(--card);}
.aff-vt-t.s-asked,.aff-vt-t.s-countered{border-style:dashed;border-color:var(--line-2);}
.aff-vt-t.s-ended,.aff-vt-t.s-declined,.aff-vt-t.s-withdrawn{opacity:.75;}
.aff-vt-th{display:flex;align-items:center;gap:10px;min-width:0;}
.aff-vt-ppl{margin-left:13px;border-left:2px solid var(--line);padding:4px 0 2px 12px;display:grid;gap:6px;}
.aff-vt-ppl>*::before{content:'';position:absolute;left:-12px;top:17px;width:10px;height:2px;background:var(--line);}
.aff-vt-leaf{margin:0;}
.aff-vt-p{width:100%;display:grid;grid-template-columns:minmax(0,1fr) 60px auto;gap:10px;align-items:center;text-align:left;
  border:1px solid var(--line);background:var(--card);border-radius:10px;padding:7px 10px;color:var(--ink);}
.aff-vt-p b{font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.aff-vt-p.on{border-color:var(--accent);background:var(--accent-wash);}
.aff-vt-more{display:grid;gap:8px;padding:10px 4px 2px 10px;}
@media (max-width:480px){.aff-vt-p{grid-template-columns:minmax(0,1fr) auto;}.aff-vt-p .aff-marks{grid-column:1 / -1;width:auto;}}
.mc-tab-n{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 5px;margin-left:6px;border-radius:99px;background:var(--accent);color:var(--on-accent);font-size:11px;font-weight:700;}
.tp-aff{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:14px 16px;border:1px dashed var(--accent);border-radius:12px;background:var(--accent-wash);}
.tp-aff>div{display:grid;gap:2px;min-width:0;}
.tp-aff span{font-size:13px;color:var(--ink-2);}
/* 261: the learner's roadmap under their class (86-demo.js, DmMine) */
.dm-rm{margin:2px 0 8px;padding:10px 12px;border-radius:10px;background:var(--panel);display:grid;gap:8px;font-size:12.5px;}
.dm-rm-h{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;}
.dm-rm-h b{font-size:13px;color:var(--ink);}
.dm-rm-h span{color:var(--ink-3);}
.dm-rm-l{margin:0;padding:0;list-style:none;display:grid;gap:6px;}
.dm-rm-l li{display:grid;grid-template-columns:22px minmax(0,1fr) auto;align-items:center;gap:4px 8px;color:var(--ink-2);}
.dm-rm-l li>i{width:22px;height:22px;border-radius:50%;display:grid;place-items:center;font-style:normal;font-size:11px;font-weight:600;
  border:1.5px solid var(--line-2);color:var(--ink-3);background:var(--card);}
.dm-rm-c{display:grid;min-width:0;}
.dm-rm-c b{color:var(--ink);font-weight:600;overflow-wrap:anywhere;}
.dm-rm-c small{color:var(--ink-3);font-size:11.5px;}
.dm-rm-l em{font-style:normal;font-size:11.5px;color:var(--ink-3);white-space:nowrap;}
.dm-rm-l li>.fee-link{grid-column:2 / -1;justify-self:start;margin-left:0;font-size:12px;}
.dm-rm-l li.done>i{background:var(--done-wash);border-color:var(--done);color:var(--done);}
.dm-rm-l li.now>i{background:var(--accent);border-color:var(--accent);color:var(--on-accent);}
.dm-rm-l li.now em{color:var(--accent-text);font-weight:600;}
.dm-rm-l li.next em{color:var(--ink-2);font-weight:600;}
.ct-me-n{color:var(--ink-3);}
.ct-me.join{margin:12px 0 10px;padding:12px 14px;text-align:left;font-size:13.5px;}
.ct-me-t{font-weight:600;color:var(--ink);}
.ct-me-l{display:grid;gap:4px;font-weight:500;}
.ct-me-l .field{margin:0;max-width:260px;}
.ct-me-c{display:flex;align-items:center;gap:8px;cursor:pointer;}
.ct-me-b{display:flex;gap:6px;flex-wrap:wrap;}
.ct-me .hint{margin:0;}
/* the teacher's column in Lớp & học viên */
.mc-ct{min-width:150px;}
.ct-cell{display:grid;gap:2px;}
.ct-mail{color:var(--ink-2);font-size:12.5px;text-decoration:none;word-break:break-all;}
.ct-mail:hover{color:var(--accent-text);text-decoration:underline;}
.ct-ph{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12.5px;}
.mc-ct-soon{margin:0 0 10px;}

/* ---- build 260: the teacher page's surround in dark mode ----
   Khoa (a picture of /t/ in dark: "the background surrounding the card should
   be a little lighter to make contrast"). The page sat at #1E1E1D around a
   sheet of #20201F: one colour. In dark the surround is lifted a step, so the
   sheet reads as a card laid on it, with a firmer edge. Light is unchanged.
   260.1 (Khoa, a picture of the invitation in dark: "apply this background
   color to Your teacher page render in dark mode"): the invitation's own
   ground, the terracotta glow at the top over the page's dark (`.inv-page`). */
:root[data-theme="dark"] .tp-page{background:radial-gradient(ellipse at top,var(--accent-wash),transparent 60%),var(--paper);}
:root[data-theme="dark"] .tp{border-color:#3B3B39;box-shadow:0 1px 3px rgba(0,0,0,.3),0 18px 48px rgba(0,0,0,.45);}

/* ---- build 260.3: Hôm nay as the day's timeline (McTodayTl, 93-myclasses.js) ----
   the now line is the week view's own (var(--wrong), as .mc-wk-now) */
.mc-tl-bar{position:relative;height:20px;margin:6px 0 26px;background:var(--panel);border-radius:6px;}
.mc-tl-blk{position:absolute;top:4px;bottom:4px;min-width:3px;border-radius:4px;background:var(--pc);}
.mc-tl-blk.alt{background:transparent;box-shadow:inset 0 0 0 2px var(--pc);}
.mc-tl-blk.done{opacity:.35;}
.mc-tl-nowl{position:absolute;top:-5px;bottom:-5px;width:2px;margin-left:-1px;background:var(--wrong);border-radius:2px;}
.mc-tl-nowl::before{content:"";position:absolute;left:-4px;top:-4px;width:10px;height:10px;border-radius:50%;background:var(--wrong);}
.mc-tl-bar small{position:absolute;top:100%;margin-top:4px;font-family:var(--mono);font-size:10.5px;color:var(--ink-3);}
.mc-tl-bar small.lo{left:0;}.mc-tl-bar small.hi{right:0;}
.mc-tl-list{list-style:none;margin:0;padding:0;}
/* 260.4: no time column; the rail runs down the left edge */
.mc-tl-i,.mc-tl-now{position:relative;padding-left:24px;}
.mc-tl-i::before,.mc-tl-now::before{content:"";position:absolute;left:5px;top:0;bottom:0;width:2px;background:var(--line-2);}
.mc-tl-list>li:first-child::before{top:16px;}
.mc-tl-list>li:last-child::before{bottom:auto;height:16px;}
.mc-tl-i::after{content:"";position:absolute;left:1px;top:12px;width:10px;height:10px;border-radius:50%;background:var(--pc);box-shadow:0 0 0 3px var(--card);}
.mc-tl-i.on::after{box-shadow:0 0 0 3px var(--card),0 0 0 6px color-mix(in srgb,var(--pc) 40%,transparent);}
.mc-tl .mc-today{border-top:0;padding:8px 0 12px;}
.mc-tl-i+.mc-tl-i .mc-today{border-top:1px solid var(--line);}
.mc-tl-i.done .mc-today>*:not(.mc-today-b){opacity:.55;}
.mc-tl-st{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ink-3);}
.mc-tl-st:empty{display:none;}
.mc-tl-i.on .mc-tl-st{color:var(--accent-text);font-weight:600;}
.mc-tl-prog{flex:1;max-width:120px;height:5px;border-radius:3px;background:var(--line);overflow:hidden;}
.mc-tl-prog i{display:block;height:100%;background:var(--accent);}
.mc-tl-now{display:flex;align-items:center;height:26px;}
.mc-tl-now i{position:absolute;left:0;right:0;top:50%;height:2px;margin-top:-1px;background:var(--wrong);}
.mc-tl-now i::before{content:"";position:absolute;left:1px;top:-4px;width:10px;height:10px;border-radius:50%;background:var(--wrong);}
.mc-tl-now span{position:relative;z-index:1;padding:0 8px;border-radius:10px;background:var(--wrong);color:#fff;font-family:var(--mono);font-size:11px;font-weight:600;line-height:18px;}

/* 264.8 (Khoa, a picture of Hôm nay's timeline: "make the lines thinner"):
   the rail and the now line 1px, the dots as they were */
.mc-tl-i::before,.mc-tl-now::before{width:1px;left:5.5px;}
.mc-tl-now i{height:1px;margin-top:-.5px;}
.mc-tl-nowl{width:1px;margin-left:-.5px;}
/* 264.8: a tile is a button that opens its list under the row */
/* 269.11: the eye beside ▾ sits over the tile, a button of its own */
.mc-kpi-w{position:relative;display:grid;min-width:0;}
.mc-kpi-w>.mc-kpi>span:first-child{padding-right:52px;}
.mc-kpi-eye{position:absolute;right:30px;top:5px;width:28px;height:28px;display:grid;place-items:center;border:0;border-radius:8px;background:none;color:var(--ink-3);font-size:15px;cursor:pointer;}
.mc-kpi-eye:hover{color:var(--ink);background:var(--sunk,rgba(0,0,0,.05));}
.mc-kpi-eye:focus-visible{outline:2px solid var(--accent);outline-offset:1px;}
.mc-kpi-eye[aria-pressed="true"]{color:var(--accent-text);}
.mc-kpi b.mc-hid{letter-spacing:.12em;color:var(--ink-3);}
.mc-kpi-b{position:relative;text-align:left;font:inherit;color:inherit;cursor:pointer;transition:border-color .15s,box-shadow .15s;}
.mc-kpi-b:hover{border-color:var(--line-2);}
.mc-kpi-b:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
.mc-kpi-b[aria-expanded="true"]{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent);}
.mc-kpi-v{position:absolute;right:12px;top:9px;font-style:normal;font-size:14px;color:var(--ink-3);transition:transform .2s;}
.mc-kpi-b[aria-expanded="true"] .mc-kpi-v{transform:rotate(180deg);color:var(--accent-text);}
.mc-drop{animation:mcOpen .18s ease-out;}
@keyframes mcOpen{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:none;}}
@media (prefers-reduced-motion:reduce){.mc-drop{animation:none;}.mc-kpi-v{transition:none;}}
.mc-drop .mc-ch{flex-wrap:wrap;gap:8px;}
/* Trước app: one cell per learner, a box when pressed */
.mc-lt td.mc-pre{white-space:nowrap;vertical-align:middle;}
.mc-pre-ed{display:inline-flex;gap:6px;align-items:center;}
.mc-pre-ed .field{width:9em;height:30px;padding:2px 8px;}
.mc-pre-ed .cls-btn{height:30px;}

/* ===================== 264.12: the four lists in one shape (93-myclasses.js) =====================
   Khoa, eight pictures: one table style for Sắp đến kỳ, Hoá đơn, Lớp and Học
   viên; Lọc ▾ in each; a row is not a button and is not shaded, only its
   links are, terracotta and underlined on hover; figures to the right in
   plain weight; a drawn pencil for "change this". */
.mc-lh{display:flex;gap:10px 16px;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;}
.mc-lh>div:first-child{display:grid;gap:2px;min-width:0;}
.mc-lh>div:first-child>span{font-size:12.5px;color:var(--ink-3);}
.mc-tabs button{white-space:nowrap;}
.mc-lbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
.mc-lbar .mc-q{width:220px;max-width:100%;height:34px;box-sizing:border-box;}
.mc-lbar .mc-chip{height:34px;box-sizing:border-box;white-space:nowrap;}
.mc-lbar-r{display:flex;gap:8px;align-items:center;margin-left:auto;}
.mc-exp span{margin-left:2px;}
.mc-flt{position:relative;}
.mc-flt>summary{list-style:none;cursor:pointer;}
.mc-flt>summary::-webkit-details-marker{display:none;}
.mc-chip.on{border-color:var(--accent);background:var(--accent-wash);color:var(--accent-text);}
.mc-flt-n{min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:var(--accent);color:var(--on-accent);font-size:11px;font-weight:600;
  display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;}
.mc-flt-b{left:0;right:auto;min-width:240px;max-width:min(340px,calc(100vw - 48px));max-height:min(420px,60vh);overflow:auto;gap:12px;padding:10px 12px;}
.mc-flt-b fieldset{border:0;margin:0;padding:0;display:grid;gap:6px;min-width:0;}
.mc-flt-b legend{padding:0 0 4px;font-family:var(--mono);font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);}
.mc-flt-b label{white-space:normal;align-items:center;}
.mc-flt-b label .mc-dot{margin-right:0;}
.mc-flt-c{font-family:var(--mono);font-size:12px;font-weight:600;}
.mc-flt-x{justify-self:start;margin-left:0;}
.ico-edit{display:inline-block;flex:none;vertical-align:-2px;}
/* the table */
.mc-t th{white-space:nowrap;vertical-align:bottom;padding:8px;}
.mc-t td{padding:10px 8px;white-space:nowrap;}
.mc-t th:first-child,.mc-t td:first-child{padding-left:4px;}
.mc-t th:last-child,.mc-t td:last-child{padding-right:4px;}
.cw-tbl.mc-t tbody tr:hover td{background:none;}
.cw-tbl.mc-t tbody tr.mc-grp-h td,.cw-tbl.mc-t tbody tr.mc-grp-h:hover td{background:var(--card-2);}
.mc-t .mc-n{text-align:right;font-variant-numeric:tabular-nums;}
.mc-t td.mc-n small{display:block;font-size:11.5px;color:var(--ink-3);margin-top:2px;}
.mc-t .mc-sort i{display:inline-block;width:10px;margin-left:3px;font-style:normal;font-size:8.5px;vertical-align:1px;}
.mc-t .mc-sort:hover{color:var(--ink);}
.mc-t .mc-act-td{text-align:right;width:1%;}
.mc-act{height:28px;font-size:12.5px;white-space:nowrap;}
.mc-acts{display:inline-flex;gap:6px;}
.mc-self-del{color:var(--wrong);}
.mc-t tr.mc-self-ask td{white-space:normal;}
.mc-t .fee-link{text-decoration:none;}
.mc-t .fee-link:hover{text-decoration:underline;text-underline-offset:3px;}
/* a link in a table: the text at rest, terracotta and underlined on hover */
.mc-a{border:0;background:none;padding:0;font:inherit;color:inherit;text-align:left;cursor:pointer;text-decoration:none;}
.mc-a:hover,.mc-a:hover span,.mc-a:hover b{color:var(--accent-text);text-decoration:underline;text-underline-offset:3px;}
.mc-a:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px;}
.mc-todo,.mc-a .mc-todo{color:var(--accent-text);}
.mc-fee-b{display:inline-flex;align-items:center;gap:6px;}
.mc-fee-b .ico-edit{color:var(--ink-3);font-size:13px;}
.mc-fee-b:hover .ico-edit{color:var(--accent-text);}
.mc-ren-b .ico-edit{font-size:15px;}
@media (max-width:640px){
  /* the tabs go across, the search takes a row, the tools share the next */
  .mc-lh .mc-tabs{width:100%;display:flex;}
  .mc-lh .mc-tabs button{flex:1 1 0;min-width:0;padding:6px 4px;font-size:12.5px;}
  .mc-lbar .mc-q{flex:1 1 100%;width:auto;}
  .mc-exp{width:34px;padding:0;justify-content:center;}
  .mc-exp span{display:none;}
}
/* the timetable form on a phone (Khoa, a picture of Lớp mới: the start time ran
   over the length): an iPhone draws a time or a date box at a width and
   height of its own, wider than its column; each one is given both */
input.field[type="time"],input.field[type="date"]{-webkit-appearance:none;appearance:none;height:38px;min-height:38px;line-height:1.2;}
input.field[type="time"]::-webkit-date-and-time-value,input.field[type="date"]::-webkit-date-and-time-value{text-align:left;}
.att-slot input.field[type="time"]{width:9.5em;}
.att-form input.field[type="date"]{width:11.5em;max-width:100%;}
.att-slot select.field,.att-form .att-f-row select.field{height:38px;}
/* 264.14: the kỳ in the bill window's line, with its pencil; pressed, a box */
.fee-k{display:inline-flex;align-items:center;gap:2px;}
.fee-k .mc-ren-b{padding:2px 4px;font-size:13px;}
.fee-k-ed{display:inline-flex;align-items:center;gap:6px;vertical-align:middle;}
.fee-k-ed .field{width:5em;height:30px;padding:2px 8px;}
.fee-k-ed .cls-btn{height:30px;}

/* ---- build 265: the course universe (83-universe.js) ----
   Always night, whatever the theme: its own colours on `.uv` and `.uv-ov`
   (the story and the landing, drawn on <body>), never the theme's tokens.
   It takes `.cols`'s place in `.app`'s column: flex 1, and everything in it
   is laid over a starfield inside its own box. */
.uv,.uv-ov{--uv-0:#020308;--uv-1:#080D18;--uv-card:rgba(12,24,44,.94);--uv-ink:#F3F1EA;--uv-ink-2:#B9C2D2;--uv-ink-3:#8590A6;
  --uv-line:rgba(185,194,210,.16);--uv-acc:#E58A68;color:var(--uv-ink);font-family:var(--sans);}
/* 269.5 (Khoa: "make the sky darker toward black"): near black at the middle, black at the edges */
.uv{position:relative;flex:1;min-height:0;overflow:hidden;background:radial-gradient(120% 90% at 50% 40%,#070A12 0%,#030409 55%,#000 100%);}
/* 285.2 (Khoa, a picture: course names highlighted blue after dragging the map): a drag that starts on the sky selected the names it crossed. Nothing on the map is text to copy; the search box stays typeable. */
.uv{-webkit-user-select:none;user-select:none;}
.uv input,.uv textarea{-webkit-user-select:text;user-select:text;}
.uv-stars{position:absolute;inset:0;width:100%;height:100%;display:block;pointer-events:none;}
.uv-eyebrow{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--uv-acc);}
.uv-eyebrow.ok{color:#A9CC8A;font-size:10px;}
.uv-led{display:inline-block;width:6px;height:6px;border-radius:50%;background:#8FB06E;box-shadow:0 0 8px #8FB06E;margin-right:6px;vertical-align:1px;}
.uv button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:0;}
.uv button:focus-visible,.uv-ov button:focus-visible,.uv input:focus-visible{outline:2px solid var(--uv-acc);outline-offset:2px;}
/* the chart */
.uv-view{position:absolute;inset:0;cursor:grab;touch-action:none;}
.uv-view.drag{cursor:grabbing;}
.uv-world{position:absolute;left:0;top:0;transform-origin:0 0;will-change:transform;}
.uv-world.ease{transition:transform .32s cubic-bezier(.2,.8,.2,1);}
.uv-view.drag .uv-world{transition:none;}
/* each type's nebula (285): painted soft, moved by a drag at a middle depth; dimmer while a filter narrows the map */
.uv-nebc{position:absolute;pointer-events:none;opacity:.85;translate:calc(var(--uv-dx,0px) * -.4) calc(var(--uv-dy,0px) * -.4);transition:opacity .35s;}
.uv-world.filt .uv-nebc{opacity:.45;}
.uv-sector{position:absolute;font-family:var(--mono);font-size:max(15px,calc(10px * var(--uv-ik,1)));letter-spacing:.3em;text-transform:uppercase;color:var(--uv-ink-3);pointer-events:none;white-space:nowrap;}
.uv-sector::after{content:"";display:block;width:36px;height:1px;background:var(--uv-line);margin-top:8px;}
/* centred on its place (285.1), its little line under the middle */
.uv-sector.mid{transform:translateX(-50%);text-align:center;}
.uv-sector.mid::after{margin-left:auto;margin-right:auto;}
/* depth (285): a drag moves each planet by its own share (`--pp`, far ones less) */
.uv-world .uv-pl{translate:calc(var(--uv-dx,0px) * var(--pp,0)) calc(var(--uv-dy,0px) * var(--pp,0));}
.uv-world.ease .uv-pl,.uv-world.ease .uv-nebc{transition:opacity .35s,translate .32s cubic-bezier(.2,.8,.2,1);}
.uv-view.drag .uv-world .uv-pl,.uv-view.drag .uv-world .uv-nebc{transition:opacity .35s;}
.uv .uv-pl{position:absolute;transform:translate(-50%,calc(var(--s) / -2));display:flex;flex-direction:column;align-items:center;gap:8px;width:var(--s);transition:opacity .35s;}
.uv-orb{position:relative;width:var(--s);height:var(--s);transition:transform .35s cubic-bezier(.2,.8,.2,1);}
/* the live planet (285, `UvOrb`): its picture is 180% of the ball, for its glow and ring */
.uv-orb-cv{position:absolute;inset:-40%;width:180%;height:180%;max-width:none;display:block;pointer-events:none;}
.uv-prog{position:absolute;inset:-10px;width:calc(100% + 20px);height:calc(100% + 20px);transform:rotate(-90deg);overflow:visible;}
.uv-prog circle{fill:none;stroke-width:3;}
.uv-prog .trk{stroke:rgba(255,255,255,.10);}
.uv-prog .val{stroke:var(--uv-acc);stroke-linecap:round;}
.uv-lbl{display:flex;flex-direction:column;align-items:center;gap:3px;transition:opacity .25s;opacity:var(--uv-la,1);}
/* names quiet (285, Khoa: "make them small to not confuse the eyes"): a far one dimmer (`--uv-la`), a near one a little bigger (`--uv-lk`); the one pointed at or picked in full */
.uv-pl:hover,.uv-pl.sel,.uv-pl:focus-visible{--uv-la:1;}
/* the names in the sans face, not bold (Khoa, third mock-up) */
.uv-nm{font-family:var(--sans);font-weight:400;font-size:calc(max(18px,calc(12.5px * var(--uv-ik,1))) * var(--uv-lk,1));line-height:1.2;color:var(--uv-ink);white-space:nowrap;text-shadow:0 1px 10px #000;}
.uv-mt{font-family:var(--mono);font-size:max(11px,calc(8.5px * var(--uv-ik,1)));letter-spacing:.08em;text-transform:uppercase;color:var(--uv-ink-3);white-space:nowrap;}
.uv-pl:hover .uv-orb{transform:scale(1.07);}
.uv-pl.sel .uv-orb{transform:scale(1.14);}
.uv-pl.sel .uv-nm{color:#FFD9C9;}
.uv-lock{position:absolute;inset:-22px;border:1.5px dashed rgba(229,138,104,.75);border-radius:50%;display:none;animation:uvSpin 18s linear infinite;}
.uv-pl.sel .uv-lock{display:block;}
.uv-pl.dim{opacity:.14;}
.uv-tag{position:absolute;top:-4px;right:-26px;font-family:var(--mono);font-size:12px;padding:2px 8px;border-radius:999px;background:var(--uv-acc);color:#1a0d07;font-weight:500;white-space:nowrap;z-index:2;}
/* a teacher's classes as a number in a small pill (285, mock-up 7) */
.uv-tag.n{top:2px;right:-4px;font-size:10px;font-weight:600;padding:1px 6px;min-width:18px;text-align:center;}
.lab-name .uv-mt{display:none;}
.lab-off .uv-lbl{opacity:0;}
.lab-off .uv-pl:hover .uv-lbl,.lab-off .uv-pl.sel .uv-lbl,.lab-off .uv-pl:focus-visible .uv-lbl{opacity:1;}
.lab-off .uv-tag{display:none;}
/* a learner's class on its planet (269.9): the name, cut short; a request waiting, dashed */
.uv-ctag{max-width:132px;overflow:hidden;text-overflow:ellipsis;font-family:var(--sans);font-weight:600;}
.uv-ctag.wait{background:rgba(6,9,18,.75);color:var(--uv-acc);border:1px dashed var(--uv-acc);font-weight:500;}
.uv-ship{position:absolute;inset:-30px;animation:uvSpin 14s linear infinite;pointer-events:none;}
.uv-ship svg{position:absolute;left:50%;top:-8px;width:22px;height:22px;transform:translateX(-50%) rotate(90deg);}
@keyframes uvSpin{to{transform:rotate(360deg);}}
/* the cockpit */
.uv-frame{position:absolute;inset:0;z-index:5;pointer-events:none;box-shadow:inset 0 0 120px 30px rgba(0,0,0,.82);}
.uv-brk{position:absolute;width:26px;height:26px;border:0 solid rgba(229,138,104,.75);z-index:6;pointer-events:none;}
.uv-brk.tl{left:16px;border-top-width:2px;border-left-width:2px;border-top-left-radius:6px;}
.uv-brk.tr{right:16px;border-top-width:2px;border-right-width:2px;border-top-right-radius:6px;}
.uv-brk.bl{left:16px;border-bottom-width:2px;border-left-width:2px;border-bottom-left-radius:6px;}
.uv-brk.br{right:16px;border-bottom-width:2px;border-right-width:2px;border-bottom-right-radius:6px;}
.uv-ret{position:absolute;width:120px;height:120px;transform:translate(-50%,-50%);z-index:4;pointer-events:none;opacity:.2;}
.uv-brow{position:absolute;left:0;right:0;top:0;height:58px;z-index:20;pointer-events:none;}
.uv-brow-svg{position:absolute;left:0;top:0;width:100%;overflow:visible;}
.uv-brow.ph{height:26px;z-index:6;}
/* the top bar's straight line gives way to the brow's curve over the universe (265.1) on a computer; a phone keeps the straight line (269) */
@media (min-width:901px){.app.uv-on .topbar{border-bottom-width:0;}}
.uv-brow-t{position:absolute;left:50%;top:14px;transform:translateX(-50%);text-align:center;white-space:nowrap;}
.uv-brow-t b{display:block;font-family:var(--serif);font-size:19px;font-weight:600;line-height:1.1;}
.uv-brow-t b small,.uv-ph-h h2 small{font-family:var(--mono);font-size:11px;font-weight:400;letter-spacing:.06em;color:var(--uv-ink-3);margin-left:8px;vertical-align:2px;}
.uv-brow-r{position:absolute;right:60px;top:62px;pointer-events:auto;}
.uv-role{display:inline-flex;gap:1px;padding:2px;border-radius:999px;border:1px solid var(--uv-line);background:rgba(255,255,255,.04);}
.uv .uv-role button{padding:3px 10px;border-radius:999px;font-size:11.5px;line-height:1.5;opacity:.75;}
.uv .uv-role button.on{background:var(--accent);color:#1a0d07;opacity:1;}
.uv-zoom{position:absolute;right:18px;z-index:8;display:flex;flex-direction:column;align-items:center;gap:6px;padding:8px 6px;border-radius:999px;background:rgba(4,7,14,.85);border:1px solid var(--uv-line);}
.uv .uv-zoom button{width:34px;height:34px;border-radius:50%;font-size:18px;line-height:1;display:grid;place-items:center;}
.uv .uv-zoom button:hover{background:rgba(255,255,255,.08);}
.uv-zoom svg{width:16px;height:16px;}
.uv-zoom.tight .uv-ztrk,.uv-zoom.tight .uv-zrd:not(:last-of-type){display:none;}
.uv-zoom.nohint .uv-hint{display:none;}
.uv-ztrk{width:4px;height:84px;border-radius:4px;background:rgba(255,255,255,.1);position:relative;cursor:pointer;}
.uv-ztrk i{position:absolute;left:0;right:0;bottom:0;border-radius:4px;background:var(--uv-acc);}
.uv-zrd{font-family:var(--mono);font-size:10.5px;color:var(--uv-ink-2);}
.uv-zsep{width:18px;height:1px;background:var(--uv-line);margin:2px 0;}
.uv .uv-aa{font-family:var(--serif);font-size:15px;font-weight:600;}
.uv-hint{position:absolute;right:-6px;top:calc(100% + 10px);width:120px;text-align:right;font-family:var(--mono);font-size:10px;line-height:1.7;letter-spacing:.04em;text-transform:uppercase;color:var(--uv-ink-3);pointer-events:none;}
/* the dashboard */
.uv-con{position:absolute;left:0;right:0;bottom:0;z-index:20;}
.uv-shell{position:absolute;inset:0;width:100%;height:100%;overflow:visible;}
.uv-grid{position:relative;display:grid;grid-template-columns:minmax(0,1fr) 400px minmax(0,1fr);gap:0 20px;padding:0 22px 16px;align-items:start;}
.uv-wing{padding-top:46px;display:grid;gap:10px;min-width:0;}
.uv-core{padding-top:14px;}
.uv-krow{display:flex;flex-wrap:wrap;align-items:center;gap:6px;}
.uv-klab{font-family:var(--mono);font-size:9.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--uv-ink-3);margin-right:6px;white-space:nowrap;}
.uv .uv-key{height:32px;padding:0 12px;border-radius:9px;font-size:12.5px;color:var(--uv-ink-2);white-space:nowrap;flex:none;
  background:linear-gradient(180deg,#1A2A45,#111D32);border:1px solid rgba(255,255,255,.07);box-shadow:inset 0 1px 0 rgba(255,255,255,.06),0 2px 0 rgba(0,0,0,.45);}
.uv .uv-key:hover{color:var(--uv-ink);}
.uv .uv-key.on{background:linear-gradient(180deg,#E8906F,#C9663F);color:#1a0d07;font-weight:600;border-color:rgba(255,200,170,.4);box-shadow:0 0 14px rgba(229,138,104,.45),inset 0 1px 0 rgba(255,255,255,.3);}
.uv-key small{opacity:.7;}
.uv-srch{position:relative;}
.uv-srch input{width:100%;height:38px;border-radius:10px;border:1px solid rgba(255,255,255,.08);background:#04070F;color:var(--uv-ink);padding:0 14px 0 36px;font:inherit;font-size:13.5px;box-shadow:inset 0 2px 6px rgba(0,0,0,.5);}
.uv-srch input::placeholder{color:var(--uv-ink-3);}
.uv-srch svg{position:absolute;left:12px;top:11px;width:16px;height:16px;stroke:var(--uv-ink-3);}
.uv-mod{display:flex;align-items:center;gap:14px;padding:12px 14px;border-radius:18px;background:linear-gradient(180deg,#0E1B30,#0A1424);border:1px solid rgba(229,138,104,.35);box-shadow:inset 0 0 22px rgba(0,0,0,.45);min-height:96px;}
.uv-gauge{position:relative;width:72px;height:72px;flex:none;}
.uv-gauge img{position:absolute;inset:6px;width:60px;height:60px;}
.uv-gauge svg{position:absolute;inset:0;transform:rotate(-90deg);}
.uv-gauge circle{fill:none;stroke-width:4;}
.uv-tx{flex:1;min-width:0;}
.uv-t1{display:block;font-weight:600;font-size:14.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.uv-t2{display:block;font-size:12.5px;color:var(--uv-ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.uv .uv-gobtn{width:76px;height:76px;flex:none;border-radius:50%;display:grid;place-items:center;text-align:center;font-weight:700;font-size:12.5px;line-height:1.15;padding:6px;color:#1a0d07;
  background:radial-gradient(circle at 40% 35%,#F2A587,#D97757 60%,#A84F30);box-shadow:0 0 0 4px #0A1424,0 0 0 5px rgba(229,138,104,.6),0 0 26px rgba(229,138,104,.5);}
.uv .uv-gobtn:hover{filter:brightness(1.07);}
/* a planet's card */
.uv-card{position:absolute;z-index:12;left:22px;top:74px;overflow-y:auto;width:360px;padding:18px 18px 16px;border-radius:16px;background:var(--uv-card);border:1px solid rgba(229,138,104,.55);box-shadow:0 18px 60px rgba(0,0,0,.55);animation:uvPop .28s cubic-bezier(.2,.8,.2,1);}
@keyframes uvPop{from{opacity:0;transform:translateY(6px) scale(.98);}}
.uv-card h3,.uv-sheet h3{font-family:var(--serif);font-size:23px;font-weight:600;margin:5px 0 6px;line-height:1.15;text-wrap:balance;}
.uv-card p,.uv-sheet p{margin:0;color:var(--uv-ink-2);font-size:13.5px;line-height:1.5;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}
.uv .uv-x{position:absolute;right:10px;top:10px;width:28px;height:28px;border-radius:50%;color:var(--uv-ink-3);font-size:18px;}
.uv-chips{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0;}
.uv-chip{font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;padding:3px 9px;border-radius:999px;border:1px solid currentColor;white-space:nowrap;}
.uv-chip.l1{color:#A9B4EE;}.uv-chip.l2{color:#D9CB7A;}.uv-chip.l3{color:#D7A5CF;}.uv-chip.tr,.uv-chip.l0{color:var(--uv-ink-2);}
.uv-figs{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:0 0 14px;}
.uv-figs dt{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--uv-ink-3);}
.uv-figs dd{margin:0;font-family:var(--serif);font-size:21px;font-weight:600;font-variant-numeric:tabular-nums;}
.uv-cls{font-size:12.5px;color:var(--uv-ink-2);margin:-4px 0 14px;display:flex;gap:6px;align-items:center;}
.uv-cls i{width:7px;height:7px;border-radius:50%;background:#8FB06E;box-shadow:0 0 0 3px rgba(143,176,110,.2);flex:none;}
.uv-btns{display:flex;gap:8px;flex-wrap:wrap;}
/* 269: the card's two buttons on one row; Thêm / Bỏ at the eyebrow's right */
.uv-card .uv-btns,.uv-sheet .uv-btns{flex-wrap:nowrap;}
.uv-card .uv-btns .uv-bt{flex:1 1 auto;min-width:0;padding:0 12px;font-size:13px;}
.uv-crow{display:flex;align-items:center;gap:10px;min-height:28px;}
.uv-crow .uv-eyebrow{flex:1;min-width:0;}
.uv-card .uv-crow{padding-right:30px;}
/* 269.1 (Khoa: "make sure the text label fit the button perfectly … rectangular"):
   written with `.uv` too, or the universe's reset (`.uv button`: no padding,
   the font inherited) outranks it and the label spills out */
.uv-shelf,.uv .uv-shelf{flex:none;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;
  height:auto;min-height:28px;padding:5px 12px;border-radius:7px;font-family:var(--sans);font-size:12.5px;line-height:1.25;
  font-weight:600;white-space:nowrap;cursor:pointer;}
.uv-shelf.add,.uv .uv-shelf.add{background:var(--accent);color:#1a0d07;border:1px solid var(--accent);animation:uvsGlow 2.4s ease-in-out infinite;}
.uv-shelf.rm,.uv .uv-shelf.rm{background:transparent;border:1px solid var(--line-2);color:var(--ink-3);font-weight:500;}
.uv-shelf.rm:hover{color:var(--ink-2);border-color:var(--ink-3);}
.uv-shelf.rm.ask{border-color:var(--accent);color:var(--accent-text);}
.uv .uv-shelf.dk.rm{border-color:var(--uv-line);color:var(--uv-ink-3);}
.uv .uv-shelf.dk.rm:hover{color:var(--uv-ink-2);}
.uv .uv-shelf.dk.rm.ask{border-color:#E58A68;color:#E58A68;}
.uv-shelf:disabled{opacity:.5;cursor:default;}
@keyframes uvsGlow{0%,100%{box-shadow:0 0 0 0 rgba(229,138,104,.0),0 0 8px rgba(229,138,104,.35)}50%{box-shadow:0 0 0 4px rgba(229,138,104,.18),0 0 16px rgba(229,138,104,.55)}}
@media (prefers-reduced-motion:reduce){.uv-shelf.add{animation:none;box-shadow:0 0 12px rgba(229,138,104,.5);}}
.uv .uv-bt,.uv-ov .uv-bt{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;font-weight:600;font-size:13.5px;white-space:nowrap;cursor:pointer;font-family:inherit;}
.uv .uv-bt.pri,.uv-ov .uv-bt.pri{background:var(--accent);color:#1a0d07;border:0;}
.uv .uv-bt.pri:hover,.uv-ov .uv-bt.pri:hover{background:#E48A6A;}
.uv .uv-bt.gh{border:1px solid var(--uv-line);color:var(--uv-ink);}
/* a phone: the regions as a scroll, the card from the foot */
.uv-phone .uv-scroll{position:absolute;inset:0;overflow-y:auto;overflow-x:hidden;padding:18px 0 140px;}
.uv-phone .uv-tools{top:16px;padding:2px 16px 8px;gap:6px;}
.uv-phone .uv-srch input{height:36px;}
/* 16px on a touch screen (269, Khoa's picture: the title and the role switch
   cut off once he tapped the search): `.uv-srch input` at 13.5px outranked
   135's rule for every field, and an iPhone zooms the page into a text box
   any smaller, and stays zoomed */
@media (pointer:coarse){.uv-srch input{font-size:16px;}}
.uv-phone .uv-key{height:30px;padding:0 11px;font-size:12.5px;}
.uv .uv-aa-ph{position:absolute;right:12px;top:-40px;height:28px;padding:0 11px;border-radius:999px;font-size:11.5px;color:var(--uv-ink-2);background:rgba(4,7,14,.9);border:1px solid var(--uv-line);}
.uv-ph-h{padding:0 16px 4px;display:flex;align-items:center;justify-content:space-between;gap:10px;}
.uv-ph-h .uv-role{flex:none;margin-top:4px;}
.uv .uv-ph-h .uv-role button{padding:4px 9px;font-size:12px;}
.uv-ph-h h2{font-family:var(--serif);font-size:21px;font-weight:600;margin:0;}
.uv-ph-h p{margin:0 0 8px;font-size:13px;color:var(--uv-ink-3);}
.uv-sec{padding:4px 16px 10px;}
.uv-sec-h{display:flex;align-items:baseline;justify-content:space-between;margin:14px 0 4px;font-family:var(--mono);font-size:10.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--uv-ink-3);}
.uv-sec-h.pad{padding:0 16px;margin-top:6px;}
.uv-mine{display:flex;gap:10px;overflow-x:auto;padding:2px 16px 14px;scrollbar-width:none;}
.uv-mine::-webkit-scrollbar{display:none;}
.uv .uv-mcard{flex:none;width:78%;display:flex;gap:12px;align-items:center;padding:12px;border-radius:16px;background:var(--uv-card);border:1px solid var(--uv-line);text-align:left;}
.uv-mcard img{width:58px;height:58px;flex:none;}
.uv-mc-tx{min-width:0;flex:1;display:flex;flex-direction:column;}
.uv-mcard .uv-t1{font-family:var(--serif);font-size:16px;}
.uv-bar{display:block;height:4px;border-radius:4px;background:rgba(255,255,255,.1);margin-top:7px;overflow:hidden;}
.uv-bar i{display:block;height:100%;background:var(--uv-acc);}
.uv-tools{position:sticky;top:0;z-index:4;padding:8px 16px 10px;background:linear-gradient(var(--uv-0) 75%,transparent);display:grid;gap:8px;}
.uv-hs{display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;margin:0 -16px;padding:0 16px 4px;}
.uv-hs::-webkit-scrollbar{display:none;}
.uv-constel{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:4px 6px;}
.uv .uv-constel .uv-pl{position:relative;transform:none;left:auto;top:auto;width:auto;min-width:0;padding:16px 0 8px;}
.uv-constel .uv-pl:nth-child(3n+2){margin-top:26px;}
.uv-constel .uv-nm{font-size:13px;white-space:normal;text-align:center;}
.uv-constel .uv-mt{font-size:9.5px;white-space:normal;text-align:center;line-height:1.5;}
.uv-constel .uv-tag{right:-18px;font-size:10px;}
.uv-none{color:var(--uv-ink-2);padding:0 16px;}
.uv-pdock{position:absolute;left:0;right:0;bottom:0;z-index:20;padding:22px 12px calc(12px + env(safe-area-inset-bottom,0px));
  background:linear-gradient(180deg,#132340,#0A1424);border-radius:50% 50% 0 0 / 26px 26px 0 0;border-top:1.5px solid rgba(229,138,104,.55);box-shadow:0 -10px 30px rgba(0,0,0,.5);}
.uv-pdock .uv-mod{padding:8px 10px;border-radius:14px;min-height:0;gap:10px;}
.uv-pdock .uv-gauge{width:56px;height:56px;}.uv-pdock .uv-gauge img{inset:5px;width:46px;height:46px;}
.uv .uv-pdock .uv-gobtn{width:64px;height:64px;font-size:11.5px;}
.uv-sheet-bg{position:absolute;inset:0;z-index:40;background:rgba(2,5,12,.55);}
.uv-sheet{position:absolute;left:0;right:0;bottom:0;z-index:41;padding:18px 16px calc(22px + env(safe-area-inset-bottom,0px));border-radius:20px 20px 0 0;background:var(--uv-1);border-top:1px solid rgba(229,138,104,.55);animation:uvUp .28s cubic-bezier(.2,.8,.2,1);}
@keyframes uvUp{from{transform:translateY(100%);}}
.uv-grab{width:40px;height:4px;border-radius:4px;background:var(--uv-line);margin:-6px auto 12px;}
.uv-sheet-hd{display:flex;gap:14px;align-items:center;margin-bottom:10px;}
.uv-sheet-hd img{width:74px;height:74px;flex:none;}
.uv-sheet-hd h3{font-size:22px;}
.uv-sheet .uv-bt{flex:1;}
/* the story and the landing, over everything */
.uv-ov{position:fixed;inset:0;z-index:10000;background:var(--uv-0);overflow:hidden;}
.uv-ov-sky{position:absolute;inset:0;width:100%;height:100%;display:block;}
.uv-skip{position:absolute;right:16px;top:calc(16px + env(safe-area-inset-top,0px));z-index:3;height:34px;padding:0 14px;border-radius:999px;border:1px solid var(--uv-line);color:var(--uv-ink-2);font:inherit;font-size:12.5px;background:rgba(6,13,25,.6);cursor:pointer;}
.uv-story{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:24px max(24px,env(safe-area-inset-left,0px));}
.uv-story .uv-eyebrow{margin-bottom:22px;}
.uv-lines{max-width:680px;display:grid;gap:18px;}
.uv-ln{font-family:var(--serif);font-size:clamp(22px,3.4vw,38px);line-height:1.2;font-weight:600;opacity:0;transform:translateY(10px);transition:opacity 1s,transform 1s;}
.uv-ln.sub{font-family:var(--sans);font-weight:400;font-size:clamp(15px,1.6vw,18px);line-height:1.6;color:var(--uv-ink-2);}
.uv-ln.on{opacity:1;transform:none;}
.uv-go2{margin-top:34px;opacity:0;transition:opacity .6s;}
.uv-go2.on{opacity:1;}
.uv-land-pl{position:absolute;left:50%;bottom:0;width:min(120vmin,900px);aspect-ratio:1;transform:translate(-50%,92%) scale(.45);transition:transform var(--dur) cubic-bezier(.3,0,.2,1);}
.uv-land-pl img{width:100%;height:100%;max-width:none;}
.uv-land.go .uv-land-pl{transform:translate(-50%,58%) scale(1);}
.uv-land-col{position:absolute;inset:0;z-index:2;display:flex;flex-direction:column;align-items:center;gap:clamp(18px,4vh,34px);padding:clamp(52px,11vh,120px) 20px 80px;}
.uv-land-t{text-align:center;}
.uv-land-t h2{font-family:var(--serif);font-size:clamp(30px,5vw,56px);font-weight:700;margin:8px 0 0;line-height:1.1;}
.uv-land-t p{color:var(--uv-ink-2);margin:8px 0 0;}
.uv-layers{display:grid;gap:10px;width:min(480px,100%);}
.uv-layers div{display:flex;gap:12px;align-items:baseline;font-size:14px;line-height:1.4;padding:10px 14px;border-radius:12px;background:rgba(6,13,25,.88);border:1px solid var(--uv-line);opacity:0;transform:translateY(10px);transition:opacity .7s,transform .7s;}
.uv-layers div.on{opacity:1;transform:none;}
.uv-layers b{font-family:var(--mono);font-weight:500;font-size:11px;letter-spacing:.12em;color:var(--uv-acc);white-space:nowrap;text-transform:uppercase;}
.uv-lbar{position:absolute;left:50%;bottom:28px;z-index:3;transform:translateX(-50%);width:min(380px,80%);height:3px;border-radius:3px;background:rgba(255,255,255,.14);overflow:hidden;}
.uv-lbar i{display:block;height:100%;width:0;background:var(--uv-acc);transition:width var(--dur) linear;}
.uv-land.go .uv-lbar i{width:100%;}
/* the top bar's way back to it */
.uv-open{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 12px 0 9px;margin-left:4px;border:1px solid var(--hdr-line);border-radius:999px;background:none;color:var(--hdr-ink);font:inherit;font-size:13px;cursor:pointer;flex:none;}
.uv-open:hover{background:var(--hdr-soft);}
.uv-open-i{width:14px;height:14px;border-radius:50%;background:radial-gradient(circle at 35% 30%,#F3B49B,#D97757 55%,#8E3D22);box-shadow:0 0 0 2px rgba(217,119,87,.25);}
@media (max-width:700px){.uv-open-t{display:none;}.uv-open{padding:0 8px;}}
@media (max-width:1180px){.uv-grid{grid-template-columns:minmax(0,1fr) 360px minmax(0,1fr);}}
@media (prefers-reduced-motion:reduce){.uv-lock,.uv-ship{animation:none;}.uv-ln,.uv-go2,.uv-layers div,.uv-land-pl,.uv-lbar i{transition:none;}}

/* ---- build 266: the course column is the planet (83-universe.js, `UvPlanetCard`) ----
   On the app's own ground, light or dark (space is the universe's only).
   The hub's card under it keeps its act row and classes; its own head is
   not drawn (`Shelf` with `only`). */
.uvc{position:relative;padding:18px 16px 14px;margin:0 0 12px;border-radius:16px;background:var(--card);border:1px solid var(--line);box-shadow:var(--shadow);}
.uvc-orb{position:relative;width:118px;height:118px;margin:4px auto 14px;}
.uvc-orb img{position:absolute;inset:-14%;width:128%;height:128%;max-width:none;}
.uvc-ring{position:absolute;inset:-9px;width:calc(100% + 18px);height:calc(100% + 18px);transform:rotate(-90deg);}
.uvc-ring circle{fill:none;stroke-width:3;}
.uvc-ring .trk{stroke:var(--line);}
.uvc-ring .val{stroke:var(--accent);stroke-linecap:round;}
.uvc-eyebrow{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent-text);}
.uvc-t{font-family:var(--serif);font-size:24px;font-weight:600;line-height:1.15;margin:4px 0 6px;text-wrap:balance;}
.uvc-g{margin:0;color:var(--ink-2);font-size:13.5px;line-height:1.5;}
.uvc-chips{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 12px;}
.uvc-chip{font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;padding:3px 9px;border-radius:999px;}
.uvc-chip.l1{background:var(--lv1-wash);color:var(--lv1-text);}
.uvc-chip.l2{background:var(--lv2-wash);color:var(--lv2-text);}
.uvc-chip.l3{background:var(--lv3-wash);color:var(--lv3-text);}
.uvc-chip.tr{border:1px solid var(--line-2);color:var(--ink-2);}
.uvc-figs{display:grid;grid-template-columns:repeat(3,1fr);margin:0;border-top:1px solid var(--line);}
.uvc-figs div{padding:10px 0 4px;}
.uvc-figs div+div{padding-left:12px;border-left:1px solid var(--line);}
.uvc-figs dt{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);white-space:nowrap;}
.uvc-figs dd{margin:0;font-family:var(--serif);font-size:21px;font-weight:600;font-variant-numeric:tabular-nums;}
.uvc-prog{margin-top:10px;padding-top:10px;border-top:1px solid var(--line);}
.uvc-prow{display:flex;justify-content:space-between;align-items:baseline;font-size:13px;color:var(--ink-2);}
.uvc-prow b{color:var(--ink);font-weight:600;}
.uvc-bar{height:6px;border-radius:6px;background:var(--sunk);margin-top:7px;overflow:hidden;}
.uvc-bar i{display:block;height:100%;border-radius:6px;background:var(--done);}
.uvc-none{padding:18px 4px;color:var(--ink-2);font-size:14px;display:grid;gap:10px;justify-items:start;}
.uvc-none p{margin:0;}
.shelf-only .kcard{margin:0;}
.shelf-only .kcard:empty{display:none;}
/* 266.1: the class card under the planet in the planet card's language
   (`uvkHead`, 90-classes.js): the same ground, edge, corner and shadow, no
   selection wash (there is only the one course here); an orbit, a serif title,
   one line in the mono face, the buttons on the right. */
.kcard.uvk,.kcard.uvk:hover,.kcard.uvk.on{border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow);}
.kcard.uvk.on::after{display:none;}
.uvk-head{display:flex;align-items:center;gap:11px;padding:12px 12px 12px 14px;}
.uvk-orb{width:38px;height:38px;flex:none;overflow:visible;}
.uvk-orb .o{fill:none;stroke:var(--ink-3);stroke-width:1.1;opacity:.7;}
.uvk-orb .p{fill:var(--pc,var(--accent));}
.uvk-orb .m{fill:var(--accent);stroke:var(--card);stroke-width:1.2;}
.uvk-orb.wait .o{stroke-dasharray:2.5 2.5;}
.uvk-orb.off .p{opacity:.4;}
.uvk-tx{flex:1;min-width:0;}
.uvk-t{font-family:var(--serif);font-size:17px;font-weight:600;line-height:1.2;color:var(--ink);text-wrap:balance;overflow-wrap:anywhere;}
.uvk-s{margin-top:3px;font-family:var(--mono);font-size:10.5px;letter-spacing:.06em;color:var(--ink-3);}
.uvk-head.off .uvk-t{font-family:var(--sans);font-size:14px;font-weight:500;color:var(--ink-2);}
.kcard.uvk .kcard-new{padding:8px 14px;}
.kcard.uvk .kcard-kids{padding:2px 14px 10px;}
.kcard.uvk .fb{margin:0 14px 12px;}
/* 267: the about link under the planet card's figures */
.uvc-about{margin-top:12px;padding-top:12px;border-top:1px solid var(--line);}
/* 269, step 3: ONE card. Thêm / Bỏ at its top right; the hub's card for the
   course inside it, edge to edge, flat */
.uvc-shelf{position:absolute;top:14px;right:14px;z-index:1;}
.uvc-in{margin:12px -16px 0;border-top:1px solid var(--line);}
.uvc-in:empty{display:none;}
.uvc-in .kcard.uvk,.uvc-in .kcard.uvk:hover,.uvc-in .kcard.uvk.on{border:0;border-radius:0;box-shadow:none;background:transparent;}
.uvc-in .kcard.uvk .kcard-kids{border-top:0;}
.uvk-head.off{padding:12px 16px;}
.uvk-codew{padding:0 16px 12px 59px;}
.uvk-code{padding:0;font-size:13px;color:var(--accent-text);text-decoration:underline;text-underline-offset:3px;text-align:left;}
.uvk-code:hover{color:var(--accent);}
/* a teacher's class line under the planet: one press picks the class (its
   tools are on Learn's card); the picked one washed, an accent at its edge */
.kls-pick .kls-win{margin-left:0;}
.kls-pick:not(.on) .kls-win:hover{background:var(--card-2);}
.kls-pick.on .kls-win{background:var(--accent-wash);box-shadow:inset 3px 0 0 var(--accent);}
.kls-start{flex:none;white-space:nowrap;}

/* ---- build 267: Learn with today on top, the stages folded (87-learn.js) ----
   From the mock-up's second screen: a card for the session to do next (an
   accent edge washed down to the card, a dot that breathes, the parts, one
   button), then each stage a header that folds, its sessions as rows with a
   round mark (done, now, shut), its checkpoint a gold diamond. */
@keyframes lnBlink{50%{opacity:.25}}
.ln-hero{margin:14px 16px 4px;padding:16px;border-radius:14px;border:1px solid var(--accent);
  background:linear-gradient(180deg,var(--accent-wash),var(--card) 85%);}
.ln-hero.shut,.ln-hero.all,.ln-hero.tall{border-color:var(--line-2);background:var(--card);}
.ln-hero-e{display:flex;align-items:center;gap:7px;font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent-text);}
.ln-hero.shut .ln-hero-e,.ln-hero.all .ln-hero-e{color:var(--ink-3);}
.ln-hero-e i{width:7px;height:7px;border-radius:50%;background:var(--accent);flex:none;animation:lnBlink 1.6s infinite;}
.ln-hero.tall .ln-hero-e i{background:var(--done);animation:none;}
.ln-hero-t{font-family:var(--serif);font-size:20px;font-weight:600;line-height:1.2;margin:6px 0 10px;text-wrap:balance;}
.ln-hero-t span{font-family:var(--mono);font-size:13px;font-weight:500;color:var(--ink-3);letter-spacing:.04em;}
.ln-hero-p{margin:6px 0 0;font-size:13.5px;color:var(--ink-2);}
.ln-hero-r{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.ln-hero-r small{color:var(--ink-3);font-size:12px;}
.ln-hero-r .btn{width:auto;margin:0;}
.ln-see{padding:8px 14px;border-radius:999px;border:1px solid var(--line-2);font-size:13px;font-weight:500;color:var(--ink);background:transparent;}
/* 278.2 (Khoa, a picture of Xem bài pressed: an orange pill, no words): every
   `.btn` takes --accent-deep as its ground on hover, which in the dark set is
   the same colour as --accent-text; this one keeps its clear ground */
.ln-see:hover{background:transparent;border-color:var(--accent);color:var(--accent-text);}
/* 269: the class's tool kit under the teacher's buttons (`ClassKit`) */
.ckit{margin-top:12px;padding-top:12px;border-top:1px dashed var(--line);}
.ckit-row{display:flex;flex-wrap:wrap;gap:6px;}
.ckit-b{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:999px;border:1px solid var(--line-2);
  background:var(--card);font-size:12.5px;color:var(--ink);white-space:nowrap;}
.ckit-b i{font-style:normal;color:var(--ink-3);}
.ckit-b:hover{border-color:var(--accent);color:var(--accent-text);}
.ckit-b.danger{color:var(--wrong);}
.ckit-b.danger:hover{border-color:var(--wrong);color:var(--wrong);}
.ckit .kls-said{margin-top:8px;}
.ckit .kls-ren{margin-top:10px;}
.ckit .fb{margin:10px 0 0;}
.ln-stages{margin:14px 16px 0;border:1px solid var(--line);border-radius:14px;background:var(--card);box-shadow:var(--shadow);overflow:hidden;}
.ln-stg+.ln-stg{border-top:1px solid var(--line);}
.ln-stg-h{width:100%;display:flex;align-items:center;gap:10px;padding:12px 14px;text-align:left;}
.ln-stg-h:hover{background:var(--card-2);}
.ln-stg-n{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);white-space:nowrap;}
.ln-stg-t{flex:1;min-width:0;font-weight:600;font-size:13.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.ln-stg-c{font-size:12px;color:var(--ink-3);white-space:nowrap;font-variant-numeric:tabular-nums;}
.ln-chev{width:16px;height:16px;flex:none;fill:none;stroke:var(--ink-3);stroke-width:2;transition:transform .2s;}
.ln-stg.open .ln-chev{transform:rotate(180deg);}
.ln-stg-b{padding:0 8px 8px;}
.ln-row{width:100%;display:flex;align-items:center;gap:12px;padding:8px 10px;border-radius:10px;text-align:left;color:var(--ink);}
button.ln-row:hover{background:var(--card-2);}
.ln-dot{width:22px;height:22px;border-radius:50%;flex:none;display:grid;place-items:center;font-size:11px;font-weight:700;
  border:1.5px solid var(--line-2);background:var(--card);}
.ln-row.done .ln-dot{border:0;background:radial-gradient(circle at 35% 30%,#C9DDB0,var(--done) 70%);color:#fff;}
.ln-row.now{background:var(--accent-wash);}
.ln-row.now .ln-dot{border:0;background:radial-gradient(circle at 35% 30%,#F6C2AD,var(--accent) 70%);box-shadow:0 0 0 4px var(--accent-wash),0 0 0 5px var(--accent);}
.ln-row.lock .ln-dot,.ln-row.soon .ln-dot{background:var(--sunk);border-color:var(--line);}
.ln-row.lock .ln-ti,.ln-row.soon .ln-ti{color:var(--ink-3);}
.ln-ti{flex:1;min-width:0;font-size:13.5px;font-weight:500;line-height:1.35;}
.ln-ti b{font-family:var(--mono);font-size:11px;font-weight:500;color:var(--ink-3);margin-right:2px;}
.ln-ti small{display:block;font-weight:400;font-size:12px;color:var(--ink-3);}
.ln-row.done .ln-ti small{color:var(--done);}
.ln-row.now .ln-ti small{color:var(--accent-text);}
.ln-act{font-size:12px;font-weight:600;color:var(--accent-text);white-space:nowrap;}
.ln-row.ck .ln-dot{width:15px;height:15px;margin:3.5px;border-radius:4px;transform:rotate(45deg);background:var(--gold-wash);border:1.5px solid var(--gold);}
.ln-row.ck.done .ln-dot{background:var(--gold);}
.ln-row.ck .ln-act{color:var(--ink-3);}
/* 285.3: the Learn row pressed once (its stop shown in Practice), and that stop.
   The stop's ring is a shadow, not padding: the route's curve is measured
   from the stones, and padding would move them under it. */
.ln-row.sel{box-shadow:inset 0 0 0 1.5px var(--accent);}
.ln-row .ln-go2{color:var(--accent-text);}
.jn-row.jn-mark>.node-wrap{border-radius:14px;box-shadow:0 0 0 6px var(--accent-wash),0 0 0 7.5px var(--accent);}
@media (prefers-reduced-motion:reduce){.ln-hero-e i{animation:none;}.ln-chev{transition:none;}}

/* ---- build 268: Practice as a route of stones (85-journey.js, `JnRock`) ----
   The mock-up's surface: a dashed track between low stones that zig-zag down
   the column, today's stage only (earlier and later ones a press away), the
   stone of today in the course's colour with your initial on it, done stones
   green with a flag, shut ones grey, the checkpoint a gold beacon. The panel
   under a stone is the "you are here" card, ending on one big Tiếp tục. */
.path.jn{padding:16px 16px 10px;--jn-l1:var(--line-2);--jn-l2:var(--sunk);--jn-l3:var(--line);}
/* a shut stone in dark: the light faces went near black, the padlock lost */
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .path.jn{--jn-l1:#5A5A55;--jn-l2:#3E3E3B;--jn-l3:#2C2C2A;}}
:root[data-theme="dark"] .path.jn{--jn-l1:#5A5A55;--jn-l2:#3E3E3B;--jn-l3:#2C2C2A;}
.jn-row{align-items:stretch;padding:6px 0;}
.jn-row .node-wrap{align-self:flex-start;gap:12px;min-width:0;}
.jn-row .node-label{min-width:0;}
.jn-stone{position:relative;width:62px;height:46px;flex:none;cursor:pointer;scroll-margin-top:18px;transition:transform .18s cubic-bezier(.2,.8,.2,1);}
.jn-stone:hover{transform:translateY(-2px);}
.jn-stone.is-locked,.jn-stone.is-soon{cursor:default;}
.jn-stone.is-locked:hover,.jn-stone.is-soon:hover{transform:none;}
.jn-stone.is-check{height:56px;}
.jn-stone:focus-visible{outline:2px solid var(--accent);outline-offset:4px;border-radius:12px;}
.jn-rock-svg{display:block;width:100%;height:100%;overflow:visible;}
.jn-rock-svg .lit{filter:brightness(1.25);}
.jn-rock-svg .shade{filter:brightness(.78);}
.jn-rock-svg.now{filter:drop-shadow(0 0 9px color-mix(in srgb,var(--pc,var(--accent)) 55%,transparent));}
.jn-rock-svg.beacon{filter:drop-shadow(0 0 10px rgba(226,178,76,.35));}
.jn-rock-svg.beacon-off{opacity:.5;filter:saturate(.45);}
.jn-stone.is-soon .jn-rock-svg{opacity:.45;}
.jn-n{position:absolute;inset:4px 0 6px;display:grid;place-items:center;font-family:var(--mono);font-size:12.5px;font-weight:600;
  color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35);pointer-events:none;}
.jn-n.shut{color:var(--ink-3);text-shadow:none;}
.jn-stone.is-pay .jn-n,.jn-stone.is-add .jn-n{color:var(--accent-text);}
.jn-me{position:absolute;left:50%;top:-14px;transform:translateX(-50%);z-index:2;width:24px;height:24px;border-radius:50%;
  display:grid;place-items:center;background:var(--card);border:2px solid var(--accent);color:var(--accent-text);
  font-size:11px;font-weight:700;box-shadow:var(--shadow);}
.jn-stage{position:relative;z-index:1;display:flex;align-items:baseline;gap:8px;width:fit-content;max-width:100%;margin:12px 0 4px;padding:3px 10px;
  border-radius:999px;background:var(--paper);border:1px solid var(--line);
  font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);}
.jn-stage span{font-family:var(--sans);font-size:12px;letter-spacing:0;text-transform:none;font-weight:600;color:var(--ink-2);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.jn-more{position:relative;z-index:1;display:block;width:100%;margin:4px 0;padding:8px;border-radius:10px;text-align:center;
  font-size:12.5px;color:var(--ink-3);background:var(--paper);}
.jn-more:hover{background:var(--card-2);color:var(--ink-2);}
.station.jn-here{max-width:none;border-radius:16px;margin:10px 0 6px;}
.station.jn-here.now{border-color:var(--accent);box-shadow:0 10px 30px rgba(217,119,87,.16);}
.jn-here-e{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent-text);margin-bottom:3px;}
.jn-here .act.is-next{background:var(--accent-wash);}
.jn-go-w{padding:12px 15px 14px;border-top:1px solid var(--line);}
.jn-go{display:block;width:100%;height:46px;padding:0 16px;border-radius:999px;background:var(--accent);color:var(--on-accent);
  font-weight:600;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-shadow:0 3px 0 var(--accent-deep);}
.jn-go:hover{background:var(--accent-deep);}
.jn-go:active{transform:translateY(2px);box-shadow:0 1px 0 var(--accent-deep);}
@media (prefers-reduced-motion:reduce){.jn-stone{transition:none;}}
/* ===================== THE COURSE SCREEN'S COLOURS (269.8) =====================
   Khoa's colour refresh (2026-10-23, the canvas
   https://claude.ai/artifact/1cCryKUoVmd1CvFC5ky7Gw: "help me update the color
   of the app exactly like this mock-up, including the Practice page"). It
   replaces 269.4-269.7: the white page with a night at its foot (`.uv-hz`,
   gone from 98-app.js), the clear columns, the cards that faded downward.

   SÁNG  a warm paper page #F6F4EF; EACH COLUMN IS A WHITE CARD (#E5E0D7 edge,
         20 px corners); inside it, cards white with the same edge. Ink
         #1F1E1B / #57524B / #6E685F (15.2 · 7.7 · 4.8 : 1), clay #D97757 with
         ink on it (white on it is 3.1 : 1), clay text #A9492A, done #1E7A4C,
         stars #B7791F. Top bar solid navy #0B1F3F, a 2 px clay line.
   TỐI   the universe's still stars (`--e2i-sky`, 83-universe.js) on the
         canvas's night #060912 with a navy haze top left, a faint clay haze
         bottom right and a violet one; a few brighter stars twinkle
         (`.cols.uv-surf::before`). EACH COLUMN IS A GLASS CARD: navy-black
         fading 78 % → 55 %, a starlight edge (#DCE4FF at 8 %), 24 px corners.
         Ink #ECE7DF / #B4ADA2 / #9A94A0, clay #E08A6C, clay text #EE9F82,
         done #5DBE8A, stars #E2B048. Top bar navy at 60 %, the stars behind it.
   BOTH  the lesson being worked on (Learn's today card, Practice's open stop,
         the Pro note, Admin's test mode) a clay tint with a clay edge, solid,
         no fade.

   NO BLUR. The canvas blurs the sky behind a column (16 px). A blur
   (`backdrop-filter`) makes the column the frame of every `position:fixed`
   thing inside it, and some dialogs are drawn inside a column (the live view,
   the hub's confirmations): they would open inside the column and be cut off
   by it. So the glass is a denser fill instead (78 → 55 % where the canvas has
   66 → 38 % with the blur), and 269.4's blur on the cards is gone too.

   THE PHONE (Khoa's iPhone picture, Practice in Sáng). On a phone the band at
   the top of a column (`.col-bar`, the progress line) is pinned while the
   page scrolls; it stands on `--paper`, which 269.4 made see-through (white
   at 88 %), so the stops scrolled visibly under the band. Here `--paper` in a
   column is the column's own ground, solid. The night at the foot also sat in
   the middle of a short Practice column with white below it: gone.

   What sits in a column takes the column's set; every card in the `:is(…)`
   lists takes the card ground. A new card that draws its own ground inside a
   column joins them. Menus and dialogs keep the app's theme. The rest of the
   app (Lớp của tôi, the landing page, lesson pages) is not touched. `--ph` is
   the planet's hue (98-app.js, `uvHue`). */
.cols.uv-surf{--ph:232;position:relative;isolation:isolate;}
.cols.uv-surf>.col{position:relative;z-index:1;color:var(--ink);}

/* ---- the page: paper by day, the sky at night ---- */
:root:not([data-theme="dark"]) .app:has(>.cols.uv-surf){background:#F6F4EF;}
:root:not([data-theme="dark"]) .cols.uv-surf{--page:#F6F4EF;background:#F6F4EF;}
:root[data-theme="dark"] .app:has(>.cols.uv-surf){
  background-color:#060912;
  background-image:var(--e2i-sky,none),
    radial-gradient(55% 45% at 12% 8%,rgba(32,64,128,.30),transparent 70%),
    radial-gradient(45% 40% at 88% 92%,rgba(217,119,87,.07),transparent 70%),
    radial-gradient(40% 35% at 70% 30%,rgba(60,50,120,.12),transparent 70%);
  background-size:1400px 1000px,100% 100%,100% 100%,100% 100%;
  background-position:center top,0 0,0 0,0 0;
  background-repeat:repeat,no-repeat,no-repeat,no-repeat;}
:root[data-theme="dark"] .cols.uv-surf{--page:transparent;background:transparent;}
/* a few brighter stars over the still sky, breathing in and out */
:root[data-theme="dark"] .cols.uv-surf::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:
    radial-gradient(1.6px 1.6px at 120px 60px,rgba(255,255,255,.9),transparent),
    radial-gradient(1.8px 1.8px at 420px 300px,rgba(214,224,255,.85),transparent),
    radial-gradient(1.5px 1.5px at 650px 140px,rgba(255,236,220,.8),transparent),
    radial-gradient(1.6px 1.6px at 260px 520px,rgba(255,255,255,.8),transparent);
  background-size:701px 701px,743px 743px,811px 811px,677px 677px;
  animation:e2iTw 6s ease-in-out infinite;}
@keyframes e2iTw{0%,100%{opacity:.45;}50%{opacity:1;}}
@media (prefers-reduced-motion:reduce){:root[data-theme="dark"] .cols.uv-surf::before{animation:none;opacity:.7;}}

/* ---- the top bar on the course screen (the universe's keeps its own) ---- */
:root:not([data-theme="dark"]) .app:has(>.cols.uv-surf) .topbar{background:#0B1F3F;border-bottom:2px solid #D97757;}
:root[data-theme="dark"] .app:has(>.cols.uv-surf) .topbar{background:rgba(8,20,44,.6);border-bottom:1px solid rgba(224,138,108,.55);}
/* the phone's three steps on the page's ground (their gap and notch are --paper) */
:root:not([data-theme="dark"]) .app:has(>.cols.uv-surf) .mobile-tabs{--paper:#F6F4EF;--sunk:#EAE6DE;--ink-3:#6E685F;border-bottom-color:#E5E0D7;}
:root[data-theme="dark"] .app:has(>.cols.uv-surf) .mobile-tabs{--paper:#080B15;--sunk:#161A27;--ink-3:#9A94A0;border-bottom-color:rgba(220,228,255,.08);}

/* ---- SÁNG: the column a white card ---- */
:root:not([data-theme="dark"]) .cols.uv-surf>.col{color-scheme:light;
  background:#FFFFFF;border:1px solid #E5E0D7;border-radius:20px;
  --paper:#FFFFFF;--panel:#F6F4EF;--card:#FFFFFF;--card-2:#F6F4EF;
  --ink:#1F1E1B;--ink-2:#57524B;--ink-3:#6E685F;--line:#EEE9E1;--line-2:#DCD6CC;
  --accent:#D97757;--accent-deep:#B85C3E;--accent-wash:#FBEFE9;--accent-text:#A9492A;--on-accent:#1F1E1B;
  --done:#1E7A4C;--done-wash:#E7F2EC;--on-done:#FFFFFF;--wrong:#B4553A;--wrong-wash:#F8E5DF;--err-ink:#A33A22;
  --gold:#B7791F;--gold-wash:#F7EDD6;
  --lv1:#4E5C9E;--lv1-text:#3A4478;--lv1-wash:#EAECF8;--lv2:#7A7B42;--lv2-text:#55561F;--lv2-wash:#F0F0DC;
  --lv3:#96628F;--lv3-text:#6A3A63;--lv3-wash:#F6E9F4;
  --sunk:#F1EEE8;--track:#EEE9E1;--glass:#F6F4EF;--shadow:none;
  --hero:transparent;--locked-bg:#F6F4EF;--soon-bg:#F9F7F3;--lock:#CFC8BD;--read-ink:#1F1E1B;
  --c-fill:#FFFFFF;--c-edge:#E5E0D7;--g-fill:#FBEFE9;--g-edge:#EFCBBB;--veil:#FFFFFF;}

/* ---- TỐI: the column a glass card on the sky ---- */
:root[data-theme="dark"] .cols.uv-surf>.col{color-scheme:dark;
  background:linear-gradient(180deg,rgba(14,17,27,.78) 0%,rgba(14,17,27,.55) 100%);
  border:1px solid rgba(220,228,255,.08);box-shadow:inset 0 1px 0 rgba(255,255,255,.05);border-radius:24px;
  --paper:rgba(10,13,21,.95);--panel:rgba(255,255,255,.035);--card:rgba(255,255,255,.035);--card-2:rgba(255,255,255,.06);
  --ink:#ECE7DF;--ink-2:#B4ADA2;--ink-3:#9A94A0;--line:rgba(220,228,255,.08);--line-2:rgba(220,228,255,.16);
  --accent:#E08A6C;--accent-deep:#EE9F82;--accent-wash:rgba(224,138,108,.10);--accent-text:#EE9F82;--on-accent:#1A1917;
  --done:#5DBE8A;--done-wash:rgba(93,190,138,.12);--on-done:#0B0E16;--wrong:#E58270;--wrong-wash:rgba(229,130,112,.12);--err-ink:#EC7E7E;
  --gold:#E2B048;--gold-wash:rgba(226,176,72,.12);
  --lv1:#7280C4;--lv1-text:#D4DAFA;--lv1-wash:rgba(114,128,196,.18);--lv2:#A89C4E;--lv2-text:#F2F2CE;--lv2-wash:rgba(168,156,78,.18);
  --lv3:#AC77A5;--lv3-text:#F0CCEB;--lv3-wash:rgba(172,119,165,.18);
  --sunk:rgba(0,0,0,.28);--track:rgba(255,255,255,.08);--glass:rgba(255,255,255,.035);--shadow:none;
  --hero:transparent;--locked-bg:rgba(255,255,255,.03);--soon-bg:rgba(255,255,255,.02);--lock:#3A4156;--read-ink:#ECE7DF;
  --c-fill:rgba(255,255,255,.02);--c-edge:rgba(220,228,255,.08);--g-fill:rgba(224,138,108,.10);--g-edge:rgba(224,138,108,.30);
  --veil:#0B0E17;}

/* ---- the cards in a column: their own ground, a plain edge ---- */
.cols.uv-surf>.col :is(.uvc,.ln-hero,.ln-stages,.l-stage,.station,.paywall,.admin,.l-today,.l-flow){
  position:relative;color:var(--ink);border:1px solid var(--c-edge);box-shadow:none;background:var(--c-fill);}
/* the lesson being worked on: a clay tint, a clay edge */
.cols.uv-surf>.col :is(.ln-hero.go,.ln-hero.t,.station.jn-here.now,.paywall,.admin){
  background:var(--g-fill);border-color:var(--g-edge);}
/* small things inside a column take the card's ground */
.cols.uv-surf>.col .fb{background:var(--card);}
.cols.uv-surf>.col .uvc-in .kcard.uvk{background:transparent;}
.cols.uv-surf .jn-more{background:var(--sunk);}
/* Practice's open stop stands over the route (269.5, Khoa: "when open the card
   in practice, hide the dash line across it"): its ground is laid on the
   column's own (`--veil`), so the dashed track behind it is covered even
   where the ground is see-through (Tối). */
.cols.uv-surf>.col .path.jn .station.jn-here{z-index:1;background:linear-gradient(var(--c-fill),var(--c-fill)),var(--veil);}
.cols.uv-surf>.col .path.jn .station.jn-here.now{background:linear-gradient(var(--g-fill),var(--g-fill)),var(--veil);}
/* A LIST IN A COLUMN (269.7, Khoa's picture of the test-mode list, white with
   pale text in Tối): a select's list takes the select's colours, and its
   ground here may be see-through, which Chrome draws as white. Its options
   stand on the column's solid ground in the column's ink. */
.cols.uv-surf select :is(option,optgroup){background-color:var(--veil);color:var(--ink);}
/* the "you are here" initial on today's stone stands on the column's ground */
.cols.uv-surf .jn-me{background:var(--veil);}
/* what is done: the green, the tick dark on it in Tối */
.cols.uv-surf .ln-row.done .ln-dot{background:var(--done);color:var(--on-done);}
.cols.uv-surf .act.is-done .act-box{color:var(--on-done);}
/* a shut stone: the lock colour, a step either side */
:root[data-theme="dark"] .cols.uv-surf .path.jn{--jn-l1:#465069;--jn-l2:#3A4156;--jn-l3:#2C3244;}
:root:not([data-theme="dark"]) .cols.uv-surf .path.jn{--jn-l1:#DDD7CD;--jn-l2:#CFC8BD;--jn-l3:#BDB5A8;}

@media (min-width:901px){
  .cols.uv-surf{padding:16px 16px 18px;overflow:hidden;}
  .cols.uv-surf>.handle{flex:0 0 16px;background:transparent;}
  .cols.uv-surf>.handle:hover{background:linear-gradient(90deg,transparent 7px,var(--accent) 7px,var(--accent) 9px,transparent 9px);}
}
@media (max-width:900px){
  .cols.uv-surf>.col.m-on{margin:8px;width:calc(100% - 16px)!important;min-height:calc(100% - 16px);}
  /* the pinned band: the column's own solid ground, its top corners the
     card's; the strip above it (a seam guard, `.col-bar::before`) the page's,
     so at rest it does not show as a white edge over the card's corners */
  .cols.uv-surf .col-bar{border-radius:19px 19px 0 0;}
  :root[data-theme="dark"] .cols.uv-surf .col-bar{border-radius:23px 23px 0 0;}
  .cols.uv-surf .col-bar::before{background:var(--page);}
}

/* ===================== PRACTICE IN THE SAME COLOURS (269.8) =====================
   The activity window (60-player.js, `.player`) takes the course screen's two
   sets (Khoa: "including the Practice page"). Sáng: the paper page, white
   cards, the same ink, clay and green. Tối (269.7's sky, Khoa: "can you apply
   the sky to our practice background too"): the canvas's night behind it, the
   head navy glass, cards a dense glass so a long text reads on a still
   ground; what stands on the page's ground (`--paper`: the bands pinned over
   scrolling text, the sticky heads) stands on the night's near-black. */
:root:not([data-theme="dark"]) .player{
  --paper:#F6F4EF;--panel:#F1EEE8;--card:#FFFFFF;--card-2:#F6F4EF;
  --ink:#1F1E1B;--ink-2:#57524B;--ink-3:#6E685F;--line:#E5E0D7;--line-2:#D6CFC4;
  --accent-wash:#FBEFE9;--accent-text:#A9492A;--on-accent:#1F1E1B;
  --done:#1E7A4C;--done-wash:#E7F2EC;--gold:#B7791F;
  --sunk:#F1EEE8;--track:#EEE9E1;--glass:rgba(246,244,239,.7);--read-ink:#1F1E1B;}
:root[data-theme="dark"] .player{
  --paper:#070A12;--panel:rgba(255,255,255,.035);--card:rgba(16,19,30,.86);--card-2:rgba(255,255,255,.06);
  --ink:#ECE7DF;--ink-2:#B4ADA2;--ink-3:#9A94A0;--line:rgba(220,228,255,.09);--line-2:rgba(220,228,255,.17);
  --accent:#E08A6C;--accent-deep:#EE9F82;--accent-wash:rgba(224,138,108,.12);--accent-text:#EE9F82;--on-accent:#1A1917;
  --done:#5DBE8A;--done-wash:rgba(93,190,138,.13);--gold:#E2B048;--gold-wash:rgba(226,176,72,.12);
  --sunk:rgba(0,0,0,.3);--track:rgba(255,255,255,.08);--glass:rgba(255,255,255,.04);--read-ink:#E4DFD6;
  background-color:#060912;
  background-image:var(--e2i-sky,none),
    radial-gradient(55% 45% at 12% 8%,rgba(32,64,128,.30),transparent 70%),
    radial-gradient(45% 40% at 88% 92%,rgba(217,119,87,.07),transparent 70%);
  background-size:1400px 1000px,100% 100%,100% 100%;background-position:center top,0 0,0 0;
  background-repeat:repeat,no-repeat,no-repeat;}
:root[data-theme="dark"] .player .p-head{background:rgba(8,20,44,.72);border-bottom-color:rgba(224,138,108,.4);}

/* ================= LỚP CỦA TÔI IN THE UNIVERSE LOOK (270) =================
   Khoa (2026-10-24): "redesign my classes page to match the universe theme";
   decided on the canvas https://claude.ai/artifact/YAgr7TVoNu5UhbDHei5qMF
   (width 1120; Tối = glass cards, "tables and lists keep a solid ground":
   "yes, agree"). The course screen's two sets (269.8) on the whole page.

   SÁNG  the paper page #F6F4EF; every card (`.mc-card`, the tiles `.mc-kpi`)
         white with the #E5E0D7 edge, 20 px corners (tiles 16); the 269.8 ink,
         clay and green.
   TỐI   the night sky behind the page (`--e2i-sky`, 83-universe.js, and its
         hazes, as Practice has it); the cards glass (navy-black 78 → 55 %, the
         starlight edge, 24 px). `--card` itself stays SOLID near-black: it is
         the ground of everything that stands on its own inside a card, a table,
         an input, a chip, the tooltip, the Cột list, the week's cells, the
         class window drawn inside, so those read on a still ground and a
         select's list is never drawn white (269.7). Only the two card kinds
         are glass, by their own rule below.
   HEAD  `.mc-top` is the top bar's: navy, a 2 px clay line (Tối: navy at 60 %
         over the stars, 1 px); its own light ink for the title and the tabs.
   WIDTH the content 1120 (the canvas's pick; 1240 did nothing in Khoa's
         ~1270 px window), Lịch lớp 1240 for its week; on a computer the
         head's contents are held to the same 1120, so the tabs end where the
         page ends. No backdrop-filter anywhere (269.8: it would trap the
         dialogs drawn inside). */
:root:not([data-theme="dark"]) .mc{
  --paper:#F6F4EF;--panel:#F1EEE8;--card:#FFFFFF;--card-2:#F6F4EF;
  --ink:#1F1E1B;--ink-2:#57524B;--ink-3:#6E685F;--line:#E5E0D7;--line-2:#D6CFC4;
  --accent:#D97757;--accent-wash:#FBEFE9;--accent-text:#A9492A;--on-accent:#1F1E1B;
  --done:#1E7A4C;--done-wash:#E7F2EC;--gold:#B7791F;--gold-wash:#F7EDD6;
  --sunk:#F1EEE8;--track:#EEE9E1;--glass:#F6F4EF;--shadow:none;--read-ink:#1F1E1B;
  background:#F6F4EF;}
:root[data-theme="dark"] .mc{color-scheme:dark;
  --paper:#070A12;--panel:rgba(255,255,255,.05);--card:#10131E;--card-2:#161A27;
  --ink:#ECE7DF;--ink-2:#B4ADA2;--ink-3:#9A94A0;--line:rgba(220,228,255,.10);--line-2:rgba(220,228,255,.18);
  --accent:#E08A6C;--accent-deep:#EE9F82;--accent-wash:rgba(224,138,108,.12);--accent-text:#EE9F82;--on-accent:#1A1917;
  --done:#5DBE8A;--done-wash:rgba(93,190,138,.13);--gold:#E2B048;--gold-wash:rgba(226,176,72,.12);
  --sunk:rgba(0,0,0,.3);--track:rgba(255,255,255,.08);--glass:rgba(255,255,255,.04);--shadow:none;--read-ink:#E4DFD6;
  background-color:#060912;
  background-image:var(--e2i-sky,none),
    radial-gradient(55% 45% at 12% 8%,rgba(32,64,128,.30),transparent 70%),
    radial-gradient(45% 40% at 88% 92%,rgba(217,119,87,.07),transparent 70%),
    radial-gradient(40% 35% at 70% 30%,rgba(60,50,120,.12),transparent 70%);
  background-size:1400px 1000px,100% 100%,100% 100%,100% 100%;
  background-position:center top,0 0,0 0,0 0;
  background-repeat:repeat,no-repeat,no-repeat,no-repeat;}
:root[data-theme="dark"] .mc select :is(option,optgroup){background-color:#10131E;color:#ECE7DF;}

/* ---- the cards ---- */
.mc .mc-card{border-radius:20px;padding:18px 20px;box-shadow:none;}
.mc .mc-kpi{border-radius:16px;box-shadow:none;}
:root:not([data-theme="dark"]) .mc :is(.mc-card,.mc-kpi){background:#FFFFFF;border-color:#E5E0D7;}
:root[data-theme="dark"] .mc :is(.mc-card,.mc-kpi){
  background:linear-gradient(180deg,rgba(14,17,27,.78) 0%,rgba(14,17,27,.55) 100%);
  border-color:rgba(220,228,255,.10);box-shadow:inset 0 1px 0 rgba(255,255,255,.05);}
:root[data-theme="dark"] .mc .mc-card{border-radius:24px;}
:root[data-theme="dark"] .mc .mc-kpi{border-radius:18px;}
/* a table in a glass card stands on the solid ground (the canvas's rule) */
:root[data-theme="dark"] .mc .mc-card .cw-tblw{background:#0E111B;border:1px solid rgba(220,228,255,.08);border-radius:14px;}

/* ---- the head: the top bar's ---- */
.mc .mc-top{--ink:#F3EEE6;--ink-2:#DCE3EF;--ink-3:#B9C3D6;
  background:#0B1F3F;border-bottom:2px solid #D97757;color:#F3EEE6;}
:root[data-theme="dark"] .mc .mc-top{background:rgba(8,20,44,.6);border-bottom:1px solid rgba(224,138,108,.55);}
.mc .mc-top .mc-tab:not([aria-selected="true"]):hover{background:rgba(255,255,255,.08);}
.mc .mc-top .mc-tab:focus-visible{outline:2px solid #F3EEE6;outline-offset:1px;}

/* ---- the width ---- */
.mc .mc-body>*{max-width:1120px;}
.mc .mc-body.t-cal>*{max-width:1240px;}
@media (min-width:701px){
  .mc .mc-top{padding-left:max(32px,calc((100% - 1120px) / 2));padding-right:max(32px,calc((100% - 1120px) / 2));}
}
/* the narrower page squeezed the Kỳ học phí column until its squares stood one
   above the other (seen in 270's first picture): in a list they keep one row */
.mc .mc-t .fee-cell{white-space:nowrap;}
.mc .mc-t .fee-dots{flex-wrap:nowrap;}

/* ===================== THE LEVEL TEST (271) =====================
   The person's page (`?lt=`, 34-leveltest.js): the mock-up's phone screen,
   paper and white cards under a navy head with the clay line, one column
   no wider than 560, big targets (52 px buttons, 50 px choices). In its own
   light colours whatever the theme, as the report page is. */
.lt-page{min-height:100vh;background:#F6F4EF;color:#1F1E1B;font-family:var(--sans);font-size:15px;color-scheme:light;}
.lt-h{display:flex;align-items:center;justify-content:space-between;gap:12px;background:#0B1F3F;border-bottom:2px solid #D97757;
  padding:calc(14px + env(safe-area-inset-top,0px)) 20px 14px;}
.lt-h b{font-family:var(--serif);font-size:18px;color:#F3EEE6;}
.lt-h span{font-size:12.5px;color:#B9C3D6;}
.lt-main{max-width:560px;margin:0 auto;padding:22px 18px calc(28px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:14px;}
.lt-card{background:#FFFFFF;border:1px solid #E5E0D7;border-radius:20px;padding:20px;display:grid;gap:12px;}
.lt-card h1{margin:0;font-family:var(--serif);font-size:24px;line-height:1.25;}
.lt-card p{margin:0;color:#57524B;line-height:1.55;}
.lt-from{font-size:13px;color:#6E685F;}
.lt-notes{margin:0;padding-left:18px;display:grid;gap:6px;font-size:13.5px;color:#57524B;line-height:1.5;}
.lt-go{min-height:52px;border:0;border-radius:14px;background:#D97757;color:#1F1E1B;font:inherit;font-size:16px;font-weight:600;cursor:pointer;padding:0 20px;}
.lt-go:disabled{opacity:.45;cursor:default;}
.lt-go:focus-visible,.lt-dk:focus-visible,.lt-opt:focus-visible,.lt-play:focus-visible{outline:3px solid #0B1F3F;outline-offset:2px;}
.lt-dk{min-height:46px;border:1px solid #E5E0D7;border-radius:14px;background:transparent;color:#57524B;font:inherit;cursor:pointer;}
.lt-prog{display:grid;gap:6px;}
.lt-prog-t{display:flex;justify-content:space-between;gap:10px;font-size:13px;color:#6E685F;}
.lt-bar{height:6px;border-radius:3px;background:#EAE6DE;overflow:hidden;}
.lt-bar i{display:block;height:100%;background:#D97757;transition:width .25s;}
.lt-text p{white-space:pre-line;font-family:var(--read);font-size:16px;color:#1F1E1B;}
.lt-qt{font-family:var(--read)!important;font-size:19px;line-height:1.45;color:#1F1E1B!important;}
.lt-opts{display:grid;gap:10px;}
.lt-opt{min-height:50px;text-align:left;padding:12px 16px;border-radius:14px;border:1px solid #E5E0D7;background:#FFFFFF;color:#1F1E1B;font:inherit;font-size:16px;cursor:pointer;}
.lt-opt[aria-checked="true"]{border:2px solid #D97757;background:#FBEFE9;font-weight:600;padding:11px 15px;}
.lt-foot{display:grid;gap:10px;margin-top:4px;}
.lt-saved{text-align:center;font-size:12.5px;color:#6E685F;}
.lt-clip{display:flex;align-items:center;gap:12px;flex-wrap:wrap;background:#FFFFFF;border:1px solid #E5E0D7;border-radius:16px;padding:12px 14px;}
.lt-play{display:inline-flex;align-items:center;gap:8px;min-height:46px;padding:0 18px;border:0;border-radius:12px;background:#0B1F3F;color:#F3EEE6;font:inherit;font-weight:600;cursor:pointer;}
.lt-play:disabled{opacity:.45;cursor:default;}
.lt-left{font-size:13px;color:#57524B;}
.lt-done{text-align:center;justify-items:center;}
.lt-done b{color:#1F1E1B;}
.lt-tick{width:52px;height:52px;border-radius:50%;background:#1E7A4C;color:#FFFFFF;display:grid;place-items:center;font-size:24px;}

/* the teacher's tab (82-leveltab.js), in Lớp của tôi's two sets (270) */
.lvt-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;}
.lvt-head h3{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.lvt-st.k-live{background:var(--accent-wash);color:var(--accent-text);font-weight:600;display:inline-flex;align-items:center;gap:6px;}
.lvt-st.k-done{background:var(--done-wash);color:var(--done);font-weight:600;}
.lvt-st.k-gone{background:none;border:1px dashed var(--line-2);color:var(--ink-3);}
.lvt-st.k-part,.lvt-st.k-new{background:var(--panel);color:var(--ink-2);}
.lvt-dot{width:7px;height:7px;border-radius:50%;background:var(--accent);animation:attPulse 1.4s ease-in-out infinite;}
@media (prefers-reduced-motion:reduce){.lvt-dot{animation:none;}}
.lvt-back{justify-self:start;margin-left:0;padding-left:0;}
.lvt-dots{display:flex;flex-wrap:wrap;gap:5px;}
.lvt-d{width:24px;height:24px;border-radius:7px;display:inline-grid;place-items:center;font-size:12px;font-weight:700;border:1px solid var(--line-2);color:var(--ink-3);}
.lvt-d.ok{background:var(--done);border-color:var(--done);color:#FFFFFF;}
:root[data-theme="dark"] .lvt-d.ok{color:#0B0E16;}
.lvt-d.no{border:1.5px solid var(--accent-text);color:var(--accent-text);}
.lvt-legend{font-size:12px;color:var(--ink-3);}
.lvt-two{display:flex;flex-wrap:wrap;gap:14px;align-items:stretch;}
.lvt-sofar{flex:1 1 260px;min-width:0;display:grid;gap:8px;align-content:start;background:var(--accent-wash);border:1px solid var(--accent);border-radius:16px;padding:14px 16px;}
.lvt-sofar b{font-family:var(--serif);font-size:22px;}
.lvt-sofar b.lvt-big{font-size:26px;}
.lvt-start{line-height:1.5;color:var(--ink-2);}
.lvt-start strong{font-weight:600;color:var(--ink);}
.lvt-eye{font-size:12px;color:var(--accent-text);}
.lvt-feed,.lvt-parts,.lvt-miss{flex:2 1 380px;min-width:0;display:grid;gap:2px;align-content:start;background:var(--card);border:1px solid var(--line);border-radius:16px;padding:12px 14px;}
.lvt-feed .lvt-eye,.lvt-miss .lvt-eye{color:var(--ink-3);padding-bottom:4px;}
.lvt-f{display:grid;grid-template-columns:32px minmax(0,1fr) auto;gap:10px;padding:8px 0;border-top:1px solid var(--line);align-items:baseline;line-height:1.45;}
.lvt-sub{color:var(--ink-2);font-size:13px;}
.lvt-m{font-weight:700;}
.lvt-m.ok{color:var(--done);}
.lvt-m.no{color:var(--accent-text);}
.lvt-lv{font-size:11.5px;color:var(--ink-3);white-space:nowrap;}
.lvt-scale{position:relative;height:8px;border-radius:4px;background:linear-gradient(90deg,var(--sunk) 0 33%,var(--line-2) 33% 33.6%,var(--sunk) 33.6% 66.4%,var(--line-2) 66.4% 67%,var(--sunk) 67%);}
.lvt-scale i{position:absolute;top:-4px;width:4px;height:16px;margin-left:-2px;border-radius:2px;background:var(--accent);}
.lvt-scale-l{display:flex;justify-content:space-between;font-size:11.5px;color:var(--ink-3);}
.lvt-parts{gap:10px;padding:14px 16px;}
.lvt-part{display:grid;gap:5px;}
.lvt-part>div{display:flex;justify-content:space-between;gap:8px;}
.lvt-part .kls-bar{width:100%;min-width:0;height:8px;}
.lvt-part .kls-bar i{background:var(--done);}
.lvt-cp{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;}
.lvt-chip{justify-self:start;font-size:11.5px;font-weight:600;border-radius:7px;padding:1px 9px;background:var(--panel);color:var(--ink-2);}
.lvt-chip.k-done{background:var(--done-wash);color:var(--done);}
.lvt-chip.k-live{background:var(--accent-wash);color:var(--accent-text);}
.lvt-chip.k-gone{background:none;border:1px dashed var(--line-2);color:var(--ink-3);}
.lvt-send textarea{font-family:var(--sans);line-height:1.5;}

/* =============================================================================
   THE LANDING PAGE IN THE UNIVERSE LOOK (272, change `landing-universe`).
   96-lpuniverse.js draws; 96-landing.js lays out. The night band (the hero and
   the rocket tour) is always night, in its own colours (`--lu-…`), as the
   course map is; the sections under it keep the page's Sáng / Tối. The small
   drawn screens use the page's own tokens, so they follow the theme like the
   app they picture. Every class here starts `lu-`.
   ============================================================================= */
.lp{background:linear-gradient(#050913,#050913) top/100% var(--navh) no-repeat,var(--paper);}
/* the bar: the night with the clay line, as the app's top bar over the map */
.lp-nav,.lp-nav::before{background:rgba(5,9,19,.94);}
.lp-nav{border-bottom:2px solid #E58A68;color:#F3F1EA;}
.lp-nav.is-stuck,:root[data-theme="dark"] .lp-nav.is-stuck{box-shadow:0 8px 24px rgba(0,0,0,.35);}
.lp-links{color:#B9C2D2;}
.lp-links a:hover{color:#fff;}
.lp-nav .lp-lang,.lp-nav .lp-icobtn{border-color:rgba(185,194,210,.28);color:#B9C2D2;}
.lp-nav .lp-lang button{color:#8590A6;}
.lp-nav .lp-lang button+button{border-left-color:rgba(185,194,210,.28);}
.lp-nav .lp-lang button:hover,.lp-nav .lp-icobtn:hover{background:rgba(255,255,255,.08);color:#fff;}
.lp-nav .lp-lang button.on{background:rgba(229,138,104,.2);color:#fff;}
.lp-nav .lp-btn{background:#E58A68;color:#1A0D07;}
.lp-nav .lp-btn.ghost{background:transparent;color:#F3F1EA;border-color:rgba(185,194,210,.35);}
.lp-nav .lp-btn.ghost:hover{background:rgba(255,255,255,.08);}

.lu-night{--lu-ink:#F3F1EA;--lu-ink2:#B9C2D2;--lu-ink3:#8590A6;--lu-line:rgba(185,194,210,.16);--lu-acc:#E58A68;
  position:relative;isolation:isolate;overflow:hidden;color:var(--lu-ink);
  background:radial-gradient(60% 40% at 80% 14%,rgba(60,90,160,.22),transparent 70%),
    radial-gradient(40% 26% at 8% 52%,rgba(229,138,104,.08),transparent 70%),
    var(--lu-sky,none),linear-gradient(180deg,#080D18,#020308);}
.lu-tw{position:absolute;width:2px;height:2px;border-radius:50%;background:#fff;box-shadow:0 0 6px #fff;
  animation:lu-tw 4s ease-in-out infinite;z-index:-1;pointer-events:none;}
@keyframes lu-tw{0%,100%{opacity:.25}50%{opacity:1}}
.lu-night .lp-eyebrow,.lu-night .lp-kicker{color:var(--lu-acc);}
.lu-night .lp-eyebrow{background:rgba(229,138,104,.1);border-color:rgba(229,138,104,.4);}
.lu-night .lp-h1,.lu-night .lp-h2{color:var(--lu-ink);}
.lu-night .lp-h1 em{color:var(--lu-acc);display:block;}
.lu-night .lp-vi{color:var(--lu-ink2);}
.lu-night .lp-lede,.lu-night .lp-sub{color:var(--lu-ink2);}
.lu-night .lp-fine{color:var(--lu-ink3);}
.lu-night .lp-trust{color:var(--lu-ink3);border-top:1px solid var(--lu-line);padding-top:18px;}
.lu-night .lp-trust b{color:var(--lu-ink);}
.lu-night .lp-btn{background:var(--lu-acc);color:#1A0D07;}
.lu-night .lp-btn:hover{background:#EF9B7B;}
.lu-night .lp-btn.ghost{background:transparent;color:var(--lu-ink);border-color:rgba(185,194,210,.35);}
.lu-night .lp-btn.ghost:hover{background:rgba(255,255,255,.07);}
.lu-night .lp-mock{background:var(--card);border-color:var(--lu-line);box-shadow:0 24px 60px rgba(0,0,0,.5);}

/* the hero */
.lu-hero{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.12fr);gap:44px;align-items:center;padding:58px 0 76px;}
.lu-cockpit{position:relative;}
/* the cockpit window: the map seen through an arched pane, the arch's clay
   line fading down its sides like the map's brow */
.lu-window{position:relative;overflow:hidden;border:1px solid var(--lu-line);
  border-radius:46% 46% 18px 18px / 16% 16% 18px 18px;box-shadow:0 30px 80px rgba(0,0,0,.55);}
.lu-window::after{content:"";position:absolute;inset:0;border-radius:inherit;border-top:2px solid var(--lu-acc);pointer-events:none;
  -webkit-mask:linear-gradient(#000,#000 8%,transparent 22%);mask:linear-gradient(#000,#000 8%,transparent 22%);}
.lu-float{position:absolute;display:flex;gap:10px;align-items:center;background:rgba(10,18,34,.9);border:1px solid var(--lu-line);
  border-radius:14px;padding:9px 13px 9px 9px;box-shadow:0 12px 30px rgba(0,0,0,.4);font-size:12.5px;color:var(--lu-ink);
  animation:lu-bob 6s ease-in-out infinite;max-width:250px;}
.lu-float b{display:block;font-weight:600;font-size:13px;line-height:1.25;}
.lu-float small{display:block;color:var(--lu-ink3);font-size:12px;}
.lu-float .fi{flex:0 0 auto;width:30px;height:30px;border-radius:9px;display:grid;place-items:center;font-weight:700;font-size:13px;}
.lu-float .fi1{background:#2A1D16;color:#E58A68;}.lu-float .fi2{background:#123323;color:#5DBE8A;}.lu-float .fi3{background:#1D2B48;color:#B9C2D2;font-size:11px;}
/* off the planets: two over the arch's empty corners, one under the dashboard's edge */
.lu-float.f1{left:-18px;top:-16px;}.lu-float.f2{right:-18px;top:-16px;animation-delay:-2s;}.lu-float.f3{right:-18px;bottom:-26px;animation-delay:-4s;}
@keyframes lu-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}

/* the rocket tour */
.lu-how{border-top:1px solid var(--lu-line);padding:64px 0 72px;}
.lu-tour-top{display:flex;justify-content:space-between;align-items:flex-end;gap:20px;flex-wrap:wrap;}
.lu-role{display:inline-flex;padding:3px;border-radius:999px;border:1px solid var(--lu-line);background:rgba(255,255,255,.04);}
.lu-role button{padding:8px 18px;border-radius:999px;color:var(--lu-ink2);font-size:14px;}
.lu-role button[aria-pressed="true"]{background:var(--lu-acc);color:#1A0D07;font-weight:600;}
/* 272.1: the route in the left column, over the words, so the route, the
   stop and its screen fit one screen; a stop's name only while pointed at */
.lu-route{position:relative;height:118px;margin:0 0 26px;}
.lu-route svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible;}
.lu-trk{fill:none;stroke:rgba(185,194,210,.35);stroke-width:2;stroke-dasharray:3 7;}
.lu-trail{fill:none;stroke:var(--lu-acc);stroke-width:2.5;stroke-linecap:round;stroke-dasharray:0 99999;}
.lu-stop{position:absolute;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;color:var(--lu-ink2);z-index:2;}
.lu-stop .pl{width:34px;height:34px;border-radius:50%;display:grid;place-items:center;font-family:var(--mono);font-size:12px;font-weight:500;color:#0B1020;
  background:radial-gradient(circle at 34% 30%,hsl(var(--h) 65% 82%),hsl(var(--h) 45% 52%) 60%,hsl(var(--h) 45% 22%));transition:box-shadow .3s,transform .3s;}
.lu-stop:hover .pl{transform:scale(1.08);}
.lu-stop.on .pl{box-shadow:0 0 0 5px rgba(229,138,104,.25),0 0 28px rgba(229,138,104,.55);transform:scale(1.18);}
.lu-stop.past .pl{filter:saturate(.6);}
.lu-stop .lb{position:absolute;top:42px;font-size:12px;white-space:nowrap;display:none;
  background:rgba(10,18,34,.92);border:1px solid var(--lu-line);border-radius:8px;padding:3px 9px;color:var(--lu-ink);z-index:4;}
.lu-stop.up .lb{top:auto;bottom:42px;}
.lu-stop:hover .lb,.lu-stop:focus-visible .lb{display:block;}
.lu-rocket{position:absolute;left:0;top:0;width:30px;height:30px;pointer-events:none;z-index:3;filter:drop-shadow(0 0 8px rgba(229,138,104,.6));}
.lu-rocket svg{width:100%;height:100%;overflow:visible;}
.lu-flame{animation:lu-fl .18s infinite alternate;transform-origin:15px 26px;}
@keyframes lu-fl{from{transform:scaleY(.8)}to{transform:scaleY(1.25)}}
.lu-tour-body{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.35fr);gap:40px;align-items:center;margin-top:34px;}
.lu-tleft{min-width:0;}
.lu-tstep .n{font-family:var(--mono);font-size:12px;color:var(--lu-acc);letter-spacing:.1em;}
.lu-tstep h3{font-family:var(--serif);font-size:28px;font-weight:600;line-height:1.12;margin:8px 0 12px;text-wrap:balance;}
.lu-tstep p{color:var(--lu-ink2);font-size:15px;line-height:1.6;margin:0 0 14px;}
.lu-press{display:inline-flex;gap:8px;align-items:center;font-size:13px;background:rgba(255,255,255,.06);border:1px solid var(--lu-line);border-radius:999px;padding:6px 12px;}
.lu-press i{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:var(--lu-acc);animation:lu-pulse 1.6s infinite;}
@keyframes lu-pulse{0%{box-shadow:0 0 0 0 rgba(229,138,104,.6)}70%{box-shadow:0 0 0 9px rgba(229,138,104,0)}100%{box-shadow:0 0 0 0 rgba(229,138,104,0)}}
.lu-tctl{display:flex;gap:8px;margin-top:22px;flex-wrap:wrap;}
.lu-tctl button{background:rgba(255,255,255,.06);border:1px solid var(--lu-line);border-radius:999px;padding:8px 15px;color:var(--lu-ink);font-size:13.5px;}
.lu-tctl button:hover{background:rgba(255,255,255,.1);}
.lu-tctl button.pri{background:var(--lu-acc);border-color:var(--lu-acc);color:#1A0D07;font-weight:600;}
.lu-shot{animation:lu-in .3s ease;}
@keyframes lu-in{from{opacity:0}to{opacity:1}}
@media (prefers-reduced-motion:reduce){.lu-tw,.lu-float,.lu-flame,.lu-press i,.lu-shot{animation:none;}}

/* ---- the drawn screens: one base size in cqw, everything else in em ---- */
.lu-scr{container-type:inline-size;position:relative;}
.lu-si{position:relative;aspect-ratio:16/10.4;overflow:hidden;font-size:1.62cqw;line-height:1.4;background:var(--paper);color:var(--ink);text-align:left;}
.lu-si b{font-weight:600;}
.lu-si .sp{flex:1;}
.lu-top{display:flex;align-items:center;gap:.8em;height:3em;padding:0 1.2em;background:#0B1F3F;color:#F3F1EA;border-bottom:.18em solid #E58A68;}
.lu-logo{width:1.4em;height:1.4em;border-radius:.4em;background:#E58A68;}
.lu-tb{border:1px solid rgba(255,255,255,.25);border-radius:999px;padding:.15em .8em;font-size:.9em;}
.lu-pill{font-family:var(--mono);font-size:.85em;color:#E9C9B9;}
.lu-av{flex:0 0 auto;width:2em;height:2em;border-radius:50%;display:grid;place-items:center;background:#24395F;color:#fff;font-size:.8em;font-weight:600;}
.lu-av.hot{background:var(--accent);color:#1A0D07;}
.lu-k{font-family:var(--mono);font-size:.78em;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);}
.lu-warm{color:#E9C9B9;}
.lu-h{font-family:var(--serif);font-weight:600;font-size:1.35em;line-height:1.15;margin:.2em 0;}
.lu-xl{font-size:1.8em;}
.lu-big{font-size:1.2em;}
.lu-s{color:var(--ink-2);font-size:.92em;}
.lu-end{margin-top:auto;}
.lu-pad{padding:.6em 1em;}
.lu-gap{margin-top:.7em;}
.lu-mid{text-align:center;}
.lu-mono{font-family:var(--mono);margin-top:.4em;}
.lu-sp{display:flex;justify-content:space-between;align-items:center;gap:.6em;}
.lu-wrapr{display:flex;gap:.45em;flex-wrap:wrap;margin-top:.55em;}
.lu-card{background:var(--card);border:1px solid var(--line);border-radius:1em;padding:1em;min-width:0;}
.lu-card.sm{padding:.6em .8em;}
.lu-card.now{background:var(--accent-wash);border-color:color-mix(in srgb,var(--accent) 45%,transparent);}
.lu-btn{display:inline-block;background:var(--accent);color:#1A0D07;border-radius:999px;padding:.45em 1.1em;font-weight:600;font-size:.95em;white-space:nowrap;}
.lu-btn.g{background:transparent;border:1px solid var(--line-2);color:var(--ink);}
.lu-bar{height:.45em;border-radius:1em;background:var(--track);overflow:hidden;}
.lu-bar i{display:block;height:100%;background:var(--accent);}
.lu-chip{display:inline-block;border-radius:999px;padding:.12em .7em;font-size:.82em;border:1px solid var(--line);color:var(--ink-2);white-space:nowrap;}
.lu-chip.ok{background:var(--done-wash);border-color:transparent;color:var(--done);}
.lu-chip.no{background:var(--wrong-wash);border-color:transparent;color:var(--wrong);}
.lu-chip.cl{background:var(--accent-wash);border-color:color-mix(in srgb,var(--accent) 45%,transparent);color:var(--accent-text);}
.lu-chip.gd{background:var(--gold-wash);border-color:transparent;color:var(--gold);}
.lu-dot{flex:0 0 auto;width:.7em;height:.7em;border-radius:50%;}
.lu-row{display:flex;align-items:center;gap:.6em;padding:.35em 0;border-bottom:1px solid var(--line);font-size:.95em;}
.lu-row:last-child{border-bottom:0;}
.lu-cols{display:grid;gap:1em;padding:1.1em;height:calc(100% - 3em);}
.lu-cols.c3{grid-template-columns:1fr 1.25fr 1fr;}
.lu-cols.c2p{grid-template-columns:.8fr 1.5fr;}
.lu-cols.c2l{grid-template-columns:1fr 1.5fr;padding:1.2em;}
.lu-cols.c2k{grid-template-columns:1fr 1.5fr;}
.lu-cols.c2t{grid-template-columns:1.3fr 1fr;padding:1.1em 1.4em;height:auto;}
.lu-cols.c2m{grid-template-columns:1.25fr 1fr;padding:.6em 1.4em;height:auto;}
.lu-cols.c2b{grid-template-columns:1.4fr 1fr;padding:1.1em 1.4em;height:auto;}
.lu-col{background:var(--card);border:1px solid var(--line);border-radius:1.2em;padding:1em;overflow:hidden;display:flex;flex-direction:column;gap:.7em;min-width:0;}
.lu-ct{font-family:var(--mono);font-size:.78em;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3);display:flex;justify-content:space-between;}
.lu-pc{display:flex;gap:.9em;align-items:center;}
.lu-planet{flex:0 0 auto;width:4.2em;height:4.2em;border-radius:50%;
  background:radial-gradient(circle at 34% 30%,hsl(var(--h) 65% 78%),hsl(var(--h) 45% 48%) 58%,hsl(var(--h) 45% 22%));box-shadow:0 0 0 .35em hsla(var(--h),50%,60%,.18);}
/* stones */
.lu-stones{position:relative;flex:1;min-height:0;}
.lu-sline{position:absolute;inset:0;width:100%;height:100%;overflow:visible;}
.lu-sline path{fill:none;stroke:var(--line-2);stroke-width:2;stroke-dasharray:4 5;}
.lu-st{position:absolute;width:2.3em;height:2.3em;border-radius:50%;display:grid;place-items:center;font-size:.9em;font-weight:600;transform:translate(-50%,-50%);}
.lu-st.d{background:var(--done);color:#fff;}
.lu-st.o{background:hsl(40 62% 54%);color:#1A0D07;box-shadow:0 0 0 .3em hsla(40,62%,54%,.25);}
.lu-st.s{background:var(--track);color:var(--ink-3);border:1px solid var(--line);}
.lu-st.cp{background:var(--gold);color:#1A0D07;border-radius:.4em;transform:translate(-50%,-50%) rotate(45deg);}
.lu-st.cp b{transform:rotate(-45deg);}
.lu-here{position:absolute;left:4%;top:56%;width:13em;background:var(--accent-wash);border:1px solid color-mix(in srgb,var(--accent) 45%,transparent);border-radius:.9em;padding:.6em .8em;font-size:.88em;}
.lu-here b{display:block;font-size:1.05em;}
/* lesson */
.lu-tabs{display:flex;gap:.3em;padding:.7em 1.2em;border-bottom:1px solid var(--line);background:var(--card);overflow:hidden;}
.lu-tabs span{padding:.3em .9em;border-radius:999px;font-size:.88em;color:var(--ink-2);white-space:nowrap;}
.lu-tabs span.on{background:#0B1F3F;color:#fff;}
.lu-dl{padding:1em 2em;font-family:var(--read);font-size:1.12em;display:grid;gap:.55em;}
.lu-dl.sm{padding:.6em 0;font-size:1em;}
.lu-dl p{margin:0;display:grid;grid-template-columns:7.5em 1fr;gap:.6em;padding:.25em .5em;border-radius:.5em;}
.lu-dl p b{font-family:var(--sans);font-weight:600;font-size:.82em;color:var(--ink-3);padding-top:.15em;}
.lu-dl p.rd{background:var(--accent-wash);}
.lu-blank{display:inline-block;min-width:6em;border-bottom:1.5px solid var(--accent);color:var(--accent-text);font-family:var(--sans);font-size:.9em;padding:0 .3em;}
.lu-blank.ok{border-color:var(--done);color:var(--done);}
.lu-vbar{position:absolute;left:1.2em;right:1.2em;bottom:1em;background:#0B1F3F;color:#F3F1EA;border-radius:999px;padding:.5em 1em;display:flex;align-items:center;gap:.8em;}
.lu-vbar .tr{flex:1;height:.4em;border-radius:1em;background:rgba(255,255,255,.18);position:relative;}
.lu-vbar .tr i{position:absolute;top:-.25em;width:1px;height:.9em;background:rgba(255,255,255,.45);}
.lu-vbar .tr u{position:absolute;left:0;top:0;bottom:0;width:31%;background:#E58A68;border-radius:1em;}
.lu-vbar .kn{position:absolute;left:31%;top:50%;width:1em;height:1em;border-radius:50%;background:#fff;transform:translate(-50%,-50%);}
.lu-vbar .tip{position:absolute;left:31%;bottom:1.6em;transform:translateX(-50%);background:#fff;color:#1F1E1B;border-radius:.5em;padding:.25em .7em;font-size:.85em;white-space:nowrap;box-shadow:0 .3em 1em rgba(0,0,0,.25);}
.lu-vbar .bt{flex:0 0 auto;width:1.9em;height:1.9em;border-radius:50%;display:grid;place-items:center;background:rgba(255,255,255,.12);font-size:.85em;}
.lu-vbar .bt.p{background:#E58A68;color:#1A0D07;}
.lu-vbar .ct{font-family:var(--mono);font-size:.85em;}
/* a practice game */
.lu-game{padding:1.3em 1.6em;gap:.85em;}
.lu-q{font-family:var(--read);font-size:1.25em;}
.lu-opts{display:grid;grid-template-columns:repeat(3,1fr);gap:.6em;}
.lu-opts span{border:1px solid var(--line);border-radius:.8em;padding:.7em;text-align:center;background:var(--card);}
.lu-opts span.ok{border-color:var(--done);background:var(--done-wash);color:var(--done);font-weight:600;box-shadow:0 0 0 .3em var(--done-wash);}
.lu-verd{display:flex;align-items:center;gap:.6em;color:var(--done);font-weight:600;}
.lu-verd i{width:1.7em;height:1.7em;border-radius:50%;background:var(--done);color:#fff;display:grid;place-items:center;font-style:normal;animation:lu-pop .5s ease;}
@keyframes lu-pop{0%{transform:scale(.4)}70%{transform:scale(1.15)}100%{transform:scale(1)}}
.lu-timer{height:.4em;border-radius:1em;background:var(--track);overflow:hidden;}
.lu-timer i{display:block;height:100%;width:62%;background:linear-gradient(90deg,var(--accent),hsl(40 62% 54%));}
/* the map, always night */
.lu-uni{position:absolute;inset:0;overflow:hidden;color:#F3F1EA;
  background:radial-gradient(70% 60% at 70% 30%,rgba(60,90,160,.25),transparent 70%),var(--lu-sky,none),linear-gradient(180deg,#080D18,#020308);}
.lu-reg{position:absolute;top:8%;font-family:var(--mono);font-size:.82em;letter-spacing:.3em;color:#8590A6;}
.lu-reg::after{content:"";display:block;width:3em;height:1px;background:rgba(185,194,210,.25);margin-top:.5em;}
.lu-pl{position:absolute;display:flex;flex-direction:column;align-items:center;gap:.3em;transform:translate(-50%,-50%);text-align:center;}
.lu-pl i{display:block;position:relative;width:var(--s);height:var(--s);border-radius:50%;
  background:radial-gradient(circle at 34% 30%,hsl(var(--h) 65% 80%),hsl(var(--h) 45% 50%) 55%,hsl(var(--h) 45% 16%));box-shadow:0 0 2em hsla(var(--h),60%,60%,.3);}
.lu-pl i u{position:absolute;inset:-.4em;border-radius:50%;background:conic-gradient(#E58A68 var(--p),rgba(255,255,255,.1) 0);
  -webkit-mask:radial-gradient(farthest-side,transparent calc(100% - .2em),#000 calc(100% - .19em));mask:radial-gradient(farthest-side,transparent calc(100% - .2em),#000 calc(100% - .19em));}
.lu-pl b{font-weight:400;white-space:nowrap;text-shadow:0 1px 8px #000;}
.lu-pl small{font-family:var(--mono);font-size:.72em;color:#8590A6;letter-spacing:.06em;}
.lu-ship{position:absolute;top:-1.2em;right:-1.6em;width:1.6em;height:1.6em;transform:rotate(35deg);}
.lu-dash{position:absolute;left:6%;right:6%;bottom:0;height:5.4em;border-radius:50% 50% 0 0 / 70% 70% 0 0;background:rgba(6,10,20,.92);
  border-top:.15em solid #E58A68;display:flex;align-items:center;justify-content:space-between;padding:1em 3em 0;font-size:.9em;color:#B9C2D2;}
.lu-dash .go{background:#E58A68;color:#1A0D07;border-radius:999px;padding:.55em 1.3em;font-weight:600;transform:translateY(-.9em);box-shadow:0 0 2em rgba(229,138,104,.4);}
/* the teacher's screens */
.lu-mc{height:100%;background:var(--paper);}
.lu-mch{background:#0B1F3F;border-bottom:.18em solid #E58A68;color:#F3F1EA;padding:.9em 1.4em 0;}
.lu-mch .t{font-family:var(--serif);font-size:1.35em;font-weight:600;}
.lu-mctabs{display:flex;gap:1.3em;margin-top:.6em;font-size:.88em;color:#B9C2D2;overflow:hidden;}
.lu-mctabs span{padding:.4em 0 .6em;white-space:nowrap;border-bottom:.2em solid transparent;}
.lu-mctabs span.on{color:#fff;border-color:#E58A68;}
.lu-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:.8em;padding:1.1em 1.4em .4em;}
.lu-kpi{background:var(--card);border:1px solid var(--line);border-radius:1em;padding:.8em 1em;}
.lu-kpi small{color:var(--ink-3);font-size:.8em;}
.lu-kpi b{display:block;font-family:var(--serif);font-size:1.6em;line-height:1.1;margin-top:.2em;}
.lu-kpi b.warn{color:var(--wrong);}
.lu-tl{position:relative;padding-left:1.4em;}
.lu-tl::before{content:"";position:absolute;left:.4em;top:.3em;bottom:.3em;width:1px;background:var(--line-2);}
.lu-tl>div{position:relative;padding:.3em 0;}
.lu-tl>div::before{content:"";position:absolute;left:-1.25em;top:.75em;width:.6em;height:.6em;border-radius:50%;background:var(--c);}
.lu-dots{display:grid;grid-template-columns:repeat(16,1fr);gap:.35em;margin:.6em 0;}
.lu-dots i{aspect-ratio:1;border-radius:50%;background:var(--track);border:1px solid var(--line);}
.lu-dots i.a{background:var(--done);border-color:transparent;}
.lu-dots i.x{background:var(--wrong);border-color:transparent;}
.lu-dots i.n{background:var(--accent);border-color:transparent;animation:lu-tw 1.4s infinite;}
.lu-pipe{display:grid;grid-template-columns:repeat(5,1fr);gap:.7em;padding:1em 1.4em;}
.lu-pipe>div{display:flex;flex-direction:column;gap:.5em;min-width:0;}
.lu-pch{font-size:.85em;font-weight:600;display:flex;justify-content:space-between;color:var(--ink-2);}
.lu-pcard{background:var(--card);border:1px solid var(--line);border-radius:.8em;padding:.6em .7em;font-size:.86em;display:flex;flex-direction:column;align-items:flex-start;gap:.25em;}
.lu-pcard small{color:var(--ink-3);}
.lu-toast{position:absolute;left:50%;bottom:1.2em;transform:translateX(-50%);background:#0B1F3F;color:#F3F1EA;border-radius:999px;padding:.5em 1.3em;font-size:.95em;box-shadow:0 .4em 1.5em rgba(0,0,0,.3);white-space:nowrap;}
.lu-inv{background:var(--card);border:1px solid var(--line);border-radius:.9em;overflow:hidden;min-width:0;}
.lu-inv .hb{background:#0B1F3F;color:#F3F1EA;padding:.8em 1em;}
.lu-inv .hb b{font-family:var(--serif);font-size:1.25em;display:block;}
.lu-inv .hb span{font-size:.85em;color:#B9C2D2;}
.lu-inv table{width:100%;border-collapse:collapse;font-size:.85em;}
.lu-inv td,.lu-inv th{padding:.4em 1em;border-bottom:1px solid var(--line);text-align:left;}
.lu-inv th{font-weight:500;color:var(--ink-3);}
.lu-inv .r{text-align:right;font-variant-numeric:tabular-nums;}
.lu-inv s{color:var(--ink-3);}
.lu-qr{background:#fff;border-radius:.6em;padding:.5em;width:9em;margin:.5em auto;}
.lu-qr svg{display:block;width:100%;height:auto;}
.lu-tph{background:#0B1F3F;color:#F3F1EA;display:grid;grid-template-columns:auto 1fr auto;gap:1.3em;align-items:center;padding:1.3em 2em;border-bottom:.18em solid #E58A68;}
.lu-tph .nm{font-family:var(--serif);font-size:1.6em;font-weight:600;}
.lu-tph .ln{color:#B9C2D2;}
.lu-photo{width:6em;height:6em;border-radius:50%;display:grid;place-items:center;font-family:var(--serif);font-size:1.8em;font-weight:600;color:#0B1F3F;
  background:radial-gradient(circle at 30% 25%,#F6D9C9,#D99A7C 60%,#A9654A);}
.lu-q2{display:flex;gap:.4em;flex-wrap:wrap;margin-top:.5em;}
.lu-q2 span{border:1px solid rgba(255,255,255,.25);border-radius:999px;padding:.1em .7em;font-size:.82em;}
.lu-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:.8em;padding:1.1em 2em;}
.lu-tile{border-radius:.8em;overflow:hidden;border:1px solid var(--line);background:var(--card);}
.lu-tile .cv{aspect-ratio:16/9;background:linear-gradient(135deg,hsl(var(--h) 45% 62%),hsl(var(--h) 40% 30%));}
.lu-tile b{display:block;padding:.4em .6em .1em;font-size:.88em;}
.lu-tile small{display:block;padding:0 .6em .5em;color:var(--ink-3);font-size:.75em;}
.lu-quote{margin:0 2em;padding:.8em 1em;border-left:.2em solid var(--accent);background:var(--card);border-radius:0 .7em .7em 0;font-family:var(--read);font-size:1.02em;}
.lu-quote .lu-s{font-family:var(--sans);margin-top:.3em;}
.lu-certw{position:relative;height:calc(100% - 3em);}
.lu-beacon{position:absolute;left:8%;top:22%;display:flex;flex-direction:column;align-items:center;gap:.5em;font-size:.88em;}
.lu-beacon i{width:3.2em;height:3.2em;background:var(--gold);transform:rotate(45deg);border-radius:.5em;box-shadow:0 0 2.4em rgba(201,150,46,.55);}
.lu-cert{position:absolute;left:30%;right:6%;top:8%;background:var(--card);border:1px solid var(--line);border-radius:.6em;padding:1.4em 2em;text-align:center;box-shadow:0 0 0 .5em var(--track);}
.lu-cert .cn{font-family:var(--serif);font-size:2em;font-weight:600;margin:.15em 0;}
.lu-cert .ct{font-size:1.2em;}
.lu-sig{display:flex;justify-content:space-between;margin-top:1.2em;font-size:.9em;}

@media (max-width:900px){
  .lu-hero{grid-template-columns:minmax(0,1fr);gap:40px;padding:40px 0 56px;}
  .lu-tour-body{grid-template-columns:minmax(0,1fr);gap:22px;}
  .lu-how{padding:56px 0 60px;}
  .lu-float.f1{left:-6px;}.lu-float.f2{right:-6px;}
}
@media (max-width:760px){
  .lu-route{height:110px;margin-bottom:18px;}
  .lu-stop:hover .lb{display:none;}
  .lu-tstep h3{font-size:25px;}
}
@media (max-width:560px){.lu-float{display:none;}}

/* ---- 273: For learners, For teachers, the night at the foot ---- */
.lu-band{padding:84px 0 88px;background:var(--paper);}
.lu-band.alt{background:color-mix(in srgb,var(--paper) 55%,var(--track));}
.lu-mt{margin-top:58px;}
.lu-h3{font-family:var(--serif);font-size:clamp(23px,3vw,30px);font-weight:600;line-height:1.18;margin:8px 0 0;text-wrap:balance;}
.lu-modes{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:18px;margin-top:30px;}
.lu-mode{background:var(--card);border:1px solid var(--line);border-radius:20px;padding:24px;display:flex;flex-direction:column;gap:10px;}
.lu-mode.cls{background:var(--accent-wash);border-color:color-mix(in srgb,var(--accent) 40%,transparent);}
.lu-mode .tg{font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3);}
.lu-mode h3{font-family:var(--serif);font-size:23px;font-weight:600;margin:0;}
.lu-mode ul{margin:0;padding:0;list-style:none;display:grid;gap:8px;}
.lu-mode li{display:grid;grid-template-columns:18px 1fr;gap:6px;color:var(--ink-2);font-size:14.5px;line-height:1.55;}
.lu-mode li::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--accent);margin-top:8px;}
.lu-mode.self li::before{background:hsl(229 50% 60%);}
.lu-inside{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:18px;margin-top:22px;}
.lu-blk{background:var(--card);border:1px solid var(--line);border-radius:20px;padding:22px;min-width:0;}
.lu-blk h4{font-family:var(--serif);font-size:20px;font-weight:600;margin:6px 0 4px;}
.lu-blk>p{margin:0 0 14px;color:var(--ink-2);font-size:14px;}
.lu-parts{display:grid;gap:8px;}
.lu-part{display:grid;grid-template-columns:26px 1fr auto;gap:10px;align-items:center;padding:10px 12px;border-radius:12px;background:var(--paper);border:1px solid var(--line);}
.lu-part i{font-style:normal;font-family:var(--mono);font-size:11px;color:var(--ink-3);}
.lu-part b{font-weight:600;font-size:14px;}
.lu-part small{display:block;color:var(--ink-3);font-size:12.5px;line-height:1.45;}
.lu-part em{font-style:normal;font-family:var(--mono);font-size:12px;color:var(--ink-3);}
.lu-games{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px;}
.lu-game2{border:1px solid var(--line);border-radius:14px;padding:12px;background:var(--paper);display:flex;flex-direction:column;gap:5px;min-width:0;}
.lu-game2 b{font-weight:600;font-size:14px;}
.lu-game2>span{color:var(--ink-2);font-size:13px;line-height:1.45;}
.lu-game2 .vis{height:44px;border-radius:9px;background:var(--card);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;gap:5px;font-size:11.5px;padding:0 8px;overflow:hidden;white-space:nowrap;}
.lu-w{border:1px solid var(--line);border-radius:6px;padding:2px 6px;background:var(--paper);}
.lu-w.on{background:var(--accent-wash);border-color:color-mix(in srgb,var(--accent) 45%,transparent);}
.lu-ok{color:var(--done);font-weight:600;}
.lu-bad{text-decoration-color:var(--wrong);}
.lu-stars{color:var(--gold);letter-spacing:.15em;font-size:18px;}
.lu-feats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;margin-top:18px;}
.lu-ft{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:16px;display:flex;flex-direction:column;gap:5px;}
.lu-ft .ill{height:34px;display:flex;align-items:center;gap:6px;font-size:12px;flex-wrap:wrap;}
.lu-ft b{font-weight:600;font-size:14.5px;}
.lu-ft>span{color:var(--ink-2);font-size:13.5px;line-height:1.5;}
.lu-ft .lu-chip,.lu-game2 .lu-chip{font-size:11.5px;}
.lu-lib{display:grid;grid-template-columns:repeat(var(--n,3),minmax(0,1fr));gap:14px;margin-top:22px;}
.lu-reg2{border-radius:18px;padding:16px 18px;background:var(--card);border:1px solid var(--line);min-width:0;}
.lu-reg2 .rt{font-family:var(--mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-3);margin-bottom:6px;}
.lu-cr{display:flex;align-items:center;gap:10px;padding:6px 6px;margin:0 -6px;border-radius:8px;color:var(--ink);}
.lu-cr:hover{background:var(--track);}
.lu-cr i{width:18px;height:18px;border-radius:50%;flex:0 0 auto;background:radial-gradient(circle at 34% 30%,hsl(var(--h) 65% 80%),hsl(var(--h) 45% 48%) 60%,hsl(var(--h) 45% 22%));}
.lu-cr span{flex:1;min-width:0;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.lu-cr small{font-family:var(--mono);font-size:11px;color:var(--ink-3);white-space:nowrap;}
.lu-lvk{display:flex;gap:16px;flex-wrap:wrap;margin-top:12px;font-size:12.5px;color:var(--ink-3);}
.lu-lvk span{display:inline-flex;align-items:center;gap:6px;}
.lu-lvk i{width:10px;height:10px;border-radius:50%;background:hsl(var(--h) 50% 58%);}
.lu-outs{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:14px;margin-top:34px;}
.lu-out{display:flex;gap:16px;align-items:center;padding:18px 20px;border-radius:18px;background:var(--card);border:1px solid var(--line);color:var(--ink);transition:transform .2s,border-color .2s;}
.lu-out:hover{transform:translateY(-2px);border-color:color-mix(in srgb,var(--accent) 50%,transparent);}
.lu-out .oi{width:44px;height:44px;border-radius:14px;flex:0 0 auto;display:grid;place-items:center;background:var(--accent-wash);color:var(--accent-text);font-family:var(--serif);font-weight:600;font-size:20px;}
.lu-out b{display:block;font-size:15.5px;}
.lu-out div>span{color:var(--ink-2);font-size:13.5px;line-height:1.45;}
.lu-out .ar{margin-left:auto;color:var(--accent-text);font-size:20px;}
.lu-show{display:grid;grid-template-columns:minmax(0,1.55fr) minmax(0,.85fr);gap:26px;margin-top:28px;align-items:start;}
.lu-stabs{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:12px;}
.lu-stabs button{border:1px solid var(--line);background:var(--card);border-radius:999px;padding:6px 13px;font-size:13px;color:var(--ink-2);}
.lu-stabs button[aria-selected="true"]{background:#0B1F3F;border-color:#0B1F3F;color:#fff;}
.lu-band .lu-frame{box-shadow:var(--shadow);}
.lu-leg{display:grid;gap:8px;}
.lu-leg button{display:block;text-align:left;padding:12px 15px;border-radius:14px;background:var(--card);border:1px solid var(--line);color:var(--ink);}
.lu-leg button.on{background:var(--accent-wash);border-color:color-mix(in srgb,var(--accent) 45%,transparent);}
.lu-leg b{display:block;font-size:14.5px;}
.lu-leg span{color:var(--ink-2);font-size:13px;line-height:1.5;}
.lu-pub{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:30px;margin-top:56px;align-items:center;padding:28px;border-radius:24px;background:var(--card);border:1px solid var(--line);}
.lu-pub ul{list-style:none;margin:16px 0 0;padding:0;display:grid;gap:10px;}
.lu-pub li{display:grid;grid-template-columns:22px 1fr;gap:6px;color:var(--ink-2);font-size:14.5px;line-height:1.55;}
.lu-pub li::before{content:"✦";color:var(--accent);}
.lu-pub li b{color:var(--ink);font-weight:600;}
.lu-zalo{background:var(--track);border-radius:18px;padding:16px;display:grid;gap:10px;}
.lu-zalo .bub{max-width:88%;padding:9px 13px;border-radius:14px;background:var(--card);border:1px solid var(--line);font-size:13.5px;line-height:1.45;}
.lu-zalo .bub.me{justify-self:end;background:#D8E9FF;color:#0E2A4F;border-color:transparent;}
:root[data-theme="dark"] .lu-zalo .bub.me{background:#1D3A63;color:#E3EEFF;}
.lu-lcard{justify-self:end;width:88%;border-radius:14px;overflow:hidden;background:var(--card);border:1px solid var(--line);font-size:13px;}
.lu-lcard .im{height:104px;background:linear-gradient(120deg,#0B1F3F 0 55%,#14305C);display:flex;align-items:center;gap:14px;padding:0 18px;color:#F3F1EA;}
.lu-lcard .im .lu-photo{width:56px;height:56px;font-size:18px;flex:0 0 auto;}
.lu-lcard .im b{display:block;font-family:var(--serif);font-size:17px;font-weight:600;}
.lu-lcard .im small{color:#B9C2D2;font-size:12px;}
.lu-lcard .tx{padding:9px 12px;}
.lu-lcard .tx small{display:block;color:var(--ink-3);}
.lu-rungs{list-style:none;margin:16px 0 0;padding:0;display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:10px;}
.lu-rungs li{padding:14px;border-radius:14px;background:var(--card);border:1px solid var(--line);min-width:0;}
.lu-rungs li:last-child{background:var(--accent-wash);border-color:color-mix(in srgb,var(--accent) 40%,transparent);}
.lu-rungs .n{font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--accent-text);}
.lu-rungs b{display:block;margin:4px 0 2px;font-size:14.5px;}
.lu-rungs li>span:last-child{font-size:12.5px;color:var(--ink-2);line-height:1.45;}
/* the week screen */
.lu-calbar{display:flex;gap:.6em;padding:.6em 1.4em;align-items:center;}
.lu-cal{position:relative;display:grid;grid-template-columns:3em repeat(7,1fr);grid-template-rows:auto 1fr;height:calc(100% - 7.4em);margin:0 1.4em;border:1px solid var(--line);border-radius:.8em;overflow:hidden;background:var(--card);}
.lu-cal .hd{font-size:.8em;text-align:center;padding:.4em 0;border-bottom:1px solid var(--line);color:var(--ink-3);}
.lu-cal .hd.td{color:var(--accent-text);font-weight:600;}
.lu-cblk{position:absolute;left:calc(3em + (100% - 3em) / 7 * var(--col) + .2em);width:calc((100% - 3em) / 7 - .4em);border-radius:.4em;padding:.25em .4em;font-size:.75em;color:#fff;overflow:hidden;line-height:1.25;}
.lu-cblk.bk{background:var(--track);color:var(--ink-2);border:1px dashed var(--ink-3);}
/* the night at the foot: the referral program, the last call, the footer */
.lu-tail{padding:10px 0 0;}
.lu-night .lp-aff-c,.lu-night .lp-aff-in,.lu-night .lp-aff-steps li,.lu-night .lp-aff-tiers>div{background:rgba(12,22,40,.62);border-color:var(--lu-line);}
.lu-night .lp-aff-c p,.lu-night .lp-aff-steps span,.lu-night .lp-aff-sl,.lu-night .lp-faq p{color:var(--lu-ink2);}
.lu-night .lp-aff-c .lp-aff-who,.lu-night .lp-aff-big span,.lu-night .lp-aff-tiers span,.lu-night .lp-aff-tk{color:var(--lu-ink3);}
.lu-night .lp-aff-tiers .lp-aff-tn,.lu-night .lp-aff-slt b{color:var(--lu-ink);}
.lu-night .lp-aff-big b{color:var(--lu-acc);}
.lu-night .lp-aff-tot{background:rgba(229,138,104,.1);border:1px solid rgba(229,138,104,.4);color:var(--lu-ink);}
.lu-night .lp-aff-steps li::before{background:rgba(229,138,104,.18);color:var(--lu-acc);}
.lu-night .lp-faq h3{color:var(--lu-ink);}
.lu-night a.lp-fine{color:var(--lu-ink2);text-decoration:underline;}
.lu-night .lp-final{background:rgba(255,255,255,.03);border-color:var(--lu-line);}
.lu-night .lp-foot{border-top-color:var(--lu-line);color:var(--lu-ink3);}
.lu-night .lp-foot a:hover{color:#fff;}
.lu-night .lp-foot .lp-icobtn{border-color:rgba(185,194,210,.28);color:var(--lu-ink2);}
@media (max-width:900px){
  .lu-modes,.lu-inside,.lu-show,.lu-pub,.lu-outs,.lu-lib{grid-template-columns:minmax(0,1fr);}
  .lu-feats{grid-template-columns:repeat(2,minmax(0,1fr));}
  .lu-rungs{grid-template-columns:repeat(2,minmax(0,1fr));}
  .lu-band{padding:60px 0 64px;}
  .lu-pub{padding:20px;}
}
@media (max-width:520px){.lu-feats,.lu-games{grid-template-columns:minmax(0,1fr);}}

/* ---- 274: Tìm giáo viên, english2impact.com/t/ (38-tpage.js TpList) ----
   The night head of the landing page (search and filters on the sky), then
   the teachers as cards on the page's Sáng / Tối paper. */
.tpl{min-height:100vh;background:var(--paper);color:var(--ink);}
.tpl a{text-decoration:none;}
.tpl-wrap{max-width:1120px;margin:0 auto;padding-left:max(20px,env(safe-area-inset-left,0px));padding-right:max(20px,env(safe-area-inset-right,0px));}
.tpl-head{padding:22px 0 34px;border-bottom:2px solid #E58A68;}
.tpl-brand{display:inline-flex;align-items:center;gap:9px;color:#F3F1EA;font-family:var(--serif);font-weight:600;font-size:16px;margin-bottom:34px;}
.tpl-brand .brand-logo{width:26px;height:26px;}
.tpl-head h1{font-family:var(--serif);font-weight:600;font-size:clamp(34px,5vw,50px);line-height:1.08;margin:8px 0 8px;color:var(--lu-ink);}
.tpl-sub{color:var(--lu-ink2);font-size:15.5px;line-height:1.6;max-width:60ch;margin:0;}
.tpl-q-in{display:flex;align-items:center;gap:8px;max-width:660px;margin-top:24px;background:rgba(255,255,255,.07);border:1px solid var(--lu-line);border-radius:999px;padding:0 16px;color:var(--lu-ink3);}
.tpl-q-in input{flex:1;min-width:0;background:none;border:0;outline:none;color:var(--lu-ink);font:inherit;font-size:16px;padding:12px 0;}
.tpl-q-in input::placeholder{color:var(--lu-ink3);}
.tpl-q-in:focus-within{border-color:#E58A68;}
/* 276.1 (Khoa, a picture of the chips running past the search box): the
   filters never run past the search box above them, one group a row.
   276.4 (Khoa: "move the edge a bit further to the right", "make the tag
   chips fit their own text, no need to align this way"): box and filters
   720 wide (was 620), each chip as wide as its words, a row ends where its
   last chip does.
   276.5 (Khoa, a picture: "optimize the chips display to 2 rows. reduce the
   length of the search box a bit"; "new row since 'a 30-minute trial'"):
   box 660; two rows, level + format, then the topic (level + topic is wider
   than the box in Vietnamese), a little more space between two groups than
   between two chips; slightly smaller chips so the first row stays inside
   the box. The subtitle's last words start their own line on a computer and
   never break inside. */
.tpl-filters{display:flex;flex-direction:column;gap:8px;max-width:660px;margin-top:14px;}
.tpl-frow{display:flex;flex-wrap:wrap;gap:8px 14px;}
.tpl-filters .tpl-chips{gap:4px;}
.tpl-filters .tpl-chips button{padding:5px 10px;font-size:12.5px;}
.tpl-sub{max-width:none;}
.tpl-sub2{white-space:nowrap;}
@media (min-width:700px){.tpl-sub2{display:block;}}
.tpl-chips{display:flex;flex-wrap:wrap;gap:6px;}
.tpl-chips button{border:1px solid var(--lu-line);background:rgba(255,255,255,.04);border-radius:999px;padding:6px 13px;color:var(--lu-ink2);font-size:13px;}
.tpl-chips button:hover{color:#fff;}
.tpl-chips button[aria-pressed="true"]{background:#E58A68;border-color:#E58A68;color:#1A0D07;font-weight:600;}
.tpl-main{padding-top:26px;padding-bottom:60px;}
.tpl-count{display:flex;gap:14px;align-items:center;font-size:13px;color:var(--ink-3);margin-bottom:14px;}
.tpl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(330px,100%),1fr));gap:18px;}
.tpl-card{background:var(--card);border:1px solid var(--line);border-radius:22px;overflow:hidden;display:flex;flex-direction:column;min-width:0;}
.tpl-top{position:relative;display:grid;grid-template-columns:auto minmax(0,1fr);gap:14px;align-items:center;padding:18px 18px 16px;background:#0B1F3F;color:#F3F1EA;border-bottom:2px solid #E58A68;}
.tpl-top .tp-av{width:68px;height:68px;font-size:24px;}
.tpl-id h2{font-family:var(--serif);font-weight:600;font-size:20px;line-height:1.2;margin:0;padding-right:70px;overflow-wrap:anywhere;}
.tpl-id p{margin:3px 0 0;color:#B9C2D2;font-size:13.5px;line-height:1.45;}
.tpl-yr{position:absolute;top:12px;right:14px;font-family:var(--mono);font-size:11px;color:#E9C9B9;}
.tpl-body{padding:15px 18px;display:flex;flex-direction:column;gap:12px;flex:1;}
.tpl-quals{display:flex;flex-wrap:wrap;gap:6px;}
.tpl-quals span{font-size:12px;border-radius:999px;padding:2px 9px;background:var(--track);color:var(--ink-2);}
.tpl-cs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;}
.tpl-c{display:block;border:1px solid var(--line);border-radius:10px;overflow:hidden;color:var(--ink);min-width:0;}
.tpl-c:hover{border-color:var(--accent);}
.tpl-cv{display:block;aspect-ratio:16/9;background:var(--track);overflow:hidden;}
.tpl-cv img{width:100%;height:100%;object-fit:cover;display:block;}
.tpl-cv .tp-c-ph{display:grid;place-items:center;height:100%;font-family:var(--mono);font-size:10.5px;color:var(--ink-3);}
.tpl-ct{display:block;padding:4px 6px;font-size:11.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-top:3px solid var(--line);}
.tpl-ct.lv1{border-top-color:var(--lv1);}.tpl-ct.lv2{border-top-color:var(--lv2);}.tpl-ct.lv3{border-top-color:var(--lv3);}
.tpl-q{margin:0;padding-left:10px;border-left:2px solid var(--accent);}
.tpl-q p{margin:0;font-family:var(--read);font-size:14.5px;line-height:1.5;color:var(--ink-2);}
.tpl-q cite{display:block;font-style:normal;font-size:12px;color:var(--ink-3);margin-top:3px;}
.tpl-meta{display:flex;flex-wrap:wrap;gap:4px 14px;font-size:12.5px;color:var(--ink-3);margin-top:auto;}
.tpl-act{display:flex;gap:8px;padding:0 18px 18px;}
.tpl-act .cls-btn{flex:1;justify-content:center;text-align:center;}
.tpl-empty{grid-column:1/-1;text-align:center;padding:36px 20px;border:1px dashed var(--line-2);border-radius:20px;color:var(--ink-2);}
.tpl-you{display:flex;gap:16px;align-items:center;justify-content:space-between;flex-wrap:wrap;margin-top:34px;padding:20px 24px;border-radius:20px;background:var(--accent-wash);border:1px solid color-mix(in srgb,var(--accent) 40%,transparent);}
.tpl-you b{display:block;font-size:16px;}
.tpl-you span{color:var(--ink-2);font-size:14px;}
/* 275.2: on a phone the filters are three small lists on one line, the head tighter */
.tpl-sels{display:none;}
.tpl-sel{min-width:0;width:100%;appearance:none;-webkit-appearance:none;font:inherit;font-size:13.5px;color:var(--lu-ink);
  background:rgba(255,255,255,.06) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23B9C2D2' stroke-width='1.5'/%3E%3C/svg%3E") no-repeat right 10px center;
  border:1px solid var(--lu-line);border-radius:999px;padding:7px 24px 7px 12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
@media (pointer:coarse){.tpl-sel{font-size:16px;}}
.tpl-sel.on{border-color:#E58A68;background-color:rgba(229,138,104,.18);}
.tpl-sel option{color:#1F1E1B;background:#fff;}
@media (max-width:560px){
  .tpl-brand{margin-bottom:16px;}
  .tpl-head{padding:16px 0 18px;}
  .tpl-head h1{font-size:30px;margin:4px 0 6px;}
  .tpl-sub{font-size:14.5px;}
  .tpl-q-in{margin-top:14px;}
  .tpl-filters{display:none;}
  .tpl-sels{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin-top:10px;}
}

/* ===== CHUYỂN KHOÁ HỌC (build 283, 90b-classmove.js) =====
   The ⇄ button sits at the top right of the class window; the three steps
   take the window's whole body. */
.mv-open{display:flex;justify-content:flex-end;margin:-4px 0 8px;}
.mv{display:flex;flex-direction:column;gap:6px;}
.mv-steps{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px;}
.mv-step{font-family:var(--mono);font-size:11px;letter-spacing:.06em;padding:4px 10px;border-radius:999px;
  border:1px solid var(--line);color:var(--ink-3);}
.mv-step.on{border-color:var(--accent);color:var(--accent-text,var(--accent));font-weight:600;}
.mv-step.was{color:var(--ink-2);}
.mv-crss{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px;}
.mv-crs{display:flex;flex-direction:column;align-items:flex-start;gap:3px;text-align:left;padding:10px 12px;
  border:1px solid var(--line-2);border-radius:10px;background:var(--card);color:var(--ink);cursor:pointer;font:inherit;}
.mv-crs b{font-weight:600;font-size:14px;}
.mv-crs span{font-size:12px;color:var(--ink-3);}
.mv-crs em{font-style:normal;color:var(--accent-text,var(--accent));}
.mv-crs:hover{border-color:var(--ink-3);}
.mv-crs.on{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset;}
.mv-more{align-self:flex-start;margin:6px 0 2px;}
.mv-route{font-size:14px;color:var(--ink-2);margin-bottom:2px;}
.mv-who{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:10px;overflow:hidden;}
.mv-p{display:flex;align-items:flex-start;gap:10px;padding:9px 12px;border-top:1px solid var(--line);cursor:pointer;}
.mv-p:first-child{border-top:0;}
.mv-p input{margin-top:3px;flex:none;}
.mv-p span{display:flex;flex-direction:column;gap:2px;min-width:0;}
.mv-p b{font-weight:600;}
.mv-p small{font-size:12px;color:var(--ink-3);}
.mv-p.off b{color:var(--ink-3);font-weight:400;}
.mv-name{display:flex;flex-direction:column;gap:4px;max-width:360px;font-size:13px;color:var(--ink-2);}
.mv-boxes{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px;}
.mv-box,.mv-after{background:var(--panel);border-radius:10px;padding:10px 12px;}
.mv-after{margin-top:8px;}
.mv-box ul,.mv-after ul{margin:0;padding-left:18px;font-size:13px;color:var(--ink-2);line-height:1.5;}
.mv-acts{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:12px;}
.mv-acts .cls-btn{padding:7px 14px;font-size:14px;}
.mv-done{font-size:15px;line-height:1.5;padding:12px 14px;border-radius:10px;background:var(--panel);}
.mv-done b{color:var(--done,var(--ink));}
.mv-err{margin-top:8px;}
@media (max-width:640px){.mv-boxes{grid-template-columns:1fr;}.mv-acts{justify-content:stretch;}.mv-acts .cls-btn{flex:1;text-align:center;}}

/* ===== MỜI BẰNG GMAIL (build 284) =====
   The teacher's box in the class window's Mời tab (ClassInvites,
   90b-classmove.js) and the learner's card from the bell (InviteCard,
   90c-invite.js). State chips are `k-<state>`, never a bare word. */
.iv-t{display:flex;flex-direction:column;gap:8px;}
.iv-t-in{width:100%;min-height:64px;resize:vertical;font:inherit;font-size:14px;line-height:1.45;box-sizing:border-box;}
.iv-t-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;}
.iv-t-w{margin:0;flex:1 1 220px;}
.iv-t-res{font-size:13px;color:var(--ink-2);}
.iv-bad{color:var(--warn,#B4532A);}
.iv-t-err{margin:0;}
.iv-t-list .iv-mail{word-break:break-all;}
.iv-chip{display:inline-block;font-size:12px;padding:2px 9px;border-radius:999px;border:1px solid var(--line-2);color:var(--ink-2);white-space:nowrap;}
.iv-chip.k-yes{border-color:var(--done,#1E7A4C);color:var(--done,#1E7A4C);}
.iv-chip.k-no,.iv-chip.k-exp{color:var(--ink-3);border-style:dashed;}
.iv-c{display:flex;flex-direction:column;gap:10px;max-width:520px;}
.iv-c-who{font-size:14px;color:var(--ink-2);}
.iv-c-cls{display:flex;flex-direction:column;gap:2px;}
.iv-c-cls b{font-family:var(--serif);font-size:24px;font-weight:600;line-height:1.2;}
.iv-c-cls span{font-size:14px;color:var(--ink-2);}
.iv-c-facts{margin:0;padding-left:18px;font-size:14px;color:var(--ink-2);line-height:1.6;}
.iv-c-msg,.iv-c-ok{font-size:15px;line-height:1.5;}
.iv-c-ok{padding:12px 14px;border-radius:10px;background:var(--panel);}
.iv-c-ok b{color:var(--done,var(--ink));}
.iv-c-ask{margin:0;}
.iv-c-acts{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:6px;}
.iv-c-acts .cls-btn{padding:7px 14px;font-size:14px;}
@media (max-width:640px){.iv-c-acts{justify-content:stretch;}.iv-c-acts .cls-btn{flex:1;text-align:center;}}
