/* ============================================================================
   English Learning with AI — design system.

   One file defines the whole product: tokens, then components. Pages compose
   these classes and add almost nothing of their own.

   Three rules this file follows without exception:

   1. LOGICAL PROPERTIES ONLY. padding-inline, margin-inline, inset-inline,
      text-align:start, border-start-start-radius. Never left/right. Arabic RTL
      then works by construction — dir="rtl" on <html> is the only switch, and
      nothing has to be mirrored by hand.
   2. TOUCH TARGETS ARE 44px. Apple's minimum. Small-looking controls get their
      size from padding or a ::before overlay, never from a 26px box.
   3. COLOUR CARRIES MEANING. The interface is one neutral ramp. Accent is used
      for the single most important action on a screen; green/amber/red only for
      correct, careful and wrong. Nothing is coloured for decoration.
   ========================================================================== */

/* ---------------------------------------------------------------- 1. tokens */
:root {
  /* type: one scale, six roles. --fs is user-adjustable in Settings. */
  --fs:16px;
  --t-xs:0.75rem;    /* 12px — meta, labels */
  --t-sm:0.8125rem;  /* 13px — captions, helper text */
  --t-md:0.9375rem;  /* 15px — secondary body */
  --t-base:1rem;     /* 16px — body, inputs (16px stops iOS zoom on focus) */
  --t-lg:1.125rem;   /* 18px — card titles */
  --t-xl:1.375rem;   /* 22px — page titles */
  --t-2xl:1.75rem;   /* 28px — the one number on a stats screen */

  --lh-tight:1.25; --lh-snug:1.4; --lh-base:1.6;
  --fw-normal:400; --fw-medium:500; --fw-semi:600;

  /* spacing: 4px base, no arbitrary values anywhere */
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px; --s-5:20px;
  --s-6:24px; --s-8:32px; --s-10:40px; --s-12:48px;

  --r-sm:8px; --r-md:12px; --r-lg:16px; --r-xl:20px; --r-full:999px;

  /* motion: two durations, one curve */
  --d-fast:120ms; --d-base:220ms;
  --ease:cubic-bezier(.4,0,.2,1);

  --tap:44px;                     /* minimum touch target */
  --kb:0px;                       /* keyboard height, published by boot.js */
  --measure:38rem;                /* ~66 characters */

  /* colour — light */
  --bg:#ffffff;
  --bg-sunk:#f7f7f8;
  --surface:#f7f7f8;
  --surface-2:#ececee;
  --line:#e3e3e6;
  --line-strong:#c9c9cf;

  --text:#16161a;
  --text-2:#5c5c66;
  --text-3:#6e6e78;   /* AA on --bg: was #8c8c96 at 3.3:1 */

  --accent:#16161a;               /* the primary action: near-black on white */
  --accent-text:#ffffff;
  --link:#1a5fd0;

  --ok:#0f7a52;   --ok-bg:#eaf5f0;
  --warn:#8a5a06; --warn-bg:#fbf3e6;
  --bad:#b3261e;  --bad-bg:#fceceb;

  --shadow-1:0 1px 2px rgba(0,0,0,.06);
  --shadow-2:0 8px 28px rgba(0,0,0,.12);

  color-scheme:light;
}

:root[data-theme="dark"], html:not([data-theme="light"]) {
  /* placeholder so the cascade below always has a base to override */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:#1c1c1f;
    --bg-sunk:#171719;
    --surface:#26262a;
    --surface-2:#313136;
    --line:#33333a;
    --line-strong:#4a4a53;

    --text:#f2f2f4;
    --text-2:#a8a8b3;
    --text-3:#9a9aa4;   /* AA on --bg: was #7c7c88 at 4.1:1 */

    --accent:#f2f2f4;
    --accent-text:#16161a;
    --link:#7fb0ff;

    --ok:#5fc194;   --ok-bg:#16261f;
    --warn:#e0a955; --warn-bg:#2a2115;
    --bad:#ef8b85;  --bad-bg:#2b1a19;

    --shadow-1:0 1px 2px rgba(0,0,0,.3);
    --shadow-2:0 8px 28px rgba(0,0,0,.5);

    color-scheme:dark;
  }
}
:root[data-theme="dark"] {
  --bg:#1c1c1f; --bg-sunk:#171719; --surface:#26262a; --surface-2:#313136;
  --line:#33333a; --line-strong:#4a4a53;
  --text:#f2f2f4; --text-2:#a8a8b3; --text-3:#9a9aa4;
  --accent:#f2f2f4; --accent-text:#16161a; --link:#7fb0ff;
  --ok:#5fc194; --ok-bg:#16261f; --warn:#e0a955; --warn-bg:#2a2115;
  --bad:#ef8b85; --bad-bg:#2b1a19;
  --shadow-1:0 1px 2px rgba(0,0,0,.3); --shadow-2:0 8px 28px rgba(0,0,0,.5);
  color-scheme:dark;
}

/* ---------------------------------------------------------------- 2. base */
*, *::before, *::after { box-sizing:border-box; }
* { -webkit-tap-highlight-color:transparent; }

html, body { margin:0; padding:0; }
html { -webkit-text-size-adjust:100%; }
/* Nothing may scroll the document sideways. `clip` rather than `hidden`, because
   `hidden` turns the element into a scroll container and that silently breaks
   every position:sticky header in the app. This is the safety net; the rules
   below remove the causes. */
html, body { overflow-x:clip; }

body {
  background:var(--bg);
  color:var(--text);
  font:var(--fw-normal) var(--fs)/var(--lh-base) -apple-system, BlinkMacSystemFont,
       "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans Arabic", sans-serif;
  -webkit-font-smoothing:antialiased;
  min-height:100dvh;
  overflow-wrap:break-word;
}

button, input, textarea, select { font:inherit; color:inherit; margin:0; }
button { background:none; border:0; padding:0; cursor:pointer; }
a { color:var(--link); text-decoration:none; }
h1, h2, h3, h4 { margin:0; font-weight:var(--fw-semi); line-height:var(--lh-tight); }
p { margin:0; }
svg { display:block; flex:0 0 auto; }

/* Focus rings are for keyboards. :focus alone fires on mouse and touch too, which
   is what makes an interface look cluttered while you are simply tapping. */
:focus { outline:none; }
:focus-visible { outline:2px solid var(--link); outline-offset:2px; border-radius:var(--r-sm); }

/* ---------------------------------------------------------------- 3. layout */
.page {
  width:100%;
  max-width:46rem;
  margin-inline:auto;
  padding-inline:var(--s-4);
  padding-block:var(--s-2) var(--s-10);
}
.stack   { display:flex; flex-direction:column; gap:var(--s-3); }
.stack-4 { display:flex; flex-direction:column; gap:var(--s-4); }
.row     { display:flex; align-items:center; gap:var(--s-3); }
.grow    { flex:1; min-width:0; }
.spacer  { flex:1; }

