/* ==========================================================================
   Daniel Sangermann, Portfolio. Route "Trace".

   Zwei Teile. Der Token-Block (bis zur Schlussmarke unten) traegt alle
   Werte und die aufgezeichneten Entscheidungen. Unterhalb davon steht kein
   Hex-, px-, Dauer- oder cubic-bezier-Literal; jede Regel liest Token.
   Ausnahmen: die Media-Queries (var() geht dort nicht, Plattformtatsache),
   Systemfarben im Kontrastmodus (CanvasText, Highlight) und Prozentwerte
   von Verlaeufen und Masken.

   Wurzeln (Details in quelle/anti-slop-brief.md):
   1 Inventar: nur Belegtes (quelle/inventar.md).
   2 Stimme: "Jeder Satz nennt etwas, das laeuft, oder eine Zahl, die gezaehlt wurde."
   3 Temperatur: technisch-praezise, nuechtern, genau ein ausdrucksstarkes Element (der Trace).
   4 Dichte: mittel bis luftig, Fliesstext gross.
   ========================================================================== */

/* Schrift. EIN Download: die Variable-Datei traegt Gewicht UND Breite.
   Subtraktion: kein Italic (nichts im Baum ist kursiv gesetzt), kein latin-ext
   (Deutsch und Englisch liegen im latin-Teil), keine Gewichte unter 400 oder ueber
   600 (font-weight-Bereich unten begrenzt, nichts wird synthetisch gefettet).
   Akzeptierte Kosten: 64 KB fuer die ganze Seite; der Pfeil U+2192 und der nicht
   trennende Bindestrich U+2011 liegen nicht im latin-Teil, deshalb setzt die Seite
   keine Pfeile als Text und haelt Bindestrich-Begriffe mit .nb (white-space) zusammen. */
@font-face {
  font-family: "Plex Sans";
  font-style: normal;
  font-weight: 400 600;
  font-stretch: 75% 100%;
  font-display: swap;
  src: url("fonts/ibm-plex-sans-latin-wdth-normal.woff2") format("woff2");
}

