/*
 * caldera.css — the design system, complete and closed.
 *
 * Seven colours, two typefaces, no shadows. Depth comes from COLOUR:
 * pumice ground → limestone cards → ember highlights. A box-shadow anywhere in
 * this project is a bug, not a variation.
 *
 * TRACKING IS NEVER SET ON THE BODY. SVG text inherits letter-spacing from the
 * page it is drawn into, and the item generators MEASURE what they draw — b1
 * sizes its rooms so the room name fits inside the wall. svg.js pins
 * `letter-spacing: normal` on every text it draws (ARCHITECTURE.md §27), and
 * this file keeps its side of the bargain by putting tracking only on display
 * classes. Whoever moves it to :root or body runs tools/test-familien.sh first.
 */

/* ---- 1. fonts ------------------------------------------------------------
 * Self-hosted, no request to a foreign server. Two families, and DM Sans in
 * ONE weight: 500. Never regular, never bold — the system distinguishes by
 * size and colour, not by weight.
 */

@font-face {
  font-family: 'Bebas Neue';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/bebas-neue-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Bebas Neue';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/bebas-neue-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/dm-sans-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/dm-sans-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---- 2. tokens ---------------------------------------------------------- */

:root {
  --color-ember:         #fc5000;  /* the only aggressive accent */
  --color-plasma-violet: #524ae9;  /* hero halftone and ONE card. never a control */
  --color-sulfur:        #f5f28e;  /* tags and badges only */
  --color-limestone:     #f7f6f2;  /* card surfaces */
  --color-pumice:        #e2e2df;  /* page ground */
  --color-obsidian:      #070607;  /* text, headings, borders */
  --color-chalk:         #ffffff;  /* text on dark surfaces */

  /* Two greys, and only two. The seven colours above carry the surface; an
     application needs a muted text and a rule that is not full obsidian, and
     inventing one at each use is how a palette stops being one.
     Measured (tools/test-kontrast.sh): dim on pumice 5.63:1, dim on limestone
     6.77:1 — both clear AA for body text. `line` is never a text colour. */
  --color-dim:           #56565a;  /* muted text: hints, meta, disabled */
  --color-line:          #c9c9c5;  /* hairlines on pumice and limestone */

  /* Measured, not assumed (tools/test-kontrast.sh): chalk on ember is 3.36:1.
     That clears AA for LARGE text (3:1) and fails it for body text (4.5:1).
     So an ember surface carries white only at display sizes — the figure on a
     Kennzahlkarte — and obsidian for everything smaller. This is the one place
     in the system where the contrast tears, which is why it is a token and not
     a judgement call at each use. */
  --on-ember-display: var(--color-chalk);
  --on-ember-text:    var(--color-obsidian);

  /* One family variable, so the licensed display face can replace Bebas Neue by
     swapping this single line. Bebas has NO lowercase — every glyph in the
     lowercase slots is a capital — which is why it carries only the large sizes
     and DM Sans takes 32px and below (ARCHITECTURE.md §27). */
  --font-display: 'Bebas Neue', 'Haettenschweiler', 'Arial Narrow', sans-serif;
  --font-body:    'DM Sans', system-ui, -apple-system, sans-serif;
  --font-system:  system-ui, -apple-system, 'Segoe UI', sans-serif;

  --size-display-1: 189px;
  --size-display-2:  96px;
  --size-display-3:  80px;
  --size-h1:         48px;
  --size-h2:         32px;
  --size-h3:         26px;   /* the floor. never a heading below this */
  --size-body:       16px;
  --size-small:      14px;
  --size-fine:       12px;

  --line-body: 1.55;
  --track-display: .02em;    /* positive. never negative, anywhere */

  /* DIE VIER REIZFARBEN VON „FARBE GEGEN WORT". Einzige Ausnahme von „sieben
     Farben, mehr nicht", freigegeben am 01.09.2026 (ARCHITECTURE.md §28). Sie
     stehen hier, damit die Markenprüfung sie findet — verwendet werden sie
     ausschließlich in `.tr-tinte-*` und `.tr-grund-*`, also im Spielfeld dieses
     einen Spiels. Wer sie anderswo einsetzt, bricht die Ausnahme.

     Die ersten vier liegen auf Obsidian (Gelb auf Limestone misst unter 2:1),
     die zweiten vier auf Limestone als Punkt am Antwortknopf. Gemessen mit
     tools/test-kontrast.sh: 8.1 / 9.1 / 11.3 / 17.3 zu eins. */
  --reiz-rot:         #ff7d6d;
  --reiz-blau:        #77b0ff;
  --reiz-gruen:       #57d98a;
  --reiz-gelb:        #f5f28e;   /* derselbe Wert wie Schwefel, hier als Reiz */
  --reiz-rot-dunkel:  #e04a35;
  --reiz-blau-dunkel: #2f6fd0;
  --reiz-gruen-dunkel:#1e8a4e;

  --radius-card:  40px;
  --radius-pill: 800px;
  --radius-field:100px;
  --radius-sm:    16px;
  --radius-md:    20px;

  --width-max:    1280px;
  --space-section:  80px;
  --space-card:     40px;
  --space-item:     16px;

  --rule: 1.5px dotted var(--color-obsidian);

  /* Die feine Abstandsskala. Der Auftrag nennt 16 als Elementabstand, und
     dabei bleibt es für die Verkaufsseiten. Eine Anwendungsoberfläche braucht
     darunter Abstufungen: zwischen einer Antwortmöglichkeit und der nächsten
     liegen keine 16 Pixel. Diese Marken werden AUSSCHLIESSLICH im Abschnitt
     „the application" gebraucht.

     Ohne sie liefen 111 Verwendungen ins Leere und die Antwortfelder lagen über
     ihren Beschriftungen — sichtbar erst im Screenshot, nicht in einer Zahl. */
  --space-4:   4px;
  --space-8:   8px;
  --space-12: 12px;
  --space-16: 16px;
  --space-20: 20px;
  --space-24: 24px;
  --space-32: 32px;
  --space-40: 40px;

  /* Bewegung. Kurz und ohne Federung; `prefers-reduced-motion` schaltet sie
     weiter unten vollständig ab. */
  --dur-hover:   .12s;
  --dur-settle:  .24s;
  --ease-settle: cubic-bezier(.2, .7, .3, 1);
  --angle: 0deg;
}

/* ---- 3. ground ---------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--color-pumice);
  color: var(--color-obsidian);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--size-body);
  line-height: var(--line-body);
  /* deliberately absent: letter-spacing. see the file header. */
}

img, svg { max-width: 100%; }

/* ---- 4. type -------------------------------------------------------------
 * Bebas carries 48px and up, where all-caps is the point. DM Sans carries 32px
 * and 26px, where a long German compound in capitals would run to four lines
 * and stop being readable.
 */

.display-1, .display-2, .display-3, h1, .h1 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: var(--track-display);
  line-height: .92;
  text-transform: uppercase;
  margin: 0;
  hyphens: none;              /* what does not fit breaks at the space */
}

.display-1 { font-size: var(--size-display-1); }
.display-2 { font-size: var(--size-display-2); }
.display-3 { font-size: var(--size-display-3); }
h1, .h1    { font-size: var(--size-h1); }

/* German compounds are long, and 26px is the floor a heading may not go under.
   "Verarbeitungsgeschwindigkeit" is 28 characters and overran a 390px column
   until this was added. Hyphenation is ON here and OFF on the display sizes:
   Bebas headings break at the space, because a hyphenated "Blö-cke" across a
   96px line reads as a mistake. `lang="de"` on <html> is what makes the
   dictionary work — a page without it hyphenates by English rules. */
h2, .h2, h3, .h3 {
  font-family: var(--font-body);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: 0;
  margin: 0;
  overflow-wrap: break-word;
  hyphens: auto;
}
h2, .h2 { font-size: var(--size-h2); }
h3, .h3 { font-size: var(--size-h3); }

/* Nothing below 26px is a heading. A fourth level would have to be smaller,
   and the system does not have one — use .small with a rule above it. */

p { margin: 0 0 var(--space-item); }

/* Running text needs a measure. The full 1280px column carries over 100
   characters a line, which is roughly twice what stays readable — and German
   compounds make long lines worse, not better. Any block of prose gets this;
   cards are already narrow enough.
   
   Die Regel steht im Abschnitt „layout" hinter `.wrap`, nicht hier. Der Grund
   ist die Reihenfolge: Beide setzen `max-width` und beide sind Klassenregeln,
   also entscheidet bei Gleichstand die spätere. Stand `.prose` hier oben, gewann
   `.wrap` — und `class="wrap section prose"`, wie es die drei Rechtstexte
   tragen, war bis zum 01.09.2026 wirkungslos: 154 Zeichen je Zeile statt 68. */
.small { font-size: var(--size-small); }

/* DIESE KLASSE TAT NICHTS. Sie steht seit jeher an vierzehn Stellen — über der
   Anzeigenzeile der Werkstattseiten, über den Rechtstexten, über dem Profil —
   und hatte bis zum 03.09.2026 keine einzige Grundregel; sie wurde nur in zwei
   Sonderfällen überschrieben. Was als kleines Etikett gedacht war, stand also
   überall als gewöhnlicher Fließtext. Dieselbe Form wie `.rx-stage-label`, das
   die Rolle im Anwendungsteil längst richtig ausfüllt. */
.meta-label {
  font-size: var(--size-fine);
  font-family: var(--font-system);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--color-dim);
  margin: 0 0 var(--space-8);
}
.fine  { font-size: var(--size-fine); font-family: var(--font-system); }

a { color: var(--color-obsidian); text-decoration-thickness: 1.5px;
    text-underline-offset: 3px; }
/* `a:hover` (0,1,1) schlägt `.btn-ember` (0,1,0): Unter dem Mauszeiger wurde
   die Beschriftung eines Ember-Knopfes ember auf ember und verschwand. Deshalb
   nimmt die Hover-Farbe die Knöpfe aus; sie melden sich über die Helligkeit.
   Nachgemessen: obsidian auf #e84a00 ist 5.21:1, klärt AA. */
a:hover:not(.btn) { color: var(--color-ember); }
.btn:hover { filter: brightness(0.92); }

/* ---- 5. layout ---------------------------------------------------------- */

.wrap    { max-width: var(--width-max); margin: 0 auto; padding: 0 24px; }
/* NACH `.wrap`, siehe die Begründung oben bei `.small`. */
.prose   { max-width: 68ch; }

/* Trägt ein Abschnitt BEIDE Klassen — so die drei Rechtstexte —, dann gilt das
   Maß für seinen Inhalt, nicht für ihn selbst. Der Abschnitt behält die 1280
   und seine Ausrichtung; die Zeilen darin brechen bei 68 Zeichen und beginnen
   links, bündig unter der Marke.
   
   Zuerst hatte ich `.prose` einfach gewinnen lassen. Dann stand die Spalte
   zentriert in der Fläche, während Kopf- und Fußzeile am Rand blieben: lesbar,
   aber ausgerichtet wie ein fremdes Element auf derselben Seite. */
.wrap.prose { max-width: var(--width-max); }
.wrap.prose > * { max-width: 68ch; }

/* Und dasselbe Maß für Fließtext, der ohne eigene Auszeichnung direkt in einem
   Abschnitt steht. `.prose` von Hand an jeden Absatz zu schreiben hieß, es an
   fünfzehn Stellen zu vergessen — gemessen am 01.09.2026 trugen Übungen,
   Ranglisten, Systemcheck, Durchgang und Profil je ein bis drei Absätze mit
   über neunzig Zeichen je Zeile.
   
   Nur DIREKTE Kinder: Was in einer Karte oder einem Raster steht, ist dort
   schon schmal genug, und eine Begrenzung würde die Spalte verziehen. */
.section > p,
.section > ul,
.section > ol { max-width: 68ch; }
.section { padding-block: var(--space-section); }
.stack > * + * { margin-top: var(--space-item); }
.rule    { border: 0; border-top: var(--rule); margin: var(--space-card) 0; }

.grid { display: grid; gap: var(--space-item); }
@media (min-width: 720px)  { .grid-2, .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .grid-3 { grid-template-columns: repeat(3, 1fr); } }
/* Vier Zugänge nebeneinander brauchen mehr Platz als drei: bei 1280 Punkten
   Layoutbreite bleiben je Karte 290, davon 210 für den Inhalt. Erst ab 1100
   Punkten, darunter zwei mal zwei. */
@media (min-width: 1100px) { .grid-4 { grid-template-columns: repeat(4, 1fr); } }

/* ---- 6. surfaces ---------------------------------------------------------
 * Layers are made of colour. No shadow appears anywhere in this file, and none
 * may be added: the system is flat on purpose.
 */

.card {
  background: var(--color-limestone);
  border-radius: var(--radius-card);
  padding: var(--space-card);
}
/* KARTEN IN EINER REIHE SIND GLEICH HOCH — das macht das Raster von selbst —,
   ihr INHALT klebte aber oben. Auf uebungen.html tragen zwölf Kacheln Titel von
   einer und von zwei Zeilen, und „Übung starten" stand dadurch auf vier
   verschiedenen Höhen; das Auge sucht dann in jeder Kachel neu. Der letzte
   Absatz einer Karte rutscht deshalb nach unten und steht auf einer Linie.

   Nur DIREKTE Kinder eines Rasters: Eine einzelne Karte im Fließtext hat keine
   Nachbarn, an denen sie sich ausrichten müsste, und würde durch die Regel nur
   in die Höhe gezogen. */
.grid > .card {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}

.grid > .card > :last-child { margin-bottom: 0; }

/* Was aussieht wie eine Aktion, geht ans Ende: der Pfeil-Absatz, ein Knopf,
   eine Knopfreihe. Alles andere bleibt, wo es steht. */
.grid > .card > p:last-child,
.grid > .card > .btn:last-child,
.grid > .card > .notice-actions:last-child { margin-top: auto; padding-top: var(--space-12); }

/* Eine Karte, die führt.

   Die Hover-Farbe bleibt draußen: Sie färbt sonst den ganzen Kartentext ember,
   und auf Limestone misst das 3.28:1. Die Rückmeldung trägt der Rand. */
a.card {
  display: block;
  text-decoration: none;
  color: inherit;
}

a.card:hover { color: inherit; outline: 2px solid var(--color-obsidian);
               outline-offset: -2px; }

a.card:focus-visible { color: inherit; }

.card-violet { background: var(--color-plasma-violet); color: var(--color-chalk); }
.card-outline { background: transparent; border: 1.5px solid var(--color-obsidian); }

/* The figure cards. These carry the numbers the platform is about. */
.stat {
  background: var(--color-ember);
  color: var(--on-ember-display);
  border-radius: var(--radius-card);
  padding: var(--space-card);
}
.stat-figure {
  font-family: var(--font-display);
  font-size: var(--size-display-3);      /* 80px floor, per the brief */
  letter-spacing: var(--track-display);
  line-height: .9;
  display: block;
}
/* Small text on ember is obsidian, not white — 3.36:1 does not carry body
   copy. The label sits under the figure and is read, not glanced at. */
.stat-label {
  color: var(--on-ember-text);
  font-size: var(--size-small);
  display: block;
  margin-top: 8px;
}

.tag {
  display: inline-block;
  background: var(--color-sulfur);
  color: var(--color-obsidian);
  border-radius: var(--radius-pill);
  padding: 6px 16px;
  font-size: var(--size-small);
}

/* ---- 7. controls ---------------------------------------------------------
 * Every control is a pill or a 40px rectangle. Never a sharp corner.
 * Plasma violet is NOT a control colour — it belongs to the hero halftone and
 * one card, and nowhere else.
 */

.btn {
  display: inline-block;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--size-body);
  line-height: 1;
  padding: 16px 32px;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--color-obsidian);
  background: var(--color-obsidian);
  color: var(--color-chalk);
  text-decoration: none;
  cursor: pointer;
}
/* Obsidian, not white. Measured: white on ember is 3.34:1, which clears AA for
   large text and fails it for a 16px label (4.5 needed). The brief named this
   exact spot as the one where the contrast tears, and it does. Ember is fixed,
   so the text moves: obsidian on ember is 6.06:1. */
/* `[hidden]` setzt display:none — aber nur mit der Kraft einer ELEMENTregel.
   Jede Klassen- oder ID-Regel mit eigenem `display` schlägt sie, und dann steht
   ein verborgenes Element sichtbar UND bedienbar auf dem Schirm.
   
   DAS IST HIER DREIMAL PASSIERT, jedes Mal woanders:
   
   1. `.btn { display: inline-block }` gegen die Knöpfe, die seat.js verbirgt.
      Auf einem Pausen- oder Kalibrierschirm ließ sich damit wegklicken, was
      nicht wegzuklicken ist. Gefunden von tools/test-weiter.sh.
   2. Drei weitere Klassen brauchten je ihre eigene Gegenzeile: .notice,
      .w-warn, .pf-vergleich.
   3. `#st-stufen { display: grid }` gegen die Stufenauswahl auf start.html —
      eine ID-Regel, also noch stärker. Das `hidden` im Markup war wirkungslos,
      und neben der Formatkarte sah es aus, als ließen sich zwei Tests
      gleichzeitig wählen.
   
   Drei Einzelfälle sind ein Muster. `!important` ist hier nicht grob, sondern
   genau richtig: `hidden` heißt „nicht anzeigen", und das soll keine
   Gestaltungsregel überstimmen können. Damit gilt es auch für jedes Element,
   das erst später eine eigene display-Regel bekommt. */
[hidden] { display: none !important; }

/* seat.js baut seinen Zurück-Knopf mit `btn-ghost`. Derselbe Knopf wie
   `.btn-quiet`, unter dem Namen, den der übernommene Code verwendet — den Namen
   dort zu ändern hieße, geprüften Code für eine Benennung anzufassen. */
.btn-ghost   { background: transparent; color: var(--color-obsidian); }

.btn-ember   { background: var(--color-ember); border-color: var(--color-ember);
               color: var(--color-obsidian); }
.btn-quiet   { background: transparent; color: var(--color-obsidian); }
.btn:disabled, .btn[aria-disabled='true'] {
  /* own colours, never opacity: an opacity-dimmed control measured 2.3:1 in the
     predecessor and was unreadable (ROADMAP of the old project, 24.08.2026) */
  background: var(--color-pumice);
  border-color: #6b6b68;
  color: #56565a;
  cursor: not-allowed;
}

input, select, textarea {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--size-body);
  padding: 14px 20px;
  border: 1.5px solid var(--color-obsidian);
  border-radius: var(--radius-field);
  background: var(--color-chalk);
  color: var(--color-obsidian);
}
textarea { border-radius: var(--radius-md); }