/* THE APP BAR — one component, three columns, the title centred on the SCREEN.

   It used to be a flex row with the title on flex:1, which centres the title in
   whatever space the buttons leave over. A page with a menu and a Back button on
   the left and nothing on the right pushed its title 26-52px to the right of the
   real centre. A grid with two side regions of identical width cannot do that:
   the middle column is symmetric by construction, however many buttons each side
   holds — including none. */
.appbar {
  position:sticky; inset-block-start:0; z-index:30;
  display:grid;
  grid-template-columns:var(--bar-side) minmax(0,1fr) var(--bar-side);
  align-items:center;
  min-block-size:var(--tap);
  padding-inline:max(var(--s-2), env(safe-area-inset-left))
                 max(var(--s-2), env(safe-area-inset-right));
  padding-block-start:env(safe-area-inset-top);
  padding-block-end:var(--s-1);
  background:var(--bg);
  border-block-end:1px solid var(--line);
}
/* Two 44px targets fit each side. A third action belongs in an overflow menu, not
   in a wider region — widening one side is exactly what moves the title. */
:root { --bar-side:calc(var(--tap) * 2); }

.appbar .bar-side {
  display:flex; align-items:center; gap:0;
  min-inline-size:0; overflow:hidden;
}
/* A bar with no title has nothing to keep centred, so its regions size to their
   own contents instead of to a fixed width — chat's mode chip is far wider than
   two icons and must not be cut off. */
.appbar.no-title { grid-template-columns:auto minmax(0,1fr) auto; }
.appbar.no-title .bar-side { overflow:visible; }
.appbar .bar-side.start { grid-column:1; justify-content:flex-start; }
.appbar .bar-side.end   { grid-column:3; justify-content:flex-end; }

.appbar .heading {
  grid-column:2; justify-self:center; max-inline-size:100%;
  text-align:center; font-size:var(--t-md); font-weight:var(--fw-semi);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
/* a badge on a bar action — the Journal's linking queue, for one */
.appbar .barbadge {
  position:absolute; inset-block-start:2px; inset-inline-end:2px;
  min-inline-size:1.05rem; block-size:1.05rem; padding-inline:3px;
  display:grid; place-items:center; border-radius:var(--r-full);
  background:var(--accent); color:var(--accent-text);
  font-size:10px; font-weight:var(--fw-semi); line-height:1;
}
.appbar .iconbtn, .appbar .menubtn { position:relative; }

/* ---------------------------------------------------------------- 4. type */
.title   { font-size:var(--t-xl); font-weight:var(--fw-semi); line-height:var(--lh-tight); }
.subtitle{ font-size:var(--t-md); color:var(--text-2); }
.label   { font-size:var(--t-sm); font-weight:var(--fw-medium); color:var(--text-2); }
.caption { font-size:var(--t-sm); color:var(--text-3); line-height:var(--lh-snug); }
.section {
  display:flex; align-items:baseline; gap:var(--s-3);
  margin-block:var(--s-6) var(--s-3);
}
.section h2 { flex:1; font-size:var(--t-base); font-weight:var(--fw-semi); }
/* a link is a touch target too: 44px tall, even though it reads as plain text */
.section a  { display:inline-flex; align-items:center; min-height:var(--tap);
              padding-inline:var(--s-2); margin-inline-end:calc(var(--s-2) * -1);
              font-size:var(--t-sm); color:var(--text-2); }
.prose { max-width:var(--measure); line-height:var(--lh-base); }

/* legacy aliases — pages still name these; they now resolve to the system */
.largetitle { font-size:var(--t-xl); font-weight:var(--fw-semi); line-height:var(--lh-tight);
              margin-block:var(--s-2) var(--s-1); }
.muted, .hint { color:var(--text-2); font-size:var(--t-md); }
.kicker, .sectitle, .secttl {
  display:block; font-size:var(--t-sm); font-weight:var(--fw-medium);
  color:var(--text-2); margin-block:var(--s-6) var(--s-3);
}

/* ---------------------------------------------------------------- 5. buttons */
.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:var(--s-2);
  min-height:var(--tap);
  padding-inline:var(--s-5); padding-block:var(--s-3);
  border-radius:var(--r-full);
  font-size:var(--t-md); font-weight:var(--fw-medium);
  transition:background var(--d-fast) var(--ease), opacity var(--d-fast) var(--ease);
  text-align:center;
}
.btn:active { opacity:.7; }
.btn[disabled], .btn:disabled { opacity:.4; pointer-events:none; }

