/* ═══════════════════════════════════════════════════════════════════════
   Design system v3.

   The identity is one hue held across the whole product — a deep blue that is
   the page's ink, its dark surfaces and its accent at three different depths —
   on a warm cream ground. The restraint is the same as v2's; only the colour
   it is being restrained around has changed.

   Class names are unchanged from v1 on purpose: all exercise engines,
   scorers and dashboards keep working untouched.
   ═══════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   The palette: deep blue → mid blue → mint → cream.

   Four supplied colours, used as a system rather than as four paint buckets.
   Cream is the page and white is the card, so surfaces separate without a
   border doing all the work. Ink is not black but the deep band taken most of
   the way down, which is what stops a blue page reading as two unrelated
   halves. The mid band is the accent, because it is the only one of the four
   that survives on both cream and a dark ground.

   Two shades are adjusted rather than used flat, which the brief allows and
   the contrast requires. The darkest band is deepened for large dark surfaces
   (#16324F): at the supplied #2F5F94 an accent button would sit at 2.4:1
   against its own background, under the 3:1 a control needs. And the mint is
   lightened for washes, so text on a chip clears AA comfortably.

   Everything else in this file resolves through these tokens, so the two
   blocks below are the whole colour system.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* paper + ink */
  --paper: #F0EDE7;            /* the supplied cream, as the page */
  --paper-2: #E7E3DA;
  --paper-3: #DBD6CB;
  --canvas: #16324F;           /* the deep band, taken down for dark surfaces */
  --canvas-2: #1E4062;
  --ink: #14263A;              /* the deep band as text, not black */
  --ink-2: #3E5A75;
  --ink-3: #4E6274;   /* clears AA on all three light grounds: cream, paper-2, white */

  /* the signal */
  --signal: #64A3C1;           /* the mid band — the accent */
  --signal-deep: #2F5F94;      /* the deep band — accessible on cream */
  --signal-ink: #08182A;
  --signal-wash: #DCEBE8;      /* the mint, lightened for washes */

  /* semantic — right and wrong stay green and red: an exam has to signal
     correctness faster than it signals brand */
  --ok: #1A6E54;   /* has to clear AA on --ok-bg, not just on white */
  --ok-bg: #DFF0EA;
  --ok-line: #A5D6C6;
  --bad: #B3261E;
  --bad-bg: #FBEAE8;
  --bad-line: #EFB0AA;
  --warn: #8A5A00;
  --warn-bg: #FDF3DF;
  --warn-line: #E8C98A;

  /* aliases kept so v1 component CSS keeps resolving */
  --bg: var(--paper);
  --bg-alt: var(--paper-2);
  --surface: #FFFFFF;
  --surface-2: var(--paper-2);
  --ink-soft: var(--ink-2);
  --muted: var(--ink-3);
  --line: #D9D5CC;
  --line-soft: #E6E2D9;
  --brand: var(--ink);
  --brand-2: #2A4761;
  --brand-ink: var(--ink);
  --brand-soft: var(--paper-2);
  --accent: var(--signal-deep);
  --accent-2: var(--signal);
  --accent-soft: var(--signal-wash);
  --accent-ink: #24507F;

  --shadow-sm: 0 1px 2px rgba(20, 38, 58, .05);
  --shadow: 0 1px 3px rgba(20, 38, 58, .07), 0 12px 32px -12px rgba(20, 38, 58, .16);
  --shadow-lift: 0 2px 4px rgba(20, 38, 58, .08), 0 24px 60px -20px rgba(20, 38, 58, .26);

  --radius-lg: 20px;
  --radius: 14px;
  --radius-sm: 9px;
  --maxw: 1180px;
  --rail: 64px;

  --font-ar: "Cairo", "Tajawal", "Segoe UI", Tahoma, sans-serif;
  --font-de: "Space Grotesk", "Inter", -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-read: "Source Serif 4", Georgia, "Times New Roman", serif;
  /* Humanist, low-contrast, open apertures — the one to read a German
     passage in. Source Serif's thin strokes read as sharp at 15px on a
     lit screen, which is where an exam text is actually read. */
  --font-soft: "Source Sans 3", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: "Space Grotesk", ui-monospace, "SF Mono", Menlo, monospace;

  --ease: cubic-bezier(.2, .8, .25, 1);
}

/* Dark keeps the same hue and inverts the ladder: the page becomes the deep
   band pushed further down, and the accent lifts to stay legible on it. */
html[data-theme="dark"] {
  --paper: #0D1B27;
  --paper-2: #142534;
  --paper-3: #1C3145;
  --canvas: #081420;
  --canvas-2: #0E1E2C;
  --ink: #E8F0F2;
  --ink-2: #B4C6D0;
  --ink-3: #96AEBC;

  --signal: #7FC0DC;
  --signal-deep: #7FC0DC;
  --signal-ink: #06131F;
  --signal-wash: #123049;

  --ok: #6FD7B5;
  --ok-bg: #0F2A26;
  --ok-line: #2E6659;
  --bad: #F2A49C;
  --bad-bg: #2A1512;
  --bad-line: #74352E;
  --warn: #EFCB86;
  --warn-bg: #2A2211;
  --warn-line: #6A5426;

  --surface: #112130;
  --surface-2: #16293A;
  --line: #22394C;
  --line-soft: #1A2E3E;
  --brand: var(--signal);
  --brand-2: #A5D4E8;
  --brand-ink: var(--signal);
  --brand-soft: #123049;
  --accent: var(--signal);
  --accent-ink: var(--signal);
  --accent-soft: #123049;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
  --shadow: 0 1px 3px rgba(0, 0, 0, .6), 0 16px 40px -16px rgba(0, 0, 0, .8);
  --shadow-lift: 0 24px 60px -20px rgba(0, 0, 0, .9);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ar);
  font-size: 16px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: inherit; text-decoration: none; }
a:hover { text-decoration: none; }

h1, h2, h3, h4 { line-height: 1.2; letter-spacing: -.015em; font-weight: 700; }

.wrap { max-width: var(--maxw); margin-inline: auto; padding-inline: 24px; }
.wrap-narrow { max-width: 860px; margin-inline: auto; padding-inline: 24px; }

/* German islands inside the Arabic shell */
[lang="de"], .de, .de-inline {
  font-family: var(--font-de);
  direction: ltr;
  text-align: start;
  font-feature-settings: "ss01";
}
.prose[lang="de"], .prose.de { font-family: var(--font-read); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; inset-inline-start: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: var(--paper); padding: 12px 18px; border-radius: 0 0 12px 0;
}
.skip-link:focus { inset-inline-start: 0; }

:focus-visible { outline: 2px solid var(--signal-deep); outline-offset: 2px; border-radius: 4px; }

/* ══════════════════════════════════════════════════════════════ header */

.site-header {
  position: sticky; top: 0; z-index: 90;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: saturate(180%) blur(16px);
  border-bottom: 1px solid var(--line);
}
.site-header .wrap {
  display: flex; align-items: center; gap: 8px;
  height: 60px;
}

.brand { display: flex; align-items: center; gap: 9px; flex: none; }
/* The mark: a bowl with two dots, which is ق read in Arabic and ü read in
   German — the same glyph in both scripts, which is the whole business in one
   shape. The dots sit at different heights: B1, then B2.

   Free-standing rather than on a tile, so it takes the page's own ink and
   follows the theme without a second rule. The tiled version of the same mark
   is the favicon, where it needs its own ground to sit on. */
.brand-mark {
  width: 30px; height: 30px; flex: none;
  display: grid; place-items: center;
  color: var(--ink);
}
.brand-mark svg { display: block; width: 100%; height: 100%; }
.bm-bowl { fill: none; stroke: currentColor; stroke-width: 12; stroke-linecap: round; }
.bm-lo { fill: var(--ink-3); }
.bm-hi { fill: var(--signal-deep); }
.brand-name {
  font-weight: 700; font-size: 1.02rem; letter-spacing: -.02em;
}
.brand small { display: none; }

/* level pill — the persistent "where am I" anchor */
.level-pill {
  display: inline-flex; align-items: center; gap: 6px;
  margin-inline-start: 4px;
  border: 1px solid var(--line);
  background: var(--surface);
  border-radius: 999px;
  padding: 5px 6px 5px 12px;
  font-family: var(--font-de); font-size: .82rem; font-weight: 700;
  cursor: pointer; color: var(--ink);
  transition: border-color .15s var(--ease), background .15s var(--ease);
}
.level-pill:hover { border-color: var(--ink-3); }
.level-pill .lp-caret { font-size: .6rem; opacity: .5; }
.level-pill .lp-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--signal-deep);
}

.nav {
  display: flex; align-items: center; gap: 2px;
  margin-inline-start: auto;
}
.nav a {
  position: relative;
  padding: 7px 13px; border-radius: 8px;
  font-size: .9rem; font-weight: 600; color: var(--ink-2);
  white-space: nowrap;
  transition: color .15s var(--ease), background .15s var(--ease);
}
.nav a:hover { color: var(--ink); background: var(--paper-2); }
.nav a.active { color: var(--ink); }
.nav a.active::after {
  content: ''; position: absolute; inset-inline: 13px; bottom: 1px;
  height: 2px; border-radius: 2px; background: var(--signal-deep);
}
html[data-theme="dark"] .nav a.active::after { background: var(--signal); }

.header-actions { display: flex; align-items: center; gap: 6px; flex: none; }

.icon-btn {
  background: transparent; border: 1px solid var(--line); color: var(--ink-2);
  width: 34px; height: 34px; border-radius: 9px; cursor: pointer;
  display: grid; place-items: center; font-size: .9rem; flex: none;
  transition: border-color .15s var(--ease), color .15s var(--ease);
}
.icon-btn:hover { border-color: var(--ink-3); color: var(--ink); }

.cta-mini {
  border: 0; border-radius: 9px; padding: 8px 15px;
  background: var(--ink); color: var(--paper);
  font: inherit; font-size: .86rem; font-weight: 700; cursor: pointer;
  white-space: nowrap;
  transition: transform .12s var(--ease), opacity .15s var(--ease);
}
.cta-mini:hover { transform: translateY(-1px); }
html[data-theme="dark"] .cta-mini { background: var(--signal); color: var(--canvas); }

/* mobile bottom tab bar */
.tabbar { display: none; }

/* 940px, measured rather than picked.
   The desktop header needs 892px of content — brand, six nav items, the level
   pill, two icon buttons and the CTA — plus the wrap's 32px inset, so it stops
   fitting at about 924px. The switch used to sit at 860, which left the band
   861–923px claiming to be a desktop it could not draw: the CTA hung 22px off
   the left edge and every page scrolled sideways. Below 940 the phone shell
   takes over, where the tab bar and the menu reach everything the nav and the
   CTA were carrying.

   Only the five SHELL blocks moved. The other 860px rules in this file —
   pt-stats, the certificate frame, the review track — are about their own
   content and are untouched. */
@media (max-width: 940px) {
  .site-header .wrap { height: 54px; gap: 6px; }
  .nav { display: none; }
  .brand-name { font-size: .96rem; }

  .tabbar {
    display: flex;
    position: fixed; inset-inline: 0; bottom: 0; z-index: 95;
    background: color-mix(in srgb, var(--paper) 94%, transparent);
    backdrop-filter: saturate(180%) blur(16px);
    border-top: 1px solid var(--line);
    padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
  }
  .tabbar a {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 6px 2px; border-radius: 10px;
    font-size: .68rem; font-weight: 700; color: var(--ink-3);
  }
  .tabbar a .tb-i {
    font-size: 1.15rem; line-height: 1;
    height: 22px; display: grid; place-items: center;
  }
  .tabbar a .tb-i svg { width: 22px; height: 22px; display: block; }
  /* One size and one font for all five labels. [lang="de"] otherwise hands the
     German tabs Space Grotesk, whose smaller x-height made Mündlich read as
     the runt of the row next to the Arabic ones. The bar is five words of
     navigation, not German text, so they match each other instead. */
  .tabbar a > span:not(.tb-i) {
    font-family: var(--font-ar); font-size: .68rem; line-height: 1.3;
  }
  .tabbar a.active { color: var(--ink); }
  .tabbar a.active .tb-i { transform: translateY(-1px); }
  /* The paid tab.
     A filled disc made this slot physically bigger than the three beside it,
     which broke the bar's rhythm — four tabs, one of them a button. So the
     glyph stays exactly the size of its neighbours and earns its difference
     from colour and a slow pulse instead: a warm halo breathing behind it,
     drawn on ::after so nothing in the layout moves while it beats. */
  /* The price tab. Same trick as Itqan+ above and for the same reason — the
     glyph keeps its neighbours' size, and stands out on colour and a slow
     pulse rather than on bulk. Warmer and brighter than the rest of the bar,
     because it is the only tab that leads anywhere money changes hands. */
  .tabbar a.is-price { color: var(--gold-ink, #8a5a06); font-weight: 800; }
  .tabbar a.is-price .tb-i { position: relative; color: #f59e0b; }
  .tabbar a.is-price .tb-i::after {
    content: ''; position: absolute; inset: -7px; border-radius: 50%;
    pointer-events: none;
    background: radial-gradient(circle,
      color-mix(in srgb, #f59e0b 60%, transparent) 0%, transparent 70%);
    opacity: 0;
  }
  @media (prefers-reduced-motion: no-preference) {
    .tabbar a.is-price .tb-i { animation: tbPlusBeat 2.6s ease-in-out infinite; }
    .tabbar a.is-price .tb-i::after { animation: tbPlusGlow 2.6s ease-in-out infinite; }
  }
  .tabbar a.is-price.active { color: #b45309; }

  .tabbar a.is-plus { color: var(--gold-ink, #8a5a06); font-weight: 700; }
  .tabbar a.is-plus .tb-i { position: relative; color: var(--sun, #f6a723); }
  .tabbar a.is-plus .tb-i::after {
    content: ''; position: absolute; inset: -7px; border-radius: 50%;
    pointer-events: none;
    background: radial-gradient(circle,
      color-mix(in srgb, var(--sun, #f6a723) 55%, transparent) 0%, transparent 70%);
    opacity: 0;
  }
  @media (prefers-reduced-motion: no-preference) {
    .tabbar a.is-plus .tb-i { animation: tbPlusBeat 2.6s ease-in-out infinite; }
    .tabbar a.is-plus .tb-i::after { animation: tbPlusGlow 2.6s ease-in-out infinite; }
  }
  @keyframes tbPlusBeat {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.18); }
  }
  @keyframes tbPlusGlow {
    0%, 100% { opacity: 0; transform: scale(.7); }
    50% { opacity: 1; transform: scale(1); }
  }
  html[data-theme="dark"] .tabbar a.is-plus { color: var(--sun, #f6a723); }
  body { padding-bottom: 68px; }
}

/* ═══════════════════════════════════════════════════════════════ buttons */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 1px solid transparent; border-radius: 11px;
  padding: 11px 20px;
  font: inherit; font-size: .93rem; font-weight: 700;
  cursor: pointer;
  background: var(--ink); color: var(--paper);
  transition: transform .12s var(--ease), box-shadow .18s var(--ease), background .15s var(--ease);
}
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow); text-decoration: none; }
.btn:active { transform: translateY(0); }
.btn:disabled { opacity: .5; cursor: default; transform: none; }
html[data-theme="dark"] .btn:not(.secondary):not(.ghost):not(.accent) {
  background: var(--signal); color: var(--canvas);
}

.btn.secondary {
  background: transparent; color: var(--ink); border-color: var(--line);
}
.btn.secondary:hover { border-color: var(--ink-3); background: var(--paper-2); }
html[data-theme="dark"] .btn.secondary { background: transparent; color: var(--ink); }

.btn.ghost { background: transparent; color: var(--ink-2); border-color: transparent; padding-inline: 12px; }
.btn.ghost:hover { background: var(--paper-2); box-shadow: none; }

.btn.accent { background: var(--signal); color: var(--signal-ink); }
.btn.accent:hover { background: var(--signal); }

.btn.block { display: flex; width: 100%; }
.btn.lg { padding: 14px 26px; font-size: 1rem; border-radius: 13px; }

.link-btn {
  background: none; border: 0; padding: 0; color: var(--ink);
  font: inherit; font-weight: 700; cursor: pointer;
  border-bottom: 2px solid var(--signal); line-height: 1.2;
}

/* ═══════════════════════════════════════════════════════════ structure */

section.block { padding-block: 72px; }
section.block.alt { background: var(--paper-2); border-block: 1px solid var(--line); }
section.block.tight { padding-block: 44px; }

/* numbered section rail — the signature structural motif */
.block-head { margin-bottom: 34px; max-width: 62ch; }
.block-head .idx {
  display: inline-block;
  font-family: var(--font-de); font-size: .74rem; font-weight: 700;
  letter-spacing: .12em; color: var(--ink-3);
  padding-bottom: 6px; margin-bottom: 12px;
  border-bottom: 2px solid var(--signal);
}
.block-head h2 {
  font-size: clamp(1.6rem, 3.4vw, 2.5rem); margin: 0 0 10px; font-weight: 700;
}
.block-head p { margin: 0; color: var(--ink-2); font-size: 1.02rem; }
.block-head.center { text-align: center; margin-inline: auto; }

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 16px; }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }

/* ═══════════════════════════════════════════════════════════════ cards */

.card {
  display: block; position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px;
  transition: border-color .18s var(--ease), transform .18s var(--ease), box-shadow .18s var(--ease);
}
a.card:hover {
  text-decoration: none; transform: translateY(-2px);
  border-color: var(--ink-3); box-shadow: var(--shadow);
}
.card h3 { margin: 0 0 6px; font-size: 1.02rem; }
/* The exercise card ends on its title now that the section line is gone, so
   the margin under it — and the one under the badge row above it — were
   holding open a gap with nothing left to separate. Fourteen pixels a card,
   on a page that shows forty of them. */
.card h3:last-child { margin-bottom: 0; }
.card-row:has(+ h3:last-child) { margin-bottom: 8px; }
.card p { margin: 0; color: var(--ink-3); font-size: .89rem; }
.card-row { display: flex; align-items: center; gap: 7px; margin-bottom: 12px; flex-wrap: wrap; }

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: .72rem; font-weight: 700; letter-spacing: .01em;
  padding: 3px 9px; border-radius: 6px;
  background: var(--paper-2); color: var(--ink-3);
  border: 1px solid var(--line-soft);
}
.badge.brand { background: var(--ink); color: var(--paper); border-color: transparent; }
html[data-theme="dark"] .badge.brand { background: var(--signal); color: var(--canvas); }
.badge.ok { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-line); }
/* accent-ink, not signal-deep: the latter is only 2.65 on signal-wash. */
.badge.audio { background: var(--signal-wash); color: var(--accent-ink); border-color: transparent; }
/* A Hören exercise with no recording. Said plainly on the card, because
   the absence of an "audio" badge is not something anyone notices. */
.badge.warn { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-line); }
html[data-theme="dark"] .badge.audio { color: var(--signal); }
.badge.de { font-family: var(--font-de); direction: ltr; }
.badge.lock { background: transparent; color: var(--ink-3); }
.badge.free { background: var(--signal-wash); color: var(--signal-deep); border-color: transparent; font-weight: 700; }

/* ══════════════════════════════════════════════════════════ page heads */

.crumbs { font-size: .82rem; color: var(--ink-3); padding-top: 22px; }
.crumbs a:hover { color: var(--ink); }
.crumbs span { margin-inline: 6px; opacity: .4; }

.page-head { padding: 16px 0 28px; margin-bottom: 28px; border-bottom: 1px solid var(--line); }
/* On an exercise page the head is a title and one line of meta, and it was
   carrying 44px of padding plus a 28px margin before the first panel — a
   third of a phone screen spent on a heading you have already read. */
.sp-page .page-head, body:has(.sp-stimulus) .page-head {
  padding: 8px 0 14px; margin-bottom: 16px;
}
body:has(.sp-stimulus) .page-head h1 { margin: 6px 0 4px; }
/* The container stays full width. Capping it at 900 kept the measure right
   but left 190px of page background down each side, which is the thing you
   notice first. The panels run the full width and the passage inside them
   holds its measure instead — margins inside a panel read as a page's
   margins; margins outside one read as the layout having given up. */
@media (min-width: 900px) {
  /* Two columns. A 1130px panel cannot hold a readable measure across its
     full width, and a centred 78ch column left 150px of white down each
     side and a 550px-tall panel for two short paragraphs. Columns use the
     width the panel actually has and halve the height it costs. */
  /* One flow. Columns fitted the panel but split a text that is meant to be
     read straight through, so the measure is capped instead and the block
     sits in the panel's own margins. */
  .sp-stim-text[lang="de"] { max-width: 92ch; margin-inline: auto; }
}
.page-head h1 { margin: 12px 0 8px; font-size: clamp(1.7rem, 3.6vw, 2.4rem); }
.page-head h1[lang="de"] { font-family: var(--font-de); }
.page-head .meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; color: var(--ink-3); font-size: .87rem; }

/* ═════════════════════════════════════════════════════════════ filters */

.filter-bar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 22px; }
.field { flex: 1 1 200px; min-width: 160px; }
.field.grow { flex: 2 1 280px; }
.field input, .field select {
  width: 100%; padding: 10px 14px;
  border-radius: 10px; border: 1px solid var(--line);
  background: var(--surface); color: var(--ink);
  font: inherit; font-size: .91rem;
  transition: border-color .15s var(--ease);
}
.field input:focus, .field select:focus { border-color: var(--ink); outline: none; }
.count-note { color: var(--ink-3); font-size: .85rem; }

/* ══════════════════════════════════════════════════════ exercise layout */

.exercise-layout {
  display: grid;
  /* RTL page: column 1 is on the right, column 2 on the left. Answers take
     column 1 and the German passage column 2, which puts the passage on the
     left. Numbered rather than named, because .exam-wrap below redefines these
     tracks and named lines would not survive that. */
  grid-template-columns: minmax(300px, .9fr) minmax(0, 1.35fr);
  gap: 20px; align-items: start;
}
.exercise-layout > *:first-child  { grid-column: 2; grid-row: 1; }  /* passage — left */
.exercise-layout > *:nth-child(2) { grid-column: 1; grid-row: 1; }  /* answers — right */

@media (max-width: 960px) {
  .exercise-layout { grid-template-columns: 1fr; }
  /* Stacked, the passage comes first — you read before you answer. */
  .exercise-layout > *:first-child  { grid-column: 1; grid-row: 1; }
  .exercise-layout > *:nth-child(2) { grid-column: 1; grid-row: 2; }
}

.panel {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 22px;
}
.panel + .panel { margin-top: 14px; }
.panel h2 { margin: 0 0 14px; font-size: 1.02rem; font-weight: 700; }

/* Sticky answer rail: the lesson text scrolls, the controls stay put. */
.sticky-side {
  position: sticky; top: 76px;
  max-height: calc(100vh - 96px);
  display: flex; flex-direction: column;
}
.sticky-side .option-bank,
.sticky-side .gap-list,
.sticky-side .word-bank { overflow-y: auto; overscroll-behavior: contain; flex: 1; min-height: 0; }
.panel-scroll { max-height: min(76vh, 720px); overflow-y: auto; overscroll-behavior: contain; }

@media (max-width: 960px) {
  .sticky-side { position: static; max-height: none; }
  .panel-scroll { max-height: none; }
}

.prose {
  font-family: var(--font-read); direction: ltr; text-align: left;
  font-size: 1.04rem; line-height: 1.85; color: var(--ink);
}
.prose p { margin: 0 0 1.05em; }
.prose h2, .prose h3, .prose h4, .prose h5 { font-family: var(--font-de); margin: 1.2em 0 .5em; }

.instructions {
  position: relative;
  background: var(--surface); border: 1px solid var(--line);
  border-inline-start: 3px solid var(--signal);
  border-radius: var(--radius-sm);
  padding: 14px 16px; font-size: .92rem; color: var(--ink-2);
  margin-bottom: 18px;
}
.instructions strong { display: block; margin-bottom: 3px; color: var(--ink); font-size: .78rem; letter-spacing: .04em; }

.notice {
  background: var(--warn-bg); color: var(--warn);
  border: 1px solid var(--warn-line); border-radius: var(--radius-sm);
  padding: 12px 14px; font-size: .87rem; margin-bottom: 16px;
}

/* --- matching --- */
.match-item {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 15px; margin-bottom: 12px; background: var(--surface);
  transition: border-color .2s var(--ease), background .2s var(--ease);
}
.match-item[data-state="correct"] { border-color: var(--ok-line); background: var(--ok-bg); }
.match-item[data-state="incorrect"] { border-color: var(--bad-line); background: var(--bad-bg); }
.match-head { display: flex; align-items: center; gap: 10px; margin-bottom: 11px; }
.match-num {
  flex: none; width: 26px; height: 26px; border-radius: 7px;
  background: var(--ink); color: var(--paper);
  font-family: var(--font-de); font-size: .78rem; font-weight: 700;
  display: grid; place-items: center;
}
html[data-theme="dark"] .match-num { background: var(--signal); color: var(--canvas); }

.slot {
  flex: 1; min-height: 42px;
  border: 1.5px dashed var(--line); border-radius: var(--radius-sm);
  padding: 10px 13px; color: var(--ink-3);
  font-family: var(--font-ar); font-size: 1rem;
  cursor: pointer; display: flex; align-items: center;
  background: var(--paper-2);
  transition: border-color .15s var(--ease), background .15s var(--ease);
}
.slot.filled, .slot.correct, .slot.incorrect { font-family: var(--font-de); direction: ltr; text-align: left; }
.slot:hover, .slot.armed { border-color: var(--signal-deep); background: var(--signal-wash); color: var(--ink); }
.slot.filled { border-style: solid; border-color: var(--line); background: var(--surface); color: var(--ink); font-weight: 600; }
.slot.correct { border-color: var(--ok-line); background: var(--ok-bg); color: var(--ok); }
.slot.incorrect { border-color: var(--bad-line); background: var(--bad-bg); color: var(--bad); }

.solution { margin-top: 9px; font-size: .85rem; color: var(--ok); background: var(--ok-bg); border-radius: 8px; padding: 8px 11px; }
.solution.hidden { display: none; }
.solution .de-inline { font-family: var(--font-de); direction: ltr; display: inline-block; }

.option-bank { display: flex; flex-direction: column; gap: 7px; }
.chip {
  text-align: start; border: 1px solid var(--line);
  background: var(--surface); color: var(--ink);
  border-radius: 10px; padding: 10px 13px;
  font-family: var(--font-de); direction: ltr; font-size: .88rem;
  cursor: pointer;
  transition: border-color .13s var(--ease), background .13s var(--ease), transform .1s var(--ease);
}
.chip:hover { border-color: var(--ink-3); }
.chip:active { transform: scale(.985); }
.chip.selected { border-color: var(--ink); background: var(--signal-wash); font-weight: 700; }
html[data-theme="dark"] .chip.selected { background: var(--signal-wash); border-color: var(--signal); }
.chip.used { opacity: .3; pointer-events: none; }

/* --- multiple choice --- */
.q-card {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 15px 16px; margin-bottom: 11px; background: var(--surface);
}
.q-card[data-state="correct"] { border-color: var(--ok-line); background: var(--ok-bg); }
.q-card[data-state="incorrect"] { border-color: var(--bad-line); background: var(--bad-bg); }
.q-card h3 { margin: 0 0 10px; font-size: .95rem; font-family: var(--font-de); direction: ltr; text-align: left; }

.opt {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 9px 11px; border-radius: 9px; cursor: pointer;
  font-size: .91rem; border: 1px solid transparent;
  font-family: var(--font-de); direction: ltr; text-align: left;
  transition: background .13s var(--ease);
}
.opt:hover { background: var(--paper-2); }
.opt input { margin-top: 4px; accent-color: var(--ink); flex: none; }
.opt.is-correct { background: var(--ok-bg); border-color: var(--ok-line); color: var(--ok); font-weight: 700; }
.opt.is-wrong { background: var(--bad-bg); border-color: var(--bad-line); color: var(--bad); }

/* --- cloze --- */
.gap {
  display: inline-block; min-width: 88px; padding: 1px 9px; margin: 0 2px;
  border-bottom: 2px solid var(--signal-deep); border-radius: 5px 5px 0 0;
  background: var(--signal-wash); color: var(--ink);
  font-family: var(--font-de); font-size: .92em; font-weight: 700;
  text-align: center; cursor: pointer;
  transition: background .15s var(--ease);
}
.gap.armed { outline: 2px solid var(--signal-deep); outline-offset: 1px; }
.gap.empty { color: var(--ink-3); font-weight: 600; }
.gap.correct { background: var(--ok-bg); border-color: var(--ok-line); color: var(--ok); }
.gap.incorrect { background: var(--bad-bg); border-color: var(--bad-line); color: var(--bad); }

.word-bank { display: flex; flex-wrap: wrap; gap: 8px; }
.word-bank .chip { padding: 9px 16px; border-radius: 999px; }


.gap-list { display: flex; flex-direction: column; gap: 10px; }
.gap-row { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 12px 13px; background: var(--surface); }
.gap-row[data-state="correct"] { border-color: var(--ok-line); background: var(--ok-bg); }
.gap-row[data-state="incorrect"] { border-color: var(--bad-line); background: var(--bad-bg); }
.gap-row.armed { outline: 2px solid var(--signal-deep); outline-offset: 2px; }
.gap-row .gap-num { font-family: var(--font-de); font-weight: 700; font-size: .76rem; color: var(--ink-3); margin-bottom: 7px; letter-spacing: .04em; }
.gap-row .opts { display: flex; flex-wrap: wrap; gap: 5px; }
.gap-row .chip { border-radius: 999px; padding: 6px 14px; }

/* --- listening --- */
.audio-part { border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; margin-bottom: 16px; background: var(--surface); }
.audio-part h3 { margin: 0 0 12px; font-size: 1rem; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.audio-part h3 .de-inline { font-family: var(--font-de); direction: ltr; }
.audio-part audio { width: 100%; margin-bottom: 14px; }

.tf-table-wrap { overflow-x: auto; }
.tf-table { width: 100%; border-collapse: collapse; font-size: .91rem; }
.tf-table th { font-family: var(--font-de); font-size: .68rem; color: var(--ink-3); font-weight: 700; padding: 7px 8px; text-align: center; white-space: nowrap; letter-spacing: .06em; }
/* Which column is which, before anyone ticks anything. */
.tf-table thead th:first-child { color: var(--ok); }
.tf-table thead th:nth-child(2) { color: var(--bad); }
.tf-table thead th small { font-weight: 700; opacity: .8; }
.tf-table td { padding: 10px 8px; border-top: 1px solid var(--line-soft); vertical-align: middle; }
.tf-table td.pick { text-align: center; width: 64px; }
.tf-table td.pick input { accent-color: var(--ink); width: 17px; height: 17px; }
.tf-table td.stmt { font-family: var(--font-de); direction: ltr; text-align: left; line-height: 1.55; }

/* Every statement is its own card rather than a table line: easier to aim at
   on a phone, and it gives the verdict somewhere to live. */
.tf-table tbody tr {
  transition: background .15s ease;
}
/* No hover wash. On a touch screen it sticks after a tap, and on a
   desktop it greyed the sentence being read. */
.tf-table tr[data-state="correct"] td { background: var(--ok-bg); }
.tf-table tr[data-state="incorrect"] td,
.tf-table tr[data-state="missed"] td { background: var(--bad-bg); }
.tf-table tr[data-state] td.stmt { font-weight: 600; }

/* The ticked box says whether it was the right box; the box that should have
   been ticked is ringed when it wasn't. Same language as the Lesen verdict:
   solid for what you chose, dashed green for the answer you missed. */
.tf-table td.pick { position: relative; }
.tf-table td.pick[data-pick]::after,
.tf-table tr[data-state="incorrect"] td.pick[data-right]:not([data-pick])::after,
.tf-table tr[data-state="missed"] td.pick[data-right]::after {
  content: ''; position: absolute; inset: 4px; border-radius: 9px; pointer-events: none;
}
.tf-table td.pick[data-pick="correct"]::after { background: color-mix(in srgb, var(--ok) 16%, transparent); border: 2px solid var(--ok); }
.tf-table td.pick[data-pick="wrong"]::after { background: color-mix(in srgb, var(--bad) 14%, transparent); border: 2px solid var(--bad); }
.tf-table tr[data-state="incorrect"] td.pick[data-right]:not([data-pick])::after,
.tf-table tr[data-state="missed"] td.pick[data-right]::after { border: 2px dashed var(--ok); }
.tf-table td.pick input { accent-color: var(--ink); position: relative; z-index: 1; }

.tf-answer { font-family: var(--font-de); font-size: .74rem; font-weight: 800; color: var(--ok); white-space: nowrap; }
.tf-answer[data-verdict] {
  display: inline-block; padding: 3px 9px; border-radius: 999px; line-height: 1.5;
  background: var(--ok-bg); border: 1px solid var(--ok-line); color: var(--ok);
}
.tf-answer[data-verdict="wrong"] { background: var(--bad-bg); border-color: var(--bad-line); color: var(--bad); }

/* --- writing --- */
.writing-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: 18px; align-items: start;
}
.writing-grid > *:first-child  { grid-column: 2; grid-row: 1; }  /* task — left */
.writing-grid > *:nth-child(2) { grid-column: 1; grid-row: 1; }  /* editor — right */

@media (max-width: 960px) {
  .writing-grid { grid-template-columns: 1fr; }
  .writing-grid > *:first-child  { grid-column: 1; grid-row: 1; }
  .writing-grid > *:nth-child(2) { grid-column: 1; grid-row: 2; }
}
.material { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 18px; background: var(--paper-2); }
.material h3 { margin-top: 0; font-family: var(--font-de); direction: ltr; text-align: left; }
.task-points { margin: 0; padding-inline-start: 20px; }
.task-points li { margin-bottom: 7px; font-family: var(--font-de); direction: ltr; text-align: left; }

textarea.essay {
  width: 100%; min-height: 380px; resize: vertical;
  padding: 16px 18px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  font-family: var(--font-read); direction: ltr; text-align: left;
  font-size: 1rem; line-height: 1.8;
}
textarea.essay:focus { border-color: var(--ink); outline: none; }

.writing-meter { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; font-size: .86rem; color: var(--ink-3); margin-bottom: 10px; }
.writing-meter b { color: var(--ink); font-family: var(--font-de); }
.writing-meter .reached { color: var(--ok); }

.ai-status { font-size: .82rem; border-radius: var(--radius-sm); padding: 9px 12px; margin-bottom: 12px; border: 1px solid var(--line); background: var(--paper-2); color: var(--ink-3); }
.ai-status.ready { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-line); }
.ai-status.pending { background: var(--signal-wash); color: var(--signal-deep); border-color: transparent; }
html[data-theme="dark"] .ai-status.pending { color: var(--signal); }

/* correction report */
.report { margin-top: 18px; border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; background: var(--surface); }
.report-head { display: flex; align-items: center; gap: 18px; padding: 16px 18px; border-radius: var(--radius-sm); margin-bottom: 20px; }
.report-head.good { background: var(--ok-bg); }
.report-head.mid { background: var(--warn-bg); }
.report-head.poor { background: var(--bad-bg); }
.report-score { display: flex; align-items: baseline; gap: 4px; font-family: var(--font-de); }
.report-score b { font-size: 2.6rem; font-weight: 700; line-height: 1; letter-spacing: -.03em; }
.report-score span { font-size: .88rem; opacity: .65; }
.report-head.good .report-score b { color: var(--ok); }
.report-head.mid .report-score b { color: var(--warn); }
.report-head.poor .report-score b { color: var(--bad); }
.report-verdict { display: flex; flex-direction: column; }
.report-verdict strong { font-size: 1rem; }
.report-verdict span { font-size: .82rem; color: var(--ink-3); }
.report-h { margin: 22px 0 12px; font-family: var(--font-de); font-size: .72rem; font-weight: 700; color: var(--ink-3); letter-spacing: .1em; padding-bottom: 7px; border-bottom: 1px solid var(--line-soft); }
.report-h:first-of-type { margin-top: 0; }

.criteria { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.crit { display: flex; gap: 11px; align-items: flex-start; }
.crit-dot { flex: none; width: 9px; height: 9px; border-radius: 50%; margin-top: 8px; background: var(--ink-3); }
.crit.ok .crit-dot { background: var(--ok); }
.crit.warn .crit-dot { background: var(--signal-deep); }
.crit.bad .crit-dot { background: var(--bad); }
.crit b { font-size: .93rem; }
.crit em { font-style: normal; font-family: var(--font-de); font-size: .72rem; font-weight: 700; padding: 1px 8px; border-radius: 5px; margin-inline-start: 7px; background: var(--paper-2); color: var(--ink-3); }
.crit.ok em { background: var(--ok-bg); color: var(--ok); }
.crit.warn em { background: var(--warn-bg); color: var(--warn); }
.crit.bad em { background: var(--bad-bg); color: var(--bad); }
.crit em.zeroed { background: var(--bad-bg); color: var(--bad); }
.crit p { margin: 3px 0 0; font-size: .88rem; color: var(--ink-2); line-height: 1.65; }
.crit p.de-inline { font-family: var(--font-de); direction: ltr; text-align: left; }

.fixes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.fix { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 11px 13px; background: var(--paper-2); }
.fix-type { display: inline-block; font-family: var(--font-de); font-size: .68rem; font-weight: 700; color: var(--signal-deep); background: var(--signal-wash); padding: 2px 8px; border-radius: 5px; margin-bottom: 7px; letter-spacing: .04em; }
html[data-theme="dark"] .fix-type { color: var(--signal); }
.fix-pair { display: flex; flex-direction: column; gap: 3px; font-family: var(--font-de); }
.fix-pair del { color: var(--bad); text-decoration-thickness: 2px; font-size: .92rem; }
.fix-pair ins { color: var(--ok); text-decoration: none; font-weight: 700; font-size: .92rem; }

.ai-level { font-size: .92rem; margin: 0 0 10px; }
.ai-level b { font-family: var(--font-de); }
.ai-summary { font-family: var(--font-de); direction: ltr; text-align: left; font-size: .9rem; color: var(--ink-2); background: var(--paper-2); border-radius: var(--radius-sm); padding: 11px 13px; margin: 0 0 14px; }
.ai-note { font-size: .87rem; color: var(--ink-3); margin: 0; }
.ai-note.ok-note { color: var(--ok); }

.redemittel { display: grid; gap: 12px; }
.rm-group { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 13px 15px; background: var(--paper-2); }
.rm-group h5 { margin: 0 0 8px; font-size: .8rem; font-weight: 700; color: var(--signal-deep); letter-spacing: .02em; }
html[data-theme="dark"] .rm-group h5 { color: var(--signal); }
.rm-group ul { margin: 0; padding-inline-start: 18px; }
.rm-group li { font-family: var(--font-de); direction: ltr; text-align: left; font-size: .89rem; margin-bottom: 5px; color: var(--ink-2); }

/* ══════════════════════════════════════════════════════ actions/results */

.actions { display: flex; flex-wrap: wrap; gap: 9px; align-items: center; margin-top: 18px; }

.result { margin-top: 16px; border-radius: var(--radius-sm); padding: 14px 17px; font-weight: 700; display: none; }
.result.show { display: block; }
.result.good { background: var(--ok-bg); color: var(--ok); }
.result.mid { background: var(--warn-bg); color: var(--warn); }
.result.poor { background: var(--bad-bg); color: var(--bad); }
.result .score-line { font-size: 1.02rem; }
.result .pct { font-family: var(--font-de); }

/* ───────────────────────────────────────────── the Sprechen walkthrough
   Six steps shown once. The drawings are markup rather than images: an <img>
   is a request that can fail on the connection these learners are on, and a
   screenshot goes stale the first time a button is relabelled — which has
   already happened twice. These are the real labels in the real shapes. */
.st-modal { width: min(440px, 100%); padding: 22px 22px 18px; text-align: start; }
.st-x {
  position: absolute; inset-inline-end: 12px; top: 10px;
  width: 30px; height: 30px; border-radius: 50%;
  border: 0; background: var(--surface-2); color: var(--ink-2);
  font-size: .85rem; cursor: pointer; line-height: 1;
}
.st-x:hover { background: var(--paper-3); }
.st-kicker {
  margin: 0 0 2px; font-size: .72rem; font-weight: 700;
  color: var(--signal-deep); letter-spacing: .02em;
}
.st-title { margin: 0 0 14px; font-size: 1.06rem; line-height: 1.45; }
.st-p { margin: 0; font-size: .88rem; line-height: 1.8; color: var(--ink-2); }
.st-p b { color: var(--ink); }

/* the little stage each step draws into */
.st-art {
  display: grid; gap: 8px; justify-items: center;
  margin: 0 0 14px; padding: 16px 12px;
  border-radius: var(--radius);
  background: var(--signal-wash);
  border: 1px solid color-mix(in srgb, var(--signal) 32%, transparent);
  min-height: 104px; align-content: center;
}

.st-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 16px; border-radius: 999px;
  font-size: .82rem; font-weight: 700; line-height: 1;
  background: var(--surface); color: var(--ink); border: 1px solid var(--line);
}
.st-primary { background: var(--accent); color: #fff; border-color: transparent; }
.st-micbtn { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-line); }
.st-stop { background: var(--bad-bg); color: var(--bad); border-color: var(--bad-line); }
.st-btnrow, .st-two { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; justify-content: center; }
.st-then { font-size: .74rem; color: var(--ink-3); font-weight: 700; }

/* step 1 — two turns of a conversation */
.st-chat { display: grid; gap: 7px; width: 100%; max-width: 260px; }
.st-bubble {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 11px; border-radius: 12px; font-size: .78rem;
  background: var(--surface); border: 1px solid var(--line);
}
.st-ex { justify-content: flex-start; }
.st-you { justify-content: flex-end; background: var(--accent-soft); border-color: transparent; }
.st-who { font-size: .66rem; color: var(--ink-3); font-weight: 700; flex: none; }
.st-mic { font-size: 1rem; }

/* step 2 — three things to do first */
.st-rows { display: grid; gap: 6px; width: 100%; max-width: 250px; }
.st-row {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; border-radius: 10px;
  background: var(--surface); font-size: .76rem; color: var(--ink);
}
.st-n {
  flex: none; display: grid; place-items: center;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--signal-wash); color: var(--signal-deep);
  font-size: .66rem; font-weight: 700;
}

/* step 3 — the browser's own permission bar, which is what they will see */
.st-perm {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  justify-content: center;
  padding: 8px 11px; border-radius: 10px;
  background: var(--surface); border: 1px solid var(--line);
  font-size: .72rem; color: var(--ink-2); max-width: 280px;
}
.st-allow {
  padding: 4px 10px; border-radius: 999px;
  background: var(--accent); color: #fff; font-weight: 700; font-size: .7rem;
}

/* step 4 — words appearing as they speak */
.st-live {
  width: 100%; max-width: 290px;
  padding: 8px 11px; border-radius: 10px;
  background: var(--surface); border: 1px dashed var(--line);
  font-size: .74rem; color: var(--ink); text-align: left;
}
.st-live-cap {
  display: block; font-size: .64rem; color: var(--ink-3);
  font-weight: 700; text-align: right; margin-bottom: 2px;
}

/* step 6 — a score and four criteria */
.st-score { display: flex; align-items: baseline; gap: 4px; color: var(--ok); }
.st-score b { font-size: 1.5rem; line-height: 1; }
.st-score span { font-size: .8rem; color: var(--ink-3); }
.st-crit { display: flex; gap: 5px; }
.st-crit span {
  width: 26px; height: 5px; border-radius: 3px;
  background: color-mix(in srgb, var(--ok) 55%, transparent);
}
.st-crit span:last-child { background: color-mix(in srgb, var(--warn) 60%, transparent); }

/* footer */
.st-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 18px; flex-wrap: wrap;
}
.st-dots { display: flex; gap: 5px; }
.st-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--line); transition: background .15s ease, width .15s ease;
}
.st-dot.on { background: var(--accent); width: 16px; border-radius: 3px; }
.st-nav { display: flex; gap: 8px; margin-inline-start: auto; }
.st-b { font-size: .82rem; padding: 8px 15px; white-space: nowrap; }

/* The way back into the walkthrough.

   Four tries to get the placement right, and then a fifth problem: it was
   legible and nobody pressed it. A flat pill sitting under a flat B2 badge
   reads as another badge — this site's own label shape — and labels are not
   things you press.

   So it takes on the cues a badge never has: a play mark, a shadow to lift it
   off the page, motion under the cursor, and a slow breath of the ring around
   it so the eye lands there once without anything blinking.

   Still inline inside the <h1>. Nothing is lifted out of flow, so it is laid
   out on the title's line rather than placed over anything. */
.sp-tour-tag {
  display: inline-flex; align-items: center; gap: 7px; vertical-align: middle;
  /* A physical margin, deliberately.

     This button carries dir="rtl" for its Arabic label, but it sits inside an
     <h1 lang="de"> that computes direction: ltr. A logical property resolves
     against the element's OWN direction, so margin-inline-start landed on the
     right — the far side from the title — and the gap never moved however
     large the number got. The side that matters here is the heading's, and
     the heading is left-to-right. */
  margin-left: 36px;
  padding: 6px 14px 6px 11px; border: 0; border-radius: 999px;
  background: var(--accent); color: #fff;
  font: 700 .74rem/1.2 var(--font-ar);
  white-space: nowrap; cursor: pointer;
  box-shadow: 0 2px 6px -1px color-mix(in srgb, var(--accent) 55%, transparent),
              0 0 0 0 color-mix(in srgb, var(--accent) 38%, transparent);
  transition: transform .15s var(--ease), box-shadow .2s var(--ease), background .15s ease;
  animation: sp-tour-breathe 2.6s ease-out 3;
}
/* Three breaths, then still. Long enough to catch an eye arriving at the
   title, short enough that it is not a thing blinking on the page while
   somebody is trying to read German. */
@keyframes sp-tour-breathe {
  0%, 100% { box-shadow: 0 2px 6px -1px color-mix(in srgb, var(--accent) 55%, transparent),
                         0 0 0 0 color-mix(in srgb, var(--accent) 38%, transparent); }
  45%      { box-shadow: 0 2px 6px -1px color-mix(in srgb, var(--accent) 55%, transparent),
                         0 0 0 7px color-mix(in srgb, var(--accent) 0%, transparent); }
}
.sp-tour-tag:hover {
  background: var(--signal-deep);
  transform: translateY(-1px);
  box-shadow: 0 5px 14px -3px color-mix(in srgb, var(--accent) 65%, transparent);
}
.sp-tour-tag:active { transform: translateY(0); }
.sp-tour-tag:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* A play triangle drawn from a border, so it needs no glyph and cannot be
   flipped by the surrounding right-to-left text. */
.sp-tour-play {
  flex: none; display: grid; place-items: center;
  width: 16px; height: 16px; border-radius: 50%;
  background: rgba(255, 255, 255, .22);
}
.sp-tour-play::before {
  content: ''; display: block;
  border-style: solid;
  border-width: 4px 0 4px 6px;
  border-color: transparent transparent transparent #fff;
  margin-inline-start: 2px;
}

@media (max-width: 560px) {
  /* Wrapped onto its own line it starts at the heading's left edge; an
     indent here would read as a stray one. */
  .sp-tour-tag { margin-left: 0; padding: 5px 12px 5px 9px; font-size: .7rem; gap: 6px; }
  .sp-tour-play { width: 15px; height: 15px; }
}

@media (prefers-reduced-motion: reduce) {
  .sp-tour-tag { animation: none; transition: background .15s ease; }
  .sp-tour-tag:hover { transform: none; }
}

@media (max-width: 560px) {
  .st-modal { padding: 20px 16px 16px; }
  .st-title { font-size: 1rem; }
  .st-p { font-size: .84rem; line-height: 1.75; }
  .st-art { padding: 14px 10px; min-height: 96px; }
  .st-foot { margin-top: 15px; }
  .st-b { font-size: .8rem; padding: 8px 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .st-dot { transition: none; }
}

/* ────────────────────────────────────────────────────────── the link counter
   Two numbers, opened from a URL by somebody with no account. Big because
   there is nothing else on the page to compete with them. */
.rl-wrap { display: grid; gap: 14px; margin: 18px 0 40px; }
.rl-top h1 { margin: 0; font-size: 1.3rem; }
.rl-sub { margin: 4px 0 0; font-size: .82rem; color: var(--ink-3); }
.rl-sub code {
  font-family: var(--font-mono); background: var(--surface-2);
  padding: 1px 7px; border-radius: 6px; color: var(--ink);
}

.rl-big { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.rl-n {
  display: grid; gap: 4px; justify-items: center; text-align: center;
  padding: 26px 14px; border-radius: var(--radius-lg);
  background: var(--surface); border: 1px solid var(--line);
}
.rl-n b { font-size: 2.6rem; line-height: 1; font-variant-numeric: tabular-nums; }
.rl-n span { font-size: .82rem; color: var(--ink-2); font-weight: 700; }
.rl-paid {
  background: var(--ok-bg); border-color: var(--ok-line);
}
.rl-paid b, .rl-paid span { color: var(--ok); }

/* The funnel: three stages of the same measure, so one bar each, scaled
   against the widest. Recessive track, thin bar, 4px rounded end anchored to
   the start edge. The count sits in ink beside the bar rather than inside it,
   because a number on a 6%-wide bar has nowhere to live. */
.rl-funnel {
  padding: 18px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--line);
  display: grid; gap: 14px;
}
.rl-h { margin: 0; font-size: .82rem; color: var(--ink-3); font-weight: 700; }
.rl-f-row { display: grid; gap: 6px; }
.rl-f-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.rl-f-k { font-size: .84rem; font-weight: 700; color: var(--ink); }
.rl-f-step { font-size: .72rem; color: var(--ink-3); margin-inline-start: auto; }
.rl-f-track {
  background: var(--surface-2); border-radius: 999px; padding: 3px;
}
.rl-f-bar {
  height: 16px; border-radius: 4px; flex: none;
  background: var(--accent); min-width: 4px;
  transition: width .35s var(--ease);
}
.rl-f-ok { background: var(--ok); }
.rl-f-v { font-size: .9rem; font-variant-numeric: tabular-nums; color: var(--ink); }

@media (prefers-reduced-motion: reduce) { .rl-f-bar { transition: none; } }

.rl-strip {
  display: flex; gap: 18px; flex-wrap: wrap; justify-content: center;
  padding: 11px 14px; border-radius: var(--radius);
  background: var(--surface-2); font-size: .82rem; color: var(--ink-2);
}
.rl-strip b { color: var(--ink); font-variant-numeric: tabular-nums; }
.rl-foot { margin: 0; font-size: .74rem; color: var(--ink-3); text-align: center; line-height: 1.7; }

.rl-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 20px;
}
.rl-mid { text-align: center; }
.rl-note { margin: 0; font-size: .85rem; color: var(--ink-2); line-height: 1.7; }
.rl-own h2 { margin: 0 0 6px; font-size: 1rem; }
.rl-form { display: flex; gap: 8px; margin: 12px 0 0; flex-wrap: wrap; }
.rl-form input {
  flex: 1 1 220px; min-width: 0; padding: 9px 11px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink); font: inherit; font-size: .85rem;
}
.rl-pair { display: grid; gap: 3px; margin: 12px 0 0; }
.rl-pair span { font-size: .74rem; font-weight: 700; color: var(--ink-3); }
.rl-pair code {
  font-family: var(--font-mono); font-size: .78rem; word-break: break-all;
  background: var(--surface-2); padding: 8px 10px; border-radius: var(--radius-sm);
}
.rl-bad {
  margin: 12px 0 0; padding: 9px 11px; border-radius: var(--radius-sm);
  background: var(--bad-bg); color: var(--bad); font-size: .82rem;
}

@media (max-width: 560px) {
  .rl-big { grid-template-columns: 1fr; gap: 10px; }
  .rl-n { padding: 22px 12px; }
  .rl-n b { font-size: 2.2rem; }
}

/* ───────────────────────────────────────────────────── the partner dashboard
   A page whose only job is to be checkable. The totals in the footer are the
   column above them added up, so a partner can verify the headline by hand —
   which is worth more than any assurance the copy could make. */
.pt-wrap { display: grid; gap: 14px; margin: 8px 0 32px; }
.pt-head { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.pt-head h1 { margin: 0; font-size: 1.4rem; }
.pt-sub { margin: 3px 0 0; font-size: .85rem; color: var(--ink-3); }
.pt-sub code, .pt-linkbox code {
  font-family: var(--font-mono); background: var(--surface-2);
  padding: 1px 7px; border-radius: 6px; color: var(--ink);
}
.pt-head .btn { margin-inline-start: auto; }

.pt-linkbox {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 11px 13px; border-radius: var(--radius);
  background: var(--signal-wash);
  border: 1px solid color-mix(in srgb, var(--signal) 40%, transparent);
}
.pt-linkcap { font-size: .76rem; font-weight: 700; color: var(--signal-deep); flex: none; }
.pt-linkbox code { font-size: .8rem; word-break: break-all; }

.pt-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.pt-stat {
  display: grid; gap: 2px; padding: 13px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.pt-stat b { font-size: 1.5rem; line-height: 1.1; color: var(--ink); }
.pt-stat span { font-size: .8rem; color: var(--ink-2); font-weight: 700; }
.pt-stat small { font-size: .72rem; color: var(--ink-3); }

.pt-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 4px;
}
.pt-mid { padding: 28px; text-align: center; display: grid; gap: 12px; justify-items: center; }
.pt-note { margin: 0; font-size: .88rem; color: var(--ink-2); }

/* Wide tables scroll inside their own box; the page never scrolls sideways. */
.pt-tablewrap { overflow-x: auto; }
.pt-table { width: 100%; border-collapse: collapse; font-size: .84rem; min-width: 560px; }
.pt-table th, .pt-table td {
  padding: 10px 11px; text-align: start;
  border-bottom: 1px solid var(--line-soft);
}
.pt-table th { font-size: .74rem; color: var(--ink-3); font-weight: 700; }
.pt-table tbody tr:last-child td { border-bottom: 0; }
.pt-table .pt-num { text-align: end; font-variant-numeric: tabular-nums; }
.pt-on td { background: color-mix(in srgb, var(--ok-bg) 55%, transparent); }
.pt-comm { font-weight: 700; color: var(--ok); }
.pt-dash { color: var(--ink-3); }
.pt-id { display: grid; gap: 1px; }
.pt-id code { font-family: var(--font-mono); font-size: .76rem; color: var(--ink-3); }
.pt-id span { font-size: .78rem; color: var(--ink); }
.pt-renew {
  display: block; font-size: .68rem; font-weight: 400; color: var(--ink-3);
}
.pt-empty { text-align: center; color: var(--ink-3); padding: 26px 10px; }
.pt-table tfoot td {
  border-top: 2px solid var(--line); font-weight: 700;
  background: var(--surface-2);
}

/* The rules, on the page rather than in a message somewhere. A partner who
   can read why a row did or did not earn does not have to ask. */
.pt-rules {
  padding: 14px 16px; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid var(--line);
}
.pt-rules b { font-size: .86rem; }
.pt-rules ul { margin: 8px 0 0; padding-inline-start: 18px; display: grid; gap: 6px; }
.pt-rules li { font-size: .8rem; line-height: 1.7; color: var(--ink-2); }

@media (max-width: 860px) {
  .pt-stats { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .pt-head h1 { font-size: 1.2rem; }
  .pt-head .btn { margin-inline-start: 0; }
  .pt-stat b { font-size: 1.25rem; }
  .pt-table { font-size: .8rem; }
  .pt-table th, .pt-table td { padding: 9px 8px; }
}

/* ──────────────────────────────────────────── the strip between neighbours
   Sticky under the header, never at the bottom: on a phone the bottom belongs
   to the tab bar, and a second fixed bar there would cover it or shove the
   page's own controls up.

   Thin on purpose. It is chrome, so it stays under 40px and carries only what
   chrome should — where you are, and the two ways out. Naming the destination
   is the bottom pager's job; a name is what makes a bar too tall to live on
   screen. */
/*
 * Opaque, and a different colour from the page.
 *
 * It used to be 92% --paper over a --paper page, which is the same cream
 * painted on itself: no edge, nothing to see, and the missing 8% let whatever
 * scrolled underneath show through the bar. Where backdrop-filter is
 * unsupported or switched off there was not even a blur to disguise it, so
 * the instructions read straight through the buttons.
 *
 * --surface is the colour every card on the site already uses, so the strip
 * now reads as a band sitting on the page rather than a hole in it. Fully
 * opaque, with a real border and a shadow under it to say that content passes
 * beneath. The blur stays as an enhancement for the browsers that have it.
 */
.quicknav {
  position: sticky; top: 60px; z-index: 80;
  background: var(--surface);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--line-soft), 0 6px 14px -10px rgba(0, 0, 0, .35);
}
.qn-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  height: 38px;
}
/* Both words carry full weight. The first version made "السابق" a muted
   grey pill, which reads as disabled rather than as secondary — and a way out
   that looks switched off is a way out nobody takes. Going back is a smaller
   decision than going on, so it is quieter in colour, not in contrast. */
.qn-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 14px; border-radius: 999px;
  font-size: .84rem; font-weight: 800; line-height: 1;
  text-decoration: none; white-space: nowrap;
  border: 1px solid transparent;
}
.qn-prev {
  background: var(--surface); color: var(--accent-ink);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.qn-next { background: var(--accent); color: #fff; }
.qn-prev:hover { background: var(--accent-soft); }
.qn-next:hover { filter: brightness(1.06); }
/* An end of the list is a fact about the list, not a button that failed. */
.qn-btn[aria-disabled] { opacity: .38; pointer-events: none; }

.qn-count {
  font-size: .8rem; color: var(--ink-2);
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
}
.qn-count b { color: var(--ink); font-weight: 700; }

@media (max-width: 940px) {
  /* The header shortens here, so the strip follows it up. */
  .quicknav { top: 54px; }
  .qn-row { height: 36px; gap: 8px; }
  .qn-btn { padding: 6px 11px; font-size: .8rem; }
  .qn-count { font-size: .74rem; }
}

/* The side column parks below the strip instead of sliding under it. */
body[data-pager] .sticky-side { top: 106px; }
@media (max-width: 960px) {
  body[data-pager] .sticky-side { top: auto; }
}

/* Printing a page should not print the way out of it. */
@media print { .quicknav { display: none !important; } }

.pager { display: flex; justify-content: space-between; gap: 12px; margin: 32px 0 10px; flex-wrap: wrap; }
.pager .btn { font-size: .86rem; }

.empty-state { text-align: center; padding: 52px 20px; color: var(--ink-3); }

/* ══════════════════════════════════════════════════════════════ footer */

.site-footer {
  border-top: 1px solid var(--line);
  margin-top: 64px; padding: 44px 0 52px;
  color: var(--ink-3); font-size: .86rem;
  background: var(--paper-2);
}
.site-footer p { margin: 0 0 8px; max-width: 60ch; }
.site-footer strong { color: var(--ink); }
.footer-links { display: flex; flex-wrap: wrap; gap: 16px; }
.footer-links a:hover { color: var(--ink); }

@media (max-width: 700px) {
  .wrap, .wrap-narrow { padding-inline: 18px; }
  section.block { padding-block: 48px; }
  .panel { padding: 16px; }
  .page-head { padding: 12px 0 20px; margin-bottom: 20px; }
}

@media print {
  .site-header, .site-footer, .actions, .nav, .pager, .tabbar, .ai-status { display: none !important; }
  body { background: #fff; padding-bottom: 0; }
  .panel { border-color: #ccc; }
}

/* ════════════════════════════════════════════════════ v2 components */

/* ---------------------------------------------------------- dark hero */

.hero-dark {
  background: var(--canvas);
  color: #EAF2F4;
  /* The zero used to be right: .hero-stats filled the bottom and was meant to
     sit flush against the section edge. With the stats gone the hero's own
     content ran into the boundary — the certificate's white sheet ended
     exactly where the dark band did, and `overflow: hidden` clipped the glow
     along that same line. */
  padding-block: 72px 76px;
  position: relative;
  overflow: hidden;
}
.hero-dark::before {
  content: '';
  position: absolute; inset: 0;
  background:
    radial-gradient(900px 420px at 82% -10%, rgba(100, 163, 193, .16), transparent 62%),
    radial-gradient(600px 400px at 10% 110%, rgba(100, 163, 193, .07), transparent 60%);
  pointer-events: none;
}
.hero-dark .wrap { position: relative; }

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(320px, .95fr);
  gap: 48px;
  align-items: center;
}
@media (max-width: 940px) { .hero-grid { grid-template-columns: 1fr; gap: 32px; } }
/* No certificate on disk yet: one column, so the hero does not reserve an
   empty half and read as something that failed to load. */
.hero-grid.solo {
  /* One real column. `minmax(0, 1fr)` alone collapsed to 0 here because the
     empty second column still participated in the track sizing. */
  display: block; max-width: 760px;
}
.hero-grid.solo .hero-demo { display: none; }

.hero-eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: .8rem; font-weight: 700;
  color: rgba(234, 242, 244, .75);
  border: 1px solid rgba(234, 242, 244, .18);
  padding: 6px 14px; border-radius: 999px; margin-bottom: 22px;
}
.he-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--signal); }

.hero-dark h1 {
  margin: 0 0 20px;
  /* Sized for the headline it holds. It has grown again: the question is now
     followed by a full sentence naming what the platform is, so the type comes
     down another step and the second line is set smaller still. At the old
     size this ran to seven lines on a phone and put the CTA well under the
     fold — the headline would have been read by nobody who did not scroll. */
  font-size: clamp(1.35rem, 2.5vw, 1.95rem);
  font-weight: 700; line-height: 1.16; letter-spacing: -.03em;
}
/* The claim under the question is support, not a second headline: coloured to
   carry the eye, a step down in size and weight so the question still leads. */
.hero-dark h1 .hl {
  color: var(--signal);
  display: inline-block; margin-top: .32em;
  font-size: .76em; font-weight: 600; line-height: 1.4; letter-spacing: -.01em;
}

.hero-lede {
  margin: 0 0 30px; max-width: 46ch;
  font-size: clamp(1rem, 1.6vw, 1.16rem);
  color: rgba(234, 242, 244, .74); line-height: 1.75;
}
.hero-cta { display: flex; flex-wrap: wrap; gap: 11px; margin-bottom: 14px; }
.btn.on-dark { color: #EAF2F4; border-color: rgba(234, 242, 244, .28); }
.btn.on-dark:hover { background: rgba(234, 242, 244, .08); border-color: rgba(234, 242, 244, .66); }
.hero-fine { margin: 0; font-size: .84rem; color: rgba(234, 242, 244, .62); }


/* --------------------------------------------------------- try-it demo */

/* ───────────────────────────────────────── the certificate on the homepage
   Where the sample question used to be. A real telc result answers the
   question a visitor is actually asking, so it takes the weight the demo had:
   the largest object in the hero, lit rather than framed.

   Two layers. A blurred colour wash sits behind the sheet and bleeds past its
   edges — that is the shadow in the background. The sheet carries a thin
   luminous rule and a matching outer bloom, which is what reads as neon
   against the dark hero without turning the page into a toy. */
.cert {
  position: relative;
  margin: 0; display: grid; justify-items: center;
  isolation: isolate;
}
.cert-glow {
  position: absolute; z-index: -1;
  inset: -12% -8% -6%;
  border-radius: 50%;
  background:
    radial-gradient(48% 42% at 30% 22%, color-mix(in srgb, var(--signal) 62%, transparent), transparent 70%),
    radial-gradient(52% 46% at 74% 72%, color-mix(in srgb, #7de3c8 52%, transparent), transparent 72%);
  filter: blur(46px);
  opacity: .58;
  pointer-events: none;
  /* Breathing, not blinking. Opacity and transform are the two things a
     browser can animate without repainting, so the certificate itself is
     never redrawn — only the light behind it moves. Animating the rim's
     box-shadow instead would repaint the sheet sixty times a second for an
     effect nobody would describe differently. */
  animation: cert-breathe 4.2s ease-in-out infinite;
  will-change: opacity, transform;
}

@keyframes cert-breathe {
  0%, 100% { opacity: .40; transform: scale(1); }
  50%      { opacity: .95; transform: scale(1.10); }
}

/* The visible one. A ring on the sheet's edge that expands and fades, twice
   over per cycle so the motion never fully stops. Opacity and transform only,
   so the certificate is never repainted — the ring is its own layer. */
.cert-ring {
  position: absolute; inset: 0;
  border-radius: 14px;
  border: 1.5px solid rgba(138, 246, 230, .95);
  box-shadow: 0 0 16px 1px rgba(122, 240, 226, .65);
  pointer-events: none;
  opacity: 0;
  animation: cert-ping 2.8s cubic-bezier(.22, .61, .36, 1) infinite;
}
.cert-ring.late { animation-delay: 1.4s; }

@keyframes cert-ping {
  0%   { opacity: .9; transform: scale(1); }
  70%  { opacity: 0;  transform: scale(1.085); }
  100% { opacity: 0;  transform: scale(1.085); }
}

/* Motion nobody asked for is motion somebody has to sit through. */
@media (prefers-reduced-motion: reduce) {
  .cert-glow { animation: none; opacity: .58; transform: none; }
  .cert-ring { animation: none; opacity: 0; }
}
.cert-frame {
  position: relative;
  width: 100%; max-width: 330px;
  line-height: 0;
}
.cert-img {
  display: block; width: 100%; height: auto;
  border-radius: 14px;
  background: #fff;
  /* The neon is three rings, not one: a hard bright edge on the sheet, a
     tight bloom just outside it, and a wide soft one past that. A single
     large shadow reads as haze; the hard edge is what makes it a rim. */
  /* Dimmed. The three rings stay — a hard edge, a tight bloom, a wide soft
     one — because that structure is what reads as a rim rather than haze.
     Only the intensities come down, so it still lifts the sheet off the dark
     hero without being the brightest thing on the page. */
  border: 1px solid color-mix(in srgb, #b8f5ea 50%, transparent);
  box-shadow:
    0 0 0 1px rgba(122, 240, 226, .34),
    0 0 8px 0 rgba(122, 240, 226, .26),
    0 0 22px -4px color-mix(in srgb, var(--signal) 42%, transparent),
    0 0 54px -8px color-mix(in srgb, #6fd7d7 30%, transparent),
    0 26px 60px -24px rgba(0, 0, 0, .78);
}
@media (max-width: 860px) {
  /* The frame owns the width now; the rings are pinned to it, so capping
     the image instead would leave them 80px wider than the sheet. */
  .cert-frame { max-width: 250px; }
  .cert-glow { filter: blur(34px); opacity: .7; }
}

.demo {
  background: rgba(234, 242, 244, .05);
  border: 1px solid rgba(234, 242, 244, .16);
  border-radius: var(--radius-lg);
  padding: 22px;
  backdrop-filter: blur(8px);
}
.demo-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 16px; }
.demo-kicker {
  font-size: .74rem; font-weight: 700; color: var(--signal);
  letter-spacing: .04em;
}
.demo .badge { background: rgba(234, 242, 244, .1); color: rgba(234, 242, 244, .7); border-color: transparent; }
.demo-q {
  font-family: var(--font-read); font-size: 1.06rem; line-height: 1.8;
  margin: 0 0 18px; color: #EAF2F4;
}
.demo-opts { display: grid; gap: 8px; }
.demo-opt {
  border: 1px solid rgba(234, 242, 244, .2); background: transparent;
  color: #EAF2F4; border-radius: 10px; padding: 12px 15px;
  font-family: var(--font-de); font-size: .96rem; font-weight: 500;
  text-align: left; cursor: pointer;
  transition: border-color .14s var(--ease), background .14s var(--ease), transform .1s var(--ease);
}
.demo-opt:hover:not(:disabled) { border-color: var(--signal); background: rgba(100, 163, 193, .08); }
.demo-opt:active:not(:disabled) { transform: scale(.99); }
.demo-opt.right { border-color: var(--signal); background: rgba(100, 163, 193, .16); color: var(--signal); font-weight: 700; }
.demo-opt.wrong { border-color: rgba(242, 160, 150, .6); background: rgba(242, 160, 150, .12); color: #F2A49C; }

.demo-fb { margin-top: 16px; padding: 15px; border-radius: 12px; }
.demo-fb.good { background: rgba(100, 163, 193, .12); }
.demo-fb.bad { background: rgba(242, 160, 150, .1); }
.demo-fb b { display: block; margin-bottom: 8px; color: #EAF2F4; }
.demo-fb p { margin: 0 0 14px; font-size: .89rem; color: rgba(234, 242, 244, .7); line-height: 1.7; }
.demo-corr { display: flex; flex-direction: column; gap: 3px; margin-bottom: 10px; font-family: var(--font-de); }
.demo-corr del { color: #F2A49C; }
.demo-corr ins { color: var(--signal); text-decoration: none; font-weight: 700; }

/* ------------------------------------------------------- level chooser */

.lvl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 18px; }
.lvl-card {
  position: relative; display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 28px; background: var(--surface);
  transition: border-color .2s var(--ease), transform .2s var(--ease), box-shadow .2s var(--ease);
}
.lvl-card:hover { border-color: var(--ink); transform: translateY(-3px); box-shadow: var(--shadow-lift); }
.lvl-card.picked { border-color: var(--signal-deep); border-width: 2px; padding: 27px; }
.lvl-card.picked::after {
  content: 'مستواك'; position: absolute; top: 18px; inset-inline-end: 18px;
  font-size: .7rem; font-weight: 700; color: var(--signal-deep);
  background: var(--signal-wash); padding: 3px 10px; border-radius: 6px;
}
.lvl-tag {
  font-family: var(--font-de); font-size: 3.2rem; font-weight: 700;
  line-height: 1; letter-spacing: -.04em; color: var(--ink); margin-bottom: 14px;
}
.lvl-card h3 { margin: 0 0 16px; font-size: 1.05rem; font-weight: 600; color: var(--ink-2); }
.lvl-list { list-style: none; margin: 0 0 20px; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.lvl-list li {
  font-size: .76rem; padding: 3px 9px; border-radius: 6px;
  background: var(--paper-2); color: var(--ink-3); border: 1px solid var(--line-soft);
}
.lvl-foot {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: auto; padding-top: 16px; border-top: 1px solid var(--line-soft);
  font-size: .84rem; color: var(--ink-3);
}
.lvl-go { font-weight: 700; color: var(--ink); }

/* level pill menu */
.level-menu {
  position: absolute; z-index: 100; margin-top: 8px;
  min-width: 230px; padding: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 13px; box-shadow: var(--shadow-lift);
}
#levelPill { position: relative; }
.level-opt {
  display: flex; flex-direction: column; gap: 1px; width: 100%;
  padding: 9px 12px; border: 0; border-radius: 9px;
  background: transparent; color: var(--ink); text-align: start;
  font: inherit; cursor: pointer;
}
.level-opt:hover { background: var(--paper-2); }
.level-opt b { font-family: var(--font-de); font-size: .95rem; }
.level-opt span { font-size: .78rem; color: var(--ink-3); }
.level-opt.on { background: var(--signal-wash); }
.level-opt.plain { border-top: 1px solid var(--line-soft); border-radius: 0 0 9px 9px; margin-top: 4px; padding-top: 11px; font-size: .84rem; color: var(--ink-3); }

.off-level { display: none !important; }

/* -------------------------------------------------------- why section */


/* --- restored: the credibility pair -----------------------------------------
   Two cards, not the original six: the other four said what the feature
   section above now shows with a figure. */
.why-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; }
.why-card { background: var(--paper); padding: 28px; position: relative; transition: background .2s var(--ease); }
.why-card:hover { background: var(--surface); }
.why-idx { position: absolute; top: 22px; inset-inline-end: 22px; font-family: var(--font-de); font-size: .74rem; font-weight: 700; color: var(--ink-3); opacity: .5; }
.why-icon { display: block; font-size: 1.5rem; color: var(--signal-deep); margin-bottom: 14px; }
html[data-theme="dark"] .why-icon { color: var(--signal); }
.why-card h3 { margin: 0 0 8px; font-size: 1.04rem; }
.why-card p { margin: 0; font-size: .93rem; color: var(--ink-2); line-height: 1.72; }

/* --- الطريق ديالك لألمانيا --------------------------------------------------
   Inverted, so the one section about the learner's life rather than the
   product reads as a different kind of page. Colours are literal rather than
   tokens here on purpose: the band is dark in both themes, so a token that
   flips would break the contrast the section is built on. */
.road { position: relative; background: var(--canvas); color: #EAF2F4; overflow: hidden; }
/* Three stops, no caption needed. */
.road-flag { position: absolute; inset-block-start: 0; inset-inline: 0; height: 3px; background: linear-gradient(to left, #111 33%, #C1121F 33% 66%, #E4B429 66%); }
.road .block-head h2 { color: #EAF2F4; }
.road .block-head p, .road .idx { color: #9DB4C2; }

.road-chips { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-bottom: 26px; }
.road-chip {
  font: inherit; font-size: .85rem; cursor: pointer;
  padding: 10px 18px; min-height: 44px; border-radius: 100px;
  background: transparent; color: #BCCFD9; border: 1px solid #2A4E6C;
  transition: background .18s var(--ease), color .18s var(--ease), border-color .18s var(--ease);
}
@media (hover: hover) { .road-chip:hover { border-color: var(--signal); color: #EAF2F4; } }
.road-chip.is-on { background: var(--signal); border-color: var(--signal); color: var(--signal-ink); font-weight: 700; }

/* ---- the goal ----------------------------------------------------------- */
.road-goal { max-width: 760px; margin: 0 auto; text-align: center; }
.road-goal-t { margin: 0 0 8px; font-size: 1.4rem; letter-spacing: .02em; color: #EAF2F4; }
.road-hook { margin: 0 auto 24px; max-width: 52ch; font-size: 1rem; line-height: 1.7; color: #BCCFD9; }
.road-q { display: block; margin: 0 0 12px; font-size: .78rem; letter-spacing: .06em; color: var(--signal); }

.road-levels { display: grid; grid-template-columns: 1fr; gap: 12px; text-align: start; }
@media (min-width: 720px) { .road-levels { grid-template-columns: 1fr 1fr; } }
.road-lv { background: #12293F; border: 1px solid #254764; border-radius: var(--radius); padding: 18px 20px; }
.road-lv p { margin: 0; font-size: .86rem; line-height: 1.7; color: #9DB4C2; }
.road-lv-h { display: flex; align-items: center; gap: 9px; margin-bottom: 9px; }
.road-lv-h b { font-size: .98rem; color: #EAF2F4; }
/* The author's 🟢 / 🔵 as design elements: an emoji here renders differently on
   every platform and sits badly next to the level it labels. */
.road-dot { width: 11px; height: 11px; border-radius: 50%; flex: 0 0 auto; }
.road-lv.b1 .road-dot { background: #6FD1B0; }
.road-lv.b2 .road-dot { background: #8FC2DE; }
.road-fields { margin-top: 9px !important; font-family: var(--font-de); font-size: .82rem; color: #CFE0E6 !important; }
/* The one-line summary the author closes each level with — it is the line a
   skimmer reads, so it sits apart from the paragraph rather than after it. */
.road-lv-tag { display: block; margin-top: 12px; padding-top: 11px; border-top: 1px dashed #274A67; font-size: .84rem; font-weight: 700; color: #EAF2F4; }
.road-lv.b1 .road-lv-tag { color: #6FD1B0; }
.road-lv.b2 .road-lv-tag { color: #9FCFE6; }
.road-vary { margin: 14px 0 0; font-size: .76rem; line-height: 1.7; color: #A8BECB; }

@keyframes road-swap { from { opacity: .3; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.road-swap { animation: road-swap .3s var(--ease); }

/* ---- what the platform drills ------------------------------------------- */
.road-prep { margin-top: 42px; text-align: center; }
.road-prep h3 { margin: 0 0 8px; font-size: 1.14rem; color: #EAF2F4; }
.road-prep-lead { margin: 0 auto 22px; max-width: 48ch; font-size: .92rem; line-height: 1.7; color: #BCCFD9; }
/* Names the exam and the certificate — the step the section used to skip. */
.road-bridge { margin: 0 auto 6px; max-width: 46ch; font-size: .88rem; line-height: 1.7; color: #9DB4C2; }
.road-bridge [lang="de"] { color: #EAF2F4; font-weight: 600; }
.road-prep-q { margin: 0 0 12px; font-size: .8rem; color: #9DB4C2; }
/* The line that hands the reader over to what the site actually drills: the
   turn from "here is the level you need" to "here is how you get it". Sized
   as a step in the argument rather than as a caption. */
.road-prep-q.lead {
  display: inline-block; margin: 6px 0 18px; padding: 10px 22px;
  font-size: 1rem; font-weight: 700; color: var(--signal-ink);
  background: var(--signal); border-radius: 100px;
}
.road-prep-q.lead [lang="de"] { font-family: var(--font-de); }
.road-prep-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; text-align: start; margin-bottom: 20px; }
@media (min-width: 720px) { .road-prep-grid { grid-template-columns: repeat(5, 1fr); } }
.road-prep-card { background: #12293F; border: 1px solid #254764; border-radius: var(--radius); padding: 16px 14px; }
/* The fifth card has no partner on a two-column phone layout, so it takes the
   row — it is the simulator, which is the one worth the extra width anyway. */
@media (max-width: 719px) { .road-prep-card:last-child { grid-column: 1 / -1; } }
.road-prep-i { display: block; font-size: 1.25rem; line-height: 1; margin-bottom: 9px; }
.road-prep-card b { display: block; font-family: var(--font-de); font-size: .88rem; color: var(--signal); margin-bottom: 4px; }
.road-prep-card span { font-size: .76rem; line-height: 1.55; color: #9DB4C2; }
.road-pills { display: flex; flex-wrap: wrap; gap: 7px; justify-content: center; }
.road-pills span { font-family: var(--font-de); font-size: .82rem; color: #BCCFD9; background: #12293F; border: 1px solid #254764; border-radius: 100px; padding: 6px 14px; }

/* ---- the ask ------------------------------------------------------------ */
.road-cta { display: flex; flex-direction: column; align-items: center; gap: 12px; margin-top: 38px; padding-top: 30px; border-top: 1px solid #20415C; }
.road-cta p { margin: 0; max-width: 46ch; font-size: 1rem; line-height: 1.6; color: #EAF2F4; }
.road-fine { font-size: .76rem; color: #9DB4C2; }

@media (prefers-reduced-motion: reduce) { .road-swap { animation: none; } }

@media (max-width: 620px) {
  /* The chips must never wrap into three ragged lines on a phone. */
  .road-chips { flex-wrap: nowrap; overflow-x: auto; justify-content: flex-start; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; padding-bottom: 4px; }
  .road-chips::-webkit-scrollbar { display: none; }
  .road-chip { flex: 0 0 auto; scroll-snap-align: start; }
  .road-goal-t { font-size: 1.2rem; }
  .road-hook { font-size: .93rem; }
  .road-cta .btn { width: 100%; justify-content: center; }
}

/* --- talk to us ------------------------------------------------------------
   A floating contact button, on every page. WhatsApp is where this audience
   already is — it is how the subscription is paid for and how the answer
   disputes get settled — so the way to reach a person should not be buried in
   a footer.

   Placed at the inline end, which is the left on this RTL site, so it never
   sits over the text a reader is following. On a phone it lifts above the tab
   bar rather than covering it: the tab bar is 68px including its safe-area
   padding, and a button that hides a navigation item is worse than no button.

   It carries WhatsApp's own green rather than a palette token. This is a
   recognised mark, and recolouring it to match the site would make it read as
   a decorative circle instead of "message us here". */
/* Solid WhatsApp green, at the size the pattern is normally used.
   The quiet treatment that was here — a white disc with a green glyph — was
   correct on paper and wrong in practice: on a cream page a white circle has
   barely any edge, so the thing people are meant to press read as part of the
   layout. Recognition is the whole job of this button, and the green is what
   carries it. */
.wa-fab {
  position: fixed; z-index: 96;
  inset-inline-end: 18px; bottom: calc(18px + env(safe-area-inset-bottom));
  width: 58px; height: 58px; border-radius: 50%;
  display: grid; place-items: center;
  background: #25D366; color: #fff;
  /* Layered rather than one soft blur: the tight shadow gives the disc an
     edge against the cream, the wide one lifts it off the page. */
  box-shadow: 0 2px 6px rgba(20, 38, 58, .22), 0 10px 26px rgba(20, 38, 58, .26);
  transition: transform .18s var(--ease), box-shadow .18s var(--ease);
}
/* On the dark page the green is bright enough to stand alone, but the disc's
   own edge disappears — a hairline in the same green, lightened, restores it
   without introducing a second colour. */
html[data-theme="dark"] .wa-fab {
  box-shadow: 0 0 0 1px rgba(122, 240, 168, .35),
              0 2px 8px rgba(0, 0, 0, .5), 0 10px 26px rgba(0, 0, 0, .45);
}
.wa-fab svg { width: 33px; height: 33px; display: block; }
@media (hover: hover) {
  .wa-fab:hover {
    transform: translateY(-2px) scale(1.04);
    box-shadow: 0 4px 10px rgba(20, 38, 58, .26), 0 16px 34px rgba(20, 38, 58, .3);
  }
}
.wa-fab:focus-visible { outline: 3px solid var(--signal-deep); outline-offset: 3px; }

@media (max-width: 940px) {
  /* Clear of the tab bar, which is fixed to the bottom at 68px. */
  .wa-fab { bottom: calc(78px + env(safe-area-inset-bottom)); width: 56px; height: 56px; }
  .wa-fab svg { width: 32px; height: 32px; }
}

/* Embedded views hide the site's own chrome; this belongs to the site. */
html[data-embed] .wa-fab { display: none; }

@media print { .wa-fab { display: none; } }

/* --- student reviews -------------------------------------------------------
   Screenshots, framed as plainly as possible: a WhatsApp thread argues by
   being obviously unretouched, and any styling that reads as art direction
   works against it.

   The strip moves on a CSS animation rather than on a script. The scripted
   version advanced scrollLeft on a timer, which needs a running script, a
   foregrounded tab, an observer that has fired and a browser that is not
   throttling timers — and when any of those was false it did nothing at all,
   so the strip looked like something you had to push by hand. This runs on
   the compositor and has none of those dependencies.

   Seamless because the markup holds the reviews twice and the animation
   travels exactly one set: at the end, the duplicate sits where the original
   began, so the reset is invisible and the motion never stops or reverses.
   It moves left to right, which is why it starts translated and returns to
   zero rather than the other way round.

   Nothing in here takes a pointer. The images are inert, and there are no
   arrows or dots to click. */
/* direction:ltr belongs on the window, not just on the strip. The track is
   width:max-content and so overflows its container; in an RTL container an
   overflowing child is aligned to the *right* edge, which put the strip's
   right edge at the window's right edge — and then translating it left by one
   set pushed the whole thing off-screen. The section was blank at the start of
   every loop and filled in only as it travelled. Anchored left, the strip
   spans the window at every point of the animation. */
.rv { overflow: hidden; padding: 4px 0 2px; direction: ltr; }
.rv-track {
  display: flex;
  gap: 18px;
  width: max-content;
  animation: rv-marquee 34s linear infinite;
  will-change: transform;
}
/* Travels one full set: the strip is two identical sets, so -50% of its own
   width is exactly one set. Ending at 0 means the content moves rightwards. */
@keyframes rv-marquee {
  from { transform: translateX(-50%); }
  to   { transform: translateX(0); }
}
/* Someone reading a review should be able to stop it. */
@media (hover: hover) { .rv:hover .rv-track { animation-play-state: paused; } }

.rv-slide { flex: 0 0 auto; margin: 0; width: min(72vw, 300px); }
.rv-slide img {
  display: block; width: 100%; height: auto;
  border-radius: var(--radius); box-shadow: var(--shadow);
  /* Inert on purpose: nothing to click, drag, select or long-press. */
  pointer-events: none; user-select: none; -webkit-user-select: none;
  -webkit-user-drag: none; -webkit-touch-callout: none;
}

@media (max-width: 860px) {
  /* Shorter strip, so the same speed would feel slower — and a phone shows
     fewer at once, so it should come round sooner. */
  .rv-track { animation-duration: 24s; gap: 14px; }
}

/* Motion is the whole point of this section, so reduced-motion gets the strip
   standing still and scrollable by hand instead of a moving one. */
@media (prefers-reduced-motion: reduce) {
  .rv { overflow-x: auto; scrollbar-width: none; }
  .rv::-webkit-scrollbar { display: none; }
  .rv-track { animation: none; }
}

/* --- homepage feature section ----------------------------------------------
   The part of the page that has to earn a signup. Structure carries the
   hierarchy: one full-width flagship panel for the thing nobody else offers,
   a grid of five beneath it, then a deliberately quiet strip for the rest.

   Everything is a token, never a literal colour, so the dark theme repaints
   it; the SVG and the flow arrows run right-to-left like the page. Motion is
   opt-in through .is-in, added by home-features.js — the CSS resting state is
   the visible one, so a page whose script never runs still reads correctly. */

.feat-tag {
  display: inline-block; font-size: .7rem; font-weight: 700; letter-spacing: .06em;
  color: var(--accent-ink); background: var(--accent-soft);
  border-radius: 100px; padding: 5px 11px;
}
.feat h3, .feat-lead h3 { margin: 12px 0 8px; font-size: 1.14rem; line-height: 1.4; letter-spacing: -.01em; }
/* One line, and it is allowed to be one line: the figure carries the detail. */
.feat-line { margin: 0; font-size: .9rem; line-height: 1.65; color: var(--ink-2); }

.feat-chips { list-style: none; display: flex; flex-wrap: wrap; gap: 7px; margin: 14px 0 0; padding: 0; }
.feat-chips li {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 6px 11px; border: 1px solid var(--line); border-radius: 100px;
  background: var(--paper); font-size: .74rem; color: var(--ink-3);
}
.feat-chips b { font-family: var(--font-de); font-size: .86rem; color: var(--ink); }

/* ---- 01, the flagship --------------------------------------------------- */
.feat-lead {
  display: grid; grid-template-columns: 1fr; gap: 26px; margin-bottom: 18px;
  padding: 30px; border-radius: var(--radius-lg);
  background: var(--surface); border: 1px solid var(--line);
  position: relative; overflow: hidden;
}
/* A single accent edge, on the right where an Arabic line starts. */
.feat-lead::before {
  content: ""; position: absolute; inset-block: 0; inset-inline-end: 0;
  width: 3px; background: linear-gradient(var(--signal-deep), transparent);
}
@media (min-width: 900px) { .feat-lead { grid-template-columns: 1fr 1fr; gap: 34px; align-items: center; padding: 38px; } }
.feat-lead h3 { font-size: 1.5rem; }
@media (min-width: 900px) { .feat-lead h3 { font-size: 1.75rem; } }
.feat-cta-row { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 20px; }
.feat-fine { font-size: .74rem; color: var(--ink-3); }

/* the mock exchange: what the product actually looks like, not an icon */
.sp-demo { display: flex; flex-direction: column; gap: 10px; }
.sp-turn { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px; background: var(--paper); }
.sp-turn.me { background: var(--accent-soft); border-color: var(--signal-deep); margin-inline-start: 22px; }
.sp-turn.examiner { margin-inline-end: 22px; }
.sp-who { display: flex; align-items: center; gap: 7px; font-size: .7rem; color: var(--ink-3); margin-bottom: 6px; }
.sp-turn p { margin: 0; font-family: var(--font-de); font-size: .87rem; line-height: 1.6; color: var(--ink); }
/* three bars, so "it is listening to you" needs no caption */
.sp-wave { display: inline-flex; align-items: flex-end; gap: 2px; height: 12px; }
.sp-wave i { width: 3px; height: 4px; border-radius: 2px; background: var(--signal-deep); }
.is-in .sp-wave i { animation: sp-bounce 1.05s var(--ease) infinite; }
.is-in .sp-wave i:nth-child(2) { animation-delay: .16s; }
.is-in .sp-wave i:nth-child(3) { animation-delay: .32s; }
@keyframes sp-bounce { 0%, 100% { height: 4px; } 50% { height: 12px; } }

.sp-score { border: 1px solid var(--line); border-radius: var(--radius); padding: 13px 14px; background: var(--surface); }
.sp-bands { display: flex; flex-wrap: wrap; gap: 6px; }
.sp-band { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-de); font-size: .68rem; color: var(--ink-2); border: 1px solid var(--line); border-radius: 100px; padding: 4px 9px; }
.sp-band i { font-style: normal; font-weight: 700; width: 16px; height: 16px; display: grid; place-items: center; border-radius: 50%; font-size: .64rem; background: var(--ok-bg); color: var(--ok); }
.sp-band.b i { background: var(--warn-bg); color: var(--warn); }
.sp-band.c i { background: var(--bad-bg); color: var(--bad); }
.sp-note { margin: 10px 0 0; font-size: .78rem; line-height: 1.7; color: var(--ink-2); }
.sp-note b { font-family: var(--font-de); color: var(--ink); font-weight: 600; }

/* ---- 02–06 -------------------------------------------------------------- */
.feat-grid { display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 760px) { .feat-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1040px) { .feat-grid { grid-template-columns: repeat(3, 1fr); } }
.feat {
  display: flex; flex-direction: column; padding: 26px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  transition: border-color .2s var(--ease), transform .2s var(--ease), box-shadow .2s var(--ease);
}
@media (hover: hover) { .feat:hover { transform: translateY(-3px); border-color: var(--signal-deep); box-shadow: var(--shadow); } }
/* Figures sit at the bottom so they line up across a row of uneven copy. */
.feat-viz { margin-top: auto; padding-top: 18px; }

.fx-card { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.fx-fix { padding: 12px 14px; background: var(--paper-2); border-bottom: 1px solid var(--line); }
.fx-fix s { display: block; font-family: var(--font-de); font-size: .82rem; color: var(--bad); opacity: .8; }
.fx-fix b { display: block; font-family: var(--font-de); font-size: .86rem; color: var(--ink); margin-top: 3px; }
.fx-tag { display: inline-block; margin-top: 8px; font-family: var(--font-de); font-size: .65rem; color: var(--ink-3); border: 1px solid var(--line); border-radius: 100px; padding: 3px 8px; }
.fx-rows { padding: 4px 14px 10px; }
.fx-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 6px 0; font-size: .72rem; color: var(--ink-3); border-bottom: 1px dashed var(--line-soft); }
.fx-row em { font-style: normal; font-family: var(--font-de); font-weight: 700; color: var(--ink-2); }
.fx-row.total { border-bottom: 0; color: var(--ink); font-size: .78rem; }
.fx-row.total em { color: var(--signal-deep); font-size: 1.05rem; }

.fx-steps { list-style: none; margin: 0; padding: 0; counter-reset: fx; }
.fx-steps li {
  counter-increment: fx; position: relative; padding: 7px 30px 7px 0;
  font-size: .78rem; color: var(--ink-2); border-bottom: 1px dashed var(--line-soft);
}
.fx-steps li:last-child { border-bottom: 0; }
.fx-steps li::before {
  content: counter(fx); position: absolute; inset-inline-end: 0; top: 6px;
  width: 20px; height: 20px; display: grid; place-items: center; border-radius: 50%;
  font-family: var(--font-de); font-size: .62rem; font-weight: 700;
  background: var(--accent-soft); color: var(--accent-ink);
}
.fx-loop { display: flex; align-items: center; gap: 8px; margin-top: 12px; padding: 9px 12px; border: 1px dashed var(--signal-deep); border-radius: var(--radius-sm); font-size: .72rem; color: var(--ink-2); background: var(--accent-soft); }

.fx-svg { display: block; width: 100%; height: auto; overflow: visible; }
.ig-area { fill: var(--signal-wash); }
.ig-curve { fill: none; stroke: var(--signal-deep); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ig-axis { stroke: var(--line); stroke-width: 1; }
.ig-dot { fill: var(--paper); stroke: var(--signal-deep); stroke-width: 1.5; }
.ig-dot.now { fill: var(--signal-deep); }
.ig-cell { fill: var(--line); }
.ig-cell.l2 { fill: var(--signal-deep); opacity: .42; }
.ig-cell.l3 { fill: var(--signal-deep); }
/* The weekly plan figure. Days already done are filled and quiet, today is
   the bright one with a dot over it, and the days ahead are outlines — so the
   week reads left-unfinished at a glance without a single label. */
.pl-bar { fill: none; stroke: var(--signal-deep); stroke-width: 1.5; opacity: .5; }
.pl-bar.done { fill: var(--signal-wash); stroke: none; opacity: 1; }
.pl-bar.today { fill: var(--signal-deep); stroke: none; opacity: 1; }
.pl-tick { fill: none; stroke: var(--signal-deep); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.pl-now { fill: var(--signal-deep); }
.pl-day { fill: var(--line); }

.fx-legend { display: flex; justify-content: space-between; font-size: .7rem; color: var(--ink-3); margin-top: 4px; }


/* the wrong answer everyone has, next to the right one */
.fx-verify { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.fx-vrow { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 13px; font-family: var(--font-de); font-size: .8rem; }
.fx-vrow.bad { background: var(--bad-bg); color: var(--bad); text-decoration: line-through; }
.fx-vrow.good { background: var(--ok-bg); color: var(--ok); font-weight: 700; }
.fx-vtag { font-family: var(--font-ar); font-size: .68rem; font-weight: 400; text-decoration: none; opacity: .85; }
.fx-vnote { padding: 9px 13px; font-size: .7rem; color: var(--ink-3); border-top: 1px solid var(--line); background: var(--paper-2); }


/* ---- the quiet strip ---------------------------------------------------- */
.feat-more {
  display: grid; grid-template-columns: 1fr; gap: 1px; margin-top: 18px;
  background: var(--line); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden;
}
@media (min-width: 620px) {
  .feat-more { grid-template-columns: 1fr 1fr; }
  /* Odd count: the last one takes the whole row rather than leaving a gap. */
  .feat-more-item:last-child { grid-column: 1 / -1; }
}
@media (min-width: 1000px) {
  .feat-more { grid-template-columns: repeat(5, 1fr); }
  .feat-more-item:last-child { grid-column: auto; }
}
.feat-more-item { display: flex; gap: 10px; padding: 14px 16px; background: var(--paper); }
.feat-more-icon { color: var(--signal-deep); font-size: 1rem; line-height: 1.3; }
.feat-more-item b { display: block; font-size: .82rem; margin-bottom: 2px; }
.feat-more-item span { font-size: .74rem; color: var(--ink-3); line-height: 1.5; }


/* ---- motion ------------------------------------------------------------- */
.feat-anim { opacity: 0; transform: translateY(14px); transition: opacity .5s var(--ease), transform .5s var(--ease); }
.feat-anim.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .feat-anim { opacity: 1; transform: none; transition: none; }
  .is-in .sp-wave i, .is-in .fx-dot { animation: none; }
  .sp-wave i { height: 8px; }
}

@media (max-width: 620px) {
  .feat-lead { padding: 22px; }
  .feat { padding: 20px; }
  .sp-turn.me { margin-inline-start: 12px; }
  .sp-turn.examiner { margin-inline-end: 12px; }
  /* Full-width tap target: the primary ask must not be a small pill on a phone. */
  .feat-cta-row .btn { width: 100%; justify-content: center; }
}

/* --------------------------------------------------------- final CTA */

.cta-final {
  display: flex; align-items: center; justify-content: space-between; gap: 26px;
  flex-wrap: wrap;
  background: var(--canvas); color: #EAF2F4;
  border-radius: var(--radius-lg); padding: 44px;
}
.cta-final h2 { margin: 0 0 8px; font-size: clamp(1.5rem, 3vw, 2.1rem); }
.cta-final p { margin: 0; color: rgba(234, 242, 244, .65); }

/* ------------------------------------------------------ explain button */

.xp-host { position: relative; }
.xp-btn {
  position: absolute; top: 4px; inset-inline-end: 0;
  width: auto; height: 21px; border-radius: 6px;
  padding: 0 7px; gap: 4px;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--ink-3); cursor: pointer;
  font-family: var(--font-ar); font-size: .68rem; font-weight: 700;
  display: inline-flex; align-items: center; line-height: 1;
  opacity: .75;
  transition: opacity .15s var(--ease), border-color .15s var(--ease), color .15s var(--ease);
}

/* Reserve the button's lane so no line of German ever runs underneath it. */
.xp-host { padding-top: 30px; }
.xp-host:hover .xp-btn { opacity: 1; }
.xp-btn:hover { border-color: var(--ink); color: var(--ink); }
.xp-btn.on { background: var(--signal-wash); border-color: var(--signal-deep); color: var(--signal-deep); opacity: 1; }
html[data-theme="dark"] .xp-btn.on { color: var(--signal); }

.xp-box {
  /*
   * The panel hangs inside .prose, which is the German — LTR and left-aligned.
   * An Arabic explanation inheriting that alignment strands every list bullet
   * on the far side of its own line, so the box states its own direction.
   */
  direction: rtl; text-align: right;
  margin-top: 10px; padding: 11px 13px;
  background: var(--signal-wash); border-radius: 9px;
  font-size: .9rem; line-height: 1.75;
  animation: trIn .18s var(--ease) both;
}
html[data-theme="dark"] .xp-box { background: var(--paper-3); }
@keyframes trIn { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
.tr-ar { margin: 0; color: var(--ink); }
.tr-ar + .tr-ar { margin-top: 9px; }
.tr-hint { margin: 6px 0 0; font-size: .78rem; color: var(--ink-3); }
.tr-loading { margin: 0; color: var(--ink-3); }
.tr-gloss { display: flex; flex-wrap: wrap; gap: 6px; }
.gl-pair {
  font-size: .8rem; background: var(--surface); border: 1px solid var(--line);
  border-radius: 6px; padding: 3px 8px; color: var(--ink-2);
}
.gl-pair b { font-family: var(--font-de); direction: ltr; margin-inline-end: 4px; color: var(--ink); }

/* ---------------------------------------------------- explanation panel */

/*
 * A translation was one paragraph; an explanation is several short sections,
 * and they only read as an explanation if the eye can tell them apart. Each
 * heading is small and quiet — the German inside is what should stand out.
 */
.xp-sec + .xp-sec { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
.xp-sec h4 {
  margin: 0 0 6px; font-size: .74rem; font-weight: 800; letter-spacing: .02em;
  color: var(--ink-3); text-transform: none;
}
.xp-kind {
  margin: 0 0 10px; font-size: .78rem; font-weight: 700;
  color: var(--signal-deep); background: var(--signal-wash);
  border-radius: 6px; padding: 4px 9px; display: inline-block;
}
html[data-theme="dark"] .xp-kind { color: var(--signal); background: var(--paper-2); }
.xp-p { margin: 0; color: var(--ink-2); font-size: .86rem; }

/* What the text is about: the first thing read, so it carries some weight. */
/*
 * The written summary, which is not the same kind of thing as the rest of the
 * panel: everything below it the page worked out from the German in front of
 * it, and this one sentence a person wrote about what the text is actually
 * about. It carries the site's signal wash, the same lift given to a sentence
 * a reader must not skim past.
 */
.xp-summary {
  margin: 0 0 14px; padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--signal) 42%, transparent);
  border-radius: 12px;
  background: var(--signal-wash);
}
.xp-summary h4 {
  margin: 0 0 6px;
  font-size: .74rem; font-weight: 800; letter-spacing: .02em;
  color: var(--signal-deep);
}
.xp-summary p {
  margin: 0;
  font-size: .95rem; line-height: 1.85; font-weight: 700;
  color: var(--accent-ink);
}
html[data-theme="dark"] .xp-summary {
  background: var(--paper-2);
  border-color: color-mix(in srgb, var(--signal) 32%, transparent);
}
html[data-theme="dark"] .xp-summary h4 { color: var(--signal); }
html[data-theme="dark"] .xp-summary p { color: var(--ink); }

/* The clause that proves the structure, under the name of the structure. */
.xp-eg {
  display: block; margin-top: 3px;
  font-family: var(--font-de); font-size: .8rem; color: var(--ink-3);
  border-inline-start: 2px solid var(--line); padding-inline-start: 8px;
  text-align: left; overflow-wrap: anywhere;
}
.xp-eg em { font-style: normal; font-weight: 700; color: var(--ink); }
.xp-list { margin: 0; padding-inline-start: 18px; display: grid; gap: 5px; }
.xp-list li { font-size: .86rem; color: var(--ink-2); line-height: 1.6; }
.xp-list b { font-family: var(--font-de); color: var(--ink); }
.xp-order {
  font-size: .72rem; color: var(--ink-3);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 5px; padding: 1px 6px; display: inline-block;
  /* On a phone this wraps under its connector rather than pushing the row wide. */
  max-width: 100%; overflow-wrap: anywhere;
}

/* A long sentence taken apart: main clause, then the clause hanging off it. */
.xp-split { display: grid; gap: 5px; }
.xp-split + .xp-split { margin-top: 10px; }
.xp-split > div {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 7px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 8px; padding: 7px 10px;
}
.xp-split span[lang="de"] {
  font-family: var(--font-de); font-size: .85rem; color: var(--ink);
  text-align: left;
  flex: 1; min-width: 0; overflow-wrap: anywhere;
}
.xp-tag {
  flex: none; font-size: .68rem; font-weight: 800;
  color: var(--signal-deep); background: var(--signal-wash);
  border-radius: 5px; padding: 2px 7px;
}
.xp-tag.alt { color: var(--ink-2); background: var(--line); }
html[data-theme="dark"] .xp-tag { color: var(--signal); }
.xp-note { margin: 2px 0 0; font-size: .78rem; color: var(--ink-3); }
.xp-note b { font-family: var(--font-de); color: var(--ink-2); }


.gloss-tip {
  position: absolute; z-index: 150;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--canvas); color: #EAF2F4;
  padding: 9px 13px; border-radius: 10px;
  font-size: .85rem; max-width: 260px;
  box-shadow: var(--shadow-lift);
  opacity: 0; transform: translateY(3px);
  transition: opacity .14s var(--ease), transform .14s var(--ease);
  pointer-events: none;
}
.gloss-tip.in { opacity: 1; transform: none; }
.gloss-tip b { font-family: var(--font-de); color: var(--signal); font-size: .9rem; }

/* --------------------------------------------------------- exam date */

.exam-section { margin-bottom: 26px; }

.exam-set {
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px; flex-wrap: wrap;
  border: 1px dashed var(--ink-3); border-radius: var(--radius-lg);
  padding: 26px 28px; background: var(--surface);
}
.exam-set-copy h3 { margin: 0 0 6px; font-size: 1.22rem; }
.exam-set-copy p { margin: 0; color: var(--ink-3); font-size: .93rem; max-width: 48ch; }
.exam-set-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.exam-set-form.inline { margin-top: 12px; }
.exam-set-form input, .exam-set-form select {
  padding: 10px 13px; border-radius: 10px; border: 1px solid var(--line);
  background: var(--surface); color: var(--ink); font: inherit; font-size: .9rem;
}

.exam-count {
  display: grid; grid-template-columns: minmax(200px, .7fr) minmax(0, 1.3fr);
  gap: 28px; align-items: start;
  border-radius: var(--radius-lg); padding: 28px;
  background: var(--canvas); color: #EAF2F4;
  position: relative; overflow: hidden;
}
.exam-count::before {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(500px 260px at 88% -20%, rgba(100, 163, 193, .16), transparent 65%);
  pointer-events: none;
}
.exam-count.hot::before { background: radial-gradient(500px 260px at 88% -20%, rgba(242, 160, 150, .22), transparent 65%); }
.exam-count > * { position: relative; }
@media (max-width: 820px) { .exam-count { grid-template-columns: 1fr; gap: 20px; } }

.exam-kicker { font-size: .82rem; color: rgba(234, 242, 244, .6); display: block; margin-bottom: 6px; }
.exam-days { display: flex; align-items: baseline; gap: 9px; margin-bottom: 4px; }
.exam-days b {
  font-family: var(--font-de); font-size: clamp(3rem, 7vw, 4.4rem);
  font-weight: 700; line-height: 1; letter-spacing: -.04em; color: var(--signal);
}
.exam-count.hot .exam-days b { color: #F2A49C; }
.exam-days span { font-size: .95rem; color: rgba(234, 242, 244, .7); }
.exam-count .count-note { color: rgba(234, 242, 244, .62); display: block; margin-bottom: 10px; }
.exam-count .btn.ghost { color: rgba(234, 242, 244, .7); }
.exam-count .btn.ghost:hover { background: rgba(234, 242, 244, .1); }

.exam-count-plan h3 { margin: 0 0 14px; font-size: 1rem; color: rgba(234, 242, 244, .85); }
.exam-focus { list-style: none; margin: 0 0 16px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.exam-focus li { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; font-size: .93rem; }
.ef-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--signal); flex: none; }
.exam-focus b { font-weight: 600; }
.exam-focus .count-note { color: rgba(234, 242, 244, .66); display: inline; margin: 0; }
.exam-count .btn.secondary { color: #EAF2F4; border-color: rgba(234, 242, 244, .28); }
.exam-locked p { margin: 0 0 12px; font-size: .93rem; color: rgba(234, 242, 244, .75); }
.exam-focus.preview { opacity: .9; }

/* -------------------------------------------------------- misc v2 bits */

.plan-note { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 16px; font-size: .87rem; }
#levelScopeNote { font-size: .86rem; color: var(--ink-3); margin: 0 0 14px; }

@media (max-width: 700px) {
  /* Same reason as the desktop rule: nothing fills the bottom any more. */
  .hero-dark { padding-block: 44px 52px; }
  .demo { padding: 18px; }
  .lvl-card { padding: 22px; }
  .lvl-tag { font-size: 2.6rem; }
  .cta-final { padding: 28px 22px; }
  .exam-count, .exam-set { padding: 20px; }
  .xp-btn { opacity: 1; }
}

/* ═════════════════════════════════════════════════ embedded exam mode */

/* An exercise opened inside the simulator is a fragment of another page,
   not a page of its own: it must not bring a second header, footer,
   breadcrumb trail or tab bar with it. */
html[data-embed] .site-header,
html[data-embed] .site-footer,
html[data-embed] .tabbar,
html[data-embed] .crumbs,
html[data-embed] .pager,
html[data-embed] .skip-link { display: none !important; }

html[data-embed] body { padding-bottom: 0; }
html[data-embed] .page-head { padding-top: 0; margin-bottom: 18px; }
html[data-embed] .wrap,
html[data-embed] .wrap-narrow { padding-inline: 16px; max-width: none; }
html[data-embed] .sticky-side { position: static; max-height: none; }

/* On mobile the tab bar already carries the daily challenge, so the header
   button beside it is a duplicate of the same destination. */
@media (max-width: 940px) {
  .cta-mini { display: none; }
}

/* ═══════════════════════════════════════════ inverted surfaces (colour fix)

   Root cause of the "phrases inherit the wrong theme colour" bug: several
   surfaces are permanently dark regardless of theme (the hero, the exam
   countdown, the closing CTA, the homepage demo, tooltips). Any component
   dropped on them still resolved --ink-2 / --muted / --line against the *page*
   theme, so in light mode dark-grey text landed on a near-black card.

   Fixed at the source by re-declaring the token set for those surfaces, so
   every current and future component inherits correct colours automatically
   instead of each phrase being patched by hand.                            */

.hero-dark,
.exam-count,
.cta-final,
.demo,
.gloss-tip,
.gloss-onboard {
  --ink: #EAF2F4;
  --ink-2: rgba(234, 242, 244, .76);
  --ink-3: rgba(234, 242, 244, .72);
  --muted: rgba(234, 242, 244, .72);
  --ink-soft: rgba(234, 242, 244, .76);
  --line: rgba(234, 242, 244, .18);
  --line-soft: rgba(234, 242, 244, .12);
  --surface: rgba(234, 242, 244, .07);
  --surface-2: rgba(234, 242, 244, .1);
  --paper-2: rgba(234, 242, 244, .1);
  /* --paper is the ground, and on an inverted surface the ground is dark.
     It was never re-declared here, so .btn.accent — background var(--ink),
     colour var(--paper) — put the page's own near-white text on the
     inverted near-white ink: the hero's primary button was invisible in
     light mode. It now reads light-on-dark, which is what it always looked
     like in dark mode. */
  --paper: var(--canvas);
  /* the paper-safe variant is unreadable here; use the bright signal. The
     mid band clears 3:1 against the deep one but not 4.5:1, and this token
     ends up on small text, so it is lifted a step for inverted surfaces. */
  --signal: #8FC6E0;
  --signal-deep: var(--signal);
  --accent: var(--signal);
  --accent-ink: var(--signal);
  --brand-ink: var(--signal);
  --accent-soft: rgba(100, 163, 193, .14);
  --brand-soft: rgba(234, 242, 244, .1);
  color: var(--ink);
}

/* Buttons and badges inside an inverted surface. */
.hero-dark .btn.secondary,
.exam-count .btn.secondary,
.cta-final .btn.secondary { color: var(--ink); border-color: var(--line); }
.hero-dark .btn.ghost,
.exam-count .btn.ghost,
.cta-final .btn.ghost { color: var(--ink-2); }
.hero-dark .btn.ghost:hover,
.exam-count .btn.ghost:hover,
.cta-final .btn.ghost:hover { background: rgba(234, 242, 244, .1); color: var(--ink); }
.hero-dark .badge,
.exam-count .badge,
.demo .badge { background: rgba(234, 242, 244, .1); color: var(--ink-2); border-color: transparent; }
.hero-dark .count-note,
.exam-count .count-note { color: var(--ink-3); }


/* ═══════════════════════════════════════════════ teaching explanation */

.pr-why.teach { padding: 0; background: transparent; }
.pr-why.teach .pr-why-t {
  display: block; padding: 11px 14px;
  background: var(--surface); border-radius: 10px 10px 0 0;
  border: 1px solid var(--line); border-bottom: 0;
  font-size: .8rem; font-weight: 700; color: var(--ink);
}
.pr-why.teach .pr-why-t [lang="de"] { color: var(--ink-3); font-weight: 600; }

.tx-row {
  display: grid; grid-template-columns: 92px minmax(0, 1fr);
  gap: 12px; align-items: baseline;
  padding: 11px 14px;
  background: var(--surface);
  border: 1px solid var(--line); border-top: 0;
  font-size: .89rem;
}
.tx-row:last-child { border-radius: 0 0 10px 10px; }
.tx-l {
  font-size: .72rem; font-weight: 700; letter-spacing: .01em;
  padding: 2px 0; color: var(--ink-3);
}
.tx-row p { margin: 0; line-height: 1.75; color: var(--ink-2); }
.tx-row.bad .tx-l { color: var(--bad); }
.tx-row.ok .tx-l { color: var(--ok); }
.tx-row.rule .tx-l, .tx-row.applied .tx-l { color: var(--accent-ink); }
html[data-theme="dark"] .tx-row.rule .tx-l,
html[data-theme="dark"] .tx-row.applied .tx-l { color: var(--signal); }
.tx-row.ex { background: var(--paper-2); }
.tx-row.ex p { font-family: var(--font-de); direction: ltr; text-align: left; }
.tx-row.ex b { color: var(--ink); font-weight: 700; }

@media (max-width: 620px) {
  .tx-row { grid-template-columns: 1fr; gap: 3px; padding: 10px 12px; }
}

/* ═══════════════════════════════════════════════ recording indicator

   This was a full-width sentence styled as prose and placed directly under the
   live transcript, so learners read it as something the microphone had heard.
   It is now unmistakably instrumentation: a chip, a meter driven by real input
   level, and a short verdict on whether we can hear anything at all. */

.rec-strip {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  margin-top: 12px; padding: 9px 12px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface);
}
.rec-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .72rem; font-weight: 700; letter-spacing: .02em;
  color: var(--ink-2); white-space: nowrap;
}
.rec-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--bad); flex: none;
  animation: recPulse 1.3s ease-in-out infinite;
}
@keyframes recPulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .45; transform: scale(.82); } }
@media (prefers-reduced-motion: reduce) { .rec-dot { animation: none; } }

/* Twelve bars, lit in proportion to measured input level. */
.rec-meter { display: inline-flex; align-items: flex-end; gap: 2px; height: 16px; flex: none; }
.rec-meter i {
  display: block; width: 3px; height: 5px; border-radius: 1px;
  background: var(--line);
  transition: height .08s linear, background .08s linear;
}
.rec-meter i.on { background: var(--signal-deep); height: 14px; }
.rec-meter i:nth-child(n+9).on { background: var(--bad); }

.rec-hear { font-size: .72rem; font-weight: 600; color: var(--ink-3); }
.rec-hear.ok { color: var(--signal-deep); }
.rec-hear.warn { color: #B4541A; }
html[data-theme="dark"] .rec-hear.ok { color: var(--signal); }
html[data-theme="dark"] .rec-hear.warn { color: #F2A25C; }

.rec-note { font-size: .7rem; color: var(--ink-3); margin-inline-start: auto; }
@media (max-width: 620px) { .rec-note { display: none; } }

/* Captioned, so nothing else in this column can be taken for the transcript. */
.sp-livewrap {
  margin-top: 10px; padding: 10px 12px;
  border: 1px dashed var(--line); border-radius: 10px;
  background: var(--surface);
}
.sp-livecap {
  display: block; font-size: .68rem; font-weight: 700;
  color: var(--ink-3); margin-bottom: 4px;
}
.sp-live {
  margin: 0; font-family: var(--font-de);
  font-size: .95rem; color: var(--ink); line-height: 1.5;
}

.mic-btn.listening { background: var(--bad); color: #fff; }
html[data-theme="dark"] .mic-btn.listening { color: #fff; }

/* ════════════════════════════════════════════ explain control + gloss */

.xp-ico { width: 12px; height: 12px; flex: none; }
.xp-label { font-size: .64rem; font-weight: 700; }
/*
 * The label survives on a phone. The old button was "A⇄ع", which could just
 * about be guessed from the two scripts in it; a lightbulb on its own says
 * nothing at all, and a phone is where most of these readers are.
 */
@media (max-width: 620px) { .xp-btn { padding: 0 6px; } }

/* Tappable words must look tappable before anyone explains them. */
.gw {
  cursor: pointer;
  border-bottom: 1.5px dotted color-mix(in srgb, var(--signal-deep) 70%, transparent);
  border-radius: 2px;
  transition: background .12s var(--ease);
}
.gw:hover { background: var(--signal-wash); }
html[data-theme="dark"] .gw:hover { background: rgba(100, 163, 193, .14); }
.gw-demo {
  background: var(--signal-wash);
  box-shadow: 0 0 0 3px var(--signal-wash);
  border-radius: 3px;
  animation: gwBlink 1.6s ease-in-out 3;
}
@keyframes gwBlink { 0%, 100% { background: var(--signal-wash); } 50% { background: color-mix(in srgb, var(--signal) 45%, transparent); } }
@media (prefers-reduced-motion: reduce) { .gw-demo { animation: none; } }

.gloss-onboard {
  display: flex; align-items: flex-start; gap: 12px;
  background: var(--canvas);
  border-radius: 12px; padding: 14px 16px; margin-bottom: 14px;
  animation: trIn .22s var(--ease) both;
}
.gloss-onboard b { display: block; font-size: .93rem; margin-bottom: 3px; color: var(--ink); }
.gloss-onboard p { margin: 0; font-size: .85rem; line-height: 1.7; color: var(--ink-2); }
.gloss-onboard > div { flex: 1; min-width: 0; }
.go-arrow { color: var(--signal); font-size: 1.1rem; line-height: 1.4; }
.go-ico {
  font-family: var(--font-de); font-size: .76rem; font-weight: 700;
  background: rgba(234, 242, 244, .14); color: var(--signal);
  padding: 1px 6px; border-radius: 5px; display: inline-block;
}
/* The one action on this card, so it is filled rather than outlined — a ghost
   button next to two lines of explanation reads as optional, and a reader who
   does not dismiss this keeps seeing it. */
.go-x {
  flex: none; align-self: center;
  border: 1px solid transparent; background: var(--signal);
  color: var(--canvas); border-radius: 8px; padding: 6px 14px;
  font: inherit; font-size: .8rem; font-weight: 800; cursor: pointer;
  box-shadow: 0 0 12px color-mix(in srgb, var(--signal) 50%, transparent);
  transition: box-shadow .15s var(--ease), filter .15s var(--ease);
}
.go-x:hover {
  filter: brightness(1.08);
  box-shadow: 0 0 18px color-mix(in srgb, var(--signal) 75%, transparent);
}
.go-x:focus-visible { outline: 2px solid var(--paper); outline-offset: 2px; }

/* ═════════════════════════════════════ Lesen: text scrolls, answers stay */

/* The reading column gets its own scroll so the answer rail beside it never
   leaves the screen — the drag target is always in view. */
.exercise-layout .panel.reading-scroll {
  max-height: calc(100vh - 130px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}
.exercise-layout .panel.reading-scroll::-webkit-scrollbar { width: 8px; }
.exercise-layout .panel.reading-scroll::-webkit-scrollbar-thumb {
  background: var(--line); border-radius: 8px;
}

.sticky-side .option-bank,
.sticky-side .word-bank,
.sticky-side .gap-list { max-height: calc(100vh - 300px); }

.answers-hint {
  font-size: .78rem; color: var(--ink-3);
  margin: -6px 0 12px;
}

@media (max-width: 960px) {
  .exercise-layout .panel.reading-scroll { max-height: none; overflow: visible; }
  /* On small screens a fixed rail would eat the screen, so the answers dock
     to the bottom instead and stay reachable while the text scrolls. */
  .exercise-layout .sticky-side {
    position: sticky; bottom: 0; top: auto;
    z-index: 40;
    max-height: 46vh; overflow-y: auto;
    box-shadow: 0 -8px 24px -12px rgba(12, 13, 12, .3);
  }
  html[data-theme="dark"] .exercise-layout .sticky-side { box-shadow: 0 -8px 24px -12px rgba(0, 0, 0, .7); }
  html[data-theme="dark"] .exercise-layout[data-sprechen] .sticky-side { box-shadow: none; }
  .sticky-side .option-bank, .sticky-side .word-bank, .sticky-side .gap-list { max-height: none; }
  /* Docking is for answers the learner reaches for while reading the passage
     above them. The Sprechen column holds the Teil list — reference, read once
     — so pinning it to the bottom of a phone gives a short static list its own
     scrollbar and takes 46vh of the screen to do it. */
  .exercise-layout[data-sprechen] .sticky-side {
    position: static; bottom: auto; top: auto;
    max-height: none; overflow-y: visible;
    box-shadow: none; z-index: auto;
  }
  body { padding-bottom: 68px; }
}

/* ═══════════════════════════════════════════ bilingual concept labels */

.cl { display: inline-flex; flex-direction: column; gap: 1px; }
.cl b { font-size: inherit; }
.cl em {
  font-style: normal; font-family: var(--font-de); direction: ltr;
  font-size: .78em; color: var(--ink-3); font-weight: 600;
}
.badge .cl { flex-direction: row; gap: 5px; align-items: baseline; }
.badge .cl em { font-size: .9em; opacity: .8; }

.cl-de { display: block; font-style: normal; font-family: var(--font-de); direction: ltr; font-size: .72rem; color: var(--ink-3); font-weight: 600; }


/* ═══════════════════════════════════════════════════════ exam sizing

   Density. A Prüfung is read closely, but the page was spending the screen on
   everything except the exam: on a 375px phone the first question began at
   y=1194, and 327 of those pixels were heading, crumbs, quicknav and
   instructions stacked above a passage that had not started yet. On a 1440px
   desktop the passage column was 855px inside a 1360px shell while the prose
   ran to 1546px against a 782px window, so the reader scrolled a narrow
   column inside a page that had room to spare.

   So: the shell is wider, the padding is smaller, the type is a step down but
   still sized for sustained reading, and the chrome above the exam is cut to
   what earns its height. Everything is scoped to .exam-wrap, so no other page
   moves. */

.exam-wrap { max-width: 1560px; }

.exam-wrap .exercise-layout {
  /* Same swap as above: answers in column 1 (right), passage in column 2.
     The passage takes more of the extra width than the answers do — options
     are short lines that only get harder to scan as they stretch. */
  /* The passage column used to take 1.85fr against .72. At the passage's
     size that is about 91 characters a line — the answers were not using
     the width and the text could not afford it. All but even now, so the
     answer panel reads as the passage's counterpart rather than a rail
     beside it, and the measure lands near 65. */
  grid-template-columns: minmax(320px, 1fr) minmax(0, 1.1fr);
  gap: 16px;
}

/* Lesen Teil 2 is the one layout where the answers are full sentences in
   three-option blocks, not short labels, so the reasoning above inverts:
   the passage has a capped measure and does not use extra width, while the
   options do. Only this exercise type has .q-card.
   Inside a min-width query because :has() outranks the plain descendant
   selector the stacking rule uses — without it this kept two tracks alive
   on a phone, pinning both panels to the 320px minimum and leaving a dead
   strip beside them. */
@media (min-width: 961px) {
  .exam-wrap .exercise-layout:has(.q-card) {
    grid-template-columns: minmax(320px, 1fr) minmax(0, 1.06fr);
  }
}

.exam-wrap .panel { padding: 16px 18px; }
.exam-wrap .panel + .panel { margin-top: 10px; }

/* The chrome above the exam. The title is already in the tab, the crumbs and
   the quicknav; at 2.4rem it was repeating itself in the largest type on the
   page. */
.exam-wrap .crumbs { padding-top: 12px; }

/*
 * One row, not four.
 *
 * The head stacked a badge row, an h1 and a question count as three full-width
 * blocks, so on a wide screen the title sat alone on the right of 1500px of
 * nothing and the count sat alone under it. Nothing is removed — level,
 * section, name and count are all still there — they just share a line and
 * sit at its two ends instead of each claiming a row of their own.
 */
.exam-wrap .page-head {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 14px;
  padding: 6px 0 10px; margin-bottom: 12px;
}
.exam-wrap .page-head h1 {
  margin: 0; font-size: clamp(1.25rem, 2.2vw, 1.6rem);
  flex: 0 1 auto; min-width: 0;
}
/* The badges follow the name; the count is pushed to the far end of the row. */
.exam-wrap .page-head .meta { margin: 0; }
.exam-wrap .page-head .meta:last-child { margin-inline-start: auto; }
.exam-wrap .instructions {
  font-size: .88rem; line-height: 1.6; padding: 9px 12px; margin-bottom: 11px;
}

/* The German passage itself. */
.exam-wrap .prose { font-size: 1.01rem; line-height: 1.62; }
.exam-wrap .prose p { margin: 0 0 .72em; }
.exam-wrap .prose h2 { font-size: 1.08rem; }
.exam-wrap .prose h3, .exam-wrap .prose h4 { font-size: 1rem; }

/* Everything the learner has to act on: options, chips, gaps, headings. */
.exam-wrap .chip { font-size: .91rem; padding: 6px 11px; }
.exam-wrap .opt, .exam-wrap .option { font-size: .93rem; line-height: 1.45; padding: 6px 9px; }
.exam-wrap .opt input { margin-top: 3px; }
.exam-wrap .q-card { padding: 10px 12px; margin-bottom: 8px; }
.exam-wrap .q-card h3 { margin: 0 0 6px; font-size: .9rem; line-height: 1.4; }
.exam-wrap .option-bank { gap: 5px; }
.exam-wrap .match-item { padding-bottom: 4px; }
.exam-wrap .solution { font-size: .88rem; }
.exam-wrap .answers-hint { font-size: .78rem; }
.exam-wrap .panel h2 { font-size: .98rem; margin: 0 0 9px; }

/* Taller reading viewport, since the shell above it is shorter than it was. */
.exam-wrap .exercise-layout .panel.reading-scroll { max-height: calc(100vh - 92px); }

/*
 * The pager was justify-content: space-between across the full shell, which at
 * 1560px put "previous" and "next" a metre apart with a void between them.
 * They sit together now, centred, where a thumb or a cursor can reach both.
 */
.exam-wrap .pager { justify-content: center; gap: 10px; margin: 18px 0 8px; }

/* ── Arabic under the Optionen ──────────────────────────────────────────
   The German keeps its weight and its place; the Arabic sits under it a step
   smaller and lighter, the same relationship the Redemittel use. It is a
   second line inside the same label, so clicking it still selects the answer
   rather than doing nothing. */
.opt { flex-wrap: wrap; }
/*
 * Quiet, but not invisible.
 *
 * The first version was .84em in --ink-3: 12.5px of grey under 14.9px of
 * near-black, the smallest and faintest thing in the panel. It passed contrast
 * at 6.3:1 and was still missed entirely by the first person to look for it,
 * which is the only test that counts. It stays subordinate to the German —
 * that is the point of the pairing — but it is now a step larger, a step
 * darker, and separated from the line above rather than tucked under it.
 */
.opt-ar {
  flex: 1 0 100%;
  font-family: var(--font-ar, inherit);
  font-size: .92em; line-height: 1.5; color: var(--ink-2);
  direction: rtl; text-align: right;
  margin-top: 4px; padding-inline-start: 22px;
}
.opt.is-correct .opt-ar, .opt.is-wrong .opt-ar { color: inherit; opacity: .75; }
body.opt-ar-off .opt-ar { display: none; }

/* The question in Arabic, under the German question it belongs to. */
.q-ar {
  margin: 3px 0 9px; font-size: .86rem; line-height: 1.5;
  color: var(--ink-2); direction: rtl; text-align: right;
}
body.opt-ar-off .q-ar { display: none; }

/* A gap's three choices sit side by side, so each keeps its Arabic inside
   itself on a second line rather than pushing the row apart. */
.opts .chip { display: inline-flex; flex-direction: column; align-items: flex-start; }
.opts .chip .opt-ar { padding-inline-start: 0; margin-top: 2px; font-size: .8em; }

/* A chip is a button, so its Arabic goes inside it on a line of its own. */
.option-bank .chip { display: block; width: 100%; text-align: start; }
.option-bank .chip .opt-ar { display: block; padding-inline-start: 0; margin-top: 2px; }

.opt-ar-bar { display: flex; justify-content: flex-end; margin: 0 0 7px; }
.opt-ar-btn {
  background: none; border: 1px solid var(--line); color: var(--ink-3);
  border-radius: 100px; padding: 3px 11px; font: inherit; font-size: .74rem;
  font-weight: 700; cursor: pointer;
}
.opt-ar-btn:hover:not(:disabled) { color: var(--ink); border-color: var(--ink-3); }
.opt-ar-btn:disabled { opacity: .6; cursor: default; }
.opt-ar-note { font-size: .74rem; color: var(--ink-3); line-height: 1.5; }

@media (max-width: 960px) {
  .exam-wrap .exercise-layout { grid-template-columns: 1fr; }
  .exam-wrap .panel { padding: 12px 13px; }
  .exam-wrap .prose { font-size: .99rem; }
  /*
   * On a phone the passage is given a window instead of the whole page.
   *
   * Stacked, the passage sits above the questions, so its full height is
   * something the reader scrolls past before reaching anything to answer —
   * the first question began at y=1119 on a 812px screen. Capped at 62vh it
   * still shows a solid block of German and scrolls inside itself, and the
   * questions start about one screen in rather than one and a half.
   */
  .exam-wrap .exercise-layout .panel.reading-scroll {
    max-height: 62vh; overflow-y: auto; overscroll-behavior: contain;
  }
  /*
   * The head said everything three times. The crumbs read
   * "الرئيسية / B2 / Lesen Teil 2 / Puppenmacher", the badge row underneath
   * repeated "B2" and "Lesen Teil 2", and the h1 repeated "Puppenmacher".
   * On a phone that is a screenful of the same four words. The two German
   * badges go; the exercise-type badge stays, because it is the only thing
   * in the row the crumbs do not already say.
   */
  .exam-wrap .page-head .meta .badge.de { display: none; }

  /* On a phone every pixel above the passage is a pixel of scrolling. */
  .exam-wrap .crumbs { padding-top: 9px; font-size: .76rem; }
  .exam-wrap .page-head { padding: 6px 0 9px; margin-bottom: 10px; gap: 4px 10px; }
  .exam-wrap .instructions { margin-bottom: 9px; padding: 8px 11px; }
}


/* ═════════════════════════════════════ the exam paper inside the simulator

   Embedded in the exam shell, the page heading repeats what the simulator's
   own bar already says — level, section, title — and cost 146px of height
   between the learner and the paper. Hidden here, both columns then scroll
   independently against the shell, so the passage and the answers stay side
   by side for the whole section. */

html[data-embed] .exam-wrap .page-head { display: none; }
html[data-embed] .exam-wrap .instructions { margin-bottom: 12px; }

html[data-embed] .exam-wrap .exercise-layout .panel.reading-scroll {
  max-height: calc(100vh - 120px);
}
html[data-embed] .exam-wrap .sticky-side {
  position: sticky; top: 0;
  max-height: calc(100vh - 120px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

@media (max-width: 960px) {
  html[data-embed] .exam-wrap .exercise-layout .panel.reading-scroll,
  html[data-embed] .exam-wrap .sticky-side {
    max-height: none; overflow: visible; position: static;
  }
}

/* ═══════════════════════════════════ Sprechen Teil 2 reading text

   Teil 2 starts from a magazine article the candidate reads before
   discussing it. It is marked up like the Lesen passages, so it inherits the
   same tap-a-word gloss and translate affordances rather than inventing a
   second reading experience. */

.sp-stimulus { margin-bottom: 18px; }
.sp-stim-instruction {
  margin: 0 0 14px; padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-de); font-size: .88rem; line-height: 1.65;
  color: var(--ink-3);
}
/* The German passage, folded. The label carries the meaning — an arrow alone
   says "something is here" without saying what — and the arrow turns a quarter
   when it opens so the state is readable at a glance. */
.sp-stim-fold { margin-top: 6px; }
/* Lifted with the site's own signal wash, the same treatment the plan notice
   and the activation instruction use. A plain bordered row read as decoration
   and said nothing about what pressing it would do — and the text behind it is
   the thing the exam is about, so it is not a row to skim past. */
.sp-stim-fold > summary {
  display: flex; align-items: center; gap: 11px;
  padding: 13px 15px; border-radius: 14px;
  border: 2px solid var(--signal);
  background: var(--signal-wash); color: var(--accent-ink);
  cursor: pointer; list-style: none; user-select: none;
  /* Lit rather than outlined. This is the text the exam is about and it ships
     folded on a phone, so a visitor who does not notice it never reads the
     thing they came for. Steady, not pulsing — the glow is strong enough to
     find without moving, and movement in the middle of a reading task is the
     kind of thing that gets a page closed. */
  box-shadow:
    0 0 20px color-mix(in srgb, var(--signal) 70%, transparent),
    0 0 4px color-mix(in srgb, var(--signal-deep) 35%, transparent),
    inset 0 0 14px color-mix(in srgb, var(--signal) 20%, transparent);
}
/* Opened, it settles: once a reader has found it, it no longer needs to
   announce itself. */
.sp-stim-fold[open] > summary {
  animation: none;
  border-color: color-mix(in srgb, var(--signal) 55%, transparent);
  box-shadow: none;
}

.sp-stim-fold > summary::-webkit-details-marker { display: none; }
.sp-stim-fold > summary:hover { border-color: var(--signal-deep); }
.sp-stim-fold > summary:focus-visible { outline: 2px solid var(--signal-deep); outline-offset: 2px; }
.sp-stim-sum-t { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sp-stim-sum-t b { font-weight: 800; font-size: .92rem; }
/* The hint carries the instruction, and swaps with the state — a reader should
   never have to work out what the arrow means. */
.sp-stim-sum-h { font-size: .78rem; color: var(--accent-ink); opacity: .78; line-height: 1.5; }
.sp-stim-fold > summary .sum-open { display: none; }
.sp-stim-fold[open] > summary .sum-closed { display: none; }
.sp-stim-fold[open] > summary .sum-open { display: inline; }
.sp-stim-chev {
  flex: none; color: var(--signal-deep); font-size: 1.05rem; line-height: 1;
  transition: transform .16s var(--ease);
}
/* Pointing down when there is more to see, up once it is open. */
.sp-stim-fold[open] > summary .sp-stim-chev { transform: rotate(180deg); }
.sp-stim-fold[open] > summary { margin-bottom: 4px; }

.sp-stim-title {
  margin: 0 0 14px !important;
  font-family: var(--font-read); font-size: 1.35rem; font-weight: 700;
  color: var(--ink); text-align: center;
}
/* The exam text on a Sprechen page is read the same way as a Lesen passage
   and gets the same setting — Cairo at 19px on 1.95 leading. It is not
   inside .exam-wrap, so it needs saying here rather than inheriting. */
/* [lang] in the selector so this beats `.prose[lang="de"]`, which sets the
   serif reading face and would otherwise win on specificity. */
.sp-stim-text[lang="de"] {
  font-family: var(--font-ar);
  font-weight: 600;
  /* A step below the Lesen passage. A Sprechen stimulus is a short prompt
     you glance back at while speaking, not a page you settle into, and at
     19px it dominated the screen the exam turns need. */
  font-size: 1.06rem; line-height: 1.82;
  letter-spacing: 0;
  color: var(--ink);
}
.sp-stim-text p { margin: 0 0 1em; }
.sp-stim-text p:last-child { margin-bottom: 0; }

/* Teil 3 restates its task in the rail so it can be re-read while planning. */
.sp-task {
  margin: 0 0 12px; padding: 10px 12px;
  border-inline-start: 3px solid var(--signal-deep);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--surface-2, var(--surface));
  font-family: var(--font-de); font-size: .84rem; line-height: 1.6;
  color: var(--ink-2);
}

/* The telc requirements for one Teil. Collapsed by default: a learner who
   already knows the format should not have to scroll past it every session,
   and one who does not is a tap away. */
.sp-telc { margin: 0 0 12px; }
.sp-telc > summary {
  cursor: pointer; list-style: none;
  padding: 7px 10px; border-radius: var(--radius-sm);
  background: var(--surface-2, var(--surface));
  border-inline-start: 3px solid var(--signal);
  font-size: .82rem; font-weight: 600; color: var(--ink-2);
}
.sp-telc > summary::-webkit-details-marker { display: none; }
.sp-telc > summary::after {
  content: '+'; float: inline-end; opacity: .6; font-weight: 700;
}
.sp-telc[open] > summary::after { content: '−'; }
.sp-telc > summary:hover { color: var(--ink); }
.sp-telc > summary:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }
.sp-telc-list {
  margin: 8px 0 0; padding-inline-start: 20px;
  font-size: .82rem; line-height: 1.75; color: var(--ink-2);
}
.sp-telc-list li { margin: 0 0 5px; }

@media (max-width: 620px) {
  .sp-stim-title { font-size: 1.15rem; }
  .sp-stim-text[lang="de"] { font-size: 1rem; line-height: 1.78; }
}


/* The two speakers in a B1 Teil 2 theme read as labelled positions rather
   than as another paragraph of the same argument. */
.sp-speaker {
  margin: 0 0 .3em !important;
  font-size: .84rem; color: var(--ink-3);
}
.sp-speaker b { color: var(--ink-2); font-weight: 700; }
.sp-speaker + p { margin-top: 0; }
.sp-stim-text p + .sp-speaker { margin-top: 1.2em !important; }


/* The seven Teil 1 themes: a list to choose from, not prose. */
.sp-stim-kicker {
  display: block; margin-bottom: 10px;
  font-size: .7rem; font-weight: 700; letter-spacing: .04em;
  /* signal-deep is only 2.80 on the panel surface at this size. */
  color: var(--accent-ink); text-transform: uppercase;
}
.sp-themen {
  margin: 0; padding-inline-start: 20px;
  font-family: var(--font-de); direction: ltr; text-align: left;
}
.sp-themen li { margin: 0 0 .55em; line-height: 1.55; font-size: .98rem; color: var(--ink); }
.sp-themen li:last-child { margin-bottom: 0; }
.sp-themen em { font-style: normal; color: var(--ink-3); font-size: .9em; }
.sp-teil1 { border-inline-start: 3px solid var(--signal-deep); }

@media (max-width: 620px) { .sp-themen li { font-size: .93rem; } }


/* ═══════════════════════════════ Sprechen access states

   A learner should be able to read the list once and know where they stand:
   what they may practise now, what they have finished, and what their plan has
   not opened. A finished theme is dimmed but never hidden — it is still worth
   opening to reread the feedback. */

.sp-state:empty { display: none; }
.sp-state.practice { background: var(--ok-bg); color: var(--ok); border-color: transparent; }
.sp-state.review   { background: var(--surface-2); color: var(--muted); border-color: transparent; }
.sp-state.locked   { background: var(--surface-2); color: var(--muted); border-color: transparent; }

.card.sp-locked { opacity: .9; }
.card.sp-locked:hover { transform: none; border-color: var(--line); }
.card.sp-review { opacity: .82; }

.sp-summary { margin: 0 0 18px; }
/* It renders nothing until there is progress to report, and an empty box with
   a margin is just a gap nobody asked for. */
.sp-summary:empty { margin: 0; }
.sp-sum {
  display: block; padding: 12px 15px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); font-size: .88rem; line-height: 1.7; color: var(--ink-2);
}
.sp-sum b { color: var(--ink); }
.sp-sum.pro { border-inline-start: 3px solid var(--signal-deep); }

.sp-gate { margin-bottom: 16px; padding: 18px; border-radius: var(--radius); background: var(--surface-2); }
.sp-gate h2 { margin: 0 0 8px; font-size: 1.05rem; font-weight: 800; }
.sp-gate p { margin: 0 0 12px; font-size: .9rem; line-height: 1.7; color: var(--ink-2); }
.sp-gate.locked { text-align: center; }
.sp-gate .locked-actions { display: flex; flex-wrap: wrap; gap: 9px; justify-content: center; }

.sp-review-panel { margin-bottom: 16px; }
.sp-review-head { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-bottom: 14px; }
.sp-review-kicker { font-size: .74rem; font-weight: 700; color: var(--muted); }
.sp-review-score { display: flex; align-items: baseline; gap: 7px; }
.sp-review-score b { font-family: var(--font-de); font-size: 1.5rem; font-weight: 800; }
.sp-review-score.pass b { color: var(--ok); }
.sp-review-score.fail b { color: var(--bad); }
.sp-review-score span { font-size: .82rem; color: var(--muted); }
.sp-review-dq { padding: 10px 13px; border-radius: var(--radius-sm); background: var(--bad-bg); color: var(--bad); font-size: .86rem; }
.sp-review-crit { display: grid; gap: 9px; margin-bottom: 16px; }
.sp-rc { padding: 10px 13px; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.sp-rc-l { font-size: .84rem; font-weight: 700; }
.sp-rc-p { float: inline-end; font-family: var(--font-de); font-size: .84rem; color: var(--muted); }
.sp-rc p { margin: 5px 0 0; font-size: .8rem; line-height: 1.65; color: var(--ink-3); }
.sp-review-h { margin: 0 0 10px; font-size: .95rem; font-weight: 700; }
.sp-review-talk { display: grid; gap: 9px; }
.sp-rt { padding: 10px 13px; border-radius: var(--radius-sm); background: var(--surface-2); }
.sp-rt.me { background: var(--brand-soft); }
.sp-rt-who { font-size: .7rem; font-weight: 700; color: var(--muted); }
.sp-rt p { margin: 3px 0 0; font-family: var(--font-de); font-size: .9rem; line-height: 1.6; }


/* On a phone the columns stack, and the free plan would otherwise push the
   recommended one below the fold. Lead with the recommendation; free is still
   one scroll away. */
@media (max-width: 900px) {
  .plan-grid.three .plan.featured { order: -1; }
}

/* Match yourself to a plan before reading any feature table. */
.plan-pick {
  margin: 0 0 26px; padding: 16px 18px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.plan-pick-h { display: block; font-size: .8rem; font-weight: 800; color: var(--muted); margin-bottom: 10px; }
.plan-pick ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.plan-pick li { font-size: .88rem; line-height: 1.7; color: var(--ink-2); }
.plan-pick li b { color: var(--ink); }

/* The same card, carrying an ordered list instead of a set of choices: the
   numbers matter here because the steps only work in this order — the account
   has to exist before there is anything to activate. */
.plan-pick.steps ol {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 12px; counter-reset: step;
}
.plan-pick.steps li {
  counter-increment: step;
  position: relative; padding-inline-start: 34px;
  font-size: .88rem; line-height: 1.7; color: var(--ink-2);
}
.plan-pick.steps li::before {
  content: counter(step);
  position: absolute; inset-inline-start: 0; top: 1px;
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--font-de); font-size: .76rem; font-weight: 700;
  background: var(--signal-wash); color: var(--accent-ink);
}
.plan-pick.steps li b { display: block; color: var(--ink); }
.plan-steps-note {
  margin: 14px 0 0; padding-top: 12px; border-top: 1px solid var(--line-soft);
  font-size: .78rem; line-height: 1.65; color: var(--ink-3);
}

/* ═══════════════════════════════════════════ pricing, ranked

   The card reads in decision order: who it is for, the price, the call to
   action, then what you get — the two or three things that matter set apart
   from the rest, and every line explained in one sentence. */

.plan-for {
  margin: 0 0 16px; padding: 8px 12px;
  border-radius: var(--radius-sm); background: var(--surface-2);
  font-size: .82rem; line-height: 1.55; color: var(--ink-2); text-align: center;
}

.plan-his { list-style: none; margin: 20px 0 4px; padding: 0; display: grid; gap: 11px; }
.plan-hi {
  padding: 11px 13px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2);
  border-inline-start: 3px solid var(--brand);
}
.plan-hi-t { display: block; font-size: .9rem; font-weight: 800; color: var(--ink); margin-bottom: 3px; }
.plan-hi-w { display: block; font-size: .8rem; line-height: 1.65; color: var(--ink-2); }
.plan.featured .plan-hi { border-inline-start-color: var(--signal-deep); }

.plan-feats .feat-body { display: block; }
.plan-feats .feat-body b { display: block; font-size: .87rem; font-weight: 700; line-height: 1.5; }
.plan-feats .feat-body em {
  display: block; margin-top: 2px;
  font-style: normal; font-size: .77rem; line-height: 1.6; color: var(--muted);
}
.plan-feats li.no .feat-body b { color: var(--muted); font-weight: 600; }
.plan-feats li.no .feat-body em { color: var(--ink-3); }

@media (max-width: 620px) {
  .plan-hi { padding: 10px 11px; }
  .plan-hi-t { font-size: .86rem; }
}


/* Says the listing is scoped to one level, and offers the way back. Without
   it a learner scoped to B2 sees no B1 and no reason why. */
.scope-note:empty { display: none; }
.scope-note {
  margin: 0 0 10px; padding: 9px 13px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: .82rem; line-height: 1.6; color: var(--ink-2);
}
.scope-note .link-btn {
  border: 0; background: none; padding: 0;
  font: inherit; font-weight: 700; color: var(--brand-ink);
  cursor: pointer; text-decoration: underline;
}


/* ═══════════════════════════════ marking a speaking exam

   Correcting takes a few seconds of real work. The steps shown are the ones
   actually being performed, so the wait reads as progress rather than as a
   page that has stopped responding. */

.sp-analyzing {
  padding: 34px 24px; text-align: center;
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface);
}
.sp-analyzing h3 { margin: 14px 0 6px; font-size: 1.1rem; font-weight: 800; color: var(--ink); }
.sp-analyzing-step { margin: 0; font-size: .88rem; color: var(--ink-2); min-height: 1.4em; }

.sp-spinner {
  width: 30px; height: 30px; margin: 0 auto;
  border: 3px solid var(--line);
  border-top-color: var(--brand);
  border-radius: 50%;
  animation: spSpin .8s linear infinite;
}
.sp-spinner.sm {
  width: 14px; height: 14px; border-width: 2px; margin: 0;
  display: inline-block; vertical-align: -2px; margin-inline-end: 7px;
}
@keyframes spSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .sp-spinner { animation-duration: 2.4s; }
}

.sp-analyzing-list {
  list-style: none; margin: 20px auto 0; padding: 0;
  max-width: 300px; text-align: start;
}
.sp-analyzing-list li {
  position: relative; padding-inline-start: 24px; margin-bottom: 9px;
  font-size: .82rem; color: var(--ink-3);
  transition: color .25s ease;
}
.sp-analyzing-list li::before {
  content: '○'; position: absolute; inset-inline-start: 0; color: var(--ink-3);
}
.sp-analyzing-list li.on { color: var(--ink); }
.sp-analyzing-list li.on::before { content: '●'; color: var(--brand-ink); }

.sp-refining {
  margin: 0 0 14px; padding: 9px 13px;
  border-radius: var(--radius-sm);
  background: var(--brand-soft); color: var(--brand-ink);
  font-size: .82rem; line-height: 1.5;
}

@media (max-width: 620px) {
  .sp-analyzing { padding: 26px 16px; }
}


/* ---------------------------------------------------------- locked preview

   Paid sections stay on screen. The learner sees the real thing — the set
   names, the counts, the exercise text — dimmed and inert, with the invitation
   above it. Hiding the feature entirely sells nothing; showing it and asking
   for the subscription is the whole point.

   The dimming is deliberately mild. Blur it too hard and the preview stops
   being evidence of anything. */
.locked-preview {
  position: relative;
  margin-top: 14px;
  /* Readable, not ghostly. The lock is communicated by the panel above and the
     🔒 badges — not by making the content too faint to evaluate. Someone
     deciding whether to pay has to be able to actually read what they would
     be paying for. */
  opacity: .92;
  pointer-events: none;      /* mouse; the keyboard is handled by inert */
  user-select: none;
  cursor: pointer;
}
.locked-preview > * { pointer-events: none; }
/* One door left open. A signed-out reader may press the start button — it asks
   for an account rather than starting anything — so the panel's blanket
   pointer-events rule is lifted for that button alone. */
.locked-preview.lock-live-start .actions,
.locked-preview.lock-live-start [data-sp-start] { pointer-events: auto; }
.locked-preview.lock-live-start [data-sp-start] { cursor: pointer; }

/* Said once, after three finished exercises, to an account that has shown it
   is here to work. Sits inside the result box so it reads as the next step
   rather than as an interruption of one. */
.plan-nudge {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 13px;
  border-top: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
}
.plan-nudge p { margin: 0; font-size: .88rem; line-height: 1.6; }
.plan-nudge .btn { margin-inline-start: auto; }
@media (max-width: 560px) {
  .plan-nudge .btn { margin-inline-start: 0; width: 100%; justify-content: center; }
}

.locked-cta-line { margin: 10px 0 0; font-size: .95rem; }

/* A single locked set inside an otherwise usable menu — the trial case, where
   Artikel and Redewendungen are open and the other two are not. */
.train-card.is-locked { opacity: .9; }
.train-lock {
  display: inline-block; margin-top: 8px;
  font-size: .74rem; font-weight: 600;
  color: var(--ink-2); white-space: nowrap;
}


/* The transcript handed back when marking fails. Selectable and scrollable:
   the learner has just spoken for ten minutes and the words are the one thing
   that must not be lost. */
.sp-raw {
  margin: 12px 0 0; padding: 12px 14px;
  max-height: 320px; overflow: auto;
  border-radius: var(--radius-sm);
  background: var(--surface-2, var(--surface));
  font-family: var(--font-de); font-size: .86rem; line-height: 1.7;
  white-space: pre-wrap; user-select: text;
}


/* --------------------------------------------- German characters bar

   Sits directly above the text box so the connection is obvious. Large enough
   to hit with a thumb — 40px is the usual floor for a touch target, and this
   is used most by learners typing on a phone with a French or Arabic layout. */
.umlaut-bar {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 0 0 8px;
}
.umlaut-key {
  min-width: 42px; min-height: 40px;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2, var(--surface));
  color: var(--ink);
  font-family: var(--font-de);
  font-size: 1.05rem; font-weight: 600; line-height: 1;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.umlaut-key:hover { border-color: var(--accent); }
.umlaut-key:active { background: var(--accent); color: #111; }
.umlaut-key:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }


/* ------------------------------------------------- mobile menu (top left)

   The bottom bar carries the four things opened daily; everything else lives
   here. On an RTL page the header's action group sits at the visual left, so
   the button placed last in it lands at the top-left corner, which is where
   this menu was asked for.

   The panel slides from the same side as its button rather than from the RTL
   start edge — a drawer that opens away from the control that summoned it
   reads as a different surface. */
.menu-btn { display: none; font-size: 1.15rem; }

.menu-sheet[hidden] { display: none; }
.menu-sheet {
  position: fixed; inset: 0; z-index: 200;
  display: flex;
  /* The document is RTL, so flex-END is the visual left — which is the side
     the button lives on and the side the panel must open from. flex-start
     here would put it on the right, under the brand. */
  justify-content: flex-end;
}
.menu-scrim {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, .45);
  animation: menuFade .16s ease-out;
}
.menu-panel {
  position: relative;
  width: min(84vw, 320px);
  height: 100%;
  overflow-y: auto;
  padding: 14px 14px calc(14px + env(safe-area-inset-bottom));
  background: var(--paper);
  border-inline-start: 1px solid var(--line);
  box-shadow: 0 0 40px rgba(0, 0, 0, .35);
  animation: menuIn .18s ease-out;
  display: flex; flex-direction: column; gap: 2px;
}
@keyframes menuFade { from { opacity: 0 } to { opacity: 1 } }
@keyframes menuIn { from { transform: translateX(-14px); opacity: .6 } to { transform: none; opacity: 1 } }

.menu-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 4px 6px 12px; margin-bottom: 6px;
  border-bottom: 1px solid var(--line);
}
.menu-panel a {
  display: flex; align-items: center; gap: 10px;
  min-height: 46px; padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--ink-2); text-decoration: none; font-size: .95rem;
}
.menu-panel a:hover, .menu-panel a:focus-visible {
  background: var(--surface-2, var(--surface)); color: var(--ink);
}
.menu-i { width: 20px; text-align: center; opacity: .75; }

@media (max-width: 940px) {
  .menu-btn { display: inline-flex; }
  /* The account link is a tab now; two routes to one page is one too many. */
  .site-header .acc-link,
  .site-header .cta-mini { display: none; }

  /* Push the action group to the far edge so the menu button — last in the
     group, and therefore leftmost in RTL — actually reaches the corner it was
     asked to sit in. Without this the level pill leaves it 58px short. */
  .site-header .header-actions { margin-inline-start: auto; }
  .site-header #levelPill { margin-inline-end: auto; }
}
@media (min-width: 941px) {
  /* Desktop keeps the full horizontal nav, so the sheet never opens. */
  .menu-sheet { display: none !important; }
}

/* Sprechen: the level, then the Teile of that level.
   One flat row of eight identical chips — B1, B2, الكل, Teil 1-3 — gave the
   level no authority over the rest, when in fact it decides which three Teile
   even exist. Two controls instead: a segmented level switch, and beneath it
   the Teile of whichever level is on. */
/* 28px of padding and 28px of margin under the lede put 60px of nothing
   between the sentence explaining Sprechen and the controls it is explaining.
   This head carries one heading and one line; it does not need the rule's
   full spacing. */
.sp-head { padding-bottom: 14px; margin-bottom: 16px; }
/* [lang="de"] sets direction:ltr and text-align:start, which throws a German
   heading to the left edge of an RTL page — the title ends up on the opposite
   side from everything beneath it. The word still reads left to right; only
   the block is aligned back to the side the page starts on. */
.sp-head h1 { margin-top: 0; text-align: right; }

.spn-nav { display: grid; gap: 10px; justify-items: center; margin: 0 0 18px; }

/* Both controls share one ground: the brand's two colours drifting slowly
   across each other, rather than a flat wash. The movement is deliberately
   almost too slow to catch — 18 seconds for a full pass — so the bar reads as
   alive when you look at it and never competes with the thing it is sitting
   above. The inset highlight along the top edge is what makes it a surface
   rather than a rectangle of colour. */
.spn-levels, .spn-teile {
  /* A surface, not a sweep. This used to run a near-clear band across the
     bar on an 11-second loop; a strip of the page that never stops moving
     pulls the eye off the exercises below it, and on the dark ground the
     travelling band was brighter than the labels sitting on it. What is
     left is the part that made it read as a surface in the first place: a
     shallow vertical tint and a highlight along the top edge. */
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--signal) 15%, var(--surface)),
    color-mix(in srgb, var(--signal) 23%, var(--surface)));
  border: 1px solid color-mix(in srgb, var(--signal) 26%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent),
              0 1px 3px color-mix(in srgb, var(--signal) 16%, transparent);
}
/* On the dark ground a white inset reads as a crack, not a highlight. */
html[data-theme="dark"] .spn-levels,
html[data-theme="dark"] .spn-teile,
html[data-theme="dark"] .uex-tabs-in {
  box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 8%, transparent),
              0 1px 3px rgb(0 0 0 / .22);
}

/* Pressing one should feel like pressing something. */
@media (prefers-reduced-motion: no-preference) {
  .spn-lv.is-on, .spn-teil.is-on { animation: spn-pop .34s var(--ease); }
  .spn-teil.is-on::after { animation: spn-bar .3s .06s var(--ease) both; }
}
@keyframes spn-pop {
  0% { transform: scale(.9); } 60% { transform: scale(1.04); } 100% { transform: scale(1); }
}
@keyframes spn-bar { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.spn-levels {
  display: inline-flex; gap: 3px; padding: 4px; border-radius: 999px;
}
.spn-lv {
  min-width: 74px; padding: 6px 18px;
  border: 0; border-radius: 999px; background: transparent;
  color: var(--ink-2); font: inherit; font-size: .88rem; font-weight: 800;
  letter-spacing: .01em; cursor: pointer;
  transition: background .18s var(--ease), color .18s var(--ease);
}
.spn-lv:hover:not(.is-on) { color: var(--ink); transform: translateY(-1px); }
.spn-lv:active { transform: scale(.97); }
/* Lit from the top: the tint keeps the dark ink comfortably above AA, which a
   flat --signal only just managed. */
.spn-lv.is-on {
  background: linear-gradient(140deg,
    color-mix(in srgb, #fff 20%, var(--signal)) 0%, var(--signal) 100%);
  color: var(--signal-ink);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--signal) 55%, transparent);
}

/* Auto-fit, not a fixed three: a level with two Teile gets two equal tabs
   rather than two and a hole, and B2 Hören — which carries the Türkei sets
   alongside its own three — wraps to a second row instead of squeezing six
   tabs into a width built for three. */
.spn-teile {
  width: 100%; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(72px, 1fr));
  gap: 4px; padding: 4px; border-radius: 14px;
}
.spn-teil {
  position: relative;
  display: grid; gap: 1px; justify-items: center; align-content: center;
  padding: 7px 6px; border: 0; border-radius: 10px; background: transparent;
  color: var(--ink-2); font: inherit; cursor: pointer;
  transition: background .18s var(--ease), color .18s var(--ease),
              transform .18s var(--ease), box-shadow .18s var(--ease);
}
.spn-teil b { font-size: .87rem; font-weight: 700; }
.spn-teil em { font-style: normal; font-size: .7rem; opacity: .75; }
.spn-teil:hover:not(.is-on) {
  color: var(--ink); background: color-mix(in srgb, var(--ink) 5%, transparent);
  transform: translateY(-1px);
}
.spn-teil:active { transform: scale(.97); }
.spn-teil.is-on {
  background: linear-gradient(180deg, var(--surface) 0%,
    color-mix(in srgb, var(--signal) 7%, var(--surface)) 100%);
  color: var(--accent-ink);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--signal) 28%, transparent);
}
/* The chosen tab is underlined by a short bar that draws itself in — the one
   piece of movement that carries meaning rather than decoration. */
.spn-teil.is-on::after {
  content: ''; position: absolute; inset-inline: 26%; bottom: 4px; height: 2px;
  border-radius: 2px; background: var(--signal-deep); transform-origin: center;
}
html[data-theme="dark"] .spn-teil.is-on { color: var(--signal); }
html[data-theme="dark"] .spn-teil.is-on::after { background: var(--signal); }

/* Schriftlich uses the same two controls, but each Teil there is its own page,
   so the tabs are links and the current one is a span. Same shapes, no
   underline, and the current tab is not a link to where you already are. */
a.spn-lv, a.spn-teil { text-decoration: none; }
a.spn-lv:hover, a.spn-teil:hover,
a.spn-lv:focus-visible, a.spn-teil:focus-visible { text-decoration: none; }
span.spn-lv, span.spn-teil { cursor: default; }

/* The skill page carries every exercise of its skill and shows one Teil's
   worth. A class, not [hidden]: .grid .card sets its own display, which the
   UA's [hidden] rule loses to — the same way .uex-teile[hidden] was ignored
   once before. */
.grid > .card.uex-off { display: none; }
/* Per-level Teil strips: one visible, the rest hidden. .spn-teile sets
   display:grid, so [hidden] alone would not have held here either. */
.spn-teile[hidden] { display: none; }
/* A one-line tab — Schriftlich names the Teil and nothing else — should sit on
   the same centre line as Sprechen's two-line one. */
.spn-teil { min-height: 38px; }

.spn-c { opacity: .55; font-size: .82em; margin-inline-start: 3px; }
.chip.selected .spn-c { opacity: .7; }
.spn-count { margin: 0 0 14px; }
.spn-more { margin-top: 18px; text-align: center; }

@media (max-width: 560px) {
  /* Three tabs fit one row on a phone once the counts are gone; only the
     Arabic name needs room. */
  .spn-teile { grid-template-columns: repeat(auto-fit, minmax(64px, 1fr)); }
  /* Schriftlich on a phone: the strip, the level, the Teile and then the
     exercises, with the page reaching the first one without a scroll. */
  .spn-nav { gap: 8px; margin-bottom: 12px; }
  .uex .spn-count { margin-bottom: 10px; }
  .uex #exerciseList { gap: 10px; }
  .uex #exerciseList .card { padding: 15px 16px; }
  .spn-teil { padding: 7px 4px; }
  .spn-teil em { font-size: .66rem; }
  .spn-lv { min-width: 68px; padding: 6px 14px; }
}

/* The line that says what Sprechen actually is — a spoken exam with a marked
   result — carried as a caption under the title. It is the argument for the
   whole section, so it is set as a statement: the same signal-coloured pill
   the Road section uses for its turn line, wrapped rather than clipped
   because this sentence is long. */
.sp-lede {
  display: inline-block; max-width: 62ch;
  margin: 10px 0 4px; padding: 12px 20px;
  border-radius: 18px;
  background: var(--signal-wash);
  border: 1px solid color-mix(in srgb, var(--signal) 40%, transparent);
  color: var(--accent-ink);
  font-size: .94rem; line-height: 1.75; font-weight: 600;
}
@media (max-width: 560px) {
  .sp-lede { padding: 11px 15px; font-size: .88rem; }
}

/* ────────────────────────────────────────────────────────── footer
   Two lines and nothing else. The link list is gone — the header, the tab bar
   and the pages themselves already carry the navigation, and repeating all
   fourteen destinations at the bottom of every page was the only thing making
   this area large. */
/* The footer as a sitemap, not a receipt.
   It used to be a mark, four links and two legal lines — which said nothing
   about what the site contains and gave a reader who had scrolled to the
   bottom nowhere to go. Now: who we are and how to reach us in the first
   column, then what there is to do, grouped the way the product is grouped.

   The wide column is 1.4fr, not 1fr. It carries a brand lockup, a sentence
   and a row of buttons; at equal width the tagline broke every three words
   while the link columns sat half empty. */
.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr repeat(3, 1fr);
  gap: 32px 28px;
  padding-bottom: 30px; margin-bottom: 22px;
  border-bottom: 1px solid var(--line-soft);
}

.footer-brand { flex: none; margin-bottom: 12px; }
.footer-tag {
  margin: 0 0 18px; max-width: 34ch;
  color: var(--ink-2); font-size: .9rem; line-height: 1.6;
}

/* The English line in the hero. Muted and small — it is not competing with
   the Arabic headline, it is answering "what is this site" for the readers
   and reviewers who cannot read the headline at all. */
.hero-en {
  margin: 12px 0 0; max-width: 54ch;
  color: rgba(247, 250, 252, .74); font-size: .88rem; line-height: 1.6;
}
@media (max-width: 720px) { .hero-en { font-size: .82rem; } }

/* The English one-liner. Quiet, but present on every page — it is what an
   English-speaking reviewer or visitor reads to learn what this site is. */
.footer-en { margin: 8px 0 18px; max-width: 46ch; color: var(--ink-3);
  font-size: .84rem; line-height: 1.6; }

.footer-col-t {
  margin: 0 0 12px;
  font-size: .82rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.footer-col ul { list-style: none; margin: 0; padding: 0; }
.footer-col li + li { margin-top: 9px; }
.footer-col a {
  color: var(--ink-2); text-decoration: none; font-size: .9rem;
  transition: color .15s var(--ease);
}
.footer-col a:hover { color: var(--ink); }
.footer-col a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }
.footer-col a[lang="de"] { font-family: var(--font-de); }

/* ── contact ────────────────────────────────────────────────────────────
   Icon buttons rather than pasted handles: three glyphs read as one group
   at a glance, where three lines of address text read as small print. Each
   carries an aria-label and a title, so neither a screen reader nor a
   hesitating pointer has to guess which is which. */
.footer-social-t {
  display: block; margin-bottom: 10px;
  font-size: .82rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3);
}
.fsoc-row { display: flex; gap: 10px; }
.fsoc {
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px;          /* a comfortable touch target */
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  transition: color .15s var(--ease), border-color .15s var(--ease),
              transform .15s var(--ease);
}
.fsoc svg { width: 20px; height: 20px; }
.fsoc:hover {
  color: var(--accent-ink); border-color: var(--accent);
  transform: translateY(-2px);
}
.fsoc:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (max-width: 900px) {
  /* Two columns before one: at 900px the four-across grid gives each link
     column less width than "Sprachbausteine" needs, and every German label
     wraps. */
  .footer-grid { grid-template-columns: 1fr 1fr; gap: 28px 24px; }
  .footer-about { grid-column: 1 / -1; }
}
@media (max-width: 520px) {
  .footer-grid { grid-template-columns: 1fr; gap: 26px; }
  .footer-tag { max-width: none; }
}

.footer-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 20px; flex-wrap: wrap;
}
.footer-bar p { margin: 0; font-size: .85rem; color: var(--ink-3); }
/* `space-between` puts the first child at the start edge, which under RTL is
   the right — so the domain, written second, settles on the far left as asked,
   and the pair swaps correctly if the document direction ever changes. */
.footer-domain { font-family: var(--font-de); letter-spacing: .01em; }
@media (max-width: 520px) {
  .footer-bar { justify-content: center; text-align: center; gap: 6px; }
}

/* ──────────────────────────────────────────── highlighted notices
   One treatment, two uses: the line that explains what separates the plans,
   and the instruction that a subscription is activated by hand. Both are
   sentences a reader must not skim past, and neither is an error — so they
   are lifted with the site's own signal wash rather than shouted in red. */
.note-hl {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 20px 0 0; padding: 13px 16px;
  border: 1px solid color-mix(in srgb, var(--signal) 42%, transparent);
  border-radius: 14px;
  background: var(--signal-wash);
  color: var(--accent-ink);
  font-size: .88rem; line-height: 1.75; text-align: start;
}
.note-hl b { font-weight: 800; }
.note-hl a { font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }

/* The activation notice is an instruction with a consequence for getting it
   wrong, so it takes the warning colours and a marker. */
.note-hl.warn {
  border-color: var(--warn-line);
  background: var(--warn-bg);
  color: var(--warn);
}
.note-hl-i {
  flex: none; width: 20px; height: 20px; margin-top: 1px;
  border-radius: 50%; display: grid; place-items: center;
  background: var(--warn); color: var(--warn-bg);
  font-size: .74rem; font-weight: 900; line-height: 1;
}

/* On the plan card list the highlighted line replaces a plain footnote, so it
   keeps the divider that separated it from the features above. */
.pc-incl-foot.note-hl {
  display: block; margin-top: 18px; text-align: center;
}

/* The exercise index carries all 578 cards — 6,000 DOM nodes — because every
   one of them is a link a search engine should follow and a filter should be
   able to match without a round trip. Compressed the page is 23 KB, so the
   transfer was never the problem; the cost is laying out and painting cards
   nobody has scrolled to yet.

   content-visibility hands that decision to the browser: off-screen cards keep
   their markup and stay findable by in-page search and by the site's own
   filters, and simply are not rendered until they approach the viewport. The
   intrinsic size is the measured height of a real card, so the scrollbar does
   not lurch as they materialise. */
.ex-grid > .card,
.grid > .card {
  content-visibility: auto;
  contain-intrinsic-size: auto 132px;
}

/* ───────────────────────────────── Sprechen: what to say, and one example
   Two cards, both open. The first version folded the example behind a bare
   line of text and it read as a label rather than a control — the owner of the
   site looked at a theme and concluded the examples had never been written.
   Nothing is hidden here now.

   Both sit on the mint wash with a signal border, which is how the rest of the
   site marks something as help rather than as exam content: the same treatment
   the Redemittel box carries. They are told apart by their headers, not by
   inventing a second accent colour.

   Placement is sprechen.js's job, not a media query's — on a phone these live
   between the question and the record button, on a wide screen they move to
   the right-hand column. See placeGuidance(). */
.sp-guide-pair { display: grid; gap: 10px; margin: 12px 0 0; }

.sp-guide {
  border: 1px solid color-mix(in srgb, var(--signal) 45%, transparent);
  border-radius: var(--radius);
  background: var(--signal-wash);
  overflow: hidden;
}

/* ── card headers, which are also the toggle on a phone ─────────────────── */
.sp-guide-head {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 13px;
  cursor: pointer; list-style: none;
}
.sp-guide-head::-webkit-details-marker { display: none; }
.sp-guide-head::marker { content: ''; }

/* Drawn from two borders rather than set in a glyph: "⌄" renders at a
   different weight and baseline in every font that has it, and rotated 180°
   it reads as "<" instead of pointing up. */
.sp-guide-chev {
  flex: none; margin-inline-start: auto;
  width: 8px; height: 8px; margin-top: -4px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  border-radius: 1px;
  color: var(--ink-3);
  transform: rotate(45deg);
  transition: transform .18s ease, margin-top .18s ease;
}
.sp-guide[open] > .sp-guide-head .sp-guide-chev {
  transform: rotate(225deg); margin-top: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .sp-guide-chev { transition: none; }
}

/* On a wide screen these are not dropdowns at all. They sit in their own
   column, sprechen.js holds them open, and a chevron there would advertise a
   control that does nothing. */
@media (min-width: 961px) {
  .sp-guide-head { cursor: default; pointer-events: none; }
  .sp-guide-chev { display: none; }
}
.sp-guide-badge {
  flex: none; display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: .95rem; line-height: 1;
}
.sp-guide-title { display: grid; gap: 1px; min-width: 0; }
.sp-guide-title b { font-size: .92rem; color: var(--signal-deep); }
.sp-guide-title small { font-size: .75rem; color: var(--ink-2); }

/* ── the points ─────────────────────────────────────────────────────────── */
.sp-guide-list {
  margin: 0; padding: 0 13px 11px; list-style: none;
  display: grid; gap: 6px;
}
.sp-guide-list li {
  display: flex; gap: 9px; align-items: flex-start;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: .87rem; line-height: 1.6; color: var(--ink);
}

/* A number, because these are points to cover in order. A checkmark would
   claim they are already done. */
.sp-guide-n {
  flex: none; display: grid; place-items: center;
  width: 20px; height: 20px; margin-top: 1px;
  border-radius: 50%;
  background: var(--signal-wash); color: var(--signal-deep);
  /* In dark the wash and the card are near neighbours, so the badge needs an
     edge of its own or the number reads as painted straight onto the row. */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--signal) 40%, transparent);
  font-size: .7rem; font-weight: 700; font-variant-numeric: tabular-nums;
}

/* ── the example ────────────────────────────────────────────────────────── */
/* One row per sentence: the German, and the same sentence in Darija under it.
   Paired line by line rather than as two blocks, so a learner can see which
   German clause carries which piece of the meaning. */
.sp-ex-lines {
  margin: 0 13px; padding: 0; list-style: none;
  display: grid; gap: 7px;
}
.sp-ex-lines li {
  padding: 9px 11px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  border-inline-start: 3px solid var(--signal);
}
.sp-ex-de {
  display: block;
  /* German inside an Arabic page: without its own direction the full stops and
     the "…" migrate to the wrong end of the line. */
  direction: ltr; text-align: left;
  font-size: .87rem; line-height: 1.6; color: var(--ink);
}
.sp-ex-ar {
  display: block; margin-top: 3px;
  padding-top: 3px;
  border-top: 1px dashed var(--line-soft);
  font-size: .79rem; line-height: 1.6; color: var(--ink-2);
}
.sp-guide-swap {
  margin: 0; padding: 8px 13px 11px;
  font-size: .76rem; color: var(--ink-2);
}

/* On a wide screen these are the top of the right-hand column, above the Teil
   list, and they scroll with it rather than being pinned. */
@media (min-width: 961px) {
  [data-sp-guide-slot]:not(:empty) { margin-bottom: 14px; }
  [data-sp-guide-slot] .sp-guide-pair { margin: 0; }
}

/* Half the card, for a visitor with no account. Legible as text, not as
   language — the same treatment the Redemittel teaser uses, and for the same
   reason: enough to show the points are specific to this theme, not enough to
   answer the question with. user-select stops a long press on a phone from
   lifting the words out from under the blur. */
.sp-guide .guide-locked {
  filter: blur(4.2px); opacity: .75;
  user-select: none; pointer-events: none;
}
.sp-guide .guide-locked * { user-select: none; }
.guide-more {
  margin: 0; padding: 2px 13px 11px;
  font-size: .76rem; font-weight: 700; color: var(--signal-deep);
}
/* Nobody should sit and wait for a blur to resolve. */
@media (prefers-reduced-motion: reduce) {
  .sp-guide .guide-locked { filter: blur(4.2px); }
}

@media (max-width: 560px) {
  .sp-guide-title small { font-size: .71rem; }
  .guide-more { padding: 2px 11px 10px; }
  .sp-guide-head { padding: 10px 11px; gap: 9px; }
  .sp-guide-list { padding: 0 11px 10px; }
  .sp-guide-list li { font-size: .83rem; gap: 8px; padding: 7px 9px; }
  .sp-ex-lines { margin: 0 11px; gap: 6px; }
  .sp-ex-lines li { padding: 8px 10px; }
  .sp-ex-de { font-size: .83rem; }
  .sp-ex-ar { font-size: .76rem; }
  .sp-guide-swap { padding: 8px 11px 10px; }
}

/* ─────────────────────────────────────────── Sprechen: the speaking toolbox
   Sits between the question and the microphone, which is the order the exam
   has: hear it, reach for the words, answer. Collapsed by default — on a
   phone the record button is the point of the screen and must not be pushed
   under the fold by a list of phrases nobody has asked for yet. */
.sp-rm {
  margin: 12px 0 14px; padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--signal) 38%, transparent);
  border-radius: 14px;
  background: var(--signal-wash);
}
.sp-rm-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.sp-rm-head b { font-size: .92rem; color: var(--accent-ink); }
.sp-rm-i { font-size: .95rem; }
.sp-rm-hint { font-size: .74rem; color: var(--ink-3); }

.sp-rm-slots { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.sp-rm-slot { flex: 1 1 auto; min-width: 0; }

.sp-rm-btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 13px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--signal-deep) 30%, transparent);
  background: var(--surface); color: var(--ink);
  font: inherit; font-size: .82rem; cursor: pointer;
  transition: background .14s var(--ease), border-color .14s var(--ease);
}
.sp-rm-btn:hover { border-color: var(--signal-deep); }
/* A chip is sized by its content, which is fine until the content is
   "Gegenargument" plus its Arabic gloss plus a lock: 155px of text in a 151px
   pill, spilling over the edge. Flex children do not shrink below their
   content unless told to, so they are told to. */
.sp-rm-btn { max-width: 100%; }
.sp-rm-btn > span { min-width: 0; overflow-wrap: anywhere; }
.sp-rm-btn:focus-visible { outline: 2px solid var(--signal-deep); outline-offset: 2px; }
.sp-rm-btn [lang="de"] { font-family: var(--font-de); font-weight: 600; }
.sp-rm-ar { color: var(--ink-3); font-size: .76rem; }
.sp-rm-chev { color: var(--signal-deep); font-weight: 800; font-size: .8rem; }
.sp-rm-btn[aria-expanded="true"] {
  background: var(--signal-deep); color: #fff; border-color: var(--signal-deep);
}
.sp-rm-btn[aria-expanded="true"] .sp-rm-ar,
.sp-rm-btn[aria-expanded="true"] .sp-rm-chev { color: #DCEBE8; }

.sp-rm-list {
  list-style: none; margin: 9px 0 2px; padding: 0;
  display: grid; gap: 6px;
}
/* `display: grid` above beats the UA rule that makes [hidden] display:none, so
   every group rendered open while its hidden attribute said otherwise — the
   DOM and the screen disagreed, and only the screen was telling the truth. */
.sp-rm-list[hidden] { display: none; }
/* German first and whole, Arabic under it — never over it, never instead of
   it. The two are separated by weight and direction rather than a rule, which
   keeps the row short enough to stay scannable on a phone. */
.sp-rm-list li {
  display: flex; flex-direction: column; gap: 3px;
  padding: 8px 12px; border-radius: 10px;
  background: var(--surface); border: 1px solid var(--line-soft);
  line-height: 1.6; text-align: start;
}
.sp-rm-list .rm-de {
  font-family: var(--font-de); font-size: .86rem; color: var(--ink);
}
.sp-rm-list .rm-ar {
  font-size: .78rem; color: var(--ink-3);
}
/* German compounds are long enough to outrun a 375px screen on their own. */
.sp-rm-list .rm-de, .sp-rm-list .rm-ar { overflow-wrap: anywhere; }

/* The exam, mocked, in front of a locked one. The demo inside is the home
   page's component; only the frame around it belongs to this page. */
.sp-preview { border-color: color-mix(in srgb, var(--signal) 38%, transparent); }
.sp-pv-head { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; }
.sp-pv-kicker { font-size: .82rem; font-weight: 700; color: var(--accent-ink); }
.sp-pv-tag {
  font-size: .68rem; font-weight: 700; letter-spacing: .02em;
  padding: 2px 8px; border-radius: 999px;
  background: var(--paper-2); color: var(--ink-3);
}
.sp-pv-note {
  margin: 12px 0 0; font-size: .82rem; line-height: 1.7; color: var(--ink-2);
}

/* The first thing a new account sees: three ways in, one tap each. Cards
   rather than links because the target has to be obvious on a phone held in
   one hand, which is 82% of this audience. */

/* Level first. Two buttons, thumb-sized, because this is the first thing a new
   account is asked and it is asked on a phone. */
.su-lvl { display: flex; gap: 8px; margin: 4px 0 16px; }
.su-lvl-b {
  flex: 0 0 auto; min-width: 62px; padding: 7px 16px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface);
  font: inherit; font-family: var(--font-de); font-weight: 700; font-size: .84rem;
  color: var(--ink); cursor: pointer;
  transition: background .14s var(--ease), border-color .14s var(--ease), color .14s var(--ease);
}
.su-lvl-b:hover { border-color: var(--signal-deep); }
.su-lvl-b:focus-visible { outline: 2px solid var(--signal-deep); outline-offset: 2px; }
.su-lvl-b.on { background: var(--signal-deep); border-color: var(--signal-deep); color: #fff; }
.su-start-cap { margin-bottom: 0; }

.su-start { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }
.su-start-card {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 14px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--surface);
  text-decoration: none; color: var(--ink);
  transition: border-color .14s var(--ease), background .14s var(--ease);
}
.su-start-card:hover, .su-start-card:focus-visible {
  border-color: var(--signal-deep); background: var(--signal-wash);
}
.su-start-i { font-size: 1.15rem; color: var(--signal-deep); flex: none; }
.su-start-t { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.su-start-t b { font-size: .93rem; }
.su-start-t span {
  font-size: .8rem; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.su-start-go { margin-inline-start: auto; color: var(--signal-deep); flex: none; }



   The toolbox in front of a locked exam. Same block, same chips, same colours
   — the point is that it is recognisably the thing they would be getting, not
   an advert for it. Everything in here is a button: the phrases are inert
   text, so a tap anywhere lands on the one action available.  */
.sp-rm-teaser { cursor: pointer; }
.sp-rm-teaser .sp-rm-btn { cursor: pointer; }
.sp-rm-teaser .sp-rm-chev { font-size: .74rem; }
/* Legible enough to be read as German, not enough to be read as a phrase. The
   user-select rule stops a long-press on a phone from lifting the text out of
   the blur.  */
.sp-rm-teaser .rm-locked {
  filter: blur(4.5px); opacity: .75; user-select: none;
  pointer-events: none;
}
.sp-rm-teaser .rm-more {
  display: block; padding: 4px 2px 0;
  background: none; border: 0;
  font-size: .76rem; color: var(--signal-deep); font-weight: 700;
  text-align: start;
}
/* Nobody should sit and wait for a blur to resolve. */
@media (prefers-reduced-motion: reduce) {
  .sp-rm-teaser .rm-locked { filter: blur(4.5px); }
}

@media (max-width: 560px) {
  .sp-rm { padding: 10px 12px; margin: 10px 0 12px; }
  .sp-rm-hint { display: none; }
  /* Two per row, not four stacked: full-width chips cost 235px of a 844px
     screen before a single phrase is shown, and the record button has to stay
     reachable without scrolling. */
  .sp-rm-slot { flex: 1 1 44%; }
  .sp-rm-btn {
    width: 100%; justify-content: center; gap: 5px; padding: 7px 9px; font-size: .78rem;
    /* Two lines inside the chip beats one line outside it. */
    flex-wrap: wrap; border-radius: 14px; line-height: 1.35;
  }
  .sp-rm-ar { font-size: .7rem; }
  .sp-rm-list li { font-size: .84rem; }
}

/* ------------------------------------------------- a recording that is paid */

/*
 * A paid recording has no src until it is asked for, so the player is replaced
 * by the asking. The wait is shown as a percentage because the whole file has
 * to arrive before it can start — a blob cannot stream.
 */
.ga-load {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 10px 0;
}
.ga-note { font-size: .84rem; color: var(--ink-3); }
.ga-note a { color: var(--signal-deep); font-weight: 700; }
html[data-theme="dark"] .ga-note a { color: var(--signal); }

/* ------------------------------------------------------------- sharing */

/*
 * Sharing is offered after a result, where the learner knows whether the thing
 * was worth a friend's time. Two buttons: the phone's own share sheet, which
 * is where WhatsApp lives, and a plain copy for a desktop.
 */
.sh-row {
  display: flex; flex-wrap: wrap; gap: 10px;
  margin-top: 16px; padding-top: 16px;
  border-top: 1px solid var(--line);
}
.sh-row .btn { flex: 1 1 auto; min-width: 0; }
@media (max-width: 460px) { .sh-row .btn { flex-basis: 100%; } }

/* ------------------------------------------- optional profile fields */

/*
 * Two fields nobody has to fill in, on the form where losing somebody is most
 * expensive. So "اختياري" is part of the label rather than a footnote, and the
 * whole group reads quieter than the required fields above it.
 */
/* The label is a grid, so a caption and its badge have to be one item or they
   stack into two rows. */
.acc-cap { display: block; }
.acc-opt {
  display: inline-block;
  font-size: .72rem; font-weight: 600; letter-spacing: .02em;
  color: var(--ink-3); background: var(--surface);
  border: 1px solid var(--line); border-radius: 5px;
  padding: 1px 6px; margin-inline-start: 6px;
}
.acc-lvl { border: 0; margin: 0; padding: 0; min-width: 0; }
.acc-lvl legend {
  padding: 0; margin-bottom: 7px;
  font-size: .88rem; font-weight: 600; color: var(--ink-2);
}
.acc-lvl-row { display: flex; gap: 10px; }
/* A tap target, not a radio dot: these are pressed on a phone. */
/* Outranks `.acc-form label { display: grid }`, which would otherwise stack the
   radio above its own label text. */
.acc-form .acc-lvl-row label,
.acc-lvl-row label {
  flex: 1 1 0; min-width: 0; margin: 0;
  display: flex; align-items: center; justify-content: center; gap: 7px;
  padding: 11px 10px; cursor: pointer;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface); color: var(--ink-2);
  font-family: var(--font-de); font-weight: 700; font-size: .95rem;
  transition: border-color .14s var(--ease), background .14s var(--ease), color .14s var(--ease);
}
.acc-lvl-row label:hover { border-color: var(--signal-deep); }
/* The dot sat in one corner with the level in the other and 100px of nothing
   between them, which on a phone reads as two empty boxes rather than a
   choice. The box is the control now: the level centred in it, and the
   chosen one filled. The input stays focusable, just not drawn. */
.acc-lvl-row input {
  position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0;
  pointer-events: none;
}
.acc-form .acc-lvl-row label,
.acc-lvl-row label { gap: 0; padding: 13px 10px; font-size: 1rem; }
.acc-lvl-row label:has(input:checked) {
  border-color: var(--signal-deep); background: var(--signal-deep); color: #fff;
}
.acc-lvl-row label:focus-within { outline: 2px solid var(--signal-deep); outline-offset: 2px; }

/* The same pair on the account page, under a heading of their own. */
.acc-extras {
  margin-top: 22px; padding-top: 20px;
  border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 10px;
}
.acc-extras h3 { margin: 0; font-size: 1rem; }
.acc-extras .count-note { margin: 0; }

/* The next-question button, once an answer is recorded and waiting.
   A ring rather than an animation: this appears mid-exam, and something
   flashing while you are trying to think in German is not help. */
.btn.awaiting {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 45%, transparent);
}

/* ══════════════════════════════════════════ Übungen: sections before lists

   Übungen → section → Teil → exercise, with each step on screen instead of
   remembered. The tab strip is the load-bearing piece: it names every room in
   the building from inside any of them, which is what the old search box
   could never do. */

.uex { padding-bottom: 8px; }

/* ── the section selector ───────────────────────────────────────────────── */

/* Scrolls sideways rather than wrapping. Five items will not fit a 360px
   phone at a readable size, and a strip that wraps to two lines stops
   reading as one control. */
.uex-tabs {
  margin: 14px 0 14px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* text-align on a block, not justify-content on a flex row. Centring an
     overflowing flex row pushes its leading edge out of the scroll range and
     the first tab becomes unreachable; a centred inline box stays scrollable
     to both ends. Matters here because this strip does overflow on a phone. */
  text-align: center;
}
.uex-tabs::-webkit-scrollbar { display: none; }

/* The same living ground as the level and Teil bars beneath it. Three
   navigation strips stacked on one page should be three of the same object,
   not three different materials. */
.uex-tabs-in {
  display: inline-flex; gap: 4px;
  padding: 5px;
  /* A surface, not a sweep. This used to run a near-clear band across the
     bar on an 11-second loop; a strip of the page that never stops moving
     pulls the eye off the exercises below it, and on the dark ground the
     travelling band was brighter than the labels sitting on it. What is
     left is the part that made it read as a surface in the first place: a
     shallow vertical tint and a highlight along the top edge. */
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--signal) 15%, var(--surface)),
    color-mix(in srgb, var(--signal) 23%, var(--surface)));
  border: 1px solid color-mix(in srgb, var(--signal) 26%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent),
              0 1px 3px color-mix(in srgb, var(--signal) 16%, transparent);
  border-radius: 999px;
  min-width: max-content;
}

.uex-tab {
  position: relative;
  display: inline-block; white-space: nowrap;
  padding: 9px 18px;
  border-radius: 999px;
  font-family: var(--font-de); font-size: .98rem; font-weight: 600;
  color: var(--ink-2); text-decoration: none;
  transition: background .15s var(--ease), color .15s var(--ease),
              transform .15s var(--ease), box-shadow .15s var(--ease);
}
.uex-tab:hover:not(.is-on) {
  color: var(--ink); background: color-mix(in srgb, var(--ink) 6%, transparent);
  transform: translateY(-1px);
}
.uex-tab:active { transform: scale(.97); }

/* The active tab is a raised chip, not a colour swap: it has to read as
   "you are standing here" at a glance on a strip that scrolls. */
.uex-tab.is-on {
  background: linear-gradient(180deg, var(--surface) 0%,
    color-mix(in srgb, var(--signal) 7%, var(--surface)) 100%);
  color: var(--ink);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--signal) 28%, transparent);
}
.uex-tab.is-on::after {
  content: ''; position: absolute; inset-inline: 28%; bottom: 5px; height: 2px;
  border-radius: 2px; background: var(--signal-deep); transform-origin: center;
}
html[data-theme="dark"] .uex-tab.is-on::after { background: var(--signal); }
@media (prefers-reduced-motion: no-preference) {
  .uex-tab.is-on { animation: spn-pop .34s var(--ease); }
  .uex-tab.is-on::after { animation: spn-bar .3s .06s var(--ease) both; }
}
.uex-tab.is-on:hover { background: linear-gradient(180deg, var(--surface) 0%,
  color-mix(in srgb, var(--signal) 7%, var(--surface)) 100%); }
/* الكل is marked as a LINK, not as a filled chip.
   It is the one item in the strip that is not an exam section — it is the way
   out of the four into the whole list — so it has to stand out without
   claiming a state. An accent wash was tried first and read as "selected":
   inside a tab strip, any filled pill is the selected one, and colour alone
   does not undo that. So: no fill at all, accent ink, an underline and an
   arrow — the arrow being the one mark in this design that means "goes
   somewhere" rather than "you are here", which is exactly the difference
   being drawn. The underline that came with it is gone: between the accent
   ink and the arrow the label is already the odd one out, and the rule under
   a single word in a row of pills was noise.

   When it IS the current page it drops all of that and takes the raised chip
   like any other tab, because then "you are here" is the truth. */
.uex-tab-alt {
  font-family: var(--font-ar); font-weight: 700;
  color: var(--accent-ink); background: transparent;
  text-decoration: none;
}
.uex-tab-alt::after {
  content: "←"; margin-inline-start: 6px; font-size: .88em;
  /* The arrow is ornament on a label that already reads; keeping it out of the
     accessibility tree stops a screen reader saying "الكل left arrow". */
  speak: never;
}
.uex-tab-alt:hover {
  color: var(--accent-ink);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.uex-tab-alt.is-on, .uex-tab-alt.is-on:hover {
  color: var(--ink); background: var(--surface);
}
.uex-tab-alt.is-on::after { content: none; }
.uex-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── the heading ────────────────────────────────────────────────────────── */

.uex-head { margin: 0 0 22px; }

.uex-eyebrow {
  display: inline-flex; align-items: center; gap: 7px;
  margin: 0 0 14px; padding: 6px 14px;
  background: var(--accent-soft); border: 1px solid var(--accent-soft);
  border-radius: 999px;
  color: var(--accent-ink); font-size: .84rem; font-weight: 600;
}
.uex-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); flex: none;
}

/* Large on purpose. The section name is the answer to "where am I", and on a
   page whose job is orientation that deserves the room. */
/* text-align: right, against the site's own [lang="de"] rule.
   That rule gives German islands direction:ltr and text-align:start, which
   puts a block-level German heading at the LEFT edge — correct for a line of
   German prose, wrong for a 4rem display word sitting above an Arabic eyebrow,
   an Arabic lede and a right-aligned tab strip. At this size the heading is
   not an island in a sentence, it is the page's own start marker, and it has
   to begin where everything under it begins. The shell is dir="rtl" with no
   LTR mode, so `right` is the start edge, full stop. */
.uex-h1 {
  margin: 0 0 6px;
  font-family: var(--font-de);
  font-size: clamp(2.4rem, 7vw, 4rem);
  line-height: 1.02; letter-spacing: -.035em;
  text-align: right;
}
.uex-lede { margin: 0; color: var(--ink-2); font-size: 1.02rem; max-width: 56ch; }
/* The badge above already carries the count on a wide screen. */
.uex-lede-n { display: none; }

/* ── the skill cards on /uebungen/ ──────────────────────────────────────── */

.uex-cards {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

.uex-card {
  display: flex; flex-direction: column; gap: 9px;
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  text-decoration: none; color: inherit;
  transition: border-color .15s var(--ease), transform .15s var(--ease),
              box-shadow .15s var(--ease);
}
.uex-card:hover, .uex-card:focus-visible {
  border-color: var(--accent); transform: translateY(-2px);
  box-shadow: var(--shadow);
}
.uex-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.uex-card-top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.uex-card-top b {
  font-family: var(--font-de); font-size: 1.42rem; font-weight: 700;
  letter-spacing: -.02em; line-height: 1.1;
}
.uex-card-ar { color: var(--ink-3); font-size: .92rem; }
.uex-card-blurb { color: var(--ink-2); font-size: .93rem; line-height: 1.55; flex: 1 1 auto; }

.uex-card-foot {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 4px; padding-top: 12px;
  border-top: 1px solid var(--line-soft);
  font-size: .82rem; color: var(--ink-3);
}
.uex-card-n { font-weight: 700; color: var(--ink); }
.uex-card-meta::before { content: "·"; margin-inline-end: 8px; color: var(--line); }

/* ── the level switch ───────────────────────────────────────────────────── */

.uex-lvlbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 0 0 18px;
}
.uex-lvl-label { color: var(--ink-3); font-size: .88rem; }

.uex-lvls {
  display: inline-flex; gap: 4px; padding: 4px;
  background: var(--paper-2);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
}
.uex-lvl {
  appearance: none; border: 0; cursor: pointer;
  padding: 8px 24px; border-radius: 999px;
  background: transparent; color: var(--ink-2);
  font-family: var(--font-de); font-size: 1rem; font-weight: 700;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.uex-lvl:hover { color: var(--ink); }
/* Filled, not outlined: this is the one control on the page that changes what
   is listed below it, so it carries the page's only strong colour.

   --signal with --signal-ink, which is the pair the rest of the site uses for
   a filled accent (.btn.accent, .road-chip.is-on). Pairing --signal-ink with
   --accent instead measured 2.71:1 in light mode — --signal-ink is the dark
   text meant to sit ON --signal, and --accent is already the dark end of that
   ramp, so the two were competing rather than contrasting. */
.uex-lvl.is-on { background: var(--signal); color: var(--signal-ink); }
.uex-lvl:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── the Teil tiles ─────────────────────────────────────────────────────── */

/* auto-fill, not auto-fit: Schreiben has a single Teil per level, and auto-fit
   collapses the empty tracks so that one card stretched the whole row and read
   as a banner rather than as one of a set. Empty tracks keep every tile the
   same size whether a section has one or six. */
.uex-teile {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
/* display:grid from the class above outranks the user-agent's
   [hidden]{display:none}, so the hidden level rendered too and both panels
   stacked. Say it here, where the display is set. */
.uex-teile[hidden] { display: none; }

.uex-teil {
  display: flex; flex-direction: column; gap: 8px;
  padding: 18px 20px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-decoration: none; color: inherit;
  transition: border-color .15s var(--ease), transform .15s var(--ease),
              box-shadow .15s var(--ease);
}
.uex-teil:hover, .uex-teil:focus-visible {
  border-color: var(--accent); transform: translateY(-2px);
  box-shadow: var(--shadow);
}
.uex-teil:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Same reason as .uex-h1: a stretched flex item, so text-align decides where
   "Teil 1" sits, and it belongs at the card's start edge with the rest. */
.uex-teil-n {
  font-family: var(--font-de); font-size: 1.22rem; font-weight: 700;
  letter-spacing: -.02em; line-height: 1.15;
  text-align: right;
}
.uex-teil-blurb { color: var(--ink-2); font-size: .9rem; line-height: 1.55; flex: 1 1 auto; }

.uex-teil-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-top: 4px; padding-top: 11px;
  border-top: 1px solid var(--line-soft);
  font-size: .85rem; color: var(--ink-2);
}
.uex-teil-foot b { font-weight: 700; color: var(--ink); }
.uex-go { color: var(--accent); font-size: 1.05rem; }

/* Phone-only; the tab strip carries this on every other width. */
.uex-alle { display: none; }

/* Without JavaScript nothing would switch the panels, so show every level
   rather than stranding the reader on the first one. */
@media (scripting: none) {
  .uex-teile[hidden] { display: grid !important; }
  .uex-lvlbar { display: none; }
}

/* ── phone ──────────────────────────────────────────────────────────────

   Desktop is untouched below this line.

   The strip stops scrolling and becomes a grid. A sideways strip works on a
   wide screen because the whole thing is visible at once; on a 375px phone it
   held 564px of tabs, so two of the five sections were off-screen behind a
   gesture with the scrollbar hidden. The entire point of this page is that a
   learner sees what there is to study without hunting — a section they cannot
   see is a section that does not exist to them. Two columns, because
   "Sprachbausteine" does not fit three across at a readable size, and الكل
   takes the last row on its own: it is the escape hatch, not a fifth skill. */
@media (max-width: 560px) {
  .uex-tabs {
    margin: 10px 0 12px;
    overflow-x: visible;
    text-align: initial;
  }
  .uex-tabs-in {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 5px;
    width: 100%;
    border-radius: var(--radius);
  }
  .uex-tab {
    /* 13px, so the tab measures 44px tall — the smallest comfortable touch
       target. At 11px it came out 40px, which is a miss often enough to be
       annoying on the one control this page is navigated by. */
    padding: 13px 8px;
    text-align: center;
    font-size: .92rem;
    white-space: normal;      /* Sprachbausteine may take two lines */
    line-height: 1.25;
    border-radius: calc(var(--radius) - 5px);
  }
  .uex-tab-alt { grid-column: 1 / -1; }

  /* Tighter head, so the Teile start near the fold rather than a scroll below
     it. The h1 keeps its size — it is the answer to "where am I".

     On a section page the badge and the descriptive half of the lede go: the
     badge's count moves inline, and every Teil tile below states its own task
     anyway, so the sentence was saying twice what the page was about. Worth
     ~75px, which is most of a Teil tile. The Übungen home keeps both — it has
     no level bar and nothing underneath repeating it. */
  .uex-eyebrow { margin-bottom: 10px; font-size: .8rem; padding: 5px 12px; }
  .uex-head { margin-bottom: 16px; }
  .uex-head-sec .uex-eyebrow { display: none; }
  .uex-head-sec .uex-lede-more { display: none; }
  .uex-head-sec .uex-lede-n { display: inline; }
  .uex-lvlbar { margin-bottom: 14px; }

  .uex-cards, .uex-teile { grid-template-columns: 1fr; gap: 10px; }
  .uex-card { padding: 16px; gap: 7px; }
  .uex-teil { padding: 14px 16px; gap: 6px; }
  .uex-teil-foot { padding-top: 9px; }

  /* The section cards ARE the selector on this page, so the strip above them
     was the same four words a second time inside one screen. */
  .uex-home .uex-tabs { display: none; }
  .uex-alle {
    display: block; margin-top: 14px; padding: 13px 16px;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface); color: var(--ink-2);
    font-size: .94rem; text-align: center; text-decoration: none;
  }
  .uex-alle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
}


/* ── Itqan+ in the navigation ──────────────────────────────────────────────
   The one paid thing on the site, and until now it looked exactly like the
   six free ones beside it. It gets the sun colour everywhere it appears —
   header pill, tab bar, slide-out menu, footer — so it reads as an offer at a
   glance, without a single extra word of copy. */
.cta-plus {
  background: linear-gradient(135deg, var(--sun, #f6a723), var(--gold, #e08a1e));
  color: #21160a;
  display: inline-flex; align-items: center; gap: 6px;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--sun, #f6a723) 45%, transparent);
}
html[data-theme="dark"] .cta-plus {
  background: linear-gradient(135deg, var(--sun, #f6a723), var(--gold, #e08a1e));
  color: #21160a;
}
.cta-plus .cta-spark { font-size: .78em; line-height: 1; }
@media (prefers-reduced-motion: no-preference) {
  .cta-plus .cta-spark { animation: ctaSpark 2.6s var(--ease) infinite; }
}
@keyframes ctaSpark {
  0%, 70%, 100% { opacity: .85; transform: none; }
  80% { opacity: 1; transform: scale(1.35) rotate(18deg); }
}

/* Slide-out menu: the row keeps its shape, and gains a colour and a tag. */
.menu-panel a.is-plus {
  color: var(--ink); font-weight: 700;
  background: color-mix(in srgb, var(--sun, #f6a723) 14%, transparent);
}
.menu-panel a.is-plus .menu-i { opacity: 1; color: var(--gold-ink, #8a5a06); }
.menu-new {
  margin-inline-start: auto; padding: 2px 8px; border-radius: 999px;
  background: var(--sun, #f6a723); color: #21160a;
  font-size: .68rem; font-weight: 800; letter-spacing: .02em;
}
html[data-theme="dark"] .menu-panel a.is-plus .menu-i { color: var(--sun, #f6a723); }

/* Footer: the paid column's own link, lit the same way. */
.footer-col a.is-plus {
  color: var(--gold-ink, #8a5a06); font-weight: 700;
}
html[data-theme="dark"] .footer-col a.is-plus { color: var(--sun, #f6a723); }

/* Footer columns: they were a wall of equal links. Wider gaps between the
   groups than inside them is the whole of the structure — the eye gets to
   see four lists instead of one column of twenty. */
.footer-col-t { color: var(--ink); opacity: .92; }
.footer-col-t::after {
  content: ''; display: block; width: 22px; height: 2px; margin-top: 7px;
  border-radius: 2px; background: var(--sun, #f6a723); opacity: .8;
}
.footer-col li + li { margin-top: 7px; }
.footer-col a { display: inline-block; padding-block: 2px; }
.footer-col a:hover { color: var(--ink); text-decoration: underline;
  text-underline-offset: 3px; }


/* ── the exercise page head ────────────────────────────────────────────────
   One line: the way back to the themes, and the name of this one. */
.ex-top {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 0 12px; margin-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.ex-top h1 {
  margin: 0; font-size: clamp(1.15rem, 2.4vw, 1.5rem);
  font-family: var(--font-de); text-align: start;
}
/* The arrow steps toward the door on hover, and keeps nudging at it while the
   page sits idle — this is the control a learner needs most often on a page
   that otherwise only goes forwards, and it used to be a breadcrumb. */
.ex-back {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  padding: 7px 14px; border-radius: 999px;
  background: linear-gradient(180deg, var(--surface) 0%,
    color-mix(in srgb, var(--signal) 10%, var(--surface)) 100%);
  border: 1px solid color-mix(in srgb, var(--signal) 34%, transparent);
  color: var(--accent-ink); text-decoration: none;
  font-size: .86rem; font-weight: 700; white-space: nowrap;
  transition: background .16s var(--ease), border-color .16s var(--ease),
              transform .16s var(--ease), box-shadow .16s var(--ease);
}
.ex-back:hover {
  text-decoration: none; transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--signal) 70%, transparent);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--signal) 26%, transparent);
}
.ex-back:active { transform: translateY(0) scale(.98); }
.ex-back-i { display: inline-block; font-size: 1rem; line-height: 1; }
.ex-back:hover .ex-back-i { animation: none; transform: translateX(4px); }
@media (prefers-reduced-motion: no-preference) {
  .ex-back-i { animation: ex-back-nudge 2.8s var(--ease) infinite; }
}
@keyframes ex-back-nudge {
  0%, 72%, 100% { transform: translateX(0); }
  80% { transform: translateX(4px); }
  88% { transform: translateX(0); }
}

/* ── the keys ──────────────────────────────────────────────────────────────
   a, b, c on the choices and A, B, C on the matching bank, drawn from an
   attribute so they never land in the answer the engine copies out of the
   button. Numbers already sat on the texts; they are just readable now. */
.chip[data-key]::before {
  content: attr(data-key);
  display: inline-grid; place-items: center;
  width: 20px; height: 20px; margin-inline-end: 8px;
  border-radius: 6px; vertical-align: -4px;
  background: color-mix(in srgb, var(--signal) 20%, transparent);
  color: var(--accent-ink);
  font-size: .72rem; font-weight: 800; font-family: var(--font-de);
}
.chip.selected[data-key]::before,
.chip[data-state="correct"][data-key]::before { background: color-mix(in srgb, var(--signal) 40%, transparent); }
/* The key sits in its own column; the German and its Arabic keep the stacked
   layout they had. Written per context rather than on .chip, because these two
   banks were already laid out differently from each other and a single rule
   flattened both into a row — the German broke to one word a line and the
   Arabic ran out of the button. */
.option-bank .chip[data-key] {
  display: grid; grid-template-columns: auto 1fr; column-gap: 9px; align-items: start;
}
.option-bank .chip[data-key] .opt-ar { grid-column: 2; margin-top: 3px; }
.opts .chip[data-key] {
  display: grid; grid-template-columns: auto 1fr; column-gap: 7px; align-items: center;
}
.opts .chip[data-key] .opt-ar { grid-column: 1 / -1; margin-top: 3px; }
.chip[data-key]::before { margin-inline-end: 0; }

/* ── Sprachbausteine: the gaps ─────────────────────────────────────────────
   A bordered box with a grey caption and four pills in a row is a form. The
   number is a chip of its own now, the row it sits on reads as a question,
   and the choices are given room to be aimed at. */
.gap-row {
  border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 13px 13px;
  background: linear-gradient(180deg, var(--surface) 0%,
    color-mix(in srgb, var(--signal) 5%, var(--surface)) 100%);
}
.gap-row .gap-num {
  display: inline-flex; align-items: center; gap: 7px;
  margin-bottom: 9px; padding: 3px 10px 3px 4px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--signal) 14%, transparent);
  color: var(--accent-ink);
  font-family: var(--font-de); font-weight: 800; font-size: .74rem;
  letter-spacing: .03em;
}
.gap-row .opts { gap: 6px; }
.gap-row .chip { border-radius: 999px; padding: 7px 14px 7px 7px; }
.gap-row[data-state="correct"] { background: var(--ok-bg); }
.gap-row[data-state="incorrect"] { background: var(--bad-bg); }

/* ── less scrolling ───────────────────────────────────────────────────────
   The reading panels carried body copy at reading-page size. A shade smaller
   and a shade tighter puts appreciably more of a text on screen at once,
   which is what the exam asks you to hold in your head anyway. */
.exam-wrap .panel { padding: 16px; }
.exam-wrap .panel h2 { font-size: .95rem; margin-bottom: 10px; }
.exam-wrap .prose { font-size: .94rem; line-height: 1.65; }
.exam-wrap .match-item { padding-block: 12px; }
.match-num {
  font-weight: 800;
  background: color-mix(in srgb, var(--signal) 22%, transparent);
  color: var(--accent-ink);
}
@media (max-width: 720px) {
  .exam-wrap .panel { padding: 13px; }
  .exam-wrap .prose { font-size: .9rem; line-height: 1.6; }
  .ex-top { gap: 9px; padding: 8px 0 10px; margin-bottom: 10px; }
}


/* ── one page, one scrollbar ───────────────────────────────────────────────
   A reading panel that scrolled inside itself, an option bank that scrolled
   inside itself, and the page behind both: three scroll surfaces stacked on
   one screen, and a wheel over the text moved whichever the pointer happened
   to be on. Reaching for the passage and moving the page instead is the
   reported symptom and the predictable one.

   The column still follows you down — that is what sticky is for — it just
   grows to its own height instead of holding a second scrollbar inside it. */
.exam-wrap .exercise-layout .panel.reading-scroll,
.exam-wrap .exercise-layout .panel-scroll,
.exam-wrap .panel.reading-scroll,
.exam-wrap .panel-scroll {
  max-height: none; overflow-y: visible; overflow: visible;
}
.exam-wrap .sticky-side .option-bank,
.exam-wrap .sticky-side .gap-list,
.exam-wrap .sticky-side .word-bank {
  overflow-y: visible; max-height: none; flex: none;
}
/* Sticky only while the column is genuinely shorter than the screen; a
   sticky element taller than the viewport pins its top and hides its own
   bottom, which is how an answer bank becomes unreachable. */
.exam-wrap .sticky-side { max-height: none; }

/* ── a softer reading face ─────────────────────────────────────────────────
   The passage and the answers are the two things read word by word here. */
.exam-wrap .prose[lang="de"], .exam-wrap .prose.de,
.exam-wrap .chip, .exam-wrap .opt,
.exam-wrap .match-item .prose, .exam-wrap .q-card h3,
.sp-turn, .sp-transcript, .sp-live {
  font-family: var(--font-soft);
}
.exam-wrap .prose[lang="de"] { font-size: .97rem; line-height: 1.75; letter-spacing: .002em; }

/* ── grounds that do not glare ─────────────────────────────────────────────
   White panels under a cream page are the brightest thing on screen, and an
   exam text is read for minutes at a time. Both drop onto the site's own
   paper, with the answer side a shade cooler so the two halves are telling
   apart at a glance rather than by position. */
.exam-wrap .panel {
  background: color-mix(in srgb, var(--paper) 55%, var(--surface));
  border-color: var(--line-soft);
}
.exam-wrap .sticky-side {
  background: color-mix(in srgb, var(--signal-wash) 34%, var(--surface));
}
.exam-wrap .match-item,
.exam-wrap .q-card,
.exam-wrap .option-bank .chip,
.exam-wrap .opt {
  background: var(--surface);
}
html[data-theme="dark"] .exam-wrap .panel {
  background: color-mix(in srgb, var(--surface) 82%, var(--paper));
}
html[data-theme="dark"] .exam-wrap .sticky-side {
  background: color-mix(in srgb, var(--surface) 88%, var(--signal));
}

/* ── Lesen Teil 2: the choices carry their letter ──────────────────────────
   Same treatment as the banks, so a, b, c means the same thing in every
   part of the exam. */
.opt[data-key] { position: relative; padding-inline-start: 38px; }
.opt[data-key]::before {
  content: attr(data-key);
  position: absolute; inset-inline-start: 10px; top: 50%;
  transform: translateY(-50%);
  display: grid; place-items: center;
  width: 20px; height: 20px; border-radius: 6px;
  background: color-mix(in srgb, var(--signal) 20%, transparent);
  color: var(--accent-ink);
  font-family: var(--font-de); font-size: .72rem; font-weight: 800;
}
.opt[data-key]:has(input:checked)::before {
  background: color-mix(in srgb, var(--signal) 45%, transparent);
}


/* ── Lesen Teil 3: a board of notices, not a column ────────────────────────
   Twelve small advertisements stacked one per row is a very long scroll for
   twelve things you are meant to skim. On anything wider than a phone they
   sit two across in equal frames, which is also how they are printed. */
.exam-wrap .panel.is-ads { padding: 14px; container-type: inline-size; }
/* Two across only when the panel can pay for it. This was keyed to the
   viewport, so a 1280px screen put two frames in a 637px column — 269px
   each, which is why the notices came out small and set solid. The panel
   asks about its own width instead, and below the threshold each notice
   gets the full column. */
@container (min-width: 760px) {
  .exam-wrap .panel.is-ads {
    display: grid; grid-template-columns: 1fr 1fr;
    gap: 14px; align-items: start;
  }
  .exam-wrap .panel.is-ads > h2 { grid-column: 1 / -1; margin-bottom: 2px; }
}
.exam-wrap .panel.is-ads .match-item {
  margin: 0 0 12px; padding: 14px 16px;
  border: 1px solid var(--line-soft); border-radius: 12px;
  background: var(--surface);
}
@container (min-width: 760px) {
  .exam-wrap .panel.is-ads .match-item { margin: 0; }
}
.exam-wrap .panel.is-ads .match-item .prose h4 { margin: 0 0 7px; font-size: 1.04rem; }
.exam-wrap .panel.is-ads .match-item .prose { font-size: 1.04rem; line-height: 1.78; }

/* ── Lesen Teil 2: a text you can find your place in ───────────────────────
   The passage is already written in paragraphs; it did not read as any,
   because they were set solid. Space between them and a measure that stops
   at a comfortable line length is the whole of it. */
/* Paragraphs, not a column set in the middle of a panel.
   The measure I put here was 68ch — sound advice for a page of prose and
   wrong inside a panel that is already narrow, because it left a band of
   nothing down one side and pushed the passage longer. The panel is the
   measure; the paragraphs just need to be told apart. */
.exam-wrap .prose p { margin: 0 0 .62em; }
.exam-wrap .prose p:last-child { margin-bottom: 0; }
.exam-wrap .prose > h3, .exam-wrap .prose > h4 { margin: .9em 0 .35em; }

/* ── Sprachbausteine: the gaps, arranged ───────────────────────────────────
   The choices were a wrapping row, so three options broke two-and-one and no
   two rows lined up with each other. Equal columns instead: every choice is
   the same size and sits under the same one above it, which is what makes a
   list of ten of them scannable rather than ragged. */
.gap-list { display: grid; gap: 10px; }
.gap-row .opts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  gap: 6px;
}
.gap-row .chip { width: 100%; justify-content: start; }
.opts .chip[data-key] { align-content: center; }

/* ── grounds, softer still ─────────────────────────────────────────────────
   Warmer and lower in contrast than plain paper: the panel recedes and the
   things you act on — a text card, an option — stay the lightest surfaces on
   the page, which is the order of attention the exercise wants. */
.exam-wrap .panel {
  background: color-mix(in srgb, var(--paper) 78%, var(--surface));
  border-color: var(--line-soft);
}
.exam-wrap .sticky-side {
  background: color-mix(in srgb, var(--signal-wash) 52%, var(--surface));
}
.exam-wrap .match-item,
.exam-wrap .q-card,
.exam-wrap .gap-row,
.exam-wrap .option-bank .chip,
.exam-wrap .opt {
  background: color-mix(in srgb, var(--surface) 92%, var(--paper));
  border-color: var(--line-soft);
}
.exam-wrap .opts .chip { background: var(--surface); }
html[data-theme="dark"] .exam-wrap .panel {
  background: color-mix(in srgb, var(--surface) 78%, var(--paper));
}
html[data-theme="dark"] .exam-wrap .sticky-side {
  background: color-mix(in srgb, var(--surface) 84%, var(--signal));
}
html[data-theme="dark"] .exam-wrap .match-item,
html[data-theme="dark"] .exam-wrap .q-card,
html[data-theme="dark"] .exam-wrap .gap-row,
html[data-theme="dark"] .exam-wrap .option-bank .chip,
html[data-theme="dark"] .exam-wrap .opt { background: var(--surface); }

/* ── every word in here, in the soft face ──────────────────────────────────
   Space Grotesk is a geometric display face: flat terminals, circular bowls,
   a very even stroke. It is right on a heading and wrong on the fortieth
   line of a German text, which is where it still was — on the panel
   headings, the slots, the notice titles and the solutions. */
.exam-wrap .panel h2, .exam-wrap .panel h3, .exam-wrap .panel h4,
.exam-wrap .slot, .exam-wrap .solution, .exam-wrap .gap-num,
.exam-wrap .instructions, .exam-wrap .q-ar, .exam-wrap .opt-ar,
.exam-wrap [lang="de"] {
  font-family: var(--font-soft);
}
/* 1.8 leading is a reading-page value. At 1.62 the same passage gives back
   roughly a fifth of its height without crowding the lines. */
.exam-wrap .prose[lang="de"] { font-size: .98rem; line-height: 1.62; }
.exam-wrap .chip, .exam-wrap .opt { font-size: .92rem; line-height: 1.55; }

/* ── the way out, made obvious ─────────────────────────────────────────────
   It is the only control on the page that goes anywhere other than the next
   exercise, and it was a quiet outline. Now it carries the accent, and a
   light crosses it every few seconds. */
.ex-back {
  position: relative; overflow: hidden;
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--signal) 34%, var(--surface)) 0%,
    color-mix(in srgb, var(--signal) 18%, var(--surface)) 100%);
  border-color: color-mix(in srgb, var(--signal) 62%, transparent);
  color: var(--accent-ink);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--signal) 26%, transparent);
}
.ex-back::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg, transparent 38%,
    color-mix(in srgb, #fff 62%, transparent) 50%, transparent 62%);
  transform: translateX(-140%);
}
@media (prefers-reduced-motion: no-preference) {
  .ex-back::after { animation: ex-back-sheen 5s 1.4s var(--ease) infinite; }
}
@keyframes ex-back-sheen {
  0% { transform: translateX(-140%); } 22%, 100% { transform: translateX(140%); }
}
html[data-theme="dark"] .ex-back {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--signal) 26%, var(--surface)) 0%,
    color-mix(in srgb, var(--signal) 12%, var(--surface)) 100%);
  color: var(--signal);
}


/* ── Sprachbausteine Teil 2: the blanks and the word box ───────────────────
   Two lists that have to be read against each other, so both are set on a
   grid rather than left to wrap where they happen to fall.

   The blanks sit in running text, so they are sized to a word rather than to
   the longest one in the bank, and their line-height is pinned so a paragraph
   with six of them keeps the leading of one without. */
.exam-wrap .gap {
  min-width: 68px; padding: 0 8px; margin: 0 3px;
  line-height: 1.45; vertical-align: baseline;
  border-bottom-width: 2px; border-radius: 6px 6px 0 0;
  background: color-mix(in srgb, var(--signal) 16%, var(--surface));
  transition: background .15s var(--ease), box-shadow .15s var(--ease);
}
.exam-wrap .gap.empty {
  background: color-mix(in srgb, var(--signal) 9%, var(--surface));
  border-bottom-style: dashed;
}
.exam-wrap .gap:hover {
  background: color-mix(in srgb, var(--signal) 28%, var(--surface));
}
/* Armed, and therefore next: the outline alone was easy to lose in a page of
   dashes. */
.exam-wrap .gap.armed {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 40%, transparent);
  background: color-mix(in srgb, var(--signal) 26%, var(--surface));
}

/* The box itself: equal cells, one word per cell, every letter in the same
   column down the left so the list can be read straight down. */
.exam-wrap .word-bank {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: 7px;
}
.exam-wrap .word-bank .chip {
  display: grid; grid-template-columns: auto 1fr; align-items: center;
  column-gap: 8px; width: 100%;
  padding: 8px 12px; border-radius: 10px; text-align: start;
}
.exam-wrap .word-bank .chip .opt-ar {
  grid-column: 1 / -1; margin-top: 2px; font-size: .78em;
}
/* Used words stay in place rather than vanishing, so the box keeps its shape
   while it empties — but they stop competing for attention. */
.exam-wrap .word-bank .chip.used,
.exam-wrap .word-bank .chip[disabled] { opacity: .38; }
/* The one you have picked up. */
.exam-wrap .word-bank .chip.selected,
.exam-wrap .option-bank .chip.selected {
  border-color: var(--signal-deep);
  background: color-mix(in srgb, var(--signal) 22%, var(--surface));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 26%, transparent);
}

@media (max-width: 720px) {
  /* Two per row on a phone, still even; one long word never makes a row of
     its own. */
  .exam-wrap .word-bank { grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); gap: 6px; }
  .exam-wrap .word-bank .chip { padding: 7px 10px; font-size: .88rem; }
  .exam-wrap .gap { min-width: 58px; padding: 0 6px; }
}


/* ── Sprachbausteine Teil 1: the blanks, compact ───────────────────────────
   Each blank was a caption on one line and its choices on the next, inside a
   box with its own padding — ten of them filled a screen and a half, and the
   number sat far enough from the options that the pairing had to be worked
   out rather than seen. Number and choices share a row now: the number is a
   marker in the margin, the options are the row, and the whole list reads
   down a single left edge. */
.exam-wrap .gap-row {
  display: grid; grid-template-columns: 30px 1fr;
  gap: 4px 10px; align-items: center;
  padding: 8px 10px; border-radius: 12px;
}
.exam-wrap .gap-row .gap-num {
  grid-column: 1; margin: 0; padding: 0;
  display: grid; place-items: center; gap: 0;
  width: 30px; height: 30px; border-radius: 9px;
  background: color-mix(in srgb, var(--signal) 20%, transparent);
  color: var(--accent-ink);
  font-family: var(--font-de); font-size: .86rem; font-weight: 800;
}
.exam-wrap .gap-row .gap-num em {
  display: none;              /* the two-part note goes under, not in the chip */
}
.exam-wrap .gap-row .opts { grid-column: 2; margin: 0; }
.exam-wrap .gap-row .solution { grid-column: 1 / -1; margin-top: 2px; }
.exam-wrap .gap-list { gap: 7px; }
/* The blank being answered, and the blank in the text, light up together. */
.exam-wrap .gap-row.armed .gap-num,
.exam-wrap .gap-row:hover .gap-num {
  background: color-mix(in srgb, var(--signal) 42%, transparent);
}
@media (max-width: 720px) {
  .exam-wrap .gap-row { grid-template-columns: 26px 1fr; gap: 4px 8px; padding: 7px 8px; }
  .exam-wrap .gap-row .gap-num { width: 26px; height: 26px; font-size: .78rem; }
  .exam-wrap .gap-row .opts { grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: 5px; }
}


/* ── Sprachbausteine: the gaps are the exercise, so they get the room ──────
   The layout gives the passage the wide column, which is right when the
   answers are four radio buttons and wrong here: the passage is a short
   letter and the answer side is ten questions with three options each. In a
   300px column those three options stack, every row runs to two hundred
   pixels, and the list stands over two thousand tall. Given equal width they
   sit in a row and the same ten questions take a third of the height. */
@media (min-width: 900px) {
  .exam-wrap .exercise-layout:has(.gap-list) {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  }
}


/* ── Lesen Teil 2: a page to read, not a screen to decode ──────────────────
   Bigger, looser and on a warm ground. The passage is the one thing here
   that is read continuously, so it gets book treatment: a comfortable size,
   generous leading, and paper rather than a lit white panel. */
.exam-wrap .prose[lang="de"] {
  /* Cairo, not the soft face. Cairo is the only family loaded here whose
     Latin is drawn at this width, and at 19px on 1.95 leading it is what a
     long German passage should look like on this site — the same face the
     rest of the page is set in, so the reading panel stops looking like a
     transplant from another document. */
  font-family: var(--font-ar);
  font-weight: 600;
  font-size: 1.19rem; line-height: 1.95;
  letter-spacing: 0;
  color: var(--ink);
  /* The passage panel is ~840px wide, which at this size is about 115
     characters a line — far past the point where the eye loses its place
     on the way back to the left margin. 64ch puts it near 70, and the
     panel keeps its width so the column sits in margins like a page. */
  /* Fills the panel rather than sitting in it. Centring a 64ch column in a
     wider panel left a band of nothing down both sides; the measure is set
     by the column the panel gets instead, so there is no empty margin to
     look at. Hyphenation is off — the passages break at word boundaries in
     the exam and should here too. */
  max-width: none;
  -webkit-hyphens: none; hyphens: none;
}
.exam-wrap .prose p { margin: 0 0 1em; }
.exam-wrap .prose p:last-child { margin-bottom: 0; }
/* Plain ground. The passage sat on a warm paper tint, which at this weight
   and size just lowers the contrast of the thing you are reading. */
.exam-wrap .panel.reading-scroll:not(.is-ads) { background: var(--surface); }
html[data-theme="dark"] .exam-wrap .panel.reading-scroll:not(.is-ads) {
  background: var(--surface);
}
/* The explain button reserved a 30px lane above the first line. It needs
   about half that to clear the text, and the rest was a strip of nothing at
   the top of every passage. */
.exam-wrap .prose.xp-host { padding-top: 15px; }
.exam-wrap .prose.xp-host > .xp-btn { top: -3px; }

@media (max-width: 720px) {
  .exam-wrap .prose[lang="de"] { font-size: 1.09rem; line-height: 1.88; }
  /* 18px of page padding either side of a 13px panel padding spends 62px of
     a 375px screen on nothing. Half that back goes to the passage. */
  .exam-wrap.wrap { padding-inline: 12px; }
  .exam-wrap .panel { padding: 13px 12px; }
}

/* ── Sprachbausteine: the blanks, sized to what is in them ─────────────────
   Still taller than its content. The chips carry reading-size text and full
   padding for a single German word; the row keeps the rest. */
.exam-wrap .gap-row { padding: 6px 8px; gap: 3px 8px; }
.exam-wrap .gap-row .chip {
  padding: 5px 10px 5px 6px; font-size: .86rem; line-height: 1.35;
  border-radius: 8px;
}
.exam-wrap .gap-row .chip[data-key]::before {
  width: 17px; height: 17px; font-size: .66rem; border-radius: 5px;
}
.exam-wrap .gap-row .chip .opt-ar { font-size: .74em; line-height: 1.35; margin-top: 1px; }
.exam-wrap .gap-row .opts { gap: 5px; }
.exam-wrap .gap-list { gap: 5px; }
.exam-wrap .gap-row .gap-num { width: 26px; height: 26px; font-size: .8rem; }
.exam-wrap .exercise-layout:has(.gap-list) .panel { padding: 12px; }


/* ── Schreiben ────────────────────────────────────────────────────────────
   The clock could not be started without finding a ghost button the size of
   a caption inside a status line. It is the first thing to press on the page
   and now looks like it. */
.sw-start {
  margin-inline-start: 10px; padding: 8px 20px; font-size: .9rem;
  box-shadow: 0 3px 10px color-mix(in srgb, var(--signal) 42%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
  .sw-start { animation: sw-start-beat 3s 1s ease-in-out infinite; }
}
@keyframes sw-start-beat {
  0%, 100% { box-shadow: 0 3px 10px color-mix(in srgb, var(--signal) 42%, transparent); }
  55% { box-shadow: 0 3px 16px color-mix(in srgb, var(--signal) 70%, transparent); }
}

/* The Aufgabe: paper, not a lit panel — it is read alongside the essay box
   for the whole of the exercise. */
.exam-wrap [data-schreiben-host],
.exam-wrap .material {
  background: color-mix(in srgb, #F6EFE2 66%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--paper-2) 80%, transparent);
  border-radius: 12px; padding: 13px 14px;
}
.exam-wrap [data-schreiben-host] .prose { font-family: var(--font-soft); }
html[data-theme="dark"] .exam-wrap [data-schreiben-host],
html[data-theme="dark"] .exam-wrap .material {
  background: color-mix(in srgb, var(--surface) 86%, var(--paper));
}

/* ── Sprachbausteine: the text leads, the blanks follow ───────────────────
   Equal columns gave the answer side as much room as the passage, which made
   ten short questions look like the larger half of the exercise. The passage
   takes the width back; three options still sit in one row because the row
   is told to make three of them rather than fitting what it can. */
@media (min-width: 900px) {
  .exam-wrap .exercise-layout:has(.gap-list) {
    grid-template-columns: minmax(280px, .72fr) minmax(0, 1.28fr);
  }
}
.exam-wrap .gap-row .opts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 720px) {
  .exam-wrap .gap-row .opts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}


/* A revised or newly added version of a theme that also exists in its
   original form. Amber rather than the accent: it is a property of the
   exercise, not a state of the learner's progress through it. */
.badge.variant {
  background: color-mix(in srgb, var(--sun, #f6a723) 22%, transparent);
  color: var(--gold-ink, #8a5a06);
  font-weight: 800;
}
html[data-theme="dark"] .badge.variant {
  background: color-mix(in srgb, var(--sun, #f6a723) 18%, transparent);
  color: var(--sun, #f6a723);
}


/* ── reading grounds, per the owner's palette ─────────────────────────────
   Lesen Teil 2 and Sprachbausteine read on white; Schreiben's task column
   takes the cool tint so the brief and the essay box are told apart. */
.exam-wrap .exercise-layout:has(.q-card) .panel.reading-scroll,
.exam-wrap .exercise-layout:has(.gap-list) .panel,
.exam-wrap .exercise-layout:has(.word-bank) .panel {
  background: #ffffff;
}
.exam-wrap .writing-grid > :first-child,
.exam-wrap [data-schreiben-host],
.exam-wrap .material {
  background: #edf5f3;
  border-color: color-mix(in srgb, #2F5F94 12%, transparent);
}
html[data-theme="dark"] .exam-wrap .exercise-layout:has(.q-card) .panel.reading-scroll,
html[data-theme="dark"] .exam-wrap .exercise-layout:has(.gap-list) .panel,
html[data-theme="dark"] .exam-wrap .exercise-layout:has(.word-bank) .panel {
  background: color-mix(in srgb, var(--surface) 82%, var(--paper));
}
html[data-theme="dark"] .exam-wrap .writing-grid > :first-child,
html[data-theme="dark"] .exam-wrap [data-schreiben-host],
html[data-theme="dark"] .exam-wrap .material {
  background: color-mix(in srgb, var(--surface) 86%, var(--signal));
}

/* ── Sprechen: quieter, softer text ───────────────────────────────────────
   The exam turns are read aloud from, so the face matters more than the
   size; a shade smaller keeps a long turn on one screen. */
.sp-turn, .sp-transcript, .sp-live, .sp-lede,
.sp-turn p, .sp-turn .prose, .sp-card p {
  font-family: var(--font-ar);
  font-size: .95rem; line-height: 1.66;
}
.sp-turn h3, .sp-card h3 { font-family: var(--font-ar); }
/* The turns are German read aloud from, so they get the passage treatment:
   same face, same weight, the leading a step below the reading panel's
   because a turn is a few lines rather than a page. */
.sp-turn p, .sp-turn .prose, .sp-card p, .sp-transcript, .sp-live {
  font-weight: 600;
  font-size: 1.02rem; line-height: 1.78;
  letter-spacing: 0;
  color: var(--ink);
}

/* ── one card, two papers ─────────────────────────────────────────────────
   A theme that exists in an original and a revised cut shows once. The card
   says so in words on its own last line — a floated caret at --ink-3 was
   invisible against the title, and a glyph alone never said what it opened.
   The line is the hint; the ring around the chevron is what makes it read
   as something you can press. Each option below is still its own exercise —
   own route, own lock, own progress — so access and tracking are unchanged. */
.ex-group { cursor: pointer; }
.ex-group > summary { list-style: none; cursor: pointer; }
.ex-group > summary::-webkit-details-marker { display: none; }
.ex-group > summary::marker { content: ''; }

.ex-group-cue {
  display: flex; align-items: center; gap: 8px;
  margin-top: 11px; padding-top: 9px;
  border-top: 1px dashed var(--line);
  font-size: .76rem; font-weight: 700; line-height: 1.35;
  color: var(--ink-2);
}
.ex-group-cue-t { flex: 1; min-width: 0; }
.ex-group-chev {
  flex: none; width: 21px; height: 21px; padding: 2px; box-sizing: border-box;
  border-radius: 50%;
  background: color-mix(in srgb, var(--signal) 15%, transparent);
  fill: none; stroke: var(--ink-2); stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform .2s var(--ease), background .2s var(--ease);
}
.ex-group:hover .ex-group-chev,
.ex-group[open] .ex-group-chev {
  background: color-mix(in srgb, var(--signal) 28%, transparent);
}
.ex-group[open] .ex-group-chev { transform: rotate(180deg); }

.ex-group-list { display: grid; gap: 7px; margin-top: 10px; }
.ex-group[open] .ex-group-list { animation: ex-group-open .22s var(--ease) both; }
@keyframes ex-group-open { from { opacity: 0; transform: translateY(-4px); } }

.ex-var {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 11px; border-radius: 10px;
  border: 1px solid var(--line-soft);
  background: color-mix(in srgb, var(--signal) 6%, var(--surface));
  color: var(--ink); text-decoration: none;
  transition: border-color .15s var(--ease), transform .15s var(--ease);
}
.ex-var:hover {
  text-decoration: none; transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--signal) 55%, transparent);
}
/* The number is doing the counting the old badge used to do, in the one
   place where it means something: beside the option you are choosing. */
.ex-var-n {
  flex: none; width: 21px; height: 21px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--signal) 18%, transparent);
  color: var(--ink-2); font-size: .72rem; font-weight: 800;
}
.ex-var-txt { display: grid; gap: 1px; min-width: 0; }
.ex-var-t { font-size: .84rem; font-weight: 700; }
.ex-var-sub {
  font-family: var(--font-de); font-size: .71rem; color: var(--ink-3);
  direction: ltr; text-align: start;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Training reads in the same face as the exercises ─────────────────────
   Schriftlich and Sprechen both moved to the soft face; the Training
   section was still on the default stack, which made the two halves of
   the site read like two products. The three surfaces are the hub cards
   (.train-*), the drill games (.tg-*) and the Hören trainer (.hz-*). */
[class^="train-"], [class*=" train-"],
[class^="tg-"], [class*=" tg-"],
[class^="hz-"], [class*=" hz-"],
.dash-empty {
  font-family: var(--font-soft);
}

/* ── Sprechen reads in the Training face ─────────────────────────────────
   The exam turns were the last content still set in Cairo while Training,
   Schriftlich and the exercise pages had all moved to --font-soft. These
   are the Sprechen rules that name a face of their own; everything else
   under an sp- class picks the soft one up from the prefix rule. The nav
   (spn-) is chrome, not content, and is left alone — "sp-" does not match
   it. */
[class^="sp-"], [class*=" sp-"],
.sp-band, .sp-note b, .sp-review-score b,
.sp-rc-p, .sp-rt p, .sp-rm-btn [lang="de"] {
  font-family: var(--font-ar);
}

/* ── Sprachbausteine: the bank and the blanks, sized like the text ────────
   The words you pick and the blanks you drop them into are the exercise,
   and they were set at .88–.92rem in the soft face while the passage
   around them ran at 19px in Cairo. Two sizes and two faces apart, so the
   thing being read and the thing being answered did not look like they
   belonged to the same page. Scoped under .exam-wrap because that is where
   the rules they have to beat live. */
.exam-wrap .word-bank .chip,
.exam-wrap .option-bank .chip,
.exam-wrap .gap-row .chip,
.exam-wrap .chip {
  font-family: var(--font-ar); font-weight: 600;
  font-size: 1.02rem; line-height: 1.5;
}
.exam-wrap .word-bank { grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 8px; }
.exam-wrap .word-bank .chip { padding: 10px 14px; }
.exam-wrap .opt { font-size: 1rem; line-height: 1.6; }
.exam-wrap .slot {
  font-family: var(--font-ar); font-size: 1.02rem;
  min-height: 46px; padding: 10px 13px;
}
.exam-wrap .gap-row { padding: 13px 14px; }
.exam-wrap .gap-row .gap-num { font-size: .84rem; margin-bottom: 8px; }
.exam-wrap .gap-row .opts { gap: 7px; }
.exam-wrap .gap-row .chip { padding: 9px 16px; }
@media (max-width: 720px) {
  .exam-wrap .word-bank { grid-template-columns: repeat(auto-fit, minmax(124px, 1fr)); gap: 7px; }
  .exam-wrap .word-bank .chip { padding: 9px 11px; font-size: .97rem; }
  .exam-wrap .opt { font-size: .97rem; }
}

/* A long compound in a 148px cell ran past the chip's border instead of
   breaking. The cell is a grid track, so the text also needs min-width:0
   before it is allowed to wrap at all. */
.exam-wrap .word-bank .chip,
.exam-wrap .option-bank .chip,
.exam-wrap .gap-row .chip,
.exam-wrap .chip {
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: normal;
  hyphens: auto;
}
.exam-wrap .word-bank .chip > *,
.exam-wrap .option-bank .chip > * { min-width: 0; overflow-wrap: anywhere; }

/* ── The offer popup ────────────────────────────────────────────────
 * One image on a dimmed backdrop. The box never exceeds the viewport, and
 * the image is bounded on both axes with its own aspect ratio kept, so a
 * tall poster on a short phone shrinks instead of overflowing or stretching.
 */
.promo-back {
  position: fixed; inset: 0; z-index: 9000;
  display: flex; align-items: center; justify-content: center;
  padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
  background: rgba(12, 26, 40, .62);
  opacity: 0; transition: opacity .2s ease;
  overscroll-behavior: contain;
}
.promo-back.is-on { opacity: 1; }

.promo-box {
  position: relative;
  max-width: min(420px, 100%);
  max-height: 100%;
  transform: translateY(8px) scale(.98);
  transition: transform .2s ease;
}
.promo-back.is-on .promo-box { transform: none; }

.promo-link { display: block; text-decoration: none; }

.promo-img {
  display: block;
  max-width: 100%;
  /* Room for the button underneath, so the pair still fits a short screen. */
  max-height: calc(100dvh - 96px);
  width: auto; height: auto;
  object-fit: contain;
  border-radius: 16px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .35);
}

/* Sits on the artwork, clear of it, and stays a comfortable tap target. */
.promo-x {
  position: absolute; top: -10px; inset-inline-end: -10px;
  width: 40px; height: 40px; padding: 0;
  display: grid; place-items: center;
  font: 600 17px/1 system-ui, sans-serif;
  color: #0c1a28; background: #fff;
  border: 0; border-radius: 50%;
  box-shadow: 0 3px 12px rgba(0, 0, 0, .3);
  cursor: pointer;
}
.promo-x:hover { background: #f1f5f9; }
.promo-x:focus-visible { outline: 3px solid #7dd3fc; outline-offset: 2px; }

@media (max-width: 480px) {
  .promo-box { max-width: 100%; }
  .promo-x { top: -6px; inset-inline-end: -6px; width: 36px; height: 36px; }
}

@media (prefers-reduced-motion: reduce) {
  .promo-back, .promo-box { transition: none; }
}

/* The call to action. Repeats the link the artwork already carries, for
   anyone who would not think to tap a picture. */
.promo-cta {
  display: block;
  margin-top: 10px;
  padding: 13px 18px;
  font: 700 1rem/1.2 var(--font-ar, system-ui), system-ui, sans-serif;
  text-align: center;
  text-decoration: none;
  color: #fff;
  background: #25d366;
  border-radius: 12px;
  box-shadow: 0 6px 18px rgba(37, 211, 102, .35);
}
.promo-cta:hover { background: #1fb855; }
.promo-cta:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }

/* ── Phone workflow for the written-exam exercises ───────────────────
 * Scoped to body[data-ex-page], which only the Lesen, Hören,
 * Sprachbausteine and Schreiben exercise pages carry. Sprechen and Training
 * are built elsewhere, have no such attribute, and nothing here can reach
 * them. Desktop is untouched: every rule below is inside the phone query,
 * except the check button, which is deliberately green everywhere.
 */

/* تصحيح — corrects the whole exercise. Green, and the widest thing in the
   row, because it is the one control every learner is looking for. */
body[data-ex-page] .actions [data-check] {
  background: #d8f3e3;
  border: 1px solid #7ccfa6;
  color: #10603f;
  font-weight: 700;
}
body[data-ex-page] .actions [data-check]:hover { background: #c7ecd6; }
html[data-theme="dark"] body[data-ex-page] .actions [data-check] {
  background: #17402f; border-color: #2f7a57; color: #b9ecd1;
}

/* The floating WhatsApp button sits over the answer controls on a phone and
   is a wrong tap away from losing the exercise. It stays on every other page;
   inside an exercise it goes. */
body[data-ex-page] .wa-fab { display: none; }

@media (max-width: 700px) {
  body[data-ex-page] .actions {
    display: flex; gap: 8px; flex-wrap: wrap;
  }
  body[data-ex-page] .actions [data-check] {
    flex: 1 1 100%; min-height: 48px; font-size: 1rem;
  }
  body[data-ex-page] .actions [data-reset] { flex: 1 1 auto; min-height: 44px; }

  /* Sprachbausteine: the choices, sitting under their own paragraph. */
  body[data-ex-page] .exm-gaps {
    margin: 10px 0 16px;
    display: flex; flex-direction: column; gap: 10px;
  }
  body[data-ex-page] .exm-gaps .gap-row { margin: 0; }
  body[data-ex-page] .exm-gaps .chip { min-height: 40px; padding: 9px 15px; }
  /* The list it came from is empty now; its heading would be a lone title. */
  body[data-ex-page] .exm-on ~ * .gap-list:empty,
  body[data-ex-page] .gap-list:empty { display: none; }

  /* Lesen Teil 1 and 3: comfortable targets. The slot already sits with its
     own text, so nothing is moved here — see the note in exercise-mobile.js. */
  body[data-ex-page] .option-bank .chip { min-height: 42px; }
  body[data-ex-page] .slot { min-height: 44px; }

  /* The reading panel is left exactly as the stylesheet already has it on a
     small screen: no cap, no inner scrollbar, growing to its own height. An
     earlier version of this block capped it at 52vh, which fought that rule —
     the cap applied but `overflow: visible` did not, so the text spilled out
     of its own card and over the panel beneath. The scrolling problem is
     solved by bringing the answers up into the text, not by boxing the text.

     Sprachbausteine: the side panel has been emptied of its rows, so its
     heading and hint would introduce nothing. The actions stay — تصحيح is the
     end of the exercise and belongs at the end of the page. */
  body[data-ex-page="cloze-mc"] .exm-done .gap-list,
  body[data-ex-page="cloze-bank"] .exm-done .gap-list,
  body[data-ex-page="cloze-mc"] .exm-done > h2,
  body[data-ex-page="cloze-bank"] .exm-done > h2,
  body[data-ex-page="cloze-mc"] .exm-done > .answers-hint,
  body[data-ex-page="cloze-bank"] .exm-done > .answers-hint { display: none; }

  /* One block of choices, sitting under its own sentence. Laid out as a
     plain stack: the number on its own line, then one full-width choice per
     line. Two columns put the German of one choice beside the Arabic of
     another and made the block twice as tall as it needed to be. */
  body[data-ex-page] .exm-gaps {
    margin: 12px 0 16px;
  }
  body[data-ex-page] .exm-gaps .gap-row {
    display: block;
    margin: 0;
    border-inline-start: 3px solid var(--signal-deep, #2563eb);
  }
  /* The badge is a 30px grid box that centres its numeral; an earlier
     `display: block` here threw that away and left the number in the corner. */
  body[data-ex-page] .exm-gaps .gap-num {
    display: grid; place-items: center;
    margin: 0 0 8px;
  }
  body[data-ex-page] .exm-gaps .opts {
    display: grid; grid-template-columns: 1fr; gap: 8px;
  }
  body[data-ex-page] .exm-gaps .chip {
    width: 100%; text-align: start; justify-content: flex-start;
    border-radius: 10px; padding: 10px 14px;
  }

  /* Hören: two columns of radio buttons become two real buttons under the
     sentence. Same inputs, same names, same values — only the arrangement. */
  body[data-ex-page="listening-tf"] .tf-table-wrap { overflow-x: visible; }
  body[data-ex-page="listening-tf"] .tf-table,
  body[data-ex-page="listening-tf"] .tf-table tbody,
  body[data-ex-page="listening-tf"] .tf-table tr,
  body[data-ex-page="listening-tf"] .tf-table td { display: block; width: auto; }
  body[data-ex-page="listening-tf"] .tf-table thead { display: none; }
  /* Grid rather than plain blocks, so the sentence can be pulled above the
     two choices. The cells are in telc's source order (richtig, falsch,
     statement) and that order is what the markup and the checker rely on —
     only the painting is reordered. */
  body[data-ex-page="listening-tf"] .tf-table tr {
    display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
    border: 1px solid var(--line); border-radius: var(--radius-sm, 10px);
    padding: 12px; margin-bottom: 10px;
  }
  body[data-ex-page="listening-tf"] .tf-table td { border-top: 0; padding: 0; }
  body[data-ex-page="listening-tf"] .tf-table td.stmt {
    order: -1; grid-column: 1 / -1; margin-bottom: 2px;
  }
  body[data-ex-page="listening-tf"] .tf-table td.tf-answer { grid-column: 1 / -1; }
  body[data-ex-page="listening-tf"] .tf-table td.pick {
    text-align: center;
    border: 1px solid var(--line); border-radius: 10px;
    padding: 9px 4px 10px;
  }
  body[data-ex-page="listening-tf"] .tf-table td.pick label,
  body[data-ex-page="listening-tf"] .tf-table td.pick input { cursor: pointer; }
  body[data-ex-page="listening-tf"] .tf-table td.pick::before {
    content: attr(data-label);
    display: block; font-size: .72rem; color: var(--ink-3); margin-bottom: 4px;
    font-weight: 700; letter-spacing: .04em;
  }
  body[data-ex-page="listening-tf"] .tf-table td.pick input {
    width: 22px; height: 22px;
  }
  /* The verdict on a phone: the two choices are cards, so they carry it
     themselves — the one tapped in solid green or red, the one that should
     have been tapped ringed in dashed green. The ::after overlay used on the
     desktop table would sit on top of the label here, so the card is painted
     directly instead. */
  body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick]::after,
  body[data-ex-page="listening-tf"] .tf-table td.pick[data-right]::after { content: none; }
  body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick="correct"] {
    background: var(--ok-bg); border: 2px solid var(--ok);
  }
  body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick="wrong"] {
    background: var(--bad-bg); border: 2px solid var(--bad);
  }
  body[data-ex-page="listening-tf"] .tf-table tr[data-state="incorrect"] td.pick[data-right]:not([data-pick]),
  body[data-ex-page="listening-tf"] .tf-table tr[data-state="missed"] td.pick[data-right] {
    border: 2px dashed var(--ok); background: var(--ok-bg);
  }
  body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick="correct"]::before { color: var(--ok); }
  body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick="wrong"]::before { color: var(--bad); }
  /* The row card itself, not just its cells — on mobile the td background
     stops at the cell edge and the card read as untouched. */
  body[data-ex-page="listening-tf"] .tf-table tr[data-state="correct"] { border-color: var(--ok-line); }
  body[data-ex-page="listening-tf"] .tf-table tr[data-state="incorrect"],
  body[data-ex-page="listening-tf"] .tf-table tr[data-state="missed"] { border-color: var(--bad-line); }
  body[data-ex-page="listening-tf"] .tf-table tr[data-state] td { background: transparent; }
  body[data-ex-page="listening-tf"] .tf-table tr[data-state="correct"] { background: var(--ok-bg); }
  body[data-ex-page="listening-tf"] .tf-table tr[data-state="incorrect"],
  body[data-ex-page="listening-tf"] .tf-table tr[data-state="missed"] { background: var(--bad-bg); }

  /* Schreiben: a taller box, and nothing floating over it. */
  body[data-ex-page="writing"] textarea { min-height: 42vh; font-size: 1rem; }
}

/* ── Lesen Teil 1 and 3 on a phone: the answer list under every text ──── */
@media (max-width: 700px) {
  /* The shared bank is what holds the state, so it stays in the DOM and is
     only hidden; the per-text lists forward their taps to it. */
  body[data-ex-page="matching"] .exm-picking [data-bank] { display: none; }
  body[data-ex-page="matching"] .exm-picking .sticky-side > h2,
  body[data-ex-page="matching"] .exm-picking .answers-hint { display: none; }

  body[data-ex-page="matching"] .exm-picks {
    display: grid; grid-template-columns: 1fr; gap: 8px;
    margin: 10px 0 20px;
    padding-inline-start: 10px;
    border-inline-start: 3px solid var(--signal-deep, #2563eb);
  }

  body[data-ex-page="matching"] .exm-pick {
    display: grid;
    grid-template-columns: 26px 1fr;
    gap: 2px 10px;
    width: 100%;
    min-height: 46px;
    padding: 10px 12px;
    text-align: start;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 10px;
    font: inherit; color: var(--ink);
    cursor: pointer;
  }
  body[data-ex-page="matching"] .exm-pick-key {
    grid-row: 1 / span 2;
    display: grid; place-items: center;
    width: 26px; height: 26px;
    border-radius: 7px;
    background: var(--signal-wash, #e8f0fe);
    color: var(--signal-deep, #2563eb);
    font-weight: 700; font-size: .8rem;
  }
  body[data-ex-page="matching"] .exm-pick-text {
    font-family: var(--font-de); direction: ltr; text-align: left;
    font-size: .93rem; line-height: 1.35;
  }
  body[data-ex-page="matching"] .exm-pick-ar {
    font-size: .8rem; color: var(--ink-3); line-height: 1.4;
  }

  /* Taken by another text: still visible, plainly not available. */
  body[data-ex-page="matching"] .exm-pick.is-used {
    opacity: .42;
  }
  /* The one chosen for this text. */
  body[data-ex-page="matching"] .exm-pick.is-chosen {
    border-color: var(--signal-deep, #2563eb);
    box-shadow: inset 0 0 0 1px var(--signal-deep, #2563eb);
    background: var(--signal-wash, #e8f0fe);
  }

  /* After تصحيح. Right in green, wrong in red, and on a wrong card the
     answer that was wanted is marked green beside it. */
  body[data-ex-page="matching"] .exm-pick.is-correct {
    border-color: var(--ok-line, #7ccfa6);
    background: var(--ok-bg, #e7f7ef);
    box-shadow: inset 0 0 0 1px var(--ok-line, #7ccfa6);
  }
  body[data-ex-page="matching"] .exm-pick.is-correct .exm-pick-key {
    background: var(--ok, #16a34a); color: #fff;
  }
  body[data-ex-page="matching"] .exm-pick.is-wrong {
    border-color: var(--bad-line, #e3a3a3);
    background: var(--bad-bg, #fdeced);
    box-shadow: inset 0 0 0 1px var(--bad-line, #e3a3a3);
  }
  body[data-ex-page="matching"] .exm-pick.is-wrong .exm-pick-key {
    background: var(--bad, #dc2626); color: #fff;
  }
  body[data-ex-page="matching"] .exm-pick.is-wrong .exm-pick-text {
    text-decoration: line-through;
  }
  body[data-ex-page="matching"] .exm-pick.is-answer {
    border-color: var(--ok-line, #7ccfa6);
    background: var(--ok-bg, #e7f7ef);
    opacity: 1;
  }
  body[data-ex-page="matching"] .exm-pick.is-answer .exm-pick-key {
    background: var(--ok, #16a34a); color: #fff;
  }
}

/* ── Sprachbausteine on a phone: the result lives in the answer boxes ───
 * The row used to tint whole — border and background — which put green and
 * red outside the answers themselves. On a phone the card stays neutral and
 * each choice carries its own verdict instead.
 */
@media (max-width: 700px) {
  body[data-ex-page="cloze-mc"] .exm-gaps .gap-row[data-state],
  body[data-ex-page="cloze-bank"] .exm-gaps .gap-row[data-state] {
    background: var(--surface);
    border-color: var(--line);
  }

  body[data-ex-page] .exm-gaps .chip.is-correct {
    border-color: var(--ok-line, #7ccfa6);
    background: var(--ok-bg, #e7f7ef);
    box-shadow: inset 0 0 0 1px var(--ok-line, #7ccfa6);
    color: var(--ok, #10603f);
  }
  body[data-ex-page] .exm-gaps .chip.is-correct[data-key]::before {
    background: var(--ok, #16a34a); color: #fff;
  }
  body[data-ex-page] .exm-gaps .chip.is-wrong {
    border-color: var(--bad-line, #e3a3a3);
    background: var(--bad-bg, #fdeced);
    box-shadow: inset 0 0 0 1px var(--bad-line, #e3a3a3);
    color: var(--bad, #b3261e);
  }
  body[data-ex-page] .exm-gaps .chip.is-wrong[data-key]::before {
    background: var(--bad, #dc2626); color: #fff;
  }
  /* `.selected` is how app.js marks the right answer once checked; with the
     verdict classes present it would paint a third colour over them. */
  body[data-ex-page] .exm-gaps .chip.is-correct.selected,
  body[data-ex-page] .exm-gaps .chip.is-wrong.selected { filter: none; }
}

/* ── The "what's new" strip ─────────────────────────────────────────
 * One line on the listing pages. Quiet enough to ignore, close enough to the
 * tabs to be read on the way past, and gone for good once dismissed.
 */
.newsbar {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 16px; padding: 11px 14px;
  background: color-mix(in srgb, var(--signal) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--signal) 32%, transparent);
  border-radius: var(--radius-sm, 10px);
  transition: opacity .18s ease;
}
.newsbar.is-going { opacity: 0; }
.newsbar-ic { font-size: 1.05rem; line-height: 1; }
.newsbar-txt {
  margin: 0; flex: 1 1 auto;
  font-size: .89rem; line-height: 1.5; color: var(--ink);
}
.newsbar-txt b { font-family: var(--font-de); font-weight: 700; }
.newsbar-x {
  flex: 0 0 auto;
  width: 30px; height: 30px; padding: 0;
  display: grid; place-items: center;
  border: 0; border-radius: 50%;
  background: transparent; color: var(--ink-3);
  font-size: .88rem; cursor: pointer;
}
.newsbar-x:hover { background: color-mix(in srgb, var(--ink) 8%, transparent); color: var(--ink); }
.newsbar-x:focus-visible { outline: 2px solid var(--signal-deep, #2563eb); outline-offset: 2px; }

/* One line of text and a close button: it stays a single row on a phone
   rather than wrapping the icon and the ✕ onto their own line. */
@media (max-width: 560px) {
  .newsbar { padding: 10px 12px; gap: 8px; }
  .newsbar-txt { font-size: .84rem; }
}

/* ── Hören on a phone: the verdict belongs to the choice ────────────
 * The row tint is dropped here. Tinting the whole card painted the option
 * that was right in red along with everything else on a wrong row, and left
 * the answer line reading "خطأ" in green inside it. Each box says its own
 * piece instead: the right one green, the one taken red when it was not it,
 * the statement and the card untouched.
 */
@media (max-width: 700px) {
  body[data-ex-page="listening-tf"] .exm-tf .tf-table tr[data-state] td {
    background: transparent;
  }
  body[data-ex-page="listening-tf"] .exm-tf .tf-table tr[data-state] {
    background: var(--surface);
    border-color: var(--line);
  }

  body[data-ex-page="listening-tf"] .exm-tf .tf-table td.pick.is-correct {
    border-color: var(--ok-line, #7ccfa6);
    background: var(--ok-bg, #e7f7ef);
    box-shadow: inset 0 0 0 1px var(--ok-line, #7ccfa6);
  }
  body[data-ex-page="listening-tf"] .exm-tf .tf-table td.pick.is-correct::before {
    color: var(--ok, #10603f);
  }
  body[data-ex-page="listening-tf"] .exm-tf .tf-table td.pick.is-wrong {
    border-color: var(--bad-line, #e3a3a3);
    background: var(--bad-bg, #fdeced);
    box-shadow: inset 0 0 0 1px var(--bad-line, #e3a3a3);
  }
  body[data-ex-page="listening-tf"] .exm-tf .tf-table td.pick.is-wrong::before {
    color: var(--bad, #b3261e);
  }
  /* The box you chose keeps a heavier outline, so "what I answered" is still
     legible once both boxes carry a colour. */
  body[data-ex-page="listening-tf"] .exm-tf .tf-table td.pick.is-picked {
    box-shadow: inset 0 0 0 2px currentColor;
  }
  body[data-ex-page="listening-tf"] .exm-tf .tf-table td.pick.is-picked.is-correct { color: var(--ok, #10603f); }
  body[data-ex-page="listening-tf"] .exm-tf .tf-table td.pick.is-picked.is-wrong { color: var(--bad, #b3261e); }

  /* The answer line is a statement of fact, not a verdict: it is the same
     colour whether you were right or wrong. */
  body[data-ex-page="listening-tf"] .exm-tf .tf-table td.tf-answer {
    color: var(--ink-3); font-weight: 600; margin-top: 8px;
  }
}

/* ── The free-week strip ────────────────────────────────────────────
 * First thing in the body and in normal flow: it pushes the page down
 * instead of sitting over it, so there is nothing for it to hide. One
 * centred line across the top of the page, like the pricing strip it
 * replaces — no button, because the whole page behind it is the offer.
 */
.freebar {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: 6px 14px;
  padding: 10px 16px;
  background: #c62828;
  color: #fff;
  text-align: center;
}
.freebar-txt { margin: 0; font-size: .9rem; line-height: 1.5; }
.freebar-txt b { font-weight: 800; }
.freebar-sub { opacity: .9; }

@media (max-width: 560px) {
  .freebar { padding: 9px 12px; gap: 6px 10px; }
  .freebar-txt { font-size: .83rem; }
  /* The countdown goes under the headline rather than squeezing it. */
  .freebar-sub { display: block; }
}

/* ── Lesen Teil 1 and 3 on a phone: how far through you are ───────────
 * A hairline and a count. Each paragraph carries its own choices now,
 * so the answered ones scroll out of sight and the emptying bank no
 * longer shows progress — this puts that back in one line, stuck to
 * the top of the engine so it reads from any paragraph.
 */
/* The progress line lives in the pager row now, not in the page: it is
   the bottom edge of the السابق / التالي strip, so it is already stuck
   where that strip is stuck and needs no ground of its own. */
body[data-ex-page] .quicknav .exm-prog {
  display: block; height: 3px; margin: 0;
  background: transparent; border: 0; border-radius: 0; box-shadow: none;
}
body[data-ex-page] .exm-prog-track {
  display: block; height: 3px; border-radius: 0;
  background: transparent;
  overflow: hidden;
}
body[data-ex-page] .exm-prog-fill {
  display: block; height: 100%; width: 0; border-radius: 999px;
  background: var(--signal-deep);
  transition: width .25s var(--ease);
}
body[data-ex-page] .exm-prog.is-done .exm-prog-fill { background: #15803d; }
/* The count is read out, not drawn: the line is the whole of it on
   screen, but "three of five" is what a screen reader needs. */
body[data-ex-page] .exm-prog-n {
  /* Pinned to the start, not left wherever the flow put it: clipped to
     1px it still has a box, and at the end of a full-width row that box
     sat one pixel past the viewport. */
  position: absolute; left: 0; right: auto; top: 0;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
body[data-ex-page] .exm-prog.is-done .exm-prog-n { color: #15803d; }

/* ── Lesen Teil 2: the letter, the radio and the text on one grid ─────
 * The letter used to be absolutely positioned at top: 50% of the whole
 * label — and the label is two lines, German then Arabic — so it sat in
 * the gap between them rather than beside the sentence it labels. A
 * three-column grid puts the letter, the control and the text on the
 * same first line, and lets the Arabic span the text column underneath,
 * where it lines up with the German instead of starting from its own
 * padding.
 */
.opt[data-key] {
  position: static;
  display: grid;
  grid-template-columns: 20px auto minmax(0, 1fr);
  align-items: start; gap: 4px 10px;
  padding-inline-start: 11px;
}
.opt[data-key]::before {
  position: static; transform: none;
  grid-column: 1; grid-row: 1;
  margin-top: 1px;
}
.opt[data-key] > input { grid-column: 2; grid-row: 1; margin-top: 3px; }
.opt[data-key] > span[data-gloss] {
  grid-column: 3; grid-row: 1;
  min-width: 0; overflow-wrap: anywhere;
}
.opt[data-key] .opt-ar {
  grid-column: 3; grid-row: 2;
  flex: none; padding-inline-start: 0; margin-top: 2px;
  min-width: 0; overflow-wrap: anywhere;
}


/* ── Lesen: one white sheet, no rules across it ──────────────────────
 * Teil 1, 2 and 3 are a passage and its questions, and the page drew a
 * line between them, a line under the breadcrumb row and a frame round
 * each half — four separations for two things that are read together.
 *
 * Here the page is white from edge to edge and the two halves are told
 * apart by the gap between them alone. The cards inside keep their own
 * edges: a question is a thing you answer, not a section of the page.
 *
 * Scoped to the two Lesen engines by their page markers. Sprachbausteine
 * (cloze-*), Hören (listening-tf) and Schreiben keep what they have.
 */
body[data-ex-page="matching"],
body[data-ex-page="reading-mc"] { background: #ffffff; }

body[data-ex-page="matching"] .exam-wrap .panel,
body[data-ex-page="reading-mc"] .exam-wrap .panel {
  background: #ffffff;
  border-color: transparent;
}
body[data-ex-page="matching"] .ex-top,
body[data-ex-page="reading-mc"] .ex-top { border-bottom: 0; }

/* The gutter does the separating now, so it has to be wide enough to. */
@media (min-width: 961px) {
  body[data-ex-page="matching"] .exam-wrap .exercise-layout,
  body[data-ex-page="reading-mc"] .exam-wrap .exercise-layout { gap: 30px; }
}

html[data-theme="dark"] body[data-ex-page="matching"],
html[data-theme="dark"] body[data-ex-page="reading-mc"] { background: var(--paper); }
html[data-theme="dark"] body[data-ex-page="matching"] .exam-wrap .panel,
html[data-theme="dark"] body[data-ex-page="reading-mc"] .exam-wrap .panel {
  background: var(--surface);
}

/* ── Lesen: two sheets, a shorter text, roomier answers ───────────────
 * The page ground is a shade off white so the two panels can be pure
 * white and read as two sheets laid on it. Pure white on pure white is
 * one surface however wide the gutter.
 */
body[data-ex-page="matching"],
body[data-ex-page="reading-mc"] { background: #FAFAF8; }
body[data-ex-page="matching"] .exam-wrap .panel,
body[data-ex-page="reading-mc"] .exam-wrap .panel {
  background: #ffffff;
  border-color: transparent;
  box-shadow: 0 1px 2px rgba(20, 38, 58, .05), 0 8px 24px -18px rgba(20, 38, 58, .35);
}

/* The passage is the long half: a step down in size is several fewer
   screens of scrolling, and it is still the largest text on the page.
   The answers go the other way — they are what the hand aims at. */
@media (min-width: 961px) {
  body[data-ex-page="matching"] .exam-wrap .option-bank .chip,
  body[data-ex-page="matching"] .exam-wrap .chip {
    font-size: .97rem; padding: 12px 15px;
  }
  body[data-ex-page="reading-mc"] .opt { font-size: 1rem; }
  body[data-ex-page="reading-mc"] .opt .opt-ar { font-size: .95em; }
}

@media (max-width: 700px) {
  body[data-ex-page="matching"] .exm-pick { min-height: 52px; padding: 12px 13px; }
  body[data-ex-page="matching"] .exm-pick-text { font-size: 1rem; line-height: 1.4; }
  body[data-ex-page="matching"] .exm-pick-ar { font-size: .86rem; }
  body[data-ex-page="reading-mc"] .opt { font-size: .97rem; }
}

/* The Teil, beside the level on a card. Quieter than the level badge:
   the level is what a learner filters by first, the Teil second. */
.badge.teil {
  background: color-mix(in srgb, var(--signal) 14%, transparent);
  border-color: color-mix(in srgb, var(--signal-deep) 22%, transparent);
  color: var(--accent-ink, #24507F);
  font-weight: 700;
}
html[data-theme="dark"] .badge.teil { color: var(--signal); }

/* ── Lesen: room under each answer ───────────────────────────────────
 * The options were set tight against each other, which reads as one
 * block of text rather than a set of separate things to choose
 * between. A gap under each is the cheapest way to say they are
 * separate.
 */
body[data-ex-page="reading-mc"] .opt { margin-bottom: 8px; }
body[data-ex-page="reading-mc"] .opt:last-child { margin-bottom: 0; }
body[data-ex-page="matching"] .exm-picks { gap: 9px; }
body[data-ex-page="matching"] .exam-wrap .option-bank .chip { margin-bottom: 9px; }
body[data-ex-page="matching"] .exam-wrap .option-bank .chip:last-child { margin-bottom: 0; }

/* ── Bionic reading ──────────────────────────────────────────────────
 * The bolded head of each word, and the button that offers it. Offered,
 * not applied: it helps some readers and does nothing for others, so it
 * is never on until someone asks for it.
 */
.bio { font-weight: 700; color: inherit; }
.bio-on { letter-spacing: .005em; }

.bio-btn .bio-ico {
  font-family: var(--font-de); font-size: .64rem; letter-spacing: .02em;
  opacity: .9;
}
.bio-btn .bio-ico b { font-weight: 900; }
.bio-btn.on {
  background: color-mix(in srgb, #15803d 13%, transparent);
  border-color: #15803d; color: #15803d; opacity: 1;
}
html[data-theme="dark"] .bio-btn.on { color: #86efac; border-color: #86efac; }

/* Two buttons in the block's top corner share one positioned row; .xp-btn
   is absolutely placed on its own and a second would sit on top of it. */
.xp-tools {
  position: absolute; top: 4px; inset-inline-end: 0;
  display: flex; align-items: center; gap: 6px; z-index: 2;
}
.xp-tools .xp-btn { position: static; top: auto; inset-inline-end: auto; }
.xp-host:hover .xp-tools .xp-btn { opacity: 1; }

/* The explainer, shown once. A reader who has never met bionic reading
   should not have their page silently rearranged. */
.bio-note-back {
  position: fixed; inset: 0; z-index: 120;
  display: grid; place-items: center; padding: 20px;
  background: rgba(10, 20, 32, .5);
  backdrop-filter: blur(3px);
}
.bio-note {
  width: min(27rem, 100%);
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: 16px;
  padding: 22px 22px 18px;
  box-shadow: 0 24px 60px -24px rgba(0, 0, 0, .5);
}
.bio-note h3 { margin: 0 0 10px; font-size: 1.05rem; }
.bio-note p { margin: 0 0 9px; font-size: .92rem; line-height: 1.7; color: var(--ink-2); }
.bio-note-s { color: var(--ink-3) !important; font-size: .86rem !important; }
.bio-note-a { display: flex; gap: 9px; margin-top: 16px; }
.bio-note-a .btn { flex: 1; }

/* ── The bionic button, made findable ────────────────────────────────
 * It was drawn like شرح: a faint outline at 75% opacity, parked in the
 * block's corner and brightening on hover. On a phone there is no
 * hover, two buttons are wider than the lane the block reserves, and
 * the row sat over the first line of German.
 *
 * So: filled rather than outlined, full opacity always, and on a phone
 * out of the absolute positioning altogether — a normal row at the top
 * of the block, which cannot overlap anything by construction.
 */
.bio-btn {
  opacity: 1;
  background: color-mix(in srgb, var(--signal) 18%, var(--surface));
  border-color: color-mix(in srgb, var(--signal-deep) 42%, transparent);
  color: var(--accent-ink, #24507F);
  font-weight: 800;
}
.bio-btn:hover {
  background: color-mix(in srgb, var(--signal) 30%, var(--surface));
  border-color: var(--signal-deep); color: var(--signal-deep);
}
html[data-theme="dark"] .bio-btn { color: var(--signal); }

@media (max-width: 700px) {
  .xp-tools {
    position: static;
    justify-content: flex-end;
    margin: 0 0 10px;
  }
  /* The reserved lane was for a button floating over the text; in flow
     there is nothing to reserve. */
  .xp-host, .exam-wrap .prose.xp-host { padding-top: 0; }
  .xp-tools .xp-btn { height: 26px; font-size: .72rem; padding: 0 9px; }
}

/* On a Lesen page the tools go at the top of the block at every width,
   not only on a phone: the passage is the one place where a button
   floating in the corner sits over the first line a reader starts on. */
body[data-ex-page="matching"] .xp-tools,
body[data-ex-page="reading-mc"] .xp-tools {
  position: static;
  justify-content: flex-end;
  margin: 0 0 10px;
}
body[data-ex-page="matching"] .xp-host,
body[data-ex-page="reading-mc"] .xp-host,
body[data-ex-page="matching"] .exam-wrap .prose.xp-host,
body[data-ex-page="reading-mc"] .exam-wrap .prose.xp-host { padding-top: 0; }

/* ── The paragraph eraser ────────────────────────────────────────────
 * A text you have finished with is still taking up the screen while you
 * hunt for the next answer. Folding it away is not deleting it: a stub
 * takes its place, a counter above says how many are away, and one
 * press brings them all back.
 *
 * The control is a labelled button in flow at the end of its text. The
 * first version was a bare × floating in the corner — which on a
 * reading exercise reads as "wrong answer" as easily as "close", and
 * which sat on top of whatever was underneath it.
 */
.pe-btn {
  display: inline-flex; align-items: center; gap: 7px;
  margin: 10px 0 0; padding: 6px 11px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink-3);
  cursor: pointer; font: inherit; font-size: .76rem; font-weight: 700;
  max-width: 100%;
}
.pe-btn-i { display: grid; place-items: center; flex: 0 0 auto; }
.pe-btn-i svg { width: 15px; height: 15px; display: block; }
.pe-btn:hover { color: var(--ink); border-color: var(--ink-3); }

.pe-hidden { display: none !important; }

/* What is left behind: it names the text, says it is hidden, and the
   word to press is spelled out rather than left to an icon. */
.pe-stub {
  display: flex; align-items: center; justify-content: flex-start; gap: 8px;
  width: 100%; margin: 0 0 10px; padding: 9px 12px;
  border: 1px dashed var(--line); border-radius: 10px;
  background: transparent; color: var(--ink-3);
  font: inherit; font-size: .8rem; font-weight: 700;
  cursor: pointer; text-align: start;
}
.pe-stub:hover { border-color: var(--signal-deep); color: var(--ink-2); }
/* display:flex above outranks the user agent's [hidden] rule, so say it
   here as well for any caller that hides one rather than removing it. */
.pe-stub[hidden] { display: none !important; }
.pe-stub-i { display: grid; place-items: center; flex: 0 0 auto; }
.pe-stub-i svg { width: 16px; height: 16px; display: block; }
/* One line, left to sit where the text was. No column pushed to the far
   edge: the action belongs in the sentence, not opposite it. */
.pe-stub-t { min-width: 0; }
.pe-stub-t u {
  color: var(--signal-deep); font-weight: 800;
  text-underline-offset: 3px;
}
html[data-theme="dark"] .pe-stub-t u { color: var(--signal); }

.pe-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 14px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 9px 13px; border-radius: 10px;
  background: color-mix(in srgb, var(--signal) 10%, transparent);
  color: var(--ink-2); font-size: .8rem; font-weight: 700;
}
.pe-bar[hidden] { display: none; }
.pe-bar-t { min-width: 0; }
.pe-bar button {
  flex: 0 0 auto; border: 0; background: transparent; cursor: pointer;
  color: var(--signal-deep); font: inherit; font-weight: 800;
  text-decoration: underline; text-underline-offset: 3px;
}
html[data-theme="dark"] .pe-bar button { color: var(--signal); }

/* ── Schreiben: free, or built up in four steps ──────────────────────
 * The mode switch is two cards rather than a toggle, because the choice
 * is between two ways of working and a toggle would read as turning a
 * feature on. Free is selected on load: the exam is written alone.
 */
.sg-modes { display: flex; gap: 9px; margin: 0 0 12px; }
.sg-mode {
  flex: 1; display: flex; flex-direction: column; gap: 2px;
  padding: 10px 13px; border-radius: 11px;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--ink-2); cursor: pointer; text-align: start; font: inherit;
  transition: border-color .15s var(--ease), background .15s var(--ease);
}
.sg-mode b { font-size: .9rem; font-weight: 800; color: var(--ink); }
.sg-mode span { font-size: .76rem; color: var(--ink-3); }
.sg-mode:hover { border-color: var(--ink-3); }
.sg-mode.is-on {
  border-color: var(--signal-deep);
  background: color-mix(in srgb, var(--signal) 12%, var(--surface));
}

.sg-steps { margin: 0 0 14px; }
.sg-steps[hidden] { display: none; }
.sg-step {
  border: 1px solid var(--line); border-radius: 11px;
  background: transparent; margin-bottom: 8px;
}
.sg-step > summary {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 13px; cursor: pointer; list-style: none;
}
.sg-step > summary::-webkit-details-marker { display: none; }
.sg-n {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 7px;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--signal) 20%, transparent);
  color: var(--accent-ink, #24507F); font-size: .76rem; font-weight: 800;
}
.sg-t { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.sg-t b { font-family: var(--font-de); font-size: .88rem; }
.sg-t span { font-size: .76rem; color: var(--ink-3); }
.sg-chev { margin-inline-start: auto; color: var(--ink-3); font-size: .9rem; }
.sg-step[open] .sg-chev { transform: rotate(45deg); }

.sg-body { padding: 0 13px 12px; }
.sg-hint { margin: 0 0 9px; font-size: .8rem; color: var(--ink-3); line-height: 1.6; }
.sg-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.sg-p {
  width: 100%; text-align: start; cursor: pointer; font: inherit;
  display: flex; flex-direction: column; gap: 2px;
  padding: 9px 11px; border-radius: 9px;
  /* No fill. A tinted card inside a tinted drawer inside a panel was three
     grounds deep, and the phrases are the only thing in here worth
     looking at. */
  border: 1px solid var(--line); background: transparent;
  color: var(--ink);
}
.sg-p b { font-family: var(--font-de); direction: ltr; text-align: left; font-size: .87rem; font-weight: 700; }
.sg-p span { font-size: .78rem; color: var(--ink-3); }
.sg-p:hover { border-color: var(--signal-deep); }
/* Used once already — still available, just no longer the obvious next one. */
.sg-p.is-used { opacity: .5; }

@media (max-width: 560px) {
  .sg-modes { flex-direction: column; }
}

/* The guided card carries the offer, so it is drawn like one even before
   it is chosen: most people will not try a second way of working unless
   something tells them it is there. */
.sg-mode[data-sg-mode="guided"] {
  border-color: color-mix(in srgb, var(--signal-deep) 45%, transparent);
  background: color-mix(in srgb, var(--signal) 10%, var(--surface));
}
.sg-mode[data-sg-mode="guided"] b { color: var(--accent-ink, #24507F); }
.sg-mode[data-sg-mode="guided"].is-on {
  border-color: var(--signal-deep);
  background: color-mix(in srgb, var(--signal) 20%, var(--surface));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 20%, transparent);
}
html[data-theme="dark"] .sg-mode[data-sg-mode="guided"] b { color: var(--signal); }

/* ── The empty slot ──────────────────────────────────────────────────
 * It is the one thing on a Lesen page the reader has to act on, and it
 * was the quietest: grey dashes on the page's own paper. White ground,
 * bold ink, and a dim neon ring in the site's blue — lit enough to find
 * at a glance, not so lit that twelve of them fight the text.
 */
body[data-ex-page="matching"] .slot {
  background: #ffffff;
  border: 1.5px solid color-mix(in srgb, var(--signal) 55%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 12%, transparent),
              0 0 14px -4px color-mix(in srgb, var(--signal-deep) 45%, transparent);
  color: var(--ink-2); font-weight: 700;
}
body[data-ex-page="matching"] .slot:hover,
body[data-ex-page="matching"] .slot.armed {
  border-color: var(--signal-deep);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--signal) 22%, transparent),
              0 0 20px -3px color-mix(in srgb, var(--signal-deep) 60%, transparent);
  background: #ffffff; color: var(--ink);
}
body[data-ex-page="matching"] .slot.filled {
  background: #ffffff;
  border-color: color-mix(in srgb, var(--signal-deep) 35%, transparent);
  box-shadow: none; font-weight: 700;
}
html[data-theme="dark"] body[data-ex-page="matching"] .slot,
html[data-theme="dark"] body[data-ex-page="matching"] .slot.filled { background: var(--surface); }

/* ── قراءة سهلة, announcing itself ───────────────────────────────────
 * A button nobody has met cannot wait to be discovered. It breathes
 * until it has been used once, and then stops for good — an animation
 * that never ends is a distraction on a reading page.
 */
.bio-btn.is-new { animation: bioCall 2.6s ease-in-out infinite; }
@keyframes bioCall {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--signal) 45%, transparent); }
  45% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--signal) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .bio-btn.is-new { animation: none; } }

/* ── Schreiben steps, legible ────────────────────────────────────────
 * Transparent on cream made four outlines with nothing inside them.
 * The drawer sits on white with a tinted head, so the step you are on
 * is a surface rather than a rectangle.
 */
.sg-step { background: #ffffff; }
.sg-step > summary {
  background: color-mix(in srgb, var(--signal) 9%, #ffffff);
  border-radius: 10px;
}
.sg-step[open] > summary {
  border-radius: 10px 10px 0 0;
  border-bottom: 1px solid var(--line);
}
.sg-step[open] { box-shadow: 0 1px 2px rgba(20, 38, 58, .05); }
.sg-t b { font-size: .95rem; }
.sg-t span { font-size: .8rem; color: var(--ink-2); }
.sg-hint { font-size: .84rem; color: var(--ink-2); }
.sg-p { background: #ffffff; }
.sg-p b { font-size: .92rem; }
.sg-p span { font-size: .8rem; color: var(--ink-2); }
html[data-theme="dark"] .sg-step,
html[data-theme="dark"] .sg-p { background: var(--surface); }
html[data-theme="dark"] .sg-step > summary {
  background: color-mix(in srgb, var(--signal) 12%, var(--surface));
}

/* ── An exercise takes the whole screen ──────────────────────────────
 * Opening one is sitting down to work: the site's header, footer, tab
 * bar and strips are all invitations to go somewhere else, and none of
 * them belong in front of a timed task. The pager stays — it is the way
 * out and the way to the next one.
 */
body[data-ex-page] .site-header,
body[data-ex-page] .site-footer,
body[data-ex-page] .tabbar,
body[data-ex-page] .newsbar,
body[data-ex-page] .freebar,
body[data-ex-page] .crumbs,
body[data-ex-page] .wa-fab { display: none !important; }
body[data-ex-page] .quicknav { top: 0; }
body[data-ex-page] .exam-wrap { padding-top: 10px; }

/* ── The exercise page's own top row ─────────────────────────────────
 * المواضيع goes to the far side from where it sat: in an RTL row the
 * first child is on the right, and the way out belongs on the left,
 * away from the thumb that is working the answers.
 */
body[data-ex-page] .ex-back { order: 3; margin-inline-start: auto; }
body[data-ex-page] .ex-top h1 { order: 1; }
body[data-ex-page] .ex-top .badge { order: 2; }

/* The theme control, which the hidden header took with it. One button,
   the same toggle the header has — a menu to pick between two states is
   a menu too many. */
.ex-theme { order: 4; }


/* المواضيع sits at the left end of the row now, so its arrow is ← in the
   markup and its nudge goes the same way.
   Flipping the glyph with transform: scaleX(-1) did nothing: .ex-back-i
   runs a keyframe animation on transform, and an animation outranks a
   plain declaration. The character itself is the honest fix. */
body[data-ex-page] .ex-back { flex-direction: row-reverse; }
body[data-ex-page] .ex-back:hover .ex-back-i { transform: translateX(-4px); }
@media (prefers-reduced-motion: no-preference) {
  body[data-ex-page] .ex-back-i { animation-name: ex-back-nudge-rev; }
}
@keyframes ex-back-nudge-rev {
  0%, 82%, 100% { transform: translateX(0); }
  88% { transform: translateX(-3px); }
}


/* ── The held screen between the list and the exercise ───────────────
 * An exercise opens with no header, no footer and no menu. That is the
 * right place to work and a jarring place to land in unannounced, so
 * the change is covered rather than cut to: the name of what is opening,
 * a line saying what is being made ready, and a bar that runs for
 * exactly as long as the wait.
 */
.exl {
  position: fixed; inset: 0; z-index: 200;
  display: grid; place-items: center;
  background: var(--paper);
  opacity: 0; transition: opacity .22s var(--ease);
}
.exl.is-on { opacity: 1; }
.exl-in { width: min(22rem, 86vw); text-align: center; }

/* Three marks of the brand's blue, breathing in turn. */
.exl-mark { display: flex; justify-content: center; gap: 7px; margin-bottom: 18px; }
.exl-mark span {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--signal-deep); opacity: .25;
}
.exl-name {
  margin: 0 0 6px; font-family: var(--font-de);
  font-size: 1.05rem; font-weight: 700; color: var(--ink);
}
.exl-line { margin: 0 0 16px; font-size: .88rem; color: var(--ink-2); }
.exl-bar {
  height: 3px; border-radius: 999px; overflow: hidden;
  background: color-mix(in srgb, var(--ink) 10%, transparent);
}
.exl-bar i { display: block; height: 100%; width: 0; background: var(--signal-deep); }

@media (prefers-reduced-motion: no-preference) {
  .exl-bar i { animation: exlFill 2s linear forwards; }
  .exl-mark span { animation: exlBeat 1.2s ease-in-out infinite; }
  .exl-mark span:nth-child(2) { animation-delay: .15s; }
  .exl-mark span:nth-child(3) { animation-delay: .3s; }
}
@media (prefers-reduced-motion: reduce) { .exl-bar i { width: 100%; } }
@keyframes exlFill { from { width: 0; } to { width: 100%; } }
@keyframes exlBeat {
  0%, 100% { opacity: .25; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.25); }
}

/* ── The exercise's own controls ─────────────────────────────────────
 * One control, on the right where the reading starts, riding the strip
 * that is already stuck to the top rather than becoming a second fixed
 * thing.
 */
.efx {
  display: flex; align-items: center; justify-content: flex-start;
  gap: 10px; flex-wrap: wrap; padding: 0 0 7px;
}
.efx-read { position: relative; }
.efx-readbtn {
  border: 1px solid var(--line); background: var(--surface); cursor: pointer;
  padding: 5px 12px; border-radius: 999px;
  font: inherit; font-size: .76rem; font-weight: 700; color: var(--ink-3);
}
.efx-readbtn:hover { color: var(--ink); border-color: var(--ink-3); }
.efx-tray {
  position: absolute; top: calc(100% + 6px); inset-inline-start: 0; z-index: 70;
  width: 15rem; padding: 12px; border-radius: 12px;
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 16px 34px -18px rgba(0,0,0,.4);
}
.efx-tray[hidden] { display: none; }
.efx-tray label {
  display: block; margin-bottom: 10px;
  font-size: .76rem; font-weight: 700; color: var(--ink-2);
}
.efx-tray input[type="range"] { width: 100%; margin-top: 5px; accent-color: var(--signal-deep); }
.efx-reset {
  width: 100%; border: 1px solid var(--line); background: transparent;
  border-radius: 8px; padding: 6px; cursor: pointer;
  font: inherit; font-size: .74rem; font-weight: 700; color: var(--ink-3);
}

/* The reading controls themselves. --read-scale is a ratio, not a
   percentage: calc cannot multiply a length by a percentage, and the
   first version's calc(1em * var(--read-size) / 100%) was invalid, so
   the sliders moved and the page did not. */
body[data-ex-page] .exam-wrap .prose,
body[data-ex-page] .exam-wrap .prose[lang="de"] {
  font-size: calc(var(--read-scale, 1) * 1.01rem);
  line-height: var(--read-lead, 1.62);
}
body[data-ex-page] .exam-wrap .match-item .prose,
body[data-ex-page] .exam-wrap .match-item .prose[lang="de"] {
  font-size: calc(var(--read-scale, 1) * 1.04rem);
}
body[data-ex-page] .exam-wrap .q-card,
body[data-ex-page] .exam-wrap .opt {
  font-size: calc(var(--read-scale, 1) * .95rem);
}





/* The two controls that moved into the sticky row keep their shapes. */
/* The two page controls, grouped at the end of the row. RTL puts the
   group's first child on its right, so the theme goes in first and
   المواضيع after it: left to right they read المواضيع, then the theme. */
.efx-end {
  display: flex; align-items: center; gap: 8px;
  margin-inline-start: auto;
}
.efx-end .ex-back { margin: 0; }
.efx-end .ex-theme { order: 0; }

/* قراءة, lit and waving until it has been opened once. A control that
   changes how the page reads is worth finding; one that keeps waving
   after it has been used is noise. */
.efx-readbtn {
  position: relative;
  animation: readCall 2.8s ease-in-out infinite;
}
.efx-readbtn.is-known { animation: none; }
@keyframes readCall {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--signal) 50%, transparent); }
  45% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--signal) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .efx-readbtn { animation: none; } }

/* ── The two columns scroll; so does the page ────────────────────────
 * This block used to stop the page scrolling as well, to leave exactly
 * two scrollbars. It left the تصحيح button unreachable: at 1280x860 it
 * sat at y=1175 with the page frozen and its own panel not scrolling
 * either, so a reader could fill every slot and never correct anything.
 *
 * The page keeps its scroll now. It is the one surface that always
 * works whatever a future rule does to a panel, and an exercise that
 * cannot be checked is a worse fault than a third scrollbar.
 */
@media (min-width: 1024px) {
  body[data-ex-page="matching"] .exam-wrap .exercise-layout > div > .panel,
  body[data-ex-page="reading-mc"] .exam-wrap .exercise-layout > div > .panel {
    max-height: calc(100vh - 150px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* ── Lesen: the two halves, each in its own light ────────────────────
 * The page is white and the panels are white, so without a line the
 * text and the answers are one sheet with a gap down the middle. A dim
 * neon ring in the site's blue separates them without putting a hard
 * border back on a page that was cleared of borders on purpose.
 */
body[data-ex-page="matching"] .exam-wrap .exercise-layout > div > .panel,
body[data-ex-page="reading-mc"] .exam-wrap .exercise-layout > div > .panel {
  border: 1.5px solid color-mix(in srgb, var(--signal) 48%, transparent);
  border-radius: 16px;
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--signal) 10%, transparent),
    0 0 22px -6px color-mix(in srgb, var(--signal-deep) 35%, transparent);
}
html[data-theme="dark"] body[data-ex-page="matching"] .exam-wrap .exercise-layout > div > .panel,
html[data-theme="dark"] body[data-ex-page="reading-mc"] .exam-wrap .exercise-layout > div > .panel {
  border-color: color-mix(in srgb, var(--signal) 40%, transparent);
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--signal) 8%, transparent),
    0 0 26px -6px color-mix(in srgb, var(--signal) 30%, transparent);
}

/* On a phone المواضيع shares a row with the pager, so it is sized to the
   pager rather than to the page title it used to sit beside: a little
   smaller than السابق / التالي, since it is the way out and not the way
   on. */
@media (max-width: 700px) {
  /* Matched to the pager by its box, not just its type size: .ex-back
     inherits a 1.5 line height from the page, which on its own made it
     six pixels taller than السابق / التالي however small the text got. */
  body[data-ex-page] .efx .ex-back {
    padding: 6px 11px; line-height: 1;
    font-size: .76rem; font-weight: 700; gap: 5px;
  }
  body[data-ex-page] .efx .ex-back .ex-back-i { font-size: .8rem; line-height: 1; }
  body[data-ex-page] .efx .ex-theme .icon-btn {
    width: 30px; height: 30px; font-size: .9rem;
  }
  body[data-ex-page] .efx-readbtn { padding: 6px 11px; line-height: 1; font-size: .74rem; }
  body[data-ex-page] .efx { gap: 7px; }
}


/* ── The exercise's two top rows, lined up ───────────────────────────
 * Navigation on the first, the exercise's own controls on the second,
 * both on the same gutters and the same baseline. On a phone the two
 * are what stands between the reader and the text, so they are kept
 * tight: one line each, nothing wrapping to a third.
 */
.quicknav .efx {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; flex-wrap: nowrap;
  /* The gutters of the row above, stated rather than inherited: .efx
     carries a padding shorthand of its own that would otherwise wipe out
     .wrap's inline padding and leave this row running edge to edge under
     an inset one. */
  padding: 0 18px 8px;
}
/* قراءة at the right where the reading starts; المواضيع and the theme
   together at the left, which is where the way out was asked for. */
.quicknav .efx-read { margin-inline-end: 0; }
.quicknav .efx-end { margin-inline-start: auto; }

@media (max-width: 700px) {
  .quicknav .efx { padding: 0 18px 7px; gap: 6px; }
  /* Every control on the row to one height, so the line reads as a line
     rather than three things that happen to be side by side. */
  body[data-ex-page] .efx .ex-theme .icon-btn {
    width: 27px; height: 27px; border-radius: 9px; font-size: .86rem;
  }
  body[data-ex-page] .efx .ex-back,
  body[data-ex-page] .efx-readbtn { height: 27px; }
  body[data-ex-page] .quicknav .qn-row { height: 34px; }
}

/* ── Lesen: the verdict on the heading itself ────────────────────────
 * After checking, the heading you chose carries its own result and the
 * one you should have chosen says so. The letter block fills with the
 * verdict's colour and runs the height of the card — a coloured edge is
 * readable at a glance in a column of ten, where a tinted background
 * alone is not.
 */
body[data-ex-page="matching"] .option-bank .chip.is-correct,
body[data-ex-page="matching"] .option-bank .chip.is-wrong,
body[data-ex-page="matching"] .option-bank .chip.is-answer {
  position: relative;
  padding-inline-start: 48px;
  overflow: hidden;
}
body[data-ex-page="matching"] .option-bank .chip.is-correct::before,
body[data-ex-page="matching"] .option-bank .chip.is-wrong::before,
body[data-ex-page="matching"] .option-bank .chip.is-answer::before {
  position: absolute; inset-inline-start: 0; top: 0; bottom: 0;
  width: 38px; height: auto; margin: 0;
  border-radius: 0; vertical-align: 0;
  display: grid; place-items: center;
  color: #fff; font-size: .9rem; font-weight: 800;
}

/* Chosen, and right. */
body[data-ex-page="matching"] .option-bank .chip.is-correct {
  background: var(--ok-bg); border-color: var(--ok-line);
}
body[data-ex-page="matching"] .option-bank .chip.is-correct::before {
  background: #16a34a;
}

/* Chosen, and wrong. */
body[data-ex-page="matching"] .option-bank .chip.is-wrong {
  background: var(--bad-bg); border-color: var(--bad-line);
}
body[data-ex-page="matching"] .option-bank .chip.is-wrong::before {
  background: #ef4444;
}
body[data-ex-page="matching"] .option-bank .chip.is-wrong::after {
  content: '✕';
  position: absolute; inset-inline-end: 12px; top: 50%;
  transform: translateY(-50%);
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, #ef4444 16%, transparent);
  color: #dc2626; font-size: .76rem; font-weight: 800;
}

/* Not chosen, but it was the answer — dashed, so it never reads as
   something the learner did. */
body[data-ex-page="matching"] .option-bank .chip.is-answer {
  background: color-mix(in srgb, #16a34a 9%, transparent);
  border: 1.5px dashed #16a34a;
}
body[data-ex-page="matching"] .option-bank .chip.is-answer::before {
  background: #16a34a;
}

/* Everything still in play after a check is set back, so the three
   verdicts are the only things with colour in the column. */
body[data-ex-page="matching"] [data-engine].lv-marked .option-bank .chip:not(.is-correct):not(.is-wrong):not(.is-answer) {
  opacity: .55;
}

html[data-theme="dark"] body[data-ex-page="matching"] .option-bank .chip.is-answer {
  background: color-mix(in srgb, #16a34a 16%, transparent);
}

/* A verdict is never dimmed. .chip.used fades a heading that has been
   placed, which is right while working and wrong once the result is on
   screen — the three marked headings are the only thing worth looking
   at then. */
body[data-ex-page="matching"] .option-bank .chip.is-correct,
body[data-ex-page="matching"] .option-bank .chip.is-wrong,
body[data-ex-page="matching"] .option-bank .chip.is-answer { opacity: 1; }

/* ── The same three verdicts on a phone ──────────────────────────────
 * The picks under each text already turned red or green, but "I chose
 * this and it was right" and "this was the right one, you missed it"
 * were drawn identically — so after checking, a learner could not tell
 * which of the two green rows was theirs. Same language as the desktop
 * column now: solid for what you did, dashed for what you should have
 * done, and an ✕ on the one you got wrong.
 */
@media (max-width: 700px) {
  body[data-ex-page="matching"] .exm-pick.is-wrong { position: relative; }
  body[data-ex-page="matching"] .exm-pick.is-wrong::after {
    content: '✕';
    position: absolute; inset-inline-end: 10px; top: 50%;
    transform: translateY(-50%);
    width: 20px; height: 20px; border-radius: 50%;
    display: grid; place-items: center;
    background: color-mix(in srgb, #ef4444 16%, transparent);
    color: #dc2626; font-size: .7rem; font-weight: 800;
  }
  body[data-ex-page="matching"] .exm-pick.is-wrong .exm-pick-text { padding-inline-end: 26px; }

  body[data-ex-page="matching"] .exm-pick.is-answer {
    background: color-mix(in srgb, #16a34a 9%, transparent);
    border: 1.5px dashed #16a34a;
  }
  body[data-ex-page="matching"] .exm-pick.is-answer .exm-pick-key {
    background: #16a34a; color: #fff;
  }
  /* Chosen and right keeps the solid card, so the two greens read apart. */
  body[data-ex-page="matching"] .exm-pick.is-correct {
    border-style: solid;
  }
}

/* ════════════════════════════════════════════════════════════════════
 * UI pass: the exercise bar, the verdicts, Hören, and the two buttons
 * that sat on the text.
 * ════════════════════════════════════════════════════════════════════ */

/* ── 1. شرح and خبّي, off the text ───────────────────────────────────
 * Both were floating in a block's top corner, where a long first line
 * runs underneath them. On an exercise page the tools now sit in their
 * own row above the German at every width — not only on a phone and not
 * only on Lesen, which is what the rule used to say.
 */
body[data-ex-page] .xp-tools {
  position: static;
  justify-content: flex-end;
  margin: 0 0 8px;
}
body[data-ex-page] .xp-host,
body[data-ex-page] .exam-wrap .prose.xp-host { padding-top: 0; }
body[data-ex-page] .xp-host > .xp-btn { position: static; }

/* خبّي: a quiet end-of-paragraph control, not a pill the size of a
   sentence sitting in the middle of one. Its own line, at the far end,
   small enough to read as an aside. */
.pe-btn {
  display: flex; margin-inline-start: auto; margin-top: 6px;
  padding: 3px 9px; gap: 5px;
  font-size: .68rem; line-height: 1.4;
  opacity: .62; max-width: max-content;
}
.pe-btn:hover { opacity: 1; }
.pe-btn-i svg { width: 12px; height: 12px; }
.pe-stub-i svg { width: 13px; height: 13px; }
.pe-stub { font-size: .74rem; padding: 7px 10px; }

/* ── 2. Lesen Teil 2: a frame on the answer, not a tint ──────────────
 * .opt carried a 1px border in the verdict's colour over a pale wash,
 * which at arm's length on a phone is two shades of grey. The option
 * the learner ticked gets a full frame; the right one they did not tick
 * is outlined dashed, the same language the bank chips and the Hören
 * table already speak.
 */
.opt.is-correct,
.opt.is-wrong { border-width: 2px; }
.opt.is-correct.is-picked {
  border-color: var(--ok);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ok) 35%, transparent);
}
.opt.is-wrong.is-picked {
  border-color: var(--bad);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bad) 35%, transparent);
}
/* The answer they missed: named, but never mistaken for their own pick. */
.opt.is-correct:not(.is-picked) {
  border-style: dashed; border-color: var(--ok);
  background: color-mix(in srgb, var(--ok-bg) 70%, transparent);
}
.opt.is-correct input { accent-color: var(--ok); }
.opt.is-wrong input { accent-color: var(--bad); }

/* ── 3. Hören: room to work, and colour where the choice is ──────────
 * The table was set at .91rem in the German face with 10px cells — a
 * dense grid for something read while listening. Bigger type, more air,
 * and the two option cells are targets rather than table cells.
 */
body[data-ex-page="listening-tf"] .audio-part {
  padding: 20px 20px 18px; border-radius: 16px; margin-bottom: 20px;
}
body[data-ex-page="listening-tf"] .audio-part h3 { font-size: 1.06rem; margin-bottom: 14px; }
body[data-ex-page="listening-tf"] .audio-part audio {
  height: 44px; margin-bottom: 18px; border-radius: 999px;
}
body[data-ex-page="listening-tf"] .tf-table { font-size: 1rem; }
body[data-ex-page="listening-tf"] .tf-table td { padding: 14px 10px; }
body[data-ex-page="listening-tf"] .tf-table td.stmt {
  /* The reading face, not the interface face: these are sentences to be
     read under time, and the body stack is the softer of the two. */
  font-family: var(--font-de);
  font-size: calc(var(--read-scale, 1) * 1rem);
  line-height: 1.7; letter-spacing: .005em;
  color: var(--ink);
}
body[data-ex-page="listening-tf"] .tf-table th { font-size: .72rem; padding: 9px; }
body[data-ex-page="listening-tf"] .tf-table td.pick { width: 84px; }
/* A radio in the middle of a wide cell is a small target with a lot of
   dead space around it. The cell itself becomes the target. */
body[data-ex-page="listening-tf"] .tf-table td.pick input {
  width: 22px; height: 22px; cursor: pointer;
}
/* The box reacts, not the row — and only where there is a pointer. */
@media (hover: hover) {
  body[data-ex-page="listening-tf"] .tf-table td.pick:hover { border-color: var(--ink-3); }
}

/* ── 4. Sprachbausteine Teil 2, the way Teil 1 works on a phone ──────
 * Teil 1 puts each gap's three options under the sentence they belong
 * to. Teil 2 had one bank at the foot of the page, so every gap meant a
 * scroll down, a tap, and a scroll back to see where it landed. The
 * mirrors exercise-mobile.js builds are styled here to match the Teil 1
 * rows exactly — same grid, same chips, same verdict colours.
 */
@media (max-width: 700px) {
  body[data-ex-page="cloze-bank"] .exm-bank {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
    gap: 7px; margin: 8px 0 18px;
    padding-inline-start: 10px;
    border-inline-start: 3px solid var(--signal-deep);
  }
  body[data-ex-page="cloze-bank"] .exm-bank .exm-word {
    display: flex; align-items: center; justify-content: center; gap: 6px;
    min-height: 42px; padding: 9px 10px;
    border: 1px solid var(--line); border-radius: 10px;
    background: var(--surface); color: var(--ink);
    font-family: var(--font-de); font-size: .92rem; font-weight: 700;
    cursor: pointer;
  }
  body[data-ex-page="cloze-bank"] .exm-bank .exm-word.is-used { opacity: .4; }
  body[data-ex-page="cloze-bank"] .exm-bank .exm-word.is-chosen {
    border-color: var(--signal-deep); background: var(--signal-wash);
  }
  body[data-ex-page="cloze-bank"] .exm-bank .exm-word.is-correct {
    border: 2px solid var(--ok); background: var(--ok-bg); color: var(--ok);
  }
  body[data-ex-page="cloze-bank"] .exm-bank .exm-word.is-wrong {
    border: 2px solid var(--bad); background: var(--bad-bg); color: var(--bad);
  }
  body[data-ex-page="cloze-bank"] .exm-bank .exm-word.is-answer {
    border: 2px dashed var(--ok); color: var(--ok);
  }
  /* The bank at the foot has done its job once every gap carries one. */
  body[data-ex-page="cloze-bank"] .exm-on [data-bank] { display: none; }
  body[data-ex-page="cloze-bank"] .exm-on .sticky-side > h2,
  body[data-ex-page="cloze-bank"] .exm-on .answers-hint { display: none; }
}

/* The gap itself, told apart at a glance on every width. */
body[data-ex-page="cloze-bank"] .gap.correct,
body[data-ex-page="cloze-mc"] .gap.correct {
  border-bottom-color: var(--ok); color: var(--ok); background: var(--ok-bg);
}
body[data-ex-page="cloze-bank"] .gap.incorrect,
body[data-ex-page="cloze-mc"] .gap.incorrect {
  border-bottom-color: var(--bad); color: var(--bad); background: var(--bad-bg);
}

/* ── 5. The exercise bar: one row, three groups ──────────────────────
 * Six controls in two stacked bands became four in one row: the way out
 * at the start, the pager in the middle, one reading button at the end.
 * The theme switch is inside that button's tray — it is a setting, and
 * a bar is not where settings belong.
 */
body[data-ex-page] .quicknav .qn-row {
  height: auto; min-height: 44px; gap: 10px; flex-wrap: nowrap;
  padding-top: 5px; padding-bottom: 5px;
}
/* The pager keeps the middle, as a group rather than two buttons pushed
   to opposite walls with a number stranded between them. */
body[data-ex-page] .quicknav .qn-prev,
body[data-ex-page] .quicknav .qn-next { flex: 0 0 auto; }
body[data-ex-page] .quicknav .qn-count {
  flex: 0 0 auto; order: 0;
  padding: 3px 10px; border-radius: 999px;
  background: var(--paper-2); font-weight: 700;
}
body[data-ex-page] .quicknav .qn-prev { order: -1; margin-inline-start: auto; }
body[data-ex-page] .quicknav .qn-next { order: 1; margin-inline-end: auto; }

/* المواضيع: first in the row and quiet — a way out, not a call to take
   it. The old rule pushed it to the end of a row it no longer sits in. */
body[data-ex-page] .quicknav .ex-back {
  order: -2; margin: 0; flex: 0 0 auto;
  height: 28px; padding: 0 10px; font-size: .72rem;
}

/* The reading button closes the row. .efx is no longer a strip of its
   own, so it carries no padding and no justification. */
body[data-ex-page] .quicknav .efx {
  order: 2; flex: 0 0 auto;
  display: flex; align-items: center; gap: 8px;
  padding: 0; margin: 0;
}
.efx-readbtn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 13px;
}
.efx-aa {
  font-family: var(--font-de); font-weight: 800; font-size: .9rem;
  line-height: 1; letter-spacing: -.02em;
}
.efx-tray { inset-inline-start: auto; inset-inline-end: 0; }
/* The theme switch, rehoused: a labelled row like the two sliders above
   it rather than a bare icon floating in a panel. */
.efx-tray-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin: 0 0 12px;
  font-size: .76rem; font-weight: 700; color: var(--ink-2);
}
.efx-tray-row .ex-theme { margin: 0; }

@media (max-width: 700px) {
  /* Words give way to glyphs before anything wraps: the bar is four
     things on a 320px phone and it still has to be one line. */
  body[data-ex-page] .quicknav .qn-row { gap: 6px; min-height: 42px; }
  body[data-ex-page] .quicknav .ex-back { height: 30px; padding: 0 10px; font-size: 0; gap: 0; }
  body[data-ex-page] .quicknav .ex-back .ex-back-i { font-size: 1rem; }
  body[data-ex-page] .quicknav .qn-btn { padding: 6px 10px; font-size: .76rem; }
  body[data-ex-page] .quicknav .qn-count { font-size: .72rem; padding: 3px 8px; }
  .efx-readlabel { display: none; }
  .efx-readbtn { height: 30px; padding: 0 11px; }
  .efx-tray { width: min(15rem, calc(100vw - 28px)); }
}

/* ── Hören on a phone ────────────────────────────────────────────────
 * The two choices are the whole interaction, so they are the size of
 * the thing being tapped. The sentence sits above them, full width, in
 * the reading face; nothing is squeezed into a table column.
 */
@media (max-width: 700px) {
  body[data-ex-page="listening-tf"] .audio-part {
    padding: 14px 13px 16px; border-radius: 14px; margin-bottom: 14px;
  }
  body[data-ex-page="listening-tf"] .audio-part h3 { font-size: .98rem; }
  body[data-ex-page="listening-tf"] .audio-part audio { height: 40px; margin-bottom: 14px; }
  body[data-ex-page="listening-tf"] .tf-table tr {
    padding: 14px 13px; border-radius: 14px; gap: 10px;
  }
  body[data-ex-page="listening-tf"] .tf-table td.stmt {
    font-size: calc(var(--read-scale, 1) * 1rem);
    line-height: 1.65; margin-bottom: 4px;
  }
  body[data-ex-page="listening-tf"] .tf-table td.pick {
    min-height: 56px; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 5px;
    padding: 8px 4px; border-radius: 12px;
  }
  body[data-ex-page="listening-tf"] .tf-table td.pick::before {
    margin: 0; font-size: .78rem;
  }
  body[data-ex-page="listening-tf"] .tf-table td.pick input { width: 20px; height: 20px; }
  body[data-ex-page="listening-tf"] .tf-table td.tf-answer { margin-top: 2px; }
  /* The actions were a row of two full-width buttons pushed off the
     bottom of a long table; they belong where the thumb is. */
  body[data-ex-page="listening-tf"] .audio-part .actions { margin-top: 12px; }
}

/* The verdict is a chip, not a banner. .tf-answer is the cell itself, and
   on a phone the cell is a full-width grid item, so the pill stretched the
   width of the card and left its text marooned at one end. */
@media (max-width: 700px) {
  body[data-ex-page="listening-tf"] .tf-table td.tf-answer[data-verdict] {
    width: max-content; max-width: 100%;
    margin-inline-start: auto; justify-self: end;
  }
}

/* ── Corrections ─────────────────────────────────────────────────────── */

/* Sprachbausteine Teil 2 on a phone: the list belongs to the gap it fills
   and opens with it. Printed open under all ten gaps it was a hundred and
   fifty buttons threaded through a letter nobody could then read. */
@media (max-width: 700px) {
  body[data-ex-page="cloze-bank"] .exm-bank[hidden] { display: none !important; }
  body[data-ex-page="cloze-bank"] .exm-bank {
    margin: 10px 0 14px; padding: 10px 0 10px 0;
    padding-inline-start: 10px;
    animation: exBankIn .16s var(--ease) both;
  }
  /* The gap is now a control, so it has to look like one. */
  body[data-ex-page="cloze-bank"] .gap.empty {
    background: var(--signal-wash);
    border-bottom-width: 2px;
    cursor: pointer;
  }
}
@keyframes exBankIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* Hören: the verdict sits with its sentence, not in a column of its own.
   Full-width pills in an empty lane read as five labels floating beside the
   exercise rather than as the result of each line. */
/* A column of its own width, not a column of none: width:1% squeezed the
   cell to nothing and the badge hung out of the table and was clipped at
   the edge. One fixed lane, every badge the same size inside it. */
body[data-ex-page="listening-tf"] .tf-table td.tf-answer {
  width: 9.5rem; min-width: 9.5rem;
  padding-inline: 12px; text-align: start; white-space: nowrap;
}
.tf-answer[data-verdict] {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 100%; padding: 4px 10px; font-size: .72rem; font-weight: 800;
}

/* The bar, the other way round: المواضيع on the left, قراءة on the right.
   The reading control is where the reading starts; the way out is at the
   far end, where nothing is being tapped by accident. */
body[data-ex-page] .quicknav .efx { order: -2; }
body[data-ex-page] .quicknav .ex-back { order: 3; }

/* قراءة, visibly a button rather than an outline that fades into the bar.
   It keeps a slow ring: it is the control that makes a German text
   readable on a small phone, and the people who need it most are the
   least likely to go looking for it. */
.efx-readbtn {
  background: var(--accent); border-color: var(--accent);
  color: #fff; font-weight: 800;
}
.efx-readbtn:hover { color: #fff; border-color: var(--accent); filter: brightness(1.06); }
.efx-readbtn .efx-aa { opacity: .9; }
.efx-readbtn.is-known { animation: readCall 3.4s ease-in-out infinite; }
@keyframes readCall {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  45% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--accent) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .efx-readbtn, .efx-readbtn.is-known { animation: none; }
}

/* The bar on a phone: it was 42px of 30px controls, which is a strip to
   squint at rather than a bar to use. Everything up to a thumb's size. */
@media (max-width: 700px) {
  body[data-ex-page] .quicknav .qn-row {
    min-height: 56px; gap: 8px; padding-top: 8px; padding-bottom: 8px;
  }
  body[data-ex-page] .quicknav .ex-back { height: 34px; min-width: 38px; padding: 0 10px; }
  body[data-ex-page] .quicknav .ex-back .ex-back-i { font-size: 1rem; }
  body[data-ex-page] .quicknav .qn-btn { padding: 9px 14px; font-size: .86rem; }
  body[data-ex-page] .quicknav .qn-count { font-size: .82rem; padding: 5px 11px; }
  /* Stated at the same specificity as the older rule that sets 27px here,
     or it keeps winning and قراءة stays a third shorter than its row. */
  body[data-ex-page] .quicknav .efx-readbtn { height: 40px; min-width: 44px; padding: 0 14px; }
  body[data-ex-page] .quicknav .efx-aa { font-size: 1rem; }
}

/* ── Sprachbausteine: green on the answer, red on what was taken ─────
 * Both parts, both widths. Teil 1 tinted the whole row and left its
 * three chips grey, so nothing said which of them had been right; Teil 2
 * coloured the gap, which says a gap is wrong without ever naming the
 * word — and the word is what the learner meets again.
 */
.gap-row .chip.is-correct,
.word-bank .chip.is-correct {
  border: 2px solid var(--ok); background: var(--ok-bg); color: var(--ok);
  font-weight: 800; opacity: 1;
}
.gap-row .chip.is-wrong,
.word-bank .chip.is-wrong {
  border: 2px solid var(--bad); background: var(--bad-bg); color: var(--bad);
  font-weight: 800; opacity: 1;
}
/* The word that belonged in the gap: dashed, the way a missed answer is
   drawn everywhere else on the site. */
.word-bank .chip.is-answer {
  border: 2px dashed var(--ok); background: var(--ok-bg); color: var(--ok);
  font-weight: 800; opacity: 1;
}
/* .used dims a bank word to 30%, which would grey out the very chip that
   is trying to say something. */
.word-bank .chip.is-correct,
.word-bank .chip.is-answer,
.word-bank .chip.is-wrong { pointer-events: none; }
.gap-row .chip.is-correct[data-key]::before,
.word-bank .chip.is-answer[data-key]::before,
.word-bank .chip.is-correct[data-key]::before {
  background: color-mix(in srgb, var(--ok) 35%, transparent); color: var(--ok);
}
.gap-row .chip.is-wrong[data-key]::before,
.word-bank .chip.is-wrong[data-key]::before {
  background: color-mix(in srgb, var(--bad) 32%, transparent); color: var(--bad);
}

/* The gap itself reads as the answer slot, so its frame is the loudest
   thing on the line rather than a tinted underline. */
body[data-ex-page="cloze-bank"] .gap.correct,
body[data-ex-page="cloze-mc"] .gap.correct {
  border: 2px solid var(--ok); border-radius: 7px;
  background: var(--ok-bg); color: var(--ok); font-weight: 800;
}
body[data-ex-page="cloze-bank"] .gap.incorrect,
body[data-ex-page="cloze-mc"] .gap.incorrect {
  border: 2px solid var(--bad); border-radius: 7px;
  background: var(--bad-bg); color: var(--bad); font-weight: 800;
}

/* ── The wrong gap shows both: what was written, and the answer ──────
 * Replacing a wrong gap with the right word and painting it red made the
 * one correct thing on the line look like the mistake.
 */
.gap .gap-mine {
  color: var(--bad); text-decoration-color: var(--bad);
  text-decoration-thickness: 2px; font-weight: 700; opacity: .85;
}
.gap .gap-right { color: var(--ok); font-weight: 800; }
body[data-ex-page="cloze-bank"] .gap.incorrect,
body[data-ex-page="cloze-mc"] .gap.incorrect {
  /* Two words now, so it needs room and a neutral ground: a red fill under
     a green answer is one colour too many. */
  background: var(--paper-2); border-color: var(--bad);
  color: var(--ink); min-width: 0; padding: 1px 10px;
}

/* ── Hören: one badge shape for every verdict ────────────────────────
 * Same width, same height, same weight, the tick or the cross in its own
 * circle — so the column reads as five results rather than five labels of
 * different lengths.
 */
.tf-answer[data-verdict] {
  display: inline-flex; align-items: center; gap: 6px;
  min-width: 100%; min-height: 28px; padding: 4px 10px;
  font-size: .72rem; font-weight: 800; letter-spacing: .01em;
  border-radius: 999px;
}
.tf-answer[data-verdict] .tf-mark {
  display: inline-grid; place-items: center;
  width: 16px; height: 16px; border-radius: 50%; flex: none;
  background: var(--ok); color: #fff; font-size: .62rem; line-height: 1;
}
.tf-answer[data-verdict="wrong"] .tf-mark { background: var(--bad); }
.tf-answer[data-verdict] .tf-word {
  font-family: var(--font-de); letter-spacing: .03em;
}

/* ── Hören: a chosen box looks chosen ────────────────────────────────
 * Before checking, the only sign of a tap was the dot inside the radio.
 * The box takes the accent instead, and its label stops being grey.
 */
.tf-table td.pick.is-chosen {
  background: var(--signal-wash);
  border-color: var(--signal-deep) !important;
}
.tf-table td.pick.is-chosen::before { color: var(--signal-deep) !important; }
.tf-table td.pick input { accent-color: var(--signal-deep); }
.tf-table td.pick[data-pick="correct"] input,
.tf-table td.pick[data-right] input { accent-color: var(--ok); }
.tf-table td.pick[data-pick="wrong"] input { accent-color: var(--bad); }

@media (max-width: 700px) {
  /* The two boxes are the interaction, so they get the weight: a real
     border at rest, the accent when chosen, the verdict after checking. */
  body[data-ex-page="listening-tf"] .tf-table td.pick {
    border: 2px solid var(--line);
  }
  body[data-ex-page="listening-tf"] .tf-table td.pick::before {
    color: var(--ink-2); font-weight: 800; font-size: .82rem;
  }
  body[data-ex-page="listening-tf"] .tf-table td.pick.is-chosen {
    background: var(--signal-wash); border-color: var(--signal-deep);
  }
  /* After checking, one size for every mark: the ring is the card's
     border and the circle inside it is the same 22px on all four states,
     so the row does not jump between a checked and an unchecked box. */
  body[data-ex-page="listening-tf"] .tf-table td.pick input {
    width: 22px; height: 22px; margin: 0;
  }
  body[data-ex-page="listening-tf"] .tf-table tr[data-state] td.pick {
    border-width: 2px;
  }
}

/* ── The bar, centred on a phone ─────────────────────────────────────
 * Five columns rather than four items pushed around with auto margins:
 * the two ends are equal 1fr, so the pager between them sits in the
 * middle of the bar whatever the back button and قراءة happen to
 * measure.
 */
@media (max-width: 700px) {
  body[data-ex-page] .quicknav .qn-row {
    display: grid;
    grid-template-columns: 1fr auto auto auto 1fr;
    align-items: center;
  }
  /* order:: the flex row used it to put قراءة first and المواضيع last.
     In a grid, order decides the sequence auto-placement walks the
     cursor in — قراءة was placed in column 5 before المواضيع was placed
     at all, so المواضيع wrapped onto a second row and the bar became
     three stacked lines. The columns say where everything goes here. */
  body[data-ex-page] .quicknav .ex-back,
  body[data-ex-page] .quicknav .qn-prev,
  body[data-ex-page] .quicknav .qn-count,
  body[data-ex-page] .quicknav .qn-next,
  body[data-ex-page] .quicknav .efx { order: 0; grid-row: 1; margin: 0; }
  body[data-ex-page] .quicknav .ex-back { grid-column: 5; justify-self: end; }
  /* Equal, so the counter is the middle of the bar and not just the
     middle of whatever the two words happen to measure. */
  body[data-ex-page] .quicknav .qn-prev,
  body[data-ex-page] .quicknav .qn-next {
    min-width: 5.8rem; justify-content: center;
  }
  body[data-ex-page] .quicknav .qn-prev { grid-column: 2; }
  body[data-ex-page] .quicknav .qn-count { grid-column: 3; white-space: nowrap; }
  body[data-ex-page] .quicknav .qn-next { grid-column: 4; }
  body[data-ex-page] .quicknav .efx { grid-column: 1; justify-self: start; }
}

/* ── Sprachbausteine Teil 2 on a phone: say the number is a button ───
 * The words live behind the gap now, so nothing on screen says where
 * they are. One line above the letter, and the gap drawn as something
 * to press — filled, outlined and breathing until it has been filled in.
 */
.exm-bank-hint {
  display: flex; align-items: center; gap: 7px;
  margin: 0 0 12px; padding: 9px 12px;
  border-radius: 10px;
  background: var(--signal-wash);
  border: 1px dashed var(--signal-deep);
  color: var(--accent-ink, #24507F);
  font-size: .82rem; font-weight: 700; line-height: 1.5;
}
.exm-bank-hint::before { content: '☝'; font-size: 1rem; line-height: 1; }
@media (min-width: 701px) { .exm-bank-hint { display: none; } }

@media (max-width: 700px) {
  body[data-ex-page="cloze-bank"] .exm-on .gap.empty {
    border: 2px solid var(--signal-deep); border-radius: 8px;
    background: var(--signal-wash); color: var(--accent-ink, #24507F);
    padding: 2px 12px; min-width: 54px; font-weight: 800;
    animation: gapCall 2.6s ease-in-out infinite;
  }
  body[data-ex-page="cloze-bank"] .exm-on .gap.filled {
    animation: none; border: 2px solid var(--line); background: var(--surface);
  }
}
@keyframes gapCall {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--signal) 55%, transparent); }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--signal) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  body[data-ex-page="cloze-bank"] .exm-on .gap.empty { animation: none; }
}

/* ════════════════════════════════════════════════════════════════════
 * One verdict language across Schriftlich
 *
 * Lesen Teil 1 and 3 already drew it: the letter block fills with the
 * result and runs the height of the card, the answer you missed is
 * dashed so it never reads as something you did, and the one you took
 * carries a ✕. Teil 2 and both Sprachbausteine each had their own
 * fainter version. They all speak it now.
 * ════════════════════════════════════════════════════════════════════ */

/* ── Lesen Teil 2: the three options ─────────────────────────────── */
body[data-ex-page="reading-mc"] .opt.is-correct,
body[data-ex-page="reading-mc"] .opt.is-wrong { border-width: 2px; }

body[data-ex-page="reading-mc"] .opt.is-correct::before { background: #16a34a; color: #fff; }
body[data-ex-page="reading-mc"] .opt.is-wrong::before { background: #ef4444; color: #fff; }

body[data-ex-page="reading-mc"] .opt.is-correct.is-picked {
  background: var(--ok-bg); border-color: #16a34a; border-style: solid;
}
body[data-ex-page="reading-mc"] .opt.is-correct:not(.is-picked) {
  background: color-mix(in srgb, #16a34a 9%, transparent);
  border: 2px dashed #16a34a;
}
body[data-ex-page="reading-mc"] .opt.is-wrong {
  background: var(--bad-bg); border-color: #ef4444; border-style: solid;
  position: relative; padding-inline-end: 42px;
}
body[data-ex-page="reading-mc"] .opt.is-wrong::after {
  content: '✕';
  position: absolute; inset-inline-end: 11px; top: 50%; transform: translateY(-50%);
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, #ef4444 16%, transparent);
  color: #dc2626; font-size: .76rem; font-weight: 800;
}
/* Once a card is decided, the option nobody touched is out of the way. */
body[data-ex-page="reading-mc"] .q-card[data-state] .opt:not(.is-correct):not(.is-wrong) {
  opacity: .5;
}

/* ── Sprachbausteine Teil 1: the three chips under the sentence ──── */
body[data-ex-page="cloze-mc"] .opts .chip.is-correct,
body[data-ex-page="cloze-mc"] .opts .chip.is-wrong { border-width: 2px; opacity: 1; }
body[data-ex-page="cloze-mc"] .opts .chip.is-correct[data-key]::before { background: #16a34a; color: #fff; }
body[data-ex-page="cloze-mc"] .opts .chip.is-wrong[data-key]::before { background: #ef4444; color: #fff; }

body[data-ex-page="cloze-mc"] .gap-row[data-state="correct"] .chip.is-correct {
  background: var(--ok-bg); border: 2px solid #16a34a;
}
/* The answer on a row that was got wrong: dashed, never solid. */
body[data-ex-page="cloze-mc"] .gap-row[data-state="incorrect"] .chip.is-correct {
  background: color-mix(in srgb, #16a34a 9%, transparent);
  border: 2px dashed #16a34a;
}
body[data-ex-page="cloze-mc"] .opts .chip.is-wrong {
  background: var(--bad-bg); border: 2px solid #ef4444;
  position: relative; padding-inline-end: 34px;
}
body[data-ex-page="cloze-mc"] .opts .chip.is-wrong::after {
  content: '✕';
  position: absolute; inset-inline-end: 8px; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, #ef4444 16%, transparent);
  color: #dc2626; font-size: .72rem; font-weight: 800;
}
body[data-ex-page="cloze-mc"] .gap-row[data-state] .chip:not(.is-correct):not(.is-wrong) { opacity: .5; }

/* ── Sprachbausteine Teil 2: the word bank ───────────────────────── */
body[data-ex-page="cloze-bank"] .word-bank .chip.is-answer,
body[data-ex-page="cloze-bank"] .word-bank .chip.is-wrong { border-width: 2px; opacity: 1; }
body[data-ex-page="cloze-bank"] .word-bank .chip.is-answer[data-key]::before { background: #16a34a; color: #fff; }
body[data-ex-page="cloze-bank"] .word-bank .chip.is-wrong[data-key]::before { background: #ef4444; color: #fff; }
body[data-ex-page="cloze-bank"] .word-bank .chip.is-answer {
  background: color-mix(in srgb, #16a34a 9%, transparent);
  border: 2px dashed #16a34a; color: var(--ok);
}
body[data-ex-page="cloze-bank"] .word-bank .chip.is-wrong {
  background: var(--bad-bg); border: 2px solid #ef4444; color: var(--bad);
  position: relative; padding-inline-end: 34px;
}
body[data-ex-page="cloze-bank"] .word-bank .chip.is-wrong::after {
  content: '✕';
  position: absolute; inset-inline-end: 8px; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, #ef4444 16%, transparent);
  color: #dc2626; font-size: .72rem; font-weight: 800;
}

/* The phone mirrors of the bank say the same thing. */
@media (max-width: 700px) {
  body[data-ex-page="cloze-bank"] .exm-bank .exm-word.is-correct {
    border: 2px solid #16a34a; background: var(--ok-bg); color: var(--ok);
  }
  body[data-ex-page="cloze-bank"] .exm-bank .exm-word.is-answer {
    border: 2px dashed #16a34a;
    background: color-mix(in srgb, #16a34a 9%, transparent); color: var(--ok);
  }
  body[data-ex-page="cloze-bank"] .exm-bank .exm-word.is-wrong {
    border: 2px solid #ef4444; background: var(--bad-bg); color: var(--bad);
  }
}

html[data-theme="dark"] body[data-ex-page="reading-mc"] .opt.is-correct:not(.is-picked),
html[data-theme="dark"] body[data-ex-page="cloze-mc"] .gap-row[data-state="incorrect"] .chip.is-correct,
html[data-theme="dark"] body[data-ex-page="cloze-bank"] .word-bank .chip.is-answer {
  background: color-mix(in srgb, #16a34a 16%, transparent);
}

/* ── Hören speaks the same verdict language ──────────────────────────
 * Lesen fills the letter block with the result and frames the card; the
 * two choice boxes here are the letter blocks. RICHTIG / FALSCH fills
 * solid with the verdict, the box you ticked is framed in it, the one
 * you should have ticked is dashed green, and the wrong pick carries
 * the same ✕ as a wrong heading.
 */
body[data-ex-page="listening-tf"] .tf-table td.pick {
  position: relative; border-radius: 10px;
}
body[data-ex-page="listening-tf"] .tf-table td.pick::before {
  content: attr(data-label);
  display: block; margin-bottom: 4px;
  padding: 2px 0; border-radius: 6px;
  font-family: var(--font-ar); font-size: .7rem; font-weight: 800;
  color: var(--ink-3); letter-spacing: .02em;
}
/* Chosen, and right. */
body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick="correct"] {
  background: var(--ok-bg); box-shadow: inset 0 0 0 2px #16a34a;
}
body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick="correct"]::before {
  background: #16a34a; color: #fff;
}
/* Chosen, and wrong. */
body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick="wrong"] {
  background: var(--bad-bg); box-shadow: inset 0 0 0 2px #ef4444;
}
body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick="wrong"]::before {
  background: #ef4444; color: #fff;
}
/* No ✕ here. The red block and the red frame already say it, and a mark
   in the corner of a 56px box is one thing too many in a column of ten. */
body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick="wrong"]::after { content: none; }
/* Not chosen, but it was the answer. */
body[data-ex-page="listening-tf"] .tf-table tr[data-state="incorrect"] td.pick[data-right]:not([data-pick]),
body[data-ex-page="listening-tf"] .tf-table tr[data-state="missed"] td.pick[data-right] {
  background: color-mix(in srgb, #16a34a 9%, transparent);
  box-shadow: inset 0 0 0 2px #16a34a;
  border-style: dashed !important; border-color: #16a34a !important;
}
body[data-ex-page="listening-tf"] .tf-table tr[data-state="incorrect"] td.pick[data-right]:not([data-pick])::before,
body[data-ex-page="listening-tf"] .tf-table tr[data-state="missed"] td.pick[data-right]::before {
  background: #16a34a; color: #fff;
}
/* The ring overlays drawn for the desktop table are replaced by the
   frames above, so they are switched off rather than doubled. */
body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick]::after,
body[data-ex-page="listening-tf"] .tf-table td.pick[data-right]::after { border: 0; }
body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick="correct"]::after,
body[data-ex-page="listening-tf"] .tf-table tr[data-state] td.pick[data-right]:not([data-pick])::after {
  content: none;
}
/* The row behind them stays quiet: two framed boxes carry the result,
   and a wash over the whole line only competes with them. */
body[data-ex-page="listening-tf"] .tf-table tr[data-state] td { background: transparent; }
body[data-ex-page="listening-tf"] .tf-table tr[data-state="correct"] td.stmt,
body[data-ex-page="listening-tf"] .tf-table tr[data-state="incorrect"] td.stmt,
body[data-ex-page="listening-tf"] .tf-table tr[data-state="missed"] td.stmt { font-weight: 600; }

/* The badge was flush against the sentence it belongs to. */
body[data-ex-page="listening-tf"] .tf-table td.tf-answer { padding-inline: 12px 16px; }
.tf-answer[data-verdict] .tf-word { margin-inline-start: 2px; }

@media (max-width: 700px) {
  /* The box is a flex column on a phone, so the label shrinks to its own
     text unless it is told to fill the width it is sitting in. */
  body[data-ex-page="listening-tf"] .tf-table td.pick::before {
    width: 100%; align-self: stretch; text-align: center;
    margin: 0 0 6px; padding: 3px 0;
  }
}

/* ── Hören: room between the frames, and a badge on the line ─────────
 * The two framed boxes sat edge to edge in their table cells, so two
 * verdicts touched and read as one block. The frame is drawn on the box
 * now with a margin around it, and the verdict badge lines up with the
 * first line of the sentence rather than floating in the middle of its
 * own lane.
 */
body[data-ex-page="listening-tf"] .tf-table td.pick {
  padding: 8px 6px;
}
body[data-ex-page="listening-tf"] .tf-table td.pick,
body[data-ex-page="listening-tf"] .tf-table td.tf-answer,
body[data-ex-page="listening-tf"] .tf-table td.stmt { vertical-align: top; }
body[data-ex-page="listening-tf"] .tf-table td.stmt { padding-top: 14px; }
body[data-ex-page="listening-tf"] .tf-table td.tf-answer { padding-top: 13px; }
/* The frames are inset shadows, so a little padding is all the gap the
   two boxes need — no negative space stolen from the tap target. */
body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick],
body[data-ex-page="listening-tf"] .tf-table tr[data-state] td.pick[data-right] {
  background-clip: padding-box;
}
body[data-ex-page="listening-tf"] .tf-table tr[data-answer] > td + td { padding-inline-start: 8px; }

@media (max-width: 700px) {
  /* The badge belongs to the sentence above it, so it starts where the
     sentence starts instead of being pushed to the opposite edge — one
     side of the card, not centred and not both. */
  body[data-ex-page="listening-tf"] .tf-table td.tf-answer[data-verdict] {
    margin-inline-start: 0; margin-inline-end: auto;
    justify-self: start; padding: 0;
  }
  body[data-ex-page="listening-tf"] .tf-table td.stmt { padding-top: 0; }
  body[data-ex-page="listening-tf"] .tf-table td.pick { padding: 9px 5px 10px; }
  /* The two boxes are grid cells on a phone; the gap gives them the air
     the inset frames need. */
  body[data-ex-page="listening-tf"] .tf-table tr { gap: 12px 12px; }
}

/* ── The exercise bar is fixed, not merely sticky ────────────────────
 * Sticky holds only while its containing block is on screen and gives
 * way to any ancestor that ever gains a transform or a contain. On a
 * phone that reads as a bar that sometimes travels. Fixed, with a
 * spacer of the same height so nothing hides underneath it.
 */
body[data-ex-page] {
  --exbar-h: 50px;
}
body[data-ex-page] .quicknav {
  position: fixed; top: 0; inset-inline: 0; z-index: 90;
}
body[data-ex-page] .exam-wrap { padding-top: calc(var(--exbar-h) + 10px); }
@media (max-width: 700px) {
  body[data-ex-page] { --exbar-h: 62px; }
}

/* ── شرح and خبّي: visible, and never over the text ──────────────────
 * Two faint outlines in the corner of a block read as decoration, and
 * on a phone they used to sit on the first line of German. They are
 * tinted like قراءة سهلة now — the third tool in the same row — and
 * they live in the flow above the text at every width.
 */
.xp-btn:not(.bio-btn) {
  opacity: 1;
  background: color-mix(in srgb, var(--signal) 26%, var(--surface));
  border-color: color-mix(in srgb, var(--signal-deep) 55%, transparent);
  color: var(--accent-ink, #24507F); font-weight: 800;
}
.xp-btn:not(.bio-btn):hover {
  background: color-mix(in srgb, var(--signal) 26%, var(--surface));
  border-color: var(--signal-deep); color: var(--signal-deep);
}
html[data-theme="dark"] .xp-btn:not(.bio-btn) { color: var(--signal); }

.pe-btn {
  opacity: 1;
  background: color-mix(in srgb, var(--signal) 20%, var(--surface));
  border-color: color-mix(in srgb, var(--signal-deep) 45%, transparent);
  color: var(--accent-ink, #24507F); font-weight: 800;
}
.pe-btn:hover {
  background: color-mix(in srgb, var(--signal) 22%, var(--surface));
  border-color: var(--signal-deep); color: var(--signal-deep);
}
html[data-theme="dark"] .pe-btn { color: var(--signal); }

@media (max-width: 700px) {
  /* In the flow, on their own line, never floating over a sentence. */
  .xp-host > .xp-btn, .xp-tools { position: static; }
  .xp-tools { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px; margin: 0 0 10px; }
  .pe-btn { margin-top: 10px; }
}

@media (max-width: 700px) {
  /* The badge: as wide as its words, on the same edge the German starts
     from. min-width:100% is a desktop rule for a fixed column; here it
     stretched the pill across the card and stranded the text in it. */
  body[data-ex-page="listening-tf"] .tf-table td.tf-answer[data-verdict] {
    min-width: 0; width: max-content; max-width: 100%;
    justify-self: left; margin: 2px 0 0;
  }
}

/* The bar on a narrow phone: the pager gives up its fixed width before
   المواضيع is pushed off the edge. At 375px the two 5.8rem buttons and
   the counter left the end columns 52px for a 58px button. */
@media (max-width: 430px) {
  body[data-ex-page] .quicknav .qn-prev,
  body[data-ex-page] .quicknav .qn-next {
    min-width: 0; padding: 9px 10px; font-size: .8rem;
  }
  body[data-ex-page] .quicknav .ex-back {
    min-width: 0; padding: 0 9px; max-width: 100%;
  }
  body[data-ex-page] .quicknav .qn-row { gap: 5px; }
}

/* ── Hören: the label on a coloured block is white ───────────────────
 * .is-chosen paints the label in the accent with !important, which it
 * needs while answering — but it was still winning after the check, so
 * خطأ sat in dark blue on a red block and disappeared.
 */
body[data-ex-page="listening-tf"] .tf-table td.pick[data-pick]::before,
body[data-ex-page="listening-tf"] .tf-table tr[data-state] td.pick[data-right]::before {
  color: #fff !important;
}
/* And the box keeps the verdict's ground, not the accent's. */
body[data-ex-page="listening-tf"] .tf-table td.pick.is-chosen[data-pick="correct"],
body[data-ex-page="listening-tf"] .tf-table td.pick.is-chosen[data-pick="wrong"] {
  border-color: transparent !important;
}

/* ── The verdict badge, on the line of its sentence ──────────────────
 * Both cells start their text at the same height, so the badge and the
 * first line of German sit on one baseline instead of the badge riding
 * a few pixels high.
 */
body[data-ex-page="listening-tf"] .tf-table td.stmt,
body[data-ex-page="listening-tf"] .tf-table td.tf-answer { padding-top: 15px; }
.tf-answer[data-verdict] {
  min-height: 26px; line-height: 1.6;
}

@media (max-width: 700px) {
  /* On a phone the badge is a line under the two boxes, not a block of
     its own: one row, its own width, hard against the sentence's edge. */
  body[data-ex-page="listening-tf"] .tf-table td.stmt,
  body[data-ex-page="listening-tf"] .tf-table td.tf-answer { padding-top: 0; }
  body[data-ex-page="listening-tf"] .tf-table td.tf-answer[data-verdict] {
    min-height: 24px; padding: 3px 10px; margin: 0;
  }
}

/* The toggle hides the Arabic on the phone mirrors too. The visible
   Arabic on a Lesen page at 700px is in the mirrored lists, not in the
   bank it was copied from, so خبّي الترجمة appeared to do nothing. */
body.opt-ar-off .exm-pick-ar { display: none !important; }