/* Focus is visible on every surface, including ember and violet. */
:focus-visible {
  outline: 3px solid var(--color-obsidian);
  outline-offset: 3px;
}
.card-violet :focus-visible, .stat :focus-visible {
  outline-color: var(--color-chalk);
}

/* ---- 8. halftone ---------------------------------------------------------
 * The signature: orange dots over a violet-to-orange gradient, 40px radius.
 * Drawn here, not fetched — no image from a foreign server, and it scales.
 */

.halftone {
  border-radius: var(--radius-card);
  background-color: var(--color-plasma-violet);
  background-image:
    radial-gradient(circle at 1.5px 1.5px, var(--color-ember) 1.5px, transparent 1.6px),
    linear-gradient(135deg, var(--color-plasma-violet) 0%, var(--color-ember) 100%);
  background-size: 12px 12px, 100% 100%;
  background-repeat: repeat, no-repeat;
}

/* ---- der Abschnittskopf --------------------------------------------------
 *
 * ZWEI DINGE FEHLTEN JEDEM ABSCHNITT: ein Anfang und ein Signal.
 *
 * Der Anfang: Die Seiten trennten ihre Abschnitte mit einem eingesetzten
 * `<hr>`, den jemand von Hand setzen musste — an manchen Stellen stand einer,
 * an den meisten nicht. Die Linie hängt jetzt am Abschnitt selbst, also überall
 * dort, wo tatsächlich ein neuer beginnt, und an keiner anderen Stelle.
 *
 * Das Signal: Eine Überschrift in 32 px DM Sans steht unter einem Seitenkopf
 * von 64 px Bebas wie eine Bildunterschrift. Die Marke davor gibt ihr eine
 * Kante — dasselbe Ember, das die Aufzählungszeichen und den Hauptknopf trägt,
 * an der einzigen Stelle im System, an der eine aggressive Farbe hingehört.
 * Sie ist Zierde und trägt keine Bedeutung: Sie steht vor JEDER Überschrift
 * zweiter Ordnung und unterscheidet nichts von nichts (ARCHITECTURE.md §9). */
.section + .section { border-top: var(--rule); }

/* Nach dem Kopf und vor einer hellen Fläche keine Linie: Dort trennt schon die
   Fläche oder der Halbton, und beides zusammen sind zwei Kanten für eine Naht.
   Die Rechtstexte tragen ihren Kopf in einem eigenen Abschnitt — ohne die
   `:has`-Zeile stünde bei ihnen ein Strich unmittelbar unter dem Halbton. */
.section.ist-hell,
.section.ist-hell + .section,
.section:has(.seitenkopf) + .section { border-top: 0; }

/* Nach dem Kopf reicht der halbe Abstand. Zwei volle Abschnittsabstände sind
   160 Punkte, und wo der erste Inhaltsabschnitt keine Überschrift trägt — auf
   uebungen.html, systemcheck.html, organisationen.html —, klaffte darunter eine
   Lücke, die wie ein Ladefehler aussah. Der Halbton darüber schafft die Luft
   bereits. */
.section:has(.seitenkopf) + .section { padding-top: var(--space-card); }

.section > h2:first-child::before,
.section > .h2:first-child::before {
  content: '';
  display: block;
  width: 44px;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--color-ember);
  margin-bottom: var(--space-16);
}


/* ---- 8a. der Seitenkopf --------------------------------------------------
 *
 * BIS ZUM 03.09.2026 HATTE NUR DIE STARTSEITE EINEN. Sie trug den Halbton, die
 * Kennzahlen auf Ember und die Karten; die elf anderen Seiten begannen mit einer
 * nackten Überschrift auf grauem Grund und benutzten vom ganzen Vokabular nur
 * die Karte. Wer von der Startseite weiterklickte, landete auf einer anderen
 * Website.
 *
 * DAS IST KEINE AUSWEITUNG VON PLASMA-VIOLETT. Die Regel lautet „Hero-Halbton
 * und eine Karte" (CLAUDE.md), und dies IST der Hero-Halbton — jede Seite hat
 * jetzt einen. An einem Bedienelement steht die Farbe nach wie vor nirgends.
 *
 * AUF DEM HALBTON STEHT NUR DIE ANZEIGENZEILE. Gemessen bei 390 px kommt weißer
 * Fließtext über dem orangen Ende des Verlaufs auf 4.46:1 gegen die 4.5, die AA
 * verlangt, und die orangen Punkte drücken einzelne Buchstaben noch tiefer. Eine
 * Überschrift ab 48 px ist große Schrift und klärt ihre 3:1 an jedem Punkt des
 * Verlaufs. Alles Kleinere steht darunter auf Pumice, wo es 15.58:1 misst.
 * Diese Trennung ist der Grund für den Aufbau und nicht verhandelbar;
 * tools/test-kontrast.sh misst sie auf jeder Seite gegen die echten Pixel. */
/* Den Verlauf selbst gibt `.halftone` weiter unten — jeder Seitenkopf trägt
   BEIDE Klassen. Eine zweite Kopie des Verlaufs hier wäre eine zweite Stelle,
   an der die Kontrastprüfung nachsehen müsste, und sie sieht über `.halftone`
   nach (tools/halbton-check.js). */
.seitenkopf {
  padding: var(--space-card);
  color: var(--color-chalk);
  display: flex;
  align-items: flex-end;
  min-height: 220px;
}

/* Die Überschrift wächst mit dem Fenster, aber nie unter 48: Darunter setzt
   Bebas nicht mehr (ARCHITECTURE.md §27), und darunter wäre sie auf dem
   Halbton auch keine große Schrift mehr. */
.seitenkopf h1,
.seitenkopf .h1 {
  margin: 0;
  font-size: clamp(48px, 5.6vw, 80px);
  /* Das Netz gegen den Überlauf, ohne Trennstrich: Bricht ein Wort hier, dann
     nur, weil es sonst aus dem Halbton liefe. */
  overflow-wrap: break-word;
}

/* Was unter dem Kopf steht: Fließtext links, Knöpfe rechts — dasselbe Paar wie
   auf der Startseite, damit die Seiten einen gemeinsamen Anfang haben. */
.seitenkopf-lead {
  display: grid;
  gap: var(--space-item);
  margin-top: var(--space-card);
}

@media (min-width: 860px) {
  .seitenkopf-lead { grid-template-columns: minmax(0, 1fr) auto; align-items: start; }
}

.seitenkopf-lead > p { margin: 0; max-width: 68ch; }

.seitenkopf-lead .btn + .btn { margin-left: var(--space-12); }

/* Ein Abschnitt auf heller Fläche. Zwölf Abschnitte hintereinander auf demselben
   Grund sind eine Textrolle; die Fläche gibt der Seite einen Takt, ohne dass das
   System eine achte Farbe braucht. Getrennt vom Kopf: Wo beides zusammenträfe,
   stünde eine Karte auf einer Karte. */
.section.ist-hell {
  background: var(--color-limestone);
  border-radius: var(--radius-card);
  padding: var(--space-card);
  margin-block: var(--space-card);
}

/* Eine Formularseite ist eine Karte und sonst nichts. Sie stand links, und
   rechts daneben lag die halbe Fensterbreite leer. */
.formular-mitte {
  max-width: 520px;
  margin-inline: auto;
}

/* TRENNEN DARF BEBAS NUR AUF DEM TELEFON. Auf 390 Punkten braucht
   „Geschäftsbedingungen" in 48 px 385 Punkte und der Kopf hat 342: Das Wort lief
   über den Halbton hinaus und stand dort weiß auf Pumice — gemessene 1.30:1
   gegen die 3.0, die große Schrift braucht (tools/halbton-check.js, 03.09.2026).
   Ein Trennstrich ist dort der kleinere Fehler als eine unlesbare Überschrift.
   Auf breiten Schirmen bleibt er aus: In einer 80-px-Zeile liest sich ein
   getrenntes „GE-DÄCHTNIS" als Versehen, und dort ist zum Brechen an der
   Wortgrenze reichlich Platz. */
@media (max-width: 560px) {
  .seitenkopf h1,
  .seitenkopf .h1 { hyphens: auto; }
}

@media (max-width: 720px) {

  .seitenkopf { padding: var(--space-24); min-height: 200px; }

  .section.ist-hell { padding: var(--space-24); }
}


/* ---- die achtzehn Blöcke auf verfahren.html ------------------------------
 *
 * Achtzehn Zeilen aus Nummer, Titel und einem Satz, jede wie die vorige und nur
 * durch eine Haarlinie getrennt: Das war eine Textrolle von 1200 Punkten Höhe,
 * die niemand von oben nach unten liest. Zwei Spalten halbieren sie, und die
 * Nummer in der Anzeigenschrift gibt dem Auge eine Kante zum Springen — man
 * sucht Block 13, man liest nicht bis dorthin.
 *
 * Die Nummer misst 48 px, weil Bebas darunter nicht gesetzt wird
 * (ARCHITECTURE.md §27). Sie steht in Dim und nicht in Obsidian: Sie ordnet,
 * sie ist nicht die Aussage. */
.blockliste {
  margin-top: var(--space-card);
  display: grid;
  gap: var(--space-12);
}

@media (min-width: 900px) {
  .blockliste { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-12) var(--space-32); }
}

.blockkarte {
  display: grid;
  grid-template-columns: 2.6rem minmax(0, 1fr);
  gap: var(--space-16);
  align-items: baseline;
  padding: var(--space-16) 0;
  border-bottom: 1px solid var(--color-line);
}

.blockkarte-nr {
  margin: 0;
  font-family: var(--font-display);
  font-size: 48px;
  line-height: .8;
  letter-spacing: var(--track-display);
  color: var(--color-dim);
}

.blockkarte-h { margin: 0 0 var(--space-4); font-size: 17px; }

.blockkarte p.small { margin: 0; color: var(--color-dim); }


/* ---- 8b. head and foot ---------------------------------------------------
 * There is no build step and no router, so every page carries this markup
 * itself. tools/test-seiten.sh checks that they stay identical rather than
 * trusting anyone to keep sixteen copies in step.
 *
 * The mark is the word, set in the display face. No logo file, no image from a
 * foreign server.
 */

/* DIE KOPFZEILE BLEIBT STEHEN. Die Verkaufsseiten sind zwischen 2000 und 5000
   Punkte lang; wer unten bei den Preisen steht, hatte bis zum 03.09.2026 keinen
   Weg zurück außer scrollen. Sie ist 65 Punkte hoch und kostet damit weniger,
   als sie einbringt.

   NICHT WÄHREND EINER MESSUNG: `body.laeuft` blendet sie ohnehin aus, und ein
   klebender Streifen über einer laufenden Aufgabe wäre ein Reiz im Blickfeld.
   Die Regel steht weiter unten und bleibt gültig. */
.kopf {
  border-bottom: var(--rule);
  background: var(--color-pumice);
  position: sticky;
  top: 0;
  z-index: 20;
}

/* Ein Sprungziel darf nicht unter der klebenden Zeile landen. Das gilt für
   jedes Ziel im Dokument, nicht nur für die, an die gerade jemand denkt. */
:target,
[id] { scroll-margin-top: 88px; }

/* Läuft eine Aufgabe, ist die Kopfzeile ausgeblendet — dann gibt es auch nichts
   auszugleichen. */
body.laeuft [id] { scroll-margin-top: 0; }
.kopf-innen {
  max-width: var(--width-max);
  margin: 0 auto;
  padding: 20px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-item);
  flex-wrap: wrap;
}
.marke {
  font-family: var(--font-display);
  font-size: var(--size-h2);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  text-decoration: none;
  line-height: 1;
}
.marke:hover { color: var(--color-ember); }

.nav { display: flex; gap: 20px; flex-wrap: wrap; align-items: center; }
.nav a {
  text-decoration: none;
  font-size: var(--size-small);
  padding: 6px 0;
  border-bottom: 2px solid transparent;
}
.nav a:hover { border-bottom-color: var(--color-ember); }
/* The current page is marked by more than colour: it carries a rule under it
   and aria-current for anything that does not see the rule.
   Die Linie ist dicker als die des Hovers: Beide gleich stark hieß, dass ein
   Zeiger über der Navigation zwei gleich markierte Einträge zeigte, von denen
   nur einer die Seite war, auf der man steht. */
.nav a[aria-current='page'] {
  border-bottom-color: var(--color-obsidian);
  border-bottom-width: 3px;
}

/* ---- die Fußzeile -------------------------------------------------------
 *
 * OBSIDIAN, NICHT PUMICE. Sie war eine helle Fläche von 400 Punkten Höhe mit
 * zwölf Links darauf, durch nichts vom Inhalt darüber getrennt als eine
 * gepunktete Linie — auf jeder Seite das gleiche unentschiedene Ende.
 *
 * Das ist kein Dunkelmodus (den gibt es hier nicht) und keine achte Farbe:
 * Obsidian ist die Textfarbe des Systems, und Chalk ist ausdrücklich „Weiß auf
 * dunklen Flächen". Beide zusammen misst tools/test-kontrast.sh mit 20.23:1 —
 * dieselbe Paarung, die jeder dunkle Knopf schon trägt. */
.fuss {
  background: var(--color-obsidian);
  color: var(--color-chalk);
  padding-block: var(--space-section) var(--space-card);
  margin-top: var(--space-section);
  border-radius: var(--radius-card) var(--radius-card) 0 0;
}

.fuss a { color: var(--color-chalk); }

/* Die Hover-Farbe der Seite ist Ember, und Ember auf Obsidian misst 4.6:1 —
   über den 4.5 für Fließtext. Sie darf hier also bleiben. */
.fuss a:hover { color: var(--color-ember); }

/* Der Nachsatz tritt zurück, ohne eine Farbe dafür zu erfinden: Chalk mit
   Deckkraft, gemessen 13.8:1 gegen Obsidian. `--color-dim` ginge hier nicht,
   das ist ein Grauton für helle Flächen und käme auf 1.4:1. */
.fuss .fine { opacity: .72; }
.fuss-spalten {
  max-width: var(--width-max);
  margin: 0 auto;
  padding: 0 24px;
  display: grid;
  gap: var(--space-card);
}
@media (min-width: 720px) { .fuss-spalten { grid-template-columns: repeat(3, 1fr); } }
.fuss h3 {
  font-size: var(--size-h3);
  margin-bottom: var(--space-item);
  color: var(--color-chalk);
}
.fuss ul { list-style: none; margin: 0; padding: 0; }
.fuss li + li { margin-top: 8px; }
.fuss a { font-size: var(--size-small); }

/* ---- 8c. das Telefon -----------------------------------------------------
 * Die Verkaufsseiten laufen auf dem Telefon (CLAUDE.md); Übung und Durchgang
 * verlangen 1024 x 700 und zeigen dort eine Erklärung. Was hier steht, betrifft
 * deshalb nur Kopf, Fuß und Formulare.
 *
 * Gemessen am 01.09.2026 auf 390 Punkten: In der Fußzeile waren zwölf Links
 * 18 Punkte hoch, untereinander gestapelt, mit 8 Punkten Abstand. Apple und
 * Google nennen 44 Punkte als Tippziel, WCAG 2.5.8 mindestens 24 — die Fußzeile
 * lag unter beidem, und wer den falschen Link trifft, landet in den AGB statt
 * beim Systemcheck. Die Navigation kam auf 36.
 *
 * Nicht kleiner gesetzt, sondern großzügiger gefasst: Die Schrift bleibt, das
 * Ziel um sie herum wächst. */
@media (max-width: 720px) {

  /* Jeder Fußzeilen-Link wird zur eigenen Zeile mit Polsterung. `li + li`
     entfällt dabei — der Abstand steckt jetzt im Ziel selbst und nicht
     dazwischen, sonst stünden 8 Punkte tote Fläche zwischen zwei Treffern. */
  .fuss li + li { margin-top: 0; }
  .fuss li a {
    display: block;
    padding-block: 12px;
    text-underline-offset: 4px;
  }

  /* Eine Fußzeile ist ein Wegweiser, keine Überschriftenebene: 26 Punkte
     Bebas über drei gestapelten Spalten machten sie länger als der Inhalt
     darüber. */
  .fuss h3 { font-size: 20px; margin-bottom: var(--space-8); }

  /* Die Navigation bricht auf zwei Zeilen um. Ohne eigene Höhe stehen die
     beiden Reihen so dicht, dass der Daumen zwischen ihnen landet.
     
     10 Punkte, nicht 12: Sechs Einträge brauchen auf 390 Punkten zwei Reihen,
     und jede Zeile Höhe zahlt die Kopfzeile doppelt. Bei 10 misst ein Ziel
     38 Punkte — deutlich über den 24 aus WCAG 2.5.8 und nah an den 44, die
     Apple und Google empfehlen. */
  .nav { gap: var(--space-12) 20px; }
  .nav a { padding-block: 10px; }

  /* Die Kopfzeile kostete 196 der 844 sichtbaren Punkte, fast ein Drittel der
     ersten Ansicht, bevor der Besucher irgendetwas vom Verfahren sah. Marke und
     Navigation stehen auf dem Telefon ohnehin untereinander; der Abstand nach
     außen darf dann kleiner sein als der zwischen ihnen. */
  .kopf-innen { padding: 14px 24px; gap: var(--space-8); }

  /* AUF DEM TELEFON KLEBT SIE NICHT. Sechs Einträge brauchen auf 390 Punkten
     zwei Reihen, und mit Marke misst die Kopfzeile dort 330 der 844 sichtbaren
     Punkte. Klebend stünde dauerhaft ein Drittel des Schirms voll Navigation —
     der schnelle Weg zurück ist das nicht wert, wo eine Wischbewegung nach oben
     dasselbe leistet. Auf dem Schreibtisch bleibt sie stehen: dort kostet sie
     65 Punkte. */
  .kopf { position: static; }

  :target,
  [id] { scroll-margin-top: 0; }

  /* Zwei Schaltflächen nebeneinander brechen auf 390 Punkten untereinander um —
     und standen dann ohne Zwischenraum aufeinander, auf systemcheck.html
     gemessen 0 Punkte. Zwei Tippziele ohne Trennung sind eines zu viel.
     `:has` steht dafür, dass die Regel nur die Absätze trifft, die wirklich
     Schaltflächen tragen; ein Fließtextabsatz bleibt ein Block. */
  p:has(> .btn) {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-12);
  }
}

/* Ein Kontrollkästchen ist im Browserstandard rund 13 Punkte groß — auf jedem
   Gerät das kleinste Ziel des ganzen Formulars, und es trägt bei der
   Registrierung die Einwilligung. Größer und mit Abstand zum Text, damit der
   Daumen nicht zwischen Kästchen und Beschriftung fällt. */