.btn-primary { background:var(--accent); color:var(--accent-text); }
.btn-secondary { background:var(--surface); color:var(--text); }
.btn-secondary:active { background:var(--surface-2); }
.btn-ghost { color:var(--text); }
.btn-ghost:active { background:var(--surface); }
.btn-danger { background:var(--bad); color:#fff; }
.btn-block { display:flex; width:100%; }
.btn-sm { min-height:40px; padding-inline:var(--s-4); padding-block:var(--s-2);
          font-size:var(--t-sm); }

/* icon button: 44px hit area whatever the glyph size */
.iconbtn {
  display:inline-grid; place-items:center;
  inline-size:var(--tap); block-size:var(--tap);
  border-radius:var(--r-md); color:var(--text-2);
  transition:background var(--d-fast) var(--ease), color var(--d-fast) var(--ease);
}
.iconbtn:active { background:var(--surface); color:var(--text); }

/* chips */
.chips { display:flex; flex-wrap:wrap; gap:var(--s-2); }
.chip {
  display:inline-flex; align-items:center; min-height:var(--tap);
  padding-inline:var(--s-4); border-radius:var(--r-full);
  border:1px solid var(--line); color:var(--text-2);
  font-size:var(--t-sm); font-weight:var(--fw-medium);
  transition:background var(--d-fast) var(--ease);
}
.chip:active { background:var(--surface); }
.chip[aria-pressed="true"], .chip.on {
  background:var(--accent); color:var(--accent-text); border-color:transparent;
}

/* ---------------------------------------------------------------- 6. surfaces */
.card {
  border:1px solid var(--line); border-radius:var(--r-lg);
  padding:var(--s-4); background:var(--bg);
}
/* a card that is also a link must not inherit link blue — only real inline
   links are coloured, so colour keeps meaning "this word is tappable". */
a.card, a.tile, a.item, a.chip { color:var(--text); }

/* Interactive cards need four visible states, not one. Depth is a 1px line plus
   a shadow that only appears on hover — a card at rest should look flat. */
.card-tap {
  display:flex; flex-direction:column; gap:var(--s-1);
  text-align:start; inline-size:100%;
  transition:background var(--d-fast) var(--ease),
             border-color var(--d-fast) var(--ease),
             box-shadow var(--d-fast) var(--ease),
             transform var(--d-fast) var(--ease);
}
@media (hover: hover) {
  .card-tap:hover { border-color:var(--line-strong); box-shadow:var(--shadow-1); }
}
.card-tap:active { background:var(--surface); transform:scale(.99); box-shadow:none; }
.card-tap[aria-current="true"], .card-tap.selected {
  background:var(--surface); border-color:var(--text-3);
}
.card-tap[aria-disabled="true"], .card-tap:disabled {
  opacity:.45; pointer-events:none;
}
.card-tap .ico {
  inline-size:2rem; block-size:2rem; border-radius:var(--r-sm);
  display:grid; place-items:center;
  background:var(--surface); color:var(--text-2);
  margin-block-end:var(--s-1);
}
.card-tap[aria-current="true"] .ico { background:var(--accent); color:var(--accent-text); }
.card-tap .ttl { font-size:var(--t-md); font-weight:var(--fw-semi);
                 line-height:var(--lh-snug); }
.card-tap .meta { font-size:var(--t-sm); color:var(--text-2); }

/* every card in a shelf is the same width and stretches to the same height */
.shelf { align-items:stretch; }
.shelf .card-tap { inline-size:10.5rem; }
.card-filled { background:var(--surface); border-color:transparent; }

/* a list of rows — settings, mistakes, files */
.list { border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden; }
.list > .item {
  display:flex; align-items:center; gap:var(--s-3);
  inline-size:100%; min-height:var(--tap);
  padding-inline:var(--s-4); padding-block:var(--s-3);
  text-align:start; color:inherit;
  transition:background var(--d-fast) var(--ease);
}
.list > .item + .item { border-block-start:1px solid var(--line); }
.list > .item:active { background:var(--surface); }
.item .lbl { flex:1; min-width:0; font-size:var(--t-md); }
.item .val { color:var(--text-2); font-size:var(--t-sm); }
.item .chev { color:var(--text-3); }
[dir="rtl"] .item .chev { transform:scaleX(-1); }

/* horizontal shelf */
.shelf {
  display:flex; gap:var(--s-3); overflow-x:auto;
  scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch;
  padding-block-end:var(--s-2); margin-inline:calc(var(--s-4) * -1);
  padding-inline:var(--s-4);
  scrollbar-width:none;
}
.shelf::-webkit-scrollbar { display:none; }
.shelf > * { scroll-snap-align:start; flex:0 0 auto; }

.tiles { display:grid; grid-template-columns:repeat(auto-fit, minmax(9rem, 1fr));
         gap:var(--s-3); }
.tile {
  border:1px solid var(--line); border-radius:var(--r-lg);
  padding:var(--s-4); color:inherit;
  transition:background var(--d-fast) var(--ease);
}
.tile:active { background:var(--surface); }
.tile .big { display:block; font-size:var(--t-2xl); font-weight:var(--fw-semi);
             line-height:var(--lh-tight); }
.tile .cap { display:block; font-size:var(--t-sm); color:var(--text-2);
             margin-block-start:var(--s-1); }

/* ---------------------------------------------------------------- 7. forms */
.field { display:block; margin-block-end:var(--s-4); }
.field > .label { display:block; margin-block-end:var(--s-2); }
.input, .textarea, .select {
  /* min-inline-size:0 is the one that matters. A native picker's intrinsic
     content width otherwise acts as a floor, so inline-size:100% computes to the
     track width but the control is *used* at its larger intrinsic width and gets
     cut off by the sheet edge. */
  inline-size:100%; max-inline-size:100%; min-inline-size:0;
  min-height:var(--tap);
  padding-inline:var(--s-4); padding-block:var(--s-3);
  font-size:var(--t-base);            /* 16px — stops iOS zooming on focus */
  color:var(--text); background:var(--bg);
  border:1px solid var(--line); border-radius:var(--r-md);
  transition:border-color var(--d-fast) var(--ease);
}
.textarea { min-height:8rem; resize:vertical; line-height:var(--lh-base); }
/* A select is as wide as its longest option wants to be. A long module name
   cannot be made to fit a 320px phone, so it ends in an ellipsis rather than
   being cut mid-character at the sheet edge. */
.select { text-overflow:ellipsis; }
.input:focus, .textarea:focus, .select:focus {
  outline:none; border-color:var(--line-strong);
}
.input::placeholder, .textarea::placeholder { color:var(--text-3); }
.field .caption { margin-block-start:var(--s-2); }
.field.invalid .input, .field.invalid .textarea { border-color:var(--bad); }


/* A native checkbox draws at roughly 13px — too small to hit reliably. The label
   around it carries the rest of the target. */
input[type="checkbox"], input[type="radio"] {
  inline-size:1.35rem; block-size:1.35rem; flex:0 0 auto;
  accent-color:var(--accent); margin:0;
}
label.row, label:has(> input[type="checkbox"]) {
  display:flex; align-items:center; gap:var(--s-3);
  min-height:var(--tap); cursor:pointer;
}

/* ---------------------------------------------------------------- 8. feedback */
.badge {
  display:inline-block; padding-inline:var(--s-2); padding-block:2px;
  border-radius:var(--r-sm); font-size:var(--t-xs); font-weight:var(--fw-medium);
  background:var(--surface-2); color:var(--text-2);
}
.badge-ok   { background:var(--ok-bg);   color:var(--ok); }
.badge-warn { background:var(--warn-bg); color:var(--warn); }
.badge-bad  { background:var(--bad-bg);  color:var(--bad); }

.note {
  border:1px solid var(--line); border-radius:var(--r-md);
  padding:var(--s-4); font-size:var(--t-md);
}
.note-ok   { background:var(--ok-bg);   border-color:transparent; }
.note-warn { background:var(--warn-bg); border-color:transparent; }
.note-bad  { background:var(--bad-bg);  border-color:transparent; }

/* empty and error states share one shape */
.state {
  display:flex; flex-direction:column; align-items:center; gap:var(--s-2);
  padding-block:var(--s-6); text-align:center; color:var(--text-2);
}
/* inside a shelf an empty state is a sibling of cards, not a whole screen */
.shelf .state { padding-block:var(--s-4); align-items:flex-start; text-align:start; }
.state .icon { color:var(--text-3); }
.state h3 { font-size:var(--t-base); color:var(--text); }
.state p { font-size:var(--t-md); max-width:22rem; }

/* loading */
.spinner {
  inline-size:1.25rem; block-size:1.25rem; border-radius:50%;
  border:2px solid var(--line); border-block-start-color:var(--text-2);
  animation:spin .7s linear infinite;
}
.skeleton {
  background:linear-gradient(90deg, var(--surface) 25%, var(--surface-2) 37%,
                             var(--surface) 63%);
  background-size:400% 100%;
  animation:shimmer 1.4s ease infinite;
  border-radius:var(--r-md);
}
.dots { display:inline-flex; gap:var(--s-1); align-items:center; }
.dots i {
  inline-size:6px; block-size:6px; border-radius:50%; background:var(--text-3);
  animation:blink 1.3s infinite;
}
.dots i:nth-child(2) { animation-delay:.18s; }
.dots i:nth-child(3) { animation-delay:.36s; }

/* toast */
.toast {
  position:fixed; inset-inline:0; z-index:80;
  inset-block-end:calc(env(safe-area-inset-bottom) + var(--s-5));
  display:flex; justify-content:center; pointer-events:none;
}
.toast > div {
  background:var(--accent); color:var(--accent-text);
  padding-inline:var(--s-5); padding-block:var(--s-3);
  border-radius:var(--r-full); font-size:var(--t-md);
  box-shadow:var(--shadow-2);
  animation:rise var(--d-base) var(--ease);
}

/* ---------------------------------------------------------------- 9. overlays */
.scrim {
  position:fixed; inset:0; z-index:60;
  display:flex; align-items:flex-end; justify-content:center;
  background:rgba(0,0,0,.4);
  opacity:0; pointer-events:none;
  transition:opacity var(--d-base) var(--ease);
}
.scrim.on { opacity:1; pointer-events:auto; }
.scrim.mid { align-items:center; padding:var(--s-4); }

.sheet {
  /* min() rather than a bare 34rem: on a 320px phone the max must be the
     viewport, not a value larger than it. */
  inline-size:100%; max-inline-size:min(34rem, 100%);
  /* dvh follows Safari's toolbar as it shows and hides, and --kb is the on-screen
     keyboard height boot.js publishes, so the sheet shrinks instead of pushing
     its action bar under the keyboard. */
  max-block-size:min(88dvh, calc(100dvh - var(--kb) - var(--s-6)));
  display:flex; flex-direction:column; min-block-size:0;
  background:var(--bg);
  border-start-start-radius:var(--r-xl); border-start-end-radius:var(--r-xl);
  box-shadow:var(--shadow-2);
  padding-block-end:env(safe-area-inset-bottom);
  transform:translateY(1rem); opacity:0;
  transition:transform var(--d-base) var(--ease), opacity var(--d-base) var(--ease);
}
.scrim.on .sheet { transform:none; opacity:1; }
.scrim.mid .sheet { border-radius:var(--r-xl); padding-block-end:0; }
.grabber {
  inline-size:2.25rem; block-size:4px; border-radius:var(--r-full);
  background:var(--line-strong); margin:var(--s-3) auto 0; flex:0 0 auto;
}
.sheet h2 { padding-inline:var(--s-5); padding-block:var(--s-4) var(--s-2);
            font-size:var(--t-lg); }
/* flex:1 with min-block-size:0 is what actually makes this scroll. A flex item
   will not shrink below its content without it, so the sheet grew past its own
   max-block-size and the last field ended up underneath the action bar. */
.sheet .body {
  flex:1 1 auto; min-block-size:0;
  padding-inline:var(--s-4); padding-block:var(--s-2) var(--s-6);
  overflow-y:auto; overflow-x:clip;
  overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
}
.sheet .foot {
  flex:0 0 auto;
  display:flex; gap:var(--s-2);
  padding-inline:var(--s-4);
  padding-block:var(--s-3) calc(var(--s-3) + env(safe-area-inset-bottom));
  border-block-start:1px solid var(--line);
  background:var(--bg);
}
.sheet .foot .btn { flex:1; }
/* sheets whose footer buttons are plain <button> (upload, confirm) still get
   full-size targets — the system styles the slot, not just the class. */
.sheet .foot button { flex:1; min-height:var(--tap); border-radius:var(--r-full);
                      font-size:var(--t-md); font-weight:var(--fw-medium);
                      background:var(--surface); color:var(--text); }
.sheet .foot button.primary, .sheet .foot #useBtn {
  background:var(--accent); color:var(--accent-text); }