:root {
  color-scheme: light dark;

  /* ---------- Palette: fuenf Farben, benannt nach dem Messgeraet ----------
     Judgment (Wurzel 3, Temperatur nuechtern-praezise): kuehles Neutral statt
     Creme, weil die Seite ein Messprotokoll ist und kein Brief; ein einziges
     Chroma, das Signal (hot). Es traegt genau eine Aussage, die Schnittmenge, in der
     alle drei Spuren zugleich liefen (Band, Fuellung, Zeile darunter), und taucht sonst nur
     als Unterstreichung der Links auf (dazu als Tipp-Hervorhebung auf Touch,
     eine Plattformfunktion). Die uebrigen vier sind Neutrale, die der Kontrast erzwingt.
     frame    der Grund, auf den der Trace aufgezeichnet wird
     ink      Schrift und die Spannen selbst (Spanne = Tinte auf dem Grund)
     ink-dim  gedaempfte Anzeige: Achsen, Messwerte, Bildunterschriften
     gridline Raster und Trennlinien (Dekoration, traegt keine Bedeutung)
     hot      das Signal der Schnittmenge, aus dem Flammengraphen geliehen,
              wo Orange "heiss" heisst
     Akzeptierte Kosten: Bandfuellung und Signal sind nur ueber Farbe vom Grund
     unterschieden; deshalb steht die Schnittmenge zusaetzlich als Text darunter.
     Tinte neben Signal innerhalb einer Spanne trennt 3,03:1 (hell) und nur 1,99:1 (dunkel);
     im Dunkeln liegt deshalb eine Haarlinie in der Grundfarbe zwischen beiden (--sep-a,
     --sep-b), und quelle/kontrast.js fuehrt das Paar als bekannte Ausnahme mit Zahl.
     Beide Themen sind vollstaendig deklariert; jedes Paar, das gerendert wird, ist
     in quelle/kontrast.js berechnet (liest diesen Block). */
  --frame: #F2F4F5;
  --ink: #12171A;
  --ink-dim: #4A555B;
  --gridline: #C4CCD0;
  --hot: #B4390A;
  --band-alpha: 12%;
  --tint-cursor: 10%; /* Monatsstreifen des Traces: Tinte ueber dem Grund */
  --tint-code: 8%;    /* Code im Text und Entwurfshinweis: Tinte ueber dem Grund */
  --tint-tap: 24%;    /* Tipp-Hervorhebung auf Touch: Signal ueber transparent */
  --mask-on: #000; /* Maskenwert, keine sichtbare Farbe */
  --shot-dim: 1;   /* Helligkeit der Bildschirmfotos der Demo; im dunklen Thema gedaempft */

  /* ---------- Schrift ---------- */
  /* Judgment (Wurzel 2, Stimme Ingenieur zu Ingenieur): IBM Plex Sans, eine
     Familie fuer alles. Sie kommt aus dem Ingenieurwesen, setzt Ziffern
     tabellarisch und hat eine Breitenachse: Fliesstext bei voller Breite, die
     Messwerte des Traces schmal (78 %), wie die Anzeige eines Profilers, ohne
     dass die Seite eine Monospace-Schrift braucht (Daniels Geschmack). */
  --face: "Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --width-reading: 100%;
  --width-readout: 78%;
  --weight-reading: 400;
  --weight-label: 500;
  --weight-title: 600;

  /* Typskala. Fuenf Arten Text zaehlt die Seite wirklich, fuenf Stufen sind deklariert:
     readout    Messwerte, Achsen, Daten, Bildunterschriften
     reading    Fliesstext. Wurzel 4 (mittel bis luftig, gross): 17 bis 20 px je nach Breite
     lede       der eine Satz unter dem Namen und die Zwischenueberschriften (h3) im Text
     heading    Abschnittsueberschriften und die gezaehlten Zahlen
     nameplate  der Name; Judgment: das Verhaeltnis zum Fliesstext (2,6 bei 375 px bis 3,6 ab
                1440 px) liegt im Bereich 2 bis 6, den eine Zeile mit Satzcharakter hat
     Subtraktion: kein xs, kein xl, keine Zwischenstufen; kein Schriftgrad ist ohne Text.
     Gewichtsdeckel 600: auf Anzeigegroesse wirkt mehr als 600 schreiend, die Seite braucht
     drei Gewichte (400, 500, 600) und laedt sie als eine Datei. */
  --size-readout: 0.9375rem;
  --size-reading: clamp(1.0625rem, 0.9rem + 0.55vw, 1.25rem);
  --size-lede: clamp(1.25rem, 1rem + 1.1vw, 1.625rem);
  --size-heading: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
  --size-nameplate: clamp(2.75rem, 1.5rem + 5vw, 4.5rem);

  /* Zeilenhoehen. Platform fact: ab etwa 72 px Schriftgrad faellt der Zeilenabstand
     unter 1,1; die Zwischenstufen (36 bis 48 px) liegen zwischen 1,0 und 1,2. */
  --leading-reading: 1.55;
  --leading-lede: 1.4;
  --leading-heading: 1.2;
  --leading-readout: 1.4;
  --leading-nameplate: 1.05;
  --tracking-nameplate: -0.01em;

  /* ---------- Abstand: Leiter aus der Zeilenbox des Fliesstextes ---------- */
  --line: calc(var(--size-reading) * var(--leading-reading));
  --gap-in-line: calc(var(--line) / 4);   /* was innerhalb einer Zeile sitzt */
  --gap-label: calc(var(--line) / 2);     /* Beschriftung zum Beschrifteten */
  --gap-line: var(--line);                /* eine Zeile von der naechsten */
  --gap-block: calc(var(--line) * 2);     /* ein Block vom naechsten */
  --gap-section: calc(var(--line) * 4);   /* ein Abschnitt vom naechsten */
  /* Subtraktion: keine Stufe ueber vier Zeilen, die Seite scrollt, aber kein Abschnitt
     braucht mehr Luft als vier Zeilen. */

  /* ---------- Mass ---------- */
  /* Derivation: 65 Zeichen mal 0,5 em je Zeichen. Der Faktor ist eine Konvention, keine
     Messung (die echte Zeichenbreite haengt von der Schrift ab). */
  --measure: calc(var(--size-reading) * 32.5);
  /* Judgment: der eine Satz unter dem Namen steht in groesserer Schrift und darf 15 % breiter
     laufen als der Fliesstext, damit die Zeichenzahl je Zeile etwa gleich bleibt. */
  --measure-lede: calc(var(--measure) * 1.15);
  /* Judgment: die Beschriftungsspalte ist 0,4 Textmass breit, genug fuer "Working with
     AI agents" in Ueberschriftengroesse auf zwei Zeilen. */
  --label-col: calc(var(--measure) * 0.4);
  /* Derivation: die Seite folgt aus dem Textmass: Textmass plus Spalte plus ein Blockabstand. */
  --page: calc(var(--measure) + var(--label-col) + var(--gap-block));
  --gutter: var(--gap-line);
  --target: 2.75rem; /* 44 px: WCAG 2.5.5 und Apple; der Katalog verlangt 40 */
  /* Umbrueche stehen als Literale in den Media-Queries unten (var() geht dort nicht, Platform fact).
     Judgment: 40rem (640 px) stellt die Beschriftung neben den Wert in den Kontaktzeilen, setzt die
     Tabelle des Traces wieder als Tabelle und gibt der Kopfleiste Luft; 60rem (960 px)
     stellt die Beschriftungsspalte neben den Text und haelt sie beim Scrollen fest. Ab dort ist der
     Inhaltsbereich fast so breit wie die Seite (972 px bei 20 px Fliesstext), darunter wuerde der Text
     neben der Spalte zu schmal. */

  /* ---------- Spuren des Traces ---------- */
  /* Derivation: anderthalb Zeilenhoehen, mindestens das Touch-Ziel (--target); bei 17 px Fliesstext
     waeren es 39,5 px, unter dem Ziel. */
  --lane-height: max(calc(var(--line) * 1.5), var(--target));
  /* Derivation: eine Zeile fuer die Jahreszahl der Achse plus ein Viertel Luft. */
  --axis-height: calc(var(--line) * 1.25);
  /* Judgment: die Schluesselspalte der Kontaktzeilen ist gut halb so breit wie die Beschriftungsspalte,
     genug fuer "LinkedIn" und "Lebenslauf auf Englisch". */
  --key-col: calc(var(--label-col) * 0.55);
  /* Judgment: das Foto (nur wenn eines gesetzt ist) ist halb so breit wie die Beschriftungsspalte. */
  --portrait: calc(var(--label-col) * 0.5);
  /* Judgment: die Spur im Kleinen neben den Abschnittsueberschriften ist so hoch wie ein halber Zeilenabstand. */
  --mark-height: var(--gap-label);
  /* Derivation: die Zahl der Spuren in quelle/trace-daten.json; build.js bricht ab, wenn sie nicht stimmt. */
  --track-count: 3;

  /* ---------- Form ---------- */
  /* Judgment (Wurzel 3): Spannen und Linien sind Rechtecke, so zeichnet ein Profiler.
     Eine einzige Rundung fuer alles, was ein Blatt ist (Foto, Bildschirmfotos, Entwurfshinweis, Code im Text).
     Kein konzentrisches Paar im Baum, deshalb keine Gleichung, kein Aussenradius. */
  --radius-plate: 0.25rem;
  --hair: 1px;
  --underline-offset: 0.22em;
  --rule: 0.125rem;
  --focus-width: 0.1875rem;
  --focus-offset: 0.1875rem;
  /* Judgment: der Aufklapp-Marker der Tabelle ist ein halbes em gross, ein Dreieck aus der Schriftfarbe. */
  --marker: 0.5em;
  /* Subtraktion Elevation: keine Schatten, keine Ebene ueber der Seite. Die Anzeige der Spur steht fest in der
     Seite unter dem Trace und schwebt nicht darueber; Trennung nur durch Haarlinien. */

  /* ---------- Ebenen ---------- */
  /* Derivation: zwei Ebenen werden gebraucht, mehr nicht: die Spuren ueber der Achse und dem Band
     (--layer-content), der Sprunglink ueber allem (--layer-skip). Die Anzeige liegt nicht darueber. */
  --layer-content: 1;
  --layer-skip: 10;
  /* Derivation: der Sprunglink wartet drei Eigenhoehen ueber dem Seitenrand (Prozent der eigenen Hoehe). */
  --skip-hide: -300%;

  /* ---------- Bewegung ---------- */
  /* Judgment (Wurzel 3): ein Weg, bei dem nichts schwingt. Eine Kurve fuer die ganze Seite,
     ease-out ohne Ueberschwingen. Bewegung gibt es an drei Stellen: das einmalige Einzeichnen beim
     Laden (Spuren, danach das Band), das Ein- und Ausblenden der Anzeige unter dem Trace und die
     Rueckmeldung auf Hover und Druck (Unterstreichung der Links, Spanne). Abstention: Nichts bewegt sich
     beim Scrollen und nichts auf Bildern.
     Derivation draw: die Spanne laeuft bis zu 972 px (Seitenbreite bei 20 px Fliesstext)
     in 640 ms, etwa 1,5 px je ms.
     Derivation settle: das Band bewegt sich 4 px und blendet ein, 240 ms (Band-Bereich
     Popover bis Dropdown, 125 bis 250 ms).
     Derivation readout: die Anzeige blendet in 160 ms ein und in 96 ms aus, sechs Zehntel der
     Einblendung; sie bewegt sich nicht, weil sie fest steht.
     Derivation press: 120 ms, knapp unter der unteren Grenze des Popover-Bereichs (125 ms), damit Druck
     sofort wirkt.
     Stagger: 60 ms zwischen den Spuren (Bereich 30 bis 80 ms), blockiert keine Eingabe.
     Derivation delay-band: das Band beginnt, wenn die letzte Spur fertig gezeichnet ist: Zeichendauer plus
     (Spurenzahl minus eins) Staffelungen.
     Judgment draw-from: eine Spur startet bei 2 % ihrer Laenge, nicht bei null; der Anfang ist sofort sichtbar,
     nichts ploppt auf. Judgment press-scale: eine gedrueckte Spanne wird 6 % niedriger (scaleY), sichtbar, aber
     weit unter dem Ziel.
     Platform fact: Spuren, Band und Anzeige animieren nur transform und opacity (visibility springt beim
     Oeffnen sofort und beim Schliessen erst nach der Ausblendung, --dur-instant); Hover und Fokus laufen als
     transition, das einmalige Einzeichnen als keyframes. Die Link-Unterstreichung und die Linkfarbe laufen
     ebenfalls als transition (klein, ohne Layout). */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --dur-draw: 640ms;
  --dur-settle: 240ms;
  --dur-readout: 160ms;
  --dur-readout-out: 96ms;
  --dur-press: 120ms;
  --dur-instant: 0s;
  --stagger: 60ms;
  --delay-band: calc(var(--dur-draw) + (var(--track-count) - 1) * var(--stagger));
  --draw-from: 0.02;
  --settle-distance: 0.25rem;
  --press-scale: 0.94;
}

