/*
  English Learning OS — base sheet: reset, layout, and the component library.

  Loaded on every page, after tokens.css and before anything page-specific.

  Two rules govern everything below, and both are enforced by
  _verify/checks/06 rather than by discipline:

  1. NO LITERAL COLOURS. Every colour is var(--lx-*). tokens.css is the only
     file allowed a hex code.

  2. NO PHYSICAL DIRECTION PROPERTIES. margin-inline-start, not margin-left;
     inset-inline-end, not right; text-align: start, not left; border-inline,
     padding-block. The UI is Hebrew (RTL) with English content (LTR) embedded
     in almost every screen, and a single margin-left is enough to put a
     progress bar's label on the wrong side of the bar in one language only.

  The components here are the ones used by more than one screen. Anything used
  by exactly one screen belongs in that screen's sheet.
*/


/* ── Reset ───────────────────────────────────────────────────── */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

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

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--lx-font);
  font-size: var(--lx-fs-md);
  line-height: var(--lx-lh-body);
  background: var(--lx-bg);
  color: var(--lx-text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 var(--lx-sp-3);
  line-height: var(--lx-lh-heading);
  font-weight: var(--lx-fw-bold);
  color: var(--lx-text);
  text-wrap: balance;
}
h1 { font-size: var(--lx-fs-3xl); letter-spacing: var(--lx-track-tight); }
h2 { font-size: var(--lx-fs-2xl); letter-spacing: var(--lx-track-tight); }
h3 { font-size: var(--lx-fs-xl); }
h4 { font-size: var(--lx-fs-lg); }
h5, h6 { font-size: var(--lx-fs-md); }

p { margin: 0 0 var(--lx-sp-4); }
p:last-child { margin-bottom: 0; }

a {
  color: var(--lx-primary);
  text-decoration: none;
  transition: color var(--lx-t-fast) var(--lx-ease);
}
a:hover { color: var(--lx-primary-hover); text-decoration: underline; }

img, svg, video, audio { max-width: 100%; }
img, svg, video { height: auto; vertical-align: middle; }

ul, ol { margin: 0 0 var(--lx-sp-4); padding-inline-start: var(--lx-sp-5); }
li { margin-bottom: var(--lx-sp-1); }

hr {
  border: 0;
  border-block-start: var(--lx-bw-hair) solid var(--lx-border);
  margin: var(--lx-sp-5) 0;
}

code, kbd, samp, pre { font-family: var(--lx-font-mono); font-size: .9em; }

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

table { border-collapse: collapse; width: 100%; }

/* One focus ring, one width, everywhere. A focus outline that is 2px on a
   button and 3px on an input reads as a browser bug rather than a decision,
   and both used to be hard-coded in three different places. */
:focus-visible {
  outline: var(--lx-ring-w) solid var(--lx-primary);
  outline-offset: var(--lx-ring-offset);
  border-radius: var(--lx-r-xs);
}


/* ── English inside Hebrew ───────────────────────────────────────
   Emitted by lx_en() in PHP. This is the only place mixed direction is
   styled — the wrapper carries the direction and the Latin type stack, so no
   template ever has to decide. unicode-bidi: isolate is what keeps a trailing
   question mark on the correct side of the fragment. */

.lx-en {
  direction: ltr;
  unicode-bidi: isolate;
  font-family: var(--lx-font-en);
}
.lx-en--block { display: block; }


/* ── Layout ─────────────────────────────────────────────────── */

.lx-container {
  width: 100%;
  max-width: var(--lx-w-content);
  margin-inline: auto;
  padding-inline: var(--lx-sp-4);
}
@media (min-width: 640px) { .lx-container { padding-inline: var(--lx-sp-5); } }

.lx-narrow { max-width: var(--lx-w-narrow); margin-inline: auto; }
.lx-reader { max-width: var(--lx-w-reader); margin-inline: auto; }

.lx-stack     > * + * { margin-block-start: var(--lx-sp-4); }
.lx-stack-sm  > * + * { margin-block-start: var(--lx-sp-2); }
.lx-stack-lg  > * + * { margin-block-start: var(--lx-sp-6); }

.lx-row {
  display: flex;
  align-items: center;
  gap: var(--lx-sp-3);
  flex-wrap: wrap;
}
.lx-row--between { justify-content: space-between; }
.lx-row--end     { justify-content: flex-end; }
.lx-row--top     { align-items: flex-start; }
.lx-row--nowrap  { flex-wrap: nowrap; }
.lx-spacer       { flex: 1 1 auto; }

