/* =========================================================
   Life Archive – Design Tokens & Reset
   ========================================================= */

/* ---------- Schriften ---------------------------------------------------
   Drei Familien, alle selbst gehostet: kein CDN, keine Google-Fonts-
   Verknüpfung. Die App startet ohne Netz, und eine Schrift, die erst
   geholt werden muss, wäre offline keine.

   Es sind Variable Fonts – eine Datei je Familie deckt den ganzen
   Schnittbereich ab, statt sechs Dateien für sechs Stärken. Der
   Zeichenvorrat ist der Latin-Ausschnitt (U+0000–00FF und was Google
   sonst in „latin" legt); die deutschen Umlaute und das ß liegen darin.

   `font-display: swap` heißt: erst der Ersatz, dann die Schrift. Der
   Text steht sofort, statt unsichtbar auf eine Datei zu warten.

   Herkunft, Fassungen und Prüfsummen: assets/fonts/HERKUNFT.md
   Neue Datei hier heißt: in sw.js eintragen und VERSION heben.
   ----------------------------------------------------------------------- */

/* Oberfläche: Leisten, Karten, Knöpfe, Fließtext */
@font-face {
  font-family: "Spline Sans";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("../assets/fonts/SplineSans[wght].woff2") format("woff2");
}

/* Zahlen: Kennzahlen, Ringmitte, Uhren, Zähler */
@font-face {
  font-family: "Martian Mono";
  font-style: normal;
  font-weight: 100 800;
  font-stretch: 100%;
  font-display: swap;
  src: url("../assets/fonts/MartianMono[wght].woff2") format("woff2");
}

/* Lesen und Schreiben: die Blöcke in Journal, Notizen, Leseansicht.
   Zwei Achsen – `opsz` stellt der Browser über font-optical-sizing
   von selbst nach der Schriftgröße. */
@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("../assets/fonts/Newsreader[opsz,wght].woff2") format("woff2");
}