@media (prefers-color-scheme: dark) {
  :root {
    --frame: #0F1417;
    --ink: #E9EDEF;
    --ink-dim: #A2AEB4;
    --gridline: #2A343A;
    --hot: #F4904F;
    --band-alpha: 16%;
    /* Judgment: die hellen Bildschirmfotos der Demo (cremefarbener Grund) wuerden im dunklen Thema als helle
       Platte aus der Seite brechen; 12 % weniger Helligkeit nimmt ihnen das Gleissende und laesst sie lesbar. */
    --shot-dim: 0.88;
  }
}

/* Platform fact (Bewegung reduzieren): die Aenderung bleibt, die Reise faellt weg.
   Die Anzeige blendet weiter ein und aus (nahe null Dauer, damit transitionend feuert),
   das Einzeichnen entfaellt, die Spannen stehen sofort. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}

/* ENDE DER TOKENS ========================================================= */

/* ---------- Grundlagen ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--frame);
  color: var(--ink);
  font-family: var(--face);
  font-size: var(--size-reading);
  font-weight: var(--weight-reading);
  font-stretch: var(--width-reading);
  line-height: var(--leading-reading);
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: color-mix(in srgb, var(--hot) var(--tint-tap), transparent);
  overflow-wrap: break-word;
}
img { display: block; max-width: 100%; height: auto; }
h1, h2, h3, p, ul, ol, dl, dd, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }

/* Fokus, global: sichtbar fuer die Tastatur, nie unter dem Mausklick. */
:focus-visible {
  outline: var(--focus-width) solid var(--ink);
  outline-offset: var(--focus-offset);
}
:focus:not(:focus-visible) { outline: none; }