.lx-grid { display: grid; gap: var(--lx-sp-4); }
.lx-grid--2 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.lx-grid--3 { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.lx-grid--4 { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }


/* ── Accessibility utilities ────────────────────────────────── */

.lx-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.lx-skip {
  position: fixed; inset-block-start: -100%; inset-inline-start: var(--lx-sp-4);
  z-index: var(--lx-z-toast);
  padding: var(--lx-sp-3) var(--lx-sp-5);
  background: var(--lx-primary); color: var(--lx-on-primary);
  border-radius: var(--lx-r-md); font-weight: 600;
  box-shadow: var(--lx-shadow-lg);
  transition: inset-block-start var(--lx-t-mid) var(--lx-ease);
}
.lx-skip:focus { inset-block-start: var(--lx-sp-4); text-decoration: none; }


/* ── Text utilities ─────────────────────────────────────────── */

.lx-muted    { color: var(--lx-text-2); }
.lx-dim      { color: var(--lx-text-3); }
.lx-small    { font-size: var(--lx-fs-sm); }
.lx-xs       { font-size: var(--lx-fs-xs); }
.lx-strong   { font-weight: 700; }
.lx-center   { text-align: center; }
.lx-start    { text-align: start; }
.lx-end      { text-align: end; }
.lx-nowrap   { white-space: nowrap; }
.lx-mono     { font-family: var(--lx-font-mono); direction: ltr; }
.lx-caps {
  text-transform: uppercase;
  letter-spacing: var(--lx-track-caps);
  font-size: var(--lx-fs-xs);
  font-weight: var(--lx-fw-bold);
  color: var(--lx-text-3);
}
.lx-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.lx-clamp-2 {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}


/* ── Card ───────────────────────────────────────────────────── */

.lx-card {
  background: var(--lx-surface);
  border: var(--lx-bw-hair) solid var(--lx-border);
  border-radius: var(--lx-r-xl);
  box-shadow: var(--lx-shadow-sm);
  padding: var(--lx-sp-5);
}
.lx-card--flush { padding: 0; overflow: hidden; }
.lx-card--tight { padding: var(--lx-sp-4); }
.lx-card--quiet { box-shadow: none; background: var(--lx-surface-2); }

/* Only interactive cards lift. A static card that moves on hover is a promise
   of a click that never arrives. */
.lx-card--link {
  display: block; color: inherit;
  transition: transform var(--lx-t-fast) var(--lx-ease),
              box-shadow var(--lx-t-fast) var(--lx-ease),
              border-color var(--lx-t-fast) var(--lx-ease);
}
.lx-card--link:hover {
  transform: translateY(-2px);
  box-shadow: var(--lx-shadow-md);
  border-color: var(--lx-border-2);
  text-decoration: none;
}

.lx-card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--lx-sp-3); flex-wrap: wrap;
  margin-block-end: var(--lx-sp-4);
  padding-block-end: var(--lx-sp-3);
  border-block-end: var(--lx-bw-hair) solid var(--lx-border);
}
.lx-card-head--plain { border-block-end: 0; padding-block-end: 0; }
.lx-card-title { margin: 0; font-size: var(--lx-fs-lg); font-weight: 700; }
.lx-card-sub   { margin: var(--lx-sp-1) 0 0; font-size: var(--lx-fs-sm); color: var(--lx-text-2); }


/* ── Buttons ────────────────────────────────────────────────── */

.lx-btn {
  --_bg: var(--lx-primary);
  --_fg: var(--lx-on-primary);
  --_bd: transparent;

  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--lx-sp-2);
  min-height: 44px;                     /* touch target floor, mobile-first */
  padding: var(--lx-sp-2) var(--lx-sp-5);
  border: var(--lx-bw-hair) solid var(--_bd);
  border-radius: var(--lx-r-md);
  background: var(--_bg);
  color: var(--_fg);
  font-weight: 600;
  font-size: var(--lx-fs-md);
  line-height: 1.2;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--lx-t-fast) var(--lx-ease),
              border-color var(--lx-t-fast) var(--lx-ease),
              transform var(--lx-t-fast) var(--lx-ease),
              box-shadow var(--lx-t-fast) var(--lx-ease);
}
.lx-btn:hover  { background: var(--lx-primary-hover); color: var(--_fg); text-decoration: none; }
.lx-btn:active { transform: translateY(1px); }
.lx-btn:focus-visible { box-shadow: 0 0 0 var(--lx-ring-w) var(--lx-primary-ring); }