:root {

  /* ---------- Graustufen-Leiter --------------------------------------------
     Eine Leiter, hell nach dunkel. Sie ist die einzige Quelle für jeden
     unbunten Ton der App – vorher standen dieselben Werte als Hex-Literale
     in vierzehn Stilblättern verstreut, und niemand konnte sagen, wie viele
     Graus es eigentlich gibt (es sind 51).

     Die Reihenfolge ist Teil der Sache: --g-01 ist das hellste, --g-51 das
     dunkelste. Der E-ink-Modus (styles/eink.css) dreht die Leiter um und
     legt sie auf Papier, ohne eine einzige Regel eines Moduls anzufassen –
     das geht nur, weil sie geordnet ist.

     Die Flächen- und Textmarken darunter greifen in dieselbe Leiter.
     Modulfarben (--c-*) stehen dagegen weiter für sich: sie sind bunt und
     vom Nutzer änderbar. */
  --g-01: #ffffff;
  --g-02: #f4f4f6;
  --g-03: #f2f2f4;
  --g-04: #ececee;
  --g-05: #e8e9ec;
  --g-06: #e8e8ea;
  --g-07: #e7e7ea;
  --g-08: #e6e6e8;
  --g-09: #e4e4e6;
  --g-10: #e2e2e6;
  --g-11: #dcdce2;
  --g-12: #dcdce0;
  --g-13: #dcdcde;
  --g-14: #d6d9e7;   /* leicht blaustichig – Silhouette der Körperkarte */
  --g-15: #d8d8dc;
  --g-16: #d7d7db;
  --g-17: #d6d6da;
  --g-18: #d4d4d8;
  --g-19: #d3d4d8;
  --g-20: #d2d3d8;
  --g-21: #cfcfd3;
  --g-22: #cbcbcf;
  --g-23: #c9c9cf;
  --g-24: #b6bbd1;   /* Muskelpartie ohne Rang */
  --g-25: #b9bcc2;
  --g-26: #bcbcc1;
  --g-27: #b9b9c0;
  --g-27b: #a6adbb;  /* kühl – --text-muted, zwischen 27 und 28 */
  --g-28: #9aa0a8;
  --g-29: #9e9e9e;
  --g-29b: #949cad;  /* kühl – --text-subtle, zwischen 29 und 30 */
  --g-30: #7d8087;
  --g-31: #6f7278;
  --g-32: #6c7280;
  --g-33: #434a59;
  --g-34: #363c48;
  --g-35: #2b303c;
  --g-36: #262b35;
  --g-37: #232733;
  --g-38: #1e222b;
  --g-39: #1c1f26;
  --g-40: #1b1f28;
  --g-41: #1b1f27;
  --g-42: #15181f;
  --g-43: #15171d;
  --g-44: #12141a;
  --g-45: #10131a;
  --g-46: #0f1114;
  --g-47: #0e1013;
  --g-48: #0b0b0b;
  --g-49: #090b0d;
  --g-50: #05060a;
  --g-51: #000000;

  /* ---------- Paletten, die aus JavaScript kommen --------------------------
     Diese Töne standen als Zeichenketten in den Modulen: die Makrofarben in
     app/nutrition/charts.js und store.js, die Ampel in rating.js, der
     Nutri-Score in ui.js, die neun Ränge in app/fitness/ranks.js, die sechs
     Kennzahlen in app/fitness/screen.js, das Ersatzcover in
     app/reader/pdf.js.

     Sie stehen jetzt hier und werden dort über LA.dom.var() gelesen – mit
     demselben Wert als Rückfall, falls das Blatt einmal nicht da ist. Damit
     ist auch für sie eine einzige Stelle zuständig, und der E-ink-Modus
     erreicht sie mit. */

  /* Makros – Diagramme und Legenden der Ernährung */
  --chart-protein: #98e2f5;
  --chart-fat:     #f6d68a;
  --chart-carbs:   #f9b4c9;
  --chart-fiber:   #c7b3ff;

  /* Nährwert-Ampel (FSA) */
  --traffic-low:    #4ade80;
  --traffic-medium: #f6d68a;
  --traffic-high:   #f26c6c;

  /* ---------- Zustand: unter Ziel, nahe dran, darüber ---------------------
     Bis hierher hatte „über dem Ziel" keine eigene Farbe – die Balken
     griffen sich --c-fitness-stroke, weil das zufällig rot ist. Damit
     hieß dieselbe Farbe an einer Stelle „Fitness" und an der anderen
     „zu viel", und keine der beiden konnte sich ändern.

     Die drei Rollen erben von der Nährwert-Ampel darüber. Das ist kein
     Zufall: „unter dem Ziel", „knapp" und „darüber" sind dieselbe
     Aussage wie grün/gelb/rot auf einer Packung, und der E-ink-Modus
     dreht die Ampel bereits um (styles/eink.css). Ein Alias erbt das
     mit, ein neuer Hex-Wert hätte es verloren. */
  --state-good: var(--traffic-low);
  --state-warn: var(--traffic-medium);
  --state-bad:  var(--traffic-high);
  --state-over: var(--traffic-high);

  /* Nutri-Score A–E */
  --nutri-a: #2e7d32;
  --nutri-b: #7cb342;
  --nutri-c: #f6d68a;
  --nutri-d: #f39c3d;
  --nutri-e: #f26c6c;

  /* Rangleiter der Fitness */
  --rank-wood:      #9a7b56;   /* AA mit dunkler Schrift (5:1) */
  --rank-bronze:    #cd7f32;
  --rank-silver:    #b9c2cc;
  --rank-gold:      #f0c04a;
  --rank-platinum:  #6fe3c8;
  --rank-diamond:   #7cc6ff;
  --rank-champion:  #c7b3ff;
  --rank-olympian:  #ff9d5c;
  --rank-immortal:  #ff5c7a;
  /* Glanz und Schatten, in die jede Rangfarbe gemischt wird – daraus
     wird der Metallverlauf der Rangmarken (Ende von styles/fitness.css). */
  --rank-glint:     #ffffff;
  --rank-shade:     #000000;

  /* Kennzahlen der Fitness-Auswertung */
  --stat-workouts: #4ade80;
  --stat-lifted:   #e879b9;
  --stat-reps:     #60a5fa;
  --stat-sets:     #fb923c;
  --stat-heaviest: #f26c6c;
  --stat-time:     #facc15;

  /* Ersatzcover eines PDF ohne eigenes Titelbild */
  --cover-text: #1b1207;
  --cover-from: #b08968;
  --cover-to:   #86664b;

  /* ---------- Überzüge: dieselben Farben, nur als Zahlenpaar -------------
     Ein Rahmen, ein Hover, eine Trennlinie ist in dieser App fast nie eine
     Farbe, sondern ein durchscheinender Überzug: `rgba(255, 255, 255, .07)`
     – heller als das, was darunter liegt. Auf dunklem Grund stimmt das;
     auf Papier ist es nichts.

     Deshalb steht der Grundton jetzt als Zahlenpaar in einem Token und die
     Regel schreibt nur noch die Deckkraft: `rgba(var(--o), .07)`. Der Wert
     ist derselbe – der E-ink-Modus dreht `--o` auf Schwarz, und aus jedem
     hellen Überzug wird in einem Zug ein dunkler.

     Die --t-* darunter sind die Modultöne als Zahlenpaar, für ihre
     durchscheinenden Flächen und Rahmen. Sie hängen bewusst nicht an den
     --c-*: eine eigene Farbe aus den Einstellungen färbt die Flächen, nicht
     die Schleier. */
  --o:          255, 255, 255;   /* heller Überzug   */
  --o-deep:     0, 0, 0;         /* Schatten, Vorhang */
  --o-bg:       9, 11, 13;       /* --bg-deep als Überzug */
  --o-bg2:      4, 5, 8;
  --o-ink:      11, 14, 20;      /* Konturen der Körperkarte */

  --t-nutrition:           130, 245, 203;
  --t-calendar:            152, 226, 245;
  --t-meditation:          199, 179, 255;
  --t-meditation-stroke:   148, 123, 239;
  --t-cue-diary:           249, 180, 201;
  --t-journal:             246, 214, 138;
  --t-journal-stroke:      232, 196, 84;
  --t-fitness:             255, 167, 167;
  --t-fitness-stroke:      242, 108, 108;
  --t-reader:              176, 137, 104;
  --t-archive:             201, 204, 211;
  --t-cover:               27, 18, 7;

  /* Grund unter einem Akzent: die Schrift auf einem eingefärbten Knopf.
     Im dunklen Bild ist das der Grund der App selbst – im E-ink-Modus
     aber nicht, denn dort ist der Grund Papier und der Akzent hell.
     Also ein eigenes Token statt --bg. */
  --on-accent:  #0f1114;

  /* Schrift eines zerstörenden Handgriffs auf stiller Fläche (Delete im
     Halte-Menü, app/hold.js): laut wird erst die Rückfrage, nicht der
     Auslöser. 7,9:1 auf --surface. */
  --danger:     #ff9a9a;

  /* „Das braucht dich noch": eine verstrichene Uhrzeit, etwas von gestern
     (die Today-Karte auf Home, seit 13.10.2026). Bewusst NICHT --danger –
     spät ist keine Zerstörung. Amber, weg vom Rosa der Fitness und vom
     Gelb des Journals; 10,4 : 1 auf --surface. */
  --late:       #ffb86b;

  /* Gitternetz und Flächen in den SVG-Diagrammen, und der Ton eines
     Ranges, den es noch nicht gibt. Sie stehen als Zeichenkette in einem
     Attribut – var() kommt dort nicht durch, also liest sie
     app/nutrition/charts.js und app/fitness/screen.js über LA.dom.var(). */
  --grid-line:  rgba(255, 255, 255, .07);
  --grid-fill:  rgba(255, 255, 255, .10);
  --rank-none:  rgba(255, 255, 255, .22);
  --rank-dim:   rgba(255, 255, 255, .18);

  /* Flächen – Werte aus AppColors */
  --bg:            var(--g-46);   /* AppColors.background */
  --bg-deep:       var(--g-49);
  --surface:       var(--g-39);   /* AppColors.surface   */
  --surface-2:     var(--g-37);
  --surface-3:     var(--g-35);
  --line:          rgba(var(--o), .08);
  --line-strong:   rgba(var(--o), .16);
  --accent-app:    #c7b3ff;   /* AppColors.accent    */

  /* ---------- Text: sechs Rollen statt drei -------------------------------
     Vorher gab es --text (reines Weiß), --text-dim und --text-faint. Das
     war an beiden Enden falsch. Reines Weiß auf #0f1114 sind 18,5:1 – auf
     OLED der klassische Grund für Halation, und es bleibt kein Ton mehr
     übrig, mit dem sich etwas hervorheben ließe. Am anderen Ende lag
     --text-faint (#6f7278) bei 3,55:1 auf --surface-3 und damit unter den
     4,5:1, die WCAG AA für Fließtext verlangt – an 148 Stellen.

     Gemessen wird gegen den hellsten Grund, auf dem Schrift steht
     (--surface-3, #2b303c); auf --surface und --bg ist der Abstand größer.
     Die Zahl hinter jeder Zeile ist dieser schlechteste Fall.

     Die Töne sind leicht kühl gehalten, weil die Flächen es auch sind:
     #1c1f26, #232733 und #2b303c ziehen alle ins Blaue, während die alten
     Graus völlig neutral waren. Ein neutrales Grau auf kühlem Grund liest
     schmutzig.

     --text-dim und --text-faint bleiben als Aliasse stehen. Sie stehen an
     333 Stellen in dreizehn Stilblättern, und so erben die alle den
     richtigen Wert, ohne dass eine einzige Datei angefasst wird. Neue
     Regeln schreiben die Rollennamen. */

  --text-strong:   var(--g-01);    /* 13,2:1 – Überschrift, die eine Zahl,
                                      auf die es ankommt. Sparsam.        */
  --text:          var(--g-05);    /* 10,8:1 – Fließtext                  */
  --text-muted:    var(--g-27b);   /*  5,9:1 – zweite Ebene, Beschriftung */
  --text-subtle:   var(--g-29b);   /*  4,8:1 – Metadaten, Einheiten, Zeit */
  --text-disabled: var(--g-31);    /*  3,6:1 – nur abgeschaltete Knöpfe;
                                      WCAG nimmt die ausdrücklich aus.    */
  --ink:           var(--g-51);    /* AppColors.textBlack – Glyphen auf Hexagons */

  /* Alte Namen, neue Werte – siehe oben. */
  --text-dim:      var(--text-muted);
  --text-faint:    var(--text-subtle);

  /* Grund von Eingabefeldern: dunkler als die Karte, damit ein Feld
     als Vertiefung liest und nicht als weitere Fläche.

     Stand als `:root { --field-bg }` in styles/meditation.css – im
     Stilblatt eines einzelnen Moduls, gelesen von sieben. Das ist
     kein Modulton, sondern ein Token wie --surface, also steht es
     hier bei den anderen. Der Wert ist unverändert; einige Stellen
     schreiben ihn noch als Rückfall mit (`var(--field-bg, #0e1013)`),
     das schadet nicht. */
  --field-bg:      var(--g-47);

  /* ---------- Die Fläche eines stillen Knopfes ---------------------------
     Ein Knopf, den man nicht sieht, ist keiner.

     Die zweite Reihe – der Dreipunkt auf einer Karte, der Rückpfeil im
     Kopf eines Fensters, „From folder" neben „Import" – trug bis hierher
     entweder `transparent` oder dieselbe Fläche wie die Karte, auf der
     sie steht (`--surface-2` auf `--surface-2`). Gemessen sind das 1,0
     bis 1,3:1. Sichtbar blieb dann nur der Sockel, ein helles Rechteck
     zwei Pixel daneben – der Knopf las sich als leerer Umriss oder,
     neben einem gefüllten Akzentknopf, als abgeschaltet.

     Deshalb ein Überzug statt eines festen Grautons: `rgba(var(--o), …)`
     rechnet sich auf jeden Grund, auf dem ein Knopf landen kann – auf
     --bg, auf einer Karte, in einem Fenster – und kommt überall auf rund
     1,7:1 heraus. Das ist die Schwelle, ab der eine Fläche als Fläche
     liest und nicht als Schatten. Und weil --o im E-ink-Modus auf
     Schwarz kippt, wird aus dem hellen Überzug dort in einem Zug ein
     dunkler.

     Eingefärbte stille Flächen (Modulton statt Grau) können nicht
     beliebig heller werden: ein Braun oder Rosa, das kräftig genug für
     die Fläche wäre, nähme seiner eigenen Beschriftung den Kontrast –
     im Reader gemessen 3,8:1 bei 30 % Deckung, also unter den 4,5:1,
     die Fließtext braucht. Sie bleiben deshalb ruhig (--btn-tint-a)
     und tragen statt dessen eine Kante aus demselben Ton
     (--btn-tint-ring). Die Kante hebt den Knopf heraus, ohne unter der
     Schrift zu liegen – das ist der Umriss-Knopf, den Stripe und
     Linear für die zweite Reihe benutzen. */
  --btn-quiet:        rgba(var(--o), .18);
  --btn-quiet-hover:  rgba(var(--o), .26);
  --btn-quiet-lift:   rgba(var(--o), .30);
  /* Der Sockel unter einer stillen Fläche muss eine Stufe HELLER sein
     als sie, sonst verschwindet die Tiefe mit ihr: die Fläche liegt
     jetzt selbst bei 18 % und träfe den alten Sockel (--g-33) fast auf
     den Ton. Als Überzug rechnet er sich wie die Fläche auf jeden
     Grund und kippt im E-ink-Modus mit ihr ins Dunkle – die eigene
     Fassung dort (styles/eink.css) fällt damit weg. */
  --btn-tint-a:       .10;
  --btn-tint-hover-a: .32;
  --btn-tint-ring:    .50;
  --btn-tint-on-a:    .42;   /* eingeschaltet: kräftiger als die stille Fläche */

  /* ---------- Drei Stufen, nicht eine ------------------------------------

     Der Überzug oben (--btn-quiet) hat den Knopf sichtbar gemacht:
     gemessen 1,78:1 gegen die Fläche, auf der er steht. Das reicht, um
     ihn zu FINDEN. Es reicht nicht, um zu sagen, WELCHER von ihnen
     gemeint ist – und genau das war das Problem: im Account standen
     sechs gleich laute graue Balken untereinander, und keiner sagte,
     welcher der Weg nach vorn ist.

     Kontrast allein ist keine Hierarchie. Es braucht drei Stufen, und
     jede muss eine Aufgabe haben:

       Primary   die eine Handlung dieses Bildschirms. Volle
                 Akzentfläche des Bereichs, dunkle Schrift darauf.
                 Gemessen über 9:1. Höchstens einer je Bildschirm.

       Tinted    tragende Zweitwege – „Back Up Data" neben „Restore".
                 Eine schwache Fläche im Akzent UND eine Kante aus
                 demselben Ton in voller Stärke. Der Kontrast sitzt in
                 der Kante (rund 7:1), nicht in der Fläche – dadurch
                 bleibt die Schrift darauf lesbar, was bei einer
                 kräftigen farbigen Fläche nicht mehr der Fall wäre
                 (im Reader gemessen: 3,8:1 bei 30 % Deckung).
                 Kein Grau, und trotzdem ruhig.

       Quiet     Abbrechen, Zurück, Wegwischen. Keine Fläche, nur eine
                 Kante und die Schrift in --text. Wer nichts tut, soll
                 nicht schreien.

     Grau kommt darin nicht mehr vor. --btn-quiet bleibt trotzdem
     stehen: es ist der Grund unter einem Knopf, der zu keiner der
     drei Stufen gehört (Werkzeuge, Segmentschalter, Chips).

     Die vier Werte sind Anteile für color-mix auf --accent-deep, also
     auf die Randfarbe DES BEREICHS, in dem der Knopf steht. Kein
     eigener Farbwert: dadurch trägt derselbe Knopf im Journal Amber,
     im Reader Braun und im Cue Diary Rosé, ohne dass eine dieser
     Farben hier stünde – und der E-ink-Modus stimmt mit, weil er
     --accent-deep umlegt und sonst nichts. */

  --btn-tinted:       16%;   /* Fläche  – ruhig genug für Schrift darauf */
  --btn-tinted-hover: 30%;
  --btn-tinted-edge:  95%;   /* Kante   – hier sitzt der Kontrast */

  /* Der Sockel unter einem Umriss-Knopf ist die Kante selbst, eine
     Spur dunkler: eine Tiefe, die HELLER wäre als die Kante darüber,
     sähe aus wie ein Fehler im Druck. */
  --btn-tinted-lift:  60%;

  /* Modulfarben – Fill + Stroke aus AppColors */
  --c-nutrition:          #82f5cb;
  --c-nutrition-stroke:   #3ddbb0;
  --c-calendar:           #98e2f5;
  --c-calendar-stroke:    #50b8d4;
  --c-meditation:         #c7b3ff;
  --c-meditation-stroke:  #947bef;

  /* Meditation: die Abendfläche der Einladung auf Today und der Ring
     der laufenden Sitzung. Aus der Bereichsfarbe gemischt, damit eine
     geänderte Farbe (Settings › Colors) mitzieht. */
  --med-dusk:        color-mix(in srgb, var(--c-meditation-stroke) 16%, var(--surface));
  --med-ring:        var(--c-meditation);
  --med-ring-track:  rgba(var(--t-meditation), .16);

  --c-cue-diary:          #f9b4c9;
  --c-cue-diary-stroke:   #f2729f;
  --c-journal:            #f6d68a;
  --c-journal-stroke:     #e8c454;
  --c-reader:             #b08968;
  --c-reader-stroke:      #86664b;
  --c-finance:            #66bb6a;
  --c-finance-stroke:     #388e3c;
  --c-fitness:            #ffa7a7;
  --c-fitness-stroke:     #f26c6c;

  /* Fitness kennt seit 0.5.0 beta vier Arten. Seit 0.6.0 beta gibt es
     dafür KEINE eigenen Farben mehr (Aprikose, Gold, Flieder sind weg):
     der Bereich hat genau zwei Rottöne, und die Arten sind Stufen davon –
     voll (Gym), hell (Cardio), hell gedämpft (Dehnen), satt gedämpft
     (Sport). Jede Stufe hält auf --surface ≥ 3:1 als Grafik. Erkannt
     wird eine Art zuerst am Zeichen; Legenden tragen es mit. */
  --fk-gym:     var(--c-fitness-stroke);
  --fk-cardio:  var(--c-fitness);
  --fk-stretch: color-mix(in srgb, var(--c-fitness) 60%, var(--surface-3));
  --fk-sport:   color-mix(in srgb, var(--c-fitness-stroke) 70%, var(--surface-3));

  /* Pulszonen 1–5: die einzige neue Farbfamilie der Ausdauer. Kühl für
     ruhig, warm für hart – dieselbe Leiter färbt die Tempo-Linie auf der
     Karte. Gemessen auf --surface: jede Stufe ≥ 3:1 als Grafik. */
  --hz-1: #8ea2c0;
  --hz-2: #5fc6b4;
  --hz-3: #e6cf63;
  --hz-4: #f2a05a;
  --hz-5: #f26c6c;

  /* Das Archive – seit 0.6.0 beta ein Bereich wie die anderen (vorher
     ein Reiter von Home). Grau wie die Notizen selbst (.ar in
     styles/notes.css), damit nichts um Aufmerksamkeit ringt, was nur
     Text hält. */
  --c-archive:            #c9ccd3;
  --c-archive-stroke:     #787d88;

  /* Home – die Umgebung der App selbst (Topbar, Reiter, Now-Karte).
     Bis 0.6.0 beta dasselbe Lavendel wie Meditation (#c7b3ff) – Home
     sah damit aus wie ein Bereich unter vielen. Jetzt weiß: das Licht,
     in dem alle Bereichsfarben liegen (Night Pond, styles/look.css).
     Weiß stößt mit keinem Bereich zusammen, 18,9 : 1 auf --bg. */
  --c-home:               #ffffff;
  --c-home-stroke:        #d9dbe0;

  /* Eigene Tracker (app/calendar/trackers.js). Acht Töne ZWISCHEN den
     Bereichsfarben – ein Tracker sieht nie aus wie ein Bereich, und keiner
     sticht aus der Palette heraus. Gleiche Helligkeit wie --c-*, damit die
     Stufen im Jahr bei jedem Ton gleich lesen. */
  --tr-iris:    #93a9ff;
  --tr-lime:    #cde77f;
  --tr-amber:   #ffc27a;
  --tr-teal:    #6fd6c9;
  --tr-coral:   #ff9478;
  --tr-sky:     #7cc8ff;
  --tr-sand:    #e3cfa6;
  --tr-orchid:  #e29bf5;

  /* Das Jahr in Quadraten (Patterns): ein leeres Quadrat, die Speiche eines
     leeren Tages im Jahresring und der Sockel unter jeder Fläche dort. */
  --py-cell:         var(--g-37);
  --py-spoke:        var(--g-35);
  --py-future-line:  var(--g-36);
  --py-plinth:       var(--g-50);

  /* Hexagon-Geometrie */
  --hex-w:         clamp(84px, 25vw, 108px);
  --hex-h:         calc(var(--hex-w) * .866);
  /* flat-top: Spitzen links/rechts, H = 0.866 · W */
  --hex-clip:      polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
  /* pointy-top: Spitzen oben/unten, H = 1.1547 · W */
  --hex-clip-pointy: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  /* Randstärke relativ zur Breite – siehe .hex in components.css */
  --hex-rim-ratio: .05;

  /* Ring-Radien (Ellipse, 6 Positionen im 60°-Raster) */
  --ring-rx:       clamp(100px, 31vw, 150px);
  --ring-ry:       clamp(122px, 39vw, 182px);

  /* ---------- Layout: zwei Breiten, nicht eine ---------------------------
     Bis hierher gab es genau eine Breite – --shell – und die war 560px.
     Damit war die App auf jedem Bildschirm ein Telefon: auf dem Tablet
     stand dieselbe schmale Säule wie in der Hand, nur mit mehr Schwarz
     daneben.

     Jetzt sind es zwei, und sie meinen Verschiedenes:

       --shell   die Spalte, in der eine Umgebung wohnt. Sie wächst mit
                 dem Bildschirm: 560 -> 720 -> 960. Was von ihr lebt,
                 sind Raster – das Monatsgitter, die Rezepte, die
                 Buchrücken. Ihnen tut Platz gut.

       --board   die Spalte, in der die Karten einer Modulseite
                 stehen. Sie wächst mit, aber nicht bis ganz nach
                 außen: 560 -> 720 -> 780. Was in einer Karte steht,
                 sind Listen, Kacheln, Legenden und Zeilen – die
                 brechen sauber um und werden mit etwas mehr Platz
                 besser. Bei 900 wären es dagegen Zeilen, deren
                 rechtes Ende man suchen muss.

       --flow    die enge Spalte: Fließtext und die schwebenden
                 Leisten (Block-Leiste, Lauf-Streifen). Sie wächst
                 NICHT. 560px ist die Breite, in der diese App
                 entworfen wurde, und ein Absatz wird durch 780px
                 nicht besser. --flow zentriert sich in --board.

     --measure ist dasselbe Argument für den Fließtext im Buch und für
     die Beschreibungszeile unter einer Kartenüberschrift: 65 Zeichen
     ist die Zeile, die man noch am Stück liest.

     --figure ist die Gegenrichtung. Die Diagramme und die Körperkarte
     sind SVG mit festem viewBox und `width: 100%` – sie skalieren als
     Ganzes, ihre Beschriftung eingeschlossen. Ein Diagramm, das für
     340px gezeichnet ist, schreibt auf 900px Breite mit 26px, also
     größer als jede Überschrift der App. Deshalb haben sie eine
     eigene Grenze und stehen mittig in ihrer Karte, statt mit ihr
     mitzuwachsen. Siehe .fc und .bodymap in styles/fitness.css.

     Was in derselben Karte NEBEN dem Diagramm steht – Legende,
     Rangliste, Filter, Muskelliste –, bricht um statt zu skalieren
     und nimmt die Breite weiter ganz. Genau diese Trennung ist der
     Grund, warum --board breiter sein darf.

     Die Haltestellen (--bp-*) stehen als Token da, damit sie einen
     Namen haben – @media rechnet nicht mit var(), die Zahlen in den
     Abfragen sind deshalb ausgeschrieben. Gelegt sind sie auf das
     Ziel-E-ink-Tablet (rund 702x936 CSS-Pixel): 640 liegt darunter,
     900 darüber – das Gerät bekommt also die Tablet-Stufe mit ihrer
     unteren Reiterleiste, nicht die Schiene.

     --rail-w ist die Breite dieser Schiene, zu der die Tableiste ab
     900 wird. Der Wert ist --sh-tab-max (styles/shell.css): die
     Höchstbreite eines Reiters. Die Schiene ist genau ein Reiter
     breit, hochkant gestellt. */
  --bp-tablet:     640px;
  --bp-desktop:    900px;
  --rail-w:        128px;

  --shell:         min(560px, 100%);
  --board:         min(560px, 100%);
  --flow:          min(560px, 100%);
  --measure:       65ch;
  /* 520 ist gerechnet, nicht geschätzt: ein Diagramm ist auf 340
     viewBox-Einheiten gezeichnet und beschriftet sich mit 11px. Auf
     der Breite W schreibt es also mit 11 · W/340. Der Kartentitel
     darüber ist 17px – darüber darf die Achsenbeschriftung nicht
     kommen, sonst steht die Hierarchie auf dem Kopf. 17 · 340/11 =
     525, abgerundet 520. Das ist zugleich ungefähr die Größe, die
     die Diagramme und die Körperkarte auf 560er Karten schon
     hatten: sie bleiben, wo sie sind, und die Karte wächst um sie
     herum. */
  --figure:        520px;
  --gutter:        20px;
  --safe-top:      env(safe-area-inset-top, 0px);
  --safe-bottom:   env(safe-area-inset-bottom, 0px);
  /* Es gab nur oben und unten. Auf einem Gerät mit gebogenem Rand hat
     `viewport-fit=cover` aber auch links und rechts einen Streifen, den
     die Hardware verdeckt – quer gehalten sind das gut vierzig Pixel je
     Seite, hochkant je nach Gerät ein paar. Was dort hineinragt, ist
     nicht weg, sondern nur nicht zu sehen: genau das Gefühl von „zu
     breit". */
  --safe-left:     env(safe-area-inset-left, 0px);
  --safe-right:    env(safe-area-inset-right, 0px);

  /* Motion */
  --ease-out:      cubic-bezier(.22, 1, .36, 1);
  --ease-spring:   cubic-bezier(.34, 1.56, .64, 1);
  --dur-fast:      140ms;
  --dur:           260ms;
  --dur-slow:      480ms;

  /* Radius / Schatten */
  /* Der Eckschnitt der Popups: ein Rechteck, dem links oben und rechts
     unten die Ecke fehlt. Diagonal gegenüber, nicht nebeneinander –
     zwei Schnitte auf derselben Seite lesen schief. Eine feste Länge,
     kein Prozentsatz: ein kleines Popup und ein hohes Sheet sollen
     denselben Schnitt tragen, sonst gehören sie nicht zusammen. */
  --cut:           18px;

  /* Der Schalter. Es gab zwei davon in der App – den in der Layer-Liste
     und den aus dem Reader, den Sheet, Bereichs-Einstellungen und
     Kalender mitbenutzen. Der eine war ein Rechteck, der andere eine
     Kapsel. Ihre Maße und Farben stehen jetzt hier, damit beide
     dasselbe Ding sind und es auch bleiben. */
  --sw-w:          42px;
  --sw-h:          24px;
  --sw-knob-w:     15px;
  --sw-knob-h:     16px;
  --sw-inset:      4px;
  --sw-travel:     19px;

  --r-sm:          10px;
  --r-md:          16px;
  --r-lg:          22px;
  --shadow-1:      0 2px 10px rgba(var(--o-deep), .45);
  --shadow-2:      0 12px 34px rgba(var(--o-deep), .55);

  /* Drei Rollen, drei Familien. Die Ersatzstapel dahinter greifen nur,
     solange die eigene Datei noch nicht da ist (font-display: swap). */
  --font:       "Spline Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
                "Helvetica Neue", Arial, sans-serif;
  --font-serif: "Newsreader", Georgia, "Times New Roman", serif;
  --font-mono:  "Martian Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
}