.sr-only { position: absolute; inline-size: var(--hair); block-size: var(--hair); margin: calc(var(--hair) * -1); padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
/* Begriffe mit Bindestrich bleiben auf einer Zeile (build.js setzt die Spans), solange die Spalte mindestens 14 em breit ist.
   Darunter (grosse Schrift auf schmalem Bildschirm) duerfen sie am Bindestrich brechen, statt seitwaerts zu laufen (WCAG 1.4.4).
   Judgment: das laengste Wort (Echtzeit-Computergrafik, 23 Zeichen) misst etwa 12 em, 14 em lassen Luft. em bezieht sich
   im Container-Query auf die Schrift des Containers. */
main { container-type: inline-size; }
@container (min-width: 14em) {
  .nb { white-space: nowrap; }
}
/* Judgment (Wurzel 3): Code im Text bleibt in der Textschrift, hinterlegt statt monospace; Daniels Geschmack. */
code { font: inherit; padding: 0 var(--gap-in-line); background: color-mix(in srgb, var(--ink) var(--tint-code), transparent); border-radius: var(--radius-plate); }

a, summary, button { touch-action: manipulation; }
a {
  color: inherit;
  text-decoration-line: underline;
  text-decoration-color: var(--hot);
  text-decoration-thickness: var(--hair);
  text-underline-offset: var(--underline-offset);
  transition: text-decoration-thickness var(--dur-press) var(--ease-out), color var(--dur-press) var(--ease-out);
}
/* Hover und Gedrueckt: jede Stufe ist deutlicher als die davor. Hover verdickt die Unterstreichung, Druck daempft
   zusaetzlich die Schrift (ink-dim); das Signal bleibt der Unterstreichung und der Schnittmenge vorbehalten. */
a:hover, a:active { text-decoration-thickness: var(--rule); }
a:active { color: var(--ink-dim); }

/* Typografische Behandlung: Ueberschriften und Bildunterschriften balanciert, kurze Absaetze ohne Waise. */
h1, h2, h3, .lede, figcaption { text-wrap: balance; }
p, .note, .counts li > span { text-wrap: pretty; }

.skip {
  position: absolute;
  inset-inline-start: var(--gutter);
  inset-block-start: var(--gap-in-line);
  z-index: var(--layer-skip);
  padding: var(--gap-in-line) var(--gap-label);
  background: var(--ink);
  color: var(--frame);
  transform: translateY(var(--skip-hide));
}
.skip:focus-visible { transform: none; }

.page { width: min(100% - var(--gutter) * 2, var(--page)); margin-inline: auto; }

/* ---------- Kopfleiste ---------- */
/* Die Leiste bricht um, statt seitwaerts zu laufen (WCAG 1.4.10 Reflow, 320 px; 1.4.4 Textgroesse 200 %): Navigation
   in der ersten Zeile, der Sprachwechsel rechts, bei Bedarf in der zweiten. Die Linktexte sitzen auf der Textkante der
   Seite (negativer Rand um das Innenpolster), die Trefferflaeche bleibt 44 px hoch. */
.bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0 var(--gap-line); padding-block: var(--gap-in-line); font-size: var(--size-readout); line-height: var(--leading-readout); }
.bar nav, .bar ul { display: flex; align-items: center; }
.bar ul { flex-wrap: wrap; margin-inline-start: calc(var(--gap-in-line) * -1); }
.bar > a { margin-inline-start: auto; margin-inline-end: calc(var(--gap-in-line) * -1); }
.bar a {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--target);
  padding-inline: var(--gap-in-line);
  text-decoration-line: none;
  font-weight: var(--weight-label);
}
.bar a:hover, .bar a:active { text-decoration-line: underline; }

/* ---------- Eroeffnung ---------- */
.opening { padding-block: var(--gap-block) 0; }
h1 {
  font-size: var(--size-nameplate);
  line-height: var(--leading-nameplate);
  letter-spacing: var(--tracking-nameplate);
  font-weight: var(--weight-label);
}
.role { margin-block-start: var(--gap-label); font-size: var(--size-reading); color: var(--ink-dim); font-weight: var(--weight-label); }
.lede { margin-block-start: var(--gap-line); max-width: var(--measure-lede); font-size: var(--size-lede); line-height: var(--leading-lede); }