/* Disabled is a colour change, not an opacity change. Fading the whole button
   fades its label too, and a 50%-opacity label on a pale surface drops under
   the contrast floor — an unavailable control still has to be readable enough
   to explain why it is unavailable. */
.lx-btn[disabled], .lx-btn:disabled, .lx-btn[aria-disabled="true"] {
  --_bg: var(--lx-disabled-bg);
  --_fg: var(--lx-text-disabled);
  --_bd: var(--lx-border);
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}
.lx-btn[disabled]:hover, .lx-btn:disabled:hover, .lx-btn[aria-disabled="true"]:hover {
  background: var(--lx-disabled-bg);
  color: var(--lx-text-disabled);
}

.lx-btn--ghost {
  --_bg: transparent; --_fg: var(--lx-text); --_bd: var(--lx-border-2);
}
.lx-btn--ghost:hover { background: var(--lx-surface-2); color: var(--lx-text); }

.lx-btn--soft {
  --_bg: var(--lx-primary-soft); --_fg: var(--lx-primary); --_bd: transparent;
}
.lx-btn--soft:hover { background: var(--lx-primary-soft); color: var(--lx-primary-hover); }

.lx-btn--danger { --_bg: transparent; --_fg: var(--lx-bad); --_bd: var(--lx-bad-border); }
.lx-btn--danger:hover { background: var(--lx-bad-soft); color: var(--lx-bad); }

.lx-btn--ok { --_bg: var(--lx-ok); --_fg: var(--lx-text-invert); }
.lx-btn--ok:hover { background: var(--lx-ok); filter: brightness(1.06); color: var(--lx-text-invert); }

.lx-btn--plain {
  --_bg: transparent; --_fg: var(--lx-text-2); --_bd: transparent;
  padding-inline: var(--lx-sp-3);
}
.lx-btn--plain:hover { background: var(--lx-surface-2); color: var(--lx-text); }

.lx-btn--sm  { min-height: 34px; padding: var(--lx-sp-1) var(--lx-sp-3); font-size: var(--lx-fs-sm); }
.lx-btn--lg  { min-height: 52px; padding: var(--lx-sp-3) var(--lx-sp-6); font-size: var(--lx-fs-lg); }
.lx-btn--block { width: 100%; }
.lx-btn--icon { padding: var(--lx-sp-2); min-width: 44px; }
.lx-btn--icon.lx-btn--sm { min-width: 34px; }

.lx-btn-group { display: inline-flex; gap: var(--lx-sp-2); flex-wrap: wrap; }


/* ── Forms ──────────────────────────────────────────────────── */

.lx-field { margin-block-end: var(--lx-sp-4); text-align: start; }
.lx-field:last-child { margin-block-end: 0; }

.lx-label {
  display: block;
  margin-block-end: var(--lx-sp-2);
  font-weight: 600;
  font-size: var(--lx-fs-sm);
  color: var(--lx-text-2);
}
.lx-label .lx-req { color: var(--lx-bad); margin-inline-start: 2px; }

.lx-input, .lx-textarea, .lx-select {
  width: 100%;
  min-height: 44px;
  padding: var(--lx-sp-2) var(--lx-sp-3);
  border: var(--lx-bw-hair) solid var(--lx-border-2);
  border-radius: var(--lx-r-md);
  background: var(--lx-surface);
  color: var(--lx-text);
  font-size: var(--lx-fs-md);
  transition: border-color var(--lx-t-fast) var(--lx-ease),
              box-shadow var(--lx-t-fast) var(--lx-ease);
}
.lx-input::placeholder, .lx-textarea::placeholder { color: var(--lx-text-3); }

.lx-input:focus, .lx-textarea:focus, .lx-select:focus {
  outline: none;
  border-color: var(--lx-primary);
  box-shadow: 0 0 0 var(--lx-ring-w) var(--lx-primary-ring);
}
.lx-input:focus-visible, .lx-textarea:focus-visible, .lx-select:focus-visible { outline: none; }