/* ---------- Warum --cut-clip auf * steht und nicht auf :root ------------

   Es stand auf :root, direkt neben --cut, und war damit kaputt – nur
   sah man es nicht.

   Eine Custom Property wird dort aufgelöst, wo sie DEKLARIERT ist. Auf
   :root deklariert, hat --cut-clip sein var(--cut) also gegen die 18px
   von :root eingesetzt und diesen fertigen Wert an alle Nachfahren
   vererbt. Ein `--cut: 9px` am Knopf setzte danach zwar die Variable,
   aber niemand las sie mehr: die Kontur war längst gerechnet.

   Das Ergebnis war ein Schnitt von 18px an JEDEM Element der App –
   auch an einem 36px-Knopf, dem damit die halbe Ecke fehlte, und an
   den kleinen Marken, denen er in die Glyphen lief. Genau die
   Fehlerbilder, die sich niemand erklären konnte: die ▲▼-Pfeile, das
   Zurücksetzen-Zeichen, die Zählmarke der Kategorien und die
   TEST-Marke haben alle Ecken ihrer Zeichen verloren.

   Auf `*` deklariert, rechnet jedes Element die Kontur mit SEINEM
   eigenen --cut. Ein Element ohne eigenen erbt weiter die 18px von
   oben, also ändert sich dort nichts. Pseudoelemente matchen `*`
   nicht, erben die fertige Kontur aber von ihrem Ursprungselement –
   deshalb stimmen ::before und ::after eines Knopfes weiterhin mit
   seiner Fläche überein.
   ----------------------------------------------------------------------- */