/* ---------- Der Trace ---------- */
.trace { margin-block-start: var(--gap-block); }
.trace-title { margin-block-start: var(--gap-label); max-inline-size: var(--measure); font-size: var(--size-readout); line-height: var(--leading-readout); font-stretch: var(--width-readout); color: var(--ink-dim); }
.trace-body { position: relative; padding-block-start: var(--axis-height); }
.trace-plate { position: absolute; inset: 0; pointer-events: none; }
.tick { position: absolute; inset-block: 0; inset-inline-start: var(--at); border-inline-start: var(--hair) solid var(--gridline); }
.tick span {
  position: absolute;
  inset-block-start: 0;
  padding-inline-start: var(--gap-in-line);
  font-size: var(--size-readout);
  line-height: var(--axis-height);
  font-stretch: var(--width-readout);
  font-variant-numeric: tabular-nums;
  color: var(--ink-dim);
}
.band {
  position: absolute;
  inset-block: 0;
  inset-inline-start: var(--from);
  inline-size: var(--span);
  background: color-mix(in srgb, var(--hot) var(--band-alpha), transparent);
  animation: settle var(--dur-settle) var(--ease-out) backwards;
  animation-delay: var(--delay-band);
}
@keyframes settle { from { opacity: 0; transform: translateY(var(--settle-distance)); } }
.cursor { position: absolute; inset-block: 0; inset-inline-start: var(--at); inline-size: var(--step); background: color-mix(in srgb, var(--ink) var(--tint-cursor), transparent); border-inline: var(--hair) solid var(--ink-dim); }
.cursor[hidden] { display: none; }

.tracks { position: relative; z-index: var(--layer-content); }
.track { padding-block: var(--gap-label) var(--gap-label); }
.track-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--gap-label);
  margin-block-end: var(--gap-in-line);
  font-size: var(--size-readout);
  line-height: var(--leading-readout);
}
/* Der Kopf liegt ueber dem Raster; Hintergrund, damit Rasterlinien nicht durch die Schrift laufen. */
.track-head > * { background: var(--frame); }
.track-head strong { font-weight: var(--weight-title); font-size: var(--size-reading); line-height: var(--leading-reading); }
.track-head .when { color: var(--ink-dim); font-stretch: var(--width-readout); font-variant-numeric: tabular-nums; }
.lane { position: relative; block-size: var(--lane-height); }
.span {
  position: absolute;
  inset-block: 0;
  inset-inline-start: var(--from);
  inline-size: var(--span);
  display: block;
  color: inherit;
  text-decoration-line: none;
}
.span:hover, .span:active { text-decoration-line: none; color: inherit; }
/* Fuellung der Spanne und der Spur im Kleinen: Tinte, in der Schnittmenge Signal. Wo die Schnittmenge nicht an
   der Kante der Spur liegt (--sep-a, --sep-b gleich 1), trennt eine Haarlinie ohne Farbe beide Segmente. */
.fill, .mark i {
  background: linear-gradient(to right,
    var(--ink) var(--cut-a),
    transparent var(--cut-a) calc(var(--cut-a) + var(--sep-a) * var(--hair)),
    var(--hot) calc(var(--cut-a) + var(--sep-a) * var(--hair)) calc(var(--cut-b) - var(--sep-b) * var(--hair)),
    transparent calc(var(--cut-b) - var(--sep-b) * var(--hair)) var(--cut-b),
    var(--ink) var(--cut-b));
}
.fill {
  position: absolute;
  inset: 0;
  transform-origin: left center;
  animation: draw var(--dur-draw) var(--ease-out) backwards;
  animation-delay: calc(var(--order) * var(--stagger));
  transition: transform var(--dur-press) var(--ease-out);
}
@keyframes draw { from { transform: scaleX(var(--draw-from)); } }
/* Offene Spanne: laeuft noch, deshalb blendet sie hinter der Schnittmenge nach rechts aus.
   Die Blende beginnt, wo das Signal endet (--cut-b), damit die Schnittmenge voll deckt.
   Der Fokusring liegt am Link, nicht am Fuellkoerper, die Maske beschneidet ihn nicht. */
.span[data-open] .fill, .mark i[data-open] {
  -webkit-mask-image: linear-gradient(to right, var(--mask-on) var(--cut-b), transparent);
  mask-image: linear-gradient(to right, var(--mask-on) var(--cut-b), transparent);
}
.span:hover .fill, .span:active .fill, .span:focus-visible .fill { transform: scaleY(var(--press-scale)); }

.trace-note { margin-block-start: var(--gap-label); margin-inline-end: calc(100% - var(--band-end)); text-align: end; font-size: var(--size-readout); line-height: var(--leading-readout); color: var(--hot); font-variant-numeric: tabular-nums; }
.trace-note strong { font-weight: var(--weight-title); }

/* Die Anzeige: fest ueber den Spuren, nicht schwebend (so liegt sie auch auf kurzen Fenstern im Bild, wenn eine Spur unten sitzt). Alle Felder (Hinweis im Ruhezustand, je ein Feld je Spur) teilen
   sich eine Zelle; die Hoehe richtet sich nach dem hoechsten, die Seite springt nie. Sichtbar ist das Feld der Spur, auf
   der Zeiger, Tastaturfokus oder der erste Tipp (Touch, .is-armed) liegt. Ohne Skript funktionieren Zeiger und Fokus
   ueber :has(). Die Felder enthalten nichts Bedienbares (WCAG 1.4.13); Escape blendet sie aus (.is-quiet, trace.js). */