input[type="checkbox"] {
  width: 20px;
  height: 20px;
  margin-right: var(--space-8);
  accent-color: var(--color-ember);
  vertical-align: -3px;
}

/* Skip link: first thing in the tab order, invisible until focused. */
.skip {
  position: absolute;
  left: -9999px;
  background: var(--color-obsidian);
  color: var(--color-chalk);
  padding: 12px 20px;
  border-radius: var(--radius-pill);
  z-index: 10;
}
.skip:focus { left: 24px; top: 12px; }

/* ---- 8c. lists that are not bullets -------------------------------------- */

.merkmale { list-style: none; margin: 0; padding: 0; }
.merkmale li {
  padding-left: 26px;
  position: relative;
  margin-bottom: 10px;
  font-size: var(--size-small);
}
/* A square, not a tick: the mark carries no meaning of approval, and it is
   drawn in text so it survives without an icon font. */
.merkmale li::before {
  content: '';
  position: absolute;
  left: 0; top: .45em;
  width: 10px; height: 10px;
  background: var(--color-ember);
  border-radius: 3px;
}

/* A definition row: label left, figure right, dotted rule between. */
.zeile {
  display: flex;
  justify-content: space-between;
  gap: var(--space-item);
  padding: 12px 0;
  border-bottom: var(--rule);
}
.zeile:last-child { border-bottom: 0; }

/* ---- 9. motion ---------------------------------------------------------- */

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

/* ---- 10. small screens ---------------------------------------------------
 * The sales pages run on a phone. The tests do not (1024 × 700, no exception)
 * — that is enforced in systemcheck.js, not here.
 */

@media (max-width: 900px) {
  :root {
    --size-display-1: 88px;
    --size-display-2: 64px;
    --size-display-3: 56px;
    --size-h1:        40px;
    --size-h2:        28px;
    --size-h3:        26px;   /* the floor holds on every width */
    --space-section:  56px;
    --space-card:     28px;
  }
}

/* ---- 11. print -----------------------------------------------------------
 * The printed profile. Taken over from the predecessor unchanged in substance:
 * these rules address the classes that C.profil.druckseite() builds, not the
 * Caldera surface, and they are deliberately achromatic — a profile that leaves
 * this site has to be readable on a grayscale printer.
 *
 * Two things here are not decoration. The reservation ("not normed, not
 * validated") is ON the sheet, because a printed profile ends up in front of
 * someone who never read the site. And part two starts on its own page, so the
 * caveat about device-bound values stands above the values it applies to
 * instead of being separated from them by a page break.
 */
@media print {
  @page { size: A4 portrait; margin: 14mm 14mm 12mm 14mm; }

  /* Everything the page normally is, gone; the profile alone remains. The
     container is a direct child of body, so this needs no knowledge of whatever
     page it was called from. */
  body > * { display: none !important; }
  body > .druckbereich { display: block !important; }

  /* The bar is a fill, not a colour — a grayscale printer has to keep it. */
  html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }

  body {
    margin: 0; background: #fff; color: #000;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 9.4pt; line-height: 1.3;
  }

  /* Black, everything, without exception. The site gives headings a tinted
     near-black (--onyx); on paper that is a colour where none is wanted, and it
     was one until this rule. The bar fill is more specific and survives. */
  .druckbereich, .druckbereich * { color: #000; background: transparent; }

  .dr-kopf { border-bottom: 1.2pt solid #000; padding-bottom: 2mm; margin-bottom: 4mm; }
  .dr-kopf h1 { margin: 0; font-size: 15pt; letter-spacing: -0.01em; }
  .dr-meta { margin: 1.5mm 0 0; font-size: 8.4pt; color: #000; }

  .dr-tab { width: 100%; border-collapse: collapse; }
  .dr-tab td {
    padding: 1.1mm 1.5mm; border-bottom: 0.4pt solid #999; vertical-align: middle;
  }
  .dr-nr { width: 6mm; text-align: right; font-weight: 700; font-size: 8.4pt; }
  .dr-titel { font-weight: 700; }
  .dr-wert { width: 20mm; text-align: right; font-weight: 700; white-space: nowrap; }
  .dr-roh { width: 26mm; text-align: right; font-size: 8.2pt; white-space: nowrap; }
  .dr-balken { width: 46mm; }
  .dr-bar { height: 2.6mm; border: 0.5pt solid #000; }
  .dr-bar > span { display: block; height: 100%; background: #000; }

  /* Die Verlaufskurve auf dem Blatt.
     SVG trägt seine Farbe in `fill` und `stroke`, und die allgemeine
     Schwarzregel oben fasst nur `color` und `background` an. Ohne diese Zeilen
     druckte die Kurve im getönten Fast-Schwarz der Website — eine Farbe, wo
     keine hingehört. */
  /* 130 statt 150 mm. Das Bild ist 640 auf 200 breit, seine Höhe hängt also an
     der Breite: Die zwanzig Millimeter sind gut zwanzig Punkt Höhe, und die
     fehlten auf Blatt eins, seit die Auszählung oben dazugekommen ist. Lesbar
     bleibt es — die Punktbeschriftung sitzt bei knapp neun Punkt. */
  .dr-verlauf-bild {
    width: 100%; max-width: 130mm; height: auto;
    margin: 2mm 0 1mm; overflow: visible;
  }
  .dr-verlauf-netz { stroke: #000; stroke-width: 0.5; }
  .dr-verlauf-linie { stroke: #000; stroke-width: 2; fill: none; }
  .dr-verlauf-punkt { fill: #000; }
  .dr-verlauf-punkt.is-allein { fill: #fff; stroke: #000; stroke-width: 2; }
  .dr-verlauf-wert { fill: #000; font-size: 13px; font-weight: 700; text-anchor: middle; }
  .dr-verlauf-achse { fill: #000; font-size: 11px; }
  .dr-verlauf-datum { fill: #000; font-size: 11px; }

  .dr-zaehlung { margin: 0 0 1mm; font-size: 10pt; }
  .dr-zaehl-wert { font-size: 13pt; font-weight: 700; }

  .dr-h { font-size: 11pt; margin: 5mm 0 1.5mm; }
  .dr-tab-kenn td { font-size: 8.6pt; }
  .dr-kenn-name { font-weight: 700; width: 52mm; }
  .dr-kenn-wert { width: 34mm; white-space: nowrap; }
  .dr-kenn-note { font-size: 8pt; }

  .dr-fuss { margin-top: 5mm; border-top: 1.2pt solid #000; padding-top: 2mm; }
  .dr-warn { margin: 0; font-size: 8.2pt; font-weight: 700; }
  .dr-quelle { margin: 1.5mm 0 0; font-size: 7.8pt; }

  /* One page is the whole point: nothing in here may break across a page, and
     nothing may push past the first. */
  .druckbereich { page-break-inside: avoid; break-inside: avoid; }
  .dr-tab tr { page-break-inside: avoid; break-inside: avoid; }

  /* TEIL ZWEI BEGINNT AUF EINEM EIGENEN BLATT, und zwar durch diese Regel.
     `profil.js` sagt seit jeher „the break is placed here on purpose" — im
     Stylesheet stand dazu nichts. Der Umbruch entstand allein daraus, dass der
     Inhalt darüber lang genug war, und fiel weg, als am 03.09.2026 die
     Kennwerte nicht erhobener Blöcke verschwanden. Der gerätegebundene
     Vorbehalt stünde dann irgendwo, statt über den Werten, für die er gilt
     (ARCHITECTURE.md §24). */
  .dr-blatt2 { page-break-before: always; break-before: page; }
}

/* ---- 12. the application ---------------------------------------------------
 * The place where the test actually runs: the seat (seat.js), the answer
 * widgets (widgets.js), the profile (profil.js) and the check lists.
 *
 * Carried over from the predecessor and remapped onto Caldera, structure
 * unchanged — the self-checks are written against this structure, and a
 * hand-rebuilt surface would have quietly moved what they measure.
 *
 * Three things changed in the remapping, all of them because the system says so:
 * every box-shadow is gone, blue and violet accents fall onto ember (there is
 * one accent), and green/red state colours fall onto obsidian and ember. States
 * are never carried by colour alone here — the checklists print the verdict as
 * a word beside the mark, which is what makes that safe.
 *
 * THE SEAT IS NOT DECORATION. Without these rules a paused screen shows a live
 * "weiter" button and every screen a "zurück": what CSS was hiding becomes
 * operable, and a break that can be clicked away makes the measurements on
 * either side of it incomparable (tools/test-weiter.sh).
 */


.check-list {
  list-style: none;
  margin: var(--space-32) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-12);
}


.check-list li {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: var(--space-12);
  align-items: start;
  color: var(--color-obsidian);
}


.check-list svg { margin-top: 3px; }


/* ---------- hero mock: running test screen ---------- */
/* TODO: replace with a real screenshot from present.html once available. */

.mock-frame {
  position: relative;
  padding: 2px;
  border-radius: calc(var(--radius-sm) + 2px);

  isolation: isolate;
}


/* The one rotating conic border on the page. Nothing else gets it. */
.mock-frame::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: conic-gradient(from var(--angle, 0deg),
    var(--color-ember), var(--color-ember), var(--color-sulfur),
    var(--color-limestone), var(--color-ember));
  animation: spin 4s linear infinite;
  z-index: -1;
}


.mock {
  background: var(--color-chalk);
  border-radius: var(--radius-sm);
  overflow: hidden;
}


.mock-bar {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  padding: var(--space-16) var(--space-20);
  border-bottom: 1px solid var(--color-line);
  background: var(--color-line);
}


.mock-bar .meta-label { font-size: 10px; letter-spacing: .08em; }


.mock-timer {
  margin-left: auto;
  font-family: var(--font-system);
  font-size: 20px;
  font-weight: 500;
  color: var(--color-obsidian);
  letter-spacing: .02em;
}


.mock-progress {
  height: 4px;
  background: var(--color-line);
}

.mock-progress span {
  display: block;
  height: 100%;
  width: 62%;
  background: var(--color-obsidian);
}


.mock-body { padding: var(--space-24); }


.mock-question {
  font-family: var(--font-body);
  font-size: 20px;
  font-weight: 500;
  color: var(--color-obsidian);
}


.mock-grid {
  margin-top: var(--space-20);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-12);
}


.mock-cell {
  aspect-ratio: 1;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
  background: var(--color-chalk);
  display: grid;
  place-items: center;
}

.mock-cell.is-marked { border-color: var(--color-obsidian); }


.mock-foot {
  margin-top: var(--space-20);
  display: flex;
  align-items: center;
  gap: var(--space-12);
}


.mock-dots {
  margin-left: auto;
  display: flex;
  gap: var(--space-4);
}

.mock-dots i {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-line);
}

.mock-dots i.is-done { background: var(--color-obsidian); }
@media (max-width: 560px) {

  .mock-grid { grid-template-columns: repeat(3, 1fr); }
}


.notice {
  margin-top: var(--space-32);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-card);
  background: var(--color-line);
  padding: var(--space-32);
}

.notice h2 { margin-top: 0; font-size: 20px; }



.notice-actions {
  margin-top: var(--space-24);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-12);
}


/* ---------- system check rows ---------- */

.check-list-full {
  margin: var(--space-40) 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--color-line);
}


.check {
  display: grid;
  grid-template-columns: 20px 1fr auto;
  gap: var(--space-8) var(--space-16);
  align-items: center;
  border-bottom: 1px solid var(--color-line);
  padding: var(--space-16) 0;
}


.check-mark {
  width: 20px;
  height: 20px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-line);
  position: relative;
}

.check.is-ok .check-mark { border-color: var(--color-obsidian); }

.check.is-ok .check-mark::after {
  content: '';
  position: absolute;
  left: 4px; top: 7px;
  width: 8px; height: 4px;
  border-left: 2px solid var(--color-obsidian);
  border-bottom: 2px solid var(--color-obsidian);
  transform: rotate(-45deg);
}

.check.is-bad .check-mark { border-color: var(--color-obsidian); }

.check.is-bad .check-mark::after {
  content: '';
  position: absolute;
  left: 8px; top: 3px;
  width: 2px; height: 10px;
  background: var(--color-obsidian);
}


.check-name {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 500;
  color: var(--color-obsidian);
}


/* the state is spelled out, never carried by colour alone */
.check-state {
  font-family: var(--font-system);
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--color-dim);
}


.check-detail {
  grid-column: 2 / -1;
  font-size: 14px;
  color: var(--color-dim);
}


.verdict {
  margin-top: var(--space-32);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-line);
  background: var(--color-line);
  padding: var(--space-20) var(--space-24);
  font-family: var(--font-body);
  font-size: 18px;
  font-weight: 500;
  color: var(--color-obsidian);
}

.verdict.is-bad { border-color: var(--color-obsidian); }

.verdict.is-ok { border-color: var(--color-obsidian); }

/* auth.js setzt `is-error` auf die Rückmeldung eines Formulars. Behandelt wie
   is-bad: Der Rahmen tritt hervor, die Aussage steht im Text — eine Meldung,
   die nur über Farbe spräche, wäre für einen Teil der Leser keine. */
.verdict.is-error { border-color: var(--color-obsidian); }


@media (max-width: 560px) {

  .notice, .tone-box { padding: var(--space-20); }

  .check { grid-template-columns: 20px 1fr; }

  .check-state { grid-column: 2; }
}


/* ---------- answer widgets (widgets.js) ---------- */

.w-field, .w-choice { margin: 0; }

.w-choice { border: 0; padding: 0; }


.w-label, .w-legend {
  display: block;
  padding: 0;
  font-size: 14px;
  font-weight: 500;
  color: var(--color-obsidian);
  margin-bottom: var(--space-8);
}


.w-hint { margin-top: var(--space-8); font-size: 13px; color: var(--color-dim); }

.w-line { display: flex; align-items: center; gap: var(--space-12); }

.w-unit { font-size: 14px; color: var(--color-dim); }


.w-input {
  border: 1px solid var(--color-line);
  border-radius: var(--radius-field);
  background: var(--color-chalk);
  padding: var(--space-12) var(--space-16);
  font-family: var(--font-body);
  font-size: 16px;
  color: var(--color-obsidian);
  transition: border-color var(--dur-hover) var(--ease-settle);
}

.w-input:hover { border-color: var(--color-dim); }

.w-input:focus { border-color: var(--color-ember); outline-offset: 1px; }


.w-input-num { width: 12ch; text-align: right; font-variant-numeric: tabular-nums; }

.w-textarea { width: 100%; resize: vertical; line-height: 1.5; }


/* choice: pills, keyboard first */
.w-choice-row { display: flex; flex-wrap: wrap; gap: var(--space-12); }

.w-opt { position: relative; }

.w-opt input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }

/* Die Antwortmöglichkeiten sind das, worauf unter Zeitdruck geschaut wird, und
   sie standen in 15px mit knapper Polsterung da — kleiner als der Fließtext der
   Aufgabe darüber. Jetzt 18px, und der Kasten misst mindestens 48 Punkte in der
   Höhe: Das ist die Größe, die ein Tippziel auf einem Tablet braucht. */
.w-opt-face {
  display: inline-flex;
  align-items: center;
  gap: var(--space-12);
  min-height: 48px;
  border: 1.5px solid var(--color-line);
  border-radius: var(--radius-pill);
  padding: var(--space-12) var(--space-20);
  background: var(--color-chalk);
  font-size: 18px;
  color: var(--color-obsidian);
  transition: border-color var(--dur-hover) var(--ease-settle),
              background var(--dur-hover) var(--ease-settle);
}

.w-opt-face:hover { border-color: var(--color-obsidian); }

.w-opt-letter {
  font-family: var(--font-system);
  font-size: 15px;
  letter-spacing: .06em;
  color: var(--color-dim);
}

/* selected state carries a filled marker as well as the border — never colour alone */
.w-opt input:checked + .w-opt-face {
  border-color: var(--color-obsidian);
  background: var(--color-line);
}

.w-opt input:checked + .w-opt-face .w-opt-letter {
  background: var(--color-obsidian);
  color: var(--color-chalk);
  border-radius: var(--radius-pill);
  padding: 1px var(--space-8);
}

.w-opt input:focus-visible + .w-opt-face { outline: 2px solid var(--color-ember); outline-offset: 3px; }


/* digits */
/* Umbruch statt Quetschung: Neunzehn Felder zu 48 Punkten passen auf 1280 in
   eine Zeile und auf 1024 nicht. Ohne `wrap` schrumpfen sie unter die Breite,
   die zwei Ziffern brauchen. */
.w-digits { display: flex; flex-wrap: wrap; gap: var(--space-8); }

.w-digit {
  flex: none;
  width: 48px;
  padding: var(--space-12) 0;
  text-align: center;
  font-family: var(--font-system);
  font-size: 20px;
}


/* counts table */
.w-counts { border-collapse: collapse; }

.w-counts-head, .w-counts-row {
  font-family: var(--font-system);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--color-dim);
  text-align: left;
  padding: var(--space-8) var(--space-12) var(--space-8) 0;
}

.w-counts td { padding: var(--space-4) var(--space-12) var(--space-4) 0; }

.w-count { width: 72px; text-align: center; font-variant-numeric: tabular-nums; }


/* ---- Block 2 am Einzelplatz ---------------------------------------------
 *
 * Der Bogen trägt alle zwölf Zeilen eines Durchgangs, der Schirm zeigt vier
 * davon. Auf Papier liegt beides nebeneinander auf dem Tisch. Am Bildschirm
 * stand es untereinander, in Projektionsgröße, und wer die vierte Zeile zählte,
 * musste zum Kästchen scrollen — dann sieht man die Zeile nicht mehr.
 *
 * Also nebeneinander, und die Kästchen so groß, wie man sie zum Tippen braucht,
 * nicht so groß wie für einen Beamer. Zwölf Zeilen brauchen damit rund 400 statt
 * 700 Punkte und stehen neben dem Bild statt darunter. */
.seat-body:has(.w-counts) {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--space-32);
  align-items: start;
}

.seat-body:has(.w-counts) > .meta-label { grid-column: 1 / -1; }

.seat-body:has(.w-counts) > .seat-stem  { grid-column: 1; min-width: 0; }

.seat-body:has(.w-counts) > .seat-answer { grid-column: 2; margin-top: 0; }


.seat-answer .w-counts td { padding: 2px var(--space-8) 2px 0; }

.seat-answer .w-counts-head,
.seat-answer .w-counts-row { padding: 2px var(--space-8) 2px 0; }

.seat-answer .w-count { width: 52px; padding: var(--space-4) var(--space-8); font-size: 15px; }


/* Welche Zeilen der Schirm gerade zeigt. Nicht Farbe allein: Die Kästchen der
   übrigen Zeilen treten zurück, die aktiven behalten ihren vollen Kontrast und
   bekommen eine kräftigere Umrandung. */