* {
  --cut-clip: polygon(var(--cut) 0, 100% 0, 100% calc(100% - var(--cut)),
                      calc(100% - var(--cut)) 100%, 0 100%, 0 var(--cut));
}

/* ---------- Die zwei Haltestellen ---------------------------------------
   Nur --shell und --gutter wachsen. --flow bleibt, wo es ist – das ist
   sein ganzer Sinn.

   --shell steht bewusst als `min(…, 100%)` da und nicht als
   `calc(100vw - var(--rail-w))`. Der Prozentwert rechnet gegen den
   Elternkasten, und der ist ab 900 schon um die Schiene eingerückt
   (`body[data-shell] { padding-left }` in styles/shell.css). Damit
   stimmt die Breite von selbst – für die Startseite, die keine
   Schiene hat, genauso wie für die Umgebungen, die eine haben. 100vw
   könnte beides nicht: es zählt die Bildlaufleiste mit und weiß
   nichts von der Schiene. */

@media (min-width: 640px) {
  :root {
    --shell:  min(720px, 100%);
    --board:  min(720px, 100%);
    --gutter: 24px;
  }
}

@media (min-width: 900px) {
  :root {
    --shell:  min(960px, 100%);
    /* Bewusst schmaler als --shell: die Karten stehen als Spalte in
       der Mitte, links und rechts bleibt Luft. Ein Raster (Monat,
       Rezepte, Regal) nimmt daneben die ganze --shell – der
       Unterschied zwischen beiden ist genau der Punkt. */
    --board:  min(780px, 100%);
    --gutter: 32px;
  }
}