.trace-info { font-size: var(--size-readout); line-height: var(--leading-readout); }
.readouts { display: grid; }
.readouts > * { grid-area: 1 / 1; max-inline-size: var(--measure); }
.trace-hint { color: var(--ink-dim); font-variant-numeric: tabular-nums; transition: opacity var(--dur-readout-out) var(--ease-out), visibility var(--dur-instant) linear var(--dur-readout-out); }
/* Ohne Skript gilt nur, was ohne Skript stimmt: Zeiger und Fokus zeigen die Anzeige per :has(). Der Satz zu den Pfeiltasten (.hint-keys)
   und der Touch-Hinweis (.hint-touch: erster Tipp zeigt die Laenge, zweiter oeffnet den Abschnitt) beschreiben trace.js; sie erscheinen erst,
   wenn das Skript angelaufen ist und .has-js auf die Figur gesetzt hat. Auf Geraeten ohne Hover und ohne Skript folgt schon der erste Tipp
   dem Verweis; dort sagt die Seite lieber nichts als etwas Falsches. */
.hint-touch, .hint-keys { display: none; }
.has-js .hint-keys { display: inline; }
@media (hover: none) {
  .hint-pointer { display: none; }
  .has-js .hint-touch { display: inline; }
}
.readout {
  color: var(--ink);
  text-align: start;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-readout-out) var(--ease-out), visibility var(--dur-instant) linear var(--dur-readout-out);
}
.readout b { display: block; font-weight: var(--weight-title); }
.readout i, .readout > span { display: block; font-style: normal; color: var(--ink-dim); font-variant-numeric: tabular-nums; }
/* Je Spur eine Regel (--track-count Spuren; build.js prueft die Zahl): n-te Spur zeigt das n-te Feld. */
.trace:not(.is-quiet):has(.track:nth-child(1) .span:is(:hover, :focus-visible, .is-armed)) .readout:nth-of-type(1),
.trace:not(.is-quiet):has(.track:nth-child(2) .span:is(:hover, :focus-visible, .is-armed)) .readout:nth-of-type(2),
.trace:not(.is-quiet):has(.track:nth-child(3) .span:is(:hover, :focus-visible, .is-armed)) .readout:nth-of-type(3) {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--dur-readout) var(--ease-out), visibility var(--dur-instant);
}
.trace:not(.is-quiet):has(.span:is(:hover, :focus-visible, .is-armed)) .trace-hint {
  opacity: 0;
  visibility: hidden;
}
/* Monatsstreifen: Zeile unter der Anzeige, reserviert eine Zeile, damit nichts springt (trace.js schreibt hinein). */
.trace-month { margin-block-start: var(--gap-in-line); min-block-size: calc(var(--size-readout) * var(--leading-readout)); color: var(--ink-dim); font-stretch: var(--width-readout); font-variant-numeric: tabular-nums; }

.trace-data { margin-block-start: var(--gap-label); font-size: var(--size-readout); line-height: var(--leading-readout); }
.trace-data summary {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-label);
  min-block-size: var(--target);
  cursor: pointer;
  color: var(--ink-dim);
  text-decoration-line: underline;
  text-decoration-color: var(--ink-dim);
  text-underline-offset: var(--underline-offset);
  list-style: none;
}
.trace-data summary::-webkit-details-marker { display: none; }
/* Der Marker ist ein gezeichnetes Dreieck (die Schrift hat kein U+25B8); offen dreht es nach unten, und der Text wechselt. */
.trace-data summary::before {
  content: "";
  inline-size: var(--marker);
  block-size: var(--marker);
  background: currentColor;
  clip-path: polygon(0 0, 100% 50%, 0 100%);
  transition: transform var(--dur-press) var(--ease-out);
}
.trace-data[open] summary::before { transform: rotate(90deg); }
.trace-data .s-open { display: none; }
.trace-data[open] .s-open { display: inline; }
.trace-data[open] .s-closed { display: none; }
.trace-data summary:hover, .trace-data summary:active { color: var(--ink); text-decoration-color: var(--hot); }
.trace-data table { width: 100%; border-collapse: collapse; margin-block-start: var(--gap-in-line); font-variant-numeric: tabular-nums; }
.trace-data th, .trace-data td { padding: var(--gap-in-line) var(--gap-label); text-align: start; vertical-align: top; border-block-end: var(--hair) solid var(--gridline); }
.trace-data thead th { color: var(--ink-dim); font-weight: var(--weight-label); }
.trace-data td:last-child, .trace-data th:last-child { text-align: end; }
.table-scroll { overflow-x: auto; }