.w-counts tr.is-aktiv .w-counts-row { color: var(--color-obsidian); font-weight: 500; }

.w-counts tr.is-aktiv .w-count { border-color: var(--color-obsidian); border-width: 2px; }

.w-counts tbody tr:not(.is-aktiv) .w-count { opacity: .45; }

.w-counts tbody tr:not(.is-aktiv):focus-within .w-count { opacity: 1; }
/* Der Rueckfall auf untereinander lag bei 1100 px und griff damit auf dem
   kleinsten zugelassenen Schirm (1024 x 700): Dort stand genau wieder, was der
   Kommentar oben abschaffen wollte — Kaestchen unter dem Raster, Sitz 1192 px
   hoch, Zaehlen nur mit Scrollen. Ab 900 px, wie die zweite Sitzregel darunter.
   Das Raster verliert Breite und skaliert mit, die Kaestchen behalten ihre. */
@media (max-width: 900px) {

  .seat-body:has(.w-counts) { display: block; }

  .seat-body:has(.w-counts) > .seat-answer { margin-top: var(--space-24); }
}


/* dual task: two stacked answers */
.w-dual { display: grid; gap: var(--space-24); }


/* ranking */
.w-rank { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-8); }

.w-rank-item { display: grid; grid-template-columns: 56px 1fr; gap: var(--space-12); align-items: center; }

.w-rank-input { text-align: center; font-family: var(--font-system); font-size: 18px; }

.w-rank-input.is-dupe { border-color: var(--color-obsidian); border-width: 2px; }

.w-rank-text { font-size: 15px; color: var(--color-obsidian); }

.w-warn {
  margin-top: var(--space-12);
  border-left: 2px solid var(--color-obsidian);
  padding-left: var(--space-12);
  font-size: 14px;
  color: var(--color-obsidian);
}



/* Ein laufender Durchgang blendet aus, was nicht zur Aufgabe gehoert.
   test-lauf.js setzt die Klasse, sobald der erste Schirm steht, und nimmt sie
   zurueck, wenn die Vorbereitung scheitert. Der Fuss bleibt, er steht unter
   dem Sitz und drueckt nichts weg. */
body.laeuft > .kopf,
body.laeuft #tp-vorspann,
body.laeuft #ue-vorspann { display: none; }

/* Auch die Ueberschrift der Uebungseinheit: Sie kostet 85 Punkte, und der
   Sitz reichte damit auf 1024 x 700 unter die Falz. Welche Einheit laeuft,
   steht im Sitzkopf. */
body.laeuft #ue-mount .section-head { display: none; }

/* Auf flachen Schirmen diktiert nicht das Raster die Hoehe, sondern die
   Antwortspalte: Zwoelf Kaestchen sind 439 px, das Raster daneben nur 298.
   Gemessen auf 1024 x 700 blieb der Sitz 748 px hoch und lag 48 px unter der
   Falz. Die Kaestchen bleiben, wie sie sind — sie sind Tippziele. Stattdessen
   geht die Polsterung zurueck, und der Abstand ueber dem Sitz entfaellt: 56 px,
   und der Sitz steht ganz auf dem Schirm. */
@media (max-height: 800px) {

  body.laeuft .seat { margin-top: 0; }

  body.laeuft .seat-body { padding: var(--space-16) var(--space-24) var(--space-8); }

  /* Die letzten Punkte auf 1024 x 700. Blocktitel und Uhr behalten ihre
     Groesse, nur die Leiste wird flacher. */
  body.laeuft .seat-head { padding: var(--space-8) var(--space-24); }
}

/* Die Skizze über einer Aufgabe von Block 6 (uebung.js, uebung-items2.js).
   In currentColor, also in der Textfarbe daneben — die Zeichnung bringt keine
   achte Farbe ins System. */
.tech-stage {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-20);
}

.tech-skizze {
  width: 100%;
  max-width: 320px;
  height: auto;
  color: var(--color-obsidian);
}


/* ---------- Auswertung einer Übungseinheit (uebung.js) --------------------
 *
 * Fünf Klassen, keine einzige hatte eine Regel: Die Karte war keine Karte, die
 * Wertepaare standen untereinander statt nebeneinander, und die Tabelle hatte
 * weder Linien noch eine abgesetzte Kopfzeile. Alles klebte am linken Rand.
 *
 * Richtig und falsch stehen hier bewusst DA — eine Übung misst nicht, sie soll
 * zeigen, was schiefging. Nicht über Ampelfarben (Caldera lässt keine zu, und
 * Farbe allein darf nichts tragen): Das Wort steht in der Zeile, dazu ein
 * Balken am Zeilenanfang, und die falsche Zeile hebt sich durch ihre Fläche ab. */
.result-card {
  margin-top: var(--space-32);
  max-width: 860px;
  background: var(--color-limestone);
  border-radius: var(--radius-card);
  padding: var(--space-card);
}

.result-list {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-8) var(--space-24);
  margin: 0 0 var(--space-32);
  align-items: baseline;
}

.result-list dt { color: var(--color-dim); }

.result-list dd {
  margin: 0;
  font-size: 20px;
  font-variant-numeric: tabular-nums;
}

.result-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: var(--space-24);
}

.result-table th,
.result-table td {
  text-align: left;
  padding: var(--space-8) var(--space-12);
  border-bottom: 1px solid var(--color-line);
  vertical-align: top;
}

.result-table th {
  font-size: var(--size-small);
  color: var(--color-dim);
  text-transform: uppercase;
  letter-spacing: .06em;
  white-space: nowrap;
}

.result-table td:first-child {
  font-variant-numeric: tabular-nums;
  color: var(--color-dim);
  width: 1%;
  white-space: nowrap;
}

/* Der Balken am Zeilenanfang: bei einer richtigen Antwort in Obsidian, bei
   einer falschen in Ember. Er trägt die Aussage nicht allein — die letzte
   Spalte sagt in Worten, woran es lag. */
.result-table tr.is-ok td:first-child { border-left: 3px solid var(--color-obsidian); }

.result-table tr.is-bad td:first-child { border-left: 3px solid var(--color-ember); }

.result-table tr.is-bad { background: var(--color-chalk); }

/* Eine unbearbeitete Aufgabe ist keine falsche: kein Balken, keine Fläche, die
   Zeile tritt zurück. */
.result-table tr.is-offen td { color: var(--color-dim); }

.result-note { margin: 0 0 var(--space-12); }

.result-seed { margin: 0; font-size: var(--size-small); color: var(--color-dim); }

.result-card + .notice-actions { margin-top: var(--space-24); }


/* ---------- Die acht Bildschirmblöcke (reaction.js, blocks/b11…b18) --------
 *
 * Diese Klassen standen in den Blöcken, Regeln dazu nirgends: von 26 hatten
 * zwei eine. Auf dem Schirm hieß das Beschriftungen ohne Ordnung, Felder ohne
 * Fläche und bei Block 14 zwei Aufgaben untereinander, die nebeneinander
 * gehören. Es betraf beide Wege gleich, den Durchgang wie die Übung.
 *
 * Der Grund bleibt ruhig: Ein Reiz ist eine Form auf leerer Fläche, und alles
 * um ihn herum, das sich bewegt oder blinkt, misst mit. Deshalb keine
 * Umrandungen um das Reizfeld, keine Farbe außer der des Reizes selbst, und
 * feste Maße — springt das Feld zwischen zwei Reizen, springt der Blick mit,
 * und die gemessene Zeit enthält das Suchen. */

/* Die drei Sekunden zwischen „Jetzt beginnen" und dem ersten Reiz. Dieselbe
   Fläche wie die Bühne, damit der Schirm nicht springt, wenn der Zähler geht. */
.rx-countdown {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-16);
  width: 100%;
  min-height: 320px;
}

/* Die Bühne: Beschriftung, Feld, Tastenlegende. */
.rx-stage { display: flex; flex-direction: column; align-items: center;
            gap: var(--space-12); width: 100%; }

.rx-stage-label { font-size: var(--size-small); color: var(--color-dim);
                  text-transform: uppercase; letter-spacing: .06em; }

/* 320 Punkte, damit ein Reiz in der Mitte bleibt, wo er beim letzten Mal war.
   Der Wert stand bisher nur im Kommentar der Sitzregel weiter unten. */
.rx-field {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-16);
  width: 100%;
  min-height: 320px;
}

.rx-hinweis { font-size: var(--size-small); color: var(--color-dim);
              text-align: center; margin: 0; }

/* ---- Block 11: zwei Orte, links und rechts ---- */

/* Feste Hälften, nicht nach Inhalt: Die leere Seite muss genauso breit sein
   wie die belegte, sonst verrät die Breite den Ort vor dem Reiz. */
/* ---- das Umkehrsignal von Block 11 ---------------------------------------
 *
 * „Für dieses eine Zeichen gilt die andere Regel." Bis zum 03.09.2026 sagte das
 * ein Ton von 90 ms und sonst nichts, und wo kein Ton lief, fielen diese Reize
 * aus dem Block heraus. Jetzt sagt es ein Rahmen und ein Wort, und der Ton legt
 * sich darüber, wo das Gerät ihn kann (blocks/b11.js).
 *
 * DER KASTEN STEHT IMMER DA. Rahmen und Wort sind auf jedem Zeichen gezeichnet
 * und nur beim Umkehrreiz sichtbar — entstünde der Kasten erst, verschöbe sich
 * die Zeichenzeile um seine Höhe, und diese Bewegung wäre ein zweites Signal,
 * das vor dem Zeichen käme. `visibility` und ein durchsichtiger Rahmen halten
 * den Platz, wie es `.tr-flanken` aus demselben Grund tut.
 *
 * KEINE FARBE ALS TRÄGER (ARCHITECTURE.md §9), und nichts links oder rechts und
 * nichts Rundes oder Eckiges: Genau über diese zwei Merkmale entscheiden die
 * beiden Regeln des Blocks, und ein Signal, das sie berührt, misst mit. */
.rx-umkehr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-8);
  width: 100%;
  max-width: 560px;
  padding: var(--space-12);
  border: 4px solid transparent;
  border-radius: var(--radius-md);
}

.rx-umkehr.ist-an { border-color: var(--color-obsidian); }

.rx-umkehr-wort {
  margin: 0;
  visibility: hidden;
  font-size: var(--size-small);
  font-weight: 500;
  letter-spacing: .08em;
  color: var(--color-obsidian);
}

.rx-umkehr.ist-an .rx-umkehr-wort { visibility: visible; }

.rx-row { display: flex; width: 100%; max-width: 560px; }

.rx-slot { flex: 1 1 0; display: flex; align-items: center; justify-content: center;
           min-height: 96px; }

.rx-ansage { text-align: center; }

.rx-ansage-h { font-size: 24px; margin: 0 0 var(--space-8); }

.rx-ansage-b { color: var(--color-dim); margin: 0; }

/* ---- Block 12: der Reiz zwischen vier Flanken ---- */
.rx-flanke { display: flex; align-items: center; justify-content: center;
             gap: var(--space-16); }

/* ---- Block 13: das Zählwerk ist der Reiz ---- */

/* Tabellarische Ziffern: Eine Anzeige, die beim Zählen die Breite ändert,
   bewegt sich — und Bewegung ist hier ein Reiz. */
.rx-zaehlwerk {
  font-family: var(--font-system);
  font-size: 96px;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* ---- Block 14: zwei Aufgaben nebeneinander ---- */

/* Nebeneinander ist die Aufgabe: Die linke Arbeit läuft weiter, während die
   rechte Vorrang verlangt. Untereinander wäre das ein Wechsel und keine
   Doppelbelastung. */
.rx-panes { display: grid; grid-template-columns: 1fr 1fr;
            gap: var(--space-32); width: 100%; max-width: 720px; }

.rx-pane { display: flex; flex-direction: column; align-items: center;
           gap: var(--space-12); min-height: 200px;
           border-left: 2px solid var(--color-line); padding: 0 var(--space-16); }

.rx-pane:first-child { border-left: none; }

.rx-pane-label { font-size: var(--size-small); color: var(--color-dim);
                 text-transform: uppercase; letter-spacing: .06em; margin: 0; }

.rx-ruhig { color: var(--color-dim); margin: 0; }

/* ---- Block 15: die Menge, die man sich merkt ---- */
.rx-merken-h { font-size: 24px; margin: 0; }

.rx-merkmenge { display: flex; align-items: center; justify-content: center;
                gap: var(--space-20); }

/* Das einzelne Zeichen — in der Menge, die man sich einprägt, und einzeln beim
   Abruf. Beide Male dieselbe Größe: Ein Zeichen, das beim Abruf anders aussieht
   als beim Einprägen, prüft das Wiedererkennen der Darstellung mit. */
.rx-zeichen { font-family: var(--font-system); font-size: 44px; line-height: 1;
              font-variant-numeric: tabular-nums; }

/* ---- Block 16: die Schlüsseltabelle ---- */
.rx-schluessel { display: flex; flex-wrap: wrap; align-items: flex-start;
                 justify-content: center; gap: var(--space-16); }

.rx-schluessel-zelle {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-8);
  background: var(--color-limestone);
  border-radius: var(--radius-sm);
}

.rx-schluessel-ziffer { font-family: var(--font-system); font-size: 20px; }

/* ---- Block 17: das Brett ---- */

/* `position: relative` ist hier keine Gestaltung, sondern die Voraussetzung:
   b17 setzt seine Felder mit `left`/`top` in Brettkoordinaten. Ohne einen
   positionierten Vorfahren rechnen sie gegen die Seite und landen irgendwo. */
.rx-brett {
  position: relative;
  background: var(--color-limestone);
  border-radius: var(--radius-sm);
  max-width: 100%;
}

.rx-feld {
  position: absolute;
  border: 2px solid var(--color-obsidian);
  border-radius: 50%;
  background: var(--color-chalk);
  cursor: pointer;
  padding: 0;
}

/* Ein abgeschaltetes Feld ist hier kein abgeschaltetes Bedienelement, sondern
   eine Stelle, die gerade nicht dran ist — sie muss SICHTBAR bleiben. Block 17
   schaltet alle neun Felder ab, während er die Folge zeigt: Wer sie dann nicht
   sieht, sieht ein leeres Brett und kann sich keine Reihenfolge merken.
   `--color-line` auf dem Brett misst 1.54:1, also nichts; `--color-dim` misst
   6.76:1. Zurücktreten heißt dünner, nicht unsichtbar. */
.rx-feld:disabled { border-width: 1.5px; border-color: var(--color-dim);
                    background: var(--color-chalk); cursor: default; }

/* b17 setzt `is-an`, solange ein Feld leuchtet, und `is-geklickt` für 180 ms
   nach dem Treffer. Beide sind Zustände des Reizes, nicht des Bedienelements. */
.rx-feld.is-an { background: var(--color-ember); border-color: var(--color-ember); }

.rx-feld.is-geklickt { background: var(--color-obsidian); border-color: var(--color-obsidian); }

/* ---- Block 18: Bild wählen, Rückmeldung ---- */
.rx-wahl { display: flex; flex-wrap: wrap; align-items: center;
           justify-content: center; gap: var(--space-16); }

.rx-wahl-feld {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 96px;
  height: 96px;
  padding: var(--space-8);
  border: 2px solid var(--color-line);
  border-radius: var(--radius-sm);
  background: var(--color-chalk);
  cursor: pointer;
}

.rx-wahl-feld:hover { border-color: var(--color-obsidian); }

/* b18 markiert die getroffene Wahl, bis die Rückmeldung steht. */
.rx-wahl-feld.is-gewaehlt { border-color: var(--color-obsidian); border-width: 3px; }

/* Beide Rückmeldungen stehen in Obsidian, und was sie unterscheidet, ist das
   Wort. Ember als Textfarbe wäre hier falsch gemessen: auf Weiß sind es
   3.34:1, und 18px brauchen 4.5. Die Farbe trägt deshalb den Strich, nicht die
   Schrift — und nichts hängt allein an ihr. */
.rx-rueckmeldung { min-height: 1.5em; margin: 0; font-size: 18px;
                   color: var(--color-obsidian); }

.rx-rueckmeldung.is-richtig,
.rx-rueckmeldung.is-falsch { padding-bottom: 2px; border-bottom: 2px solid; }

.rx-rueckmeldung.is-richtig { border-color: var(--color-obsidian); }

.rx-rueckmeldung.is-falsch { border-color: var(--color-ember); }


/* ---------- single seat (seat.js) ---------- */

.seat {
  margin-top: var(--space-24);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-sm);
  background: var(--color-chalk);
  overflow: hidden;
}


.seat-head {
  display: flex;
  align-items: center;
  gap: var(--space-16);
  padding: var(--space-16) var(--space-24);
  border-bottom: 1px solid var(--color-line);
  background: var(--color-line);
}

.seat-clock {
  margin-left: auto;
  font-family: var(--font-system);
  font-size: 24px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--color-obsidian);
}


.seat-progress { height: 4px; background: var(--color-line); }

.seat-progress span { display: block; height: 100%; background: var(--color-obsidian); transition: width var(--dur-settle) var(--ease-settle); }


.seat-body { padding: var(--space-32) var(--space-24); }

.seat-stem {
  margin-top: var(--space-8);
  font-family: var(--font-body);
  font-size: 24px;
  font-weight: 500;
  line-height: 1.3;
  color: var(--color-obsidian);
}

.seat-answer { margin-top: var(--space-32); }


/* The block body is the projection's own markup, and its layout rules come from
   the battery's present.css — one stylesheet for block content, not a second one
   that would have to agree with it. Only what `.stage` would otherwise supply is
   set here: the unit every rule scales from, and the light palette. Nine pixels
   put the question at 22 px and the options at 20 px, which is a seat and not a
   beamer. */
.seat-stem > .body {
  --u: 9px;
  /* DECKEND, NICHT DURCHSICHTIG. `--bg` ist am Platz nicht nur der Grund des
     Kastens, sondern die Füllfarbe der Würfelflächen (battery/js/iso.js) und des
     Pfeilkopfs im Kompass. Der Maleralgorithmus zeichnet die Würfel von hinten
     nach vorne und verlässt sich darauf, dass eine vordere Fläche die dahinter
     liegende ZUDECKT. Auf `transparent` deckt sie nichts zu: Bei einer
     gedrungenen Figur schlagen alle verdeckten Kanten durch, und aus zwölf
     Würfeln wird ein Dreiecksgitter, in dem keine Form mehr zu erkennen ist.
     Kreide ist der Grund der Sitzkarte — dieselbe Farbe, die man ohnehin sieht,
     nur jetzt undurchsichtig. Gefunden am 07.09.2026 an Block 3. */
  --bg: var(--color-chalk);
  --fg: var(--color-obsidian);
  --dim: var(--color-obsidian);
  --line: var(--color-line);
  --fill: var(--color-obsidian);
  color: var(--color-obsidian);
  font-family: inherit;
  height: 46vh;
  min-height: 320px;
}