.lx-input[disabled], .lx-textarea[disabled], .lx-select[disabled],
.lx-input[readonly], .lx-textarea[readonly] {
  background: var(--lx-disabled-bg);
  color: var(--lx-text-disabled);
  border-color: var(--lx-border);
  cursor: not-allowed;
}

.lx-textarea { min-height: 120px; resize: vertical; line-height: var(--lx-lh-body); }

/* An input the student types English into. Set by the template, never guessed
   by CSS — the direction has to be on the element for the caret to behave. */
.lx-input--en, .lx-textarea--en { font-family: var(--lx-font-en); }

.lx-select {
  appearance: none;
  padding-inline-end: var(--lx-sp-6);
  /* The chevron is drawn with a border trick rather than a background image so
     it inherits currentColor and needs no asset and no theme variant. */
}
.lx-select-wrap { position: relative; }
.lx-select-wrap::after {
  content: '';
  position: absolute;
  inset-inline-end: var(--lx-sp-3);
  inset-block-start: 50%;
  width: 8px; height: 8px;
  margin-block-start: -6px;
  border-inline-end: var(--lx-bw-thick) solid var(--lx-text-3);
  border-block-end: var(--lx-bw-thick) solid var(--lx-text-3);
  transform: rotate(45deg);
  pointer-events: none;
}

.lx-help { margin-block-start: var(--lx-sp-1); font-size: var(--lx-fs-xs); color: var(--lx-text-3); }
.lx-error-text { margin-block-start: var(--lx-sp-1); font-size: var(--lx-fs-xs); color: var(--lx-bad); }

.lx-input[aria-invalid="true"], .lx-textarea[aria-invalid="true"] {
  border-color: var(--lx-bad);
}

/* Check / radio, styled as a whole clickable row rather than a naked control.
   On a phone a 16px checkbox is not a target; the row is. */
.lx-check {
  display: flex; align-items: flex-start; gap: var(--lx-sp-3);
  padding: var(--lx-sp-3);
  border: var(--lx-bw-hair) solid var(--lx-border);
  border-radius: var(--lx-r-md);
  background: var(--lx-surface);
  cursor: pointer;
  min-height: 44px;
  transition: border-color var(--lx-t-fast) var(--lx-ease),
              background var(--lx-t-fast) var(--lx-ease);
}
.lx-check:hover { border-color: var(--lx-border-2); background: var(--lx-surface-2); }
.lx-check input { margin: 0; flex: none; inline-size: 18px; block-size: 18px; accent-color: var(--lx-primary); }
.lx-check--inline { display: inline-flex; border: 0; padding: 0; background: none; min-height: 0; }
.lx-check--inline:hover { background: none; }

.lx-switch { display: inline-flex; align-items: center; gap: var(--lx-sp-3); cursor: pointer; }
.lx-switch input { inline-size: 18px; block-size: 18px; accent-color: var(--lx-primary); }

.lx-fieldset { border: 0; margin: 0; padding: 0; min-inline-size: 0; }
.lx-fieldset > legend { padding: 0; font-weight: 700; font-size: var(--lx-fs-lg); margin-block-end: var(--lx-sp-3); }


/* ── Alerts ─────────────────────────────────────────────────── */

.lx-alert {
  display: flex; align-items: flex-start; gap: var(--lx-sp-3);
  padding: var(--lx-sp-3) var(--lx-sp-4);
  border: var(--lx-bw-hair) solid var(--lx-border);
  border-radius: var(--lx-r-md);
  background: var(--lx-surface-2);
  font-size: var(--lx-fs-md);
  margin-block-end: var(--lx-sp-4);
}
.lx-alert--ok   { background: var(--lx-ok-soft);   border-color: var(--lx-ok-border);   color: var(--lx-ok-text); }
.lx-alert--bad  { background: var(--lx-bad-soft);  border-color: var(--lx-bad-border);  color: var(--lx-bad-text); }
.lx-alert--warn { background: var(--lx-warn-soft); border-color: var(--lx-warn-border); color: var(--lx-warn-text); }
.lx-alert--info { background: var(--lx-info-soft); border-color: var(--lx-info-border); color: var(--lx-info-text); }
.lx-alert a { color: inherit; text-decoration: underline; }


/* ── Badges and chips ───────────────────────────────────────── */