/* ---------- Abschnitte ---------- */
.section {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap-line) var(--gap-block);
  padding-block: var(--gap-block) 0;
  margin-block-start: var(--gap-section);
  border-block-start: var(--hair) solid var(--gridline);
}
.section > header { align-self: start; }
.section > header .note { margin-block-start: var(--gap-in-line); }
.section h2 { font-size: var(--size-heading); line-height: var(--leading-heading); font-weight: var(--weight-title); }
/* Die Spur im Kleinen unter der Abschnittsueberschrift: dieselbe Lage und Laenge wie im Trace, Schnittmenge in Signal. */
.mark { display: block; position: relative; max-inline-size: var(--label-col); block-size: var(--mark-height); margin-block-start: var(--gap-label); border-block-end: var(--hair) solid var(--gridline); }
.mark i { position: absolute; inset-block: 0; inset-inline-start: var(--from); inline-size: var(--span); }
.prose > * + * { margin-block-start: var(--gap-line); }
.prose p, .prose ul { max-width: var(--measure); }
.note { font-size: var(--size-readout); line-height: var(--leading-readout); color: var(--ink-dim); font-stretch: var(--width-readout); }
/* Zwischenueberschriften im Text: eine Stufe ueber dem Fliesstext (lede), damit die Hierarchie sichtbar bleibt. */
.prose > h3 { font-size: var(--size-lede); line-height: var(--leading-lede); font-weight: var(--weight-title); }
.prose > h3:not(:first-child) { margin-block-start: var(--gap-block); }

/* Gezaehlte Zahlen: eine Liste, kein Raster. Ziffern tabellarisch, damit die Spalte steht. */
.counts { display: grid; grid-template-columns: minmax(0, 1fr); max-width: var(--measure); }
.counts li { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap-in-line); padding-block: var(--gap-label); border-block-start: var(--hair) solid var(--gridline); }
.counts li:last-child { border-block-end: var(--hair) solid var(--gridline); }
.counts data { font-size: var(--size-heading); line-height: var(--leading-heading); font-weight: var(--weight-label); font-variant-numeric: tabular-nums; }
.counts small { display: block; margin-block-start: var(--gap-in-line); font-size: var(--size-readout); line-height: var(--leading-readout); color: var(--ink-dim); font-variant-numeric: tabular-nums; font-stretch: var(--width-readout); }

/* Bildschirmfotos der Demo. Jedes Bild ist ein Link auf die Vollansicht; unter 40rem liefert <picture> einen engeren
   Ausschnitt. Im dunklen Thema daempft --shot-dim die hellen Aufnahmen. */
.shots { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap-block); margin-block-start: var(--gap-block); }
.shot-link { display: block; color: inherit; text-decoration-line: none; cursor: zoom-in; }
.shot picture { display: block; }
.shot img { inline-size: 100%; border: var(--hair) solid var(--gridline); border-radius: var(--radius-plate); filter: brightness(var(--shot-dim)); }
.shot figcaption { margin-block-start: var(--gap-label); max-width: var(--measure); font-size: var(--size-readout); line-height: var(--leading-readout); color: var(--ink-dim); }

/* Weitere Arbeit: Datum und Titel in einer Zeile, soweit es passt. */
.work { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; }
.work li { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap-in-line); padding-block: var(--gap-label); border-block-start: var(--hair) solid var(--gridline); }
.work li:last-child { border-block-end: var(--hair) solid var(--gridline); }
.work .when { font-size: var(--size-readout); line-height: var(--leading-readout); color: var(--ink-dim); font-stretch: var(--width-readout); font-variant-numeric: tabular-nums; }
.work h3 { font-size: var(--size-reading); font-weight: var(--weight-title); }
.work p { max-width: var(--measure); }

/* Kontakt. */
.rows { display: grid; grid-template-columns: minmax(0, 1fr); max-width: var(--measure); }
.rows li { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--gap-line); border-block-start: var(--hair) solid var(--gridline); }
.rows li:last-child { border-block-end: var(--hair) solid var(--gridline); }
.rows .k { font-size: var(--size-readout); line-height: var(--leading-readout); color: var(--ink-dim); font-stretch: var(--width-readout); padding-block-start: var(--gap-label); }
.rows a { display: flex; align-items: center; min-block-size: var(--target); overflow-wrap: anywhere; font-weight: var(--weight-label); }
/* Foto: derzeit nicht gesetzt (inhalt.*.json contact.photo); die Regel haelt es mit Rand bereit. */
.portrait { margin-block-start: var(--gap-line); }
.portrait img { inline-size: var(--portrait); border: var(--hair) solid var(--gridline); border-radius: var(--radius-plate); }

.foot { margin-block-start: var(--gap-section); padding-block: var(--gap-block); border-block-start: var(--hair) solid var(--gridline); font-size: var(--size-readout); line-height: var(--leading-readout); color: var(--ink-dim); }
.foot ul { display: flex; flex-wrap: wrap; gap: 0 var(--gap-line); }
.foot a { display: inline-flex; align-items: center; gap: var(--gap-in-line); min-block-size: var(--target); }
.foot p { max-width: var(--measure); }
.foot ul + p { margin-block-start: var(--gap-label); }