/* Große Bildschirme. Bei 960 blieb auf einem 1920er Monitor mehr als
   die Hälfte der Fläche leer – links und rechts je ein Streifen breiter
   als die ganze App. Ab 1280 wachsen --shell und --board deshalb weiter
   mit; die Raster bekommen Spalten, die Karten Luft. --flow und
   --figure bleiben, was sie sind: ein Absatz und ein Diagramm werden
   durch Breite nicht besser. */
@media (min-width: 1280px) {
  :root {
    --shell:  min(1240px, 100%);
    --board:  min(980px, 100%);
    --gutter: 36px;
  }
}

@media (min-width: 1600px) {
  :root {
    --shell:  min(1560px, 100%);
    --board:  min(1180px, 100%);
    --gutter: 40px;
  }
}

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

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

body {
  margin: 0;
  min-height: 100dvh;
  /* Unter 320px wird nicht mehr gequetscht, sondern geschoben. Das
     ist schmaler als jedes Gerät, das diese App je sehen wird – die
     Grenze ist da, damit ein sehr schmales Fenster am Rechner die
     Leisten nicht ineinanderschiebt. */
  min-width: 320px;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  overscroll-behavior-y: none;
  -webkit-tap-highlight-color: transparent;

  /* Der Streifen, den die Hardware verdeckt (gebogene Ecken, Kamera).
     Auf jedem Gerät ohne so einen Rand sind beide Werte 0 und die
     Zeile tut nichts. Sie steht am body und nicht an jeder Karte
     einzeln: was hier hineingeschoben wird, gilt für alles darunter. */
  padding-inline-start: var(--safe-left);
  padding-inline-end: var(--safe-right);
}