.lx-badge {
  display: inline-flex; align-items: center; gap: var(--lx-sp-1);
  padding: 2px var(--lx-sp-2);
  border-radius: var(--lx-r-pill);
  background: var(--lx-surface-3);
  color: var(--lx-text-2);
  font-size: var(--lx-fs-xs);
  font-weight: 700;
  line-height: 1.6;
  white-space: nowrap;
}
.lx-badge--ok   { background: var(--lx-ok-soft);   color: var(--lx-ok-text); }
.lx-badge--bad  { background: var(--lx-bad-soft);  color: var(--lx-bad-text); }
.lx-badge--warn { background: var(--lx-warn-soft); color: var(--lx-warn-text); }
.lx-badge--info { background: var(--lx-info-soft); color: var(--lx-info-text); }
.lx-badge--xp   { background: var(--lx-warn-soft); color: var(--lx-warn-text); }

/* The CEFR pill. --_c is set inline from the level's token by the template,
   which is why this needs no seven-way selector. */
.lx-level {
  display: inline-flex; align-items: center; justify-content: center;
  min-inline-size: 2.4em;
  padding: 2px var(--lx-sp-2);
  border-radius: var(--lx-r-sm);
  background: var(--_c, var(--lx-primary));
  color: var(--lx-on-accent);
  font-family: var(--lx-font-en);
  font-size: var(--lx-fs-xs);
  font-weight: 800;
  letter-spacing: var(--lx-track-caps);
  direction: ltr;
}

.lx-chip {
  display: inline-flex; align-items: center; gap: var(--lx-sp-2);
  padding: var(--lx-sp-1) var(--lx-sp-3);
  border: var(--lx-bw-hair) solid var(--lx-border);
  border-radius: var(--lx-r-pill);
  background: var(--lx-surface);
  color: var(--lx-text-2);
  font-size: var(--lx-fs-sm);
  font-weight: 600;
  text-decoration: none;
  min-height: 34px;
  transition: border-color var(--lx-t-fast) var(--lx-ease),
              background var(--lx-t-fast) var(--lx-ease),
              color var(--lx-t-fast) var(--lx-ease);
}
a.lx-chip:hover { border-color: var(--lx-primary); color: var(--lx-primary); text-decoration: none; }
.lx-chip--on {
  background: var(--lx-primary); border-color: var(--lx-primary); color: var(--lx-on-primary);
}
a.lx-chip--on:hover { color: var(--lx-on-primary); }

.lx-chips { display: flex; flex-wrap: wrap; gap: var(--lx-sp-2); }

/* Status dot for draft / published / archived, used in every admin list. */
.lx-dot {
  display: inline-block; inline-size: 8px; block-size: 8px;
  border-radius: 50%; background: var(--lx-text-3);
  margin-inline-end: var(--lx-sp-2); flex: none;
}
.lx-dot--published { background: var(--lx-ok); }
.lx-dot--draft     { background: var(--lx-warn); }
.lx-dot--archived  { background: var(--lx-text-3); }


/* ── Progress ───────────────────────────────────────────────────
   --lx-pct is written inline from PHP as a plain number (0..100). Keeping the
   unit out of the variable is what lets the same value drive a width here and
   a stroke-dashoffset in the SVG ring without a second attribute. */

.lx-bar {
  position: relative;
  block-size: 8px;
  border-radius: var(--lx-r-pill);
  background: var(--lx-surface-3);
  overflow: hidden;
}
.lx-bar > i {
  display: block;
  block-size: 100%;
  inline-size: calc(var(--lx-pct, 0) * 1%);
  border-radius: inherit;
  background: var(--_c, var(--lx-primary));
  transition: inline-size var(--lx-t-slow) var(--lx-ease-out);
}
.lx-bar--lg { block-size: 12px; }
.lx-bar--xs { block-size: 5px; }

.lx-meter { display: grid; gap: var(--lx-sp-2); }
.lx-meter-top {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--lx-sp-3);
  font-size: var(--lx-fs-sm);
}
.lx-meter-name { font-weight: 600; color: var(--lx-text-2); }
.lx-meter-val  { font-weight: 700; font-variant-numeric: tabular-nums; }


/* ── Tabs ───────────────────────────────────────────────────── */