/* ---------------------------------------------------------------- 10. nav */
.menubtn { display:inline-grid; place-items:center;
           inline-size:var(--tap); block-size:var(--tap);
           border-radius:var(--r-md); color:var(--text); }
.menubtn:active { background:var(--surface); }

.drawer { position:fixed; inset:0; z-index:70; pointer-events:none; }
.drawer .veil { position:absolute; inset:0; background:rgba(0,0,0,.4);
                opacity:0; transition:opacity var(--d-base) var(--ease); }
.drawer .panel {
  position:absolute; inset-block:0; inset-inline-start:0;
  inline-size:min(19rem, 84vw);
  display:flex; flex-direction:column;
  background:var(--bg); border-inline-end:1px solid var(--line);
  padding-inline:var(--s-2);
  padding-block:calc(env(safe-area-inset-top) + var(--s-3))
               calc(env(safe-area-inset-bottom) + var(--s-3));
  transform:translateX(-100%);
  transition:transform var(--d-base) var(--ease);
}
[dir="rtl"] .drawer .panel { transform:translateX(100%); }
.drawer.on { pointer-events:auto; }
.drawer.on .veil { opacity:1; }
.drawer.on .panel { transform:none; }

.drawer .brand {
  display:flex; align-items:center; gap:var(--s-2);
  padding-inline:var(--s-3); padding-block:var(--s-2) var(--s-4);
  font-size:var(--t-md); font-weight:var(--fw-semi); color:var(--text);
  line-height:var(--lh-tight);
}
.drawer .brand svg { flex:0 0 auto; }
.drawer .brand span { min-inline-size:0; }

/* the lockup, wherever a page needs it whole */
.brandmark { display:inline-flex; align-items:center; gap:var(--s-2);
             color:var(--text); }
.brandmark .name { font-size:var(--t-lg); font-weight:var(--fw-semi);
                   letter-spacing:-.02em; }
.brandmark.stacked { flex-direction:column; gap:var(--s-3); text-align:center; }
.drawer nav { display:flex; flex-direction:column; gap:2px; flex:1; overflow-y:auto; }
.drawer nav a {
  display:flex; align-items:center; gap:var(--s-3);
  min-height:var(--tap); padding-inline:var(--s-3);
  border-radius:var(--r-md); color:var(--text);
  font-size:var(--t-md); font-weight:var(--fw-medium);
  transition:background var(--d-fast) var(--ease);
}
.drawer nav a { position:relative; }
@media (hover: hover) {
  .drawer nav a:hover { background:var(--surface); }
}
.drawer nav a:active { background:var(--surface-2); }