/* ---------- Rechtsseiten ---------- */
.legal { padding-block: var(--gap-block) 0; }
.legal h1 { font-size: var(--size-heading); line-height: var(--leading-heading); letter-spacing: normal; font-weight: var(--weight-title); }
.legal h1 + .draft { margin-block-start: var(--gap-label); }
.legal .prose { margin-block-start: var(--gap-line); }
.legal .prose h2 + * { margin-block-start: var(--gap-label); }
.legal h2 { margin-block-start: var(--gap-block); font-size: var(--size-reading); line-height: var(--leading-reading); }
.legal dl { max-width: var(--measure); }
.facts { display: grid; grid-template-columns: minmax(0, 1fr); margin-block-start: var(--gap-label); }
.facts div { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--gap-line); padding-block: var(--gap-label); border-block-start: var(--hair) solid var(--gridline); }
.facts div:last-child { border-block-end: var(--hair) solid var(--gridline); }
.facts dt { font-size: var(--size-readout); line-height: var(--leading-readout); color: var(--ink-dim); font-stretch: var(--width-readout); }
.facts dd { margin: 0; }
/* Entwurfshinweis und offene Stellen: Tinte statt Signal. Der Hinweis ist ein Kasten mit Haarlinie auf getoentem Grund
   (wie Code im Text), die offene Stelle steht fett; das Signal bleibt der Schnittmenge vorbehalten. */
.draft { max-width: var(--measure); padding: var(--gap-label) var(--gap-line); border: var(--hair) solid var(--gridline); border-radius: var(--radius-plate); background: color-mix(in srgb, var(--ink) var(--tint-code), transparent); font-size: var(--size-readout); line-height: var(--leading-readout); }
.gap { font-weight: var(--weight-label); }

/* ---------- Breite ---------- */
/* Unter 40rem: die Tabelle des Traces steht als Karten je Zeile (eine fuenfspaltige Tabelle laeuft bei 375 px
   seitwaerts). Die Kopfzeile bleibt fuer Bildschirmleser erhalten (sr-only), jede Zelle traegt ihren Namen davor. */
@media (max-width: 39.99rem) {
  .trace-data thead { position: absolute; inline-size: var(--hair); block-size: var(--hair); margin: calc(var(--hair) * -1); overflow: hidden; clip-path: inset(50%); }
  .trace-data table, .trace-data tbody { display: block; }
  .trace-data tr { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gap-in-line) var(--gap-label); padding-block: var(--gap-label); border-block-end: var(--hair) solid var(--gridline); }
  .trace-data th, .trace-data td { display: block; padding: 0; border: 0; text-align: start; }
  .trace-data tbody th { grid-column: 1 / -1; font-weight: var(--weight-title); }
  .trace-data td:nth-child(2) { grid-column: 1 / -1; color: var(--ink-dim); }
  .trace-data td::before { content: attr(data-label); content: attr(data-label) / ""; display: block; color: var(--ink-dim); font-stretch: var(--width-readout); }
  .trace-data td:nth-child(2)::before { display: none; }
}
@media (min-width: 40rem) {
  .bar ul { column-gap: var(--gap-label); }
  .counts { grid-template-columns: max-content minmax(0, 1fr); column-gap: var(--gap-block); }
  .counts li { grid-template-columns: subgrid; grid-column: 1 / -1; align-items: baseline; }
  .counts data { text-align: end; }
  .rows li { grid-template-columns: var(--key-col) minmax(0, 1fr); }
  .rows .k { padding-block-start: 0; display: flex; align-items: center; }
}
@media (min-width: 60rem) {
  .section { grid-template-columns: var(--label-col) minmax(0, 1fr); }
  /* Die Beschriftungsspalte bleibt beim Lesen im Bild: Ueberschrift, Zeitraum und die Spur im Kleinen. */
  .section > header { position: sticky; inset-block-start: var(--gap-block); }
}
/* Die Bildschirmfotos brechen aus der Textspalte nach links aus und nutzen die ganze Seite. */
@media (min-width: 60rem) {
  .shots { margin-inline-start: calc((var(--label-col) + var(--gap-block)) * -1); }
}

/* ---------- Kontrastmodus und Druck ---------- */
/* Platform fact: im Kontrastmodus (forced-colors) entfaellt der Hintergrund-Verlauf; ohne diese Regel stuenden nur Raster und
   Beschriftung da, die Spuren fehlten. Die Spuren nehmen Systemfarben: Tinte als CanvasText, Schnittmenge als Highlight;
   das Band wird zu zwei senkrechten Linien. */
@media (forced-colors: active) {
  .fill, .mark i {
    forced-color-adjust: none;
    background: linear-gradient(to right,
      CanvasText var(--cut-a),
      Canvas var(--cut-a) calc(var(--cut-a) + var(--sep-a) * var(--hair)),
      Highlight calc(var(--cut-a) + var(--sep-a) * var(--hair)) calc(var(--cut-b) - var(--sep-b) * var(--hair)),
      Canvas calc(var(--cut-b) - var(--sep-b) * var(--hair)) var(--cut-b),
      CanvasText var(--cut-b));
  }
  .band { forced-color-adjust: none; border-inline: var(--hair) solid Highlight; }
  .shot img { filter: none; }
}
/* Platform fact: Browser drucken Hintergruende standardmaessig nicht; Recruiter drucken oder speichern Seiten als PDF. */
@media print {
  .fill, .band, .mark i, .cursor { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  /* Das Einzeichnen gehoert auf den Bildschirm: wer sofort druckt, soll die fertigen Spuren sehen, nicht den Anfang der Bewegung. */
  .fill, .band { animation: none; }
  /* Hinweis und Anzeige sind Bedienung, kein Inhalt. */
  .trace-info { display: none; }
}