.lx-tabs {
  display: flex; gap: var(--lx-sp-1);
  overflow-x: auto;
  border-block-end: var(--lx-bw-hair) solid var(--lx-border);
  margin-block-end: var(--lx-sp-5);
  scrollbar-width: thin;
}
.lx-tab {
  padding: var(--lx-sp-3) var(--lx-sp-4);
  border: 0;
  border-block-end: var(--lx-bw-thick) solid transparent;
  background: none;
  color: var(--lx-text-2);
  font-weight: 600;
  font-size: var(--lx-fs-md);
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  transition: color var(--lx-t-fast) var(--lx-ease),
              border-color var(--lx-t-fast) var(--lx-ease);
}
.lx-tab:hover { color: var(--lx-text); text-decoration: none; }
.lx-tab--on   { color: var(--lx-primary); border-block-end-color: var(--lx-primary); }

/* The secondary row, emitted by lx_admin_page(). It was referenced from PHP
   and defined in no stylesheet at all, so the admin sub-nav rendered as a run
   of plain links. Lighter than .lx-tabs because it is the second level: no
   bottom rule of its own, and it sits directly under the page title. */
.lx-tabs--sub {
  border-block-end: 0;
  margin-block: calc(var(--lx-sp-2) * -1) var(--lx-sp-5);
  gap: var(--lx-sp-2);
}
.lx-tabs--sub .lx-tab {
  padding: var(--lx-sp-2) var(--lx-sp-3);
  border-block-end: 0;
  border-radius: var(--lx-r-pill);
  font-size: var(--lx-fs-sm);
}
.lx-tabs--sub .lx-tab--on {
  background: var(--lx-primary-soft);
  color: var(--lx-primary);
}


/* ── Tables ─────────────────────────────────────────────────── */

/* Wide tables scroll inside their own box. The page body must never scroll
   sideways — on a phone that turns every vertical swipe into a fight. */
.lx-table-wrap {
  overflow-x: auto;
  border: var(--lx-bw-hair) solid var(--lx-border);
  border-radius: var(--lx-r-lg);
  background: var(--lx-surface);
}

/* The sticky header below only works inside a scroll container that has a
   HEIGHT to stick within. .lx-table-wrap has overflow-x: auto and no height,
   so overflow-y computes to auto and the box grows to fit its content — a
   scroll container that never scrolls, and a `position: sticky` that has
   therefore never once fired since it was written.

   This modifier is the missing half. Put it on any table long enough that you
   lose the header on the way down. */
.lx-table-wrap--tall { max-block-size: 70vh; overflow-y: auto; }

.lx-table { width: 100%; font-size: var(--lx-fs-sm); }
.lx-table th, .lx-table td {
  padding: var(--lx-sp-3);
  text-align: start;
  border-block-end: var(--lx-bw-hair) solid var(--lx-border);
  vertical-align: middle;
}
.lx-table th {
  font-weight: var(--lx-fw-bold);
  color: var(--lx-text-2);
  background: var(--lx-surface-2);
  white-space: nowrap;
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
}
.lx-table tbody tr:last-child td { border-block-end: 0; }
.lx-table tbody tr:hover td { background: var(--lx-surface-2); }
.lx-table .lx-num { text-align: end; font-variant-numeric: tabular-nums; }
.lx-table .lx-acts { display: flex; gap: var(--lx-sp-1); flex-wrap: wrap; align-items: center; }


/* ── Empty / loading / error states ─────────────────────────── */

.lx-empty {
  padding: var(--lx-sp-7) var(--lx-sp-5);
  text-align: center;
  color: var(--lx-text-2);
}
.lx-empty-icon { font-size: 2.5rem; line-height: 1; margin-block-end: var(--lx-sp-3); opacity: .5; }
.lx-empty-title { font-weight: 700; color: var(--lx-text); margin-block-end: var(--lx-sp-2); }

.lx-skeleton {
  background: linear-gradient(90deg, var(--lx-surface-2) 25%, var(--lx-surface-3) 50%, var(--lx-surface-2) 75%);
  background-size: 200% 100%;
  border-radius: var(--lx-r-sm);
  block-size: 1em;
}
@media (prefers-reduced-motion: no-preference) {
  .lx-skeleton { animation: lx-shimmer 1.4s linear infinite; }
  @keyframes lx-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
}

.lx-spinner {
  display: inline-block; inline-size: 1em; block-size: 1em;
  border: var(--lx-bw-thick) solid currentColor; border-block-start-color: transparent;
  border-radius: 50%;
  vertical-align: -.15em;
}
@media (prefers-reduced-motion: no-preference) {
  .lx-spinner { animation: lx-spin .7s linear infinite; }
  @keyframes lx-spin { to { transform: rotate(360deg); } }
}