/* Active item: a filled row plus a short accent bar on the leading edge — no
   outline, and the icon and label change colour together. */
.drawer nav a.on { background:var(--surface); color:var(--text); font-weight:var(--fw-semi); }
.drawer nav a.on::before {
  content:''; position:absolute; inset-inline-start:0;
  inset-block:20%; inline-size:3px; border-radius:var(--r-full);
  background:var(--text);
}
.drawer nav a svg { color:var(--text-3); }
.drawer nav a.on svg { color:var(--text); }
.drawer nav a[aria-disabled="true"] { opacity:.4; pointer-events:none; }

/* A pointer should never leave a ring behind; a keyboard always should. */
.drawer nav a:focus { outline:none; }
.drawer nav a:focus-visible { outline:2px solid var(--link); outline-offset:-2px; }
.drawer .panel:focus { outline:none; }
.drawer .foot { border-block-start:1px solid var(--line);
                padding-block-start:var(--s-2); margin-block-start:var(--s-2); }
/* the footer link is a navigation item too — it sits outside nav, so it never
   inherited the navigation styling and rendered in link blue */
.drawer .foot a {
  display:flex; align-items:center; gap:var(--s-3);
  min-height:var(--tap); padding-inline:var(--s-3);
  border-radius:var(--r-md); color:var(--text);
  font-size:var(--t-md); font-weight:var(--fw-medium);
  transition:background var(--d-fast) var(--ease);
}
.drawer .foot a:active { background:var(--surface); }
.drawer .foot a svg { color:var(--text-3); }
@media (hover: hover) { .drawer .foot a:hover { background:var(--surface); } }

/* ---------------------------------------------------------------- 11. motion */
@keyframes spin { to { transform:rotate(360deg); } }
@keyframes shimmer { to { background-position:-400% 0; } }
@keyframes blink { 0%,60%,100% { opacity:.25; } 30% { opacity:1; } }
@keyframes rise { from { opacity:0; transform:translateY(8px); } }
@keyframes fade { from { opacity:0; } }
.rise { animation:rise var(--d-base) var(--ease); }

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

/* ---------------------------------------------------------------- 12. legacy */
/* Old class names still present in page markup, mapped onto the system so no
   screen is left on the previous design while the JS keeps working. */
.tabbar { display:none; }
.has-tabs { padding-block-end:0; }
.glass { background:var(--bg); }
.group, .grouped { border:1px solid var(--line); border-radius:var(--r-lg);
                   overflow:hidden; margin-block-start:var(--s-3); }
.group .item { display:flex; align-items:center; gap:var(--s-3); inline-size:100%;
               min-height:var(--tap); padding-inline:var(--s-4);
               padding-block:var(--s-3); text-align:start; color:inherit; }
.group .item + .item { border-block-start:1px solid var(--line); }
.group .item:active { background:var(--surface); }
.pills { display:flex; flex-wrap:wrap; gap:var(--s-2); }
.pills button { display:inline-flex; align-items:center; min-height:var(--tap);
                padding-inline:var(--s-4); border-radius:var(--r-full);
                border:1px solid var(--line); color:var(--text-2);
                font-size:var(--t-sm); font-weight:var(--fw-medium); }
.pills button.on { background:var(--accent); color:var(--accent-text);
                   border-color:transparent; }
/* play.html writes `class="btn-primary wide"` without `.btn`; the system gives
   those the same size and shape so they are still real touch targets. */
.btn-primary, .btn-quiet, .textbtn {
  display:inline-flex; align-items:center; justify-content:center; gap:var(--s-2);
  min-height:var(--tap); padding-inline:var(--s-5);
  border-radius:var(--r-full); font-size:var(--t-md); font-weight:var(--fw-medium);
  transition:opacity var(--d-fast) var(--ease), background var(--d-fast) var(--ease);
}
.btn-primary { background:var(--accent); color:var(--accent-text); }
.btn-primary:active { opacity:.75; }
.btn-quiet, .textbtn { background:var(--surface); color:var(--text); }
.btn-quiet:active, .textbtn:active { background:var(--surface-2); }
.wide { display:flex; inline-size:100%; margin-block-start:var(--s-2); }
.empty { color:var(--text-2); font-size:var(--t-md); padding:var(--s-4); }

/* ---------------------------------------------------------------- 13. progress
   progress.html names these; defining them here (not on the deployed file) is
   what keeps them alive across redeploys. */
.wrap { inline-size:100%; max-inline-size:46rem; margin-inline:auto;
        padding-inline:var(--s-4); padding-block-end:var(--s-6); }

.tilegrid { display:grid; grid-template-columns:repeat(auto-fit, minmax(6.5rem, 1fr));
            gap:var(--s-2); margin-block:var(--s-4) var(--s-2); }
.tilegrid .t { border:1px solid var(--line); border-radius:var(--r-lg);
               padding:var(--s-3); min-inline-size:0; }
.tilegrid .t b { display:block; font-size:var(--t-xl); font-weight:var(--fw-semi);
                 line-height:var(--lh-tight); }
.tilegrid .t span { display:block; font-size:var(--t-sm); color:var(--text-2);
                    margin-block-start:2px; }

h2.sec, .sec { font-size:var(--t-base); font-weight:var(--fw-semi);
               margin-block:var(--s-6) var(--s-3); }
.none { color:var(--text-2); font-size:var(--t-sm); line-height:var(--lh-base); }

/* every panel on Progress is the same card, so the page reads as one column */
#learning > div, #usage > div {
  border:1px solid var(--line); border-radius:var(--r-lg);
  padding:var(--s-4); margin-block-end:var(--s-3);
}
#learning h3, #usage h3 { font-size:var(--t-md); font-weight:var(--fw-semi);
                          margin-block-end:var(--s-1); }
#learning p, #usage p { font-size:var(--t-md); color:var(--text-2);
                        line-height:var(--lh-base); }

/* charts scale with the card instead of overflowing it */
#learning svg, #usage svg { inline-size:100%; block-size:auto; max-inline-size:100%;
                            display:block; margin-block-start:var(--s-3); }


/* ---- clipboard component (Journal and College share it) ---- */
.paste { border:2px dashed var(--line-strong); border-radius:var(--r-lg);
         padding:var(--s-4) var(--s-3); text-align:center; margin-block-end:var(--s-3);
         background:var(--surface); position:relative; }