/* Das Regelwerk von Block 10 steht unter der Lage, weil es am Bildschirm sonst
   nirgends steht (bloecke.js). Kleiner als die Handlungen: Es ist die Grundlage,
   nicht die Aufgabe, und es darf die fünf Handlungen nicht überwachsen. */
/* Die Grundlage über einer Rangfolge — bei Block 10 das Regelwerk, das auf
   Papier auf dem Antwortbogen steht (widgets.js). Kleiner als die Handlungen:
   Es ist die Grundlage, nicht die Aufgabe. */
.w-grundlage {
  margin: 0 0 var(--space-12);
  padding: var(--space-8) 0;
  border-top: 1px solid var(--color-line);
  border-bottom: 1px solid var(--color-line);
  font-size: 14px;
  line-height: 1.4;
}

.w-grundlage > summary {
  cursor: pointer;
  color: var(--color-dim);
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.w-grundlage > summary:focus-visible { outline: 2px solid var(--color-ember); outline-offset: 3px; }

.w-grundlage-zeile { margin-top: var(--space-4); }

/* Block 2 bekommt mehr Höhe als die anderen. Sein Raster ist seit dem größeren
   Zeilenabstand 800 statt 600 Einheiten hoch; in einen 46vh flachen Kasten
   gerechnet, schrumpfen die Buchstaben auf die Hälfte, und die Lesbarkeit, die
   der Abstand gebracht hat, nimmt die Verkleinerung wieder weg. Nach oben
   begrenzt, damit die Fußzeile mit dem Antwortknopf auf dem Schirm bleibt. */
/* 60vh rechnete an der Fensterhoehe und nicht an dem, was uebrig bleibt: Auf
   1024 x 700 stand der Sitz 48 px unter der Falz, auf 1100 x 760 zwoelf. Alles
   um das Raster herum — Sitzkopf, Polsterung, Ansage, Antwortzeile — misst
   gemessene 328 px; 340 ist dieselbe Zahl mit etwas Luft und die Stelle zum
   Nachstellen, falls sich am Sitzkopf etwas aendert. Nach unten begrenzt, damit
   das Raster auf einem flachen Schirm nicht unlesbar wird, nach oben wie
   bisher. */
.seat-stem > .body.b2 { height: clamp(280px, calc(100vh - 340px), 560px); }

/* FÜNF FIGUREN NEBENEINANDER BRAUCHEN HÖHE, NICHT BREITE.
 *
 * Eine Figurenreihe (Block 3, 6, 7) stellt fünf Optionen in eine Zeile. Jede
 * bekam den Buchstaben ÜBER die Figur gesetzt, und in einem 110 Punkte hohen
 * Kasten nahm er samt Polsterung die Hälfte: Für die Würfelfigur blieben 53
 * Punkte. Auf so wenig ist eine Drehung nicht mehr von einer Spiegelung zu
 * unterscheiden, und genau das ist die Aufgabe.
 *
 * Der Buchstabe steht deshalb IN der Ecke statt darüber. Er bleibt gleich groß
 * und an derselben Stelle wie auf der Projektion, nur nimmt er der Figur keine
 * Höhe mehr weg. Dazu etwas mehr Kastenhöhe, soweit der kleinste zugelassene
 * Schirm (1024 × 700) sie hergibt. */
.seat-stem > .body.item-row.has-fig { height: clamp(320px, calc(100vh - 330px), 520px); }

/* In Punkten und nicht in `--u`: Diese Marke gehört der Projektion und steht
   nicht in `:root`, tools/test-kontrast.sh besteht zu Recht darauf. Fünf Punkte
   sind die halbe Einheit am Platz. */
.seat-stem > .body.item-row.has-fig .opt { position: relative; padding-top: 5px; }

.seat-stem > .body.item-row.has-fig .opt > .opt-l {
  position: absolute;
  top: 5px;
  left: 5px;
  background: var(--color-chalk);
}

.seat-stem > .body.item-row.has-fig .opt > .opt-c { flex: 1 1 auto; height: 100%; }


/* REAKTIVE BLÖCKE BRINGEN IHRE EIGENE HÖHE MIT.
 *
 * Die feste Höhe oben ist für die Blockkörper der Batterie gedacht: Deren SVG
 * füllt den Kasten, egal wie hoch er ist. Ein reaktiver Block hat kein solches
 * Bild. Er baut eine Bühne aus festen Maßen — das Reizfeld ist mindestens 320
 * px hoch, das Brett von Block 17 ist 400 px hoch —, und die passen nicht in
 * 46vh. Der Kasten schnitt sie ab, und `.seat` verdeckt den Rest: bei Block 17
 * fehlten die unteren Felder, bei Block 16 die halbe Schlüsseltabelle. Gemessen
 * bei 1440x1000: alle acht reaktiven Blöcke abgeschnitten, zwischen 28 und 148
 * px.
 *
 * Der Grund für die feste Höhe fällt hier ohnehin weg. Sie soll verhindern,
 * dass der Reiz zwischen zwei Aufgaben springt — das erledigt bei den reaktiven
 * Blöcken die Bühne selbst, deren Feld seine Höhe nicht ändert.
 *
 * Dieselbe Ausnahme wie bei den Anleitungsschirmen darüber, und aus demselben
 * Grund über `:has()`: ohne in die Batteriekopie zu schreiben. */
.seat-stem > .body:has(.rx-stage) {
  height: auto;
  min-height: 0;
  overflow: visible;
}
@media (max-width: 900px) {

  .seat-stem > .body { --u: 7px; height: auto; min-height: 260px; }
}


/* ---- Anleitungsschirme am Einzelplatz -----------------------------------
 *
 * Ein Aufgabenschirm braucht die feste Höhe: Das Feld darf zwischen zwei
 * Aufgaben nicht springen, sonst wandert die Aufgabe unter dem Blick weg. Ein
 * ANLEITUNGSSCHIRM ist Fließtext, und der wurde damit abgeschnitten — der
 * Kasten war fest, `.body` schneidet ab, und die Seite konnte nicht scrollen.
 *
 * Zwei Dinge dagegen, und das zweite ist die eigentliche Ursache:
 *
 * 1. Der Kasten wächst mit dem Text, statt ihn abzuschneiden.
 * 2. Der Text bekommt Lesegrößen. present.css setzt Anleitungen in
 *    Projektionsgröße — 24 px, Gewicht 650, gedacht für einen Beamer aus zehn
 *    Metern. Am eigenen Bildschirm sitzt man einen halben Meter davor: Bei
 *    Lesegröße passen dieselben fünf Absätze auf den Schirm, und sie lesen
 *    sich besser.
 *
 * Chrome-only ist hier ausdrücklich in Ordnung: seat.js sagt das über sich
 * selbst, und `:has()` ist der einzige Weg, einen Anleitungsschirm von einem
 * Aufgabenschirm zu unterscheiden, ohne in die Batteriekopie zu schreiben. */
.seat-stem:has(.instr),
.seat-stem:has(.notice) {
  font-size: 17px;
  /* 500, nicht 400: DM Sans liegt nur in diesem einen Schnitt vor, Regular
     waere synthetisch gemagert (CLAUDE.md). Die Lesegroesse macht der
     Schriftgrad, nicht das Gewicht. */
  font-weight: 500;
  line-height: 1.55;
  letter-spacing: 0;
}

/* `.instr` sind die Anleitungen der zehn materialgestützten Blöcke. `.notice`
   sind die Schirme, die ein reaktiver Block selbst mitbringt: Lichthinweis,
   Geräteprüfung, Anleitung (reaction.js). Beide sind Fließtext in einem Kasten,
   der für Aufgaben gebaut ist. Die B11-Anleitung lief zuletzt 522 px über. */
.seat-stem > .body:has(.instr),
.seat-stem > .body:has(.notice) {
  height: auto;
  min-height: 0;
  overflow: visible;
  display: block;
  padding: 0;
}

/* Fließtext liest sich linksbündig. Die zentrierte Ausrichtung kommt aus
   present.css und ist für die Projektion richtig, wo einzelne Zeilen mittig in
   einem großen Feld stehen — für fünf Absätze am eigenen Bildschirm nicht.
   Der obere Abstand des Kastens entfällt hier: Der Schirm beginnt bereits mit
   ihm, ein Abstand nach oben ließe ihn tiefer rutschen als nötig. */
.seat-stem > .body:has(.instr),
.seat-stem > .body:has(.notice) { text-align: left; }

.seat-stem .notice { margin-top: 0; }

.seat-stem .rx-tasten { text-align: left; }


.seat-stem .instr { max-width: 62ch; }

.seat-stem .instr h1 {
  font-family: var(--font-body);
  font-size: 30px;
  font-weight: 500;
  line-height: 1.1;
  margin: 0 0 var(--space-16);
}

.seat-stem .instr-body {
  font-size: 17px;
  line-height: 1.55;
  color: var(--color-obsidian);
}


.seat-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-16);
  padding: var(--space-16) var(--space-24);
  border-top: 1px solid var(--color-line);
  background: var(--color-line);
}

.seat-hint { font-size: 13px; color: var(--color-dim); }

.seat-actions { margin-left: auto; display: flex; gap: var(--space-12); }

/* Not opacity. A dark button faded to 40 % over a light page ends up as white
   on light grey, about 2.3:1 — unreadable, and this is an instrument people use
   under time pressure. The disabled state gets its own real colours instead:
   #646464 on #f8f9fa is 5.6:1, past AA, and the missing fill already says the
   button is inactive. */
.seat-actions .btn:disabled {
  background: var(--color-line);
  color: var(--color-dim);
  border-color: var(--color-line);
  cursor: not-allowed;
}


/* ---------- profile ----------
 *
 * Das Profil ist die Seite, für die jemand zwei Stunden gearbeitet hat. Sie
 * war bis zum 03.09.2026 eine Folge unformatierter Absätze: `.pf-teil`,
 * `.pf-teil-h`, `.pf-teil-note` und `.pf-detail` hatten überhaupt keine Regel,
 * also standen die Abschnittsüberschriften in Fließtextgröße mitten im
 * Datenstrom, und die Rohwertspalte mit `white-space: nowrap` schob die langen
 * Reaktionszeilen rechts aus dem Layout heraus.
 *
 * DREI ENTSCHEIDUNGEN, damit es sich nicht wiederholt:
 *
 * 1. JEDER ABSCHNITT IST EINE KARTE. Limestone auf Pumice, Radius 40 — die
 *    Ebene entsteht durch Farbe, wie überall in diesem System, nicht durch
 *    einen Schatten. Zwei Ebenen und nicht mehr: Was im Aufklapper steht, ist
 *    durch eine Haarlinie getrennt und nicht noch einmal eingerahmt.
 *
 * 2. DAS RASTER HAT VIER SPALTEN, und keine davon ist `auto`. Was daneben
 *    gehört — Herkunft, Treffer, Einordnung — steht in einer eigenen Zeile über
 *    die volle Breite und darf umbrechen. Damit stehen die Prozentzahlen aller
 *    Zeilen auf derselben Kante; vorher wanderte die Spalte mit der Länge des
 *    Rohwerttexts, und nichts fluchtete.
 *
 * 3. NICHTS UNTERSCHEIDET SICH ALLEIN DURCH FARBE (ARCHITECTURE.md §9). Ein
 *    nicht erhobener Bereich ist blasser UND trägt seinen Satz; der Balken
 *    trägt immer seine Zahl daneben.
 */

.pf { margin-top: var(--space-24); }

.pf-kopf { margin-bottom: var(--space-16); }

/* Die Karte eines Abschnitts. Nur direkte Kinder von `.pf`: Was im Aufklapper
   liegt, gehört zu dessen Karte und bekommt keine zweite. */
.pf > .pf-zaehlung,
.pf > .pf-teil,
.pf > .pf-detail {
  background: var(--color-limestone);
  border-radius: var(--radius-card);
  padding: var(--space-32);
  margin: 0 0 var(--space-16);
}

/* Karten haben ihren Abstand aussen. Der letzte Absatz darin bringt sonst
   seinen Fließtextabstand mit, und unter dem Knopf klafft eine Handbreit. */
.pf > .pf-zaehlung > :last-child,
.pf > .pf-teil > :last-child,
.pf-ref-gesperrt > :last-child { margin-bottom: 0; }

/* Die Abschnittsüberschrift. Sie ist der vierte Rang, den die Skala oben nicht
   hat (dort endet sie bei 26px); hier trägt ihn die Fläche, auf der sie steht.
   20px ist dieselbe Größe, die `.notice h2` in der Anwendung schon führt. */
.pf-teil-h {
  margin: 0;
  font-size: 20px;
  line-height: 1.25;
  color: var(--color-obsidian);
}

.pf-teil-note {
  margin: var(--space-8) 0 var(--space-20);
  max-width: 68ch;
  font-size: var(--size-small);
  line-height: 1.5;
  color: var(--color-dim);
}

.pf-liste { display: grid; }

/* Vier Spalten, feste Kanten: Nummer, Titel, Balken, Zahl. Alles Weitere steht
   darunter in einer eigenen Rasterzeile. */
.pf-row {
  display: grid;
  grid-template-columns: 2rem minmax(0, 1fr) minmax(96px, 1.2fr) 5rem;
  align-items: center;
  column-gap: var(--space-16);
  row-gap: var(--space-4);
  padding: var(--space-12) 0;
  border-bottom: 1px solid var(--color-line);
}

.pf-liste > .pf-row:last-child { border-bottom: 0; padding-bottom: 0; }

/* Ein Bereich hat keine Blocknummer. Ohne diese Regel bliebe seine leere Zelle
   stehen und die sechs Bereiche stünden um 2rem eingerückt neben nichts. */
.pf-row.is-domaene { grid-template-columns: minmax(0, 1fr) minmax(96px, 1.2fr) 5rem; }
.pf-row.is-domaene .pf-nr { display: none; }

.pf-nr {
  font-family: var(--font-system);
  font-size: 12px; color: var(--color-dim); text-align: right;
  font-variant-numeric: tabular-nums;
}

.pf-titel { font-size: 15px; color: var(--color-obsidian); min-width: 0; }

/* Die sechs Bereiche sind die Aussage, die achtzehn Blöcke sind der Beleg. */
.pf-row.is-domaene .pf-titel { font-size: 17px; }

.pf-bar {
  height: 10px; border-radius: 9999px; background: var(--color-pumice); overflow: hidden;
}

.pf-bar > span { display: block; height: 100%; background: var(--color-obsidian);
                 border-radius: 9999px; }

.pf-row.is-domaene .pf-bar { height: 14px; }

.pf-wert {
  font-family: var(--font-system);
  font-size: 14px; font-weight: 500; text-align: right; color: var(--color-obsidian);
  font-variant-numeric: tabular-nums;
}

.pf-row.is-domaene .pf-wert { font-size: 17px; }

.pf-wert-zusatz {
  display: block;
  font-size: 11px;
  font-weight: 500;
  color: var(--color-dim);
}

/* Nicht erhoben ist kein schwaches Ergebnis. Die Zeile tritt zurück, sagt es
   aber auch — die Blässe allein trüge die Auskunft nicht. */
.pf-row.is-leer .pf-titel,
.pf-row.is-leer .pf-wert { color: var(--color-dim); }

/* Was neben der Zahl stand und sie verschob: Herkunft, Treffer, Einordnung.
   Alle drei stehen jetzt unter dem Titel über die volle Restbreite und dürfen
   umbrechen. `nowrap` hat die Reaktionszeilen aus dem Layout geschoben. */
.pf-roh,
.pf-treffer {
  grid-column: 2 / -1;
  font-family: var(--font-system);
  font-size: 12px;
  color: var(--color-dim);
  white-space: normal;
}

.pf-row.is-domaene .pf-roh { grid-column: 1 / -1; }

.pf-reaktiv-aus { grid-column: 2 / -1; font-size: 13px; color: var(--color-dim); }

/* Die Einordnung im Vergleichsbestand. Eigene Zeile unter dem Wert, über die
   ganze Breite: Sie gehört zur Zeile darüber und darf nicht in die enge
   Rohwertspalte gedrängt werden, in der sie umbricht. Die Spanne steht ebenso
   groß da wie die Zahl selbst, denn sie ist bei zwanzig Vergleichswerten die
   ehrlichere Hälfte der Aussage. */
.pf-ref {
  grid-column: 2 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-8);
  margin-top: 2px;
}

.pf-ref-kopf { font-size: 13px; font-weight: 500; color: var(--color-obsidian); }

.pf-ref-spanne, .pf-ref-bestand { font-size: 12px; color: var(--color-dim); }

.pf-ref-bestand::before { content: '· '; }

/* Der Hinweis, wenn die Einordnung nicht im Zugang steht. Eine Karte mit
   Rahmen statt Fläche: Sie ist ein Hinweis und kein Befund. */
.pf-ref-gesperrt { padding: var(--space-32); margin: 0 0 var(--space-16); }
.pf-ref-gesperrt h3 { font-size: 20px; margin: 0 0 var(--space-8); }
.pf-ref-gesperrt .notice-actions { margin-top: var(--space-20); }

.pf-h { margin: var(--space-32) 0 var(--space-12); font-size: 20px; }


/* ---- die drei Zahlen und der Vergleichssatz ------------------------------
   Sie stehen vor allem anderen und müssen aus zwei Metern lesbar sein. Farbe
   trägt nichts allein (ARCHITECTURE.md §9): Jede Zahl hat ihr Wort daneben,
   und der Anteil steht als Prozentangabe da, nicht als Balkenlänge. */
.pf-zahlen {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-12);
  margin-top: var(--space-16);
}

/* Rahmenstärke gleich für alle drei. Sie vorher nur bei „richtig" auf 2px zu
   setzen hat die Kachel um einen Punkt gegen ihre Nachbarn versetzt. */
.pf-zahl {
  border: 2px solid var(--color-line);
  border-radius: var(--radius-md);
  padding: var(--space-20);
  display: grid;
  gap: 2px;
  background: var(--color-chalk);
}

.pf-zahl.is-gut { border-color: var(--color-obsidian); }

/* Bebas trägt ab 48px, DM Sans darunter (ARCHITECTURE.md §27). Diese drei
   Zahlen sind die Stelle im Profil, an der die Anzeigeschrift hingehört. */
.pf-zahl-wert {
  font-family: var(--font-display);
  font-size: 48px;
  font-weight: 400;
  letter-spacing: var(--track-display);
  line-height: .95;
  color: var(--color-obsidian);
}