/* ── Toast ──────────────────────────────────────────────────── */

.lx-toasts {
  position: fixed;
  inset-block-end: var(--lx-sp-5);
  inset-inline-start: 50%;
  transform: translateX(-50%);
  z-index: var(--lx-z-toast);
  display: flex; flex-direction: column; gap: var(--lx-sp-2);
  align-items: center;
  pointer-events: none;
  padding-inline: var(--lx-sp-4);
  max-inline-size: 100%;
}
.lx-toast {
  pointer-events: auto;
  padding: var(--lx-sp-3) var(--lx-sp-4);
  border-radius: var(--lx-r-md);
  background: var(--lx-toast-bg);
  color: var(--lx-toast-fg);
  box-shadow: var(--lx-shadow-lg);
  font-size: var(--lx-fs-sm);
  font-weight: 600;
  max-inline-size: 90vw;
}
.lx-toast--ok  { background: var(--lx-ok);  color: var(--lx-text-invert); }
.lx-toast--bad { background: var(--lx-bad); color: var(--lx-toast-fg); }


/* ── Modal ──────────────────────────────────────────────────────
   A real <dialog>. The polyfill era is over, and ::backdrop plus the built-in
   focus trap and Esc handling are three things not worth reimplementing. */

.lx-modal {
  border: 0;
  padding: 0;
  border-radius: var(--lx-r-xl);
  background: var(--lx-surface);
  color: var(--lx-text);
  box-shadow: var(--lx-shadow-lg);
  max-inline-size: min(560px, calc(100vw - 2rem));
  inline-size: 100%;
}
.lx-modal::backdrop { background: var(--lx-overlay); }
.lx-modal-body { padding: var(--lx-sp-5); }
.lx-modal-foot {
  display: flex; justify-content: flex-end; gap: var(--lx-sp-2);
  padding: var(--lx-sp-4) var(--lx-sp-5);
  border-block-start: var(--lx-bw-hair) solid var(--lx-border);
  background: var(--lx-surface-2);
  border-end-start-radius: var(--lx-r-xl);
  border-end-end-radius: var(--lx-r-xl);
}


/* ── Popover ────────────────────────────────────────────────────
   Used by the click-a-word vocabulary lookup. Positioned by JS; this only
   describes what it looks like. */

.lx-pop {
  position: absolute;
  z-index: var(--lx-z-overlay);
  max-inline-size: 320px;
  padding: var(--lx-sp-4);
  border: var(--lx-bw-hair) solid var(--lx-border);
  border-radius: var(--lx-r-lg);
  background: var(--lx-surface);
  box-shadow: var(--lx-shadow-lg);
  font-size: var(--lx-fs-sm);
}


/* ── Pager ──────────────────────────────────────────────────── */

.lx-pager { display: flex; gap: var(--lx-sp-1); flex-wrap: wrap; justify-content: center; margin-block-start: var(--lx-sp-5); }
.lx-page {
  min-inline-size: 40px; min-block-size: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0 var(--lx-sp-2);
  border: var(--lx-bw-hair) solid var(--lx-border);
  border-radius: var(--lx-r-sm);
  background: var(--lx-surface);
  color: var(--lx-text-2);
  font-size: var(--lx-fs-sm); font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}
.lx-page:hover { border-color: var(--lx-primary); color: var(--lx-primary); text-decoration: none; }
.lx-page--on { background: var(--lx-primary); border-color: var(--lx-primary); color: var(--lx-on-primary); }
.lx-page--on:hover { color: var(--lx-on-primary); }


/* ── Page furniture ─────────────────────────────────────────────
   Everything below is emitted by BOTH shells and therefore belongs here.

   It used to live in app.css (and .lx-chart in practice.css), neither of which
   an admin page loads — inc/lx/admin_ui.php sets $page_css to admin.css alone.
   So lx_admin_page()'s title bar, every lx_admin_stat() tile and every
   <h2 class="lx-section-title"> rendered as unstyled inline text on all 24
   admin screens, and lx_stats_bars()/lx_stats_heat() drew a postage stamp and
   a row of zero-size spans.

   Nothing detected it, and nothing could have: check 06 resolves var(--lx-*)
   against tokens.css and never resolves a class name against anything. A
   class emitted from PHP with no rule anywhere is invisible to every one of
   the thirteen checks. That is worth remembering before inventing a new one.

   base.css loads before app.css and practice.css in every case, so moving
   these changed no learner screen. */