.paste.on { border-color:var(--accent); background:var(--accent-soft); }
.paste h3 { font-size:var(--t-md); margin-block-end:var(--s-1); }
.paste p { font-size:var(--t-sm); color:var(--text-2); margin-block-end:var(--s-3); }
.paste .btnrow { display:flex; gap:var(--s-2); flex-wrap:wrap; justify-content:center; }
.paste .pastenote { margin-block:var(--s-2) 0; }
.pastesink { position:absolute; inset-inline-start:-9999px; inline-size:1px;
             block-size:1px; opacity:0; }
.pending { border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--s-3);
           margin-block-end:var(--s-2); }
.pending .prev { display:flex; gap:var(--s-3); align-items:flex-start; }
.pending .prev img { inline-size:5rem; block-size:5rem; object-fit:cover;
                     border-radius:var(--r-sm); flex:0 0 auto; }
.pending .prev .txt { flex:1; min-inline-size:0; font-size:var(--t-sm);
                      max-block-size:6rem; overflow:auto; white-space:pre-wrap;
                      overflow-wrap:anywhere; }
.pending .fields { display:grid; gap:var(--s-2); margin-block-start:var(--s-3); }
.pending .fields input, .pending .fields select { inline-size:100%; }
.pending .acts { display:flex; gap:var(--s-2); margin-block-start:var(--s-3); }
.pending .bar { block-size:3px; border-radius:2px; background:var(--surface-2);
                margin-block-start:var(--s-2); overflow:hidden; }
.pending .bar i { display:block; block-size:100%; background:var(--accent); inline-size:0;
                  transition:inline-size .2s; }

/* ---- one lecture occurrence, named the same way everywhere ---- */
.occ { display:inline-block; }
.occ b { font-weight:var(--fw-medium); display:block; }
.occ span { font-size:var(--t-sm); color:var(--text-2); display:block; }
@media (min-width:40rem) {
  .occ b, .occ span { display:inline; }
  .occ span::before { content:' \2014 '; color:var(--text-2); }
}


/* ---- attachment cards and the viewer (used on every page) ---- */
.attcard { display:flex; gap:var(--s-3); align-items:flex-start; padding:var(--s-3);
           border:1px solid var(--line); border-radius:var(--r-lg);
           margin-block-end:var(--s-2); background:var(--surface); }
.attthumb { inline-size:4rem; block-size:4rem; flex:0 0 auto; border-radius:var(--r-sm);
            background:var(--surface-2); overflow:hidden; display:grid;
            place-items:center; color:var(--text-2); }
.attthumb img { inline-size:100%; block-size:100%; object-fit:cover; }
.attthumb.tap { cursor:pointer; }
.attbody { flex:1; min-inline-size:0; }
.attt { font-size:var(--t-md); font-weight:var(--fw-medium);
        line-height:var(--lh-snug); overflow-wrap:anywhere; }
.attf { font-size:var(--t-xs); color:var(--text-2); font-family:ui-monospace,monospace;
        overflow-wrap:anywhere; }
.attc { font-size:var(--t-sm); color:var(--text-2); margin-block-start:var(--s-1);
        overflow-wrap:anywhere; }
.attm { font-size:var(--t-xs); color:var(--text-2); margin-block-start:var(--s-1); }
.attr { font-size:var(--t-sm); margin-block-start:2px; }
.atttags { display:flex; gap:var(--s-1); flex-wrap:wrap; margin-block-start:var(--s-1); }
.attacts { display:flex; gap:var(--s-1); flex-wrap:wrap; margin-block-start:var(--s-2); }

.attscrim { position:fixed; inset:0; background:rgba(0,0,0,.86); z-index:200;
            display:grid; place-items:stretch;
            padding:env(safe-area-inset-top) 0 env(safe-area-inset-bottom); }
.attshell { display:flex; flex-direction:column; inline-size:100%; block-size:100%;
            min-block-size:0; }