.pf-zahl-name { font-size: var(--size-small); color: var(--color-obsidian); }

.pf-zahl-anteil { font-size: 13px; color: var(--color-dim); font-variant-numeric: tabular-nums; }


/* ---- der Aufklapper mit den einzelnen Blöcken ----------------------------
   Ein natives `details`: Es nimmt den Fokus, öffnet mit Enter und Leertaste und
   druckt geöffnet. Der voreingestellte Fokusrahmen legte einen schwarzen Kasten
   über die ganze Breite; er bleibt, aber nur bei Tastaturbedienung. */
.pf-detail > summary {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  list-style: none;
  cursor: pointer;
  font-size: 20px;
  color: var(--color-obsidian);
  border-radius: var(--radius-pill);
  margin: calc(var(--space-8) * -1) calc(var(--space-12) * -1);
  padding: var(--space-8) var(--space-12);
}

.pf-detail > summary::-webkit-details-marker { display: none; }

/* Das Zeichen ist ein Winkel aus Rand, keine Schrift und kein Symbolsatz: Es
   dreht sich beim Öffnen und braucht dafür keine zweite Datei. */
.pf-detail > summary::before {
  content: '';
  flex: none;
  width: 9px; height: 9px;
  border-right: 2px solid var(--color-obsidian);
  border-bottom: 2px solid var(--color-obsidian);
  transform: rotate(-45deg) translate(-2px, -2px);
  transition: transform var(--dur-settle) var(--ease-settle);
}

.pf-detail[open] > summary::before { transform: rotate(45deg) translate(-2px, -2px); }

.pf-detail > summary:focus { outline: none; }
.pf-detail > summary:focus-visible { outline: 2px solid var(--color-obsidian); outline-offset: 2px; }
.pf-detail > summary:hover { background: var(--color-pumice); }

/* Im Aufklapper trennt eine Haarlinie, keine zweite Karte. */
.pf-detail .pf-teil,
.pf-detail .pf-h {
  margin-top: var(--space-32);
  padding-top: var(--space-24);
  border-top: 1px solid var(--color-line);
}

.pf-detail .pf-h { margin-bottom: var(--space-12); }


/* ---- die Verlaufskurve ---------------------------------------------------
   Zahlen am Bild, Zahlen darunter als Text. Die Linie ist die Abkürzung, nicht
   die Quelle. */
/* Der Abschnitt „Durchgänge und Verlauf". Zwei gleich breite Spalten waren
   falsch verteilt: Links stehen drei kurze Zeilen, rechts ein Diagramm mit
   Tabelle. Ab 1040 Punkten bekommt der Verlauf zwei Drittel. */
/* `min-width: 0` an den Spalten, sonst nutzt der Rollrahmen um die Tabelle
   nichts: Ein Rasterfeld ist von sich aus so breit wie sein breitestes Kind, und
   die Verlaufstabelle hat eine Mindestbreite von 30 rem. Auf dem Telefon schob
   sie die ganze Seite 114 Punkte nach rechts. */
.pf-spalten > * { min-width: 0; }

@media (min-width: 1040px) {
  .pf-spalten { grid-template-columns: minmax(0, 1fr) minmax(0, 1.7fr); gap: var(--space-card); }
}

/* ---- Das Beispiel vor einer Übung ---------------------------------------
 *
 * Eine Karte in Limestone, wie jede andere Karte im System. Die richtige Zeile
 * trägt zwei Merkmale, nicht nur Farbe: den Ember-Streifen an der Kante und das
 * Wort „richtig" in der Spalte daneben. */
.ue-bsp {
  margin-top: var(--space-card);
  padding: var(--space-card);
  background: var(--color-limestone);
  border-radius: var(--radius-card);
}

.ue-bsp-h { margin: 0 0 var(--space-16); font-size: var(--size-h3); }

.ue-bsp-stem { margin: 0 0 var(--space-16); max-width: 62ch; }

.ue-bsp-figur { display: flex; justify-content: center; margin-bottom: var(--space-16); }

.ue-bsp-opts { list-style: none; margin: 0; padding: 0; }

.ue-bsp-opt {
  display: grid;
  grid-template-columns: 2.2rem minmax(0, 1fr) minmax(0, 1.4fr);
  gap: var(--space-12);
  align-items: center;
  padding: var(--space-8) var(--space-12);
  border-left: 3px solid transparent;
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

.ue-bsp-opt + .ue-bsp-opt { margin-top: 2px; }

.ue-bsp-opt.is-richtig { background: var(--color-chalk); border-left-color: var(--color-ember); }

.ue-bsp-opt-l { font-weight: 500; }

.ue-bsp-opt-wert { font-variant-numeric: tabular-nums; }

.ue-bsp-opt-figur { display: flex; }

/* Figurenzeilen: Die Zeichnung braucht nur ihre eigene Breite, die Begründung
   den Rest. Mit zwei gleich breiten Spalten stand die Begründung weit rechts
   und die Figur allein in der Mitte. */
.ue-bsp-opt:has(.ue-bsp-opt-figur) { grid-template-columns: 2.2rem auto minmax(0, 1fr); }

/* Das Raster von Block 2 im Beispiel: es bringt seine eigene Größe mit und
   bekommt hier nur einen Deckel, damit es die Karte nicht sprengt. */
.ue-bsp-raster {
  margin: var(--space-16) 0;
  padding: var(--space-16);
  background: var(--color-chalk);
  border-radius: var(--radius-md);
  overflow-x: auto;
}

/* Das Raster kommt aus der Projektion und rechnet seine Größe aus `--u` und der
   Höhe des Kastens. Beides muss hier stehen, sonst fällt es auf null zusammen. */
.ue-bsp-raster .body {
  --u: 7px;
  --bg: var(--color-chalk);
  --fg: var(--color-obsidian);
  --dim: var(--color-obsidian);
  --line: var(--color-line);
  --fill: var(--color-obsidian);
  height: 210px;
  min-height: 0;
  color: var(--color-obsidian);
  font-family: inherit;
}

/* Die Zeichen einer Folge stehen im Beispiel als einzelne Plättchen: Sie
   erscheinen im Durchgang nacheinander, und aneinandergeschoben läsen sich vier
   Zeichen wie ein Wort. */
.ue-bsp .series-terms { display: flex; flex-wrap: wrap; gap: var(--space-8); margin: 0 0 var(--space-8); }

.ue-bsp .series-term {
  min-width: 2.4rem;
  padding: var(--space-8) var(--space-12);
  background: var(--color-chalk);
  border-radius: var(--radius-md);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.ue-bsp .series-term.is-open { color: var(--color-dim); }

.ue-bsp-frage {
  margin: 0 0 var(--space-16);
  padding: var(--space-12) var(--space-16);
  background: var(--color-chalk);
  border-radius: var(--radius-md);
}

.ue-bsp-opt-grund { font-size: var(--size-small); color: var(--color-dim); }

.ue-bsp-opt.is-richtig .ue-bsp-opt-grund { color: var(--color-obsidian); }

.ue-bsp-rang { list-style: none; margin: 0; padding: 0; }

.ue-bsp-rang-zeile {
  display: grid;
  grid-template-columns: 2.2rem minmax(0, 1fr);
  gap: var(--space-12);
  align-items: baseline;
  padding: var(--space-8) 0;
}

.ue-bsp-rang-zeile + .ue-bsp-rang-zeile { border-top: 1px solid var(--color-line); }

.ue-bsp-rang-nr { font-weight: 500; font-variant-numeric: tabular-nums; }

.ue-bsp-loesung {
  display: flex;
  align-items: baseline;
  gap: var(--space-12);
  margin: var(--space-16) 0 0;
}

.ue-bsp-loesung-l {
  font-size: var(--size-fine);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--color-dim);
}

.ue-bsp-loesung-w {
  padding: var(--space-4) var(--space-16);
  background: var(--color-chalk);
  border-radius: var(--radius-pill);
  font-size: 20px;
  letter-spacing: .12em;
  font-variant-numeric: tabular-nums;
}

.ue-bsp-tasten {
  margin: 0;
  padding: var(--space-12) var(--space-16);
  background: var(--color-chalk);
  border-radius: var(--radius-md);
}

.ue-bsp-note {
  margin: var(--space-16) 0 0;
  max-width: 68ch;
  font-size: var(--size-small);
  line-height: 1.5;
  color: var(--color-dim);
}


/* ---- Die Liste der Durchgänge -------------------------------------------
 *
 * Eine Zeile ist ein Knopf, und die Zeile mit dem Ember-Streifen ist die, deren
 * Profil unten steht. Vorher stand hier der `check`-Baustein der Prüfseiten:
 * oranges Quadrat, Häkchen und eine Spalte „Zustand" — drei Zeichen für
 * „bestanden", die bei einem Durchgang nichts bedeuten. */
.pf-laeufe { list-style: none; margin: var(--space-item) 0 0; padding: 0; }

.pf-lauf:not(:last-child) { border-bottom: 1px solid var(--color-line); }

.pf-lauf-knopf {
  display: grid;
  gap: 2px;
  width: 100%;
  padding: var(--space-12) var(--space-16);
  border: 0;
  border-left: 3px solid transparent;
  /* Links eckig, rechts rund: Der Ember-Streifen der laufenden Zeile soll eine
     gerade Kante sein und keine gebogene Sichel. */
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  background: transparent;
  font-family: inherit;
  color: var(--color-obsidian);
  text-align: left;
  cursor: pointer;
}

.pf-lauf-knopf:hover { background: var(--color-limestone); }

.pf-lauf-knopf:focus-visible { outline: 2px solid var(--color-ember); outline-offset: 2px; }

/* Der laufende Durchgang. Zwei Merkmale, nicht nur Farbe (ARCHITECTURE.md §9):
   der Streifen an der Kante und die hellere Fläche. */
.pf-lauf-knopf[aria-current="true"] {
  background: var(--color-limestone);
  border-left-color: var(--color-ember);
}

.pf-lauf-kopf { display: flex; align-items: baseline; gap: var(--space-8); flex-wrap: wrap; }

.pf-lauf-datum { font-size: 17px; font-variant-numeric: tabular-nums; }

.pf-lauf-zeit { font-size: var(--size-small); color: var(--color-dim);
                font-variant-numeric: tabular-nums; }

.pf-lauf-meta { font-size: var(--size-fine); color: var(--color-dim); }


.pf-verlauf { margin: var(--space-32) 0; }

.pf-verlauf-kopf {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-16);
  flex-wrap: wrap;
}

.pf-verlauf-achse-text {
  margin: 0;
  font-size: 13px;
  color: var(--color-dim);
}

/* Zwei Knöpfe, einer gedrückt. Der gedrückte trägt seinen Zustand in
   `aria-pressed` und nicht nur in der Farbe (ARCHITECTURE.md §9). */
.pf-schalter { display: flex; gap: var(--space-4); }

.pf-schalter-knopf {
  border: 1px solid var(--color-line);
  background: var(--color-chalk);
  border-radius: var(--radius-pill);
  padding: var(--space-4) var(--space-16);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  color: var(--color-obsidian);
  cursor: pointer;
}

.pf-schalter-knopf[aria-pressed="true"] {
  background: var(--color-obsidian);
  border-color: var(--color-obsidian);
  color: var(--color-chalk);
}

.pf-schalter-knopf:focus-visible { outline: 2px solid var(--color-obsidian); outline-offset: 2px; }

.pf-verlauf-bild {
  width: 100%;
  height: auto;
  max-width: 40rem;
  margin-top: var(--space-12);
  overflow: visible;
}

/* Die Zwischenlinien blasser als 0 % und 100 %: Der Rahmen soll die Grenze
   zeigen, die Zwischenlinien nur den Maßstab. */
.pf-verlauf-netz { stroke: var(--color-line); stroke-width: 1; opacity: .55; }

.pf-verlauf-netz.is-rand { opacity: 1; }

.pf-verlauf-linie { stroke: var(--color-obsidian); stroke-width: 2; }

.pf-verlauf-punkt { fill: var(--color-obsidian); }

.pf-verlauf-punkt.is-allein {
  fill: var(--color-chalk);
  stroke: var(--color-obsidian);
  stroke-width: 2;
}

/* Ohne `text-anchor` in der Regel: Der erste und der letzte Wert setzen ihn
   selbst, damit sie nicht über den Bildrand hinausragen (profil.js). */
.pf-verlauf-wert {
  fill: var(--color-obsidian);
  font-size: 13px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.pf-verlauf-achse { fill: var(--color-dim); font-size: 11px; font-variant-numeric: tabular-nums; }

.pf-verlauf-datum { fill: var(--color-dim); font-size: 11px; font-variant-numeric: tabular-nums; }


.pf-tempo {
  margin: var(--space-12) 0 0;
  font-size: var(--size-small);
  color: var(--color-obsidian);
}


/* Der eine Satz über alle Einordnungen zusammen. Er steht auf eigener Fläche,
   weil er das Einzige auf dieser Seite ist, das über den ganzen Durchgang
   spricht — und weil der Vorbehalt darunter mitreisen muss. */
.pf-vergleich {
  margin: 0 0 var(--space-16);
  background: var(--color-limestone);
  border-left: 4px solid var(--color-obsidian);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  padding: var(--space-20) var(--space-24);
  display: grid;
  gap: var(--space-4);
}


.pf-vergleich-h { font-size: 17px; color: var(--color-obsidian); }

.pf-vergleich-b { font-size: 13px; color: var(--color-dim); line-height: 1.5; max-width: 68ch; }

.pf-scroll { overflow-x: auto; margin-top: var(--space-12); }

.pf-tab { width: 100%; border-collapse: collapse; min-width: 30rem; }

.pf-tab th, .pf-tab td {
  text-align: left; padding: var(--space-12);
  border-bottom: 1px solid var(--color-line); font-size: var(--size-small);
  vertical-align: top;
}

.pf-tab tr:last-child th, .pf-tab tr:last-child td { border-bottom: 0; }

.pf-tab th { font-weight: 500; color: var(--color-obsidian); padding-left: 0; }

.pf-tab-wert {
  font-family: var(--font-system);
  white-space: nowrap; color: var(--color-obsidian);
  font-variant-numeric: tabular-nums;
}

.pf-tab-note { color: var(--color-dim); font-size: 13px; }

/* Die Kopfzeile der Verlaufstabelle: klein, gesperrt, in Dim — sie benennt die
   Spalten und soll die Zahlen darunter nicht überstimmen. */
.pf-tab-kopf {
  font-size: var(--size-fine);
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--color-dim);
  text-align: left;
  white-space: nowrap;
}

.pf-tab-kopf-wert { text-align: right; }

.pf-tab tbody .pf-tab-wert { text-align: right; }

.pf-leer { margin-top: var(--space-12); font-size: 13px; color: var(--color-dim); }

@media (max-width: 760px) {

  .pf > .pf-zaehlung,
  .pf > .pf-teil,
  .pf > .pf-detail,
  .pf-ref-gesperrt { padding: var(--space-20); border-radius: var(--radius-md); }

  .pf-zahlen { grid-template-columns: 1fr; }

  .pf-zahl-wert { font-size: 40px; }

  /* Der Balken geht auf eine eigene Zeile, statt zwischen Titel und Zahl
     zerdrückt zu werden. Er behält seine Zahl daneben — auf schmalem Schirm
     wie auf breitem trägt der Balken das Ergebnis nie allein. */
  .pf-row,
  .pf-row.is-domaene { grid-template-columns: 1.6rem minmax(0, 1fr) 4.5rem; }

  .pf-row.is-domaene { grid-template-columns: minmax(0, 1fr) 4.5rem; }

  /* Alles ausser der Blocknummer fluchtet auf einer Kante. */
  .pf-bar { grid-column: 2 / -1; order: 2; }
  .pf-row.is-domaene .pf-bar { grid-column: 1 / -1; }

  .pf-roh, .pf-treffer, .pf-ref, .pf-reaktiv-aus { order: 3; }

  .pf-ref-bestand { display: none; }
}


/* Format- und Stufenwahl auf start.html.
 *
 * `.demo-card` war nicht gestaltet: Sie trug nur die Regel für den gewählten
 * Zustand, und die setzte `border-color` auf ein Element ohne Rahmen. Auf dem
 * Schirm standen drei Textblöcke ohne Form, und die graue Fläche der gewählten
 * Karte lief in die Angabe des nächsten Formats. Die Karte ist jetzt eine
 * `.card` wie jede andere; hier steht nur, was sie darüber hinaus braucht. */
#st-formate,
#st-stufen {
  display: grid;
  gap: var(--space-16);
}

/* Ohne das schiebt ein langer Text die schmale Spalte seitlich auf. */
.demo-card { min-width: 0; }

/* ---- die Preiskarte -------------------------------------------------------
 * Vier Zugänge in einer Reihe, der billigste vorn. Die Karte ist eine `.card`
 * und bringt nur mit, was darüber hinaus nötig ist: eine Spalte, in der die
 * Merkmalsliste unten steht, egal wie lang die Zeile über dem Preis ist.
 */
.preis-karte {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
  min-width: 0;
}

.preis-karte h3 { margin: 0; }
.preis-karte p { margin: 0; }
/* Die Fußzeile steht unten, nicht die Liste: Die Listen sind verschieden lang,
   und unten ausgerichtet stünden sie auf vier verschiedenen Höhen. */
.preis-karte .fine { margin-top: auto; }

/* Die Zahl trägt die Anzeigenschrift, die Einheit steht klein daneben und
   wandert nicht mit. */
.preis-zahl { display: flex; align-items: baseline; gap: var(--space-8); }

/* Die Stufe, die das Training enthält, sagt es als erste Zeile ihrer Liste —
   und die Zeile ist ausgezeichnet, damit niemand versehentlich zwei Abos
   abschließt (Trainingsauftrag §7). */
.merkmale li.ist-enthalten {
  background: var(--color-sulfur);
  border-radius: var(--radius-pill);
  padding: 3px 14px;
  margin-left: -14px;
}

/* Ohne das Aufzählungsquadrat: Die Pille IST die Auszeichnung, und beides
   nebeneinander liest sich als zwei Marken für eine Sache. */
.merkmale li.ist-enthalten::before { content: none; }
.merkmale li.ist-enthalten { padding-left: 14px; }

.demo-head { margin-bottom: var(--space-16); }

.demo-head h2 { margin-top: var(--space-4); }

.demo-card .fam-stem { color: var(--color-dim); }

.demo-card .btn { margin-top: var(--space-20); }

/* Die gewählte Karte, nicht über Farbe allein: Der Rahmen kommt dazu und die
   Fläche wechselt, damit die Wahl auch ohne Farbunterscheidung zu sehen ist.
   `outline` statt `border`, weil ein Rahmen, der erst beim Wählen entsteht,
   die Karte um zwei Punkte wachsen ließe und die Liste springen. */