h1, h2, h3, h4, p, figure, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg { display: block; max-width: 100%; }

button {
  font: inherit;
  color: inherit;
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
}

/* Safari auf iOS zoomt in die Seite, sobald ein angetipptes Feld kleiner
   als 16px schreibt – und zoomt danach nicht von selbst zurück. Die Grenze
   gilt deshalb überall, wo wirklich getippt wird. Der Block-Editor
   (contenteditable) ist davon nicht betroffen und behält seine Typografie. */
input, select, textarea { font-size: 16px; }

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

:focus-visible {
  outline: 2px solid rgba(var(--o), .75);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: fixed;
  top: 8px;
  inset-inline-start: 50%;
  transform: translate(-50%, -200%);
  z-index: 100;
  padding: 10px 16px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  font-size: 14px;
  transition: transform var(--dur) var(--ease-out);
}
.skip-link:focus-visible { transform: translate(-50%, 0); }

/* App-Shell / Router-Outlet */
.app {
  width: var(--shell);
  min-width: 320px;
  margin-inline: auto;
  padding: 0 var(--gutter) calc(56px + var(--safe-bottom));
  outline: none;
}

/* Einspaltiger Fluss: Text, Listen, Formulare. Er hört bei --flow auf
   und steht dabei in der Mitte von --shell. Wer breiter sein darf –
   ein Raster – hebt die Grenze bei sich selbst wieder auf. */
.flow { max-width: var(--flow); margin-inline: auto; }

/* View-Wechsel-Animation */
/* `backwards` statt `both`: sichtbar ist es dasselbe – der Anfangszustand
   gilt vor dem Start, der Endzustand ist ohnehin der natürliche. `both`
   hält zusätzlich die Vorwärtsfüllung, und damit bleibt die ganze View
   dauerhaft eine eigene Compositing-Ebene, auch lange nach der halben
   Sekunde Animation. Bei einer 2 300 px hohen Seite ist das eine große
   Ebene für nichts. */
.view { animation: view-in var(--dur-slow) var(--ease-out) backwards; }
/* Neu gezeichnet, weil Daten von anderswo kamen: ohne Bewegung (router.refresh). */
.view--still { animation: none; }

@keyframes view-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

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

/* Dasselbe, wenn es in der App eingestellt ist (Sheet › App › Reduce
   motion) – für Geräte, deren Systemeinstellung man nicht ändern kann. */
html[data-motion="reduce"] { scroll-behavior: auto; }
html[data-motion="reduce"] *,
html[data-motion="reduce"] *::before,
html[data-motion="reduce"] *::after {
  animation-duration: .001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: .001ms !important;
}