/* Page title bar. */
.lx-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--lx-sp-4); flex-wrap: wrap;
  margin-block-end: var(--lx-sp-5);
}
.lx-head h1, .lx-head h2 { margin: 0; }
.lx-head-sub { margin: var(--lx-sp-1) 0 0; color: var(--lx-text-2); font-size: var(--lx-fs-md); }
.lx-head-acts { display: flex; gap: var(--lx-sp-2); flex-wrap: wrap; }

.lx-section-title {
  display: flex; align-items: center; gap: var(--lx-sp-2);
  margin-block: var(--lx-sp-6) var(--lx-sp-3);
  font-size: var(--lx-fs-lg); font-weight: 700;
}
.lx-section-title:first-child { margin-block-start: 0; }
.lx-section-title .lx-badge { font-weight: 700; }


/* The brand square in both the learner top bar and the admin bar. */
.lx-brand-mark {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 30px; block-size: 30px;
  border-radius: var(--lx-r-md);
  background: var(--lx-primary); color: var(--lx-on-primary);
  font-family: var(--lx-font-en); font-size: var(--lx-fs-md); font-weight: 800;
}
.lx-foot-sep { opacity: .5; }


/* Stat tile.
   The label sits above the number, not below. A column of tiles reads top to
   bottom and the eye should meet the question before the answer. */
.lx-stat {
  display: grid; gap: var(--lx-sp-1);
  padding: var(--lx-sp-4);
  border: var(--lx-bw-hair) solid var(--lx-border);
  border-radius: var(--lx-r-lg);
  background: var(--lx-surface);
}
.lx-stat-l { font-size: var(--lx-fs-xs); font-weight: var(--lx-fw-bold); color: var(--lx-text-3); letter-spacing: var(--lx-track-caps); }
.lx-stat-v { font-size: var(--lx-fs-2xl); font-weight: var(--lx-fw-black); line-height: 1.1; font-variant-numeric: tabular-nums; }
.lx-stat-n { font-size: var(--lx-fs-xs); color: var(--lx-text-3); }
.lx-stat--accent .lx-stat-v { color: var(--lx-primary); }


/* Progress ring. Drawn by lx_ui_ring(); --_s carries the diameter. */
.lx-ring {
  position: relative;
  inline-size: var(--_s, 92px);
  block-size: var(--_s, 92px);
  flex: none;
  display: grid;
  place-items: center;
}
.lx-ring svg { position: absolute; inset: 0; }
.lx-ring-bg { stroke: var(--lx-surface-3); }
.lx-ring-fg { transition: stroke-dashoffset var(--lx-t-slow) var(--lx-ease-out); }
.lx-ring-mid {
  position: relative;
  font-weight: 800;
  font-size: var(--lx-fs-md);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  text-align: center;
}


/* The bar chart from inc/lx/stats.php. preserveAspectRatio="none" on the SVG
   means the viewBox stretches to whatever width it is given, so the height
   here is the only size decision. */
.lx-chart {
  inline-size: 100%;
  block-size: 6rem;
  display: block;
  margin-block-end: var(--lx-sp-4);
}


/* Activity heat strip. The five buckets are relative to the learner's own
   goal, so the colours mean the same thing at 10 minutes a day and at 45. */
.lx-heat {
  display: grid;
  grid-template-columns: repeat(14, 1fr);
  gap: 4px;
  margin-block-end: var(--lx-sp-3);
}
@media (min-width: 640px) { .lx-heat { grid-template-columns: repeat(28, 1fr); } }

.lx-heat-cell {
  aspect-ratio: 1;
  border-radius: var(--lx-r-xs);
  background: var(--lx-heat-0);
}
.lx-heat-cell--1 { background: var(--lx-heat-1); }
.lx-heat-cell--2 { background: var(--lx-heat-2); }
.lx-heat-cell--3 { background: var(--lx-heat-3); }
.lx-heat-cell--4 { background: var(--lx-heat-4); }


/* ── Print ──────────────────────────────────────────────────── */

@media print {
  .lx-skip, .lx-toasts, .lx-tabs, .lx-pager { display: none !important; }
  body { background: var(--lx-parch-0); }
  .lx-card { box-shadow: none; border-color: var(--lx-parch-300); break-inside: avoid; }
}