.demo-card.is-gewaehlt {
  outline: 2px solid var(--color-obsidian);
  outline-offset: -2px;
  background: var(--color-line);
}

/* ---- 13. the training screen -----------------------------------------------
 * Ein Trainingsspiel läuft auf einem reduzierten Schirm: Restzeit als Balken,
 * Stufe, Punktestand, die Aufgabe. Keine Navigation, kein Fußbereich.
 *
 * DAS TELEFON IST HIER AUSDRÜCKLICH DABEI, anders als bei den Tests
 * (ARCHITECTURE.md §8). Deshalb 44 Punkte Mindestfläche an jeder Schaltfläche,
 * Hochformat, Bedienung ohne Tastatur — und die Taste steht trotzdem auf dem
 * Knopf, weil am Schreibtisch die Hand auf der Tastatur bleibt.
 *
 * KEIN VOLLFLÄCHENWECHSEL. Die Rückmeldung ist ein Streifen unter der Aufgabe,
 * keine Fläche, die aufblitzt: nichts zwischen 3 und 60 Hz, nirgends.
 */

.tr-schirm {
  max-width: 640px;
  margin: 0 auto;
  padding: var(--space-24) 0 var(--space-32);
}


/* ---- die zwei Schirme vor dem Lauf ---------------------------------------
 *
 * BEREIT und ZÄHLUNG. Bis zum 03.09.2026 gab es beide nicht: Der Klick auf
 * „Starten" stellte die erste Aufgabe hin und ließ die Uhr laufen, während
 * niemand wusste, was zu tun war. Beide gelten für alle zwölf Spiele.
 */
.tr-bereit {
  max-width: 640px;
  margin: 0 auto;
  display: grid;
  gap: var(--space-12);
}

.tr-bereit h3 { margin: 0; }
.tr-bereit p  { margin: 0; }

/* Die Gruppenmarke bleibt so breit wie ihr Wort. Im Raster streckte sie sich
   sonst über die ganze Karte, und aus einer Marke wurde ein Farbbalken. */
.tr-bereit .tag { justify-self: start; }

/* Die Anleitung ist der Grund, warum dieser Schirm steht. Sie bekommt
   Lesegröße und ein Maß, keine Kleinschrift. */
.tr-bereit-text {
  font-size: var(--size-body);
  line-height: var(--line-body);
  max-width: 60ch;
}

.tr-bereit-meta { margin: 0 !important; }

.tr-bereit .notice-actions { margin-top: var(--space-12); }

/* Die Zählung. Eine Zahl in der Anzeigenschrift, mehr steht nicht da — was
   hier ablenkt, kostet den Start. */
.tr-countdown {
  max-width: 640px;
  margin: 0 auto;
  min-height: 320px;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: var(--space-16);
}

.tr-countdown-zahl {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--size-display-2);
  letter-spacing: var(--track-display);
  line-height: 1;
  color: var(--color-obsidian);
  font-variant-numeric: tabular-nums;
}

.tr-countdown-note { margin: 0 !important; }

/* Ein kurzes Aufsetzen je Zahl, damit der Wechsel zu sehen ist und nicht nur
   zu lesen. Keine Fläche, die blinkt: nichts zwischen 3 und 60 Hz (§13). */
.tr-countdown-zahl.ist-neu {
  animation: tr-zaehlen var(--dur-settle) var(--ease-settle);
}

@keyframes tr-zaehlen {
  from { transform: scale(.82); opacity: .35; }
  to   { transform: none;       opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .tr-countdown-zahl.ist-neu { animation: none; }
}

/* Die Uhr. Läuft von rechts leer, ohne Übergang — der Wert kommt je Bild aus
   dem Takt, und eine CSS-Transition darüber würde eine zweite, langsamere
   Uhr daraus machen. */
.tr-balken {
  height: 8px;
  background: var(--color-line);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.tr-balken span {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--color-obsidian);
}

.tr-kopf {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-16);
  margin: var(--space-12) 0 var(--space-32);
}

.tr-stufe, .tr-punkte { margin: 0; font-size: var(--size-small); }

.tr-punkte { font-variant-numeric: tabular-nums; }

.tr-feld { min-height: 220px; }

/* Jedes Zeichen in einer Box fester Breite. Ohne das verrät das Breitenmuster
   eine Vertauschung, bevor jemand gelesen hat (spiele/symbolabgleich.js). */
.tr-reihe {
  margin: 0 0 var(--space-16);
  text-align: center;
  font-size: 40px;
  line-height: 1.3;
}

.tr-zeichen {
  display: inline-block;
  width: 1.15em;
  text-align: center;
}

.tr-wahl {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--space-12);
  margin-top: var(--space-32);
}

/* Ein Knopf wie jeder andere im System, nur größer: 64 Punkte hoch, damit ein
   Daumen ihn im Hochformat trifft, ohne zu zielen. */
.tr-taste {
  min-height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-12);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--size-body);
  padding: var(--space-16) var(--space-24);
  border: 1.5px solid var(--color-obsidian);
  border-radius: var(--radius-pill);
  background: var(--color-limestone);
  color: var(--color-obsidian);
  cursor: pointer;
  touch-action: manipulation;
}

/* Die Antwort steht in der Mitte des Knopfes, die Taste als Marke am linken
   Rand. */
.tr-taste-text { flex: 1; }

/* DIE TASTENZIFFER DARF KEINE ZWEITE ZAHL SEIN. Bei den Rechenspielen stehen
   Zahlen auf dem Knopf, und eine freistehende graue „1" daneben liest sich als
   Teil der Antwort — „3 1" statt „3, Taste 1". Also bekommt sie eine eigene
   dunkle Scheibe: umgekehrte Farbe, kleiner, am Rand. Eine Marke, kein Wert.
   Ebene durch Farbe, nicht durch Schatten. */
.tr-taste-key {
  order: -1;
  flex: none;
  /* EINE PILLE, KEINE SCHEIBE FESTER BREITE. Zwei Spiele setzen hier kein
     Zeichen, sondern ein Wort: „Leertaste". In eine 22 Punkte breite Scheibe
     passt es nicht — es lief über den dunklen Grund hinaus und stand weiß auf
     hellem Limestone, also praktisch unlesbar (gemeldet 03.09.2026). Mit
     `min-width` bleibt die Ziffer rund und das Wort bekommt seinen Platz. */
  min-width: 22px;
  height: 22px;
  padding: 0 var(--space-8);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-obsidian);
  border-radius: var(--radius-pill);
  font-family: var(--font-system);
  font-size: var(--size-fine);
  line-height: 1;
  color: var(--color-chalk);
}

/* ---- Ranglisten ---------------------------------------------------------
 *
 * Die eigene Zeile trägt Obsidian, nicht Ember: Sie ist ein Hinweis und keine
 * Warnung. Die Auslassung zwischen Spitze und Nachbarschaft ist eine eigene
 * Zeile, damit niemand die Plätze dazwischen sucht. */
.rang-wahl {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-16);
}

.rang-tafel td.rang-platz  { width: 4em; color: var(--color-dim); }
.rang-tafel td.rang-punkte { text-align: right; font-variant-numeric: tabular-nums; }

.rang-tafel tr.is-ich {
  background: var(--color-obsidian);
  color: var(--color-chalk);
}
.rang-tafel tr.is-ich td.rang-platz { color: var(--color-chalk); }

.rang-tafel tr.rang-luecke td {
  text-align: center;
  color: var(--color-dim);
  letter-spacing: .3em;
}

/* Das Urteil steht als WORT da, nicht als Farbe (ARCHITECTURE.md §9). Die
   Fläche ist ein Streifen und bleibt einer; „richtig" trägt Obsidian, „falsch"
   Ember — die eine aggressive Farbe des Systems, an der einzigen Stelle, an der
   sie hier etwas zu sagen hat. */
.tr-echo {
  min-height: 40px;
  margin: var(--space-20) 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  font-size: var(--size-small);
}

.tr-echo.is-richtig { background: var(--color-obsidian); color: var(--color-chalk); }
.tr-echo.is-falsch  { background: var(--color-ember);    color: var(--on-ember-text); }

@media (max-width: 900px) {
  .tr-reihe { font-size: 32px; }
}

/* Das Suchfeld der Zielsuche. Das Zielzeichen steht über dem Feld und bleibt
   dort stehen: Müsste es jemand behalten, misst das Spiel Gedächtnis mit. */

.tr-suche-ziel {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-12);
  margin: 0 0 var(--space-16);
}

.tr-suche-wort {
  font-family: var(--font-system);
  font-size: var(--size-fine);
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--color-dim);
}

.tr-suche-marke {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border: 1.5px solid var(--color-obsidian);
  border-radius: var(--radius-sm);
  background: var(--color-limestone);
}

/* Quadratisch, mittig, und nie breiter als das Feld selbst. Das SVG bringt sein
   eigenes Verhältnis mit; die Breite kommt von der Seite. */
.tr-suche-brett {
  max-width: 360px;
  margin: 0 auto;
  background: var(--color-limestone);
  border-radius: var(--radius-md);
}

.tr-suche-brett svg { display: block; width: 100%; height: auto; }

/* ---- 14. die Spielfelder der Gruppe Genauigkeit ---------------------------
 *
 * DIE VIER REIZFARBEN. Freigegeben am 01.09.2026 als einzige Ausnahme von
 * „sieben Farben, mehr nicht" (ARCHITECTURE.md §28). Sie gelten AUSSCHLIESSLICH
 * im Spielfeld von „Farbe gegen Wort": Dort sind sie die Aufgabe und nicht
 * Gestaltung — ein Stroop ohne Farben ist keiner.
 *
 * Das Feld ist dunkel, weil Gelb auf Limestone unter 2:1 misst und unlesbar
 * wäre. Auf Obsidian tragen alle vier. Gemessen mit tools/test-kontrast.sh,
 * nicht geschätzt.
 *
 * Wer eine dieser Farben außerhalb von `.tr-farbfeld` und `.tr-punkt`
 * verwendet, bricht die Ausnahme.
 */

.tr-farbfeld {
  background: var(--color-obsidian);
  border-radius: var(--radius-md);
  padding: var(--space-32) var(--space-24);
  text-align: center;
  min-height: 132px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.tr-farbwort {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 44px;
  line-height: 1;
  letter-spacing: .04em;
}

.tr-tinte-rot   { color: var(--reiz-rot); }
.tr-tinte-blau  { color: var(--reiz-blau); }
.tr-tinte-gruen { color: var(--reiz-gruen); }
.tr-tinte-gelb  { color: var(--reiz-gelb); }

/* Der Farbpunkt auf dem Antwortknopf. Der Name steht daneben — nichts wird
   allein über Farbe unterschieden. Die Punkte sitzen auf Limestone und tragen
   deshalb dunklere Werte als die Reize auf Obsidian. */
.tr-punkt {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1.5px solid var(--color-obsidian);
  flex: none;
}

.tr-grund-rot   { background: var(--reiz-rot-dunkel); }
.tr-grund-blau  { background: var(--reiz-blau-dunkel); }
.tr-grund-gruen { background: var(--reiz-gruen-dunkel); }
.tr-grund-gelb  { background: var(--reiz-gelb); }

/* Die Frage über der Aufgabe. Sie wechselt bei „Farbe gegen Wort" ab Stufe 11,
   und was gilt, muss dastehen statt erinnert zu werden. */
.tr-frage {
  margin: 0 0 var(--space-16);
  text-align: center;
  font-size: var(--size-small);
  color: var(--color-dim);
}

/* Randstörung: die Pfeilreihe. `visibility` statt `hidden`, damit der Platz
   bleibt — verschwände er, spränge die Knopfreihe nach oben, und der Sprung
   wäre selbst ein Reiz. */
.tr-flanken {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 132px;
  background: var(--color-limestone);
  border-radius: var(--radius-md);
}

.tr-flanken.ist-weg { visibility: hidden; }

/* Handeln oder Halten: ein Zeichen in der Mitte, das sich still in ein Kreuz
   verwandelt. Kein Flächenwechsel, keine Bewegung — die Fläche bleibt stehen,
   nur das Zeichen darin wechselt. */
.tr-halt {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 132px;
  background: var(--color-limestone);
  border-radius: var(--radius-md);
}

/* KREIS GEGEN KREUZ, nicht Farbe gegen Farbe. Ember auf Limestone misst 3.09:1
   — knapp über der Schwelle für Grafik und darunter für Text, und das
   wichtigste Signal des Spiels darf nicht an einer Nachkommastelle hängen. Die
   Form trägt die Unterscheidung, wie es die Regel ohnehin verlangt
   (ARCHITECTURE.md §9). */
.tr-halt-marke { color: var(--color-obsidian); line-height: 0; }

.tr-taste-breit { grid-column: 1 / -1; }

/* ---- 15. die Spielfelder der Gruppe Rechnen -------------------------------- */

/* Ein Term. Tabellenziffern, damit die vier Optionen darunter nicht springen,
   und keine Laufweite — die gehört auf Anzeigeklassen, nicht auf Zahlen. */
.tr-term {
  margin: 0;
  text-align: center;
  font-size: 36px;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

.tr-vergleich {
  display: grid;
  gap: var(--space-12);
  align-items: center;
  background: var(--color-limestone);
  border-radius: var(--radius-md);
  padding: var(--space-24) var(--space-16);
  min-height: 132px;
}

@media (min-width: 560px) {
  .tr-vergleich { grid-template-columns: 1fr auto 1fr; }
}

.tr-vergleich-mitte {
  margin: 0;
  text-align: center;
  font-size: var(--size-small);
  color: var(--color-dim);
}

/* Die Sachaufgabe. Ein Satz, zwei Zahlen, eine Frage — mehr trägt die Zeit
   nicht, und das Maß hält den Satz auf zwei Zeilen. */
.tr-sachaufgabe {
  margin: 0;
  max-width: 46ch;
  margin-inline: auto;
  text-align: center;
  font-size: 20px;
  line-height: 1.45;
  background: var(--color-limestone);
  border-radius: var(--radius-md);
  padding: var(--space-24);
  min-height: 132px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ---- 16. die Spielfelder der Gruppe Gedächtnis ---------------------------- */

/* Zahlenspanne: eine Ziffer zur Zeit, danach das Eingegebene an derselben
   Stelle. Die Fläche bleibt stehen — verschwände sie zwischen den Ziffern,
   wäre der Sprung selbst ein Reiz. */
.tr-spanne {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 132px;
  background: var(--color-limestone);
  border-radius: var(--radius-md);
  padding: var(--space-16);
}

.tr-spanne-ziffer {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 56px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: .08em;
}

.tr-ziffernfeld {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-8);
  margin-top: var(--space-20);
}

/* Elf Tasten in vier Spalten: neun Ziffern, Löschen, Fertig. Die Null fehlt —
   sie kommt in keiner Folge vor (spiele/zahlenspanne.js). */
.tr-ziffer {
  min-height: 56px;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 20px;
  font-variant-numeric: tabular-nums;
  border: 1.5px solid var(--color-obsidian);
  border-radius: var(--radius-md);
  background: var(--color-limestone);
  color: var(--color-obsidian);
  cursor: pointer;
  touch-action: manipulation;
}

.tr-ziffer.ist-fertig {
  grid-column: span 2;
  background: var(--color-ember);
  border-color: var(--color-ember);
  color: var(--on-ember-text);
  font-size: var(--size-body);
}

.tr-ziffer.ist-zurueck { font-size: var(--size-h3); }

/* Feldfolge: ein Brett mit unregelmäßig verteilten Feldern. Quadratisch, mittig,
   nie breiter als es selbst. */
.tr-brett {
  max-width: 340px;
  margin: 0 auto;
  background: var(--color-limestone);
  border-radius: var(--radius-md);
}

.tr-brett svg { display: block; width: 100%; height: auto; }

.tr-brett-feld {
  fill: var(--color-pumice);
  stroke: var(--color-obsidian);
  stroke-width: 1.5;
}

/* Aufleuchten heißt Obsidian, nicht Ember: Ein Feld, das leuchtet, ist kein
   Urteil, und Ember trägt in diesem System die Rückmeldung. */
.tr-brett-feld.ist-an { fill: var(--color-obsidian); }

.tr-brett.ist-offen .tr-brett-feld { cursor: pointer; }

.tr-brett-feld.ist-gewaehlt { fill: var(--color-sulfur); }

/* Zwei zurück: ein Zeichen in der Mitte, darunter ein Punkt je Reiz. */
.tr-nback {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 132px;
  background: var(--color-limestone);
  border-radius: var(--radius-md);
}

.tr-nback-zeichen {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 64px;
  line-height: 1;
}

.tr-nback-spur {
  display: flex;
  justify-content: center;
  gap: var(--space-8);
  margin-top: var(--space-16);
}

.tr-nback-punkt {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid var(--color-line);
}

/* Gemeldet heißt: die Meldung ist angekommen. Ob sie richtig war, sagt der
   Punkt nicht — das käme einer Rückmeldung mitten in der Kette gleich. */
.tr-nback-punkt.ist-gemeldet {
  background: var(--color-obsidian);
  border-color: var(--color-obsidian);
}

/* ---- 17. das Spielfeld der Gruppe Logik ----------------------------------- */

/* Eine Reihe mit einer Lücke am Ende. Tabellenziffern, damit die Glieder in
   einer Flucht stehen und der Blick die Abstände vergleichen kann. */
.tr-reihenglieder {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-8) var(--space-16);
  margin: 0;
  min-height: 132px;
  background: var(--color-limestone);
  border-radius: var(--radius-md);
  padding: var(--space-24) var(--space-16);
  font-size: 30px;
  font-variant-numeric: tabular-nums;
}

.tr-glied.ist-luecke {
  color: var(--color-dim);
  border-bottom: 2px solid var(--color-obsidian);
  min-width: 1.2em;
  text-align: center;
}

/* ---- 18. Kacheln, Serie und Ergebnis der Trainingsspiele -------------------
 *
 * Die Gestaltung, die der Auftrag für die Trainingsseite beschreibt: Kacheln
 * auf Limestone mit 40er Radius, Sulfur-Pillen für die Gruppe, Ember für den
 * Startknopf. Keine Schatten — Ebenen entstehen durch Farbe.
 */

/* Die Gruppenüberschrift trägt eine Linie, weil fünf Gruppen untereinander
   sonst als eine lange Liste gelesen werden. `h3` ist die kleinste Größe, die
   das System als Überschrift kennt — darunter gibt es keine. */
.tr-gruppenkopf {
  margin: var(--space-40) 0 var(--space-16);
  padding-bottom: var(--space-8);
  border-bottom: 1.5px solid var(--color-obsidian);
}

/* Auch der erste Gruppenkopf braucht Luft nach oben: Er ist zwar das erste Kind
   seines Behälters, steht auf der Trainingsseite aber unmittelbar unter einer
   Karte und klebte an deren Unterkante. Nur weniger Luft als die folgenden. */
.tr-gruppenkopf:first-child { margin-top: var(--space-24); }

/* Die Kachel ist eine `.card` und bringt nur mit, was darüber hinaus nötig
   ist: eine Spalte, in der der Startknopf unten steht, egal wie lang der
   Beschreibungssatz ist. */
.tr-kachel {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-12);
}