.attbar { display:flex; gap:var(--s-2); align-items:center; padding:var(--s-2) var(--s-3);
          color:#fff; flex:0 0 auto; }
.attbar .iconbtn { color:#fff; }
.attbartitle { flex:1; min-inline-size:0; font-size:var(--t-md);
               font-weight:var(--fw-medium); overflow:hidden;
               text-overflow:ellipsis; white-space:nowrap; }
.attbar .btn { color:#fff; border-color:rgba(255,255,255,.35); }
.attstage { flex:1; min-block-size:0; display:grid; place-items:center;
            overflow:auto; padding:var(--s-3); }
.attzoom { inline-size:100%; block-size:100%; display:grid; place-items:center;
           overflow:hidden; touch-action:none; }
.attzoom img { max-inline-size:100%; max-block-size:100%; transform-origin:center;
               transition:transform .08s linear; }
.attpdf { inline-size:100%; block-size:100%; min-block-size:60vh; border:0;
          background:#fff; border-radius:var(--r-sm); }
.attmedia { inline-size:100%; max-inline-size:34rem; }
.attspeed { display:flex; gap:var(--s-1); justify-content:center;
            margin-block-start:var(--s-3); }
.attspeed .btn { color:#fff; border-color:rgba(255,255,255,.3); }
.attspeed .btn.on { background:#fff; color:#000; }
.atttext { inline-size:100%; max-inline-size:44rem; max-block-size:70vh; overflow:auto;
           background:var(--bg); color:var(--text); padding:var(--s-4);
           border-radius:var(--r-md); white-space:pre-wrap; overflow-wrap:anywhere;
           font-size:var(--t-md); line-height:var(--lh-normal); }
.attnone { text-align:center; color:#fff; display:grid; gap:var(--s-2);
           place-items:center; }
.attfoot { flex:0 0 auto; color:rgba(255,255,255,.85); padding:var(--s-2) var(--s-3);
           font-size:var(--t-sm); }
.attfoot .attm, .attfoot .attc { color:rgba(255,255,255,.7); }
.attnav { display:flex; gap:var(--s-2); align-items:center; justify-content:center;
          margin-block-start:var(--s-2); }
.attnav .muted { color:rgba(255,255,255,.7); }

/* grids for My Files */
.attgrid { display:grid; gap:var(--s-2);
           grid-template-columns:repeat(auto-fill, minmax(6.5rem, 1fr)); }
.attgrid .attcard { flex-direction:column; padding:var(--s-2); }
.attgrid .attthumb { inline-size:100%; block-size:6rem; }
.attgrid .attm, .attgrid .attr, .attgrid .attacts, .attgrid .attf { display:none; }
.attgrid .attt { font-size:var(--t-sm); }


/* ================================================================= form system
   Standard field height (--tap, 44px), standard spacing (--s-4 between fields), one
   maximum width, one breakpoint for pairing fields. Every form on the platform uses
   these rather than its own arrangement. */
.form { max-inline-size:34rem; }
.form.wide { max-inline-size:44rem; }

/* Two fields side by side only where two genuinely fit.

   A viewport media query is the wrong test: the same row sits at the full page
   width on one screen and inside a padded sheet on another, so 390px of viewport
   can mean 358px or 288px of actual track. auto-fit asks the real container
   instead — two columns appear when two 10.5rem columns fit and not before, and
   min() stops the floor itself from overflowing a container narrower than that. */
.field-row {
  display:grid; gap:var(--s-3);
  grid-template-columns:repeat(auto-fit, minmax(min(10.5rem, 100%), 1fr));
  margin-block-end:var(--s-4);
}
.field-row > .field { margin-block-end:0; min-inline-size:0; }
.field-row.thirds { grid-template-columns:repeat(auto-fit, minmax(min(9rem, 100%), 1fr)); }

/* Native date and time pickers.

   iOS Safari does not apply box-sizing:border-box to a date or time input while
   it carries its native appearance: the padding is added OUTSIDE the declared
   width, so inline-size:100% plus 12px of padding each side renders 24px wider
   than its track and hangs over the edge of the sheet. Chromium honours
   border-box, which is exactly why this passed every test here and still failed
   on the phone.

   appearance:none restores the standard box model. The picker itself is
   untouched — tapping the field still opens the native iOS wheel. */
.input[type="date"], .input[type="time"], .input[type="datetime-local"],
.input[type="month"], .input[type="week"] {
  -webkit-appearance:none; appearance:none;
  box-sizing:border-box;
  min-block-size:var(--tap); font-variant-numeric:tabular-nums; text-align:start;
  padding-inline:var(--s-3);
  inline-size:100%; max-inline-size:100%; min-inline-size:0;
}
/* WebKit puts the calendar/clock glyph in its own box; keep it inside the field
   and clear of the digits rather than overlapping them. */
.input[type="date"]::-webkit-calendar-picker-indicator,
.input[type="time"]::-webkit-calendar-picker-indicator {
  margin-inline-start:var(--s-2); flex:0 0 auto;
}
.input[type="date"]::-webkit-date-and-time-value,
.input[type="time"]::-webkit-date-and-time-value { text-align:start; }

/* A row holding a native picker needs more room per column than a row of plain
   text fields: iOS renders a wider control than Chromium does. Asking for 13rem
   per column means a phone-width sheet gets one column — the single-column
   fallback — and only a tablet or desktop sheet goes two-up. */
.field-row:has(> .field > .input[type="date"]),
.field-row:has(> .field > .input[type="time"]),
.field-row:has(> .field > .input[type="datetime-local"]) {
  grid-template-columns:repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
}
.field.compact > .input { inline-size:100%; max-inline-size:11rem; }
.field.compact { max-inline-size:11rem; }

/* help and error text always sit under their field, never beside it */
.field > .caption, .field > .err { display:block; margin-block-start:var(--s-2);
                                   font-size:var(--t-sm); }
.field > .err { color:var(--bad); }
.field.invalid > .input, .field.invalid > .select,
.field.invalid > .textarea { border-color:var(--bad); }

/* one focus ring, on keyboard focus only, everywhere */
.input:focus-visible, .select:focus-visible, .textarea:focus-visible,
.btn:focus-visible, .chip:focus-visible, .optcard:focus-visible,
.iconbtn:focus-visible {
  outline:2px solid var(--accent); outline-offset:2px;
}

/* actions sit together at the end of a form, full width on a phone */
.formacts { display:flex; gap:var(--s-2); flex-wrap:wrap;
            margin-block-start:var(--s-5); }
.formacts .btn { min-height:var(--tap); }
@media (max-width:26rem) { .formacts .btn { flex:1 1 100%; } }

/* ---- equal-size option cards ----
   A chip sizes to its text, so a long label made one option twice the width of
   another. These are a grid: every option is the same size whatever it says. */
.optiongrid { display:grid; gap:var(--s-2); grid-template-columns:1fr;
              margin-block:var(--s-2) var(--s-4); }
@media (min-width:26rem) { .optiongrid { grid-template-columns:1fr 1fr; } }
@media (min-width:60rem) { .optiongrid.three { grid-template-columns:1fr 1fr 1fr; } }
.optiongrid.inline { grid-template-columns:repeat(3, 1fr); }

.optcard { display:flex; flex-direction:column; gap:2px; text-align:start;
           min-block-size:var(--tap); padding:var(--s-3) var(--s-3);
           border:1px solid var(--line); border-radius:var(--r-md);
           background:var(--surface); color:var(--text);
           transition:border-color var(--d-fast) var(--ease),
                      background var(--d-fast) var(--ease); }
.optcard > .t { font-weight:var(--fw-medium); font-size:var(--t-md);
                line-height:var(--lh-snug); }
.optcard > .h { font-size:var(--t-sm); color:var(--text-2);
                line-height:var(--lh-snug); }
.optcard.on { border-color:var(--accent); background:var(--accent-soft); }
.optcard.on > .t { color:var(--accent); }
.optcard[disabled] { opacity:.5; }
@media (hover:hover) { .optcard:hover:not(.on) { border-color:var(--line-strong); } }
.optcard:active { transform:scale(.995); }
.optiongrid.inline .optcard { align-items:center; justify-content:center;
                              text-align:center; }


/* ------------------------------------------------- 14. horizontal scrollers */
/* One component for every row of choices too wide to fit: the Journal's views and
   filters, the Listen categories and files, any future chip row.

   The negative margin lets the row bleed to the screen edge — so a half-visible
   chip reads as "there is more this way" rather than as a clipped layout — while
   its own padding keeps the first and last item aligned with the page gutter.
   scroll-padding does the same for keyboard and programmatic scrolling, and the
   trailing padding is what finally lets the last item come fully into view. */
.scroller {
  display:flex; gap:var(--s-2);
  overflow-x:auto; overflow-y:hidden;
  padding-inline:var(--s-4); margin-inline:calc(var(--s-4) * -1);
  scroll-padding-inline:var(--s-4);
  scroll-snap-type:inline proximity;
  overscroll-behavior-inline:contain;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none; -ms-overflow-style:none;
}
.scroller::-webkit-scrollbar { display:none; }
.scroller > * { flex:0 0 auto; scroll-snap-align:start; }
/* WebKit drops a scroll container's trailing padding once you reach the end, so
   padding-inline alone leaves the last chip flush against the edge. A margin on
   the last child is counted, and is what actually lets it scroll clear. */
.scroller > *:last-child { margin-inline-end:var(--s-4); }
.sheet .scroller > *:last-child, .scroller.inset > *:last-child { margin-inline-end:0; }
/* inside a sheet or any already-padded box the row must not bleed further */
.sheet .scroller, .scroller.inset { margin-inline:0; padding-inline:0; }
/* when everything fits there is nothing to scroll — centre nothing, just stop */
.scroller.fits { justify-content:flex-start; }

/* a segmented row of views: same component, sized to its labels */
.segrow > button {
  min-block-size:var(--tap); padding-inline:var(--s-4);
  border:1px solid var(--line); border-radius:var(--r-full);
  background:var(--bg); color:var(--text-2);
  font-size:var(--t-md); font-weight:var(--fw-medium); white-space:nowrap;
}
.segrow > button.on {
  background:var(--accent); color:var(--accent-text); border-color:var(--accent);
}

/* ------------------------------------------------- 15. bottom-anchored room */
/* Safari draws its toolbar over the bottom of the page and the installed PWA does
   not, so a fixed number is wrong in one of the two. env() is 0 in Safari and the
   real inset in the PWA; the max() adds the breathing room Safari needs. */
.page { padding-block-end:max(var(--s-10), calc(var(--s-8) + env(safe-area-inset-bottom))); }
.addbtn, .fab {
  inset-block-end:max(var(--s-5), calc(var(--s-4) + env(safe-area-inset-bottom)));
}


/* ------------------------------------------------- 16. English inside RTL */
/* The interface mirrors, but the content of this app is English: lecture names,
   dates, counts. In an RTL paragraph the bidi algorithm reorders a run that
   starts with a digit, so "24 Aug - 30 Aug 2026" was drawn as
   "Aug - 30 Aug 2026 24" and "1 homework" as "homework 1".

   unicode-bidi:plaintext lets each run take its direction from its own first
   strong character: English text renders left to right inside a right-to-left
   page, which is what it should do, while the layout around it stays mirrored. */
/* Applied to the containers, not to a list of classes: unicode-bidi:plaintext
   inherits, and every block inside works out its own direction from its own first
   strong character. So an English label keeps its question mark at the end, an
   English date keeps its day at the front, and anything genuinely Arabic still
   reads right to left. One rule covers text that has not been written yet. */
/* unicode-bidi is NOT an inherited property, so setting it on the containers
   alone does nothing for the text inside them — it has to reach the elements
   that actually hold the words. Each one then works out its own direction from
   its own first strong character: an English label keeps its question mark at
   the end, an English date keeps its day at the front, and anything genuinely
   Arabic still reads right to left. */
[dir="rtl"] .page, [dir="rtl"] .page *,
[dir="rtl"] .sheet, [dir="rtl"] .sheet *,
[dir="rtl"] .appbar, [dir="rtl"] .appbar *,
[dir="rtl"] .drawer .panel, [dir="rtl"] .drawer .panel * {
  unicode-bidi:plaintext;
}
/* an explicit escape hatch for anything else that is known to be English */
.ltrtext { direction:ltr; unicode-bidi:isolate; text-align:start; }


/* A chart is a coordinate system, not a paragraph. direction:rtl inherits into
   the SVG and flips what text-anchor:start means, which pushed the axis date
   labels off both edges in Arabic. The plotted geometry is authored left to
   right, so the chart keeps that regardless of the interface direction. */
[dir="rtl"] svg, [dir="rtl"] svg * {
  direction:ltr; unicode-bidi:normal;
}


/* ------------------------------------------------- 17. attendance */
/* A status must never be carried by colour alone: every badge shows a mark and
   a word, and the colour only reinforces them. */
.attbadge {
  display:inline-flex; align-items:center; gap:var(--s-1);
  padding-inline:var(--s-2); padding-block:2px;
  border-radius:var(--r-full); border:1px solid var(--line);
  font-size:var(--t-xs); font-weight:var(--fw-medium); white-space:nowrap;
  color:var(--text-2); background:var(--bg);
}
.attbadge .mk { font-weight:var(--fw-semi); }
.attbadge.ok   { color:var(--ok);   border-color:var(--ok);   background:var(--ok-bg); }
.attbadge.warn { color:var(--warn); border-color:var(--warn); background:var(--warn-bg); }
.attbadge.bad  { color:var(--bad);  border-color:var(--bad);  background:var(--bad-bg); }
.attbadge.mute { color:var(--text-3); }
.attbadge.none { color:var(--text-3); border-style:dashed; }

/* Month view has no room for words — a 8px mark, with the label still on the
   element for a screen reader and on hover. */
.attbadge.dot {
  inline-size:8px; block-size:8px; padding:0; gap:0;
  border-radius:var(--r-full); justify-content:center;
}
.attbadge.dot .mk { display:none; }
.attbadge.ok.dot   { background:var(--ok); }
.attbadge.warn.dot { background:var(--warn); }
.attbadge.bad.dot  { background:var(--bad); }
.attbadge.mute.dot { background:var(--text-3); }
.attbadge.none.dot { background:transparent; }

/* the picker inside the lecture sheet */
.attpick { display:grid; gap:var(--s-2);
           grid-template-columns:repeat(auto-fit, minmax(min(7rem,100%), 1fr)); }
.attpick button {
  min-block-size:var(--tap); padding-inline:var(--s-3);
  border:1px solid var(--line); border-radius:var(--r-md);
  background:var(--bg); color:var(--text); font-size:var(--t-md);
  display:flex; align-items:center; justify-content:center; gap:var(--s-2);
}
.attpick button[aria-pressed="true"] {
  border-color:var(--accent); border-width:2px; font-weight:var(--fw-semi);
  background:var(--surface);
}
.attpick button[aria-pressed="true"]::before { content:'✓'; font-weight:var(--fw-semi); }

/* the summary */
.attrow { display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2);
          padding-block:var(--s-3); border-block-start:1px solid var(--line); }
.attrow:first-of-type { border-block-start:0; }
.attrow .who { flex:1 1 9rem; min-inline-size:0; overflow-wrap:break-word; }
.attrow .pct { font-variant-numeric:tabular-nums; font-weight:var(--fw-semi); }
/* attmeter, not attbar — .attbar is already the attachment viewer toolbar, and
   the two rules were cancelling each other out. */
.attmeter { inline-size:100%; block-size:6px; border-radius:var(--r-full);
            background:var(--surface-2); overflow:hidden; display:flex; }
.attmeter i { display:block; block-size:100%; }
.attmeter i.ok { background:var(--ok); }
.attmeter i.warn { background:var(--warn); }
.attmeter i.bad { background:var(--bad); }