.tr-kachel h3 { margin: 0; }
.tr-kachel p { margin: 0; }
.tr-kachel-meta { margin-top: auto !important; }
.tr-kachel .btn { margin-top: var(--space-4); }

/* Drei Punkte für die laufende Folge. Sie machen sichtbar, warum die Stufe
   gleich steigt — ohne sie wirkt der Sprung wie Zufall. */
.tr-serie { display: flex; gap: 6px; align-items: center; }

.tr-serie-punkt {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1.5px solid var(--color-line);
}

.tr-serie-punkt.ist-an {
  background: var(--color-obsidian);
  border-color: var(--color-obsidian);
}

/* Der Kopf trägt jetzt drei Dinge. Die Punktzahl steht rechts in
   Tabellenziffern, damit sie beim Hochzählen nicht springt. */
.tr-kopf { justify-content: space-between; }

.tr-punkte {
  display: flex;
  align-items: baseline;
  gap: 6px;
  line-height: 1;
}

.tr-punkte-zahl {
  font-size: var(--size-h3);
  font-variant-numeric: tabular-nums;
}

.tr-punkte-wort { font-size: var(--size-fine); color: var(--color-dim); }

/* ---- der Ergebnisbildschirm ------------------------------------------------
 * Reihenfolge nach dem Auftrag: Punktzahl, Höchststufe, Vergleich mit dem
 * eigenen letzten und besten Durchgang, Verlauf. Der Selbstvergleich steht vor
 * jedem Fremdvergleich.
 */

.tr-ergebnis {
  max-width: 640px;
  margin: 0 auto;
  display: grid;
  gap: var(--space-20);
}

.tr-ergebnis-punkte { text-align: center; }

.tr-ergebnis-zahlen {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: var(--space-12);
}

.tr-ergebnis-zelle {
  background: var(--color-limestone);
  border-radius: var(--radius-md);
  padding: var(--space-16) var(--space-12);
  text-align: center;
  display: grid;
  gap: 2px;
}

.tr-ergebnis-wert {
  font-size: var(--size-h3);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.tr-ergebnis-name { font-size: var(--size-fine); color: var(--color-dim); }

.tr-ergebnis-vergleich { text-align: center; margin: 0; }

.tr-ergebnis-verlauf {
  background: var(--color-limestone);
  border-radius: var(--radius-md);
  padding: var(--space-20);
  display: grid;
  gap: var(--space-8);
}

.tr-ergebnis-verlauf svg { display: block; width: 100%; height: auto; }

/* „Nochmal" und „Anderes Spiel", nichts dazwischen und kein Zwischenschritt. */
.tr-ergebnis-knoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-12);
  justify-content: center;
}

/* Die Kontingentzeile über den Kacheln. Dezent, nicht als Warnung und nicht als
   Countdown (Trainingsauftrag §6). */
.tr-kontingent {
  margin: 0 0 var(--space-24);
  font-size: var(--size-small);
  color: var(--color-dim);
}

.tr-kontingent.ist-aus { color: var(--color-obsidian); }

.tr-gesperrt { max-width: 560px; display: grid; gap: var(--space-12); }
.tr-gesperrt h3 { margin: 0; }
.tr-gesperrt p { margin: 0; }

/* Der Kaufbereich einer Preiskarte. Er liegt hinter dem Freigabeschalter und
   ist unsichtbar, solange die Bezahlung nicht eingerichtet ist. */
.preis-kauf {
  display: grid;
  gap: var(--space-8);
  margin-top: var(--space-12);
}

.preis-kauf select { width: 100%; }
.preis-kauf .btn { width: 100%; text-align: center; }

/* ---- 19. viz — die Datengrafiken -------------------------------------------
 *
 * Farbe für alles, was viz.js zeichnet, an EINER Stelle (viz.js, Entscheidung
 * 3). Die Bausteine vergeben Klassen und nie Farbwerte; was ohne Stylesheet
 * stimmen muss, trägt `currentColor` und kommt hier gar nicht vor.
 *
 * KEINE NEUE FARBE. Ember für die eigene Position, Obsidian für Daten und
 * Beschriftung, Limestone als Fläche, Schwefel für die Beispielpille — das
 * sind vier der sieben, in genau den Rollen, die sie ohnehin haben.
 */

.viz {
  display: block;
  width: 100%;
  height: auto;
  /* Gezeichnet wird in echte Pixel; was schmaler gezeichnet wurde als sein
     Platz, wird zentriert und nicht gedehnt (der Deckel steht in viz.js). */
  margin-inline: auto;
  color: var(--color-obsidian);
  font-family: var(--font-body);
}

/* Beschriftung. `font-family` steht auf dem SVG, die Größe setzt viz.js in
   echten Pixeln — die viewBox zählt echte Pixel, also stimmt beides überein. */
.viz-text        { fill: currentColor; font-weight: 500; }
.viz-achsentext  { fill: var(--color-dim); }

/* Das wiederkehrende Detail des Systems (--rule): 1.5px, gepunktet, Obsidian.
   `stroke-dasharray: 0 5` mit runden Enden gibt echte Punkte statt kurzer
   Striche; ohne `stroke-linecap: round` bliebe die Linie unsichtbar. */
.viz-hilfslinie {
  stroke: var(--color-obsidian);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-dasharray: 0 5;
  opacity: .45;
}

.viz-linie {
  stroke: var(--color-obsidian);
  stroke-width: 1.5;
  fill: none;
}

/* Die Spur, in der ein Balken läuft. PUMICE, NICHT LIMESTONE: Diese Grafiken
   stehen fast immer IN einer Karte, und die ist selbst Limestone — eine Spur
   in derselben Farbe ist keine. Auf Pumice-Grund liegt sie eine Stufe
   dunkler als die Fläche darunter, was dasselbe Verhältnis ist. */
.viz-grund   { fill: var(--color-pumice); }
.viz-balken  { fill: var(--color-obsidian); }
.viz-flaeche { fill: var(--color-obsidian); opacity: .12; }
.viz-band    { fill: var(--color-obsidian); opacity: .16; }
.viz-marke   { fill: var(--color-ember); stroke: none; }
.viz-marke-linie { stroke: var(--color-ember); stroke-width: 2.5; fill: none; }

/* Nicht ausgewiesen. Eine Lücke ist eine Information und darf nicht wie ein
   Wert von null aussehen — deshalb eine Schraffur mit Umriss, nicht eine
   blasse Füllung, die sich mit „schwach" verwechseln lässt. */
.viz-leer {
  fill: none;
  stroke: var(--color-line);
  stroke-width: 1.5;
  stroke-dasharray: 3 4;
}

/* Baustein 3.5, die achtzehn Blockzeichen. EINE Formensprache: 24 × 24,
   Strichstärke 2, runde Enden, keine Füllung. Der Körper ist Obsidian, genau
   ein Zug je Zeichen trägt Ember — die Stelle, an der die Aufgabe entschieden
   wird.

   Die Regeln greifen in den <use>-Inhalt hinein: Ein Sprite mit zwei Tönen
   braucht das, und Klassenselektoren tun es zuverlässig. Was NICHT ginge, wäre
   ein `fill`-Attribut mit var() im Symbol selbst. */
.viz-sym { display: inline-block; vertical-align: middle; flex: none; }
.viz-sym-koerper, .viz-sym-akzent {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.viz-sym-koerper { stroke: currentColor; }
.viz-sym-akzent  { stroke: var(--color-ember); }

/* Baustein 3.4, das Ermüdungsprofil. VIER MUSTER, KEINE VIER FARBEN: Das
   System hat sieben Farben und keine davon ist für Datenreihen da. Ein Muster
   überlebt Schwarzweißdruck und Farbsehschwäche, eine Farbe nicht. */
.viz-ermuedung-linie { fill: none; stroke: var(--color-obsidian);
                       stroke-width: 1.5; stroke-linejoin: round;
                       stroke-linecap: round; }
.viz-linie-1 { stroke-dasharray: none; }
.viz-linie-2 { stroke-dasharray: 7 4; }
.viz-linie-3 { stroke-dasharray: 1.5 4; }
.viz-linie-4 { stroke-dasharray: 10 4 1.5 4; }

/* Die Nulllinie ist der Bezug, nicht eine Hilfslinie: Jeder Wert im Bild wird
   an ihr gemessen. Deshalb kräftiger als die gepunkteten Marken darüber. */
.viz-nulllinie { stroke-width: 2; }

/* Die Pausen. Pumice auf Limestone, dieselbe Stufe wie die Balkenspur — sie
   sollen den Grund färben und nicht die Kurve überdecken. */
.viz-pause { fill: var(--color-pumice); }

/* Baustein 3.3, der Stufenverlauf. Die Treppe ist Obsidian; RICHTIG UND FALSCH
   trennt die FÜLLUNG, nicht die Farbe — gefüllt gegen offen bleibt auch in
   Graustufen, im Ausdruck und bei einer Farbsehschwäche unterscheidbar
   (ARCHITECTURE.md §9). Ember trägt nur die gefüllten Punkte, also das, was
   gelungen ist. */
.viz-treppe { fill: none; stroke: var(--color-obsidian); stroke-width: 1.5;
              stroke-linejoin: round; }
.viz-treppe-hoechste { opacity: .8; }
.viz-antwort-richtig { fill: var(--color-ember); stroke: none; }
.viz-antwort-falsch  { fill: var(--color-limestone);
                       stroke: var(--color-obsidian); stroke-width: 1.5; }

/* Das Netz aus Baustein 3.1. Die FLÄCHE ist Obsidian, stark zurückgenommen —
   die Ember-Punkte darauf sind die Aussage, nicht der Umriss. Ein Netz in
   Ember-Fläche wäre eine Alarmfarbe über dem halben Hero. */
.viz-netz-flaeche { fill: var(--color-obsidian); opacity: .14;
                    stroke: var(--color-obsidian); stroke-width: 1.5;
                    stroke-opacity: .55; }
.viz-netz-kante   { fill: none; stroke: var(--color-obsidian);
                    stroke-width: 1.5; stroke-opacity: .55; }

/* Nicht ausgewiesen, im Netz: ein offener Ring. Eine andere FORM als der
   gefüllte Punkt, nicht bloß eine andere Farbe. */
.viz-netz-leer { fill: none; stroke: var(--color-line); stroke-width: 2;
                 stroke-dasharray: 2.5 3; }

/* Die Beispielpille. Schwefel in seiner einzigen Rolle: ein Tag. */
.viz-beispiel-grund { fill: var(--color-sulfur); }
.viz-beispiel-text  { fill: var(--color-obsidian); font-weight: 500;
                      letter-spacing: .04em; }

/* Druck. 3.1, 3.2 und 3.4 gehen in den PDF-Bericht, also müssen die Flächen
   dort auch wirklich drucken — Browser lassen Hintergründe sonst weg. */
@media print {
  .viz { break-inside: avoid; }
  .viz-grund, .viz-balken, .viz-flaeche, .viz-band, .viz-marke,
  .viz-beispiel-grund {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

/* Der Prüfstand viz-werkstatt.html. Gehört nicht ins Produkt, steht aber hier
   wie `.demo-card` auch: Ein zweites Stylesheet für vier Regeln wäre eine
   Datei mehr, die jemand pflegen müsste. */
.viz-raster { display: grid; gap: var(--space-24); margin-top: var(--space-24); }
.viz-buehne { margin-top: var(--space-16); min-width: 0; }
.viz-mass   { color: var(--color-dim); margin: var(--space-8) 0 0; }
.viz-mass.is-bad { color: var(--color-ember); }
.viz-regler { display: flex; align-items: center; gap: var(--space-16);
              flex-wrap: wrap; margin-top: var(--space-16); }
.viz-regler input[type=range] { flex: 1 1 240px; accent-color: var(--color-ember); }
.viz-regler output { font-family: var(--font-system); font-size: var(--size-small); }

/* Die Symbolliste im Prüfstand. */
.viz-symbolliste { display: grid; gap: var(--space-12); margin-top: var(--space-16);
                   grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.viz-symbolzeile { display: flex; gap: var(--space-12); align-items: flex-start; }
.viz-symbolzeile p { margin: var(--space-4) 0 0; color: var(--color-dim); }
.viz-symbolreihe { display: flex; flex-wrap: wrap; gap: var(--space-16);
                   margin-top: var(--space-12); }

/* Baustein 3.7, die Vorschau auf der Startseite. Sie sitzt in einer Karte des
   Systems und bringt keine eigene Fläche mit — was sie zeigt, ist das
   Spielfeld, und das hat seine Regeln im Abschnitt „the training screen". */
/* Keine Mindesthöhe, solange nichts drinsteht: Ein leeres Feld von 96 Pixeln
   sieht aus, als sei etwas nicht geladen. Sobald die Aufgabe da ist, gibt sie
   die Höhe selbst vor. */
.viz-vorschau-feld { display: flex; flex-direction: column;
                     justify-content: center; }
.viz-vorschau-feld:not(:empty) { margin: var(--space-20) 0; min-height: 96px; }
.viz-vorschau-stand { margin-top: var(--space-12); }
.viz-vorschau .fine { color: var(--color-dim); margin-top: var(--space-12); }

/* ---- 20. die Naht — das Signaturmotiv als Abschnittstrennung ---------------
 *
 * Das Punktraster des Hero, schmal und ohne Grund: derselbe Ember-Punkt im
 * selben Raster, nur ohne die Fläche darunter.
 *
 * KEIN PLASMA-VIOLETT. Der Hero-Halbton trägt es, weil er der Hero ist —
 * CLAUDE.md erlaubt die Farbe für „den Hero-Halbton und eine Karte", und ein
 * Trennstreifen ist weder das eine noch das andere. Was hier wiederkehrt, ist
 * die FORM: Punkte im Raster, 1,5 Pixel, wie oben.
 *
 * DER EINZIGE BAUSTEIN OHNE DATEN. Er ist zu rechtfertigen, weil er der Seite
 * einen eigenen Charakter gibt, den kein Wettbewerber hat — und nur, solange
 * er selten bleibt. Höchstens drei- bis viermal je Seite; `tools/test-naht.sh`
 * zählt nach.
 *
 * REINE ZIERDE, also `aria-hidden` am Element und nichts, was eine
 * Sprachausgabe vorliest. Sie unterscheidet nichts von nichts
 * (ARCHITECTURE.md §9).
 *
 * Als CSS und nicht als SVG: Ein Verlaufsmuster kostet keinen Knoten im DOM,
 * kein Skript und keine Anfrage — und es skaliert von selbst.
 */

.naht {
  height: 28px;
  margin-block: var(--space-32);
  background-image:
    radial-gradient(circle at 1.5px 1.5px,
                    var(--color-ember) 1.5px, transparent 1.6px);
  background-size: 12px 12px;
  background-repeat: repeat;
  /* DIE AUSRICHTUNG ENTSTEHT DURCH AUSBLENDEN, nicht durch Drehen. Ein
     gedrehtes Raster braucht einen Rahmen, der es beschneidet, und der bringt
     eine zweite Kante ins Bild. Die Maske lässt das Muster zu einer Seite hin
     auslaufen — dieselbe Formensprache, andere Richtung.

     `currentColor` UND NICHT `#000`: In einer Maske zählt allein der
     Alphakanal, die Farbe ist gleichgültig. Ein Literalwert wäre trotzdem ein
     fremder Farbwert im Bestand, und `tools/test-kontrast.sh` hat ihn prompt
     gemeldet — zu Recht: Eine Ausnahme, die man erklären muss, ist eine
     Ausnahme zu viel. */
  -webkit-mask-image: linear-gradient(90deg, currentColor 0%, transparent 82%);
          mask-image: linear-gradient(90deg, currentColor 0%, transparent 82%);
}

/* Die Dichte variiert, die Form nicht. */
.naht-dicht { background-size: 8px 8px; height: 22px; }
.naht-weit  { background-size: 18px 18px; height: 34px; }

/* Und die Richtung. */
.naht-rechts {
  -webkit-mask-image: linear-gradient(270deg, currentColor 0%, transparent 82%);
          mask-image: linear-gradient(270deg, currentColor 0%, transparent 82%);
}
.naht-mitte {
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, currentColor 42%, currentColor 58%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0%, currentColor 42%, currentColor 58%, transparent 100%);
}

/* Auf dem Telefon schrumpft sie mit: Achtundzwanzig Punkte Zierde zwischen
   zwei Abschnitten sind dort ein Achtel des Bildschirms. */
@media (max-width: 700px) {
  .naht, .naht-dicht, .naht-weit { height: 18px; margin-block: var(--space-20); }
}

/* Gedruckt wird sie nicht. Auf Papier ist sie ein Raster aus orangen Punkten
   ohne Aussage, und der Bericht ist ohnehin dicht. */
@media print { .naht { display: none; } }

/* Das Blockzeichen in der Blockliste. Es sitzt unter der Nummer und trägt
   dieselbe Spalte — die Karte bekommt dadurch keine neue Struktur. */
.blockkarte-zeichen { display: block; margin-top: var(--space-8); }

/* Baustein 3.1 und 3.2 auf der Profilseite. Die Bereichsgrafik steht dort, wo
   bis zum 04.09.2026 sechs div-Zeilen standen; die textliche Entsprechung
   darunter sagt dasselbe für eine Sprachausgabe und für den, der lieber liest. */
.pf-domaenen-grafik { margin-top: var(--space-20); }
.pf-domaenen-text { list-style: none; padding: 0;
                    margin: var(--space-20) 0 0; color: var(--color-dim); }
.pf-domaenen-text li + li { margin-top: var(--space-8); }

/* 3.2 unter einer Kennwertzeile. Sie erscheint nur, wo eine Einordnung
   wirklich vorliegt — sonst steht dort weiterhin nur der Satz. */
.pf-ref-grafik { grid-column: 1 / -1; margin-top: var(--space-8); }

@media print { .pf-domaenen-grafik, .pf-ref-grafik { display: none; } }

.pf-ermuedung { margin-bottom: var(--space-24); }
@media print { .pf-ermuedung { display: none; } }
