@font-face {
    font-family: 'Geologica';
    src: url('../font/Geologica/Geologica-VariableFont_CRSV,SHRP,slnt,wght.ttf') format('truetype');
    font-weight: 100 900;
    font-display: swap;
}

/* ===========================================================================
   DESIGN-TOKENS — die EINZIGE Token-Quelle der Datei (DESIGN_SYSTEM.md §10).
   Bis PRIO-2 lagen hier drei konkurrierende `:root`-Blöcke (Brutalism oben,
   „Bevel-Standard" und „Elevation" weiter unten); der jeweils letzte gewann,
   womit die DS-Werte oben wirkungslos waren. Jetzt steht alles hier — wer
   einen Wert ändern will, ändert ihn an genau dieser Stelle.
   Offen bleibt DS-03: die ~120 Alt-Literale im Regelwerk unten auf diese
   Tokens migrieren (`#f0f0f0`, doppelte Greens, Brutalism-Reste).
   =========================================================================== */
:root {
    /* --- Flächen & Struktur ------------------------------------------------ */
    --surface-color: #ffffff;   /* ein höchstes Weiss für ALLE Flächen */
    /* Ebene 3 — was FREI über allem schwebt (Bottom-Nav, Sheets, Toasts).
       Im Hellen dasselbe Weiss: dort trennt der Schlagschatten. Im Dunkeln
       trägt die Helligkeit die Tiefe (DS §12), also muss das, was über einer
       Card schwebt, HELLER sein als sie — sonst verschwindet die Nav, sobald
       eine Card unter ihr durchläuft. */
    --surface-raised: #ffffff;
    /* Der Licht-Rim des Bevels (die helle Kante oben auf erhabenen Flächen).
       Auf Weiss darf er fast deckend sein — man sieht ihn kaum. Auf einer
       dunklen Card wäre derselbe Wert eine leuchtende Umrandung; im Dunkeln
       ist er darum nur noch eine Andeutung (DS §12). */
    --bevel-light: rgba(255, 255, 255, 0.95);
    --bg-color:      #ffffff;   /* Canvas = Hochweiss (Endtyp; vormals Grau #efeef4) */
    --surface-light: #f1eff8;   /* eingelassene Spur: Poll-Balken, graue Feld-Tiles */
    --shadow-rgb:    13, 35, 69;       /* Brand-Dunkelblau getönt — nie reines Schwarz */
    --hairline:      rgba(0, 0, 0, 0.12); /* einzige Border-Ausnahme: Segment-/Filter-Tiles */

    /* --- Text: Schwarz + zwei Graustufen ----------------------------------- */
    --text-primary:   #1f1f1f;
    --text-secondary: #5c5c5c;
    --text-muted:     #767676; /* dezent: Sub-Labels, Zeit, Platzhalter, Counts.
                                  Solid statt rgba, damit überlappende Icons (z.B. die
                                  Personen-Symbole mit -2px margin-left) keine dunkleren
                                  Schnittflächen erzeugen.
                                  #767676 ist das DUNKELSTE Grau, das auf Weiss noch
                                  4.5:1 hält (4.54:1) — die AA-Schwelle für normalen
                                  Text. Vorher #969696 mit 2.96:1: das reisst sogar die
                                  3:1 für grosse Schrift. Heller darf dieser Ton nie
                                  werden, sonst wird Meta unlesbar. */
    --text-light:     #ffffff; /* nur noch für Text auf Rosa (Weiss auf Rosa, DS §2) */

    /* --- Brand -------------------------------------------------------------- */
    --accent-yellow: #FFE600;   /* Updates */
    --accent-pink:   #FF0066;   /* Pins & Boards */
    --accent-turkis: #52cda7;   /* neutral: Freunde, Aktionen, Notifications, Suche */
    --danger:        #DC2626;   /* Löschen / Melden / Fehler / Warnsignal */
    /* Das Gegenstück zu --danger (*2026-08-04 von Jascha*): ein Zustand, der
       läuft und laufen soll. Bewusst der gleichwertige Gegenton derselben
       Palette — #DC2626 ist deren Rot, #16A34A deren Grün —, damit die beiden
       als Paar lesbar sind und nicht als zwei Zufallsfarben.
       NICHT dasselbe wie Türkis: Türkis ist die neutrale Brandfarbe (Freunde,
       Aktionen, Suche) und sagt nichts über gut oder schlecht.
       Es steht am AKTIVEN Zustand, nicht am inaktiven — auf der Karte am
       „Jetzt" des eigenen Standorts, während er geteilt wird. Der ausgeschaltete
       Fall bleibt grau: gedämpft wird, was gerade nicht stattfindet. */
    --success:       #16A34A;   /* aktiv, und so gewollt */
    /* Dunkelblau ist als FLÄCHENfarbe abgeschafft (DS §2): --primary-color zeigt
       auf Türkis, damit Such-Zunge & Co. mitziehen, ohne jede Regel anzufassen.
       Dunkelblau lebt nur noch in --shadow-rgb und im Splash-Logo (dort hart
       gesetzt, weil Gelb auf Türkis nur ≈1,6:1 ergäbe). */
    --primary-color: #52cda7;

    /* --- Typografie --------------------------------------------------------- */
    --font-main: 'Geologica', -apple-system, BlinkMacSystemFont, sans-serif;
    --font-logo: 'Geologica', -apple-system, BlinkMacSystemFont, sans-serif;

    /* --- Geometrie & Tiefe --------------------------------------------------- */
    --radius-sm: 12px;
    --radius-md: 18px;
    --radius-lg: 24px;
    --radius-full: 9999px;
    --icon-sw: 2.4;             /* Outline-Strichstärke der SVG-Icons (live justierbar) */

    /* --- Die schwebende Bottom-Nav (GLOB-16) --------------------------------
       Zwei Zahlen, an denen mehr hängt als die Nav selbst: wie hoch sie über
       dem unteren Rand schwebt und wie hoch sie ist. Wer unter dem Feed Platz
       freihält (`#app`) oder etwas über der Nav platziert (die Seiten-Punkte im
       Chooser), rechnet ab hier — vorher standen dort ausgerechnete Zahlen
       (96px, 70px) samt Kommentar, der die 14px nannte. Ein solcher Kommentar
       ist kein Verweis: er veraltet still, sobald jemand die Nav verschiebt.
       Höher gesetzt am 2026-08-04 (14px → 21px, das 1,5-Fache). */
    --nav-bottom: 21px;         /* Abstand Nav-Unterkante ↔ Screen-Unterkante */
    --nav-h: 56px;              /* Höhe der Nav-Leiste */
    /* Oberkante der Nav über dem Screen-Boden — der Bezugspunkt für alles,
       was „über der Nav" sitzt. */
    --nav-top: calc(var(--nav-bottom) + var(--nav-h));
    /* (Hier standen `--shadow-solid`/`-sm` und `--border-thick`/`-thin` — die
       vier Brutalism-Tokens. Mit DS-03 hat sie niemand mehr referenziert; die
       Schatten-Werte waren ohnehin schon auf weiche Brand-Schatten umgebogen,
       die Border-Werte trugen als einzige noch reines Schwarz. Weg damit: was
       Tiefe braucht, holt sie aus den Ebenen-Regeln in §3.) */
}

/* ===========================================================================
   DARK MODE (GLOB-02) — die Tokens von oben, dunkel gelesen
   ---------------------------------------------------------------------------
   Spec: DESIGN_SYSTEM.md §12. Kurzfassung des Kerngedankens:

     IM DUNKELN TRÄGT DIE HELLIGKEIT, WAS IM HELLEN DER SCHATTEN TRÄGT.

   Im Hellen sind Canvas und Card dasselbe Weiss und werden allein durch Tiefe
   getrennt. Das lässt sich nicht übersetzen — ein dunkelblauer Schatten auf
   dunklem Grund ist unsichtbar. Also kehrt sich das MITTEL um, nicht die
   Aussage: was gegen mich ragt, fängt mehr Licht (Card heller als Canvas), was
   weggedrückt ist, weniger (Spur dunkler als die Fläche). Die drei Höhen aus
   DS §1 bleiben vollständig erhalten.

   Umgeschaltet wird über den Mond links im Home-Header (GLOB-02).
   =========================================================================== */
/* NUR ÜBER DEN SCHALTER, NIE ÜBER DAS SYSTEM (*2026-08-05 von Jascha*: „Dark
   Mode bitte im Home-Tab oben links als Mond-Symbol aktivieren oder
   deaktivieren. Nie vom System-Status ziehen.").
   Die frühere `@media (prefers-color-scheme: dark)` ist ersatzlos weg: mit ihr
   entschied das Telefon, und der Mond hätte nur dagegen angeschaltet — zwei
   Quellen für einen Zustand. Jetzt gibt es genau eine, und sie steht als
   `data-theme` am <html> (gesetzt in app.js).

   GEMERKT WIRD SIE NICHT — jeder Start ist hell (*2026-08-05 von Jascha*).
   Der helle Modus ist die Referenz, und eine Referenz, die man einmal verlässt
   und danach nie wieder von selbst sieht, ist keine. Begründung ausführlich in
   app.js bei `applyTheme`. *(Kurz stand die Wahl in localStorage. Das war
   richtig für einen fertigen Modus und falsch für einen Entwurf.)* */
:root[data-theme="dark"] {
    --surface-color: #1A2434;
    --surface-raised: #232F42;
    --bevel-light: rgba(255, 255, 255, 0.07);
    --bg-color:      #0E1523;
    --surface-light: #0A101B;
    --hairline:      rgba(255, 255, 255, 0.14);
    --text-primary:   #EEF1F6;
    --text-secondary: #AFBACA;
    --text-muted:     #8B98AB;
    --accent-yellow: #E8D200;
    --shadow-rgb: 0, 0, 0;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    /* Kein blaues Rechteck beim Antippen. Mobile Browser blenden es auf allem
       ein, was sie für einen Link oder Button halten — in der Bottom-Nav
       blitzte es bei jedem Tab-Wechsel auf. Es ist die Rückmeldung des
       BROWSERS, nicht die der App — und die App gibt ihre eigene dadurch, dass
       etwas PASSIERT (Seite öffnet, Filter greift, Haken erscheint), nicht
       durch ein Wippen unter dem Finger (DS §7, siehe Merksatz unten). */
    -webkit-tap-highlight-color: transparent;
}

/* Keine Scrollbalken. Eine App zeigt keine Bildlaufleiste — sie ist ein
   Bedienelement des Fensters, und hier gibt es kein Fenster. Gescrollt wird
   trotzdem: das ist nur die Anzeige, nicht die Funktion.
   Beide Schreibweisen, weil sie sich die Browser teilen: `scrollbar-width`
   (Firefox, neuere Chromium) und das WebKit-Pseudoelement. */
html {
    scrollbar-width: none;
    -ms-overflow-style: none;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar,
*::-webkit-scrollbar {
    width: 0;
    height: 0;
}
* {
    scrollbar-width: none;
}

/* Formular-Elemente erben die Schrift NICHT — ohne diese Zeile rendert jeder
   Button, dem `font-family: inherit` fehlt, in der System-Schrift statt in der
   App-Schrift. Genau daran lag es bei „200 folgen": der Follower-Link ist ein
   <button> und stand darum in einer anderen Schrift als die Friend-/FoF-Zahlen
   direkt darunter. Rund zwei Dutzend weitere Buttons hatten dasselbe Problem,
   nur ohne direkten Nachbarn zum Vergleich. Einmal zentral statt an jedem
   Control einzeln — und einmal vergessen. */
button,
input,
select,
textarea {
    font-family: inherit;
}

/* Disable native overscroll bounce — otherwise the sticky header visually
   drags along when the user pulls down past scroll top. Pull-to-refresh is
   handled by the custom indicator (see #pull-refresh-indicator). */
html, body {
    overscroll-behavior-y: none;
}

body {
    font-family: var(--font-main);
    background-color: var(--bg-color);
    color: var(--text-primary);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}

#app {
    max-width: 600px;
    margin: 0 auto;
    min-height: 100vh;
    position: relative;
    /* Platz unter dem Feed für die schwebende Nav: ihre Oberkante plus die
       26px Luft, die dort vorher schon standen (14 + 56 + 26 = 96). */
    padding-bottom: calc(var(--nav-top) + 26px);
    background: var(--bg-color);
}

/* ===========================================================================
   DESKTOP-RAHMEN — die App bleibt eine Telefon-App
   ---------------------------------------------------------------------------
   Auf einem breiten Screen steht in der Mitte ein Telefon, und die App läuft
   darin (iframe, siehe index.html). So braucht es keine zweite, breite Fassung
   der App — und keine Sonderfälle im Code: das iframe bringt ein eigenes
   Viewport mit, also stimmen sticky Header, schwebende Nav, die Fullscreen-
   Overlays (`position: fixed`) und jedes `window.scrollY` von selbst.

   Auf schmalen Screens ist von alldem nichts aktiv: `#desk-shell` ist versteckt
   und sein iframe hat kein `src`.
   =========================================================================== */
#desk-shell { display: none; }

html.desk,
html.desk body {
    height: 100%;
    overflow: hidden;                 /* die Seite selbst scrollt nicht — das Telefon tut es */
}
/* EINE Regel statt einer Liste: alles ausser dem Rahmen fällt weg — auch das,
   was zur Laufzeit an den Body gehängt wird (Sheets, Toasts, Tooltips). Sonst
   müsste diese Liste jedes neue Overlay kennen. */
html.desk body > *:not(#desk-shell) { display: none !important; }

html.desk #desk-shell {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    height: 100%;
    /* Eine ruhige Bühne, nicht Weiss: sonst verschwämme das weisse Telefon-
       Display mit der Seite und der Rahmen wäre der einzige Hinweis darauf,
       wo die App aufhört. */
    background: var(--surface-light);
}
.desk-device {
    position: relative;
    width: 390px;
    /* Höhe des iPhone-Displays, aber nie höher als das Fenster: auf einem
       kurzen Laptop-Screen soll das Gerät schrumpfen statt abgeschnitten zu
       werden. */
    height: min(844px, calc(100vh - 7rem));
    padding: 12px;
    border-radius: 56px;
    background: #10131a;              /* Gehäuse — bewusst kein Token: das ist kein UI der App */
    box-shadow:
        0 32px 60px rgba(var(--shadow-rgb), 0.28),
        0 0 0 1px rgba(255, 255, 255, 0.06) inset;
}
.desk-device iframe {
    width: 100%;
    height: 100%;
    border: none;
    border-radius: 44px;
    background: var(--surface-color);
    display: block;
}
.desk-note {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-weight: 600;
}

/* === GLOB-06 · Start-Up-Splash === weisser Screen + Brand-Logo-Treatment
   (blaues Rechteck / gelbe Schrift / rosa Punkt — dieses Treatment NUR hier,
   im Header ist `friends.` schlichter Text). ~0.8s halten + 0.2s Fade = 1s. */
#splash {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-color);
    animation: splash-out 1s ease forwards;
}
.splash-logo {
    font-family: var(--font-logo);
    font-weight: 900;
    font-size: 2.8rem;
    letter-spacing: -1px;
    line-height: 1;
    color: var(--accent-yellow);
    /* Einzige Stelle, an der Dunkelblau als FLÄCHE überlebt (DS §2): das
       Logo-Treatment. Gelb auf Türkis wäre nur ≈1,6:1, auf Dunkelblau ≈10,4:1
       — darum hier hart gesetzt statt über --primary-color. */
    background: #0D2345;
    padding: 0.45rem 1.25rem;
    border-radius: 16px;
}
.splash-dot { color: var(--accent-pink); }
@keyframes splash-out {
    0%, 80% { opacity: 1; }
    100% { opacity: 0; visibility: hidden; }
}

/* --- Typography --- */
h1, h2, h3 { font-weight: 800; letter-spacing: -0.5px; }
h2 { font-size: 1.5rem; margin-bottom: 0.5rem; }
h3 { font-size: 1.25rem; }
p { color: var(--text-secondary); font-size: 0.95rem; font-weight: 500; }

/* ==========================================================================
   KEIN WIPPEN BEIM ANTIPPEN — global (*2026-08-04 von Jascha*)
   --------------------------------------------------------------------------
   Karten, Knoepfe und Pillen hatten ein `:active { transform: translate(…) }`:
   beim Beruehren kippten sie kurz nach hinten. Das war die letzte Geste aus der
   Brutalism-Zeit, in der ein Knopf sichtbar „gedrueckt" wurde — auf einer
   Oberflaeche, die sonst nichts huepfen laesst, liest es sich als Wackeln.
   26 Regeln sind dafuer gefallen. Die Rueckmeldung auf einen Tap ist das, was
   danach passiert: die Seite oeffnet, der Filter greift, der Haken erscheint.
   Wer hier eine neue `:active`-Verschiebung einbaut, macht das rueckgaengig.
   ========================================================================== */

/* --- Header --- */
.app-header {
    position: sticky;
    top: 0;
    z-index: 100;
    /* Auf dem Geraet (PWA / Vollbild) liegt oben die System-Leiste. Ohne diese
       Zeile schiebt sich der Header darunter und die Rubrik-Pille wird
       angeschnitten. Im Browser ist der Wert 0 — die Zeile kostet dort nichts. */
    padding-top: env(safe-area-inset-top, 0px);
    background: var(--surface-color);
    color: var(--text-primary);
    /* Sanfter Übergang in den Suchmodus (Header wird türkis) — plus das
       Polster des eingeklappten Zustands, in derselben Dauer wie die Logo-Zeile
       (0.26s): die Rubrik-Reihe soll mit ihr fahren, nicht vorher springen. */
    transition: background-color 0.25s ease, color 0.25s ease, padding-top 0.26s ease;
}
/* --- Suchmodus (global) ---
   Wenn die Suchzunge offen ist, wird der gesamte Header türkis und der Inhalt
   des aktuellen Tabs verschwindet hinter einer weissen Fläche. So liegt unter
   Header (türkis) → Such-Filter-Reihe (türkis) → weisser Leerraum, auf dem
   später Resultate / Mobile-Tastatur sitzen.
   Tinte: SCHWARZ, nicht weiss (DS §2 „Schwarz auf Türkis" — weiss läge bei
   ≈2:1, schwarz bei ≈10,6:1). Vormals war der Header dunkelblau, da war weiss
   richtig. */
.app-header.search-active {
    background: var(--primary-color);
    color: #000;
}
.app-header.search-active .tab-icon {
    color: rgba(0, 0, 0, 0.6);
}
/* Auf Tabs ohne Rubriken (Boards / Chats / Profile) wird `.tabs-container`
   per Inline-Style auf `display: none` gesetzt. Im Suchmodus müssen wir den
   Slot zurückholen — sonst klappt `.tabs-wrap` auf Höhe 0 zusammen, der
   dunkelblaue Header endet oberhalb der Such-Kategorien und nur ein
   weisser Streifen bleibt unter den Kategorien sichtbar. `!important`
   nötig weil es gegen einen Inline-Style kämpft. Die Tabs selbst bleiben
   via opacity:0/pointer-events:none unsichtbar — sie halten nur den
   vertikalen Slot offen, damit der Header sauber bis unter die
   Such-Kategorien durchgeblockt ist. */
.app-header.search-active .tabs-container {
    display: flex !important;
}
body.search-active #app {
    background: var(--surface-color);
}
body.search-active main {
    visibility: hidden;
}

.header-top {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 1rem 1.5rem;
    /* EINE HÖHE FÜR DIE LOGO-ZEILE, EGAL WAS DRINSTEHT (GLOB-18).
       Glocke und Such-Zunge sind `position: absolute` in dieser Zeile und
       zentrieren sich in ihr. Die Zeile war aber verschieden hoch: auf Home
       steht dort das Brand-Logo (1.8rem → 43.2px), auf den anderen Tabs ein
       Tab-Titel (1.3rem → 31.2px). 12px Unterschied = **6px Versatz**, und
       genau um die sprangen Glocke und Zunge beim Wechsel Home ↔ Boards —
       den zwei Tabs, die sich diese Griffe teilen.
       2.7rem = die 43.2px der Logo-Zeile; die Höhe ist damit die des grössten
       Inhalts, Home bleibt also, wie es war. Die anderen drei Tabs werden um
       diese 12px höher — dafür steht der Header überall gleich (DS §8: fester
       Rahmen des Screens) und nichts springt mehr, in keiner Kombination. */
    min-height: calc(2.7rem + 2rem);   /* Inhalt + das 1rem Polster oben/unten */
    position: relative;
    /* Für den eingeklappten Zustand (siehe `.app-header.is-collapsed`): die
       Zeile fährt zusammen, statt zu verschwinden — `display: none` spränge. */
    overflow: hidden;
    /* Eine Dauer für alles, damit die Bewegung als EINE gelesen wird — und
       dieselbe, die `HEADER_ANIM_MS` in feed.js sperrt (0.26s). Laufen Höhe
       und Deckkraft verschieden lang, ist die Zeile schon unsichtbar, während
       sie noch zusammenfährt: das liest sich als Ruckeln, nicht als Falten. */
    transition: height 0.26s ease, padding 0.26s ease, opacity 0.26s ease;
}

/* --- Eingeklappter Header (nur im Home-Feed) ------------------------------
   Wer liest, braucht die Marke nicht — er braucht zu wissen, in welcher
   Rubrik er ist. Nach ein paar Zeilen gibt der Header darum die Logo-Zeile ab
   und lässt nur die Rubrik-Pillen stehen. Zurück kommt sie erst, wenn man
   wirklich zurück will: ein volles Beitrags-Stück nach oben (JS in feed.js) —
   nicht schon beim ersten Wackeln des Daumens, sonst klappt die Zeile bei
   jeder kleinen Korrektur auf und zu.
   Die Höhe wird von JS gesetzt (`--header-top-h`), nicht geraten: sie hängt an
   der Schriftgrösse und am Gerät. */
.app-header.is-collapsed .header-top {
    height: 0;
    /* Ohne diese Zeile gewinnt die `min-height` von oben gegen `height: 0` und
       die Zeile klappte nie mehr zu — `min-height` schlägt `height`. */
    min-height: 0;
    padding-top: 0;
    padding-bottom: 0;
    opacity: 0;
    pointer-events: none;
}
/* Eingeklappt bekommt die Rubrik-Reihe oben denselben Abstand wie unten —
   sonst klebt die Pille an der Oberkante und wird auf dem Geraet von der
   System-Leiste angeschnitten (*2026-08-04 von Jascha*). Der Wert wird in
   `measureHeader` (feed.js) aus dem unteren Polster gelesen; wer ihn hier
   aendert, aendert ihn dort automatisch mit.

   DAS POLSTER LIEGT AM HEADER, NICHT AN DER REIHE (*2026-08-04 von Jascha*:
   „das gelbe Tile schiebt nicht gleich mit, es geht kurz zu hoch rauf").
   Die gleitende Pille ist absolut in der Reihe positioniert und kennt ihre
   Lage nur aus dem letzten `placeRubricPill`. Sass das Polster an der Reihe,
   rueckten die WOERTER darin nach unten und die Pille blieb, wo sie war — sie
   stand die ganze Bewegung lang 13px zu hoch und sprang erst am Ende zurecht.
   Am Header wandert die Reihe samt Pille als EIN Block; nachzurechnen gibt es
   dann nichts mehr. `padding-top` statt `padding`, damit die Safe-Area des
   Geraets erhalten bleibt. */
.app-header.is-collapsed { padding-top: calc(env(safe-area-inset-top, 0px) + 0.8rem); }
/* Sicherung: Im Detail-Modus darf der Header NIE eingeklappt sein. Die
   Zurück-Zeile (`.header-detail-bar`) liegt IN der Logo-Zeile — faehrt die auf
   Hoehe 0, ist der Zurueck-Button mit ihr weg. `applyTop` klappt bei jeder
   Navigation ohnehin auf; diese Regel macht den Fehler zusaetzlich unmoeglich,
   falls je ein Weg an `applyTop` vorbeifuehrt. */
.app-header.detail-mode.is-collapsed .header-top {
    height: auto;
    padding: 1rem 1.5rem;
    opacity: 1;
    pointer-events: auto;
}

.logo {
    font-family: var(--font-logo);
    font-size: 1.8rem;
    font-weight: 900;
    letter-spacing: -1px;
    color: var(--text-primary);
    text-transform: lowercase;
}
/* Tab-Titel statt Brand-Logo: linksbündig, bold (700), aber andere Typo
   als die Brand-„friends."-Marke (System-Font statt Inter, normales
   Letter-Spacing, kleinere Größe). */
.app-header.show-title .header-top {
    justify-content: flex-start;
}
.app-header.show-title .logo {
    font-family: var(--font-logo);
    font-weight: 700;
    font-size: 1.3rem;
    letter-spacing: normal;
    /* Tab-Titel folgen der Copy-Regel (normale Grossschreibung); nur das
       Brand-„friends." auf Home bleibt klein (via .logo ohne .show-title). */
    text-transform: none;
}

/* --- Dark-Mode-Schalter (GLOB-02) -----------------------------------------
   Links in der Logo-Zeile, absolut wie Glocke und Such-Zunge rechts — damit
   bleibt das Logo mittig, obwohl links etwas dazukommt.

   DER ABSTAND ZUM RAND IST DER DES ZURÜCK-PFEILS (*2026-08-05 von Jascha*).
   `.header-detail-bar` beginnt am Innenrand der Zeile (1.5rem), und
   `.header-back-btn` polstert davon 0.4rem nach innen — sein Icon steht also
   1.9rem von der Kante. Genau dort steht auch der Mond; gerechnet aus
   denselben zwei Werten statt als fertige Zahl, damit er mitwandert, wenn
   jemand das Polster der Zeile ändert.

   NUR IM HOME-TAB: auf den anderen Tabs steht links der Tab-Titel, und im
   Detail-Modus der Zurück-Pfeil — beides genau hier. Ein Schalter, der unter
   einem Titel liegt, ist kein Schalter. */
.theme-toggle-btn {
    position: absolute;
    left: calc(1.5rem + 0.4rem);
    color: var(--text-primary);
}
/* Der Griff gehört dem Home-Tab. `body[data-tab]` setzt app.js beim
   Tab-Wechsel; im Detail-Modus und bei offener Suche ist die Zeile ohnehin
   anders belegt. */
body:not(.tab-home) .theme-toggle-btn,
.app-header.detail-mode .theme-toggle-btn,
.app-header.search-active .theme-toggle-btn {
    display: none;
}

.header-actions {
    position: absolute;
    right: 4rem; /* leave room for the closed search tongue on the right */
    display: flex;
    align-items: center;
    gap: 1rem;
    transition: opacity 0.15s ease;
}

.icon-btn {
    background: transparent;
    border: none;
    color: var(--text-light);
    font-size: 1.2rem;
    cursor: pointer;
}
/* HIER STAND `.icon-btn:hover { transform: scale(1.1) }` — und es war der
   Grund, warum das Schliessen-X der Suche beim Hover nach unten fiel (FT-04).
   `.search-close-btn` zentriert sich mit `top: 50%; transform:
   translateY(-50%)`. Die Hover-Regel setzt dieselbe Eigenschaft neu und ist
   spezifischer (0-2-0 gegen 0-1-0) — beim Hover blieb also nur `scale(1.1)`
   übrig, die Zentrierung fiel weg und der Button rutschte um seine halbe Höhe
   nach unten (gemessen: **genau 12px** bei 24px Höhe).

   Weggenommen statt geflickt (die Zentrierung im Hover mitzuführen wäre die
   Alternative gewesen): DS §7 hat dieselbe Geste als `:active`-Wippen bereits
   global gestrichen — „die Rückmeldung auf einen Tap ist das, was danach
   passiert". Ein Hover-Wackeln ist dieselbe Aussage, nur mit der Maus, und auf
   einem Telefon-Prototyp gibt es es ohnehin nicht. Mitgegangen ist die nun
   gegenstandslose `transition: transform`.

   **Inzwischen sind ALLE Hover-Regeln weg** (2026-08-04, auf Ansage von
   Jascha) — die sieben übrigen aus demselben Grund wie diese hier: eine App
   auf dem Telefon hat keinen Zeiger, der über etwas fährt. Wer eine neue
   `:hover`-Regel einbaut, holt die Unterscheidung zurück, die es hier nicht
   gibt. `:active` bleibt erlaubt, wo es eine gedrückte FLÄCHE zeigt (nicht
   eine Verschiebung) — siehe `.pin-action-tile:active`. */
.icon-btn.dark-icon { color: var(--text-primary); }

/* --- Search tongue ---
   Closed: small pill on the right with just the search icon.
   Open:   expands to full header width, with input field and close-X. */
.search-tongue {
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    height: 40px;
    background: var(--primary-color);
    color: #000;                 /* türkise Zunge → schwarze Tinte (DS §2) */
    display: flex;
    align-items: center;
    padding: 0 0.9rem;
    border-radius: 20px 0 0 20px;
    overflow: hidden;
    width: 52px;
    transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1),
                height 0.35s ease,
                border-radius 0.45s ease,
                top 0.35s ease,
                transform 0.35s ease;
    z-index: 5;
    cursor: pointer;
    box-sizing: border-box;
}
.search-tongue.open {
    width: 100%;
    height: 100%;
    top: 0;
    transform: none;
    border-radius: 0;
    cursor: text;
    padding: 0 1.1rem;
}
/* Reset native button styling. Die Zunge ist türkis → SCHWARZE Icons (DS §2);
   vormals dunkelblau → weiss. Explizit gesetzt, weil sonst .app-header .icon-btn
   auf Spezifität gewinnt. */
.app-header .search-tongue button {
    background: transparent;
    border: none;
    margin: 0;
    padding: 0;
    color: #000;
    cursor: pointer;
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}
.app-header .search-tongue .search-open-btn {
    font-size: 1.2rem;
}
.search-input-field {
    flex: 1;
    min-width: 0;
    background: transparent;
    border: none;
    outline: none;
    color: #000;                 /* Eingabe auf Türkis → schwarz (DS §2) */
    font-family: inherit;
    font-size: 1rem;
    font-weight: 600;
    padding: 0;
    margin: 0 0.7rem;
    opacity: 0;
    pointer-events: none;
}
/* Dieselbe Behandlung wie beim Schliessen-X darunter: Transition nur am
   offenen Zustand, damit beim Schliessen nichts mitfährt. Das Feld ist zwar
   ein Flex-Kind und kein absolut gesetztes — es wandert aber aus demselben
   Grund nach oben (die Zunge schrumpft, die vertikale Zentrierung folgt).
   Nur das X anzuhalten hätte den Text allein hochfahren lassen: ein Element,
   das sich bewegt, neben einem, das verschwindet, liest sich schlechter als
   vorher. */
.search-tongue.open .search-input-field {
    opacity: 1;
    pointer-events: auto;
    transition: opacity 0.2s ease 0.25s;
}
.search-input-field::placeholder {
    color: rgba(0, 0, 0, 0.5);
}
/* Close-X sits absolutely so it doesn't occupy flex space in the closed state. */
/* BEIM SCHLIESSEN VERSCHWINDET DAS X SOFORT — es soll nicht nach oben fahren
   (*2026-08-04 von Jascha*: „Abbruch-Button bei grüner Suche geht bei Klick
   gegen oben, bitte Animation weg").

   Was passierte: `.search-tongue.open` ist `height: 100%`, geschlossen sind es
   40px, und dazwischen liegt eine 0,35-s-Transition. Das X sitzt mit
   `top: 50%` IN der Zunge — schrumpft sie, wandert sein Bezugspunkt nach oben
   und das X mit. Sichtbar wurde die Fahrt durch die **0,25 s Verzögerung**
   seines Ausblendens: es stand die ersten 250ms der 350-ms-Bewegung noch voll
   da und fuhr mit.

   Die Transition steht darum jetzt nur noch am OFFENEN Zustand. Beim Öffnen
   blendet das X also weiterhin verzögert ein (sonst stünde es in einer erst
   halb ausgefahrenen Zunge); beim Schliessen fällt die Regel weg, es gibt
   keine Transition mehr und das X ist sofort weg. Die Zunge selbst fährt
   weiter zusammen — die Bewegung gehört ihr, nicht dem Knopf. */
.search-close-btn {
    position: absolute;
    right: 1.1rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: 1.1rem;
    opacity: 0;
    pointer-events: none;
}
.search-tongue.open .search-close-btn {
    opacity: 1;
    pointer-events: auto;
    transition: opacity 0.2s ease 0.25s;
}

/* Logo and notification fade out fast so the extending tongue doesn't slam
   into them — short transition keeps the disappearance ahead of the slide. */
.logo {
    transition: opacity 0.15s ease;
}
.app-header.search-active .logo,
.app-header.search-active .header-actions {
    opacity: 0;
    pointer-events: none;
}

/* --- Tabs --- */
.tabs-wrap {
    position: relative;
}
.tabs-container {
    display: flex;
    justify-content: center;
    gap: 2rem;
    padding-bottom: 0.8rem;
    transition: opacity 0.25s ease;
}
/* Search categories share the same vertical slot as the rubric tabs and cross-fade. */
.search-categories {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    display: flex;
    justify-content: center;
    gap: 2rem;
    padding-bottom: 0.8rem;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
}
.app-header.search-active .tabs-container {
    opacity: 0;
    pointer-events: none;
}
.app-header.search-active .search-categories {
    opacity: 1;
    pointer-events: auto;
    transition-delay: 0.1s;
}
.search-cat-btn {
    background: none;
    border: none;
    /* Sitzt auf dem TÜRKISEN Such-Header → schwarze Schrift (DS §2), inaktiv
       gedimmt, aktiv voll. Vormals dunkelblauer Header mit weisser Schrift und
       türkisem Aktiv-Akzent — der Akzent wäre jetzt Türkis auf Türkis, also
       unsichtbar; die Aktiv-Kennzeichnung trägt allein der Unterstrich. */
    color: rgba(0, 0, 0, 0.55);
    font-size: 1.1rem;
    font-weight: 700;
    cursor: pointer;
    position: relative;
    padding: 0;
    font-family: inherit;
}
.search-cat-btn.active { color: #000; }
.search-cat-btn.active::after {
    content: '';
    position: absolute;
    bottom: -0.8rem;
    left: 0;
    width: 100%;
    height: 4px;
    background: currentColor;
}

/* Inaktive Rubrik: --text-secondary, derselbe Wert wie die Rubrik-Reihe im
   Profil. Vorher stand hier rgba(0,0,0,.5) — ein untokenisiertes Grau, das
   dieselbe Komponente an zwei Orten unterschiedlich einfärbte. */
.tab-btn {
    background: none;
    border: none;
    color: var(--text-secondary);
    font-size: 1.1rem;
    font-weight: 700;
    cursor: pointer;
    position: relative;
}
/* Search icon and other header icons readable on the white header. */
.app-header .icon-btn { color: var(--text-primary); }
/* Notification button anchor for the badge. */
.notification-btn {
    position: relative;
    padding: 0.25rem;
}
/* Rubrik-Symbol links vom Namen — bei BEIDEN Rubriken sichtbar, nicht nur bei
   der aktiven. Historie: die Regel stand ursprünglich auf `display: none` mit
   einem `.active`-Override, verlor aber gegen Font Awesome (das auf `.fa-solid`
   ein eigenes `display` setzt und damals später geladen wurde). Sichtbar waren
   dadurch immer beide Symbole — und genau so wurde der Endtyp abgenommen. Mit
   der korrigierten Ladereihenfolge ist das jetzt explizit statt zufällig.
   Die Farbe kommt via currentColor von der Pill bzw. der Textfarbe. */
.tab-icon {
    display: inline;
    margin-right: 0.4rem;
    font-size: 0.95rem;
}

/* Aktive Rubrik = farbig hinterlegte PILL in der Rubrikfarbe (DESIGN_SYSTEM.md
   §8); der Header selbst bleibt weiss. Ersetzt den früheren farbigen Unterstrich
   samt farbiger Schrift — Gelb als Schrift war auf Weiss unlesbar, als Fläche
   mit schwarzer Tinte ist es das Gegenteil.
   Die Pill ist EIN gleitendes Element (`.rubric-pill`, siehe Dateiende) statt
   eines Hintergrunds pro Button: nur so kann sie beim Wechsel mitwandern, wie
   die türkise Kugel der Bottom-Nav. Sie ist grösser als ihr Wort — das frühere
   `inset: -0.34rem -0.72rem` steht darum als `--pill-grow-*`. */
.tabs-container {
    position: relative;
    --pill-grow-x: 11.5px;   /* = 0.72rem */
    --pill-grow-y: 5.4px;    /* = 0.34rem */
}
.tabs-container .tab-btn { position: relative; z-index: 1; }
.tabs-container[data-active="updates"] .rubric-pill { background: var(--accent-yellow); }
.tabs-container[data-active="pins"]    .rubric-pill { background: var(--accent-pink); }
/* Tinte auf der Pill, kontrastgeprüft: Schwarz auf Gelb ≈16,6:1 · Weiss auf
   Rosa ≈3,9:1 (DS §2). Der Wechsel wartet, bis die Pille fast da ist — sonst
   stünde die weisse Pins-Schrift kurz auf weissem Header. */
.tab-btn { transition: color 0.12s ease 0.2s; }
.tab-btn[data-tab="updates"].active { color: #000; }
.tab-btn[data-tab="pins"].active    { color: #fff; }

/* --- Main Layout & States --- */
main { padding: 1.5rem; }

.state-container {
    transition: opacity 0.4s ease;
}
.state-container.hidden {
    display: none;
    opacity: 0;
}
.state-container.active {
    display: block;
    opacity: 1;
}

/* --- Posts / Feed --- */
.feed {
    display: flex;
    flex-direction: column;
    gap: 1.875rem;
}

.post-card {
    background: var(--surface-color);
    border: none;
    border-radius: 0;
    box-shadow: none;
    overflow: hidden;
    /* Prevent text selection and image save dialogs on long press */
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    transition: transform 0.1s;
}
/* Der Rubrik-Akzent der Cards (früher Neon-Glow auf schwarzem Canvas, heute
   Card-Offset 10px) sitzt weiter unten im Elevation-Abschnitt — gleiche
   Selektoren, deshalb hier keine zweite Fassung. */

.post-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.6rem;
    padding: 1rem;
}
.user-info { min-width: 0; }
.user-meta { min-width: 0; }

/* Die rechte Seite der Kopfzeile ist EINE Gruppe: Zeit, Typ-Marker und das
   ⋮-Menü stehen zusammen, vertikal mittig zur Zeile. Vorher sass am Header ein
   Inline-`align-items: flex-start` — alles klebte oben, und zwischen dem Marker
   (unter der Zeit) und den Punkten (ganz oben rechts) klaffte eine diagonale
   Lücke, die aus nichts anderem als der Flexbox kam. */
.post-head-right {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    flex-shrink: 0;
}

/* Right column of the post header: time on top, type-marker icon below. */
.post-time-block {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.35rem;
}
.post-time {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-muted);
    white-space: nowrap;
}
/* ⋯-Menü am Beitrag (FEED-10). Steht als äusserstes Element der Kopfzeile
   rechts vom Zeit-/Marker-Block und ist flach wie aller Card-Inhalt (DS §3,
   Ebene 2). Meta-Grau wie Zeit und Ort: es ist eine Nebentür, kein Angebot. */
/* **Nur in Fullscreen-Ansichten.** Auf einer Card im Feed war der Griff ein
   dritter Ausgang neben Tap (Detailseite) und Long-Press (Reaktionen) — und
   melden oder löschen tut man aus dem Beitrag heraus, nicht im Vorbeiscrollen.
   Sichtbar bleibt er nur in der **Update-Sammlung** des Profils: die zeigt
   mehrere Beiträge, ein Griff im Header könnte dort nicht sagen, welchen er
   meint. Auf der Detailseite eines einzelnen Beitrags steht er im Header. */
.post-more-btn {
    flex: 0 0 auto;
    display: none;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.75rem;
    margin-right: -0.4rem;   /* optischer Rand: die Punkte sind schmal */
    padding: 0;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--text-muted);
    font-size: 1.15rem;
    cursor: pointer;
}
.post-detail-view.is-collection .post-more-btn { display: inline-flex; }
/* Typ-Marker (DESIGN_SYSTEM.md §4): farbiger Punkt mit Icon darin, statt eines
   umrandeten Glyphs. Update = gelber Punkt + schwarzer Blitz · Pin = rosa Punkt
   + weisser Pin — dieselbe Farbregel wie Chooser-Bubble und Rubrik-Pill. Der
   frühere `-webkit-text-stroke` (gelber Blitz brauchte auf Weiss eine Kontur)
   entfällt: die Fläche trägt jetzt den Kontrast. */
.post-type-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.55rem;
    height: 1.55rem;
    border-radius: 50%;
    font-size: 0.85rem;
    line-height: 1;
    -webkit-text-stroke: 0;
}
.post-type-icon.is-update {
    background: var(--accent-yellow);
    color: #000;
}
.post-type-icon.is-pin {
    background: var(--accent-pink);
    color: #fff;
}

.user-info {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

/* **40px — die eine Grösse für eine Person in einer Zeile** (2026-08-04,
   Jascha): Post-Card, Follower-/Members-Liste, Freundschaftsvorschlag,
   Reaktions-Liste und Kommentar tragen alle dasselbe Mass. Vorher standen
   dafür drei Werte nebeneinander (42/40/36), und keiner davon war eine eigene
   Aussage — nur Gewachsenes.
   Ausnahmen mit Grund bleiben: Aufrufe-Stapel 24, Kommentar-Teaser 28,
   Chat-Header 32, Profil-Kopf 88 (siehe DESIGN_SYSTEM §11).
   Die generische Klasse selbst wird an genau zwei Stellen gerendert — Kopf
   einer Post-Card und Aufrufe-Stapel (der auf 24px überschreibt). */
.avatar {
    width: 40px;
    height: 40px;
    /* EIN RUNDES BILD DARF NIE GEQUETSCHT WERDEN (*2026-08-04 von Jascha*: „alle
       Profilbilder bei den Pins sind verdrückt"). `width` ist in einer Flex-Zeile
       nur ein Wunsch — reicht der Platz nicht, schrumpft das Kind. Die
       Pin-Kopfzeile ist länger als die eines Updates („hat einen Pin auf #board
       mit … geteilt"), und genau dort wurde aus dem Kreis ein Ei: gemessen
       21,6 × 40 statt 40 × 40. `flex: none` nimmt die Breite aus der
       Verhandlung; kürzen soll der Text neben dem Bild, nicht das Bild. */
    flex: none;
    border-radius: 50%;
    overflow: hidden;
    background: var(--surface-color);
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
/* (Das Profilbild der Post-Card nimmt die 40px von `.avatar` oben. Davor der
   Versuch, es auf die volle Höhe des Zeit-/Marker-Blocks zu ziehen (3.1rem
   ≈ 50px): so gross wurde es zum Hauptdarsteller der Kopfzeile.) */

.user-meta {
    display: flex;
    flex-direction: column;
}

.user-meta h4 { font-size: 1rem; margin: 0; }
.user-meta span { font-size: 0.8rem; color: var(--text-muted); font-weight: 600; }

/* Der Name muss die Werte von h4 zurückholen, weil `.user-meta span` darüber
   sonst greift (0.75rem, grau) — die Regel trifft jeden Span in diesem Block.
   Das hängt an `.author-name`, die IMMER gesetzt ist: an `.author-link` gehängt
   verloren eigene Posts die Typografie, weil „Du" nicht in mockFriends steht und
   darum keinen Link bekommt. */
.user-meta h4 .author-name { font: inherit; color: inherit; }

/* Autor-Avatar und -Name führen aufs Profil (GLOB-13). Bewusst OHNE eigene
   Optik — kein Unterstrich, keine Farbe: der Name ist schon der Name, ein
   Link-Look würde die Card-Typografie zerreissen. */
.author-link { cursor: pointer; }
.author-link:active { opacity: 0.6; }

/* Meta im Kopf einer Card — Ort, Zeit, Untertitel, Reichweite, Aufrufe —
   tragen EINE Behandlung: --text-muted, 0.8rem, 600. Vorher war der Ort
   schwarz und fett, die Zeit muted und fett, der Untertitel secondary: drei
   Töne in derselben Zeile. Fett gesetztes Grau wirkt zudem schmutzig statt
   zurückhaltend — Meta darf leicht sein. */
.post-location {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-muted);
}

.post-image {
    width: 100%;
    max-height: 400px;
    object-fit: cover;
    display: block;
}

.post-content {
    font-size: 1.05rem;
    font-weight: 600;
    padding: 1rem;
    line-height: 1.4;
}

/* Reaction Menu */
.post-actions {
    position: relative;
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.8rem 1rem;
    /* Höhe der aufgeklappten Auswahl (1.5rem Emoji + Padding) — die Zeile ist
       in jedem Zustand gleich hoch, damit das Öffnen des Pickers die Card nicht
       wachsen lässt (FEED-05).
       ACHTUNG: eine Mindesthöhe hält nur, solange kein Kind höher ist. Genau
       das riss `.action-count` auf — siehe die Regel dort (FEED-18). Wer hier
       etwas hinzufügt, misst die Zeile in beiden Zuständen nach. */
    min-height: 3.1rem;
}

/* Reaction counts (bars) — absolutely centered in the post-actions row */
.reaction-counts-wrap {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    cursor: pointer;
}
.reaction-counts {
    display: flex;
    align-items: center;
    gap: 2px;
}
.reaction-count {
    display: flex;
    align-items: center;
    gap: 0;
}
.reaction-emoji {
    position: relative;
    z-index: 2;
    font-size: 1rem;
    line-height: 1;
}
.reaction-count-top .reaction-emoji {
    font-size: 1.5rem;
}
.reaction-bar {
    position: relative;
    z-index: 1;
    display: inline-block;
    height: 4px;
    background: var(--text-primary);
    border-radius: 2px;
    margin-left: -8px;
}

/* Push the emoji button to the right */
.action-btn.emoji-btn {
    margin-left: auto;
    font-size: 1rem;
    gap: 0.3rem;
}

/* Stacked emoji preview (default state) */
/* FEED-05: **gleich gross wie aufgeklappt.** Der Stapel stand auf 1rem, die
   Auswahl daneben grösser — beim Öffnen wuchs die Aktions-Zeile und die Card
   sprang. Jetzt tragen zusammengeklappt, aufgeklappt und der reagierte Zustand
   dieselbe Emoji-Grösse; die Überlappung wächst mit (−10px war auf 1rem
   gerechnet, sonst läuft der Stapel auseinander). Die feste Mindesthöhe der
   Zeile hält sie zusätzlich ruhig, auch wenn eine Variante mal nur Icons hat. */
.reaction-preview {
    display: inline-flex;
    align-items: center;
}
.reaction-preview-emoji {
    position: relative;
    font-size: 1.5rem;
    line-height: 1;
    margin-left: -15px;
}
.reaction-preview-emoji:first-child {
    margin-left: 0;
}
/* Front-most = first child (flame on top, others behind) */
.reaction-preview-emoji:nth-child(1) { z-index: 3; }
.reaction-preview-emoji:nth-child(2) { z-index: 2; }
.reaction-preview-emoji:nth-child(3) { z-index: 1; }

/* Reacted state on the button */
.reaction-applied-emoji {
    font-size: 1.5rem;
    line-height: 1;
}

/* Inline reaction picker (replaces the action bar when open) */
.post-actions.picker-open > *:not(.reaction-picker) {
    display: none;
}
.reaction-picker {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 4px;
}
.reaction-pick {
    background: transparent;
    border: none;
    padding: 0;
    font-family: inherit;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
}
/* Abbrechen: Icon, und in derselben ruhigen Tinte wie im Circle — es wirft
   nichts weg (Rot ist Löschen/Melden vorbehalten). */
.reaction-pick.cancel {
    color: var(--text-muted);
    font-size: 1.4rem;
}
/* Der „mehr"-Auslöser ist ein Icon, kein Gesicht (FEED-06): kleiner gesetzt als
   die Emojis daneben und in Meta-Grau — er ist der Weg zu einer Reaktion, nicht
   selbst eine. */
.reaction-pick.reaction-pick-more {
    color: var(--text-muted);
    font-size: 1.25rem;
}
.radial-option.reaction-pick-more {
    color: var(--text-muted);
    font-size: 1.25rem;
}
.radial-option.reaction-pick-more.active {
    color: var(--text-primary);   /* Schwarz auf Türkis (DS §2) */
}

/* Reaktions-Liste — Inhalt eines Bottom-Sheets (openSheet, FEED-02).
   Das frühere Fullscreen-Overlay samt türkisem Kopfbalken ist entfallen: es
   war die einzige Ansicht der App, die für acht Namen den ganzen Bildschirm
   nahm, und es deckte den Beitrag zu, zu dem es gehört.
   Die Zeile ist flach und ohne Trennlinie — dieselbe Sprache wie `.member-item`
   und die Kommentare (DS-05): eine Personenzeile sieht app-weit gleich aus.
   Kein seitliches Polster, das trägt `.sheet-body`. */
.reaction-list-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0;
}
.reaction-list-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
}
.reaction-list-name {
    flex: 1;
    font-weight: 800;
}
.reaction-list-emoji {
    font-size: 1.5rem;
}

/* Inline latest comment under an Update */
.latest-comment {
    display: flex;
    gap: 0.6rem;
    padding: 0.7rem 1rem;
    cursor: pointer;
}
.latest-comment-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}
.latest-comment-body {
    flex: 1;
    min-width: 0;
}
.latest-comment-header {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.85rem;
    font-weight: 800;
    margin-bottom: 0.15rem;
}
.latest-comment-name {
    display: inline-flex;
    align-items: center;
}
.latest-comment-time {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-muted);
    margin-left: auto;
}
.latest-comment-text {
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--text-primary);
}

/* Comments overlay items */
/* Keine Trennlinie zwischen Kommentaren — app-weit (Detailseite UND
   Kommentar-Sheet, die teilen sich `commentItemHtml`). Getrennt wird über das
   Padding. Entschieden 2026-07-30: Linien nach jedem Kommentar streifen die
   Ansicht, und die einzige strukturell wichtige Grenze ist die zwischen zwei
   Beiträgen. */
/* Seitlich KEIN eigenes Polster: ein Kommentar steht immer in einem Container,
   der schon eines hat — im Sheet `.sheet-body` (0 1rem), auf der Detailseite
   `.post-detail-comments` (1rem). Beide zusammen ergaben 2rem und schoben die
   Kommentare doppelt so weit ein wie den Beitragstext darüber (`.post-content`,
   1rem). Oben und unten bleibt das Polster: es ist die Trennung zwischen zwei
   Kommentaren, seit die Linien dazwischen weg sind (DS-05). */
.comment-item {
    display: flex;
    gap: 0.75rem;
    padding: 0.9rem 0;
}
.comment-avatar {
    /* Personenzeilen-Grösse (40px) wie Post-Card und Listen. */
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}
.comment-body {
    flex: 1;
    min-width: 0;
}
.comment-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.2rem;
}
.comment-name {
    font-weight: 800;
    font-size: 0.95rem;
    display: inline-flex;
    align-items: center;
}
.comment-time {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-muted);
    margin-left: auto;
}
.comment-text {
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--text-primary);
}
.comment-empty {
    padding: 2rem 1rem;
    text-align: center;
    color: var(--text-secondary);
    font-weight: 600;
}

.action-btn {
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-size: 1.4rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.2rem;
}

/* Die Zahl neben dem Symbol — und der Grund, warum die Aktions-Zeile trotz
   FEED-05 noch sprang (FEED-18). Sie hatte gar keine eigene Regel: 1.4rem vom
   Button geerbt, dazu `line-height: normal` — das sind 28px Zeilenkasten gegen
   22,4px Glyphe. Damit war die geschlossene Zeile 53,6px hoch und fiel beim
   Öffnen der Auswahl auf die reservierten 49,6px zurück; die Card schrumpfte
   um 4px. Auf Pins bei JEDER Card, weil dort jeder Pin eine Antwortzahl trägt,
   auf Updates nur bei denen mit Kommentaren — darum fiel es an den Pins auf.
   `line-height: 1` legt den Kasten auf die Glyphe: nichts in der Zeile ist mehr
   höher als die aufgeklappte Auswahl, und die Mindesthöhe hält wieder alle
   Zustände. Die Zahl selbst wird nicht kleiner und bleibt auf der Mitte des
   Symbols (gemessen: 0,00px Versatz vorher wie nachher). */
.action-count {
    line-height: 1;
}

.seen-by {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-muted);
    cursor: pointer;
}

.seen-by-icon {
    font-size: 1.2rem;
}

.seen-avatars {
    display: flex;
    align-items: center;
}

/* Die Avatare überlappen (-8px) und brauchen eine Trennung. Statt der alten
   schwarzen Border (DS §1: global keine Borders) ein Ring in der Flächenfarbe —
   er trennt gegen die Card, ohne als Umrandung zu lesen. */
.seen-avatars .avatar {
    width: 24px;
    height: 24px;
    box-shadow: 0 0 0 1.5px var(--surface-color);
    margin-left: -8px;
}
.seen-avatars .avatar:first-child {
    margin-left: 0;
}

.location-label {
    font-size: 1rem;
    font-weight: 700;
}

/* iOS Style Toggle Switch (Neo-brutalist) */






/* Image Preview */
.image-preview-container {
    position: relative;
    margin-top: 1rem;
    margin-bottom: 1rem;
    width: 120px;
    height: 120px;
    border-radius: var(--radius-md);
    border: none;
    background: #000;
}

.image-preview-container.hidden {
    display: none;
}

#image-preview-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 6px; /* slightly smaller than outer border radius */
}

.remove-image-btn {
    position: absolute;
    top: -10px;
    right: -10px;
    width: 26px;
    height: 26px;
    background: var(--accent-pink);
    color: #fff;
    /* Liegt auf dem Bild, nicht auf einer Card — die Trennung macht ein
       weicher Schatten, nie eine Border (DS §1, wie der Map-Marker). */
    border: none;
    border-radius: 50%;
    font-size: 0.9rem;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 2px 6px rgba(var(--shadow-rgb), 0.28);
    transition: transform 0.1s;
}
/* ===========================================================================
   ACTION-BARRIER (Paywall) — BLUR STATT BLOCK
   ---------------------------------------------------------------------------
   Spec: CLAUDE.md → Updates → Action-Barrier.

   Früher: ab Post 4 wurden die Cards per `display: none` entfernt (Dokumenthöhe
   gekappt → man kam nicht weiter), dazu ein Störer-Popup, das bei jedem
   Scroll-Versuch wackelte.

   Jetzt: die Cards bleiben stehen, das Dokument hat volle Höhe → **freies
   Scrollen**. Gesperrt ist nicht die Bewegung, sondern der INHALT.
     · Unscharf wird ausschliesslich der Beitrag: Bild und Text/Poll — bei den
       eingemischten Pin-Previews deren Teaser-Zeile.
     · Scharf bleibt der Post-Kopf: Avatar, Name, Reichweite, Ort, Zeit. Man
       sieht WER WANN WO gepostet hat, nur nicht WAS — das ist der Anreiz.
     · Weg fallen Aktions-Leiste, Kommentar-Vorschau und Aufrufe-Zeile: man
       könnte sie ohnehin nicht bedienen (die Card ist `pointer-events: none`),
       und Reaktionen auf einen unlesbaren Beitrag sind ein Widerspruch. Die
       Cards werden dadurch kürzer, es passen mehr ins Bild.
     · Kein Störer, kein Popup, kein Scrim, kein Shake. Ein Screen-Dim über frei
       scrollbarem Inhalt läse sich wie ein Modal; der Shake war die Antwort auf
       „du kommst nicht weiter" — jetzt kommt man weiter.
   =========================================================================== */

/* Cards ab Post 4: sichtbar und scrollbar, aber nicht öffenbar — sonst führte
   die Detailseite am Blur vorbei. */
body.barrier-locked #full-feed > .post-card:nth-of-type(n+4) {
    pointer-events: none;
    user-select: none;
    cursor: default;
}

/* Der verhängte Bereich. `clip-path` greift in der Render-Kette NACH dem Filter
   und schneidet das Ergebnis exakt auf die eigene Box zurück — ohne das
   verschmiert `blur()` über die Elementkante hinaus und die ganze Card wirkt
   dunstig statt nur der Inhalt. */
body.barrier-locked #full-feed > .post-card:nth-of-type(n+4) .post-content,
body.barrier-locked #full-feed > .post-card:nth-of-type(n+4) .pin-preview-line {
    filter: blur(5px);
    clip-path: inset(0);
}

/* Mindesthöhe von zwei Zeilen: kurze Beiträge — vor allem einzeilige
   Pin-Teaser — ergäben sonst ein so flaches Feld, dass der Freischalt-Button
   den ganzen verhängten Bereich zudeckt. `em` = relativ zur eigenen
   Schriftgrösse, Padding kommt on top (border-box). */
body.barrier-locked #full-feed > .post-card:nth-of-type(n+4) .post-content {
    box-sizing: border-box;
    min-height: calc(2 * 1.4em + 2rem);        /* 2 Zeilen (line-height 1.4) + Padding 2×1rem */
}
body.barrier-locked #full-feed > .post-card:nth-of-type(n+4) .pin-preview-line {
    box-sizing: border-box;
    min-height: calc(2 * 1.5em + 1.25rem);     /* 2 Zeilen + Padding 0.5rem/0.75rem */
}

/* Bild: gleiche Clip-Logik, zusätzlich leicht hochskaliert — `blur()` franst
   die Bildkante aus, darunter schimmert sonst das Weiss der Card durch. Die
   Skalierung schiebt den weichen Rand aus dem sichtbaren Bereich. */
body.barrier-locked #full-feed > .post-card:nth-of-type(n+4) .post-image {
    filter: blur(10px);
    transform: scale(1.06);
    clip-path: inset(0);
}

/* Aufgeräumt: übrig bleibt Kopf → verhängter Inhalt mit Freischalt-Button.
   (Das ⋮-Menü braucht hier keine eigene Regel mehr: es steht seit FEED-10/
   2026-08-03 ohnehin nur noch in den Fullscreen-Ansichten.) */
body.barrier-locked #full-feed > .post-card:nth-of-type(n+4) .post-actions,
body.barrier-locked #full-feed > .post-card:nth-of-type(n+4) .latest-comment,
body.barrier-locked #full-feed > .post-card:nth-of-type(n+4) .seen-by {
    display: none;
}

/* --- Freischalt-Aktion auf JEDEM gesperrten Post ---------------------------
   Nicht nur auf dem ersten: scrollt man mittendrin in den gesperrten Bereich,
   ist die Aktion sofort greifbar, egal wo man landet.

   Der Button kann NICHT im unscharfen Element liegen — Kinder eines Elements
   mit `filter` werden mitgefiltert. Er ist deshalb ein Geschwister auf
   Card-Ebene, das `positionUnlockCta` (feed.js) auf die Mitte des Ziels setzt. */
body.barrier-locked #full-feed > .post-card.unlock-host { position: relative; }

.unlock-cta {
    position: absolute;
    left: 50%;
    /* `top` setzt das Script auf die Mitte von Bild bzw. Text. */
    transform: translate(-50%, -50%);
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0 1.2rem;
    box-sizing: border-box;
    pointer-events: none;   /* nur der Button ist anfassbar */
}

/* Erklärzeile — nur auf Bild-Posts, wo Fläche ist (das Script setzt sie auch
   nur dort ein). Auf Text-Posts, erst recht auf den zweizeiligen
   Mindesthöhen-Feldern, wäre sie grösser als das, was sie erklärt. */
.unlock-text {
    font-size: 0.98rem;
    font-weight: 800;
    line-height: 1.35;
    text-align: center;
    color: #fff;
    /* Ein einziger weicher Schatten — genug, damit Weiss auch auf hellen
       Bildbereichen steht, ohne Halo. Dunkelblau getönt, nie schwarz. */
    text-shadow: 0 1px 10px rgba(var(--shadow-rgb), 0.55);
}

.unlock-btn {
    /* Masse = die etablierte kleine Aktions-Pille (DS §7, Referenz
       `.board-action`): kein eigenes Button-Format erfinden — die
       Sichtbarkeit trägt die Brand-Fläche, nicht die Grösse. */
    border: none;
    border-radius: var(--radius-md);
    background: var(--accent-yellow);
    color: #000;                       /* Schwarz auf Gelb */
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
    max-width: calc(100% - 2.4rem);    /* Sicherheitsnetz auf schmalen Screens */
    padding: 0.4rem 0.8rem;
    /* Die `min-height` gehoert zur Pille, nicht zum Inhalt (DS §7) — sie hat
       hier gefehlt, obwohl der Kommentar oben die Referenz `.board-action`
       nennt: 29,8 px statt 32. Aufgefallen beim Angleichen der „N neue
       Beitraege"-Pille (*2026-08-04 von Jascha*), die dieselben Masse traegt. */
    min-height: 2rem;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
    box-shadow: none;                  /* §7: Aktions-Button ist flach */
    pointer-events: auto;              /* die Card ist none — der Button nicht */
}

/* --- Buttons --- */

/* --- Modals --- */
.modal-overlay {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,0.8);
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
    padding: 1rem;
}
.modal-overlay:not(.hidden) {
    opacity: 1;
    pointer-events: auto;
}

.modal-content {
    background: var(--surface-color);
    width: 100%;
    max-width: 500px;
    /* Ebene 3 (DS §3): nur Schlagschatten, kein Bevel und kein Rahmen — ein
       Panel ist an den Screen gedrückt, es ragt nicht heraus. */
    border: none;
    box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.14);
    border-radius: var(--radius-lg);
    padding: 1.5rem;
    transform: scale(0.9);
    transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    max-height: 90vh;
    overflow-y: auto;
    overflow-x: hidden;
}
.modal-overlay:not(.hidden) .modal-content {
    transform: scale(1);
}

.modal-step {
    transition: opacity 0.2s ease;
}
.modal-step.hidden {
    display: none;
    opacity: 0;
}
.modal-step.active {
    display: block;
    opacity: 1;
}

/* Camera UI */
.camera-dummy-view {
    position: relative;
    background: #000;
    height: 400px;
    border-radius: var(--radius-md);
    border: none;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 1rem;
    margin-bottom: 1rem;
}

/* Camera-Mode: the create-modal is rendered as a true fullscreen camera
   (no card frame, no modal-header). Activated by JS while step 1 is shown.
   Overrides the base #create-modal rule which leaves 70px for the bottom nav. */
#create-modal.camera-mode {
    bottom: 0;
    padding: 0;
    background: #000;
}
#create-modal.camera-mode .modal-content {
    width: 100vw;
    max-width: none;
    max-height: none;
    height: 100vh;
    height: 100dvh;
    border: none;
    box-shadow: none;
    border-radius: 0;
    padding: 0;
    overflow: hidden;
    background: #000;
}
#create-modal.camera-mode .modal-header { display: none; }
#create-modal.camera-mode #modal-step-1 { height: 100%; }
#create-modal.camera-mode .camera-dummy-view {
    height: 100%;
    border: none;
    border-radius: 0;
    margin-bottom: 0;
    padding: 1.25rem 1.25rem 2rem;
    padding-top: max(1.25rem, env(safe-area-inset-top));
    padding-bottom: max(2rem, env(safe-area-inset-bottom));
}

.camera-cancel-btn {
    position: absolute;
    top: max(1rem, env(safe-area-inset-top));
    right: 1.25rem;
    background: transparent;
    border: none;
    color: #fff;
    font-family: inherit;
    font-weight: 800;
    font-size: 1rem;
    letter-spacing: 0.02em;
    cursor: pointer;
    padding: 0.5rem 0.25rem;
    z-index: 1;
}
.camera-cancel-btn:active { opacity: 0.6; }
/* Der Sucher hat DASSELBE Format wie die Aufnahme: 4:5 Hochformat (der
   Zuschnitt steckt in `handleCameraCapture`, app.js). Vorher füllte er die
   ganze freie Höhe und zeigte per `object-fit: cover` einen anderen Ausschnitt,
   als das Bild danach hatte — man richtete etwas ein und bekam etwas anderes.
   `flex: 0 1 auto` statt `1`: er soll seine Form behalten, nicht die Restfläche
   füllen; die Spalte darum herum verteilt den Raum (`space-between`). */
.camera-viewfinder {
    flex: 0 1 auto;
    align-self: center;
    width: 100%;
    aspect-ratio: 4 / 5;
    max-height: 100%;
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}
.camera-stream {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    background: #000;
    display: none;
}
.camera-viewfinder.live .camera-stream { display: block; }
.camera-viewfinder.live .camera-icon-center { display: none; }
.camera-icon-center {
    font-size: 4rem;
    color: rgba(255,255,255,0.2);
}
/* Auslöser mittig, Galerie rechts (CLAUDE.md → Kamera-Step). Seit „Überspringen"
   weg ist (NEW-04), gäbe `space-between` dem Auslöser die linke Kante — er wird
   darum echt zentriert und die Galerie absolut rechts gesetzt. */
.camera-controls {
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
}
.camera-controls #camera-gallery-btn {
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
}
.text-btn {
    background: transparent;
    border: none;
    color: #fff;
    font-weight: 700;
    cursor: pointer;
}
.text-btn.secondary { color: var(--text-secondary); }
.capture-btn {
    width: 60px; height: 60px;
    border-radius: 50%;
    background: transparent;
    border: 3px solid #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}
.capture-inner {
    width: 46px; height: 46px;
    border-radius: 50%;
    background: var(--surface-color);
    transition: transform 0.1s;
}
.icon-btn-large {
    background: transparent;
    border: none;
    color: #fff;
    font-size: 1.5rem;
    cursor: pointer;
}

.modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1.5rem;
    border-bottom: 1px solid var(--hairline);
    padding-bottom: 0.5rem;
}
.close-btn {
    /* Abbrechen ist keine Brand-Aktion (DS §7): ruhige Fläche, Text in
       Primärfarbe, kein Rahmen. Gelb gehörte den Updates, nicht dem Schliessen. */
    background: var(--surface-light);
    border: none;
    width: 32px; height: 32px;
    border-radius: 50%;
    color: var(--text-primary);
    cursor: pointer;
    font-weight: bold;
}


textarea {
    width: 100%;
    background: var(--surface-color);
    /* Eingabefelder sind eingelassen, nicht umrandet (DS §5). */
    border: none;
    border-radius: var(--radius-sm);
    padding: 0.8rem;
    color: var(--text-primary);
    font-size: 1.1rem;
    font-family: inherit;
    font-weight: 600;
    resize: none;
    outline: none;
    margin-bottom: 1rem;
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
}

.modal-footer {
    display: flex;
    justify-content: flex-end;
    align-items: center;
}

/* ===========================================================================
   CELEBRATION — Vollfarbe, Nachricht direkt drauf, Karma-Fortschritt
   ---------------------------------------------------------------------------
   Spec: CLAUDE.md → Compose-Screen → Celebration-Overlay. Drei Punkte:

   1) KEINE weisse Card mehr — die Nachricht steht direkt auf der Farbfläche.
      Dieselbe Sprache wie der Post-Chooser. Kontrast über `--ink`:
      Schwarz auf Gelb · Weiss auf Rosa (DS §2).
   2) DIESELBE FÜLL-GESTE wie beim Chooser-Tap: die Farbe wächst per
      clip-path-Kreis vom „Teilen"-Button aus über den Screen, statt
      einzublenden — der eigene Tap ist sichtbar die Ursache.
   3) KARMA-BLOCK darunter, gedämpft: Punkte, Level und ein Balken, dessen Zahl
      SYNCHRON mithochzählt. „Gedämpft" = die eigene Tinte mit reduzierter
      Deckkraft; echtes Grau wäre auf Gelb kontrastschwach und bräche die
      Farbregel.
   =========================================================================== */
.celebration-overlay {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: var(--accent-yellow);
    z-index: 300;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    pointer-events: none;
    /* Tinte der jeweiligen Welt — steuert Schrift, Balken und Deckkraft-Stufen. */
    --ink: #000;
    /* Kein Opacity-Fade: den Auftritt macht die Füllung. Das Overlay ist im
       Ruhezustand allein über `opacity: 0` unsichtbar — hier deshalb niemals
       `opacity` unbedingt setzen, sonst klebt die Farbfläche über jedem Screen. */
    transition: none;
    /* Radius und Ursprung setzt app.js beim Öffnen. */
    clip-path: circle(var(--reveal-r, 150%) at var(--reveal-x, 50%) var(--reveal-y, 50%));
}
.celebration-overlay:not(.hidden) {
    opacity: 1;
    pointer-events: auto;
    cursor: pointer;            /* Klick irgendwo überspringt */
}
.celebration-overlay.is-revealing {
    transition: clip-path 420ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.celebration-content {
    text-align: center;
    color: var(--ink);
    border: none;
    background: none;
    box-shadow: none;
    padding: 0 1.6rem;
    max-width: 22rem;
    transform: scale(0.8);
    transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.celebration-overlay:not(.hidden) .celebration-content {
    transform: scale(1);
}

/* --- Karma-Block --- */
.celebration-karma {
    margin-top: 1.9rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    color: var(--ink);
    opacity: 0;
    transform: translateY(6px);
    transition: opacity 320ms ease-out, transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
}
.celebration-overlay.is-shown .celebration-karma {
    opacity: 1;
    transform: none;
    transition-delay: 260ms;   /* erst wenn die Farbe steht */
}
.celebration-karma-gain {
    font-size: 1rem;
    font-weight: 800;
    opacity: 0.62;
}
/* Balken: Spur = Tinte sehr transparent, Füllung = Tinte gedämpft. */
.celebration-karma-bar {
    position: relative;
    height: 8px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.14);
    overflow: hidden;
}
.celebration-karma-bar span {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    border-radius: 999px;
    background: var(--ink);
}
/* Läuft von der alten Breite auf die neue; die Zahl zählt synchron mit.
   Delay und Dauer müssen zu BAR_DELAY/BAR_MS in app.js passen, sonst laufen
   Balken und Zahl auseinander. */
.celebration-karma-bar .now {
    opacity: 0.85;
    width: 0;
    transition: width 900ms cubic-bezier(0.22, 0.7, 0.3, 1);
    transition-delay: 420ms;
}
.celebration-karma-meta {
    display: flex;
    justify-content: space-between;
    font-size: 0.82rem;
    font-weight: 700;
    opacity: 0.62;
}
/* Gleiche Schrift wie die Meta-Zeile, damit beim Hochzählen nichts hüpft —
   tabular-nums hält die Ziffernbreite konstant. */
.celebration-karma-count {
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}


/* --- Bottom Nav --- */
/* Font-Awesome bleibt nur für: ⚡ fa-bolt, 📌 fa-thumbtack (Brand), die Reichweite-
   Grade fa-user/fa-globe (filled/outline-Semantik) und den Map-Tab. Alles übrige =
   SVG-Sprite (siehe .ic). */
/* === SVG-Icon-Sprite (Iconoir-Geometrie via <use>) ===
   Strokefarbe kommt aus den Pfaden (stroke="currentColor"); HIER KEIN `stroke`,
   sonst wuerden rein gefuellte Detail-Pfade (Punkte) faelschlich umrandet. Outline-
   Strichstaerke = --icon-sw (live); gefuellte Detail-Punkte haben fixe 1.5 gebacken. */
.ic {
    width: 1em;
    height: 1em;
    display: inline-block;
    vertical-align: -0.15em;
    flex-shrink: 0;
    fill: none;
    stroke-width: var(--icon-sw, 2);
    overflow: visible;
}


/* Bottom-Nav: schwebend (Insets + Radius + Schatten) — DS §8 Chrome-Ausnahme. */
.bottom-nav {
    position: fixed;
    bottom: var(--nav-bottom);
    left: 50%;
    transform: translateX(-50%);
    width: 88%;                         /* relativer Anteil der Screenbreite — immer schmäler als der Screen */
    max-width: 560px;                   /* Desktop: bleibt innerhalb der Feed-Breite (#app = 600px) */
    height: var(--nav-h);               /* vertikal schlanker */
    display: flex;
    justify-content: space-around;
    align-items: center;
    background: var(--surface-color);
    border-radius: 26px;
    z-index: 100;
}

.nav-item {
    color: var(--text-primary);         /* inaktiv = Textfarbe (der Slider trägt jetzt „aktiv") */
    font-size: 1.4rem;
    text-decoration: none;
    padding: 0.5rem;
    transition: transform 0.1s;
    position: relative;
    z-index: 1;                         /* Icons über dem türkisen Indikator */
}
/* Aktiv = SCHWARZ auf der türkisen Kugel (DS §2/§8): weiss läge bei ≈2:1,
   schwarz bei ≈10,6:1. Gleiche Regel wie beim Rubrik-Pill und der Such-Zunge. */
.nav-item.active { color: #000; }

/* Türkise Aktiv-Kugel — sitzt hinter dem aktiven Icon und gleitet beim Wechsel mit.
   Position setzt JS (updateNavIndicator); .animate aktiviert die Gleit-Transition
   erst nach der ersten Platzierung (kein Flug von 0 beim Laden). */
.nav-indicator {
    position: absolute;
    top: 50%;
    left: 0;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--accent-turkis);
    z-index: 0;
    pointer-events: none;
    transform: translate(0, -50%);
}
.nav-indicator.animate {
    transition: transform 0.38s cubic-bezier(0.34, 1.3, 0.5, 1);
}
.bottom-nav.cancel-mode .nav-indicator { opacity: 0; }

/* Plus-Kugel (Beitrag erstellen): runde Kugel, etwas grösser als die Bar →
   sitzt mittig/zentriert auf der Navbar und ragt rundum leicht raus.
   UNIFARBEN UND RUBRIKGEFÜHRT (DESIGN_SYSTEM.md §8): in „Updates" gelb mit
   schwarzem Plus, in „Pins" rosa mit weissem Plus — die frühere feste
   Gelb/Rosa-Diagonale ist damit weg. Die Farbe ist keine Dekoration, sondern
   eine Ankündigung: der Tap öffnet den Chooser auf der passenden Seite
   (siehe openPostTypeChooser in app.js). Der Übergang beim Rubrikwechsel läuft
   weich, ist also die Folge einer User-Handlung statt ein Dauerzustand. */
.nav-fab {
    width: 64px;
    height: 64px;
    padding: 0;
    border-radius: 50%;
    background: var(--accent-yellow);
    color: #000;                        /* Plus via currentColor */
    font-size: 1.7rem;                  /* Plus-Größe */
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color 420ms ease-out, color 420ms ease-out;
    /* Kein Schatten: die Nav ist das schwebende Element (DS §8); der `+` darauf
       ist ein flacher Aktions-Button (DS §7) — kein „3D auf 3D" (DS §1). */
}
/* Grundlage ist body.rubric-<tab>, das switchTab (feed.js) setzt. Ausserhalb des
   Home-Feeds (Boards, Chats, Profil) bleibt die zuletzt gezeigte Rubrikfarbe
   stehen — dort gibt es keine Rubrik, und ein Sprung auf eine „neutrale" Farbe
   wäre eine Aussage ohne Inhalt. */
body.rubric-updates .nav-fab { background: var(--accent-yellow); color: #000; }
body.rubric-pins    .nav-fab { background: var(--accent-pink);   color: #fff; }
.nav-fab .ic { stroke-width: 2.6; }     /* kräftigeres Plus */
/* Cancel-Mode: when post-type chooser is open, the whole bottom-nav
   collapses into a single "Abbrechen" button. */
.nav-cancel-btn {
    display: none;
    background: none;
    border: none;
    color: var(--text-secondary);
    font-family: inherit;
    font-weight: 800;
    font-size: 1.05rem;
    letter-spacing: 0.02em;
    width: 100%;
    height: 100%;
    cursor: pointer;
}
/* Im Chooser (cancel-mode) schwebt der Abbrechen-Button ÜBER dem jetzt fullscreen
   Chooser (z 200), damit Abbrechen erreichbar bleibt. */
.bottom-nav.cancel-mode { z-index: 250; }
.bottom-nav.cancel-mode .nav-item { display: none; }
.bottom-nav.cancel-mode .nav-cancel-btn { display: block; }

/* Zahl-Bubble an Glocke und Chat-Tab: türkis = Notification-Farbe, kein Border
   (DS: global keine Borders ausser Segment-Tiles).
   **Tinte SCHWARZ, nicht weiss** — Weiss auf Türkis liegt bei ≈2,1:1 und reisst
   WCAG AA (4,5:1) deutlich; Schwarz liegt bei ≈10,6:1. Es ist zudem die
   Hausregel „Schwarz auf Türkis" (DS §2), die für Rubrik-Pill, Such-Zunge und
   Nav-Kugel längst gilt — die Badges waren die letzte Ausnahme.
   **Grösse:** 0.72rem ≈ 11,5px statt 9,6px. Kleiner als etwa 11px ist eine
   Zahl auf einem farbigen Fleck nicht mehr sicher lesbar, und diese hier ist
   die einzige Auskunft des Elements. */
.badge {
    position: absolute;
    top: -3px;
    right: -3px;
    background: var(--accent-turkis);
    color: #000;
    font-size: 0.72rem;
    font-weight: 800;
    line-height: 1;
    min-width: 19px;
    height: 19px;
    padding: 0 5px;
    border-radius: 9999px;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* --- Hint Toast --- */
/* Geparkt wird über die EIGENE HÖHE, nicht über einen festen Pixelwert.
   Vorher stand hier `top: -100px`: das reichte für eine einzeilige Notiz, aber
   sobald der Text umbrach (z.B. „Melden ist im Prototyp nicht ausgearbeitet."
   auf einem fremden Profil), wuchs die Pille über die 100px hinaus — und ihr
   Schlagschatten blieb als grauer Streifen am oberen Rand stehen, die halbe
   Sekunde lang, bis `hidden` sie ganz wegnahm.
   `-100%` ist immer exakt die eigene Höhe; die 48px darüber sind der Raum, den
   der Schatten braucht (12px Versatz + 28px Weichzeichnung, plus Reserve). */
.hint-toast {
    position: fixed;
    top: 0;
    left: 20px;
    right: 20px;
    z-index: 500;
    transform: translateY(calc(-100% - 48px));
    transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    display: flex;
    justify-content: center;
}
.hint-toast.visible {
    transform: translateY(20px);
}
/* In-App-Notification: WEISS mit Schatten, exakt die Optik der Bottom-Nav
   (*2026-08-04 von Jascha*) — dieselbe Fläche, derselbe Schlagschatten, kein
   Bevel und kein Rahmen. Beides sind schwebende Leisten über dem Inhalt; dass
   sie gleich aussehen, ist die Aussage.
   Die frühere türkise Pille war aus der Zeit, als Türkis jede neutrale Meldung
   markierte — auf einem Screen, der sonst weiss ist, schrie sie lauter als das,
   worüber sie berichtete. Türkis trägt jetzt nur noch, was Karma feiert
   (Karma-Flug, Level-Screen). */
.toast-content {
    background: var(--surface-raised);
    color: var(--text-primary);
    padding: 0.85rem 1.4rem;
    border-radius: var(--radius-full);
    box-shadow: 0 12px 30px rgba(var(--shadow-rgb), 0.18);
    font-weight: 700;
    font-size: 0.95rem;
    text-align: center;
    /* Mit Icon (KARMA-02: Medaille) sitzt es links vom Text auf einer Linie;
       ohne Icon ändert der Flex-Kontext nichts an der zentrierten Zeile. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
}
.toast-content .toast-icon {
    width: 1.15em;
    height: 1.15em;
    flex: 0 0 auto;
}
/* Anklickbare Notiz (KARMA-03: Direktsprung ins Profil). */
.hint-toast.toast-clickable { cursor: pointer; }

/* Karma-Flug: poppt am frisch geschriebenen Kommentar auf und fliegt zum
   Profil-Tab (Bewegung in `flyKarma`, app.js). Türkis wie die Karma-Notiz —
   Karma gehört keiner Rubrik, es ist neutral (DS §2), und Schwarz auf Türkis
   ist die Kontrastregel. Ebene 3: Schlagschatten, kein Bevel.
   `pointer-events: none`, damit die Pille auf ihrem Weg keinen Klick abfängt. */
.karma-fly {
    position: fixed;
    z-index: 600;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.35rem 0.7rem;
    border-radius: 999px;
    background: var(--accent-turkis);
    color: #000;
    font-family: var(--font-main);
    font-size: 0.82rem;
    font-weight: 800;
    white-space: nowrap;
    pointer-events: none;
    box-shadow: 0 8px 20px rgba(var(--shadow-rgb), 0.22);
}
.karma-fly .ic { width: 1em; height: 1em; }

/* --- Das Ende des Feeds ---------------------------------------------------
   Flach und ohne Card: es ist kein Beitrag, sondern eine Ansage über die
   Liste — Tiefe gehört den Beiträgen (DS §1). Mittig, mit viel Luft darüber:
   der Abstand ist das Signal, dass hier Schluss ist. */
.feed-end {
    padding: 2.5rem 1.5rem 1rem;
    text-align: center;
}
.feed-end-title {
    margin: 0 0 0.35rem;
    font-size: 1.05rem;
    font-weight: 800;
    color: var(--text-primary);
}
.feed-end-text {
    margin: 0;
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 1.45;
    color: var(--text-muted);
}
/* Die kleine Aktions-Pille aus DS §7 — dieselbe Optik wie „Fragen hinzufügen"
   und die Filter-Status-Pille: eine Affordanz, kein Brand-Button. */
.feed-end-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-height: 2rem;
    margin-top: 1rem;
    padding: 0.5rem 1rem;
    border: none;
    border-radius: var(--radius-md);
    background: var(--surface-light);
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
}
.feed-end-btn .ic { width: 1.05em; height: 1.05em; }
.feed-end-btn i { font-size: 0.85em; }

/* --- Level-Celebration ----------------------------------------------------
   Ein neues Level ist ein Ereignis, kein Hinweis am Rand — es bekommt darum
   den ganzen Screen. Türkis, weil Karma keiner Rubrik gehört (DS §2), und
   schwarze Tinte darauf (Kontrastregel „Schwarz auf Türkis").
   Ebene 3: die Kästchen darauf sind flach (DS §3b — Inhalt auf einem Panel
   trägt kein eigenes 3D). */
.levelup-overlay {
    position: fixed;
    inset: 0;
    z-index: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem 1.5rem;
    background: var(--accent-turkis);
    color: #000;
    opacity: 0;
    transition: opacity 0.32s ease;
    cursor: pointer;
}
.levelup-overlay.is-shown { opacity: 1; }
.levelup-content {
    width: 100%;
    max-width: 420px;
    text-align: center;
}
/* Gedämpft in eigener Tinte statt in Grau: echtes Grau ist auf Türkis
   kontrastschwach — dieselbe Lösung wie im Karma-Block des Post-Celebration-
   Screens. */
.levelup-kicker {
    display: block;
    font-size: 0.85rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: rgba(0, 0, 0, 0.55);
}
.levelup-level {
    font-size: 2.6rem;
    font-weight: 900;
    line-height: 1.1;
    margin-top: 0.15rem;
}
.levelup-sub {
    margin: 0.15rem 0 1.25rem;
    font-size: 0.95rem;
    font-weight: 600;
    color: rgba(0, 0, 0, 0.62);
}
/* Die Kästchen tragen dieselbe Form wie in der Level-Ansicht, nur die Fläche
   wechselt: `--surface-light` verschwände auf Türkis fast, ein durchscheinendes
   Weiss hält sie sichtbar, ohne eine zweite Farbe einzuführen. */
/* `auto-fit` statt zweier fester Spalten: schaltet ein Level nur EINE Frage
   frei, füllt das Kästchen die Breite, statt als halbe Karte links zu kleben.
   Ab zwei stehen sie wie in der Level-Ansicht nebeneinander. */
.levelup-grid {
    text-align: left;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.levelup-overlay .level-unlock-box { background: rgba(255, 255, 255, 0.42); }
.levelup-cta {
    margin-top: 1.5rem;
    padding: 0.6rem 1.2rem;
    border: none;
    border-radius: var(--radius-md);
    background: #000;
    color: var(--accent-turkis);
    font-family: inherit;
    font-size: 0.9rem;
    font-weight: 800;
    cursor: pointer;
}

/* --- Radial Menu (FEED-08: im DS-Design statt Brutalism) --- */
.radial-menu-overlay {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    z-index: 1000;
    /* Abdunklung dunkelblau getönt statt schwarz — dieselbe Tinte wie die
       Schatten (DS §2: Schatten nie reines Schwarz). */
    background: rgba(var(--shadow-rgb), 0.18);
}
.radial-menu {
    position: absolute;
    width: 0; height: 0;
}
.radial-option {
    /* Auf diesem Overlay sind die Kugeln das, was die Bubbles auf der Karte
       sind: die einzigen Objekte. Also tragen sie die Tiefe — Ebene 1 mit
       Elevation, Licht-Rim oben und Bevel unten, dieselben Werte wie eine
       Post-Card (DS §3). Vorher: 3px schwarze Kante + Vollschatten, das
       letzte Brutalism-Erbe im Feed. */
    --bubble-elev: 0 12px 28px rgba(var(--shadow-rgb), 0.14);
    position: absolute;
    width: 50px;
    height: 50px;
    margin-left: -25px;
    margin-top: -25px;
    background: var(--surface-color);
    border: none;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    box-shadow: var(--bubble-elev),
                inset 0 1px 0 var(--bevel-light),
                inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
    transition: transform 0.12s, box-shadow 0.12s, background 0.12s;
    user-select: none;
}
.radial-option.active {
    /* Türkis, nicht Gelb: eine Reaktion ist eine neutrale Aktion (DS §2), und
       der Circle öffnet in beiden Rubriken — eine Rubrikfarbe hier würde die
       Geste je nach Kontext anders einfärben, obwohl es dieselbe ist.
       Farbige Vollfläche → Bevel-Deckkraft doppelt + weiches Licht oben
       (DS §3), sonst bliebe die Kante auf Türkis unsichtbar. Die Elevation
       bleibt gleich: sie beschreibt die Ebene, nicht das Objekt. */
    transform: scale(1.3);
    background: var(--primary-color);
    box-shadow: var(--bubble-elev),
                inset 0 3px 7px rgba(255, 255, 255, 0.25),
                inset 0 -5px 10px rgba(var(--shadow-rgb), 0.15);
    z-index: 10;
}

/* Floating reaction emoji — pops in, drifts up and fades (Instagram-like).
   Positioned via JS at the thumb gesture point or the post center. */
.floating-reaction {
    position: fixed;
    z-index: 2000;
    font-size: 5rem;
    line-height: 1;
    pointer-events: none;
    will-change: transform, opacity;
    animation: reaction-float 0.9s ease-out forwards;
    -webkit-text-stroke: 0;
}
@keyframes reaction-float {
    0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.3); }
    15%  { opacity: 1; transform: translate(-50%, -50%) scale(1.15); }
    35%  { opacity: 1; transform: translate(-50%, -35%) scale(1); }
    100% { opacity: 0; transform: translate(-50%, 70%) scale(0.85); }
}
/* Abbrechen ist kein Destruktiv-Fall — es wirft nichts weg, es lässt nur los.
   Darum kein Rot (das gehört Löschen/Melden), sondern die ruhige graue Spur mit
   dunklem Icon. Der Bevel bleibt der weisse: die Fläche ist hell, die
   Vollflächen-Regel gilt hier nicht. */
.radial-cancel {
    color: var(--text-muted);
    font-size: 1.1rem;
}
.radial-cancel.active {
    background: var(--surface-light);
    color: var(--text-primary);
    box-shadow: var(--bubble-elev),
                inset 0 1px 0 var(--bevel-light),
                inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
}

/* --- Pins Feature --- */
.reach-badge {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    /* Umrahmt — bewusste Border-Ausnahme (DS §1: „Rahmen = Marke"). Das Badge
       ist ein Stempel, kein Fliesstext: der Rahmen macht es als eigenständige
       Angabe lesbar und zeigt, dass man es antippen kann (öffnet den
       Reichweite-Tooltip). Nur das Gewicht ist zurückgenommen (800 → 600),
       fett gesetztes Grau drängt sich auf. */
    background: var(--surface-color);
    color: var(--text-muted);
    font-size: 0.7rem;
    font-weight: 600;
    padding: 0.15rem 0.5rem;
    border-radius: var(--radius-sm);
    border: 1.5px solid var(--text-muted);
    margin-left: 0.4rem;
    vertical-align: middle;
    /* KEIN `text-transform: lowercase` mehr (*2026-08-04 von Jascha*): das war
       ein Rest aus der Zeit vor GLOB-08, als in der App alles klein geschrieben
       war. „Friends" ist ein verdeutschtes Substantiv und wird gross
       geschrieben — im Badge wie in der Status-Zeile und überall sonst. Der
       Text stand im Code immer schon gross; nur die Anzeige machte ihn klein. */
    line-height: 1;
    cursor: pointer;
}
.reach-icons {
    display: inline-flex;
    align-items: center;
}
.reach-icons i {
    font-size: 0.55rem;
    margin-left: -2px;
}
/* NUR DIE PERSONEN, NICHT DER GLOBUS (*2026-08-04 von Jascha*: „das Weltkugel-
   Symbol ist ausgefüllt grau und fast nicht erkennbar"). Der Globus lebt von
   seinen dünnen Meridianen — ein Strich in derselben Farbe wächst von beiden
   Seiten in die Lücken und macht aus ihm einen grauen Klumpen. Und er braucht
   den Ausgleich gar nicht: er steht immer allein, es gibt keinen Stapel, in dem
   er neben einem anderen Glyph sitzt.
   DERSELBE STRICH FÜR DIE PERSONEN (*2026-08-04 von Jascha*: „die dunklen
   Männchen sitzen ein wenig weiter oben als das weisse"). Der Strich läuft
   mittig auf der Kontur, das Glyph wächst dadurch rundum um seine halbe
   Breite. Trug ihn nur das weisse „Ich"-Icon, malte es 0,22px höher und
   0,26px tiefer als seine Nachbarn. An der gemeinsamen Unterkante liest sich
   das als Versatz: die dunklen sitzen scheinbar höher, obwohl sie nur kleiner
   sind.
   `currentColor` heisst: die dunklen umranden sich in ihrer eigenen Farbe und
   werden dadurch nur eine Spur satter, das weisse behält die dunkle Kontur
   (Regel darunter). */
.reach-icons i.fa-user {
    -webkit-text-stroke: 0.5px currentColor;
}
.reach-icons i:first-child {
    margin-left: 0;
}
/* „Ich"-Icon: solides User-Glyph in weiss mit dünner Outline-Linie via
   text-stroke. Sieht aus wie ein Outline-Icon mit weissem Fill — dadurch
   wird das nachfolgende, überlappende Icon nicht durch eine transparente
   Outline-Mitte sichtbar. Gleiche Bounding-Box wie ein normales fa-solid →
   kein Layout-Versatz im Stack. */
/* Zwei Klassen im Selektor, damit die Farbe die Kurzschrift oben schlägt:
   `.reach-icons i` ist spezifischer als `.reach-icon-me` allein, und die
   Kurzschrift dort setzt die Farbe mit. Nur die FARBE wird überschrieben —
   die Breite ist für alle dieselbe, das ist der ganze Punkt. */
.reach-icons i.reach-icon-me {
    color: #fff;
    -webkit-text-stroke-color: var(--text-muted);
}
/* Label bleibt im Badge versteckt — wird im Feed-Kontext NICHT inline gerendert.
   Beim Klick zeigt JS stattdessen eine globale .reach-tooltip-Bubble auf Body-
   Level (position: fixed), die alle overflow:hidden-Ancestors umgeht. Im
   Compose-Hint überschreibt ein eigener Scope das hier und macht das Label
   wieder inline sichtbar. */
.reach-label {
    display: none;
}

/* Body-level Tooltip — gepositioniert via JS in toggleReachBadge.
   Wird ausserhalb aller Cards eingehängt → keine Probleme mit overflow:hidden. */
/* Ebene 3 (Panel): Schlagschatten, kein Bevel, KEIN Border (DS §3/§1). */
.reach-tooltip {
    position: fixed;
    background: var(--surface-color);
    color: var(--text-primary);
    font-weight: 800;
    font-size: 0.8rem;
    /* Dasselbe wie beim Badge: der Tooltip zeigt denselben Namen und darf ihn
       nicht anders schreiben. */
    padding: 0.35rem 0.6rem;
    border-radius: var(--radius-sm);
    white-space: nowrap;
    pointer-events: none;
    z-index: 10000;
    line-height: 1;
    box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.16);
    transform: translateX(-50%);
}
/* Pfeil unten — jetzt einstufig weiss. Die zweite, schwarze Stufe war nur die
   Fortsetzung der weggefallenen Border. */
.reach-tooltip::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 100%;
    transform: translateX(-50%);
    border-style: solid;
    border-color: transparent;
    border-width: 6px;
    border-top-color: var(--surface-color);
}

.audience-badge {
    background: var(--accent-turkis);
    color: #000;
    font-size: 0.7rem;
    font-weight: 800;
    padding: 0.1rem 0.4rem;
    border-radius: var(--radius-sm);
    margin-bottom: 0.5rem;
    display: inline-block;
}

/* Creation Modal Options */
.pin-options-container {
    margin-top: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
}
.poll-container {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.poll-container.hidden { display: none; }
/* Basis-Regel für die Umfrage-Optionen; im Compose überschreibt sie die
   `#create-modal.compose-mode`-Regel weiter unten. Eingelassen statt umrandet
   (DS §5) — die 3px-Kante war Brutalism-Erbe (DS-03). */
.poll-input {
    width: 100%;
    padding: 0.8rem;
    font-family: inherit;
    font-size: 1rem;
    border: none;
    border-radius: var(--radius-md);
    background: var(--surface-color);
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
}

/* Poll UI in Feed */
.poll-view {
    margin-top: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
/* Frage über den Optionen (PIN-03): ohne sie wären die Balken Antworten ohne
   Frage. Etwas kräftiger als der Beitragstext, aber kein eigener Titel-Look —
   sie gehört zum Beitrag, nicht über ihn. */
.poll-question {
    font-weight: 800;
    font-size: 0.95rem;
    color: var(--text-primary);
    margin-bottom: 0.15rem;
}

/* Event-Block auf der Pin-Card (PIN-03). Flach auf der Card (DS §3, Ebene 2)
   und auf der eingelassenen Spur wie die Poll-Balken — es ist Inhalt, keine
   zweite Card. Icon links, Titel und Meta rechts: dieselbe Lesart wie eine
   Personenzeile (Bild links, Auskunft daneben). */
.event-block {
    display: flex;
    align-items: flex-start;
    gap: 0.7rem;
    margin-top: 1rem;
    padding: 0.8rem 0.9rem;
    background: var(--surface-light);
    border-radius: var(--radius-sm);
}
.event-block-icon {
    flex: 0 0 auto;
    width: 1.2rem;
    height: 1.2rem;
    margin-top: 0.1rem;
    color: var(--accent-pink);   /* Event ist ein Pin-Modul — Rubrikfarbe */
}
.event-block-body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.event-block-title {
    font-weight: 800;
    font-size: 0.95rem;
    color: var(--text-primary);
}
/* Meta wie überall auf einer Card: gedämpft, 0.8rem, 600 — nie fett (DS §11). */
.event-block-meta {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-muted);
}
.event-block-meta .ic {
    width: 0.9rem;
    height: 0.9rem;
    flex: 0 0 auto;
}
.poll-option-btn {
    width: 100%;
    padding: 0.8rem;
    background: var(--surface-light);
    /* EIN BUTTON ERBT SEINE TEXTFARBE NICHT (*2026-08-05 von Jascha gemeldet:
       „Text bei Umfrage ist zu dunkel"). `font-family: inherit` stand hier,
       `color` nicht — also galt das Schwarz des Browsers. Im Hellen sah man
       das nie, im Dunkeln stand schwarze Schrift auf der fast schwarzen Spur.
       Jetzt aus dem Token, damit sie in beiden Modi mitgeht. */
    color: var(--text-primary);
    border: none;
    border-radius: var(--radius-sm);
    font-family: inherit;
    font-size: 1rem;
    font-weight: 700;
    text-align: left;
    cursor: pointer;
    transition: background 0.1s;
    position: relative;
    overflow: hidden;
}
.poll-bar {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    background: var(--accent-pink);
    opacity: 0.3;
    z-index: 0;
}
.poll-text {
    position: relative;
    z-index: 1;
}

/* (Das frühere `#pin-detail-modal` samt `.pin-detail-container`,
   `.pin-detail-content`, `.pin-reply-bar` und den `.reply-*`-Cards ist mit
   PIN-02 entfallen: die Pin-Detailseite ist seit FEED-03 die gemeinsame
   Full-Bleed-Detailseite, und der Antworten-Pfeil öffnet seit FEED-02 dasselbe
   Bottom-Sheet wie das Kommentar-Symbol. Im Markup kam nichts davon mehr vor —
   es waren die letzten Brutalism-Regeln dieser Ecke, harte Kanten und
   Vollschatten inklusive.) */

/* Create Post Modal — fullscreen, leaves space for the bottom nav */
#create-modal {
    bottom: 0;                  /* fullscreen — deckt die schwebende Nav; Compose hat eigenes „abbrechen" */
    padding: 0;
    background: var(--bg-color);
}
#create-modal .modal-content {
    width: 100%;
    height: 100%;
    max-width: none;
    max-height: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    transform: none;
    padding: 1.5rem;
    overflow-y: auto;
}
#create-modal:not(.hidden) .modal-content {
    transform: none;
}
/* Modal Redesign (Audience, Poll) */





.audience-label {
    font-weight: 800;
    font-size: 0.9rem;
    color: var(--accent-pink);
}

/* (`.board-search` / `.board-chips` / `.board-chip` sind mit
   BOARD-02 ersatzlos entfallen — die Board-Auswahl im Compose läuft über
   `.board-search-input` / `.board-chip`.) */

/* ================================
   FRIENDS TAB
   ================================ */

/* Home Rubric Views (Map / Updates / Pins) */
#main-feed-state {
    touch-action: pan-y;
}
.home-view {
    display: block;
    /* Positioniert, damit der Geist des Rubrik-Wechsels an `#full-feed` hängen
       kann (GLOB-15, siehe `.rubric-pane-ghost` am Dateiende). `#profile-content`
       ist es bereits. */
    position: relative;
}
.home-view.hidden {
    display: none;
}

/* Filter Strip (Home Feed) — white panel that breaks out of main's 1.5rem
   padding (so it touches the header bottom directly, no dark-blue gap above)
   and extends edge-to-edge. The downward glow takes the rubric's accent color
   (yellow for Updates, pink for Pins) — set via .filter-strip-updates /
   .filter-strip-pins variant classes. */
.filter-strip {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    /* Oben und unten gleich viel. Vorher 0.85 / 1rem — der Unterschied fiel
       auf, sobald der Strip eine eigene Fläche bekam: der Abstand über der
       ersten Sektions-Überschrift war sichtbar kleiner als der unter der
       letzten Reihe. Eine Bahn, die man hervorzieht, muss oben und unten
       gleich atmen.
       SEITLICH 14px — dieselbe Achse wie die Post-Cards (`.post-card` trägt
       `margin: 0 14px`). Die Bahn selbst läuft weiter von Rand zu Rand, nur ihr
       Inhalt steht auf der Linie der Cards darunter. Vorher waren es 16px am
       Strip PLUS 16px an den Reihen (Media-Query) = 32px: die Chips lagen damit
       auf der Achse der Card-INHALTE (Avatar, Zeitstempel), nicht auf der Kante
       der Cards — im Bild wirkte der Filter dadurch schmaler als der Feed. */
    padding: 0.85rem 14px;
    /* Ganz leichtes Grau statt Weiss: der Strip hebt sich beim Herunterziehen
       vom weissen Inhalt ab. `--surface-light` ist das einzige und zugleich
       hellste helle Grau der App (DS-02, „eingelassene Spur") — bewusst kein
       neuer Ton, die Graustufen sind mit PROF-01 gerade zusammengeführt worden.
       Folge davon: die Chips wechseln auf Weiss (siehe unten). */
    background: var(--surface-light);
    /* Keine Rundung: der Strip ist eine Bahn, die unter dem Header hervorkommt,
       keine Card. Mit Fläche wären die gerundeten Unterecken plötzlich sichtbar
       und liessen ihn über der Liste schweben. */
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    /* margin-top overshoots main's padding slightly so sub-pixel rounding
       can never expose the page-bg between header and strip.
       SEITLICH 0: die früheren -1.5rem hoben ein horizontales Padding von `main`
       auf, das es nicht mehr gibt — dadurch war der Strip 48px breiter als der
       Viewport, die Seite horizontal scrollbar und wirkte beim Laden nach links
       verschoben (FEED-12). */
    margin: -2rem 0 1.875rem;
}
.filter-strip-updates {
    box-shadow:
        0 4px 12px rgba(255, 230, 0, 0.55),
        0 10px 28px rgba(255, 230, 0, 0.3),
        0 20px 48px rgba(255, 230, 0, 0.12);
}
.filter-strip-pins {
    box-shadow:
        0 4px 12px rgba(255, 0, 102, 0.45),
        0 10px 28px rgba(255, 0, 102, 0.25),
        0 20px 48px rgba(255, 0, 102, 0.1);
}

/* Pull-to-refresh indicator — sits between header and main. Height grows with
   the touch pull; rubric color follows the active tab. */
.pull-refresh-indicator {
    height: 0;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--accent-yellow);
    transition: height 0.25s cubic-bezier(0.22, 1, 0.36, 1);
    will-change: height;
}
.pull-refresh-indicator.pulling {
    /* During active touch we drive height directly — no easing in the way. */
    transition: none;
}
.pull-refresh-indicator.pins-mode {
    background: var(--accent-pink);
}
.pull-refresh-icon {
    font-size: 1.4rem;
    color: #000;
    transition: transform 0.15s ease-out;
}
.pull-refresh-indicator.pins-mode .pull-refresh-icon {
    color: #fff;
}
.pull-refresh-indicator.refreshing {
    height: 64px;
}
.pull-refresh-indicator.refreshing .pull-refresh-icon {
    animation: pull-refresh-spin 0.8s linear infinite;
}
@keyframes pull-refresh-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}
/* Chips im Strip: WEISS statt der eingelassenen Spur-Farbe. Sie tragen sonst
   denselben Ton wie der Strip und verschwinden darin. Auf dem grauen Grund ist
   das helle Steuerelement ohnehin die klarere Lesart — dieselbe Logik wie die
   Sortier-Pillen, die schon immer weiss sind. Aktive Chips behalten ihre
   Rubrikfarbe. Die frühere Hilfs-Border („weiss auf weiss") ist damit
   überflüssig geworden. */
.filter-strip .filter-chip:not(.active) {
    background: var(--surface-color);
}
.filter-strip .filter-section-label {
    color: var(--text-secondary);
}
.filter-row {
    display: flex;
    gap: 0.5rem;
    overflow-x: auto;
    white-space: nowrap;
    scrollbar-width: none;
}
.filter-row::-webkit-scrollbar {
    display: none;
}
.filter-chip {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.8rem;
    background: var(--surface-color);
    border: none;
    border-radius: var(--radius-md);
    font-family: inherit;
    font-weight: 800;
    font-size: 0.85rem;
    cursor: pointer;
    transition: all 0.1s;
    color: var(--text-primary);
}
.filter-chip.disabled {
    opacity: 0.35;
    pointer-events: none;
}
.filter-chip.active {
    background: var(--accent-yellow); /* fallback */
}
/* Tab-context decides the active accent. Updates → yellow, Pins → pink. */
.filter-strip-updates .filter-chip.active { background: var(--accent-yellow); color: #000; }
.filter-strip-pins .filter-chip.active { background: var(--accent-pink); color: #fff; }
/* „+ Neue Liste" ist kein Filter, sondern eine Aktion — kein An/Aus-Zustand,
   sie öffnet einen Dialog. DS §6 reserviert die Tile-Optik für Controls MIT
   Zustand, DS §7 macht reine Aktionen flach. Die flache Fläche steht weiter
   unten im Elevation-Block (Spezifität), hier nur die Masse. */
.filter-chip.filter-add {
    padding: 0.4rem 0.7rem;
}
.filter-chip .filter-emoji {
    font-size: 1rem;
}

/* Filter sections (Pins): label above each filter group */
.filter-section {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.filter-section-label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    /* Etwas Luft um die Zeile: der Rücksetz-Griff ist grösser als die Schrift,
       und die Überschrift soll ihn tragen, nicht von ihm gesprengt werden. */
    min-height: 1.6rem;
    font-size: 0.75rem;
    font-weight: 700;
    color: rgba(255, 255, 255, 0.8);   /* KEIN Bevel: weisse Schrift auf farbigem Grund — bleibt in beiden Modi weiss */
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding-left: 0.1rem;
}
/* Zurücksetzen-Griff, rechtsbündig auf der Höhe der Überschrift. Er erbt ihre
   Farbe (`currentColor`) und trägt weder Fläche noch Rahmen: er gehört zur
   Zeile, er ist kein Objekt darin. Etwas grösser als die Schrift, damit er als
   Ziel taugt — 0.75rem Text, 1rem Icon. */
.filter-reset-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    padding: 0;
    border: none;
    background: none;
    color: inherit;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}
.filter-reset-btn .ic { width: 1em; height: 1em; }
.filter-reset-btn:active { opacity: 0.5; }
/* Sichtbar nur, wenn die Sektion von ihrem Default abweicht — ein
   Zurücksetzen, das nichts zurücksetzt, wäre ein Schalter ohne Wirkung.
   `visibility` statt `display`, damit die Überschrift ihre Höhe behält und die
   Zeile darunter beim Filtern nicht zuckt. */
.filter-reset-btn.is-hidden { visibility: hidden; }

/* --- Filter-Status unter dem Strip ----------------------------------------
   Sagt in der Ruhelage (Strip geparkt), was gerade gefiltert wird, und führt
   per Pfeil zurück zu den Chips. Optik wie „Fragen hinzufügen" im Profil:
   gestrichelte Hairline, keine Fläche, Text in Sekundär-Grau — eine
   Affordanz, kein Inhalt, und darum auch kein Tile mit Zustand (DS §6).
   Zentriert, weil sie zu keiner Seite gehört: sie beschreibt die Liste
   darunter als Ganzes. */
.filter-status {
    display: none;
    justify-content: center;
    padding: 0 1rem;
    /* Mittig im Abstand zwischen Filter-Section und erstem Post. Unten liegt
       das gewohnte Mass (1.875rem), oben elf Pixel weniger — genau die Höhe,
       um die der **Top-Akzent** der ersten Card über ihrer Kante steht
       (Card-Offset, DS §4). Gerechnet sind beide Abstände sonst gleich; zu
       SEHEN war der untere kleiner, weil dort die Farbe schon früher anfängt.
       Der negative Rand kollabiert mit dem `margin-bottom` des Strips.
       Ist die Pille unsichtbar, fallen beide Ränder mit ihr weg — der Abstand
       ist dann exakt der alte. */
    margin-top: -11px;
    margin-bottom: 1.875rem;
}
.filter-status.is-visible { display: flex; }

/* DIESELBE PILLE, DREI ORTE — UND DREI VERSCHIEDENE ABSTÄNDE (BOARD-05).
   Die Werte oben sind auf den Feed gerechnet: darüber ein Strip mit 1.875rem
   Bodenabstand, darunter eine Card mit Top-Akzent. Auf den Board-Ansichten
   steht darüber und darunter anderes, also musste das Mass neu gemessen und
   nicht übernommen werden — gleiche Zahlen ergäben hier ungleiche Abstände.

   BOARD-DETAILSEITE. Der Strip trägt dort `margin-bottom: 0` (siehe
   `#board-detail-filter-strip`), und den Bodenabstand zur Pin-Liste liefert der
   sticky-Spacer mit 1.875rem. Die Pille bringt darum ihren Abstand nach OBEN
   selbst mit und gibt nach unten gar keinen: sonst stünden Pillen-Rand und
   Spacer übereinander (60px statt 30px).
   `calc(1.875rem - 11px)` ist dieselbe Rechnung wie oben, nur als positiver
   Wert: der Spacer darunter misst 1.875rem, und der Top-Akzent des ersten Pins
   beginnt 11px über dessen Kante — optisch sind das unten 19px, also stehen
   oben auch 19px. */
.filter-status--board-detail {
    margin-top: calc(1.875rem - 11px);
    margin-bottom: 0;
}

/* BOARDS-VOLLANSICHT. Darunter liegt keine Card mit Akzent, sondern eine
   flache Liste, deren erste Zeile 0.85rem Innenpolster trägt — sichtbar
   beginnt sie also erst dort (weisse Liste auf weissem Canvas, es gibt keine
   Kante). Der Abstand zum SICHTBAREN Anfang ist damit exakt der
   `margin-bottom` der Pille: die -0.85rem der Liste und ihr Zeilenpolster
   heben sich auf.
   Oben kollabiert der -11px mit dem 1.875rem des Strips zu 19px — also stehen
   unten dieselben 19px, statt der 30px, die die Feed-Regel liefern würde. */
.filter-status--section {
    margin-bottom: calc(1.875rem - 11px);
}

/* Die Optik der Pille steht bei `.rubric-action-btn` („Fragen hinzufügen") —
   eine Regel für beide, damit sie nicht auseinanderdriften. (Der Umbruch aus
   FEED-16 steht ebenfalls dort unten, direkt nach der geteilten Regel — er muss
   nach deren `white-space: nowrap` kommen, um zu greifen.) Eigen ist ihr nur
   der Pfeil: er dreht sich weich um, sobald der Strip hervorgezogen ist.

   RICHTUNG UMGEKEHRT (*2026-08-04 von Jascha*: „man zieht ja die Filterbox beim
   Öffnen gegen unten, also Pfeil runter"). Zu = **runter**, offen = **hoch** —
   der Pfeil zeigt, wohin sich die Box bewegt, nicht wohin man scrollt. Vorher
   war es andersherum begründet („hoch heisst, dort geht es zu den Chips"); die
   Aufklapp-Lesart ist die geläufigere und trägt beide Zustände gleich gut.
   Das Ruhe-Icon steht darum im Markup als `#i-nav-arrow-down` (`feed.js`), die
   Drehung hier bleibt unverändert — sie dreht jetzt von unten nach oben.
   Das Drehen statt Austauschen zeigt, dass es derselbe Schalter bleibt. */
.filter-status-pill .ic {
    transition: transform 240ms cubic-bezier(0.22, 1, 0.36, 1);
}
.filter-status-pill.is-open .ic {
    transform: rotate(180deg);
}

/* --- Reach-Segment-Tile im Compose (NEWPIN-01, DS §6) ---------------------
   Ein breites Tile, IMMER inset/aktiv, mit drei Stopps und dünnen Trennlinien
   dazwischen. Gefüllt wird kumulativ von links: ein Klick färbt den Stopp und
   alles links davon rosa — „Friends of Friends" schliesst die Friends ein, das
   soll man sehen. Border bleibt (Tiles sind die eine Border-Ausnahme).
   Beim LESEN bleibt es der Schieber im Filter-Strip: dort ist Reichweite eine
   Achse, hier eine Entscheidung. */
.reach-segment {
    display: flex;
    width: 100%;
    overflow: hidden;
    border: 1px solid rgba(0, 0, 0, 0.12);
    border-radius: var(--radius-md);
    /* **Weiss, auch im Compose** (2026-08-04, Jascha): ein Segment-Tile ist
       laut DS §6 immer weiss mit Hairline — die Vertiefung macht der Inset,
       nicht die Fläche. Das frühere Grau im Compose war eine Abweichung und
       liess dasselbe Bauteil an zwei Orten verschieden aussehen. */
    background: var(--surface-color);
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
}
/* `width: auto` statt 100 %: im Strip bekommt das Tile seitliche Ränder (siehe
   Media-Query), und 100 % PLUS Rand ragt über die Bahn hinaus. */
.filter-strip .reach-segment {
    width: auto;
}
.reach-segment-stop {
    /* Zeichen **links vom Wort**, klein und zurückgenommen: es begleitet den
       Text, statt ihn anzuführen. Grösser oder darüber gesetzt wurde es zum
       Hauptdarsteller des Tiles — die Auskunft ist das Wort. */
    display: flex;
    align-items: center;
    justify-content: center;
    /* Breite nach Inhalt (`auto`), nicht drei starre Drittel: „Friends of
       Friends" ist mit seinen drei Zeichen deutlich länger als „Friends" und
       bräche im Drittel um — der Rest verteilt sich gleichmässig auf alle drei.
       Bleibt auf sehr schmalen Screens trotzdem zu wenig Platz, bricht das Wort
       um und das Zeichen wandert mit (es steht im selben Textfluss). */
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.55rem 0.3rem;
    border: none;
    background: transparent;
    font-family: inherit;
    /* Knapp genug, dass „Friends of Friends" samt Zeichen auf EINE Zeile passt —
       bricht der mittlere Stopp um, wird das ganze Tile eine Zeile höher und
       die drei Stopps wirken ungleich. */
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1.2;
    /* Schwarz statt Grau im leeren Stopp: die drei Stopps sind hier die
       Auskunft, nicht ein Label über etwas anderem. Grau liess die nicht
       gewählten Stufen wie deaktiviert aussehen — wählbar sind sie alle. */
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}
/* Das Zeichen sitzt im **umrahmten Stempel** — dieselbe Form wie das Reach-Badge
   im Untertitel einer Pin-Card. Der weisse Grund trägt es auch auf der rosa
   Füllung, ohne dass die Symbole selbst die Farbe wechseln müssten: sie sehen
   im leeren wie im gefüllten Stopp gleich aus, und die Tinte bleibt nah am Text
   daneben. Werte aus `.reach-badge` (DS §1: kleiner Stempel = 1.5px Muted).
   Es steht IM Textfluss und klebt darum am Wort; bricht das Wort um, wandert es
   mit. */
.reach-segment-badge {
    display: inline-flex;
    align-items: center;
    margin-right: 0.3rem;
    padding: 0.1rem 0.32rem;
    border: 1.5px solid var(--text-muted);
    border-radius: var(--radius-sm);
    background: var(--surface-color);
    color: var(--text-muted);
    /* Auf der Linie des Worts. `text-bottom` setzt die Unterkante des Stempels
       auf die Unterkante der Textzeile — er ist gleich hoch wie sie, also sitzt
       er damit mittig. Ein fester em-Wert wäre nur für diese eine Schriftgrösse
       richtig; gemessen lagen die Mitten vorher 3px auseinander. */
    vertical-align: text-bottom;
}
.reach-segment-stop .reach-icons i { font-size: 0.48rem; }
/* Stempel und Wort bleiben auf EINER Zeile: bricht die Zeile, steht der
   Stempel allein über dem Wort und der Stopp wird doppelt so hoch. Dafür trägt
   der mittlere Stopp im Tile die Kurzform „Fr. of Fr." (siehe components.js). */
.reach-segment-text {
    min-width: 0;
    white-space: nowrap;
}
/* **Leere Stopps stehen erhaben** wie ein Filter-Tile: Licht an der Oberkante,
   weiche Abschattung unten (2026-08-04, Jascha — nach direktem Vergleich mit
   der durchgehend eingelassenen Fassung gewählt). Damit trägt auch dieses Tile
   die Zustands-Sprache aus DS §6: erhaben = noch nicht gewählt, eingedrückt +
   Farbe = gewählt.
   Als **Verlauf** gebaut, nicht als `inset`-Schatten: ein Inset legt sich auch
   an die Seitenkanten jedes Stopps, und dann würfen die Trennlinien innen im
   Tile Schatten — sie sollen flach hinten liegen (dieselbe Begründung wie bei
   der Füllung). */
.reach-segment-stop:not(.filled) {
    background-color: var(--surface-color);
    background-image:
        linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0) 2px),
        linear-gradient(to top, rgba(var(--shadow-rgb), 0.18), rgba(var(--shadow-rgb), 0) 7px);
}

/* Trennlinien nur ZWISCHEN den Stopps — aussen sitzt schon der Rahmen. */
.reach-segment-stop + .reach-segment-stop {
    border-left: 1px solid rgba(0, 0, 0, 0.12);
}
.reach-segment-stop.filled {
    background: var(--accent-pink);
    color: var(--text-light);   /* Weiss auf Rosa (DS §2) */
}
/* **Die Vertiefung muss auf der Füllung nochmal stehen:** sie sitzt am Tile,
   und die eigene Fläche des gefüllten Stopps malt darüber — die rosa Strecke
   sah dadurch flach aus, während die leeren Stopps eingelassen wirkten.
   Sie kommt als **Verlauf von oben**, nicht als `inset`-Schatten: ein Inset
   legt sich auch an die linke und rechte Kante jedes Stopps, und dann würfen
   die Trennlinien innen im Tile Schatten — sie sollen aber flach hinten
   liegen. Der Verlauf schattet nur die Oberkante ab und endet nach 8px.
   Kräftiger als am Tile (0.22 gegen 0.20), weil eine gesättigte Vollfläche
   den Verlauf schluckt (DS §3). */
.reach-segment-stop.filled {
    background-image: linear-gradient(
        to bottom,
        rgba(var(--shadow-rgb), 0.22),
        rgba(var(--shadow-rgb), 0) 8px
    );
}
/* Auf der gefüllten Fläche wechselt nur der **Rahmen** des Stempels auf Weiss:
   die graue Kante ist auf Rosa ein Fremdkörper, und die Fläche darunter ist
   ohnehin weiss — der Stempel liest sich dort als ruhige weisse Marke, den
   Kontrast trägt die Farbe ringsum. Die Zeichen darin bleiben, wie sie sind. */
.reach-segment-stop.filled .reach-segment-badge {
    border-color: #fff;
}
/* Die Trennlinie innerhalb der gefüllten Strecke wird hell: sie liegt auf
   Rosa und wäre als dunkle Kante ein Fremdkörper — sichtbar bleiben soll die
   Rasterung, nicht ein Gitter. */
.reach-segment-stop.filled + .reach-segment-stop.filled {
    border-left-color: rgba(255, 255, 255, 0.35);
}

/* (Der frühere Range-Schieber der Reichweite — `.reach-slider`, `.reach-slider-row`,
   `.reach-slider-label*` — ist entfallen: Feed und Board-Detailseite tragen seit
   NEWPIN-01/2026-08-04 dasselbe Segment-Tile wie das Compose. Damit gibt es die
   Reichweite app-weit in EINER Form. Regeln dazu: `.reach-segment` weiter unten.) */

/* Friends Views */
.friends-view {
    display: block;
}
.friends-view.hidden {
    display: none;
}

/* Board Sub-Views */
#board-list-view.hidden,
#board-detail-view.hidden,
#board-members-view.hidden,
#board-section-detail-view.hidden,
/* Es gibt in diesem Projekt KEIN generisches `.hidden` — jede Sub-View muss
   hier namentlich stehen. Fehlte sie, bliebe der Swipe-Stapel (fest
   positioniert) über der Boards-Liste stehen, sobald man zurückgeht. */
#board-swipe-view.hidden {
    display: none;
}

/* Karte (MAP-03) — fullscreen unter dem Header, OHNE Bottom-Nav: sie ist eine
   Detail-Ansicht im Chat-Tab, zurück führt der Back-Button im Header
   (Detail-/Sub-Ansicht-Regel). Fix positioniert, damit ein Zug an der Karte
   nicht die Seite darunter verschiebt. Z-Index wie die Chat-Ansicht: beide
   sind Body-Level-Overlays über dem stehenden Tab. */
.map-view {
    position: fixed;
    top: var(--header-h, 110px);
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 100%;
    max-width: 600px;
    z-index: 92;
}
.map-view.hidden { display: none; }
#map-container {
    width: 100%;
    height: 100%;
}
/* Solange die Karte offen ist, scrollt die Seite darunter nicht mit. */
body.map-mode {
    overflow: hidden;
    overscroll-behavior: none;
}

/* --- MAP-02 · 3D-Bubble-Marker -------------------------------------------
   Auf der Karte gilt DS §3, Beispiel B: die Karte ist der Background, die
   Profil-Bubbles sind die Hauptelemente — also tragen NUR sie die Tiefe.
   Ebene-1-Behandlung wie eine Card: Elevation + Bevel unten, dazu der Akzent
   oben in derselben Card-Offset-Technik (§4), nur auf die Bubble-Grösse
   heruntergerechnet.
   Türkis als Akzent, weil die Karte im Freunde-Kontext lebt (DS §2). */
.friend-marker-wrapper {
    /* Das Icon ist breiter als die Bubble, damit lange Vornamen im Schild
       Platz haben, ohne den Anker zu verschieben. */
    text-align: center;
    cursor: pointer;
    /* Leaflet setzt die Icon-Grösse hart; der türkise Akzent ragt oben hinaus
       und darf nicht abgeschnitten werden. */
    overflow: visible;
}
/* Die eigene Bubble führt nirgendwohin (das eigene Profil lebt im Account-Tab),
   also zeigt sie auch keine Hand. */
.friend-marker-wrapper.is-me { cursor: default; }
/* Steht „Standort teilen" auf Aus, wird sie grau — sie bleibt stehen, statt zu
   verschwinden (*2026-08-04 von Jascha*): der Schalter regelt, wer MICH sieht,
   nicht was ich sehe. Grau sagt beides auf einmal — hier bin ich, und gerade
   sieht es niemand. Nur die eigene: die Freunde teilen ja weiter.
   `filter` statt einer zweiten Farbe: das Grau soll dasselbe Bild sein, nicht
   ein anderes. NUR Graustufe, NICHT durchsichtig (*2026-08-04 von Jascha*):
   durchscheinend sähe die Bubble aus, als verschwände sie halb — und dass sie
   bleibt, ist ja die Aussage. Grau heisst „nicht geteilt", blass hiesse „nicht
   ganz da". */
/* Die Graustufe liegt auf dem BILD, nicht auf dem ganzen Marker. Lag sie am
   Wrapper, entfärbte sie auch das Schild darunter — und damit das Grün des
   Hinweises, das dort gerade etwas sagen soll. Ein Filter färbt alles unter
   sich um, auch das, was gar nicht gemeint war. */
#map-container.sharing-off .friend-marker-wrapper.is-me .friend-marker-bubble {
    filter: grayscale(1);
}
/* Und das Foto wird zusätzlich blass (*2026-08-04 von Jascha*). Ein Overlay
   braucht es dafür nicht: die Bubble ist innen weiss, also genügt Deckkraft auf
   dem BILD — das Weiss dahinter tut, was ein aufgelegter Schleier täte, nur
   ohne zweites Element.
   Wichtig ist, WO die Deckkraft sitzt. Auf dem ganzen Marker sah er aus, als
   verschwände er halb (deshalb war sie dort schon einmal draussen); auf dem
   Foto allein bleiben Kreis, Schatten und Schild voll stehen — blass wird nur
   das Gesicht, und genau das ist die Aussage. */
#map-container.sharing-off .friend-marker-wrapper.is-me .friend-marker {
    opacity: 0.45;
}
/* Und das Schild darunter sagt es in Worten (*2026-08-04 von Jascha*). Graustufe
   allein sieht auf einem Bild, das ohnehin dunkel sein kann, nach Zufall aus.

   Der Name bleibt FETT und wird nur blass: das Gewicht gehört zur Form des
   Schilds — nähme man es weg, sähe die Bubble aus wie eine andere Sorte Marker
   statt wie dieselbe im anderen Zustand. Die Farbe ist ein Inaktiv-Grau,
   bewusst unter der Lesbarkeitsschwelle für Fliesstext: hier ist das Verblassen
   die Aussage, und die Auskunft steht in der Zeile darunter.

   Die tritt an die Stelle der Zeitangabe: „Jetzt" wäre falsch, wenn gerade
   niemand etwas sieht. Beide Fassungen stehen im Markup und werden per CSS
   getauscht — der Schalter kippt eine Klasse am Karten-Container, und Leaflet
   baut die Bubble beim Zoomen neu auf, wo ein gesetzter Text verloren ginge.
   Der Satz darf umbrechen (die Schilder sind sonst `nowrap`): in einer Zeile
   wäre er dreimal so breit wie die Bubble und läge über den Nachbarn. Wie
   schmal er bricht, bestimmt der 64px breite Icon-Kasten von Leaflet, nicht
   diese Regel — nachgemessen: eine `max-width` von 92 bis 124px ändert nichts,
   der Satz bleibt bei 50px. Damit steht er im Fussabdruck des Markers und
   deckt keinen Nachbarn zu. */
/* EINE Tinte für den ganzen ausgeschalteten Marker (*2026-08-04 von Jascha*:
   „das ‚Standort wird nicht geteilt' sollte im gleichen Grau sein wie das
   ‚Du'"). Als Variable am Marker, nicht zweimal als Wert: der eine Zustand hat
   eine Farbe, und zwei getrennte Werte driften beim nächsten Nachjustieren
   auseinander. */
#map-container.sharing-off .friend-marker-wrapper.is-me {
    --inaktiv-tinte: #c9c9c9;
}
#map-container.sharing-off .friend-marker-wrapper.is-me .friend-marker-name {
    color: var(--inaktiv-tinte);
}
/* Das „Jetzt" am eigenen Marker ist grün, solange geteilt wird — es ist die
   Auskunft, dass der Standort gerade rausgeht, und die ist positiv gemeint.
   Nur am eigenen: bei den Freunden ist die Zeitangabe eine Alters-Angabe
   („Vor 2 Std"), keine Zusage. Im ausgeschalteten Fall ist diese Zeile ohnehin
   ausgeblendet — dort steht der graue Hinweis. */
.friend-marker-wrapper.is-me .friend-marker-time { color: var(--success); }

.friend-marker-off { display: none; }
#map-container.sharing-off .friend-marker-wrapper.is-me .friend-marker-time { display: none; }
#map-container.sharing-off .friend-marker-wrapper.is-me .friend-marker-off {
    display: block;
    white-space: normal;
    font-weight: 600;
    font-size: 0.6rem;
    line-height: 1.2;
    /* Dieselbe blasse Tinte wie der Name darüber. Grün war der Zwischenschritt
       und ist wieder raus: der ganze Marker steht hier auf inaktiv — graues
       Bild, blasser Name —, und eine Farbe mittendrin zöge die Aufmerksamkeit
       auf das, was gerade NICHT stattfindet. Rot war ohnehin nie gemeint:
       „nicht geteilt" ist kein Fehler, sondern die Einstellung. */
    color: var(--inaktiv-tinte);
}

.friend-marker-bubble {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    border-radius: 50%;
    background: var(--surface-color);
    /* Reihenfolge von aussen nach innen:
       1. Elevation — die Ebene, nicht das Objekt (DS §3): unveränderte
          System-Werte, damit die Bubble genauso hoch schwebt wie eine Card.
       2. Bevel unten.
       KEIN türkiser Akzent (2026-08-04, Jascha): auf einer Karte voller Farbe
       war er ein weiteres Signal, das um Aufmerksamkeit streitet — die weisse
       Bubble trennt schon deutlich genug. Die Card-Offset-Technik aus DS §4
       bleibt den Cards vorbehalten, wo der Akzent eine Rubrik benennt. */
    box-shadow:
        0 12px 28px rgba(var(--shadow-rgb), .14),
        inset 0 -3px 6px rgba(var(--shadow-rgb), .07);
}
/* Inhalt auf einem Ebene-1-Objekt ist flach (DS §1) — der Avatar hat darum
   weder Ring noch eigenen Schatten mehr. Beides kam aus der Zeit, als er
   ungeschützt auf der Karte lag. */
.friend-marker {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--surface-light);
    display: block;
}
/* Das Namensschild ist Inhalt neben der Bubble, keine zweite Ebene: weisse
   Fläche, kein Schatten, kein Rahmen. Die frühere Fassung setzte den Namen
   direkt auf die Karte und trennte ihn mit vier weissen Text-Schatten davon —
   eine Kontur, die die App sonst nirgends kennt. */
.friend-marker-label {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    margin-top: 6px;
    padding: 0.15rem 0.45rem;
    border-radius: var(--radius-sm);
    background: var(--surface-color);
    font-family: var(--font-main);
    line-height: 1.25;
    white-space: nowrap;
}
.friend-marker-name {
    font-weight: 700;
    font-size: 0.7rem;
    color: var(--text-primary);
}
/* Wie alt der Standort ist — Meta, also Meta-Grau und kleiner (dieselbe
   Behandlung wie Zeit und Ort auf einer Post-Card). */
.friend-marker-time {
    font-weight: 600;
    font-size: 0.6rem;
    color: var(--text-muted);
}

/* (Die Zoom-Tasten sind mit 2026-08-04 ganz entfallen — eine App zoomt mit
   zwei Fingern. Die Regeln, die sie an die App-Sprache angleichen sollten,
   sind damit gegenstandslos; abgeschaltet wird das Control beim Aufsetzen der
   Karte, `zoomControl: false` in friends.js.) */
/* Die Quellenangabe muss stehen bleiben (Lizenz), aber leise: Meta-Grau auf
   halbtransparentem Weiss statt kräftigem Blau auf Vollweiss. */
.map-view .leaflet-control-attribution {
    background: rgba(255, 255, 255, 0.8);   /* Leaflet-Plakette: bleibt hell, sie steht auf der Karte */
    color: var(--text-muted);
    font-size: 0.6rem;
}
.map-view .leaflet-control-attribution a { color: var(--text-secondary); }
/* Der letzte Hover in der App kam nicht von uns: Leaflet unterstreicht seine
   Attributions-Links beim Darüberfahren. Eigenes CSS trägt seit 2026-08-04
   **keine** `:hover`-Regel mehr, und ein fremdes Stylesheet ist kein Grund für
   eine Ausnahme — das Ziel war „keine Hover-Effekte", nicht „keine eigenen".
   Angefasst wird nur die Dekoration; der Link bleibt ein Link. */
.map-view .leaflet-control-attribution a:hover,
.map-view .leaflet-control-attribution a:focus {
    text-decoration: none;
}

/* --- FT-03 · „N neue Beiträge" -------------------------------------------
   Schwebt fix unter dem Header, mittig über dem Feed. Ebene 3 (DS §3): nur
   Schlagschatten, kein Bevel — sie ist an den Screen gedrückt, nicht Teil der
   Beitragsliste.
   Türkis mit schwarzer Schrift (Kontrastregel „Schwarz auf Türkis", DS §2):
   das Eintreffen ist eine neutrale Meldung und gehört keiner Rubrik — dieselbe
   Pille erscheint über Updates wie über Pins, und eine Rubrikfarbe färbte
   dieselbe Sache je nach Kontext anders ein.
   z-index unter dem Header (100), damit sie beim Scrollen dahinter liegt, und
   unter den Fullscreen-Overlays (90+), die sie ohnehin ausblenden. */
.feed-new-pill {
    position: fixed;
    /* Fester Abstand zum Header — und sie GLEITET mit ihm, statt zu springen:
       dieselbe Dauer wie das Ein-/Ausklappen (*2026-08-04 von Jascha*). Ohne
       das sass sie einen Moment hinter dem Header, waehrend der wieder
       ausfuhr. */
    top: calc(var(--header-h, 110px) + 0.6rem);
    transition: top 0.22s ease;
    left: 50%;
    transform: translateX(-50%);
    z-index: 80;
    /* MASSE = die kleine Aktions-Pille aus DS §7, Referenz `.board-action` —
       dieselben Werte wie „Inhalt freischalten" auf einem gesperrten Post
       (*2026-08-04 von Jascha*: „die beiden Hinweise hätte ich angeglichen").
       Vorher stand hier ein eigenes Format (700 statt 600, 0.45/0.9 statt
       0.4/0.8, kein min-height) — 31,4 px hoch neben 32 px, gerade so viel
       daneben, dass man es sieht und nicht benennen kann.
       `--radius-md` statt der frueheren 999px: bei 32 px Hoehe kappt der
       Browser die 18 px ohnehin auf 16, die Pille bleibt also exakt so rund
       wie zuvor — der Wert kommt jetzt nur aus derselben Quelle wie ueberall.
       EINZIGE ABWEICHUNG von §7 ist der Schatten. Die Regel „Aktions-Button
       ist flach" gilt fuer Buttons AUF einer Flaeche; diese Pille schwebt als
       einzige ueber dem Feed auf nichts. Flach saehe sie aus wie ein Knopf,
       der von einer Card gefallen ist — der Schatten ist hier Ebene 3
       (DS §3), nicht Zierde. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2rem;
    gap: 0.4rem;
    padding: 0.4rem 0.8rem;
    border: none;
    border-radius: var(--radius-md);
    background: var(--accent-turkis);
    color: #000;
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    box-shadow: 0 8px 20px rgba(var(--shadow-rgb), .22);
    opacity: 1;
    /* Hereinfahren statt aufblinken: sie kommt von oben, wie der Beitrag, den
       sie ankündigt — und GEHT DENSELBEN WEG ZURÜCK. Darum eine Transition und
       keine Keyframe-Animation: eine Animation kennt nur den Hinweg und läuft
       nur, wenn das Element neu eingeblendet wird. Beim Scrollen über die
       Filter-Schwelle kommt und geht die Pille aber ständig; ein hartes
       Wegschalten (`display: none`) auf dem Rückweg liess sie verschwinden,
       während sie auf dem Hinweg noch einfuhr — zwei Sprachen für dieselbe
       Bewegung. */
    transition: top 0.22s ease, opacity 0.22s ease, transform 0.22s ease;
}
/* `visibility` statt `display`, damit überhaupt etwas zu sehen ist: `display`
   schaltet ohne Zwischenschritt. Sie wird erst NACH dem Ausblenden gesetzt
   (Verzögerung = Dauer), auf dem Rückweg dagegen sofort wieder aufgehoben.
   `pointer-events` muss mit — eine unsichtbare Pille darf keinen Tap fangen. */
.feed-new-pill.hidden {
    opacity: 0;
    transform: translateX(-50%) translateY(-10px);
    pointer-events: none;
    visibility: hidden;
    transition: top 0.22s ease, opacity 0.22s ease, transform 0.22s ease,
                visibility 0s linear 0.22s;
}
.feed-new-pill .ic { width: 1rem; height: 1rem; }
/* Über einer Ansicht, die den ganzen Screen deckt, hat sie nichts zu melden —
   dieselbe Regel wie für die Bottom-Nav (Detail-/Sub-Ansicht-Regel).
   `scroll-locked` deckt Chooser und Compose ab: die liegen als Modal über dem
   Feed, ohne `fullscreen-open` zu setzen.
   Hier bleibt es beim harten `display: none`: das ist kein Scrollen über eine
   Schwelle, sondern ein Ansichtswechsel — da soll nichts nachfaden. */
body.fullscreen-open .feed-new-pill,
body.scroll-locked .feed-new-pill,
body.map-mode .feed-new-pill { display: none; }

/* MAP-01 · Das Zahnrad sitzt im HEADER, nicht auf der Karte (2026-08-04,
   Jascha): die Karte ist Inhalt, und ein Einstellungs-Griff gehört an die
   Stelle, an der in dieser App alle Griffe sitzen. Stille Optik wie die lokale
   Suche und das ⋮-Menü daneben — kein Hintergrund, keine Pille. */
.header-map-settings-btn {
    display: none;
    background: transparent;
    border: none;
    color: var(--text-primary);
    padding: 0.4rem 0.35rem;
    cursor: pointer;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    margin-right: -0.35rem;
}
.app-header.detail-mode-map .header-map-settings-btn { display: flex; }
.header-map-settings-btn:active { opacity: 0.5; }

/* Der Haken sitzt rechts am Zeileneende — er markiert, was gilt, statt die
   Zeile anzuführen. */
.sheet-action-check { margin-left: auto; color: var(--text-secondary); }

/* Der Ein/Aus-Schalter über den drei Kreisen (*2026-08-04 von Jascha*). Er trägt
   dasselbe Tile wie die Listen-Chips im Filter-Strip; hier steht nur, was dort
   der Rubrik-Kontext setzt: die aktive Farbe. Türkis, weil Gelb und Rosa den
   Rubriken gehören und die Karte keine ist (DS §2) — Schrift schwarz, nach der
   Kontrastregel „Schwarz auf Türkis". */
/* Die Zeile liest sich wie die Griffe app-weit: was geschaltet wird links,
   der Schalter rechts (*2026-08-04 von Jascha*) — dieselbe Anordnung wie
   „Gefolgt" an einer Board-Zeile. Die Beschriftung trägt die Masse der
   Sheet-Zeilen darunter, damit die vier Zeilen eine Spalte bilden. */
.map-share-switch {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.2rem 0.25rem 0.7rem;
}
.map-share-switch-label {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-primary);
}
.map-share-switch .filter-chip.active {
    background: var(--accent-turkis);
    color: #000;
}
/* Aus heisst: die Kreise bleiben stehen und werden blass. Dieselbe Behandlung
   wie ein deaktivierter Filter-Chip (`.filter-chip.disabled`), damit „hier ist
   gerade nichts zu holen" app-weit gleich aussieht. */
.map-share-options.is-off {
    opacity: 0.35;
    pointer-events: none;
}

/* Der Datensammlungs-Hinweis: klein, Meta-Grau, ohne Fläche und ohne Icon —
   genau die Behandlung, die solche Sätze in echten Apps bekommen. Das ist die
   Aussage, nicht ein Versehen. */
/* Die Trennlinie läuft über die GANZE Sheet-Breite — dieselbe wie über dem
   Composer im Kommentar-Sheet. `.sheet-body` hat seitlich 1rem Polster; ohne
   die negativen Ränder endete die Linie 1rem vor jeder Kante und sähe aus wie
   eine Unterstreichung des Textes statt wie eine Grenze im Sheet.
   (*2026-08-04 von Jascha*) */
.map-share-note {
    margin: 0.6rem -1rem 0;
    padding: 0.75rem 1rem 0;
    border-top: 1px solid var(--hairline);
    color: var(--text-muted);
    font-size: 0.72rem;
    line-height: 1.45;
}
/* Die Personen-Icons in einer Sheet-Zeile: so breit wie ein Icon daneben,
   damit die drei Zeilen auf einer Achse beginnen. */
.sheet-action-reach {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.25rem;
    flex-shrink: 0;
}
.sheet-action-reach .reach-icons i { font-size: 0.66rem; }

/* Boards List */
.boards-list {
    padding: 0.8rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
}

/* (Die alte `.board-card` samt `-info` / `-tag` / `-desc` / `-meta` /
   `-arrow` ist mit BOARD-02 entfallen — sie war seit dem Umbau auf
   `.board-card-v2` unbenutzt.) */

/* --- People View (Boards-Tab) ---
   Section-Titel und Tiles stehen auf EINER Achse: 1rem vom Rahmen. Die Tiles
   liegen in Carousels, die aus dem Seiten-Padding ausbrechen (sie sollen beim
   Scrollen vom Bildschirmrand kommen) — die Titel müssen denselben Ausbruch
   mitmachen, sonst sitzt die Überschrift woanders als ihr Inhalt: mobil 5 px
   links davon, auf Desktop 24 px rechts. Dieselben Werte wie `.people-carousel`
   und `.people-tile-list`, damit es eine Achse bleibt, wenn sich das Padding
   je ändert. */
.people-view {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0 1rem 2rem;
}
.people-section-title {
    color: #fff;
    font-size: 1.4rem;
    font-weight: 900;
    letter-spacing: -0.5px;
    margin: 1.2rem -2.5rem 0.6rem;
    padding: 0 1rem;
}
.people-section-title:first-child {
    margin-top: 0;
}
/* Section-Header mit klickbarem Chevron (z.B. "Meine Boards >") */
.people-section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin: 1.2rem -2.5rem 0.6rem;
    padding: 0 1rem;
}
.people-section-header:first-child {
    margin-top: 0;
}
/* Im Header trägt der Titel weder Margin noch Padding — beides sitzt am
   Header. Vorher blieb sein eigenes `padding: 0 1rem` stehen und addierte sich
   auf das des Headers: „Meine Boards" und „Entdecken" standen 32 px vom Rand,
   während Sub-Titel, Tiles und „Freundschaftsvorschläge" bei 16 px sassen.
   Angeglichen wird auf die Tile-Achse — die Überschrift richtet sich nach
   ihrem Inhalt, nicht umgekehrt. */
.people-section-header .people-section-title {
    margin: 0;
    padding: 0;
}
.people-section-header--clickable {
    cursor: pointer;
    user-select: none;
}
/* Chevron und Würfel standen auf Weiss, als der Canvas noch dunkel war — auf
   dem Hochweiss waren beide unsichtbar. Sekundär wie die Sektions-Labels: sie
   sind wählbare Navigation, nicht Inhalt (DS §2). */
.people-section-chevron {
    color: var(--text-secondary);
    /* So gross wie der Würfel bei „Entdecken" — beide gehören zur grossen
       Kategorie (h2, 1.4rem). Vorher war der Pfeil der Hauptkategorie kleiner
       als der einer Unterrubrik, was die Hierarchie umgedreht hat. Die
       Sub-Titel behalten ihren kleinen Pfeil (er erbt ihre 0.95rem). */
    font-size: 1.3rem;
    padding: 0 0.2rem;
}

/* Würfel-Button rechts vom „Entdecken"-Titel — neue Vorschläge würfeln */
.discovery-shuffle-btn {
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--text-secondary);
    font-size: 1.3rem;
    padding: 0.4rem 0.5rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
}
.discovery-shuffle-btn.shuffling {
    animation: dice-roll 0.6s cubic-bezier(0.4, 0, 0.2, 1);
    pointer-events: none; /* nicht nochmal klickbar während der Animation */
}
@keyframes dice-roll {
    0%   { transform: rotate(0deg)   scale(1); }
    20%  { transform: rotate(135deg) scale(1.18); }
    40%  { transform: rotate(290deg) scale(0.88); }
    60%  { transform: rotate(450deg) scale(1.12); }
    80%  { transform: rotate(620deg) scale(0.94); }
    100% { transform: rotate(720deg) scale(1); }
}

/* (`.my-board-*` — der zweizeilige Preview der alten Meine-Boards-Zeile —
   ist mit BOARD-02 entfallen; die Card zeigt heute Counts + Poster-Avatare.) */

/* --- Einheitliche Board-Card ---
   Grid-Layout: `#`-Avatar links, Info mitte, Aktion rechts.
   Seit BOARD-02 ist jede Card ein EIGENES erhabenes Tile (Ebene 1: Elevation +
   Bevel, Werte 1:1 aus `.post-card`) statt einer flachen Zeile in einer
   Sammel-Card. **Ohne** Top-Akzent: der Streifen unterscheidet im Feed Updates
   von Pins — im Boards-Tab ist alles ein Board, es gibt nichts zu
   unterscheiden (entschieden 2026-07-30, wie die Cards im Profil).
   `overflow: hidden`, damit der rosa Aktions-Streifen der Entdecken-Cards der
   Rundung folgt; die inset-Bevel bleiben davon unberührt. */
.board-card-v2 {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-rows: auto auto;
    column-gap: 0.8rem;
    row-gap: 0.4rem;
    padding: 0.6rem 1rem;
    background: var(--surface-color);
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.14),
                inset 0 1px 0 var(--bevel-light),
                inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
    cursor: pointer;
    transition: transform 0.1s;
}
/* Grid-Layout: Avatar (Col 1) spannt beide Inhalts-Reihen (Titel + Counts).
   Titel (Col 2) sitzt in Reihe 1 und kann in Meine-Boards-Cards bis zur
   rechten Karten-Kante laufen. Counts in Reihe 2 (Col 2). Action-Slot (Col 3)
   standardmässig in Reihe 1 — per Modifier in Reihe 2 (Meine Boards) bzw.
   Span 1/-1 (Discover Rosa-Streifen) verschoben. */
.board-avatar {
    grid-column: 1;
    grid-row: 1 / 3;
    align-self: center;
    width: 72px;
    height: 72px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: var(--surface-light);
}
/* Entdecken-Cards haben kompakteres Layout → kleinerer Avatar (2/3 vom
   Standard, 80px → 54px). Die dritte Reihe trägt den Follower-Streifen; Avatar
   und `+` bleiben auf die zwei Inhalts-Reihen darüber bezogen und sind dazu
   mittig — der Streifen ist ein Anhängsel, kein Teil des Card-Kopfs. */
/* Dritte Reihe = der Streifen. Beide Card-Arten tragen ihn. */
.board-card-v2--discover,
.board-card-v2--mytopics {
    grid-template-rows: auto auto auto;
}
.board-card-v2--discover .board-avatar {
    width: 54px;
    height: 54px;
}
.board-avatar--initials {
    display: flex;
    align-items: center;
    justify-content: center;
    color: #000;
    font-weight: 800;
    font-size: 1.15rem;
    letter-spacing: -0.02em;
    background: var(--surface-light);
}
.board-card-v2--discover .board-avatar--initials {
    font-size: 0.9rem;
}
.board-card-title {
    grid-column: 2;
    grid-row: 1;
    align-self: end;
    min-width: 0;
    font-weight: 800;
    font-size: 1.05rem;
    color: var(--text-primary);
    /* Lange Hashtags werden mit … abgekürzt statt umzubrechen */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* CSS Grid mit 2 Spalten: [Count+Icons-Block] | [Megaphon-Block].
   Count und Icons sind als Sub-Flex (.board-stat-count) zusammengeklebt
   und wachsen gemeinsam je nach Inhalt. Die erste Grid-Spalte sizet auto
   auf den BREITEREN der beiden Count+Icons-Blöcke — dadurch landen beide
   Megaphone automatisch am rechten Rand des breiteren Blocks.
   .board-stat selbst nutzt display: contents, damit seine zwei Kinder
   (.board-stat-count und .board-stat-activity) direkte Grid-Items
   sind, ohne ein zusätzliches Box-Niveau zu erzeugen. */
.board-card-counts {
    grid-column: 2;
    grid-row: 2;
    align-self: start;
    display: grid;
    grid-template-columns: auto auto;
    justify-content: start;
    row-gap: 0.15rem;
}
.board-stat {
    display: contents;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text-muted);
}
.board-stat-count {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    justify-self: start;
}
/* Inline-Variante für Entdecken: Friends + FoF nebeneinander in einer Zeile.
   Override des display: contents auf .board-stat, damit die Items als
   normale Flex-Items mit Abstand erscheinen. */
.board-card-counts--inline {
    display: flex;
    flex-direction: row;
    gap: 0.8rem;
    grid-template-columns: none;
    row-gap: 0;
}
.board-card-counts--inline .board-stat {
    display: inline-flex;
    align-items: center;
}
.board-stat-activity {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    margin-left: 0.4rem;
    padding-left: 0.4rem;
    border-left: 1px solid rgba(0,0,0,0.12);
    color: var(--accent-pink);
    font-weight: 700;
    font-size: 0.78rem;
}
/* Wenn keine neuen pins vorliegen (newPins = 0) wird das Span leer
   gerendert — Border/Padding/Margin müssen dann verschwinden, sonst sieht
   man eine separator-Linie ohne Inhalt. */
.board-stat-activity:empty {
    margin: 0;
    padding: 0;
    border: 0;
}
.board-stat-activity i.fa-thumbtack {
    font-size: 0.72rem;
}
.board-card-actions {
    grid-column: 3;
    grid-row: 1;
    align-self: center;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    justify-content: center;   /* Inhalt (Avatar-Stack / Gefolgt-Btn) vertikal zentriert */
    gap: 0.3rem;
}
/* („Meine Boards" hat im Carousel keinen Action-Slot: der Status ist bekannt,
   und die Poster-Gesichter sind in den Streifen zuunterst gewandert.)
   Ausnahme: in der Vollansicht stehen dort Favorit, Glocke und „Gefolgt" —
   nebeneinander, auf der Höhe des Avatars (also über die zwei Inhalts-Reihen
   zentriert, nicht über die ganze Zeile inklusive Streifen). */
.board-card-actions--list {
    grid-row: 1 / 3;
    align-self: center;
    flex-direction: row;
    align-items: center;
    gap: 0.35rem;
}
/* Favorit-Stern: leer = nicht markiert, gefüllt + rosa = Favorit. Dieselbe
   Aussage wie die Swipe-Geste, nur direkt greifbar. Masse wie die Glocke
   daneben, damit die drei Controls eine Zeile bilden. */
.board-fav-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 2rem;
    height: 2rem;
    border: none;
    border-radius: 50%;
    background: var(--surface-light);
    color: var(--text-secondary);
    font-size: 0.85rem;
    cursor: pointer;
    /* Wie die Glocke: kein Übergang, kein Press-Effekt. Der Stern ist gefüllt
       oder er ist es nicht. */
}
.board-fav-btn.is-fav {
    color: var(--accent-pink);
}
/* Entdecken-Cards: genau EIN Griff — der runde rosa `+`. Der frühere
   Button-Streifen über die volle Card-Höhe (Folgen oben, Ausblenden unten) ist
   mit BOARD-02 gefallen: „nicht interessiert" braucht keine eigene Aktion,
   ein Vorschlag, der mehrfach kommt und nie genommen wird, ist von selbst
   abgelehnt. Der Button sitzt vertikal mittig am rechten Rand und ist derselbe
   Körper wie `+ Freund` bei den Freundschaftsvorschlägen — nur in Rosa statt
   Türkis, weil er in die Board-Welt gehört (DS §2). */
.board-card-v2--discover .board-card-actions {
    grid-row: 1 / 3;
    align-self: center;
}
.board-card-v2--discover .board-action--join {
    width: 36px;
    height: 36px;
    min-height: 0;
    padding: 0;
    border-radius: 50%;
    background: var(--accent-pink);
    color: #fff;
    font-size: 1rem;
}
/* --- Follower-Bubbles auf der Entdecken-Card ---
   Eigene Zeile zuunterst, über die Spalten von Avatar und Titel (Spalte 3
   gehört dem `+`, der über die ganze Cardhöhe zentriert bleibt). Gesichter
   statt Zahlen: ein kurzer Blick darauf, wer folgt. Flach wie aller Inhalt auf
   einer Card (DS §3) — die Klickbarkeit trägt der Pfeil, nicht eine Fläche. */
.board-followers {
    /* Anhängsel an die Card: ein eigener Streifen zuunterst, edge-to-edge über
       die ganze Cardbreite (negative Margins heben das Card-Padding auf) und
       mit einer Hairline abgesetzt. Bubbles links, Pfeil rechtsbündig — die
       Diagonale sagt „das gehört zusammen und führt nach rechts weiter".
       Die Hairline ist hier erlaubt, weil sie zwei *Bereiche* trennt und nicht
       eine Liste rastert (DS §1). */
    grid-column: 1 / -1;
    grid-row: 3;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0.1rem -1rem -0.6rem;
    padding: 0.5rem 1rem;
    background: none;
    border: none;
    border-top: 1px solid var(--hairline);
    font: inherit;
    cursor: pointer;
    transition: opacity 0.1s;
}
.board-followers:active { opacity: 0.55; }
/* „Meine Boards": derselbe Streifen, aber ohne Ziel — er führt nirgends hin,
   wo die Card nicht ohnehin hinführt. Also kein Pfeil, kein Press-Feedback. */
.board-followers--static {
    cursor: inherit;
}
.board-followers--static:active { opacity: 1; }
.board-follower-bubbles {
    display: inline-flex;
    align-items: center;
}
/* Überlappender Stapel — getrennt über einen Ring in der FLÄCHENFARBE, nie
   über eine Border (DS §11). Die Hülle trägt Überlappung und z-index, damit
   der Aktivitäts-Punkt im Pin-Streifen daran hängen kann. */
.board-follower-bubble-wrap {
    position: relative;
    display: inline-flex;
}
/* Überlappung -12px statt -9px: gestapelte Gesichter dürfen enger sitzen, und
   die gewonnenen Pixel braucht der Text daneben — die FoF-Fassungen fehlten
   sonst um 4 bis 11px und wurden angeschnitten. */
.board-follower-bubble-wrap:not(:first-child) { margin-left: -12px; }
.board-follower-bubble {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    box-shadow: 0 0 0 2px var(--surface-color);
}
/* Aktivitäts-Punkt — nur im Pin-Streifen („Meine Boards"). Das Profilbild sagt,
   WER etwas gepostet hat, der Punkt sagt „da ist etwas Neues". Damit gehen die
   zwei gleich gebauten Streifen auch optisch auseinander: Gesicht mit Punkt =
   Pins, Gesicht ohne = Follower. Rand in der Flächenfarbe, damit der Punkt
   sich vom Bild darunter absetzt, ohne eine Border zu sein (DS §11). */
.board-followers--pins .board-follower-bubble-wrap::after {
    content: "";
    position: absolute;
    /* Der Mittelpunkt des Punkts liegt auf dem sichtbaren Aussenkreis des
       Profilbilds — also auf dem Ring, nicht auf der Fotokante:
         sichtbarer Radius = 13px (Bild) + 2px (Ring)      = 15
         Position auf 45°   = 15 × cos45°                  = 10.61
         Versatz            = 10.61 + 3.5 (halber Punkt) − 13 (halbe Box) = 1.11
       Ohne den Versatz sass er innerhalb des Rings und wirkte eingerückt. */
    top: -1.1px;
    right: -1.1px;
    width: 7px;
    height: 7px;
    background: var(--accent-pink);
    border: 1.5px solid var(--surface-color);
    border-radius: 50%;
}
/* Schliesst direkt an die Gesichter an und nimmt den Rest der Zeile — dadurch
   steht der Pfeil rechtsbündig, ohne dass zwischen Bubbles und Pfeil ein
   leeres Feld klafft. Meta-Ton, es ist eine Auskunft über den Inhalt, nicht
   der Inhalt selbst (DS §2). */
.board-followers-label {
    flex: 1;
    min-width: 0;
    text-align: left;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.board-followers-chevron {
    flex-shrink: 0;
    color: var(--text-muted);
    font-size: 0.9rem;
}

/* (Der zweireihige Poster-Stapel rechts in „Meine Boards" — `.board-card-
   posters*` samt rosa Aktivitäts-Punkt — ist entfallen: die Gesichter stehen
   jetzt im Streifen zuunterst, in derselben Form wie auf den Entdecken-Cards.
   Was der Punkt sagte („neu"), sagt dort der Text.) */
/* Aktion-Buttons in der Card — Basis wie .filter-chip im Feed:
   weisser BG, kein Border, radius-md, bold. Aktive Variante (Folgen)
   bekommt grünen Fill (CTA), Status-Variante (Gefolgt) bleibt weiss. */
.board-action {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Feste Mindesthöhe: sonst baut eine Icon-only-Variante („Folgen" = nur ein
       Plus) niedriger als dieselbe Pille mit Text, und die Zeile ruckt beim
       Umschalten. Die Höhe gehört zur Pille aus DS §7, nicht zum Inhalt. */
    min-height: 2rem;
    gap: 0.4rem;
    padding: 0.4rem 0.8rem;
    background: var(--surface-color);
    border: none;
    border-radius: var(--radius-md);
    font-family: inherit;
    font-weight: 600;
    font-size: 0.85rem;
    /* Sekundär, nicht Schwarz: die Pille ist eine Affordanz, kein Inhalt
       (DS §7). Schwarz bleibt dem aktiven Zustand vorbehalten. */
    color: var(--text-secondary);
    cursor: pointer;
    white-space: nowrap;
    transition: all 0.1s;
}
/* „Folgen": rosa gefüllt — aktive CTA (Boards gehören zur Pins-Welt) */
.board-action--join {
    background: var(--accent-pink);
    color: #fff;
}
/* „Gefolgt": ruhig statt Brand-Fläche, Haken statt Plus. Hinzugefügt ist ein
   Status, kein Call-to-Action — dieselben Werte wie `.unlock-add-btn.is-added`
   bei „Fragen hinzufügen". Form und Grösse bleiben, damit beim Umschalten
   nichts springt; nochmal tippen nimmt es zurück. */
.board-action--join.is-added {
    background: var(--surface-light);
    color: var(--text-secondary);
}
/* „Gefolgt": Status, kein Call-to-Action — dieselbe ruhige Pille wie der
   Haken-Zustand der Entdecken-Cards (`.is-added`) und wie „Hinzugefügt" bei
   den Profil-Fragen. Vorher war das weiss-auf-weiss mit schwarzer Schrift und
   las sich auf der Board-Detail-Card wie ein Text-Label, nicht wie ein Button. */
.board-action--joined {
    background: var(--surface-light);
    color: var(--text-secondary);
}
/* Bell-Button: eigener kleiner Toggle für Notifications, steht links neben
   „Gefolgt" — und trägt darum dieselbe ruhige Optik wie dieses (graue Fläche,
   sekundäre Schrift, kein Rahmen). Die Höhe ist die der kleinen Aktions-Pille
   (DS §7, `min-height: 2rem`), sonst steht ein 28px-Kreis neben einer 32px-
   Pille und die Zeile wirkt schief. */
.board-bell-btn {
    background: var(--surface-light);
    color: var(--text-secondary);
    border: none;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.78rem;
    flex-shrink: 0;
    /* KEINE transition und kein Press-Effekt: die Glocke ist ein Schalter, der
       an oder aus ist. Ein Ein-/Ausblenden der Farbe macht daraus ein
       Ereignis — dieselbe Regel wie beim Favoriten-Stern. */
}
/* An = rosa und GEFÜLLT. Die Iconoir-Glocke ist eine Strichzeichnung; `fill:
   currentColor` macht daraus die massive Form — derselbe Sprung wie beim
   Favoriten-Stern (leer ⇄ gefüllt), ohne ein zweites Icon-Set anzufassen. */
/* Die Glocke hat genau ZWEI Zustände, und sie sehen überall gleich aus —
   Board-Detailseite wie Vollansicht (ein Markup: `boardBellBtnHtml`).
   (Hier stand ein `:hover:not(.is-on)`, das den Aus-Zustand aufhellte — samt
   einer Notiz über das `:not`, ohne das der Hover bei gleicher Spezifität das
   Rosa des An-Zustands überschrieb. Mit dem Hover ist auch die Falle weg.) */
.board-bell-btn.is-on {
    color: var(--accent-pink);
}
.board-bell-btn.is-on .ic {
    fill: currentColor;
}
/* (`.board-action-dismiss` ist mit BOARD-02 entfallen — siehe die Notiz
   beim Entdecken-Action-Slot weiter oben.) */

/* Members-Preview-Row unter der Board-Card auf der Detail-Seite */
.board-members-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.7rem 1rem;
    background: var(--surface-color);
    border-top: 1px solid var(--hairline);
    cursor: pointer;
}
.board-members-row .board-members-label {
    flex: 1;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--text-secondary);
}
.people-subsection-title {
    color: rgba(255, 255, 255, 0.8);
    font-size: 0.95rem;
    font-weight: 700;
    /* Gleiche Achse wie Titel und Tiles — siehe .people-view */
    margin: 0.2rem -2.5rem 0.5rem;
    padding: 0 1rem;
}
/* Klickbare Unter-Titel mit ">" Pfeil → signalisiert „öffnen zur Vollansicht" */
.people-subsection-title--clickable {
    display: flex;
    align-items: center;
    justify-content: space-between;
    cursor: pointer;
    user-select: none;
}
.people-subsection-title--clickable i {
    font-size: 0.85rem;
    opacity: 0.7;
}
/* Mobil ist main padding-los, der Ausbruch beträgt darum nur das Padding von
   `.people-view`. Steht bewusst NACH allen drei Basis-Regeln — Media-Queries
   erhöhen die Spezifität nicht, ein früherer Block würde von der späteren
   `margin`-Kurzform wieder überschrieben. */
@media (max-width: 600px) {
    .people-section-title,
    .people-section-header,
    .people-subsection-title {
        margin-left: -0.7rem;
        margin-right: -0.7rem;
    }
}

/* Beitreten + X bei Entdecken-Items, rechts oben am Item */
.discover-item {
    position: relative;
    padding-right: 5.5rem; /* Platz für Aktion-Buttons */
}
.discover-actions {
    position: absolute;
    top: 0.6rem;
    right: 0.7rem;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.discover-action-dismiss {
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 0.85rem;
    cursor: pointer;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
}
.discover-action-join {
    background: var(--accent-turkis);
    color: #000;
    border: none;
    padding: 0.3rem 0.7rem;
    border-radius: var(--radius-full);
    font-size: 0.78rem;
    font-weight: 600;
    cursor: pointer;
    transition: transform 0.1s;
}

/* Stats-Zeile unterhalb des Board-Namens bei Entdecken-Items.
   Nutzt das bekannte Reach-Symbol-System (👤👤 friends, 👤👤👤 fof, 🌐 public),
   aber OHNE die weiße Badge-Umrandung — die ist Friend-Name-Labels vorbehalten. */
.discover-stats {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    margin-top: 0.35rem;
    flex-wrap: wrap;
}
.discover-stat {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text-secondary);
}
.discover-stat i.fa-user {
    font-size: 0.55rem;
}
.discover-stat i.fa-user + i.fa-user {
    margin-left: -2px;
}
.discover-stat i.fa-globe {
    font-size: 0.85rem;
}

/* Rosa Pille für „neue Aktivitäten" von Freunden / FoF */
.activity-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.55rem;
    background: var(--accent-pink);
    color: #fff;
    border-radius: var(--radius-full);
    font-weight: 800;
    font-size: 0.8rem;
    white-space: nowrap;
}
.activity-pill i {
    font-size: 0.55rem;
}
.activity-pill i + i {
    margin-left: -2px;
}
.activity-pill .fa-globe {
    font-size: 0.75rem;
}
/* Rote Bubble für Public-Anteil (nur Listenansicht bei reach=public) */
.activity-pill--public {
    background: #DC2626;
}
.discover-stat--public {
    background: #DC2626;
    color: #fff;
    padding: 0.15rem 0.5rem;
    border-radius: var(--radius-full);
    font-weight: 800;
}
.discover-stat--public i.fa-globe {
    font-size: 0.75rem;
}

/* Section-Detail-Seite: Fullscreen-Sub-View bei Klick auf ein Subsection-
   Chevron. KEIN umschliessender Card-Glow mehr — der Pin-Welt-Akzent kommt
   aus der Pull-to-Refresh-Fläche oben, dem Glow des Filter-Strips
   (`filter-strip-pins`, gleicher Look wie im Pins-Feed) und dem Glow der
   Liste selbst (wie ein langer Pin-Post). Padding 0 — der Filter-Strip nutzt
   sein eigenes negatives Margin (gleiche Regel wie im Feed) für den
   Breakout, die Liste hat ihr eigenes negatives Margin. */
#board-section-detail-view {
    padding: 0;
}
/* Die Sortier-Pillen sind jetzt die zweite Sektion IM Filter-Strip (Aufbau wie
   im Home-Feed) — sie parken damit zusammen mit dem Filter hinter dem Header,
   statt darunter stehen zu bleiben. Als Filter-Reihe brauchen sie nur noch die
   Ausrichtung; Padding und Abstände kommen von `.filter-section`. */
.section-detail-sort-row {
    /* Linksbündig wie jede andere Filter-Reihe. Rechtsbündig stand sie allein
       auf weiter Flur, seit sie im Strip zwischen zwei linksbündigen
       Sektionen sitzt. */
    justify-content: flex-start;
}

/* (Fläche, Rundung und Chip-Farbe des Strips stehen jetzt global bei
   `.filter-strip` — Feed, Board-Detail und Vollansicht sehen gleich aus.) */
.sort-pill,
.sort-direction-pill {
    background: var(--surface-color);
    /* `--text-primary` statt `#000`: „+ Neue Liste" steht im selben Strip und
       soll dieselbe Schrift tragen — bei #000 gegen #1f1f1f sähe man den
       Unterschied nebeneinander. */
    color: var(--text-primary);
    border: none;
    border-radius: 999px;
    font-family: inherit;
    font-weight: 800;
    font-size: 0.78rem;
    cursor: pointer;
    transition: transform 0.1s;
    display: inline-flex;
    align-items: center;
    /* Rosa Pin-Glow — gleiches Layering wie .post-card[data-type="pin"]. */
    box-shadow:
        0 0 6px #fff,
        0 0 10px rgba(255, 0, 102, 0.7),
        0 0 22px rgba(255, 0, 102, 0.35),
        0 0 40px rgba(255, 0, 102, 0.12);
}
.sort-pill {
    gap: 0.4rem;
    padding: 0.38rem 0.7rem 0.38rem 0.85rem;
}
.sort-direction-pill {
    width: 32px;
    height: 32px;
    justify-content: center;
    font-size: 0.85rem;
}
.sort-pill .ic {
    font-size: 0.7rem;
    color: var(--text-secondary);
}
/* Bottom-Sheet (Reddit-Style): vom unteren Rand fullwidth eingeschoben,
   Backdrop dimmt den Rest, ESC oder Tap aufs Backdrop schliesst. */
.bottom-sheet-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    pointer-events: none;
}
.bottom-sheet-overlay.open {
    pointer-events: auto;
}
.bottom-sheet-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0);
    transition: background 0.22s ease-out;
}
.bottom-sheet-overlay.open .bottom-sheet-backdrop {
    background: rgba(0, 0, 0, 0.35);
}
.bottom-sheet {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    max-width: 600px;
    margin: 0 auto;
    background: var(--surface-raised);
    border-top-left-radius: 18px;
    border-top-right-radius: 18px;
    padding: 0.4rem 0 max(0.6rem, env(safe-area-inset-bottom)) 0;
    transform: translateY(100%);
    transition: transform 0.26s cubic-bezier(0.22, 1, 0.36, 1);
    box-shadow:
        0 -2px 12px rgba(0, 0, 0, 0.18),
        0 -8px 32px rgba(0, 0, 0, 0.12);
}
.bottom-sheet-overlay.open .bottom-sheet {
    transform: translateY(0);
}
.bottom-sheet-overlay.closing .bottom-sheet {
    transform: translateY(100%);
}
.bottom-sheet-grabber {
    width: 36px;
    height: 4px;
    background: rgba(0, 0, 0, 0.15);
    border-radius: 2px;
    margin: 0.3rem auto 0.5rem;
}
.bottom-sheet-title {
    font-size: 0.85rem;
    font-weight: 800;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0.4rem 1.2rem 0.6rem;
}
.bottom-sheet-list {
    display: flex;
    flex-direction: column;
}
.bottom-sheet-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    background: transparent;
    border: none;
    font-family: inherit;
    font-weight: 700;
    font-size: 1rem;
    color: var(--text-primary);
    padding: 0.85rem 1.2rem;
    cursor: pointer;
}
.bottom-sheet-item:active {
    background: #f3f3f3;
}
.bottom-sheet-item.active {
    color: var(--accent-pink);
}
.bottom-sheet-item.active i {
    color: var(--accent-pink);
}
.bottom-sheet-cancel {
    display: block;
    width: calc(100% - 1.6rem);
    margin: 0.5rem 0.8rem 0.2rem;
    background: #f3f3f3;
    border: none;
    border-radius: var(--radius-md);
    font-family: inherit;
    font-weight: 800;
    font-size: 0.95rem;
    color: var(--text-primary);
    padding: 0.85rem 1.2rem;
    cursor: pointer;
}
.bottom-sheet-cancel:active {
    background: #e8e8e8;
}
/* Die Liste selbst: ein einziger langer Block, fullwidth, rosa Glow nur am
   Top- und Bottom-Edge — wirkt wie ein hoher Pin-Post. Innen trennen
   Hairlines die Board-Zeilen. Bricht horizontal aus main's Padding aus
   (Desktop), auf mobile reicht die normale Breite (main hat dort 0
   horizontales Padding). */
.section-detail-list {
    background: var(--surface-color);
    border-radius: 0;
    margin-left: -1.5rem;
    margin-right: -1.5rem;
    /* Der Strip lässt darunter 1.875rem — im Feed ist das der Abstand bis zur
       sichtbaren OBERKANTE der ersten Card. Hier ist die erste Zeile flach,
       ihr Inhalt beginnt erst nach dem Zeilen-Padding (0.85rem); optisch
       klaffte die Lücke darum um genau diesen Betrag weiter. Wir ziehen ihn
       wieder heraus, damit Header→erste Zeile gleich weit ist wie
       Header→erste Post-Card im Feed. */
    margin-top: -0.85rem;
    box-shadow:
        0 0 6px #fff,
        0 0 12px rgba(255, 0, 102, 0.9),
        0 0 26px rgba(255, 0, 102, 0.45),
        0 0 46px rgba(255, 0, 102, 0.16);
}
@media (max-width: 600px) {
    .section-detail-list {
        margin-left: 0;
        margin-right: 0;
    }
}
/* „Aus Boards von deinem Umfeld" Detail: gestapelte Discovery-Cards (gleiche
   Preview-Optik wie das Carousel oben, nur full-width + Folgen-Button). Kein
   Filter-Strip → margin-top negiert main's padding-top, padding-top setzt dann
   denselben Header→erste-Card-Abstand wie der Pins-Feed (1.875rem). */
.section-detail-pins-list {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    margin-top: -1.5rem;
    padding-top: 1.875rem;
    padding-bottom: 1.875rem;
}
/* Detail-Variante der Inspiration-Card: feste 23.398px Seiten-Margin (= Margin
   des schwebenden Pin-Preview-Posts im Updates-Feed). Default-Stretch der
   Flex-Column füllt die Restbreite, hebt die schmale Carousel-Spalte + Snap auf. */
.inspiration-pin-card--detail {
    flex: none;
    width: auto;
    max-width: none;
    margin-left: 23.398px;
    margin-right: 23.398px;
    scroll-snap-align: none;
}
/* Folgen-Button rechts oben in der Board-Zeile. */
.inspiration-pin-card--detail .inspiration-pin-board {
    gap: 0.6rem;
}
.inspiration-pin-join {
    margin-left: auto;
    flex-shrink: 0;
}
/* Swipe-Row: zwei Layer übereinander. Hinten der rosa Action-Hintergrund
   mit Stern-Icon (sichtbar wenn die Front-Card nach links gezogen wird),
   vorne die eigentliche Card. Overflow:hidden cropt die Front beim Ziehen.
   user-select/tap-highlight komplett aus, sonst markiert der Browser beim
   Drag den Card-Text blau und schluckt die Geste. */
.section-detail-row {
    position: relative;
    overflow: hidden;
    /* Weiss, NICHT rosa. Die Swipe-Fläche ist `.section-detail-row-action`
       (absolut) und liegt ohnehin über diesem Grund. Rosa hier hiess: überall
       wo die weisse Front die Zeile nicht auf den Sub-Pixel genau abdeckte,
       schimmerte eine rosa Kante durch — mal unter der Hairline (die dadurch
       magenta statt grau las), mal unter der letzten Zeile. Zusammen mit dem
       1px-Einzug der Action-Fläche (unten) bleibt jetzt in jedem Rundungsfall
       Weiss übrig statt Rosa. */
    background: var(--surface-color);
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
    /* pan-y = Browser scrollt vertikal nativ, horizontale Gesten gehören JS.
       Muss schon VOR dem ersten Pointer-Event gesetzt sein, also via CSS
       statt zur Laufzeit per JS-Style. */
    touch-action: pan-y;
}
.section-detail-row,
.section-detail-row * {
    user-select: none;
    -webkit-user-select: none;
}
/* (Die Hairline zwischen zwei Zeilen ist entfallen — siehe die Notiz bei
   `.section-detail-row-front .board-card-v2` weiter unten.) */
.section-detail-row-action {
    position: absolute;
    /* 1px oben/unten eingezogen — siehe die Notiz bei `.section-detail-row`.
       Beim Wischen ist der fehlende Millimeter Rosa unsichtbar, die rosa
       Sub-Pixel-Kante im Ruhezustand war es nicht. */
    inset: 1px 0;
    background: var(--accent-pink);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.55rem;
    padding-right: 1.5rem;
    font-weight: 800;
    font-size: 0.95rem;
    transition: background 0.15s ease;
}
.section-detail-row-action-icon {
    font-size: 1.5rem;
    transition: transform 0.15s ease;
}
.section-detail-row.swipe-armed .section-detail-row-action {
    background: #c4004f; /* dunklere Rosa-Stufe → klares „loslassen-toggelt" Signal */
}
.section-detail-row.swipe-armed .section-detail-row-action-icon {
    transform: scale(1.25);
}
/* (Die Pulse-Animation nach dem Favorisieren ist entfallen — ein Schalter
   braucht keine Feier, gefüllt oder nicht gefüllt sagt alles.) */
.section-detail-row-front {
    position: relative;
    background: var(--surface-color);
    will-change: transform;
    transform: translateX(0);
}
/* In der Vollansicht bleibt die Board-Card eine flache Zeile — fullwidth, ohne
   Tiefe. Das Tile-Kleid aus BOARD-02 wird hier also wieder ausgezogen: ein
   eigenes Tile je Zeile würde den Gmail-Swipe (rosa Fläche darunter)
   zerschneiden und die Liste in 20 schwebende Objekte zerlegen.
   **Keine Trennstriche** — dieselbe Sprache wie „Fragen hinzufügen" (DS §3):
   eine flache Unterseite gliedert über Abstand, nicht über ein Linienraster.
   Der Abstand sitzt darum im Zeilen-Padding, nicht als Gap zwischen den
   Zeilen — so behält die Swipe-Fläche die volle Zeilenhöhe. */
.section-detail-row-front .board-card-v2 {
    background: transparent;
    border-radius: 0;
    box-shadow: none;
    padding-top: 0.85rem;
    padding-bottom: 0.85rem;
}
/* Der Follower-Streifen ist auf der Card ein Anhängsel und trägt dort eine
   Hairline. In der Vollansicht gibt es keine Cardkante, an die er sich hängen
   könnte — die Linie wäre genau das Raster, das hier keins sein soll. Also
   ohne Linie und ohne den Edge-to-Edge-Ausbruch: der Streifen bleibt im
   Zeilen-Padding. */
.section-detail-row-front .board-followers {
    border-top: none;
    margin: 0.1rem 0 0;
    padding: 0.15rem 0 0;
}
.filter-chip .filter-count {
    background: rgba(0, 0, 0, 0.08);
    color: inherit;
    border-radius: 999px;
    padding: 0.05rem 0.4rem;
    font-size: 0.72rem;
    font-weight: 800;
    margin-left: 0.15rem;
}
.filter-chip.active .filter-count {
    background: rgba(255, 255, 255, 0.25);
}
.section-detail-empty {
    padding: 2rem 1rem;
    text-align: center;
    color: var(--text-muted);
    font-weight: 700;
}
/* Local-search button auch in Section-Detail anzeigen */
.app-header.detail-mode-section .header-local-search-btn {
    display: flex;
}
/* …ausser in der Pins-Detailansicht („Aus Boards von deinem Umfeld") — dort
   gibt es keine sinnvolle lokale Suche. Gleiche Spezifität, danach platziert. */
.app-header.detail-mode-section-pins .header-local-search-btn {
    display: none;
}

/* Header Detail-Bar: ersetzt Logo + Actions + Such-Zunge auf Detail-Seiten. */
.header-detail-bar {
    display: none;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
}
/* **Eine Grösse für alle Icons im Header.** Vorher setzte jeder Griff seine
   eigene `font-size` (1 / 1.05 / 1.15 / 1.2rem) und das Icon erbte sie — vier
   Grössen in derselben Zeile, und weil die Strichstärke mit der Grösse
   skaliert, auch vier Strichstärken. Die Grösse hängt jetzt am Icon statt am
   Button; Beschriftungen (z.B. „Speichern") bleiben davon unberührt.

   Glocke und die beiden Such-Griffe fehlten bis GLOB-18 in dieser Liste — sie
   erbten weiter vom Button (1.2rem = 19.2px, das Schliessen-X 1.1rem =
   17.6px) und waren damit die letzten drei, die aus der Reihe fielen.
   Ausgerechnet sie sind die Griffe, die Home und Boards sich TEILEN; genau
   dort fällt jede Abweichung beim Tab-Wechsel auf. */
.header-back-btn .ic,
.header-local-search-btn .ic,
.header-post-menu-btn .ic,
.header-map-settings-btn .ic,
.header-settings-btn .ic,
.header-edit-btn .ic,
.notification-btn .ic,
.search-open-btn .ic,
.search-close-btn .ic,
/* Der Mond gehört in diese Liste wie jeder andere Header-Griff (GLOB-02): ohne
   den Eintrag erbt er die 1.2rem des Buttons und stünde als einziges Icon auf
   19,2px neben 18,4px — genau der Fehler, den GLOB-18 an Glocke und Lupe
   behoben hat. */
.theme-toggle-btn .ic,
.app-header.chats-tab .header-chats-actions .icon-btn .ic {
    width: 1.15rem;
    height: 1.15rem;
}
.app-header.detail-mode .header-detail-bar {
    display: flex;
}
.app-header.detail-mode .logo,
.app-header.detail-mode .header-actions,
.app-header.detail-mode .search-tongue {
    display: none;
}
.header-back-btn {
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-size: 1.2rem;
    padding: 0.25rem 0.4rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}
.header-detail-title {
    margin: 0;
    /* Die App-Schrift, nicht die des Systems. Hier stand die native
       -apple-system-Kette — ein Rest aus der Zeit, als der Detail-Header wie
       eine iOS-Navigationsleiste aussehen sollte. Sichtbar war das an genau der
       Stelle, an der man es am wenigsten erwartet: `@alexb` im Profil-Header,
       „#bern" auf der Board-Seite, „Update" auf einer Detailseite — alles
       Titel, die neben Geologica-Text stehen. */
    font-family: var(--font-main);
    font-weight: 700;
    font-size: 1.3rem;
    color: var(--text-primary);
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Lokales Such-Icon — nur sichtbar wenn eine Board geöffnet ist.
   Bewusst minimalistisch (kein Background, kein Pill, keine Tongue-Animation)
   um vom globalen Such-Zunge-Pattern unterscheidbar zu sein. */
.header-local-search-btn {
    display: none;
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-size: 1rem;
    padding: 0.4rem 0.5rem;
    cursor: pointer;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    /* Dieselbe Polster-Rücknahme wie am Zahnrad (GLOB-18): die rechte Kante
       gehört dem Icon, nicht dem Button. */
    margin-right: -0.5rem;
}
.app-header.detail-mode-board .header-local-search-btn {
    display: flex;
}
.header-local-search-btn:active {
    opacity: 0.5;
}
/* ⋮-Menü im Detail-Header (FEED-10) — nur auf der Post-Detailseite, wo genau
   EIN Beitrag steht. Gleiche stille Optik wie die lokale Suche: kein
   Hintergrund, keine Pille; vertikal mittig in der Kopfzeile. */
.header-post-menu-btn {
    display: none;
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-size: 1.15rem;
    padding: 0.4rem 0.35rem;
    cursor: pointer;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    margin-right: -0.35rem;
}
.app-header.detail-mode-post .header-post-menu-btn {
    display: flex;
}
.header-post-menu-btn:active { opacity: 0.5; }
/* Zahnrad rechts im Profil-Header (anstelle der lokalen Suche). Im Profil-
   Modus gibt es zudem keinen Back-Button (Profil ist ein Top-Level-Tab). */
.header-settings-btn {
    display: none;
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-size: 1.05rem;
    padding: 0.4rem 0.5rem;
    cursor: pointer;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    margin-left: auto;
    /* Das Polster zurücknehmen, damit das ICON an der Kante steht und nicht der
       Button (GLOB-18). Sichtbar ist das Icon; ohne diese Zeile sass das
       Zahnrad 8px weiter innen als die Chat-Griffe, die kein Polster tragen —
       ein Versatz beim Wechsel Chats ↔ Profil. Dieselbe Kompensation führen
       `.header-post-menu-btn` und `.header-map-settings-btn` schon länger. */
    margin-right: -0.5rem;
}
.app-header.detail-mode-profile .header-settings-btn { display: flex; }
.app-header.detail-mode-profile .header-back-btn { display: none; }
.header-settings-btn:active { opacity: 0.5; }

/* Bearbeiten-Schalter im Profil-Header, links vom Zahnrad. `margin-left: auto`
   schiebt die Gruppe nach rechts — das Zahnrad darf es dann nicht nochmal tun,
   sonst reisst es die beiden auseinander. */
.header-edit-btn {
    display: none;
    align-items: center;
    gap: 0.35rem;
    background: transparent;
    border: none;
    color: var(--text-primary);
    font-family: inherit;
    font-size: 1.05rem;
    padding: 0.4rem 0.5rem;
    cursor: pointer;
    flex-shrink: 0;
    margin-left: auto;
}
.app-header.detail-mode-profile .header-edit-btn { display: flex; }
.app-header.detail-mode-profile .header-edit-btn ~ .header-settings-btn { margin-left: 0; }
.header-edit-btn:active { opacity: 0.5; }
/* Im Modus trägt der Schalter Text statt Icon — Schwarz, weil „Speichern" die
   aktive Aktion ist (DS §7: Schwarz bleibt dem aktiven Zustand vorbehalten). */
.header-edit-btn.is-done { font-size: 0.9rem; font-weight: 700; }

/* „Verwerfen" steht links daneben und ist bewusst die leisere der zwei:
   Sekundärfarbe, kein Fett. Es ist der Rückweg, nicht das Ziel — und ein
   destruktives Rot wäre zu viel, weil hier nichts Bestehendes gelöscht wird,
   sondern nur eine unfertige Bearbeitung zurückgenommen. */
/* „Verwerfen" steht LINKS, an der Stelle des Nutzernamens — links zurück,
   rechts bestätigen, wie in jedem Formular. Der Handle ist im Modus ohnehin
   überflüssig (man weiss, wessen Profil man bearbeitet), und nach rechts
   geschoben würden sich die zwei Wörter den Platz streitig machen.
   Bewusst ein WORT, kein `<`: der Pfeil bedeutet in dieser App `popNav()` —
   folgenlos einen Schritt zurück. Derselbe Pfeil, der hier Änderungen wegwirft,
   sähe harmlos aus und wäre es nicht. */
.header-discard-btn {
    display: none;
    align-items: center;
    /* Kreuz + Wort, spiegelbildlich zu „✓ Speichern". */
    gap: 0.35rem;
    background: transparent;
    border: none;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    padding: 0.4rem 0.5rem;
    margin-left: -0.5rem;   /* Polsterung ausgleichen: bündig mit dem Titel darunter */
    cursor: pointer;
    flex-shrink: 0;
}
.app-header.profile-editing .header-discard-btn { display: inline-flex; }
/* Im Modus tritt der Handle zurück — „Verwerfen" nimmt seinen Platz ein. */
.app-header.profile-editing .header-detail-title { display: none; }
.header-discard-btn:active { opacity: 0.5; }
/* Während des Bearbeitens gibt es an den App-Einstellungen nichts zu tun —
   das Zahnrad blendet aus, damit der Header im Modus ruhig bleibt. */
.app-header.profile-editing .header-settings-btn { display: none; }
/* Sammel-Card der Boards-Sections. Mit BOARD-02 tragen Board-Vorschläge und
   Freundschaftsvorschläge ihre Tiefe je Tile — geblieben ist `--pink` als
   Basis der Inspiration-Pin-Cards (BOARD-03 stylt die neu). Die alten
   Neon-Glows sind weg; die Elevation kommt aus dem Ebene-1-Block weiter unten.
   (`.people-card--green` / `--yellow` sind damit ersatzlos entfallen.) */
.people-card--pink {
    background: var(--surface-color);
    border-radius: var(--radius-md);
    overflow: hidden;
}

/* Horizontales Carousel: jede Karte ~85% Breite, der Rest deutet die nächste
   Karte rechts an. Snap-to-card beim Scrollen.
   Wie der Filter-Strip im Feed bricht das Carousel aus dem horizontalen
   Padding von .people-view + main aus → Karten kommen beim Scrollen vom
   Bildschirmrand und verschwinden auch dort. Das eigene padding-left/right
   bestimmt den Ruhe-Indent der ersten/letzten Karte. */
.people-carousel {
    display: flex;
    /* Gap > scroll-padding-left (1rem), damit nach dem Snap die vorherige
       Karte komplett ausserhalb der Snap-Kante liegt (kein Card-Content sichtbar). */
    gap: 1.5rem;
    overflow-x: auto;
    overflow-y: visible;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    /* Edge-to-edge wie der Filter-Strip: bricht aus dem horizontalen Padding
       von .people-view (1rem desktop / 0.7rem mobile) + main (1.5rem desktop /
       0 mobile) aus. Eigenes padding setzt den Ruhe-Indent der Karten vom
       Bildschirmrand. */
    padding: 2.4rem 1rem;
    margin: -1.2rem -2.5rem;
    /* Snap-Start muss padding-left berücksichtigen, sonst zieht der Snap die
       Karten unter das padding und scrollLeft=0 ist keine valide Snap-Position. */
    scroll-padding-left: 1rem;
}
@media (max-width: 600px) {
    .people-carousel {
        /* Mobile: main hat 0 horizontales Padding, .people-view 0.7rem.
           Breakout = nur das .people-view-Padding (body overflow-x:hidden
           clipped Rest am Viewport-Rand). */
        margin-left: -0.7rem;
        margin-right: -0.7rem;
    }
}
.people-carousel::-webkit-scrollbar { display: none; }
/* Eine Carousel-Seite = drei Board-Tiles übereinander. Die Seite selbst ist
   nur noch Stapel-Container: keine Fläche, keine Tiefe — die tragen die Tiles
   (BOARD-02, DS §1 „Tiefe stapelt sich nicht"). Der Gap muss grösser sein als
   der Schatten-Versatz der Tiles (12px), sonst legt sich der Schlagschatten
   des oberen Tiles sichtbar auf das untere. */
.carousel-card {
    flex: 0 0 calc(100% - 2.5rem);
    scroll-snap-align: start;
    scroll-snap-stop: always;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
/* „Pins von deinen Freunden": schmale Pin-Spalten im horizontalen Carousel.
   Schmaler als die Board-Carousel-Karten (calc(100% - 2.5rem)), damit mehrere
   Spalten gleichzeitig sichtbar sind und klar wird, dass man durchscrollen
   kann. Nutzt das gemeinsame .people-carousel (Breakout + Snap + Glow-Padding). */
.inspiration-pin-card {
    flex: 0 0 68%;
    max-width: 280px;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    background: var(--surface-color);
    border-radius: var(--radius-md);
    padding: 0.9rem 1rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    cursor: pointer;
}
.inspiration-pin-board {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.inspiration-pin-board-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 800;
    font-size: 0.7rem;
    color: #000;
    flex-shrink: 0;
}
.inspiration-pin-board-tag {
    font-weight: 800;
    font-size: 0.95rem;
    color: var(--text-primary);
}
.inspiration-pin-content {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.35;
    color: var(--text-primary);
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.inspiration-pin-author {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin-top: auto;
}
.inspiration-pin-author-avatar {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}
.inspiration-pin-author-name {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-secondary);
}

.people-card-context {
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--text-secondary);
    margin: 0;
    padding: 0.7rem 1rem 0.4rem;
    border-bottom: 1px solid var(--hairline);
}
/* Stapel der Freundschaftsvorschlags-Tiles. Bricht aus dem Padding von
   `.people-view` aus (wie `.people-carousel`), damit die Tiles exakt auf der
   Achse der Board-Tiles im Carousel darüber stehen — 1rem vom Rand. */
.people-tile-list {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 0.4rem 1rem 1.4rem;
    margin: 0 -2.5rem;
}
@media (max-width: 600px) {
    .people-tile-list {
        margin-left: -0.7rem;
        margin-right: -0.7rem;
    }
}
/* Ein Vorschlag = ein eigenes erhabenes Tile, dieselbe Ebene-1-Behandlung wie
   die Board-Cards (BOARD-02). Keine Trennlinie mehr — getrennt wird über den
   Gap zwischen den Tiles. */
.people-list-item {
    display: flex;
    align-items: center;
    /* Umbruch, damit der „Was verbindet uns"-Streifen als eigene Zeile unter
       Kopf und `+`-Button sitzt — wie auf den Board-Cards.
       NUR column-gap: der Gap gilt sonst auch zwischen den umgebrochenen
       Zeilen und schob den Streifen zusätzlich zu seinem eigenen Abstand nach
       unten. Horizontal hält er Avatar, Text und `+` auseinander. */
    flex-wrap: wrap;
    column-gap: 0.8rem;
    row-gap: 0;
    padding: 0.75rem 1rem;
    background: var(--surface-color);
    border-radius: var(--radius-lg);
    box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.14),
                inset 0 1px 0 var(--bevel-light),
                inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
    cursor: pointer;
    transition: transform 0.1s;
}
/* „Was verbindet uns" — zwei Zeilen unter dem Kopf, durch eine Hairline
   abgesetzt: oben die gemeinsamen Friends (Gesichter + Zahl), darunter die
   gemeinsamen Boards. Zwei Zeilen statt einer, weil beides eigenständige
   Auskünfte sind; nebeneinander drängten sie sich und der Board-Text wurde
   abgeschnitten. Edge-to-edge bis an die Tile-Kante (negative Margins heben
   das Card-Padding auf) — dieselbe Bauform wie der Streifen der Board-Cards. */
.people-list-item .suggestion-shared {
    /* NICHT `flex: 0 0 100%`: die Basis bezieht sich auf die Content-Box, die
       negativen Margins schieben die Box dann nur nach links, ohne sie zu
       verbreitern — die Hairline endete 32px vor der rechten Cardkante.
       `calc(100% + 2rem)` nimmt das Padding auf beiden Seiten wieder auf. */
    flex: 0 0 auto;
    width: calc(100% + 2rem);
    min-width: 0;
    margin: 0.55rem -1rem -0.75rem;
    /* NUR seitlich: das vertikale Polster gehört den Zeilen (0.5rem, Masse aus
       dem Board-Streifen). Hatte die Box es zusätzlich, addierte es sich oben
       und unten zum Zeilen-Polster — der Abstand zur Kopf-Hairline war dann
       grösser als der zwischen den beiden Zeilen. */
    padding: 0 1rem;
    border-top: 1px solid var(--hairline);
    display: flex;
    flex-direction: column;
    /* Kein Gap: die Zeilen bringen ihr Polster selbst mit (siehe unten), sonst
       addierte es sich zur Trennlinie und die zweite Zeile stünde tiefer. */
    gap: 0;
}
.suggestion-shared-row {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    width: 100%;
    /* Masse aus dem Board-Streifen übernommen (`.board-followers`): 0.5rem
       Polster über und unter den 26px-Gesichtern, macht 42px Zeilenhöhe.
       Mit den vorherigen 0.3rem blieben nur 5px Luft — die Gesichter füllten
       die Zeile fast randvoll und wirkten dadurch nicht zentriert, obwohl sie
       es rechnerisch waren. Die Mindesthöhe gilt für BEIDE Zeilen, damit die
       Board-Zeile mit ihren kleineren Pillen gleich hoch baut und die
       Trennlinie mittig dazwischen sitzt. */
    min-height: 42px;
    padding: 0.5rem 0;
    /* `min-width: 0` auf Zeile UND Label: ohne das wächst ein Flex-Item über
       seinen Container hinaus, statt zu schrumpfen — genau daran lief der
       Board-Text vorher rechts aus der Card heraus. */
    min-width: 0;
    /* Als <button>: eigene Optik zurücksetzen, die Zeile ist die Klickfläche.
       ACHTUNG: kein `padding: 0` hier — das stand früher darunter und hob das
       Polster oben wieder auf, wodurch die erste Zeile flacher baute als die
       zweite. */
    background: none;
    border: none;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.suggestion-shared-row:active { opacity: 0.55; }
/* Hairline zwischen den zwei Zeilen — Friends und Boards sind zwei getrennte
   Auskünfte, nicht ein Block. Nur ZWISCHEN ihnen, nicht über der ersten
   (die trennt schon die Linie zum Kopf) und nicht unter der letzten. */
.suggestion-shared-row + .suggestion-shared-row {
    /* Edge-to-edge wie die Linie über dem Block — zwei Hairlines auf derselben
       Card in zwei verschiedenen Breiten sehen nach Versehen aus. Die
       negativen Margins heben das Block-Padding auf, das Padding holt es für
       den Inhalt zurück. */
    width: calc(100% + 2rem);
    margin: 0 -1rem;
    padding: 0.5rem 1rem;
    border-top: 1px solid var(--hairline);
}
/* Der Pfeil sitzt rechtsbündig am Zeilenende — wie bei den Entdecken-Cards.
   Der Text davor nimmt den Rest der Zeile und schiebt ihn nach aussen. */
.suggestion-shared-chevron {
    flex-shrink: 0;
    margin-left: auto;
    color: var(--text-muted);
    font-size: 0.9rem;
}
/* Die Pills sind das Gegenstück zu den Gesichtern der Zeile darüber: gleiche
   Position, gleiche Rolle. Sie dürfen NICHT schrumpfen — abgeschnittene
   Board-Namen wären unlesbar; kürzt wird die Zahl daneben. */
.suggestion-shared-pills {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    /* Das Komma zwischen den Pillen ist ein Textknoten IN diesem Container und
       erbt sonst die Textfarbe der Card — es stand schwarz zwischen zwei
       gedämpften Pillen. Es gehört zur Aufzählung, nicht zum Inhalt. */
    color: var(--text-muted);
    font-size: 0.78rem;
    font-weight: 600;
    /* KEIN gap: zwischen den Pills steht ein Komma als Textknoten, und der Gap
       würde auch davor Luft setzen („#velöle , #bern"). Den Abstand bringt das
       „, " selbst mit. */
    gap: 0;
}
.suggestion-shared-label {
    min-width: 0;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.people-list-item-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.people-list-item-title {
    font-weight: 800;
    font-size: 1rem;
    color: var(--text-primary);
    display: flex;
    align-items: center;
    gap: 0.25rem;
}
.people-list-item-sub {
    font-size: 0.8rem;
    color: var(--text-secondary);
    font-weight: 600;
}
.people-list-item-stats {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-shrink: 0;
    flex-wrap: wrap;
    justify-content: flex-end;
}
.np-total {
    font-weight: 800;
    font-size: 0.85rem;
    color: var(--accent-pink);
    padding: 0.15rem 0.5rem;
    background: rgba(255, 0, 102, 0.1);
    border-radius: var(--radius-full);
}
.np-total.np-zero {
    color: var(--text-muted);
    background: transparent;
}
.np-total-small {
    font-weight: 700;
    font-size: 0.8rem;
    color: var(--text-secondary);
}
.np-breakdown {
    display: flex;
    gap: 0.4rem;
}
.np-bucket {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    font-size: 0.75rem;
    font-weight: 700;
    color: var(--text-secondary);
}
.np-bucket i {
    font-size: 0.55rem;
}
.np-bucket i + i {
    margin-left: -2px;
}

/* Eingerahmtes Board-Pill (z.B. in „folgt auch #velöle #bern") */
/* Formatierung der Board-Tiles aus dem Feed (`.board-chip`): weisse Fläche,
   1px-Rahmen, volle Rundung, Textfarbe statt Schwarz. Nur die Masse sind
   kleiner — die Pille sitzt hier IN einer Textzeile, nicht als Control. */
/* --- Board-Tile: so heisst ein Board, wenn es GENANNT wird ------------------
   Ein Board im Fliesstext trägt app-weit dieselbe Pille: im Feed-Kopf („hat
   einen Pin auf #bern"), in den Freundschaftsvorschlägen, überall.
   Die Werte kommen aus `formatPinSubtitle` (feed.js), wo sie als Inline-Style
   standen — sie sind bewusst auf den Reach-Badge daneben abgestimmt: gleiche
   Schriftgrösse, gleiche Border-Stärke, gleiche gedämpfte Farbe, damit `#tag`
   und das Personen-Icon wie Geschwister wirken. Jetzt EINE Quelle statt zwei
   Definitionen, die auseinanderdriften. */
/* `.user-meta .board-tag-pill` muss mit: `.user-meta span { font-size: .8rem }`
   ist höher spezifisch und hat die Pille im Feed-Kopf sonst vergrössert —
   solange die Werte als Inline-Style dort standen, fiel das nicht auf. */
.board-tag-pill,
.user-meta .board-tag-pill,
.shared-topic-pill {
    display: inline-flex;
    align-items: center;
    padding: 0.15rem 0.5rem;
    background: var(--surface-color);
    color: var(--text-muted);
    border: 1.5px solid var(--text-muted);
    border-radius: var(--radius-sm);
    font-weight: 800;
    font-size: 0.7rem;
    line-height: 1;
    text-transform: lowercase;
    vertical-align: middle;
}
/* @handle unter dem Namen — füllt den Kopf und ist die Identität, die im
   Header einer Person app-weit steht (CLAUDE.md). Exakt die Werte der
   Friends-/FoF-Zahlen auf den Board-Cards (`.board-stat`): dieselbe
   Meta-Ebene, also dieselbe Dosis. */
.people-list-item-handle {
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text-muted);
}
.friend-suggestion-shared {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.25rem;
}

/* Friend Suggestion Row inside the People card */
.friend-suggestion-row .friend-suggestion-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}
.friend-suggestion-add {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--accent-turkis);
    color: #000;
    border: none;
    font-size: 1rem;
    font-weight: 800;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.1s;
}



/* --- Personen-/Board-Zeilen (Board-Follower + die Listen im Profil) ---
   Sprache wie „Fragen hinzufügen": eine Unterseite ist KOMPLETT FLACH — keine
   Cards, kein Bevel, keine Trennlinien. Tiefe wird einmal auf der Hauptebene
   vergeben (DS §3); eine Ansicht, die sich über etwas anderes legt,
   konkurriert nicht mit ihm. Gegliedert wird über Überschrift und Abstand.
   Die Zeilen laufen fullwidth über die Ansicht, das Padding sitzt in der
   Zeile — sonst wandert der Tap-Bereich vom Rand weg. */
.member-list {
    display: flex;
    flex-direction: column;
}
.member-item {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    padding: 0.55rem 1rem;
    background: none;
    border: none;
    box-shadow: none;
}
.member-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}
.member-info {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}
.member-name {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-weight: 700;
    font-size: 0.95rem;
    color: var(--text-primary);
}
.member-city {
    font-size: 0.75rem;
    color: var(--text-muted);
    font-weight: 600;
}

/* (`.board-pin-card` samt Kopf/Avatar/Autor/Zeit/Body ist mit BOARD-02
   entfallen — die Pins der Board-Detailseite rendern seit FEED-03 über
   `createPostHTML(pin, id, 'full')`, also als volle Post-Cards.) */
/* Leeres Ergebnis — auf der Board-Detailseite und im Feed (FEED-13) dieselbe
   ruhige Zeile: die Filter, die dazu geführt haben, stehen ja direkt darüber. */
.board-empty,
.feed-empty {
    text-align: center;
    padding: 2rem;
    color: var(--text-muted);
    font-weight: 700;
    font-size: 0.9rem;
}

/* (Der frühere Inline-Kopf der Board-Detailseite — `.board-detail-header-inline`,
   `.inline-back-btn`, `-title-block`, `-title-row`, `-name`, `-desc` — ist mit
   BOARD-02 entfallen. Die Seite nutzt den globalen Detail-Header (Back + `#tag`
   + lokale Lupe) und darunter die `.board-detail-info-card`.) */

/* Stacked Avatars */
.stacked-avatars {
    display: flex;
    align-items: center;
}
.stacked-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    /* Der Trennring eines überlappenden Stapels steht in der FLÄCHENFARBE
       (DS §11) — er trennt gegen die Card, er ist keine Umrandung. Hart
       gesetztes Weiss leuchtete im Dunkeln als Ring um jedes Gesicht.
       Der schwarze Aussenring darunter ist aus demselben Grund gefallen:
       reines Schwarz kommt im System nicht vor (DS §1). */
    border: 2px solid var(--surface-color);
    object-fit: cover;
}

/* Members Preview (clickable) */
.board-members-preview {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
    padding: 0.3rem;
    border-radius: var(--radius-md);
    transition: background 0.1s;
}
.board-members-preview:active {
    background: var(--surface-light);
}
.board-members-arrow {
    font-size: 0.8rem;
    color: var(--text-muted);
}

.board-detail-pins-list {
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1.875rem;
    /* KEIN margin-top — der Gap zur Card kommt aus dem padding-bottom des
       sticky-group-Containers darueber. So endet der sticky-Container genau
       1.875rem unter der Filter-Unterkante, und wenn der erste Pin diesen
       Container-Boden erreicht, unstickt die sticky-Card automatisch. */
}
/* Sticky-Container fuer Card + PTR-Indikator + Filter + Spacer. Der
   Spacer (h=1.875rem) liefert den Gap zur Pin-Liste UND erweitert die
   sticky-Range, sodass die Card sticky bleibt bis der erste Pin den
   Spacer-Boden = Card-Bottom erreicht (= sticky.top + cardH). Padding
   wuerde nicht funktionieren — CSS-sticky nutzt content-box als
   Containing-Block. */
.board-detail-sticky-group {
    /* kein padding */
}
.board-detail-sticky-spacer {
    height: 1.875rem;
}

/* --- Board-Detail-Info-Card ---
   Sitzt flush direkt unter dem Header (kein vertikaler Gap). Bricht
   horizontal aus main's Padding aus (Desktop), edge-to-edge auf Mobile.
   Layout: rundes Avatar links, Text mittig (Title + Follower-Link + Counts),
   Actions rechts (Folgen-Button + Bell). */
.board-detail-info-card {
    display: grid;
    grid-template-columns: auto 1fr auto;
    column-gap: 0.95rem;
    align-items: center;
    background: var(--surface-color);
    padding: 0.85rem 1rem;
    /* STICKY direkt unter dem Header. Z-Index 60 (Header ist 100) — sodass
       Filter+PTR-Indikator (DOM nach Card) bei parked-Scroll DAHINTER
       verschwinden. Side-Margins fuer Full-Width-Breakout. KEIN negatives
       Top-Margin (wuerde sticky-Math durcheinander bringen) — stattdessen
       wird main's padding-top fuer die Board-Detail-View entfernt. */
    margin: 0 -1.5rem;
    position: sticky;
    top: var(--header-h, 110px);
    z-index: 60;
}
@media (max-width: 600px) {
    .board-detail-info-card { margin: 0; }
}

/* Main-Padding-Reset fuer die Board-Detail-View, damit die sticky-Card
   ohne Top-Gap direkt am Header haftet. Ohne diesen Reset wuerde main's
   `padding-top: 1.5rem` einen 24px-Gap erzwingen. */
#friends-section .friends-view #board-detail-view,
#friends-section.active #board-detail-view:not(.hidden) {
    margin-top: -1.5rem;
}

/* Per-View PTR-Indikator sitzt DOM-maessig zwischen Card und Filter. Sticky
   direkt UNTER der Card (top: header-h + card-h) — damit die rosa Bar im
   parked-State exakt an der Card-Unterkante haengt und beim Pull dort
   waechst. Z-Index unter Card (50), sodass Card visuell ueber dem Indikator
   liegt wenn er kollabiert ist (h=0). */
#board-detail-pull-indicator {
    position: sticky;
    top: calc(var(--header-h, 110px) + var(--card-h, 95px));
    z-index: 50;
    margin: 0 -1.5rem;
}
@media (max-width: 600px) {
    #board-detail-pull-indicator { margin: 0; }
}

/* Per-View Pull-to-Refresh-Indikator (innerhalb der Board-Detail-View,
   im DOM direkt nach der Card). Rosa Pin-Welt-Optik, wächst beim Pull bei
   scrollY=0 — die Bar erscheint genau zwischen Card-Unterkante und Filter-
   Oberkante. Kein sticky (Card ist auch nicht sticky), normales Fluss-
   Element. */
#board-detail-pull-indicator {
    margin: 0 -1.5rem;
}
@media (max-width: 600px) {
    #board-detail-pull-indicator { margin: 0; }
}
.board-detail-info-avatar {
    width: 60px;
    height: 60px;
    font-size: 1.05rem;
    align-self: center;
}
.board-detail-info-text {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.board-detail-info-title {
    font-weight: 800;
    font-size: 1.1rem;
    color: var(--text-primary);
}
.board-detail-info-followers {
    background: transparent;
    border: none;
    padding: 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    /* `font-family: inherit` ist bei einem <button> Pflicht — er erbt die
       Schrift NICHT, sondern nimmt die des Systems. „200 folgen" lief darum in
       einer anderen Schrift als die Friend-/FoF-Zahlen direkt darunter.
       Grösse und Gewicht ebenfalls auf die Counts-Zeile gezogen: es sind
       dieselben Meta-Angaben zum selben Board. */
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text-muted);
    align-self: flex-start;
}
.board-detail-info-followers:active { opacity: 0.6; }
.board-detail-info-followers i {
    font-size: 0.7rem;
}
.board-detail-info-counts {
    display: flex;
    gap: 0.8rem;
    margin-top: 0.1rem;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text-muted);
}
.board-detail-info-count {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}
/* Glocke und „Gefolgt" nebeneinander in EINER Zeile (Glocke links). Beide
   sagen etwas über denselben Zustand; untereinander lasen sie sich wie zwei
   Ebenen. */
.board-detail-info-actions {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    align-self: center;
}

/* Wrapper um PTR-Indikator + Filter-Strip — KOLLABIERT by default
   (max-height: 0, overflow: hidden). Beim Pull (scrollY=0) wird `.revealed`
   gesetzt → Wrapper slidet aus mit Transition. Beim Scroll-Down (scrollY>0)
   wird `.revealed` von JS entfernt → Wrapper kollabiert zurück (Filter +
   rosa Bar verschwinden „hinter der Card"). */
/* Filter-Strip-Overrides fuer die Board-Detail-Seite: Strip sitzt jetzt
   NACH der Card im DOM, das Default-Negativ-Top (-2rem) wuerde ihn in die
   Card ziehen. Reset auf 0; Bottom-Gap kommt aus posts-margin-top. */
#board-detail-filter-strip.filter-strip {
    margin-top: 0;
    margin-bottom: 0;
}

/* Bottom-Spacer im Pin-Listen-Container, damit das Dokument IMMER mindestens
   einen Viewport mehr Scroll-Höhe als die natürliche Content-Höhe bekommt.
   Ohne diesen Spacer kann `parkBoardDetailFilterStrip()` nicht scrollen,
   wenn das Board wenig Pins hat — der Browser clampt scrollY auf 0 wenn das
   Dokument bereits in den Viewport passt. Mit dem Spacer landen die Posts
   im Default (parked) im sichtbaren Bereich unter der Card; beim Hochscrollen
   wird der Filter aus seinem Versteck hinter der Card enthüllt. */
.board-detail-pins-list {
    min-height: 100vh;
}

/* ================================
   PROFILE PAGE
   ================================ */
/* ACHTUNG: Es gibt KEIN globales `.hidden` in dieser App — jede Profil-
   Unteransicht muss hier namentlich stehen, sonst bleibt sie im Fluss und
   erscheint unter der gerade offenen Ansicht. Genau das ist der Level-Ansicht
   passiert (2026-08-02). Wer eine neue Unteransicht baut: hier eintragen. */
#profile-main-view.hidden,
#profile-unlock-view.hidden,
#profile-level-view.hidden {
    display: none;
}

.profile-avatar-img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
}
.profile-name {
    font-weight: 900;
    font-size: 1.3rem;
    margin: 0;
    padding: 0.05rem 0.5rem;
    border-radius: 10px;
    outline: none;
}
/* Mit gesetztem Hintergrund etwas mehr Luft, damit es wie ein Pill wirkt. */
.profile-name.has-bg { padding: 0.12rem 0.7rem; }
/* Namensfarben-Controls (nur im Bearbeiten-Modus). */
.profile-name-colors {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    margin-top: 0.55rem;
}
.name-color-field {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    cursor: pointer;
}
.name-color-field input[type="color"] {
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1px solid rgba(0, 0, 0, 0.2);
    border-radius: 50%;
    background: none;
    cursor: pointer;
}
.name-color-field input[type="color"]::-webkit-color-swatch-wrapper { padding: 2px; }
.name-color-field input[type="color"]::-webkit-color-swatch { border: none; border-radius: 50%; }
.name-color-clear {
    width: 26px;
    height: 26px;
    border: 1px solid rgba(0, 0, 0, 0.2);
    border-radius: 50%;
    background: var(--surface-color);
    color: var(--text-muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
}
.name-color-clear:active { background: rgba(0, 0, 0, 0.06); }

/* Appearance-Controls (Seiten-/Rubriken-Hintergrund + Rubrik-Textfarbe).
   z-index 9, damit sie über der Pin-Ebene (z8) klickbar bleiben. */
.profile-appearance {
    position: relative;
    z-index: 9;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.7rem 1rem;
    padding: 0.1rem 1rem 0.5rem;
}
.appearance-field {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--text-muted);
}
.appearance-field input[type="color"] {
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1px solid rgba(0, 0, 0, 0.2);
    border-radius: 50%;
    background: none;
    cursor: pointer;
}
.appearance-field input[type="color"]::-webkit-color-swatch-wrapper { padding: 2px; }
.appearance-field input[type="color"]::-webkit-color-swatch { border: none; border-radius: 50%; }
.appearance-clear {
    width: 24px;
    height: 24px;
    border: 1px solid rgba(0, 0, 0, 0.2);
    border-radius: 50%;
    background: var(--surface-color);
    color: var(--text-muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.65rem;
}
.appearance-clear:active { background: rgba(0, 0, 0, 0.06); }

/* Karma: bewusst NICHT Blitz/Gelb (das ist Updates). Award-Icon + Violett,
   passend zu den höheren Karma-Ring-Stufen. */
.profile-section-title {
    font-weight: 900;
    font-size: 0.9rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-secondary);
    padding: 1rem 1rem 0.5rem 1rem;
}
/* Fragen-Card: dieselbe Optik wie eine Post-Card im Feed (weiss, gerundet, kein
   Rahmen, Elevation + Bevel) — ABER ohne den farbigen Streifen oben. Der
   markiert Updates (gelb) und Pins (rosa); eine Frage gehört zu keiner der
   beiden Rubriken (CLAUDE.md: „wie ein Feed-Post, aber ohne Farbstreifen und
   ohne Interaktionen"). Werte 1:1 aus .post-card. */
/* Avatare tragen app-weit keinen Ring und keine Border (DS §11). Überlappende
   Stapel trennen sich über einen Ring in der FLÄCHENFARBE — diese zwei Stellen
   waren beim Sweep DS-04 durchgerutscht. */

/* (`.locked-overlay` ist mit BOARD-02 entfallen — den Gesperrt-Zustand tragen
   heute `.person-locked-note` im Fremdprofil und die Blur-Mechanik im Feed.) */

/* ---- „Frage hinzufügen" (Unterseite über dem Profil) --------------------
   Fullscreen-Unterseite, also KOMPLETT FLACH: keine Cards, kein Bevel, keine
   Schatten. Tiefe wird einmal auf der Hauptebene vergeben (DS §3) — eine
   Ansicht, die sich über das Profil legt, konkurriert nicht mit ihm. Getrennt
   wird über Hairlines und Abstand, wie in der Boards-Vollansicht.
   (`.unlock-overview-header` ist ersatzlos entfallen — die Seite nutzt den
   globalen Detail-Header mit Back + Titel.) */
.unlock-list {
    display: flex;
    flex-direction: column;
    padding-bottom: 2rem;
}
/* Gegliedert nach Level. Keine Trennlinien — gruppiert wird über Abstand und
   die Überschrift; Linien würden auf einer flachen Seite ein Raster erzeugen,
   das die Gliederung nach Level nur doppelt. */
.unlock-level-group {
    display: flex;
    flex-direction: column;
    padding: 1.4rem 0 0;
}
.unlock-level-title {
    padding: 0 1rem 0.5rem;
    font-weight: 900;
    font-size: 0.9rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-secondary);
}
/* Alles oberhalb des eigenen Levels: grau, ohne Aktion. Der Zustand steht
   einmal an der Gruppe statt an jeder Zeile. */
.unlock-level-group.locked .unlock-level-title,
.unlock-level-group.locked .unlock-item-title {
    color: var(--text-muted);
}
.unlock-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.55rem 1rem;
    background: none;
    border: none;
    box-shadow: none;
}
.unlock-item-info { display: flex; align-items: center; gap: 0.5rem; min-width: 0; }
.unlock-item-title { font-weight: 700; font-size: 0.95rem; color: var(--text-primary); }

/* Ausblick-Gruppe: sichtbar, aber unlesbar. Dieselbe Mechanik wie die Paywall
   im Feed (Blur statt Block) — eine leere Stelle sagt „hier ist nichts", eine
   unscharfe sagt „hier ist etwas". Nach unten zusätzlich ausblendend, damit die
   Liste ausläuft statt abzubrechen. */
.unlock-level-group--teaser .unlock-item-title {
    filter: blur(3.5px);
    user-select: none;
    pointer-events: none;
}
.unlock-level-group--teaser {
    -webkit-mask-image: linear-gradient(to bottom, #000 35%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 35%, transparent 100%);
}
/* Der Schluss-Satz erklärt, was der Blur zeigt.
   Dieselbe Behandlung trägt die Zusicherung unter der Chat-Liste (CHAT-02) —
   beides sind leise Fussnoten unter einer Liste, keine Werbung. Sie stehen
   darum in EINER Regel: zwei Kopien derselben drei Werte driften auseinander,
   sobald jemand einen davon anfasst. Die Chat-Zeile ist zusätzlich zentriert,
   weil sie unter einer Liste aus Cards steht und nicht unter linksbündigem
   Fliesstext. */
.unlock-outro,
.chat-outro {
    padding: 0.4rem 1rem 0;
    color: var(--text-muted);
    font-size: 0.82rem;
    font-weight: 600;
    line-height: 1.45;
}
.chat-outro {
    text-align: center;
    padding: 1.1rem 2rem 0.25rem;
}

/* Die Haupt-Aktion der Seite → Türkis (neutrale Aktion, DS §2), schwarze
   Schrift (Kontrastregel „Schwarz auf Türkis"). Werte der kleinen Aktions-
   Pille aus DS §7, aber als Primär-Button gefärbt statt weiss. */
.unlock-add-btn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2rem;          /* wie .board-action — Haken und Text gleich hoch */
    gap: 0.4rem;
    padding: 0.4rem 0.8rem;
    border: none;
    border-radius: var(--radius-md);
    background: var(--accent-turkis);
    color: #000;
    font-family: inherit;
    font-weight: 600;
    font-size: 0.85rem;
    cursor: pointer;
}
/* Hinzugefügt: nur ein Haken, ruhig statt Brand-Fläche — ein Status ist kein
   Call-to-Action (dieselbe Logik wie „Folgen" türkis / „Gefolgt" weiss auf den
   Board-Cards). Quadratisch, damit die Zeile beim Umschalten nicht springt …
   sie wird schmaler, aber der Button bleibt rechts bündig. */
.unlock-add-btn.is-added {
    background: var(--surface-light);
    color: var(--text-secondary);
    padding: 0.4rem 0.6rem;
}

.unlock-empty {
    padding: 1rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-weight: 600;
}

/* ---- Profil-Neuaufbau: weisses Sheet (Banner + zentrierter Avatar + Karma +
   Name/Spitzname + freie Rubrik-Blöcke + „Rubrik hinzufügen") ---- */
#profile-main-view,
#profile-unlock-view,
#profile-level-view {
    background: var(--surface-color);
    min-height: calc(100vh - 88px);
}
/* Weisse Basis-Fläche + Stacking-Context für die Pin-Ebenen-Staffelung.
   Ebenen-Reihenfolge (unten→oben): Basis-Weiss → Banner (z1) → Pins im
   Ansichtsmodus (z2) → Inhalt (z3) → Identity/Toolbar-Controls (z9). Im
   Bearbeiten-Modus liegen die Pins zum Platzieren vorne (z8). */
#profile-content {
    position: relative;
    z-index: 0;
    background: var(--surface-color);
}
/* Banner flush unter den Header: main's top-padding negieren. */
/* Wie #main-feed-state im Feed: pan-y heisst „vertikal scrollt der Browser
   nativ, horizontale Gesten gehören JS". Muss via CSS gesetzt sein, VOR dem
   ersten Pointer-Event — sonst deutet der Browser den horizontalen Wisch als
   Pan und bricht den Pointer-Stream mit pointercancel ab. Genau daran ist der
   Rubrik-Wisch im Profil auf dem Handy gescheitert (Desktop-Maus kennt
   touch-action nicht, dort lief er von Anfang an). */
#profile-section {
    margin-top: -1.5rem;
    touch-action: pan-y;
}

.profile-cover {
    position: relative;
}
.profile-banner {
    position: relative;
    z-index: 1;
    height: 150px;
    background-size: cover;
    background-position: center;
    background-color: #d8dee9;
}
.profile-identity {
    position: relative;
    z-index: 9;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
    padding: 58px 1rem 1.1rem;
}
.profile-identity .profile-name { font-size: 1.45rem; }

.profile-rubrics {
    position: relative;
    z-index: 3;
    padding: 0.5rem var(--profile-axis, 14px) 2.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}
/* Halbtransparente „Frosted-Glass"-Karte: die Hintergrund-Fotos schimmern
   durch, die Rubrik bleibt durch Border + Blur trotzdem als Block erkennbar.
   (Experiment — Deckkraft/Blur leicht anpassbar.) */
/* Fragen-Card: dieselbe Optik wie eine Post-Card im Feed (weiss, gerundet, kein
   Rahmen, Elevation + Bevel) — ABER ohne den farbigen Streifen oben. Der
   markiert Updates (gelb) und Pins (rosa); eine Frage gehört zu keiner der
   beiden (CLAUDE.md: „wie ein Feed-Post, aber ohne Farbstreifen und ohne
   Interaktionen"). Werte 1:1 aus .post-card.
   Das frühere Milchglas stammt aus der Zeit, als das Profil eine eigene
   Hintergrundfarbe tragen konnte — auf dem Hochweiss-Canvas hat es nichts
   mehr zu durchscheinen. */
.profile-rubric-block {
    background: var(--surface-color);
    border: none;
    border-radius: var(--radius-lg);
    box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.14),
                inset 0 1px 0 var(--bevel-light),
                inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
    padding: 0.9rem 1rem;
}
.profile-rubric-head {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 0.5rem;
}
/* Drag-Handle zum Sortieren (nur im Bearbeiten-Modus sichtbar). */
.rubric-drag-handle {
    background: transparent;
    border: none;
    color: var(--text-muted);
    cursor: grab;
    font-size: 0.95rem;
    line-height: 1;
    padding: 0.1rem 0.2rem;
    margin: -0.1rem 0.1rem -0.1rem -0.25rem;
    touch-action: none;
}
.rubric-drag-handle:active { cursor: grabbing; color: var(--text-secondary); }
/* Block, der gerade umsortiert wird: angehoben über die anderen. */
.profile-rubric-block.reordering {
    position: relative;
    z-index: 20;
    box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2);
    opacity: 0.97;
}
/* Normale Schreibung, keine Versalien: die Frage ist Sprache, kein Etikett —
   und Versalien lesen sich als System-Label, nicht als etwas, das jemand über
   sich sagt. Ohne Caps darf sie eine Spur grösser sein, sonst wirkt sie kleiner
   als vorher. (Die Caps-Optik bleibt den Sektions- und Level-Titeln.) */
.profile-rubric-label {
    font-weight: 700;
    font-size: 0.8rem;
    color: var(--text-muted);
}
/* `×` statt Papierkorb: „vom Profil nehmen" heisst in allen drei Rubriken
   dasselbe und trägt jetzt dasselbe Zeichen (Foto-Raster: `×` auf dem Bild,
   Pins: das Auge, weil dort ein Zustand umgeschaltet wird).
   Grau und ohne Fläche — es ist Inhalt auf einer Card (DS §3). */
.profile-rubric-remove {
    background: transparent;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 0.9rem;
    line-height: 1;
    padding: 0.1rem 0.3rem;
    margin: -0.1rem -0.3rem -0.1rem auto;
}
.profile-rubric-remove:active { color: var(--text-secondary); }
.profile-rubric-answer {
    margin-top: 0.35rem;
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--text-primary);
    line-height: 1.35;
    outline: none;
    min-height: 1.2em;
    word-break: break-word;
}
.profile-rubric-answer:empty::before {
    content: attr(data-placeholder);
    color: var(--text-muted);
    font-weight: 500;
}
/* Im Bearbeiten-Modus ist die Antwort ein Eingabefeld — also eingelassen, wie
   Name und Bio (DS §5: „Textfelder immer inset, kein Border"). Vorher war sie
   auch im Modus blosser Text, man sah nicht, dass man hineinschreiben kann.
   Werte identisch zu .profile-field, damit sich beide Felder gleich anfühlen. */
.profile-rubric-answer.editing {
    background: var(--surface-light);
    border-radius: var(--radius-md);
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
    padding: 0.45rem 0.7rem;
    margin-top: 0.4rem;
}
/* --- Frage-Typ „Fotos" (PROF-08) ------------------------------------------
   Dieselbe Frage-Card wie eine Textfrage, nur steht statt des Antwort-Felds ein
   Raster darunter. Kachel-Optik aus dem Updates-Raster (4:5, --radius-sm) — ein
   Bild in einer Card sieht in dieser App überall gleich aus.

   IMMER DREI SPALTEN, auch bei einem Bild: die Kacheln behalten damit ihre
   Grösse, egal wie viele es sind, und die Card wächst nicht je nach Füllstand.
   Neben den Text-Cards steht sie so ruhig. Ein einzelnes Bild über die volle
   Breite wäre ein Fotoalbum mit einem Bild.
   Der Abstand oben ist der der Text-Antwort (0.35rem) — beide Typen setzen an
   derselben Stelle unter dem Label an. */
.profile-photo-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 3px;
    margin-top: 0.4rem;
}
.profile-photo-tile {
    position: relative;
    aspect-ratio: 4 / 5;
    overflow: hidden;
    border-radius: var(--radius-sm);
    background: var(--surface-light);
    padding: 0;
    border: none;
}
.profile-photo-tile > img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* Der leere Platz ist EINGELASSEN, kein gestrichelter Rahmen: Rahmen gibt es
   global nicht (DS §1), und ein Platz, in den etwas hineingehört, ist genau
   das, wofür die Spur da ist (DS §5/§6). Das Plus liegt flach darauf. */
.profile-photo-tile--empty {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    cursor: pointer;
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
}
.profile-photo-tile--empty .ic { width: 1.15rem; height: 1.15rem; }
/* Gedrückte Fläche, keine Verschiebung (DS §7). */
.profile-photo-tile--empty:active { background: rgba(var(--shadow-rgb), 0.08); }
/* Unbeantwortet und nicht im Modus: derselbe graue Platzhaltersatz wie bei
   einer Textfrage — die Antwort fehlt, das sagt man gleich. */
.profile-photo-empty { color: var(--text-muted); font-weight: 500; }

/* (Hier lag `.profile-add-rubric` — der gestrichelte „Rubrik hinzufügen"-Button
   am Ende der Fragen-Liste. Seit PROF-01 sitzt „Fragen hinzufügen" als
   `.rubric-action-btn` in der Aktions-Zeile; die Klasse kam im Markup nicht
   mehr vor. Mit DS-06 ersatzlos entfernt — siehe die Regel dort.) */

/* Bearbeiten-Toggle + Pin-Werkzeuge. Liegt z-index-mässig über der Pin-Ebene,
   damit die Steuer-Buttons immer klickbar bleiben. */
/* Pin-Werkzeuge-Zeile (nur im Bearbeiten-Modus). Liegt z-index-mässig über der
   Pin-Ebene, damit die Buttons immer klickbar bleiben. */
.profile-toolbar-row {
    position: relative;
    z-index: 9;
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 0.5rem;
    padding: 0.1rem 1rem 0.4rem;
}
/* Bearbeiten-Toggle sitzt zentriert im Identity-Block, direkt unter dem
   Spitznamen. */

/* Freie Pin-Ebene: liegt über dem GESAMTEN Profil-Content (inset:0). Im
   Ansichtsmodus pointer-events:none → blockiert weder Scroll noch Klicks; im
   Bearbeiten-Modus werden die Pins ziehbar/löschbar. */
.profile-pin-layer {
    position: absolute;
    inset: 0;
    /* Ansichtsmodus: hinter dem Inhalt (über Banner/Basis-Weiss, unter
       Text/Karten/Avatar) → Fotos liegen als Hintergrund. */
    z-index: 2;
    overflow: hidden;
    pointer-events: none;
}
/* Bearbeiten-Modus: nach vorne, damit Fotos frei greif- und platzierbar sind
   (auch über Rubriken, um sie danach „dahinter" zu legen). */
.profile-pin-layer.editing {
    z-index: 8;
}
.profile-pin {
    position: absolute;
    transform: translate(-50%, -50%) rotate(var(--rot, 0deg));
}
.profile-pin-layer.editing .profile-pin {
    pointer-events: auto;
    cursor: grab;
    touch-action: none;
}
.profile-pin-layer.editing .profile-pin.dragging {
    cursor: grabbing;
    z-index: 10;
}
.profile-pin--photo img {
    display: block;
    width: 84px;
    height: 84px;
    object-fit: cover;
    border: 4px solid #fff;
    border-radius: 2px;
    box-shadow: 0 3px 9px rgba(0, 0, 0, 0.3);
    pointer-events: none;
}
.pinboard-item-delete {
    position: absolute;
    top: -9px;
    right: -9px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: none;
    background: #000;
    color: #fff;
    font-size: 0.7rem;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 6;
}
/* Pin-Werkzeuge (links in der Toolbar-Zeile) + Sperr-Hinweis. */
.profile-pin-tools { display: flex; align-items: center; gap: 0.35rem; }
.pinboard-tool-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.6rem;
    border: 1px solid rgba(0, 0, 0, 0.15);
    border-radius: var(--radius-full);
    background: var(--surface-color);
    color: var(--text-secondary);
    font-family: inherit;
    font-weight: 700;
    font-size: 0.75rem;
    cursor: pointer;
}
.pinboard-tool-btn:active { background: rgba(0, 0, 0, 0.05); }
.pin-locked-hint {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--text-muted);
    font-weight: 700;
    font-size: 0.78rem;
}

/* Post-it-Notiz als Pin: farbiger Zettel mit editierbarem Text. */
.profile-pin--note {
    width: 116px;
    min-height: 92px;
    padding: 10px 11px 14px;
    box-sizing: border-box;
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.22);
    display: flex;
}
.note-text {
    flex: 1;
    font-size: 0.82rem;
    font-weight: 600;
    color: #3a3320;
    line-height: 1.25;
    outline: none;
    word-break: break-word;
    overflow: hidden;
}
.note-text:empty::before {
    content: attr(data-placeholder);
    color: rgba(0, 0, 0, 0.32);
}
.note-color {
    position: absolute;
    bottom: -9px;
    left: -9px;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 1px solid rgba(0, 0, 0, 0.25);
    border-radius: 50%;
    background: none;
    cursor: pointer;
}
.note-color::-webkit-color-swatch-wrapper { padding: 1px; }
.note-color::-webkit-color-swatch { border: none; border-radius: 50%; }

/* Hinweis unter den Rubriken: mehr Karma sammeln → mehr Rubriken. */
.profile-rubrics-hint {
    margin-top: 0.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    text-align: center;
    font-size: 0.82rem;
    font-weight: 600;
    /* War Lila (#8a7bd8 / #7C4DFF) — die Farbe ist mit dem Endtyp abgeschafft
       (DS §2). Ein Hinweis ist Nebentext, also Muted. */
    color: var(--text-muted);
}

/* Kommentarspalte zuunterst — dieselbe Card wie die Fragen darüber. Das
   frühere Milchglas (halbtransparentes Weiss + backdrop-filter + dünner
   Rahmen) stammt aus der Zeit, als das Profil eine eigene Hintergrundfarbe
   tragen konnte; mit dem Hochweiss-Canvas hat es nichts mehr zu durchscheinen. */
.profile-comments {
    position: relative;
    z-index: 3;
    padding: 0.5rem var(--profile-axis, 14px) 2.5rem;
}
.profile-comments-card {
    background: var(--surface-color);
    border: none;
    border-radius: var(--radius-lg);
    box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.14),
                inset 0 1px 0 var(--bevel-light),
                inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
    padding: 0.9rem 1rem 1rem;
    color: var(--text-primary);
}
.profile-comments-title {
    font-weight: 900;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
    margin-bottom: 0.55rem;
}
.profile-comment-list {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.profile-comment {
    display: flex;
    gap: 0.6rem;
    align-items: flex-start;
}
.profile-comment-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}
.profile-comment-body { flex: 1; min-width: 0; }
.profile-comment-head {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
}
/* Name + Text erben die (rubrik-gekoppelte) Textfarbe der Card; die Zeit ist
   eine gedimmte Variante davon → auf hell wie dunkel lesbar. */
.profile-comment-name { font-weight: 800; font-size: 0.85rem; color: var(--text-primary); }
.profile-comment-time { font-size: 0.72rem; color: var(--text-muted); }
.profile-comment-text {
    font-size: 0.9rem;
    color: inherit;
    margin-top: 0.1rem;
    word-break: break-word;
}
.profile-comment-empty { color: var(--text-muted); font-size: 0.85rem; font-weight: 600; }
/* Löschen-Button pro Kommentar (nur im Bearbeiten-Modus). */
.profile-comment-remove {
    margin-left: auto;
    background: transparent;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 0.85rem;
    line-height: 1;
    padding: 0.1rem 0.3rem;
}
.profile-comment-remove:active { color: var(--text-secondary); }
/* Reaktionen auf Kommentare: Chips + Reagieren-Button + Emoji-Picker. */
.profile-comment-reactions {
    position: relative;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin-top: 0.35rem;
}
/* Herz auf einem Profil-Kommentar (PROF-06). Ein Element in zwei Zuständen:
   AUS = leeres Herz, das Angebot. AN = das eigene Profilbild mit kleinem
   türkisem Herz — die Aktion wird zu ihrem eigenen Ergebnis (YouTube-Muster).
   Flach nach DS §7 (Inhalt auf einer Card), kein Rahmen, keine Fläche. */
.comment-heart {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: none;
    padding: 0;
    cursor: pointer;
    color: var(--text-muted);
}
.comment-heart .ic { width: 1.05rem; height: 1.05rem; }

/* AN: Avatar + Badge. Das Herz ist hier gefüllt — `.ic` setzt global
   `fill: none`, das muss für die Vollfläche zurückgenommen werden. */
.comment-heart.is-on {
    position: relative;
    width: 22px;
    height: 22px;
}
.comment-heart-avatar {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    object-fit: cover;
}
/* Der Badge sitzt auf dem Avatar — getrennt durch einen Kreis in der
   FLÄCHENFARBE, nicht durch eine Border (DS §11, überlappende Avatare). */
.comment-heart-badge {
    position: absolute;
    right: -5px;
    bottom: -4px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--surface-color);
    display: flex;
    align-items: center;
    justify-content: center;
}
.comment-heart-badge .ic {
    width: 9px;
    height: 9px;
    color: var(--accent-turkis);
    fill: currentColor;
    stroke-width: 2.5;
}

/* (Die früheren „Unlock-Overview Ergänzungen" sind entfallen: `.unlock-item.locked`
   dimmte per opacity, obwohl die Gruppe schon --text-muted setzt — doppeltes Grau;
   `.unlock-add-btn` und `.unlock-empty` waren Zweitfassungen der Regeln weiter oben.) */

/* ================================
   Mobile: use the full screen width
   Below 600px (the #app cap), drop the side padding so cards / lists go
   edge-to-edge. Inner elements keep their own padding for breathing room.
   ================================ */
@media (max-width: 600px) {
    main { padding: 1.5rem 0; }
    .people-view { padding-left: 0.7rem; padding-right: 0.7rem; }
    .profile-section-title { padding-left: 0; padding-right: 0; }

    /* Filter-Strip: der Inhalt steht in Ruhe auf der **Card-Achse** (14px), die
       Bahn selbst läuft von Rand zu Rand. Die Polsterung sitzt an den Kindern
       statt am Strip, damit die scrollende Chip-Reihe beim Ziehen weiterhin bis
       an den Bildrand laufen kann. */
    .filter-strip { padding-left: 0; padding-right: 0; }
    .filter-row { padding-left: 14px; padding-right: 14px; }
    .filter-section-label { padding-left: 14px; padding-right: 14px; }
    .filter-strip .reach-segment { margin-left: 14px; margin-right: 14px; }
}

/* ===========================================================================
   POST-TYPE-CHOOSER — zwei weisse Seiten mit 3D-Bubble (CLAUDE.md → Neuer Post)
   ---------------------------------------------------------------------------
   Löst den 45°-Diagonal-Split ab (gelbes + rosa Dreieck über den ganzen Screen).
   Der Chooser übernimmt jetzt die Logik der Startseite: ZWEI weisse Seiten, die
   man horizontal durchwischt, mit je einer gefüllten Kugel in der Mitte —
   Update = gelbe Kugel/schwarzer Blitz · Pin = rosa Kugel/weisser Pin, dieselbe
   Farbregel wie beim Typ-Marker der Cards.

   Gesten und Choreografie liegen in `js/chooser.js`:
     · ziehbares Wischen (Rubber-Band + Feder-Snap statt Schwellwert-Sprung)
     · Verweil-Erklärung (Bubble steigt, Stichworte faden ein)
     · Farb-Füllung beim Tap

   Ohne das Script bleibt Seite 1 sichtbar und antippbar.
   =========================================================================== */
.post-type-chooser {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0; /* fullscreen; Abbrechen schwebt via cancel-mode z-index darüber */
    z-index: 200;
    background: var(--bg-color);
    overflow: hidden;
    /* Das horizontale Ziehen gehört uns — kein Browser-Pan/Bounce dazwischen. */
    touch-action: none;
    /* Am Desktop würde ein Mausdrag sonst Text markieren statt zu wischen. */
    user-select: none;
    -webkit-user-select: none;
    /* Kugel-Durchmesser: eng genug, dass Blitz + „Update" als EIN Objekt lesen. */
    --chooser-circle: min(50vw, 188px);
    animation: chooser-fade-in 180ms ease-out;
}
.post-type-chooser.hidden { display: none; }

@keyframes chooser-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* --- Die zwei Seiten: nebeneinander im Track statt geclippter Dreiecke ---
   Der Track wird über --chooser-x (px, vom Script gesetzt) verschoben.
   --bubble-y hängt PRO SEITE, nicht am Chooser: nur so kann die Seite, die man
   verlässt, ihren hochgefahrenen Zustand behalten, während die ankommende Seite
   neutral hereinkommt (das Zurückfahren passiert erst beim Einrasten, also
   unsichtbar). */
.chooser-half {
    position: absolute;
    inset: 0;
    border: none;
    background: transparent;           /* die Fläche ist der weisse Container */
    cursor: pointer;
    font-family: inherit;
    padding: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    will-change: transform;
    transition: none;                  /* kein Helligkeits-Flash beim Ziehen */
    --bubble-y: 0px;                   /* 0 = mittig · negativ = oben */
}
.chooser-update {
    color: #000;                       /* Schwarz auf Gelb */
    transform: translate3d(var(--chooser-x, 0px), 0, 0);
}
.chooser-pin {
    color: #fff;                       /* Weiss auf Rosa */
    transform: translate3d(calc(100% + var(--chooser-x, 0px)), 0, 0);
}

/* --- Die gefüllte Kugel (liegt hinter Icon + Schrift) ---
   3D nach DESIGN_SYSTEM.md §3 „Bevel auf farbigen Vollflächen", an genau diesem
   Screen hergeleitet: Elevation bleibt der Card-Systemwert (die Höhe über dem
   Canvas ist eine Eigenschaft der Ebene, nicht des Objekts), die Bevel-Deckkraft
   wird verdoppelt (.07 → .15) und ein weiches Licht oben kommt dazu, weil die
   Card-Werte auf Weiss kalibriert sind und eine gesättigte Fläche den Verlauf
   schluckt. Die Bevel-Geometrie skaliert mit der Objektgrösse (-3px/6px →
   -10px/20px), sonst ist die Wölbung auf 188px nur ein Strich. */
.post-type-chooser .chooser-half::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--chooser-circle);
    height: var(--chooser-circle);
    border-radius: 50%;
    transform: translate(-50%, calc(-50% + var(--bubble-y)));
    transition: transform 520ms cubic-bezier(0.22, 1, 0.36, 1);
    box-shadow:
        0 12px 28px rgba(var(--shadow-rgb), 0.14),          /* Elevation — Systemwert */
        inset 0 7px 14px rgba(255, 255, 255, 0.25),         /* Licht oben, weich */
        inset 0 -10px 20px rgba(var(--shadow-rgb), 0.15);   /* Bevel — 2× Deckkraft */
}
.post-type-chooser .chooser-update::before { background: var(--accent-yellow); }
.post-type-chooser .chooser-pin::before    { background: var(--accent-pink); }

/* Tap-Feedback: die Kugel gibt kurz nach — schnell (140 ms), damit es nicht
   mushy wirkt, und nur wenn gerade nicht gezogen wird. */

/* --- Icon + Schrift, mittig in der Kugel --- */
.post-type-chooser .chooser-icon {
    position: static;
    font-size: 2.9rem;
    line-height: 1;
    z-index: 1;
    margin-bottom: 0.45rem;
    transform: translateY(var(--bubble-y));
    transition: transform 520ms cubic-bezier(0.22, 1, 0.36, 1);
}
.post-type-chooser .chooser-label {
    position: static;
    font-size: 1.45rem;
    font-weight: 900;
    letter-spacing: 0.01em;
    z-index: 1;
    transform: translateY(var(--bubble-y));
    transition: transform 520ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* --- Verweil-Zustand: Kugel steigt, Stichworte erscheinen (pro Seite) --- */
.chooser-half.is-hinting { --bubble-y: -12vh; }

/* Zurücksetzen einer NICHT sichtbaren Seite: ohne Animation, weil sie ohnehin
   niemand sieht — das Script setzt `is-instant` nur für einen Reflow. */
.chooser-half.is-instant::before,
.chooser-half.is-instant .chooser-icon,
.chooser-half.is-instant .chooser-label,
.chooser-half.is-instant .chooser-facts,
.chooser-half.is-instant .chooser-facts span {
    transition: none;
}

/* --- Erklär-Liste (vom Script injiziert, Inhalt je Post-Art) --- */
.chooser-facts {
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.55rem;
    /* Sitzt immer knapp unter der Kugel — wandert mit --bubble-y mit. */
    transform: translateY(calc(var(--bubble-y) + var(--chooser-circle) / 2 + 2.1rem));
    transition: transform 520ms cubic-bezier(0.22, 1, 0.36, 1);
    pointer-events: none;
    color: var(--text-muted);
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.35;
    letter-spacing: 0.01em;
    text-align: center;
    padding: 0 1.9rem;   /* längere Zeilen dürfen umbrechen, aber nicht anstossen */
}
/* Zeilenweiser Fade-in, leicht gestaffelt → wirkt gesetzt statt geschaltet. */
.chooser-facts span {
    opacity: 0;
    transform: translateY(7px);
    transition: opacity 420ms ease-out, transform 480ms cubic-bezier(0.22, 1, 0.36, 1);
}
.chooser-half.is-hinting .chooser-facts span {
    opacity: 1;
    transform: none;
}
.chooser-half.is-hinting .chooser-facts span:nth-child(1) { transition-delay: 140ms; }
.chooser-half.is-hinting .chooser-facts span:nth-child(2) { transition-delay: 240ms; }
.chooser-half.is-hinting .chooser-facts span:nth-child(3) { transition-delay: 340ms; }
.chooser-half.is-hinting .chooser-facts span:nth-child(4) { transition-delay: 440ms; }

/* --- Seiten-Punkte (dezente Wisch-Affordanz, vom Script injiziert) --- */
.chooser-dots {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(var(--nav-top) + 1.4rem);   /* über der Nav (im Chooser: über dem Abbrechen-Button) */
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    pointer-events: none;
    z-index: 2;
}
.chooser-dots i {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: rgba(31, 31, 31, 0.18);
    transition: background 180ms ease-out, transform 180ms ease-out;
}
.post-type-chooser[data-page="0"] .chooser-dots i:nth-child(1),
.post-type-chooser[data-page="1"] .chooser-dots i:nth-child(2) {
    background: #1f1f1f;
    transform: scale(1.15);
}

/* --- Farb-Füllung beim Tap ---
   Hängt am <body>, nicht am Chooser: der Chooser (200) liegt unter der
   Bottom-Nav im cancel-mode (250), und der Compose-Screen liegt bei 200 — die
   Farbe soll über allem liegen und den neuen Screen beim Ausfaden freigeben. */
.chooser-ripple {
    position: fixed;
    border-radius: 50%;
    background: var(--accent-yellow);
    transform: translate(-50%, -50%) scale(0);
    opacity: 1;
    pointer-events: none;
    z-index: 400;
    transition: transform 340ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.chooser-ripple.is-pin { background: var(--accent-pink); }
.chooser-ripple.is-expanding { transform: translate(-50%, -50%) scale(1); }
/* Langsames Ausfaden — die Farbe verabschiedet sich, sie schneidet nicht weg. */
.chooser-ripple.is-fading {
    opacity: 0;
    transition: opacity 500ms ease-out;
}

/* === Compose-Mode (Step 2): fullscreen post-creation screen ===
   Two category variants:
     #create-modal.compose-update — yellow accent (submit + celebration)
     #create-modal.compose-pin — pink accent
   Both share the same layout. JS sets the title and the category class. */

#create-modal.compose-mode {
    bottom: 0;
    padding: 0;
    background: var(--bg-color);
}
#create-modal.compose-mode .modal-content {
    width: 100vw;
    max-width: none;
    max-height: none;
    height: 100vh;
    height: 100dvh;
    border: none;
    box-shadow: none;
    border-radius: 0;
    padding: 0;
    overflow: hidden;
    background: var(--surface-color);
    display: flex;
    flex-direction: column;
}
#create-modal.compose-mode .modal-header { display: none; }
#create-modal.compose-mode #modal-step-2 {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.compose-header {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 1.25rem 0.75rem;
    padding-top: max(1rem, env(safe-area-inset-top));
    border-bottom: 1px solid rgba(0,0,0,0.08);
}
/* Zurück-Button zur Kamera. Nur im Update-Compose sichtbar — der Pin-Pfad hat
   keinen Kamera-Schritt, aus dem man käme (dort kommt das Foto übers
   Action-Icon und trägt sein eigenes ×). Flach, in Textfarbe: Navigation, keine
   Aktion (DS §7). Der Titel bleibt dadurch links, „Abbrechen" rechts. */
/* Werte 1:1 vom globalen Header-Back (.header-back-btn) — ein Zurück-Pfeil
   sieht in der App überall gleich aus, gleiches Symbol (#i-nav-arrow-left,
   nicht der lange #i-arrow-left) und gleiche Grösse. */
.compose-back-btn {
    display: none;
    align-items: center;
    justify-content: center;
    border: none;
    background: none;
    padding: 0.25rem 0.4rem;
    margin-left: -0.4rem;
    margin-right: 0.35rem;
    font-size: 1.2rem;
    color: var(--text-primary);
    cursor: pointer;
}
#create-modal.compose-update .compose-back-btn { display: inline-flex; }
/* Titel + Zurück bilden die linke Gruppe; „Abbrechen" bleibt rechts aussen. */
.compose-header .compose-title { margin-right: auto; }

.compose-title {
    font-size: 1.15rem;
    font-weight: 800;
    color: var(--text-primary);
    letter-spacing: 0.01em;
    margin: 0;
}
.compose-cancel-btn {
    background: transparent;
    border: none;
    color: var(--text-secondary);
    font-family: inherit;
    font-weight: 800;
    font-size: 1rem;
    letter-spacing: 0.02em;
    cursor: pointer;
    padding: 0.25rem 0.25rem;
}
.compose-cancel-btn:active { opacity: 0.6; }

.compose-body {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden; /* no horizontal scroll bleed from slider labels */
    overscroll-behavior: contain; /* scroll stays inside the modal, doesn't propagate to body */
    padding: 1rem 1.25rem 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

/* Image preview: portrait 4:5 crop, fills the available width.
   Uses the padding-top % hack instead of aspect-ratio because aspect-ratio +
   height:auto collapses to 0px inside a flex column (.compose-body) — flex
   asks the child for its intrinsic height, the child asks the image, the
   image asks the container, infinite loop → 0. padding-top % is relative to
   the parent's WIDTH, sidestepping the loop entirely. */
#create-modal.compose-mode .image-preview-container {
    position: relative;
    width: 100%;
    height: 0;
    padding-top: 125%; /* 5/4 = 1.25 → 4:5 portrait */
    margin: 0;
    border: none;
    box-shadow: none;
    border-radius: var(--radius-md);
    overflow: hidden;
    background: #000;
}
#create-modal.compose-mode .image-preview-container img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
#create-modal.compose-mode .remove-image-btn {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: rgba(0,0,0,0.6);
    border: none;
    color: #fff;
    cursor: pointer;
    font-size: 0.85rem;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* … ausser im Update-Compose: dort gibt es das × nicht (NEW-04), das Bild ist
   Pflicht. Muss NACH der Regel darüber stehen — beide haben Spezifität (1,2,0),
   also entscheidet die Reihenfolge, nicht die Absicht. */
#create-modal.compose-update .remove-image-btn { display: none; }

.compose-textarea {
    width: 100%;
    border: none;   /* eingelassen statt umrandet (DS §5) — Inset unten im Endtyp-Block */
    border-radius: var(--radius-md);
    padding: 0.85rem 1rem;
    font-family: inherit;
    font-size: 1rem;
    font-weight: 500;
    color: var(--text-primary);
    background: var(--surface-color);
    resize: vertical;
    box-shadow: none;
    min-height: 90px;
}
.compose-textarea:focus {
    outline: none;
}

/* Pin action icons: plain icon-only buttons, no state — clicking opens the
   corresponding compose-module below. */
.pin-action-tiles {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}
.pin-action-tile {
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(0,0,0,0.05);
    border: none;
    border-radius: 50%;
    cursor: pointer;
    color: var(--text-secondary);
    transition: background 0.12s;
}
.pin-action-tile i { font-size: 1.15rem; }
/* `:active` bleibt: das ist kein Wippen, sondern die gedrückte Fläche eines
   Tiles — und sie fällt weg, sobald der Finger geht. Die `transition` oben
   gehört ihr. */
.pin-action-tile:active { background: rgba(0,0,0,0.12); }

/* Compose modules (Umfrage / Event) — no card wrapper; just a small subhead
   ("Umfrage" / "Event" — like "Vorschläge" / "Meine Boards") with an × on the
   right, followed by the input fields. */
.compose-module.hidden { display: none; }
.compose-module-fields {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

/* Close X on the right of a compose-subhead. */
.compose-subhead-close {
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 0.95rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.2rem 0.4rem;
    margin: -0.2rem -0.4rem;
    border-radius: 999px;
}
.compose-subhead-close:active { background: rgba(0,0,0,0.06); }

/* Generic compose input — Frage der Umfrage, Event-Felder. **Eingelassen ohne
   Border** (DS §5, NEW-01): dieselbe Behandlung wie das Beitrags-Textfeld
   darüber, nur kleiner. Vorher trugen sie eine 1px-Kante und wurden im Fokus
   dunkler — ein Border, den es im System nicht gibt, und ein Zustand, den kein
   anderes Feld der App zeigt. */
#create-modal.compose-mode .compose-input,
#create-modal.compose-mode .compose-module-fields .poll-input {
    width: 100%;
    padding: 0.65rem 0.85rem;
    border: none;
    border-radius: var(--radius-md);
    background: var(--surface-color);
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--text-primary);
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
}
#create-modal.compose-mode .compose-input::placeholder,
#create-modal.compose-mode .compose-module-fields .poll-input::placeholder {
    color: var(--text-muted);
}
/* Kein eigener Fokus-Look: eingelassen ist eingelassen, und der Cursor sagt
   ohnehin, wo man schreibt. */
#create-modal.compose-mode .compose-input:focus,
#create-modal.compose-mode .compose-module-fields .poll-input:focus {
    outline: none;
}

/* Event start/end row — one datetime-local picker per side (start + end). */
.event-datetime-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
}
.event-field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}
.event-field-label {
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
/* datetime-local inputs need explicit width so they don't overflow the grid cell on mobile */
#create-modal.compose-mode .event-field .compose-input { width: 100%; min-width: 0; }

/* Dauer section: custom row shows a datetime-local picker below the dropdown
   when "Datum" is selected. Picker spans full width like the event-fields. */
.duration-custom-row {
    margin-top: 0.5rem;
}
.duration-custom-row.hidden { display: none; }
.duration-custom-row .compose-input { width: 100%; }

/* "Online bis" inline indicator in the section heading — same look as the Reichweite total. */
#pin-duration-until-inline strong {
    color: var(--accent-pink);
    font-weight: 900;
}

/* === Pin post-card variants ===
   .pin-subtitle: two-line subtitle ("hat einen pin gestartet" / "in #tag mit [reach]")
   .pin-preview-line: single-line teaser shown inline in the Updates feed
   .post-actions-pin: reactions LEFT, send+reply RIGHT */
/* Kein Spalten-Layout mehr: der Untertitel ist EIN Satz, der umbricht, wo er
   muss (*2026-08-04 von Jascha*). Vorher zwang ein Flex-Column zwei Zeilen,
   auch wenn beides nebeneinander gepasst hätte. */
.pin-subtitle {
    display: block;
}
.pin-subtitle-line {
    /* `inline` statt `inline-flex`: nur so bricht der Satz mitten drin um.
       Die Abstände zwischen Wort, Board-Pille und Badge machen die Elemente
       selbst (margin), nicht ein `gap` — ein Flex-Container haelt seine Kinder
       in einer Zeile zusammen. */
    display: inline;
    font-size: 0.85rem;
    line-height: 1.7;
    color: var(--text-secondary);
}
/* Im Fliesstext brauchen Pille und Badge ihren Abstand selbst — das `gap` des
   früheren Flex-Containers gibt es nicht mehr.
   BEIDE STEHEN SYMMETRISCH, und zwar auf demselben Wert (*2026-08-04 von
   Jascha*: „zu viel Leerraum zwischen ‚mit' und dem Friends-Symbol"). Das
   Badge brachte von seiner Grundregel 0.4rem nach links mit — dort steht es
   hinter einem NAMEN und braucht den Abstand. Hier steht davor ein Wort mit
   Leerzeichen, und beides zusammen riss die Lücke auf: links 6,4px plus
   Leerzeichen, rechts 2,4px plus Leerzeichen. Die Board-Pille eine Zeile
   weiter machte es die ganze Zeit richtig vor. */
.pin-subtitle-line .reach-badge { margin: 0 0.15rem; }
/* HIER IST DIE PILLE EINE SPUR KLEINER als überall sonst (*2026-08-04 von
   Jascha*: „das #board wirkt grösser als die Reichweiten-Tiles"). Und sie war
   es auch: 19px hoch gegen 16,6px des Badges daneben. Der Grund steckt im
   Inhalt — die Pille trägt Text, das Badge nur Icons auf 0.55rem; gleiche
   Schriftgrösse heisst darum noch lange nicht gleiche Höhe. Nachgemessen und
   ausprobiert, bis die zwei auf denselben 16,6px stehen: 0.65rem und ein
   engeres Polster.
   Nur in dieser Zeile: in den Freundschaftsvorschlägen steht dieselbe Pille
   allein, ohne Badge daneben — dort gibt es nichts anzugleichen, und kleiner
   wäre dort bloss kleiner. */
.pin-subtitle-line .board-tag-pill {
    margin: 0 0.15rem;
    font-size: 0.65rem;
    padding: 0.1rem 0.42rem;
}

/* Pin preview line: single-line teaser with ellipsis, no chevron inline
   (chevron is a card-level absolute child — see .post-card-preview below). */
.pin-preview-line {
    padding: 0.5rem 1rem 0.75rem;
    color: var(--text-primary);
}
.pin-preview-text {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 1.02rem;
    font-weight: 600;
    line-height: 1.3;
}

/* Pin preview card: narrower than full width and horizontally centered so
   it visually "floats" — clearly distinct from regular feed posts. Keeps the
   pink pin glow (inherited from .post-card[data-type="pin"]) since the
   layout difference alone is enough to signal "preview", no chevron needed. */
.post-card[data-type="pin"].post-card-preview {
    max-width: 88%;
    margin: 0.5rem auto 1.25rem;
    border-radius: var(--radius-md);
}

/* Pin full action bar:
     - Emoji-picker on the FAR LEFT (override default margin-left:auto)
     - Reactions-counts stay absolutely centered (inherits the base rule —
       same look as Updates)
     - Send + Reply pushed to the RIGHT via margin-left:auto on Senden */
.post-actions-pin .emoji-btn {
    margin-left: 0;
}
.post-actions-pin > [title="Senden"] {
    margin-left: auto;
}
/* Reaction picker on pins opens on the LEFT (cancel-X is also first there). */
.post-actions-pin .reaction-picker {
    margin-left: 0;
    margin-right: auto;
}

/* Hint badge slot: ensure the injected reach-badge sits inline-flex nicely */
.compose-hint-badge-slot {
    display: inline-flex;
    align-items: center;
}

/* Body scroll-lock when modal or chooser is open — prevents the feed underneath
   from scrolling along when the user scrolls inside the overlay. */
body.scroll-locked {
    overflow: hidden;
}

/* Compose-Hint: force the reach-badge label to always be visible inline (without
   click and without bubble-styling). Global default hides the label and shows
   it as a positioned bubble above on .expanded. */
.compose-hint .reach-badge .reach-label {
    display: inline;
    position: static;
    transform: none;
    background: transparent;
    color: inherit;
    padding: 0;
    border-radius: 0;
}
.compose-hint .reach-badge .reach-label::after {
    display: none;
}

/* Section labels inside compose */
.compose-section h4 {
    font-size: 0.95rem;
    font-weight: 800;
    margin: 0 0 0.6rem;
    color: var(--text-primary);
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
}
.compose-section h4 .reach-total {
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text-secondary);
    letter-spacing: 0;
}
.compose-section h4 .reach-total strong {
    color: var(--accent-pink);
    font-weight: 900;
}
.compose-subhead {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 0.85rem 0 0.5rem;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Das Reach-Segment-Tile im Compose ist dieselbe Komponente wie im Filter-Strip
   (js/components.js → renderReachSegment); die globalen `.reach-segment`-Regeln
   decken beide Kontexte ab. Unterschied ist nur die Fläche: auf der hellgrauen
   Bahn des Strips wechselt das Tile auf Weiss (wie die Chips daneben). */

/* Reach counts: three buckets visualizing how many people you'll reach.
   The bucket up to and including the current reach level is highlighted. */
.reach-counts {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;
    margin-top: 0.85rem;
}
/* Der Zähler unter dem Segment-Tile zeigt seit 2026-08-04 **nur die Zahl** —
   Symbol und Wort stehen im Tile darüber, und jede Zahl sitzt in derselben
   Spalte wie ihr Stopp. Darum flacher und ohne eigene Fläche: sie ist die
   Fussnote zum Tile, kein zweites Bedienelement. */
.reach-count-bucket {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    padding: 0.45rem 0.4rem 0;
    background: transparent;
    border: none;
    border-radius: var(--radius-md);
    transition: border-color 0.15s, background 0.15s;
}
.reach-count-bucket.active .reach-count-number { color: var(--accent-pink); }
.reach-count-bucket.muted { opacity: 0.45; }
/* (`.reach-count-icon` und `.reach-count-label` sind entfallen — das Tile
   darüber trägt Symbol und Wort.) */
.reach-count-number {
    font-size: 1.1rem;
    font-weight: 900;
    color: var(--text-primary);
    line-height: 1;
}

/* Board chips inside compose */
.board-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.board-default-row { margin-bottom: 0.25rem; }
.board-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.4rem 0.75rem;
    background: var(--surface-color);
    border: 1px solid rgba(0,0,0,0.18);
    border-radius: 999px;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--text-primary);
    cursor: pointer;
    user-select: none;
    transition: background 0.15s, border-color 0.15s;
}
.board-chip:active { background: #fafafa; }
.board-chip.active {
    background: var(--accent-pink);
    color: #fff;
    border-color: var(--accent-pink);
}
.board-chip.foreign {
    border-style: dashed;
    color: var(--text-secondary);
}
.board-chip.foreign.active {
    background: var(--accent-pink);
    color: #fff;
    border-style: dashed;
    border-color: var(--accent-pink);
}
.board-legend {
    display: inline-flex;
    align-items: center;
    gap: 0.7rem;
    text-transform: none;
    letter-spacing: 0;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--text-muted);
}
.board-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}
.board-legend-swatch {
    display: inline-block;
    width: 14px;
    height: 10px;
    border-radius: 999px;
    background: var(--surface-color);
}
.board-legend-swatch.joined { border: 1px solid rgba(0,0,0,0.4); }
.board-legend-swatch.foreign { border: 1px dashed rgba(0,0,0,0.4); }

/* Das Feld selbst ist nur noch Text: Fläche, Vertiefung und Radius trägt das
   Tile drumherum (`.compose-field-tile`, NEW-03). */
.board-search-input {
    flex: 1;
    min-width: 0;
    padding: 0;
    border: none;
    background: transparent;
    font-family: inherit;
    font-size: 0.92rem;
    font-weight: 500;
    color: var(--text-primary);
}
.board-search-input::placeholder { color: var(--text-muted); }
.board-search-input:focus { outline: none; }

/* Live board search results: matching boards + a "create new" row, each with
   invented friends/FoF people-counts. */
.board-search-results {
    display: none;
    flex-direction: column;
    gap: 4px;
    margin-top: 0.5rem;
}
.board-search-results.visible { display: flex; }
.board-result {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    width: 100%;
    background: var(--surface-color);
    border: 1px solid rgba(0,0,0,0.1);
    border-radius: var(--radius-md);
    padding: 0.6rem 0.8rem;
    font-family: inherit;
    cursor: pointer;
    text-align: left;
}
.board-result:active { background: #fafafa; }
.board-result-tag {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    font-weight: 700;
    font-size: 0.92rem;
    color: var(--text-primary);
}
.board-result-new {
    font-size: 0.62rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--accent-pink);
    border: 1px solid var(--accent-pink);
    border-radius: 999px;
    padding: 1px 6px;
}
.board-result-meta {
    color: var(--text-muted);
    font-size: 0.76rem;
    font-weight: 600;
    white-space: nowrap;
}
.board-result-meta i { font-size: 0.68rem; }
/* Reach-Symbole in der Meta-Zeile der Board-Suche: klein wie ihr Text. */
.board-result-meta .reach-icons i { font-size: 0.62rem; }
.board-result-pending {
    color: var(--text-muted);
    font-style: italic;
    opacity: 0.7;
}

/* Footer with submit button — color follows category */
.compose-footer {
    flex-shrink: 0;
    padding: 0.85rem 1.25rem;
    padding-bottom: max(0.85rem, env(safe-area-inset-bottom));
    border-top: 1px solid rgba(0,0,0,0.08);
    background: var(--surface-color);
}
.compose-submit-btn {
    width: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.85rem 1rem;
    border: none;
    border-radius: var(--radius-md);
    font-family: inherit;
    font-weight: 800;
    font-size: 1.05rem;
    color: #000;
    cursor: pointer;
    background: var(--accent-yellow); /* default for Update */
    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
    transition: transform 0.1s;
}

/* Category-specific accents */
#create-modal.compose-update .compose-submit-btn {
    background: var(--accent-yellow);
    color: #000;
}
#create-modal.compose-pin .compose-submit-btn {
    background: var(--accent-pink);
    color: #fff;
}

/* Update mode hides pin sections; Pin mode hides location + image preview by default */
#create-modal.compose-update #pin-action-tiles,
#create-modal.compose-update #poll-container,
#create-modal.compose-update #event-container,
#create-modal.compose-update #pin-reach-section,
#create-modal.compose-update #pin-board-section,
#create-modal.compose-update #pin-duration-section {
    display: none;
}
/* Celebration folgt der Kategorie (Klasse setzt app.js). Die Tinte kippt über
   --ink mit: Schwarz auf Gelb, Weiss auf Rosa. */
.celebration-overlay.pin-celebration {
    background: var(--accent-pink);
    --ink: #fff;
}
.celebration-overlay.pin-celebration .celebration-karma-bar {
    background: rgba(255, 255, 255, 0.28);
}
#celebration-icon {
    display: block;
    font-size: 3.5rem;
    margin-bottom: 0.75rem;
    color: var(--ink);
    -webkit-text-stroke: 0;
}
#celebration-title { color: var(--ink); }

/* Location pill (Update-only) with three states:
     .state-pill    → location set, tappable to edit, X to remove
     .state-editing → input field + confirm button
     .state-empty   → "+ Standort hinzufügen" button
   JS toggles the state-* classes; CSS shows the relevant children for each. */
/* Standort- und Board-Such-Feld sind derselbe Griff (DS §5, NEW-03): ein
   graues Container-Tile mit dem Icon darauf und dem eingelassenen Feld darin —
   so hebt es sich vom weissen Beitrags-Textfeld ab. Gebaut wie die Chat-Suche:
   EIN graues Tile, das die Vertiefung trägt, Icon in Meta-Grau, Feld
   transparent darin. Zwei geschachtelte Flächen wären hier eine zu viel — ein
   weisses Feld auf grauem Grund läse sich als erhaben, nicht als eingelassen.
   Das Emoji 📍 ist dabei dem Standort-Icon aus dem Set gewichen (DS §9). */
.compose-field-tile {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    width: 100%;
    padding: 0.65rem 0.9rem;
    border-radius: var(--radius-md);
    background: var(--surface-light);
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
}
.compose-field-icon {
    width: 1.05rem;
    height: 1.05rem;
    flex-shrink: 0;
    color: var(--text-muted);
}
.board-search-tile { margin-top: 0.85rem; }

.compose-location-pill {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 0.55rem;
    padding: 0.65rem 0.9rem;
    background: var(--surface-light);
    border: none;
    border-radius: var(--radius-md);
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--text-primary);
    width: 100%; /* full width so the × sits at the right edge — thumb-reachable */
}
.compose-location-pill .location-icon {
    width: 1.05rem;
    height: 1.05rem;
    flex-shrink: 0;
    color: var(--text-muted);
}
/* Right-align the action button in each state (× / ✓) */
.compose-location-pill .location-remove,
.compose-location-pill .location-confirm {
    margin-left: auto;
}

/* Hide all children by default — state classes reveal the relevant ones. */
.compose-location-pill > *,
.compose-location-pill .location-icon-editing {
    display: none;
}

/* State: pill (default with location set) */
.compose-location-pill.state-pill .location-edit-target,
.compose-location-pill.state-pill .location-remove {
    display: inline-flex;
}

/* State: editing */
.compose-location-pill.state-editing .location-icon-editing,
.compose-location-pill.state-editing .location-input,
.compose-location-pill.state-editing .location-confirm {
    display: inline-flex;
}

/* State: empty (after remove) */
.compose-location-pill.state-empty .location-add {
    display: inline-flex;
}

/* Sub-element styling */
.compose-location-pill .location-edit-target {
    background: transparent;
    border: none;
    padding: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
    align-items: center;
    gap: 0.4rem;
}
.compose-location-pill .location-edit-target:active { opacity: 0.6; }

.compose-location-pill .location-remove,
.compose-location-pill .location-confirm {
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.4rem;
    border-radius: 999px;
    align-items: center;
}
.compose-location-pill .location-remove:active,
.compose-location-pill .location-confirm:active { background: rgba(0,0,0,0.05); }
.compose-location-pill .location-confirm { color: var(--accent-pink); }

.compose-location-pill .location-input {
    background: transparent;
    border: none;
    outline: none;
    font: inherit;
    color: inherit;
    min-width: 6rem;
    flex: 1;
}

.compose-location-pill .location-add {
    background: transparent;
    border: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
    align-items: center;
    gap: 0.4rem;
    padding: 0;
}
.compose-location-pill .location-add i {
    color: var(--text-secondary);
    font-size: 0.85rem;
}
.compose-location-pill.state-empty {
    color: var(--text-secondary);
}

/* Hint paragraph below textarea (Update-only): centered, info-icon + inline friend-badge */
.compose-hint {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--text-secondary);
    margin: -0.25rem 0 0 0;
    text-align: center;
}
.compose-hint .compose-hint-icon {
    color: var(--text-muted);
    font-size: 0.95rem;
}
.compose-hint .inline-reach-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    background: rgba(0,0,0,0.06);
    font-weight: 800;
    color: var(--text-primary);
}
.compose-hint .inline-reach-badge i { font-size: 0.78rem; }

/* In Pin mode, hide the Update-specific bits */
#create-modal.compose-pin .compose-location-pill,
#create-modal.compose-pin .compose-hint {
    display: none;
}


/* ===========================================================================
   BEVEL-STANDARD — eingebacken aus designs/007f-3d-d-bevel (neuer Default-Look).
   Helle Cards, leichter Schatten + Bevel-Kante, farbiger Typ-Top-Rand, Glow entfernt.
   Brand-Farben/Semantik unveraendert. Alte Glow-Optik liegt im _archive/.
   =========================================================================== */
/* 007m — "3D · D Bevel"  (Basis 007f, 3D NUR auf Card-Ebene)
   Schlagschatten + heller Innen-Highlight an der Oberkante + zarter Innen-Schatten
   unten → angehobener, glänzender Rand (Bevel). Rest flach.
   (Der frühere `:root`-Block dieses Abschnitts ist mit PRIO-2 in den einen
   Token-Block ganz oben gewandert — Werte dort, nicht mehr hier.) */
body, #app { background: var(--bg-color); }
/* Diese spaetere Regel gewinnt gegen den Header-Block oben — die Flaeche muss
   darum auch hier --bg-color sein (Chrome, kein schwebendes Panel, DS §8).
   Im Hellen war der Unterschied keiner, beide Tokens sind Weiss. */
.app-header { background: var(--bg-color); box-shadow: 0 4px 14px rgba(var(--shadow-rgb), 0.07); border-bottom: none; }
.feed { gap: 1.6rem; }

/* === 3D: Card-Ebene — Bevel / Licht-Rim === */
.post-card { background: var(--surface-color); border: none; border-radius: var(--radius-lg); margin: 0 14px; overflow: hidden; box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.14), inset 0 1px 0 var(--bevel-light), inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07); }
/* Post-Bilder bleiben ECKIG und ohne Kantenbehandlung (FEED-07): die Card ist
   gerundet, das Bild darin nicht — so liest es sich als Fläche IN der Card statt
   als zweite, kleinere Card. Ausnahme mit eigener Begründung: die Kacheln im
   Updates-Raster des Profils sind leicht gerundet, weil sie in einer gerundeten
   Card sitzen (PROF-01). */
.post-image { border-radius: 0; }
/* Top-Akzent = CARD-OFFSET 10px (DESIGN_SYSTEM.md §4). Statt `border-top` liegt
   eine deckungsgleiche Farbfläche 10px höher: `0 -11px 0 -1px <farbe>` — 11px
   nach oben, 1px Spread negativ, damit die Seiten eingezogen sind und kein
   AA-Faden neben der Card steht. Ergebnis liest sich als „Card auf Card"; zu den
   Ecken hin wird der sichtbare Rand schmaler (border-top fächert dort auf, ein
   konzentrischer Ring wirkt wie eine Umrandung — beide verglichen, siehe §4).
   Der Bevel unten bleibt, das Licht-Rim oben entfällt: dort sitzt jetzt Farbe. */
.post-card[data-type="update"] {
    border-top: none;
    box-shadow:
        0 -11px 0 -1px var(--accent-yellow),
        0 12px 28px rgba(var(--shadow-rgb), 0.14),
        inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
}
.post-card[data-type="pin"] {
    border-top: none;
    box-shadow:
        0 -11px 0 -1px var(--accent-pink),
        0 12px 28px rgba(var(--shadow-rgb), 0.14),
        inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
}
.people-card--pink, .inspiration-pin-card { background: var(--surface-color); border: none; border-radius: var(--radius-lg); border-top: none; box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.14), inset 0 1px 0 var(--bevel-light), inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07) !important; }

/* === Rest flach === */
.people-section-title { color: var(--text-primary); }
.people-subsection-title, .people-subsection-title--clickable { color: var(--text-secondary); }
.bottom-nav { background: var(--surface-raised); border-top: none; box-shadow: 0 -4px 14px rgba(var(--shadow-rgb), 0.07); }
.filter-chip, .board-chip, .board-result { border-radius: var(--radius-md); border: none; background: var(--surface-light); box-shadow: inset 0 1px 3px rgba(var(--shadow-rgb), 0.12), 0 1px 1px var(--bevel-light); }
/* (Die frühere Fläche+Vertiefung an `.board-search-input` ist mit NEW-03 an das
   Tile `.compose-field-tile` gewandert — das Feld darin ist nur noch Text.) */
#post-input, .post-input { border-radius: var(--radius-md); }
.share-btn { border-radius: var(--radius-md); box-shadow: 0 8px 18px rgba(255, 0, 102, 0.24); }
.filter-strip-updates, .filter-strip-pins, .section-detail-list, .sort-pill, .sort-direction-pill { box-shadow: none !important; }


/* ===========================================================================
   DESIGN-SYSTEM · Säule 1: Elevation  (eingebacken aus explorations/elevation, DS2)
   Siehe DESIGN_SYSTEM.md. Ein höchstes Weiss für alle Flächen; Tiefe stapelt nicht.
   (Auch dieser `:root`-Block ist mit PRIO-2 nach oben gewandert.)
   =========================================================================== */

/* Chrome: Header flach; Bottom-Nav schwebend MIT Schatten (DS §8 Ausnahme). */
.app-header { box-shadow: none; }
.bottom-nav { box-shadow: 0 12px 30px rgba(var(--shadow-rgb), 0.18); }

/* Aktivierbare Tiles: weiss + globaler Border + Bevel (inaktiv) / inset+Farbe (aktiv) */
.filter-chip, .board-chip,
.filter-strip .filter-chip:not(.active):not(.default) {
    border: 1px solid rgba(0, 0, 0, 0.12);
    background: var(--surface-color);
    box-shadow: inset 0 1px 0 var(--bevel-light), inset 0 -2px 4px rgba(var(--shadow-rgb), 0.20);
}
.filter-chip.active, .board-chip.active { box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.34); }
/* Ausnahme: „+ Neue Liste" ist kein Tile mit Zustand, sondern eine Aktion —
   also die Optik der Sortier-Pillen: weiss, flach, schwarze fette Schrift
   (DS §7).
   ACHTUNG Spezifität: die Tile-Regel darüber trägt `:not(.active):not(.default)`,
   und `:not()` zählt die Spezifität seines Arguments MIT — sie steht damit bei
   (0,4,0). Ein Opt-out mit `.filter-strip .filter-chip.filter-add` (0,3,0)
   verliert, egal wie weit unten er steht. Darum hier dieselben `:not()`
   dazu. */
.filter-strip .filter-chip.filter-add:not(.active):not(.default),
.filter-chip.filter-add {
    background: var(--surface-color);
    border: none;
    box-shadow: none;
    color: var(--text-primary);
}

/* Textfelder eingelassen (das Board-Such-Feld über sein Tile, NEW-03) */
#post-input, .post-input { border: none; box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20); }

/* Inhalt/Aktionen flach (Ebene 2) */
.poll-option-btn { background: var(--surface-light); border: none; box-shadow: none; }
.poll-option-btn:active { box-shadow: none; }
.share-btn { border: none; box-shadow: none; }
.post-actions .action-btn { border: none; box-shadow: none; }
.remove-image-btn, .close-btn { box-shadow: none; }

/* ===========================================================================
   FEED-03 · Full-Bleed Post-Detailseite (Updates UND Pins)
   Fixe Ebene unter dem Header (z 90 < Header/Nav 100), heller Canvas, eigener
   Scroll-Container. Kontext aus dem globalen Detail-Header (Back + Titel).
   =========================================================================== */
.post-detail-view {
    position: fixed;
    top: var(--header-h, 110px);
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 90;
    background: var(--surface-color);   /* alles auf Weiss — kein grauer Canvas */
    display: flex;
    flex-direction: column;             /* Scroll-Bereich oben, Composer docked unten */
}
.post-detail-view.hidden { display: none; }
.post-detail-scroll {
    flex: 1 1 auto;
    min-height: 0;                      /* erlaubt dem Flex-Child zu scrollen */
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-y: contain;
}
.post-detail-content {
    max-width: 600px;
    margin: 0 auto;
    padding: 0 0 24px;
}

/* Comment-Composer (DS §5: inset-Feld, kein Border; kein Sendebutton —
   Enter sendet). GIF-Button ist nur ein Teaser (Toast). Ebene-3-Leiste:
   weisse Fläche, dezente Hairline nach oben, kein Bevel. Genutzt von der
   Detailseite (docked) UND vom Kommentar-Bottom-Sheet (FEED-02). */
.comment-composer {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.7rem 1rem;
    padding-bottom: calc(0.7rem + env(safe-area-inset-bottom, 0px));
    background: var(--surface-color);
    border-top: 1px solid var(--hairline);
}
.composer-gif-btn {
    flex: 0 0 auto;
    font-family: inherit;
    font-weight: 800;
    font-size: 0.78rem;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
    background: var(--surface-light);
    border: none;
    border-radius: var(--radius-md);
    padding: 0.55rem 0.7rem;
    cursor: pointer;
}
.composer-input {
    flex: 1 1 auto;
    min-width: 0;
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--text-primary);
    background: var(--surface-light);
    border: none;
    border-radius: var(--radius-full);
    padding: 0.65rem 1rem;
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
}
.composer-input::placeholder { color: var(--text-muted); }
.composer-input:focus { outline: none; }

/* Detailseite zeigt KEIN Tile: die Card-Optik (Bevel, Schatten, Radius, Seiten-
   abstand, Farbstreifen) fällt weg — der Post sitzt full-bleed auf Weiss. */
.post-detail-view .post-card {
    margin: 0;
    border-radius: 0;
    border-top: none;
    background: transparent;
    box-shadow: none;
}

/* Kopfzeile in den Fullscreen-Ansichten (FEED-10):
   · **Einzelner Beitrag** → der ⋮-Griff sitzt im Header, nicht auf der Card;
     zwei Ausgänge für dieselbe Sache wären einer zu viel.
   · **Sammlung** (Profil-Updates) → dort stehen mehrere Beiträge, ein Griff im
     Header könnte gar nicht sagen, welchen er meint. Er bleibt an der Card, in
     der Gruppe mit Zeit und Typ-Marker.
   Das Profilbild behält seine Feed-Grösse und liegt **vertikal mittig** auf
   derselben Achse wie Zeitangabe und Typ-Marker — bei jeder Post-Art. Das ist
   nicht selbstverständlich: bei Pins baut der linke Block (Name + zweizeiliger
   Untertitel) höher als der rechte, und beide Seiten hängen an derselben
   Regel. Sie steht hier ausdrücklich, damit niemand versehentlich wieder ein
   `flex-start` an die Kopfzeile schreibt. */
.post-detail-view .post-header,
.post-detail-view .user-info { align-items: center; }
.post-detail-view .post-header { padding: 1rem 1.1rem; }

/* Floating-Nav ausblenden, solange die Detailseite offen ist (Back via Header). */
/* Jede fullscreen Ansicht blendet die schwebende Nav aus — Post-Detailseite,
   Fremdprofil, Profil-Listen, Update-Sammlung UND die Profil-Unterseiten
   („Fragen hinzufügen", Level). Sie decken den ganzen Screen inkl. Header
   (CLAUDE.md → Detail-/Sub-Ansicht-Regel); eine Nav darüber wäre ein Ausgang,
   den es dort nicht gibt — zurück geht es über den Back-Button. */
body.fullscreen-open .bottom-nav { display: none; }

/* Kommentare unter dem Post — gleiche weisse Fläche, dezente Trennlinie zum Post. */
.post-detail-comments {
    margin-top: 0.5rem;
    padding: 1rem 1rem 0;
}
.post-detail-comments-title {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 1.05rem;
    font-weight: 800;
    color: var(--text-primary);
    margin-bottom: 0.5rem;
}
.post-detail-comments-count {
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--text-muted);
}
/* (Die frühere Kommentar-Hairline ist am 2026-07-30 app-weit entfallen — die
   Regel steht jetzt bei `.comment-item` weiter oben.) */

/* Detail-Mode: die Home-Rubrik-Tabs (Map/Updates/Pins) ausblenden — der
   Header zeigt dann nur Back + Titel. !important schlägt das von switchMainView
   gesetzte inline display:flex. */
.app-header.detail-mode .tabs-container { display: none !important; }

/* ===========================================================================
   FEED-02 · Bottom-Sheet-Primitiv (Kommentar-Sheet + ⋯-Menü FEED-10)
   Ebene 3: Schlagschatten, kein Bevel, kein Border. Slide-in von unten.
   =========================================================================== */
.sheet-backdrop {
    position: fixed;
    inset: 0;
    z-index: 390;
    background: rgba(var(--shadow-rgb), 0.38);
    opacity: 0;
    transition: opacity 0.25s ease;
}
.sheet-backdrop.open { opacity: 1; }
.sheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 400;
    max-width: 600px;
    margin: 0 auto;
    max-height: 85vh;
    display: flex;
    flex-direction: column;
    background: var(--surface-color);
    border-radius: 20px 20px 0 0;
    box-shadow: 0 -12px 30px rgba(var(--shadow-rgb), 0.20);
    transform: translateY(100%);
    transition: transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.sheet.open { transform: translateY(0); }
.sheet-handle {
    flex: 0 0 auto;
    width: 40px;
    height: 5px;
    border-radius: 3px;
    background: var(--hairline);
    margin: 0.6rem auto 0.1rem;
}
.sheet-header {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.4rem 1rem 0.6rem;
}
.sheet-title { margin: 0; font-size: 1.05rem; font-weight: 800; color: var(--text-primary); }
/* Die Zahl neben dem Titel (Reaktionen 8) — Meta, nicht Titel: dieselbe
   Behandlung wie der Zähler an „Kommentare 2" auf der Detailseite. */
.sheet-title-count { font-size: 0.85rem; font-weight: 700; color: var(--text-muted); margin-left: 0.35rem; }
.sheet-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0 1rem;
}
/* Im Kommentar-Sheet sitzt der Composer als Footer (eigene Hairline/Padding). */
.sheet-comments .sheet-body { padding: 0 1rem 0.25rem; }
.sheet-footer { flex: 0 0 auto; }

/* Menü-Sheet (FEED-10): eine Zeile pro Aktion, flach auf dem Panel (DS §3b —
   Inhalt auf einem Panel trägt kein eigenes 3D). Destruktiv-Rot nur beim
   Löschen; Melden bleibt in Textfarbe, es ist kein Angriff auf die eigenen
   Daten. */
.sheet-menu .sheet-body { padding-bottom: 0.75rem; }
.sheet-action {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.9rem 0.25rem;
    border: none;
    background: none;
    font-family: inherit;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-primary);
    text-align: left;
    cursor: pointer;
}
.sheet-action .ic { width: 1.25rem; height: 1.25rem; }
.sheet-action.is-danger { color: var(--danger); }

/* ==========================================================================
   PROFIL — Struktur (PROF-02 / PRIO-3b)
   Kopfbereich ohne Bearbeiten-Button + drei Rubriken (Über mich / Highlights /
   Pins) mit Aktions-Zeile. Steht am Dateiende, damit es die älteren
   .profile-*-Regeln überschreibt. Endgültige Optik: PROF-01 (PRIO-3g) —
   die alten Brutalism-Regeln oben sind hier bewusst noch nicht angefasst.
   ========================================================================== */

/* EINE Achse fürs ganze Profil.
   Wert = 14px, weil die `.post-card` ein festes `margin: 0 14px` trägt und in
   der Pins-Rubrik die Kante vorgibt. Der Rest stand auf 1rem (16px) — die 2px
   Differenz war als schiefe Kante zwischen Boards-Button und Pin-Cards sichtbar.
   Die Card ist global und kann nicht wandern, also folgt ihr das Profil.
   Bei Pillen zählt die Kante der GEFÜLLTEN Pille: im aktiven Zustand ist die
   Fläche das, was man sieht, und die fluchtet mit Bild, Name und Bio. Die
   Beschriftung steht dadurch um das Innen-Padding weiter innen — Variante mit
   Label auf der Achse wurde probiert und verworfen (2026-07-30). */
#profile-content,
#person-content {
    --profile-axis: 14px;
}

/* LINKSBÜNDIGER KOPFBEREICH (Instagram-Achse).
   Alles im Profil-Kopf teilt eine Kante links bei 1rem: Avatar, Name, und auch
   die Rubrik-Reihe. Der Header trägt den Handle (@alexb) links — die Achse
   setzt sich also im Content fort, statt dagegen zu arbeiten. Zentriert wirkte
   der Kopf wie eine Visitenkarte; linksbündig liest er sich als Seitenanfang.

   Der Avatar ist NICHT mehr absolut im Cover positioniert, sondern erstes Kind
   einer Flex-Zeile. Dadurch kann rechts von ihm etwas stehen (Level + Buttons),
   und zwar zuverlässig auf Weiss: die Zeile wird nur so weit ins Banner
   gezogen, dass der Avatar es überlappt, die Meta-Spalte aber unterhalb der
   Bannerkante bleibt (align-items: flex-end). Text auf dem Bannerfoto wäre
   schlecht lesbar. */
.profile-banner { height: 132px; }

.profile-identity {
    align-items: stretch;
    text-align: left;
    padding: 0 0 0.2rem;
    gap: 0;
}

/* Zwei Spalten: Bild links, Name + Navigation rechts, vertikal aufs Bild
   gemittet. Kein Überlappen mit dem Banner — das Bild sitzt komplett darunter,
   damit das Banner für sich allein steht. */
.profile-head {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 0.9rem;
    align-items: center;
    padding: 0 var(--profile-axis);
    margin-top: 0.85rem;
}
.profile-avatar-wrap {
    position: relative;
    flex: 0 0 auto;
    width: 88px;
    height: 88px;
}
.profile-avatar-wrap > .profile-avatar-img {
    position: static;
    width: 100%;
    height: 100%;
    background: var(--surface-color);
}
/* Das `Lv`-Tile am Bild ist mit der klickbaren Karma-Zeile gefallen
   (2026-08-02): das Level stand damit zweimal im selben Kopfbereich. Es lebt
   jetzt nur noch in der Zeile neben dem Namen (`.profile-karma`), die es
   zusammen mit dem Karma-Stand trägt und die Level-Ansicht öffnet. */
/* Rechte Spalte: Name über den zwei Buttons, vertikal auf das Bild gemittet. */
.profile-head-meta {
    grid-column: 2;
    grid-row: 1;
    align-self: center;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
    min-width: 0;
}

/* Level + Karma-Stand — bewusst DEZENT (CLAUDE.md → Karma-System: „eine Zeile
   zusammen mit dem Karma-Stand"). Meta-Grau wie Ort und Zeit auf einer Card,
   kein eigener Rahmen: die Zahl ist Auskunft, kein Abzeichen. Der Zähler
   `<b>` trägt nur das Gewicht, damit beim Hochlaufen (KARMA-02) die Zahl das
   Auge hält und nicht die Wörter. Der Abstand nach unten ist kleiner als der
   Zeilen-Gap: die Zeile gehört zum Namen darüber, nicht zu den Buttons. */
.profile-karma {
    margin-top: -0.25rem;
    color: var(--text-muted);
    font-size: 0.8rem;
    font-weight: 600;
    /* Als Button: flach, ohne Fläche und ohne Rahmen — sie sieht aus wie die
       Meta-Zeile, die sie ist, und der Pfeil zeigt, dass es weitergeht. */
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    text-align: left;
}
.profile-karma b { font-weight: 800; }
.profile-karma-chevron {
    width: 1em;
    height: 1em;
    margin-left: 0.1rem;
}
.profile-karma:active { opacity: 0.6; }
/* Fremdes Profil: dieselbe Zeile, aber ohne Griff. */
.profile-karma--static { cursor: default; }

/* Bio (Instagram-artig): über die GANZE Breite unterhalb des Kopfbereichs —
   nicht in der rechten Spalte, wo sie neben dem Bild eingeklemmt wäre. */
.profile-bio {
    margin: 0.8rem 0 0;
    padding: 0 var(--profile-axis);
    font-size: 0.9rem;
    line-height: 1.45;
    color: var(--text-primary);
}

/* Deutlich kleiner als vorher: der Name ist hier nicht die Schlagzeile der
   Seite — das Bild trägt die Identität, und der Handle steht im Header. */
.profile-identity .profile-name {
    margin: 0;
    padding: 0;
    font-size: 1.05rem;
    line-height: 1.2;
    text-align: left;
}

/* ===== EIN Look für alle kleinen Aktions-Pillen ==========================
   Friends/Boards · Bearbeiten/Fertig/Hinzufügen in den Rubriken · die Aktionen
   auf fremden Profilen · „Profil bearbeiten" auf dem Banner. Werte = die kleine
   Pille aus DESIGN_SYSTEM.md §7 (Referenz `.board-action`), damit nicht
   jede Stelle ihr eigenes Format erfindet.
   Grundzustand ist NEUTRAL: ruhige Fläche, Text in Primärfarbe, Icon erbt die
   Textfarbe. Eine Brand-Farbe bekommt nur, was wirklich die eine Haupt-Aktion
   des Screens ist (→ .person-action--primary).

   DS-06 — „Hinzufügen"-Affordanzen sind FLACH, nicht gestrichelt. Drei Gründe:
     1. Das Regelwerk sagt es schon: DS §6 reserviert die Tile-/Chip-Optik für
        Controls MIT An/Aus-Zustand, DS §7 macht reine Aktionen flach.
        „Fragen hinzufügen" hat keinen Zustand — es öffnet eine Seite.
     2. Gestrichelt ist in dieser App bereits vergeben: im Pin-Compose heisst
        eine gestrichelte Kante „fremdes Board, dem du nicht folgst"
        (`.board-chip.foreign`), samt eigener Legende darunter. Dieselbe
        Strichart ein zweites Mal für „das fügt etwas hinzu" hiesse: eine
        Linienart, zwei Bedeutungen.
     3. Dieselbe Pille trägt schon der Filter-Status im Feed (FEED-14) — und
        zwar aus derselben Regel, damit die beiden nicht auseinanderdriften.
   Ausnahme mit Grund: `.unlock-add-btn` bleibt türkis. Es ist die eine
   HAUPT-Aktion seiner eigenen Seite, nicht eine Affordanz am Rand. */
.profile-meta-btn,
.rubric-action-btn,
.person-action,
/* Der Filter-Status im Feed (FEED-14) trägt dieselbe Pille: beide sind
   dauerhaft verfügbare Affordanzen, keine Inhalte — eine Optik, eine Regel. */
.filter-status-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.4rem 0.8rem;
    border: none;
    border-radius: var(--radius-md);
    background: var(--surface-light);
    /* Sekundärfarbe, nicht Primär: DESIGN_SYSTEM §2 definiert --text-secondary
       genau dafür („bewusst sekundär: abbrechen-Buttons, Sub-/Sektions-Labels").
       Diese Pillen sind dauerhaft verfügbare Affordanzen, nicht Inhalt — in
       Tiefschwarz konkurrieren sie mit Namen und Beiträgen.
       Schwarz bleibt dem AKTIVEN Zustand vorbehalten (siehe .is-done). */
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 0.85rem;
    /* Bewusst NICHT fett (DS §7 nennt 800 für die kleine Pille): im Profil-Kopf
       stehen mehrere dieser Pillen nebeneinander, und fett gesetzt schreien sie
       lauter als der Name. 600 hält sie lesbar, aber ruhig. */
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
}
.profile-meta-btn:active,
.rubric-action-btn:active,
.person-action:active,

.profile-meta-btn .ic,
.rubric-action-btn .ic,
.person-action .ic,
.filter-status-pill .ic { width: 1.05em; height: 1.05em; color: currentColor; }

/* FEED-16 — die Filter-Status-Pille darf umbrechen, die drei anderen nicht.
   In der weitesten Stellung steht dort „Reichweite: Friends & Friends of
   Friends · 2 Listen aktiv": gemessen 415px auf einem 375px-Schirm, die Pille
   ragte links und rechts je 20px hinaus.
   Steht hier UNTEN und nicht oben bei den anderen Filter-Status-Regeln (~2680),
   weil `white-space: nowrap` aus der geteilten Regel direkt darüber kommt —
   gleiche Spezifität, also gewinnt die spätere. Ein Umbruch weiter oben wäre
   still wirkungslos geblieben.
   Der Pfeil braucht nichts dazu: `align-items: center` steht schon in der
   geteilten Regel und setzt ihn damit von selbst vertikal mittig zwischen die
   zwei Zeilen. `flex: none` hält ihn nur davon ab, selbst schmaler zu werden,
   wenn der Text drückt.
   NUR diese Pille: `.profile-meta-btn`, `.rubric-action-btn` und
   `.person-action` teilen die Optik, aber im Profil-Kopf stehen mehrere
   nebeneinander — dort ist einzeilig richtig. */
.filter-status-pill {
    white-space: normal;
    max-width: 100%;
    text-align: center;
}
.filter-status-pill .ic { flex: none; }

/* ZUERST ZWISCHEN DEN TEILEN BRECHEN, NICHT IRGENDWO IM SATZ (*2026-08-04 von
   Jascha*). Die Zeile besteht aus zwei Aussagen — der Reichweite und der Zahl
   der Listen —, und wenn sie nicht nebeneinander passen, gehört der Bruch
   genau dazwischen. Vorher entschied der Browser nach Wörtern und brach mitten
   in „Friends of Friends · 2 | Listen aktiv", mit dem Trennpunkt am
   Zeilenende, wo er nichts mehr trennt.

   Darum ist der Text kein String mehr, sondern ein Span je Teil (`feed.js`,
   `filterStatusParts`) — und diese Spans sind Flex-Items: sie passen
   nebeneinander oder gehen komplett auf die nächste Zeile. **Den Trennpunkt
   braucht es damit nicht mehr**; die Lücke bzw. der Zeilenwechsel trennt.

   `min-width: 0` ist der Rückfall: ohne die Zeile bestünde ein Flex-Item auf
   seiner vollen Breite und ein einzelner zu langer Teil ragte wieder hinaus.
   Mit ihr bricht ein Teil, der allein nicht passt, ganz normal nach Wörtern —
   also erst zwischen den Teilen, und wenn das nicht reicht, wie zuvor. */
.filter-status-text {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    min-width: 0;
}
.filter-status-part { min-width: 0; }

/* Auf EINER Zeile trennt ein Punkt, auf zwei Zeilen der Zeilenwechsel
   (*2026-08-04 von Jascha*). Welcher Fall vorliegt, misst `feed.js`
   (`syncFilterStatusSeparator`) und setzt `.has-separator` — CSS kann einen
   Umbruch nicht abfragen.
   Punkt und Abstand ergeben zusammen wieder das gewohnte „ · ": 0.4rem vor dem
   Zeichen (als Rand am Pseudo-Element), 0.4rem danach (als Spalten-Lücke). */
.filter-status-text.has-separator { column-gap: 0.4rem; }
.filter-status-text.has-separator .filter-status-part:not(:last-child)::after {
    content: '·';
    margin-left: 0.4rem;
}
/* Umgebrochen steht kein Zeichen dazwischen — dann darf die Lücke auch grösser
   sein, sie wirkt ohnehin nur, falls doch einmal zwei Teile nebeneinander
   stehen, ohne dass die Messung gelaufen ist (defensiv, nicht der Normalfall). */
.filter-status-text:not(.has-separator) { column-gap: 1rem; }

/* Der Friends-Button trägt DIESELBEN Personen-Icons wie das Reach-Badge im
   Feed (erstes Glyph = „ich", das zweite gefüllt) statt eines Profil-Symbols:
   der Knopf führt zu den Friends, nicht zu einem Profil, und Freundschaft hat
   in dieser App genau ein Zeichen. Hier grösser gesetzt als im Badge — dort
   sitzt es als Stempel im Fliesstext, hier steht es allein neben einem Wort. */
.profile-meta-btn .reach-icons i { font-size: 0.72rem; }

/* (Friends / Boards werden weiter unten behandelt — sie waren schon umrahmt
   statt gefüllt, siehe dort. Hier stand kurzzeitig eine zweite Fassung.) */

/* Friends / Boards — bewusst OHNE Zähler und ohne Brand-Ton. Sie stehen rechts
   neben dem Avatar, unter dem Namen — dort, wo bei Instagram die Zahlen sitzen.
   Nur eben ohne Zahlen. Sie füllen die Spalte bis zum rechten Seitenrand. */
/* `align-self: stretch` ist nötig, weil die Elternspalte `align-items:
   flex-start` setzt — ohne das bleibt die Zeile so breit wie ihr Inhalt und
   klebt links. */
.profile-meta-actions {
    align-self: stretch;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
}
/* Friends / Boards sind NAVIGATION, nicht Aktion: **umrahmt statt gefüllt**.
   Damit wird eine Regel lesbar, die die App schon benutzt — der `#tag`-Chip auf
   Pin-Cards und das `Lv`-Tile am Profilbild sind ebenfalls Rahmen ohne Fläche:
   **Rahmen = Marke/Navigation, Fläche = Aktion.** Im Kopfbereich steht damit gar
   keine gefüllte Fläche mehr — „Profil bearbeiten" ist in den Header gewandert.
   Rahmenwert = `1.5px var(--text-muted)` — der Wert der Familie „umrahmte
   Marke/Navigation" (DS §1), denselben tragen das `Lv`-Tile am Profilbild und
   das Reach-Badge. NICHT der Tile-Hairline aus §6: der ist für Filter-Chips
   gedacht, die in Reihen liegen und sich nur vom Untergrund lösen müssen.
   Hier stand er einmal (1px rgba(0,0,0,.12) ≈ 1.32:1) — direkt neben dem
   `Lv`-Tile mit 4.54:1 sah das aus wie ein Fehler. */
.profile-meta-btn {
    /* Beide teilen die Breite der Spalte, also bis zum rechten Seitenrand. */
    flex: 1 1 0;
    min-width: 0;
    padding: 0.3rem 0.7rem;
    background: none;
    /* Hairline, NICHT der Muted-Rahmen der kleinen Stempel (`Lv`-Tile,
       Reach-Badge). Wahrgenommen wird Fläche mal Kante: ein Stempel von zwei
       Zentimetern Umfang braucht eine kräftige Linie, um überhaupt als umrahmt
       zu gelten — ein Button über die halbe Bildschirmbreite ist schon durch
       seine Form als Fläche lesbar, dort genügt die Andeutung. Am Bild
       verglichen (2026-07-30): mit Muted konkurrieren die beiden mit Name und
       Bio, mit Hairline treten sie zurück. Sie sind Wege weg von hier, nicht
       der Inhalt der Seite. */
    border: 1px solid var(--hairline);
    color: var(--text-secondary);
}

/* Rubrik-Reihe: drei Bereiche, feste Reihenfolge, aktiver als Pill in der
   Rubrikfarbe (Kontrastregel DS §2: schwarz auf Türkis/Gelb, weiss auf Rosa).
   Eigene Klassen statt .tab-btn — die Reihe sitzt im Content, nicht im Header. */
/* Bleibt beim Scrollen unter dem Header stehen — die Rubrik-Auswahl muss
   erreichbar bleiben, ohne zurück nach oben zu scrollen. `--header-h` wird in
   applyProfileTop auf die aktuelle Header-Höhe gesetzt.

   Abgetrennt wird OHNE Linie, nur über Weissraum: der Abstand nach oben ist
   deutlich grösser als der nach unten, damit die Reihe zum Inhalt darunter
   gehört und nicht zum Profil-Kopf. Das trägt allein über die Proportion —
   eine Hairline wäre der stärkere, aber lautere Weg (DS §1: keine Borders). */
.profile-rubric-nav {
    position: sticky;
    top: var(--header-h, 110px);
    z-index: 40;
    display: flex;
    /* Über die ganze Breite verteilt, aber die Pillen bleiben so breit wie ihr
       Wort — genau wie die Rubrik-Tabs im Home-Header. `space-between` gibt
       gleiche Abstände zwischen den drei Tabs und hält die äusseren auf der
       Seitenachse. */
    justify-content: space-between;
    margin-top: 1.1rem;
    /* Innenabstand oben bewusst nicht minimal: im geklebten Zustand scrollt der
       Inhalt darunter durch, und ohne diese Luft stösst er an die Pills.
       Seitlich 1rem = dieselbe Achse wie Bild, Name und Bio. */
    padding: 0.6rem var(--profile-axis) 0.85rem;
    /* DIESELBE FLÄCHE WIE DER KOPFBEREICH DARÜBER (*2026-08-05 von Jascha*).
       Hier stand `--bg-color`; im Hellen war das derselbe Wert wie
       `--surface-color`, im Dunkeln ist es der dunklere Canvas — die Reihe
       lag dann als dunkler Balken zwischen Bio und Fragen. Sie gehört zum
       Kopfbereich, also trägt sie dessen Fläche; abgesetzt wird sie über
       Weissraum, nicht über einen Farbwechsel (DS §1). */
    background: var(--surface-color);
}
/* Die Pille umschliesst nur ihr Wort (wie im Home-Header) — kein Segment, das
   ein Drittel der Breite füllt. */
.profile-rubric-nav-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    padding: 0.45rem 0.85rem;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 0.9rem;
    /* 700 wie die Rubrik-Reihe im Header — 800 in Grau drängt sich auf. */
    font-weight: 700;
    cursor: pointer;
}
/* Die Fläche liegt nicht mehr am Button, sondern an der EINEN gleitenden Pille
   (`.rubric-pill`, siehe Dateiende) — hier deckungsgleich mit dem Button, darum
   kein `--pill-grow-*`. Am Button bleibt nur die Tinte. Sie wechselt verzögert,
   damit die weisse Pins-Schrift nicht kurz auf weissem Grund steht. */
.profile-rubric-nav-btn { position: relative; z-index: 1; transition: color 0.12s ease 0.2s; }
.profile-rubric-nav[data-active="about"]      .rubric-pill { background: var(--accent-turkis); }
.profile-rubric-nav[data-active="highlights"] .rubric-pill { background: var(--accent-yellow); }
.profile-rubric-nav[data-active="pins"]       .rubric-pill { background: var(--accent-pink); }
.profile-rubric-nav-btn.active[data-profile-rubric="about"]      { color: #000; }
.profile-rubric-nav-btn.active[data-profile-rubric="highlights"] { color: #000; }
.profile-rubric-nav-btn.active[data-profile-rubric="pins"]       { color: #fff; }
.profile-rubric-nav-icon { font-size: 0.85em; }

.profile-rubric-body { position: relative; z-index: 3; }

/* Aktions-Zeile einer Rubrik: Stift + Hinzufügen, rechtsbündig, klein und
   flach. Pins hat keine — dort wird nichts kuratiert. */
/* Mittig: die Zeile gehört zur ganzen Rubrik darunter, nicht zu deren rechter
   Kante. Rechtsbündig las sie sich wie eine Kopf-Aktion der ersten Card. */
.rubric-actions {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 0.35rem;
    padding: 0.5rem var(--profile-axis) 0.3rem;
}
/* (Der frühere Platzhalter `.rubric-actions--spacer` ist entfallen: die Zeile
   erscheint nur noch im Bearbeiten-Modus, also beginnen alle drei Rubriken
   ausserhalb davon ohnehin auf derselben Höhe.) */
/* („Fertig" als aktiver Zustand ist mit dem Umzug in den Header zu
   `.header-edit-btn.is-done` gewandert — Tiefschwarz bleibt die einzige Stelle
   im Profil, an der es etwas bedeutet: „du bearbeitest gerade".) */

.rubric-empty {
    padding: 2.2rem var(--profile-axis) 3rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
}

/* Pins-Rubrik: volle Pin-Cards wie im Feed, chronologisch. */
.profile-pins-list {
    display: flex;
    flex-direction: column;
    gap: 1.6rem;
    padding: 0.6rem 0 2.5rem;
}

/* --- Fremdes Profil (Overlay) ------------------------------------------- */
/* Zeigt nur den Kopfbereich bis zur Bio. Alles darunter ist Freundschafts-
   Inhalt; statt Inhalt stehen die Kontakt-Aktionen. Positionierung wie
   .post-detail-view, aber ohne docked Composer. */
/* WICHTIG: Es gibt in diesem Projekt KEIN generisches `.hidden { display:none }` —
   jede Komponente deklariert ihre eigene Regel (siehe .post-detail-view.hidden).
   Ohne diese Zeile liegt das Overlay als weisse Vollbild-Fläche über der App. */
.person-view.hidden { display: none; }
.person-view {
    position: fixed;
    top: var(--header-h, 110px);
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 90;
    background: var(--surface-color);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-y: contain;
}

/* Kontakt-Aktionen mittig unter der Bio. Flach (DS §7); „Freund hinzufügen" ist
   die eine Haupt-Aktion und trägt darum Türkis — Freundschaft ist der türkise
   Bereich (DS §2). Nachricht bleibt neutral, ⋯ ist nur ein Auslöser. */
/* Vertikal gestapelt und zentriert: Hinzufügen → Nachricht → ⋯. Die zwei
   beschrifteten Buttons teilen dieselbe Breite, damit der Stapel eine Kante
   hat; das ⋯ bleibt kompakt (tertiäre Aktion). */
.person-actions {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 0.5rem;
    padding: 1.4rem 1rem 0;
}
/* Look kommt aus der geteilten Basis; hier nur die Stapel-Breite. */
.person-action { min-width: 11rem; padding: 0.5rem 1rem; }
/* Die EINE Haupt-Aktion des Screens darf Brand-Farbe tragen (DS §7). */
.person-action--primary {
    background: var(--accent-turkis);
    color: #000;   /* Schwarz auf Türkis, DS §2 */
}
.person-action--more {
    min-width: 0;
    padding: 0.4rem 1rem;
    font-size: 1.1rem;
    line-height: 1;
    letter-spacing: 0.05em;
    color: var(--text-secondary);
}

.person-locked-note {
    padding: 1.6rem 2rem 2.5rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
}

/* Follower-Zeilen sind Einstiegspunkte ins Profil. Rückmeldung über Deckkraft
   statt Versatz: eine fullwidth-Zeile, die 1 px wandert, sieht nach Wackler
   aus — dieselbe stille Geste wie beim Follower-Link der Board-Card. */
.member-item { cursor: pointer; }
.member-item:active { opacity: 0.55; }

/* --- Friends-/Boards-Liste eines Profils (Overlay, PROF-07) -------------- */
/* Erreichbar aus dem eigenen UND aus einem fremden Profil — beide können in
   jedem Tab offen sein, darum body-level wie .person-view. Eine z-index-Stufe
   darüber, weil die Liste sich aus einem offenen Fremdprofil heraus über
   dieses legt.
   WICHTIG: Es gibt in diesem Projekt KEIN generisches `.hidden { display:none }` —
   jede Komponente deklariert ihre eigene Regel (siehe .person-view.hidden).
   Ohne diese Zeile liegt das Overlay als weisse Vollbild-Fläche über der App. */
.profile-list-view.hidden { display: none; }
.profile-list-view {
    position: fixed;
    top: var(--header-h, 110px);
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 91;
    background: var(--surface-color);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-y: contain;
}
/* Fullwidth: das seitliche Padding sitzt in den Zeilen, nicht im Container. */
.profile-list-scroll { padding: 1rem 0 2.5rem; }

/* Gruppen-Titel („Gemeinsam" / „Weitere"). Erscheint nur auf fremden Profilen
   von Friends/FoF; auf öffentlichen und auf dem eigenen Profil entfällt die
   Gruppierung ganz (CLAUDE.md → Account / Profil). Dieselben Werte wie die
   Level-Überschrift in „Fragen hinzufügen" — beide gliedern eine flache
   Unterseite, das soll dieselbe Sprache sein. */
.member-group + .member-group { padding-top: 1.4rem; }
.member-group-title {
    margin: 0 0 0.5rem;
    padding: 0 1rem;
    font-size: 0.9rem;
    font-weight: 900;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-secondary);
}
.member-list-empty {
    padding: 1.6rem 1rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
}

/* Board-Zeile: dieselbe Zeilenform wie .member-item, aber mit dem
   `#`-Initialen-Tile statt eines Avatars. BEWUSST ohne Klickziel — die
   Board-Detailseite ist eine Sub-View des Boards-Tabs und wäre von hier nur
   mit einem Tab-Wechsel erreichbar, was die Nav-Stack-Regel verbietet
   (CLAUDE.md: „Klicks auf Profil, Board o.ä. wechseln nie den Tab").
   Darum auch keine Klick-Optik: ein stummer Tap auf etwas, das nicht wie ein
   Button aussieht, ist ehrlicher als ein Hinweis bei jedem Tap.
   Dieselbe statische Zeile trägt „Du" in einer Friends-Liste — mein eigenes
   Profil ist der Account-Tab, dorthin zu springen wäre ein Tab-Wechsel. */
.member-item--static { cursor: default; }
.member-avatar--board {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #000;
    font-weight: 800;
    font-size: 0.8rem;
    letter-spacing: -0.02em;
}
/* Follower-Zahlen der Board-Zeile: Total, dann Friends/FoF mit den
   Personen-Icons (renderReachIcons) — dieselbe Lesart wie auf den
   Board-Cards. Nicht auf 99+ gekappt (CLAUDE.md → Boards-Card). */
.member-boardstats {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-muted);
}
.member-boardstats .reach-icons { margin-left: 0.2rem; }

/* Bild ändern: dunkle Scheibe mit Kamera, nur im Bearbeiten-Modus. Dieselbe
   Sprache wie das × auf den Raster-Kacheln — ein Griff, der AUF einem Bild
   liegt, trägt die Scheibe, weil er sonst auf hellen Motiven verschwindet
   (DS §3: Inhalt auf einer Fläche bleibt flach, die Lesbarkeit macht die
   Scheibe, nicht ein Schatten). */
.image-edit-badge {
    position: absolute;
    z-index: 4;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    cursor: pointer;
}
/* BEIDE mittig im jeweiligen Bild: der Griff sitzt dort, wo man hinschaut, und
   die Regel ist an beiden Stellen dieselbe — „tippe auf das Bild" statt „suche
   die Ecke". Beim Banner war er zuerst unten rechts; nebeneinander sahen die
   zwei dann aus wie zwei verschiedene Funktionen. */
.image-edit-badge {
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
}
.image-edit-badge--banner {
    width: 2.2rem;
    height: 2.2rem;
}
.image-edit-badge--banner .ic { width: 1.15rem; height: 1.15rem; }
.image-edit-badge--avatar {
    width: 1.9rem;
    height: 1.9rem;
}
.image-edit-badge--avatar .ic { width: 1rem; height: 1rem; }

/* --- Profil im Bearbeiten-Modus ----------------------------------------- */
/* Name + Bio werden zu Textfeldern. Inset nach DESIGN_SYSTEM §5 („Textfelder
   immer eingelassen, KEIN Border") — dieselben Werte wie der Composer und die
   Board-Suche, damit es sich wie ein Feld der App anfühlt und nicht wie ein
   neues Format. */
.profile-field {
    font-family: inherit;
    color: var(--text-primary);
    background: var(--surface-light);
    border: none;
    border-radius: var(--radius-md);
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
    padding: 0.45rem 0.7rem;
    width: 100%;
}
.profile-field:focus { outline: none; }
.profile-field::placeholder { color: var(--text-muted); }
.profile-name-field {
    /* Gleiche Grösse wie der Name im Ruhezustand — nichts springt beim Umschalten. */
    font-size: 1.05rem;
    font-weight: 900;
    line-height: 1.2;
}
.profile-bio-field {
    margin: 0.8rem 0 0;
    /* Die Bio läuft über die ganze Breite, ihr Feld also auch — die 1rem-Achse
       kommt hier aus einer eigenen Breitenrechnung, weil das Feld anders als
       der <p> kein Padding erben kann. */
    width: calc(100% - 2 * var(--profile-axis));
    margin-left: var(--profile-axis);
    margin-right: var(--profile-axis);
    font-size: 0.9rem;
    line-height: 1.45;
    /* Die Höhe kommt aus dem Inhalt (autoGrowField in friends.js) — also weder
       Scrollbalken noch Zieh-Griff: beides wären Bedienelemente für ein
       Problem, das es nicht mehr gibt. */
    resize: none;
    overflow: hidden;
}

/* Pin auf dem Profil ausblenden: Auge oben rechts auf der Card. Grösse und Look
   wie die anderen Aktions-Pillen, nur quadratisch (nur Icon). */
.profile-pin-item { position: relative; }
/* Runder Button in der oberen rechten Ecke. Bezugspunkt ist die **optische**
   Oberkante des Blocks, nicht die der Card: der Card-Offset-Akzent liegt 11px
   darüber (DESIGN_SYSTEM §4) und wird als Kartenrand gelesen. Bündig zur Card
   sass der Kreis darum 11px zu tief. `translateY(-10px)` hebt ihn auf die
   Oberkante des Streifens — er überdeckt dessen äusserstes Ende, und genau dort
   ist der Streifen konstruktionsbedingt am schmalsten.
   FLACH — kein Schatten, kein Bevel: DESIGN_SYSTEM §1/§3, auf einem
   Ebene-1-Objekt sitzt Inhalt flach auf. Sichtbar wird er über die Fläche. */
/* Das Auge sitzt IM Kopf der Card, an der Stelle von Zeit + Typ-Marker (die im
   Modus ausblenden, siehe unten) — nicht mehr als schwebender Kreis über der
   Ecke. Damit überlappt kein Bedienelement mehr den Beitrag, und der Griff
   liegt dort, wo bei jeder Card die Meta-Information steht.
   Flach und grau, ohne Fläche und ohne Rand: es ist Inhalt auf einer Card
   (DS §3), kein eigenes Objekt.
   Koordinaten: 14px Card-Aussenrand (.post-card margin) + 1rem Kopf-Polsterung
   (.post-header padding). */
.pin-visibility-btn {
    position: absolute;
    top: 1rem;
    right: calc(14px + 1rem);
    z-index: 3;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--text-muted);
    box-shadow: none;
    cursor: pointer;
}
/* Im Bearbeiten-Modus macht der Zeit-/Typ-Marker-Block dem Auge Platz. Der
   Marker ist in dieser Rubrik ohnehin redundant — jede Card hier ist ein Pin. */
.profile-pin-item.is-editing .post-time-block { visibility: hidden; }
.pin-visibility-btn .ic { width: 1.15rem; height: 1.15rem; }

/* Ausgeblendet: Auge durchgestrichen (die Sprite hat kein eye-off, der Strich
   kommt darum aus CSS) + die Card wird grau. */
.pin-visibility-btn.is-hidden::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 1.25rem;
    height: 1.5px;
    background: currentColor;
    transform: translate(-50%, -50%) rotate(-45deg);
    border-radius: 1px;
}
.profile-pin-item.is-hidden > .post-card {
    filter: grayscale(1);
    opacity: 0.45;
}

/* --- Profil-Rubrik „Updates": Raster ------------------------------------- */
/* Drei pro Zeile, **4:5 Hochformat** und eckig (Post-Bilder sind nicht
   abgerundet). 4:5 ist genau das Format, in dem Compose die Update-Bilder
   anlegt — im Raster wird damit gar nicht beschnitten, das Bild erscheint wie
   gepostet. Quadratisch war die erste Fassung und hätte oben und unten
   weggeschnitten. Die Monats-Zeile läuft über die ganze Rasterbreite und bricht
   den Rhythmus bewusst: sie ist eine Zäsur, kein Tile. */
/* Ein Monat = eine Card. Die Rubrik „Updates" bekommt damit dieselbe erhabene
   Ebene wie die Fragen und die Kommentar-Card; die Monats-Zeile ist nicht mehr
   eine blosse Trennzeile im durchlaufenden Raster, sondern die Überschrift
   ihrer Card. Werte 1:1 aus .post-card. */
.profile-months {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 0.5rem var(--profile-axis, 14px) 2.5rem;
}
/* Der 24h-Block wird von „Updates hinzufügen" gefolgt — der Abstand kommt von
   dessen eigener Zeile, sonst klaffte hier die volle Rubrik-Unterkante. */
.profile-months--fresh { padding-bottom: 0; }
.profile-month-group {
    background: var(--surface-color);
    border: none;
    border-radius: var(--radius-lg);
    box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.14),
                inset 0 1px 0 var(--bevel-light),
                inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
    padding: 0.8rem;
}
.profile-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 3px;
}
.profile-grid-month {
    padding: 0 0.1rem 0.55rem;
    color: var(--text-muted);
    font-size: 0.78rem;
    font-weight: 800;
    letter-spacing: 0.02em;
}
/* Abgerundet, seit die Bilder in der Monats-Card sitzen: eckige Kacheln in
   einer gerundeten Card lesen sich wie ein Fehler. --radius-sm (12px), nicht
   -md — die Kacheln sind nur ~1/3 der Cardbreite, mehr Rundung frisst das
   Motiv an den Ecken. Gilt NUR hier; Post-Bilder im Feed bleiben eckig. */
.profile-grid-tile {
    position: relative;
    aspect-ratio: 4 / 5;
    overflow: hidden;
    border-radius: var(--radius-sm);
    cursor: pointer;
    background: var(--surface-light);
}
.profile-grid-tile > img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* × zum Entfernen, nur im Bearbeiten-Modus. Liegt AUF dem Bild, also flach
   (DESIGN_SYSTEM §3) — die dunkle Scheibe trägt die Lesbarkeit, nicht ein
   Schatten. */
.grid-remove-btn {
    position: absolute;
    top: 4px;
    right: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    cursor: pointer;
}
.grid-remove-btn .ic { width: 0.95rem; height: 0.95rem; }

/* Auf frischen Kacheln (letzte 24 h) steht statt des × ein +: „behalten, wenn
   das Update aus dem Feed fällt". Gleiche Scheibe, gleiche Stelle — nur das
   Zeichen wechselt, und es zeigt die AKTION: einmal vorgemerkt, wird daraus
   wieder das gewohnte × („doch nicht"). Deshalb braucht `.is-kept` keine
   eigene Farbe: es IST dann derselbe Griff wie auf den älteren Kacheln. */

/* --- Detail-Ansicht im Sammlungs-Modus (Profil → Rubrik „Updates") -------- */
/* Tap auf ein Raster-Bild öffnet die ganze Sammlung, vorgescrollt auf den
   angetippten Beitrag: man scrollt durch die Kommentare weiter zum nächsten
   Update. Nur hier — im Feed bleibt die Detailseite ein einzelner Beitrag,
   weil die Sammlung dort endlos ist. */
.post-detail-view.is-collection .comment-composer {
    /* Bei mehreren Beiträgen wäre nicht erkennbar, welchen der Composer meint.
       Kommentiert wird über das Sheet, wie im Feed. */
    display: none;
}
/* Die einzige Linie überhaupt: die am Postende. Innerhalb eines Beitrags gibt
   es app-weit keine mehr (Kommentare, Kommentarliste, Aufrufe-Zeile) — getrennt
   wird dort über Abstand. Kein Abstand zwischen den Beiträgen: die Linie allein
   trennt sie. */
.post-detail-view.is-collection .detail-post + .detail-post {
    margin-top: 0;
    padding-top: 0;
    border-top: 1px solid var(--hairline);
}
.post-detail-view.is-collection .detail-post:last-child {
    padding-bottom: 3rem;
}

/* === RUBRIK-WECHSEL: Übergangs-Animation (TODO GLOB-15 Stufe 1) ============
   Choreografie in `js/rubric-switch.js`. Die alte Pane fährt hinaus, die neue
   herein; beide existieren nur WÄHREND der Animation gleichzeitig. Steht
   bewusst am Dateiende: `.rubric-pane-ghost` trägt die Klassen der Pane
   (`.feed` / `.profile-rubric-body`), damit die verschobenen Kinder unverändert
   liegen — und muss die Positionierung von `.profile-rubric-body` schlagen,
   was bei gleicher Spezifität die Quellreihenfolge entscheidet. */
.rubric-pane-ghost {
    position: absolute;
    /* Unter der neuen Pane (z-index 3) und unter der sticky Rubrik-Reihe im
       Profil (40). Überlappen tun sie ohnehin nie: beide fahren gleich weit. */
    z-index: 2;
    pointer-events: none;
}

/* Nur während des Wechsels auf dem Stage (`#home-feed-view` /
   `#profile-content`). Ohne das liefe die hinausfahrende Pane auf dem Desktop
   sichtbar in die Fläche neben `#app` — das ist 600px breit und zentriert,
   `body { overflow-x: hidden }` greift erst am Viewportrand.
   Bewusst `clip` statt `hidden`: `hidden` erzeugt einen Scroll-Container und
   hängt damit die sticky Rubrik-Reihe im Profil für die Dauer der Animation ab.
   `clip` klemmt nur die X-Achse; vertikal darf der Geist überstehen, sonst
   würde er abgeschnitten, wenn die neue Rubrik kürzer ist als die alte. */
.is-pane-switching {
    overflow-x: clip;
}

/* --- Die gleitende Rubrik-Pille ---
   Dieselbe Mechanik wie die türkise Kugel der Bottom-Nav (`.nav-indicator`):
   EIN Element pro Reihe, dessen Lage aus der echten Geometrie des aktiven
   Buttons kommt (js/rubric-switch.js), statt eines Hintergrunds pro Button —
   ein Hintergrund kann nur springen. Farbe hängt am `data-active` der Reihe und
   blendet beim Gleiten mit; `.animate` schaltet die Bewegung erst NACH der
   ersten Platzierung frei (kein Flug aus der Ecke beim Laden), genau wie beim
   Nav-Indikator. Kurve = die der Nav-Kugel, inklusive des leichten
   Überschwingens — die Reihen sollen sich gleich anfühlen. */
.rubric-pill {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    border-radius: 999px;
    z-index: 0;                 /* hinter den Labels (die stehen auf z-index 1) */
    pointer-events: none;
    transform: translate(0, 0);
    /* KEINE Transition in der Basis — auch nicht für die Farbe. Der Wechsel
       setzt die Farbe der alten Rubrik kurz direkt (damit die neu gerenderte
       Pille einen Vorzustand hat, aus dem sie überblenden kann); läge hier eine
       Transition, würde schon dieses Setzen blenden und die Pille käme nie bei
       der alten Farbe an. */
}
.rubric-pill.animate {
    transition:
        transform 0.38s cubic-bezier(0.34, 1.3, 0.5, 1),
        width 0.38s cubic-bezier(0.34, 1.3, 0.5, 1),
        height 0.38s cubic-bezier(0.34, 1.3, 0.5, 1),
        background-color 0.3s ease;
}

/* === LEVEL-ANSICHT (hinter der Karma-Zeile im Profil) =======================
   Unterseite über dem Profil: **flach und fullwidth** wie „Fragen hinzufügen"
   (DS §3 — Tiefe wird einmal vergeben). Die Liste des nächsten Levels nutzt
   bewusst dieselben Klassen wie der Fragen-Katalog (`.unlock-level-group.locked`
   + `.unlock-item.locked`): es ist dieselbe Sache in derselben Sprache, nur
   vorgezogen. */
.level-view {
    display: flex;
    flex-direction: column;
    gap: 1.4rem;
    padding-bottom: 2rem;
}

/* Der Kopf trägt die Zahl, nicht ein Abzeichen: das Level gross, darunter der
   Balken und die zwei Eckwerte. Kein Tile, keine Fläche — die Seite soll wie
   eine Auskunft wirken, nicht wie ein Pokal. */
.level-hero {
    padding: 0.5rem 1rem 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.level-hero-level {
    font-size: 1.6rem;
    font-weight: 900;
    letter-spacing: -0.01em;
}
/* Die Spur ist das eingelassene Weiss aus DS-02 — dieselbe Rolle wie überall:
   „hier läuft etwas hinein". Die Füllung türkis, weil Karma keine Rubrik ist,
   sondern neutral (DS §2). */
.level-bar {
    height: 10px;
    border-radius: var(--radius-full);
    background: var(--surface-light);
    overflow: hidden;
}
.level-bar-now {
    display: block;
    height: 100%;
    border-radius: var(--radius-full);
    background: var(--accent-turkis);
    transition: width 0.6s cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* Die zwei Zahlen am Balken sind die EINZIGE gedämpfte Stelle der Seite: sie
   sind Skala, nicht Inhalt. Alles andere steht in Textfarbe — auf einer Seite,
   die man bewusst öffnet, gibt es nichts Nebensächliches. */
.level-hero-meta {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    color: var(--text-muted);
    font-size: 0.82rem;
    font-weight: 600;
}
.level-hero-meta b { font-weight: 800; color: var(--text-primary); }

/* Überschrift + Subzeile über den Kästchen: das Level benennt, was kommt, die
   Subzeile sagt, was mit ihm passiert. */
.level-next {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0 1rem;
}
/* Der Ausblick aufs übernächste Level: unscharf und nach unten auslaufend —
   dieselbe Mechanik wie im Fragen-Katalog. Der Titel bleibt lesbar (man soll
   wissen, WAS da kommt), der Inhalt nicht. Bis zum letzten Level sieht man
   bewusst nicht. */
.level-next--teaser .level-unlock-desc { filter: blur(3.5px); user-select: none; }
.level-next--teaser .level-frame-preview { filter: blur(2px); }
.level-next--teaser {
    pointer-events: none;
    -webkit-mask-image: linear-gradient(to bottom, #000 30%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 30%, transparent 100%);
}
.level-next-title {
    font-size: 1.15rem;
    font-weight: 900;
    letter-spacing: -0.01em;
}
.level-next-sub {
    /* Explizit gesetzt: `p` erbt hier sonst das Sekundär-Grau des Umfelds. */
    color: var(--text-primary);
    font-size: 0.9rem;
    font-weight: 600;
    margin-bottom: 0.7rem;
}

/* Ein Kästchen je Belohnung. Zwei Spalten, weil die Labels kurz sind und eine
   Reihe voller Einzeiler mehr nach Liste als nach Auswahl aussähe.
   FLACH: Fläche in der eingelassenen Spur (DS-02), kein Schatten, kein Bevel —
   die Seite liegt über dem Profil und vergibt keine zweite Tiefe (DS §3). */
.level-unlock-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.6rem;
}
/* Titel oben, das Konkrete darunter — beide Kästchen gleich gebaut. Sie stehen
   dicht beieinander (kleiner Gap), weil sie EINE Aussage sind; die Kästchen
   einer Reihe werden vom Grid ohnehin auf gleiche Höhe gezogen, ein
   `space-between` bräuchte es dafür nicht und risse den Inhalt nur
   auseinander. Rundum dasselbe Polster: nichts klebt an einer Kante. */
.level-unlock-box {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.3rem;
    padding: 0.95rem;
    border-radius: var(--radius-md);
    background: var(--surface-light);
}
/* Die Art ist die Auskunft und trägt darum das Gewicht. */
.level-unlock-title {
    font-size: 1rem;
    font-weight: 800;
    line-height: 1.25;
}
.level-unlock-desc {
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.3;
}
/* Der Rahmen zeigt sich selbst: das eigene Profilbild mit einem Ring drum.
   Türkis, weil eine Belohnung keiner Rubrik gehört (DS §2). */
.level-frame-preview {
    display: block;
    margin-top: 0.1rem;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    box-shadow: 0 0 0 2.5px var(--accent-turkis);
}
.level-frame-preview img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}

.level-outro {
    padding: 0 1rem;
    color: var(--text-primary);
    font-size: 0.82rem;
    font-weight: 600;
    line-height: 1.45;
}

/* ===========================================================================
   CHAT-01 · Chats — Header-Griffe, Suchfeld & Liste
   Nur Einzelchats. Rechts im Header die Map (keine Rubrik, sondern ein Griff)
   und ganz rechtsbündig „neuer Chat"; gesucht wird im Feld über der Liste.
   Glocke und türkise Such-Zunge treten in diesem Tab zurück (2026-08-03).
   =========================================================================== */
.header-chats-actions {
    display: none;
}
.app-header.chats-tab .header-chats-actions {
    position: absolute;
    right: 1.5rem;
    display: flex;
    align-items: center;
    gap: 1.1rem;
}
.app-header.chats-tab .header-chats-actions .icon-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-primary);
    font-size: 1.15rem;
}
.app-header.chats-tab .header-chats-actions .icon-btn:active { opacity: 0.5; }
/* Im Chat-Tab weichen Glocke und Such-Zunge. */
.app-header.chats-tab .header-actions,
.app-header.chats-tab .search-tongue {
    display: none;
}
/* Auf Detail-Seiten (Chat-Ansicht, Karte) übernimmt die Detail-Bar den Header —
   die Tab-Griffe verschwinden dort wie Logo, Glocke und Zunge. */
.app-header.detail-mode .header-chats-actions {
    display: none;
}

/* Profilbild im Chat-Header, zwischen Back und @handle. Nur dort — auf allen
   anderen Detail-Seiten steht keine Person im Titel. */
.header-chat-avatar { display: none; }
.app-header.detail-mode-chat .header-chat-avatar {
    display: block;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: 50%;
    object-fit: cover;
    background: var(--surface-light);
    cursor: pointer;
    margin-right: -0.1rem;   /* Titel rückt näher ans Bild, sie gehören zusammen */
}
.app-header.detail-mode-chat .header-chat-avatar:active { opacity: 0.6; }

/* --- Ungelesen-Punkt am Chat-Tab ---
   Ein Punkt, keine Zahl: in der Nav genügt „da ist etwas", wie viel es ist,
   liest man eine Ebene tiefer in der Liste. Er darf darum deutlich kleiner
   sein als die Zahl an der Glocke — er muss nichts tragen ausser sich selbst.
   Der Ring in der Flächenfarbe trennt ihn von der türkisen Aktiv-Kugel, auf
   der er sonst verschwände (DS §11: Ring statt Border).

   Sein Mittelpunkt liegt AUF der Linie des Symbols: die grosse Sprechblase ist
   im Icon ein Kreis um (14, 10) mit Radius 8 (viewBox 24), der Punkt sitzt auf
   diesem Bogen bei 45°. Umgerechnet auf die Icon-Grösse (22.4 px, also 0.933 px
   je Einheit) und die Padding-Box des Nav-Items ergibt das die Werte unten —
   nicht geschätzt, sondern gerechnet. Ändert sich die Icon-Grösse, ändern sie
   sich mit. */
.nav-dot {
    position: absolute;
    top: 13px;
    right: 7.5px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--accent-turkis);
    box-shadow: 0 0 0 1.5px var(--surface-color);
}
/* Das Verstecken übernimmt JS (`chatsBadgeSeen`, chats.js): der Besuch des
   Tabs quittiert den Hinweis, danach bleibt der Punkt weg, bis Neues eintrifft.
   Eine eigene Active-Regel wäre eine zweite Wahrheit über denselben Zustand. */
.nav-dot.hidden { display: none; }

/* --- Suchfeld über der Liste ---
   Eingelassen und ohne Border (DS §5), Icon im Feld statt daneben: das Feld
   IST die Suche, ein zweites Element daneben wäre nur Beschriftung. */
.chat-search {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin: 0 14px 1.1rem;
    padding: 0.65rem 0.9rem;
    border-radius: var(--radius-md);
    background: var(--surface-light);
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);
}
.chat-search-icon {
    width: 1.05rem;
    height: 1.05rem;
    color: var(--text-muted);
    flex-shrink: 0;
}
.chat-search-input {
    flex: 1;
    min-width: 0;
    background: transparent;
    border: none;
    outline: none;
    font-family: inherit;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--text-primary);
}
.chat-search-input::placeholder {
    color: var(--text-muted);
    font-weight: 600;
}
.chat-list-empty {
    padding: 1.5rem 0.5rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
    font-weight: 600;
}

/* --- Die Liste: eine Card pro Chat ---
   Bewusst Cards und nicht die flache Personenzeile der Follower-Listen —
   sonst hätte diese Seite als einzige keine Tiefe. Ebene-1-Werte 1:1 wie
   `.post-card` / `.board-card-v2`, aber OHNE Top-Akzent: ein Chat gehört
   zu keiner Rubrik (dieselbe Regel wie bei den Board-Tiles). */
.chat-list {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    /* Dieselbe Achse wie die Post-Cards im Feed (`.post-card { margin: 0 14px }`) —
       auf Mobile fällt `main`s Seitenpadding weg, die Cards behalten ihre Kante. */
    padding: 0 14px;
}
.chat-card {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 0.85rem;
    align-items: center;
    padding: 0.75rem 1rem;
    background: var(--surface-color);
    border-radius: var(--radius-lg);
    box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.14),
                inset 0 1px 0 var(--bevel-light),
                inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
    cursor: pointer;
    transition: transform 0.1s;
}
.chat-card-avatar {
    width: 52px;
    height: 52px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--surface-light);
    flex-shrink: 0;
}
.chat-card-body {
    min-width: 0;               /* erlaubt der Vorschauzeile das Kürzen */
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.chat-card-line {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    min-width: 0;
}
.chat-card-name {
    font-weight: 800;
    font-size: 1rem;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Zeit und Vorschau folgen der Meta-Behandlung der Post-Cards (DS §11):
   `--text-muted`, 0.8rem, 600 — nie fett. */
.chat-card-time {
    margin-left: auto;
    flex-shrink: 0;
    color: var(--text-muted);
    font-size: 0.8rem;
    font-weight: 600;
}
.chat-card-preview {
    min-width: 0;
    flex: 1;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.chat-preview-prefix { font-weight: 700; }
/* Sprachnachricht und Update haben keinen Text — ihr Zeichen steht in der
   Zeile mit, leicht abgesetzt vom Wort. */
.chat-preview-icon {
    width: 0.95em;
    height: 0.95em;
    vertical-align: -0.13em;
    margin-right: 0.3em;
}
/* Das Mikrofon ist ein SVG, der Blitz ein Font-Glyph (FA-Ausnahme, DS §9) —
   `width`/`height` greift nur beim SVG. Damit beide Zeichen in der Vorschau
   gleich gross stehen, bekommt das Glyph seine Grösse über `font-size`
   (UPD-01: der Blitz sieht überall gleich aus). */
i.chat-preview-icon { font-size: 0.95em; }
/* Ungelesen: türkise Zahl wie das Badge an der Glocke (Notification-Farbe) —
   und wie dort mit SCHWARZER Tinte: Weiss auf Türkis liegt bei ≈2,1:1 und
   reisst WCAG AA, Schwarz bei ≈10,6:1 (DS §2). */
.chat-unread {
    flex-shrink: 0;
    align-self: center;
    background: var(--accent-turkis);
    color: #000;
    font-size: 0.72rem;
    font-weight: 800;
    min-width: 19px;
    height: 19px;
    padding: 0 5px;
    border-radius: var(--radius-full);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* ===========================================================================
   CHAT-01 · Die Chat-Ansicht
   Fullscreen-Overlay unter dem Header (z 92 — über Fremdprofil und Profil-
   Liste, aus denen sie geöffnet werden kann), eigener Scroll-Bereich, unten
   der angedockte Composer. Aufbau 1:1 wie `.post-detail-view`.
   Flach: die Tiefe ist auf dieser Seite bei den Cards der Liste vergeben (DS §1).
   =========================================================================== */
.chat-view {
    position: fixed;
    top: var(--header-h, 110px);
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 92;
    background: var(--surface-color);
    display: flex;
    flex-direction: column;
}
.chat-view.hidden { display: none; }

.chat-thread {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-y: contain;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    padding: 1rem 1rem 1.4rem;
    max-width: 600px;
    width: 100%;
    margin: 0 auto;
}
.chat-thread-empty {
    margin: auto;
    padding: 1rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
    font-weight: 600;
}
/* Zeile: Profilbild aussen, Blase innen. Bei eigenen Nachrichten spiegelt sich
   die Reihenfolge — das Bild steht immer an der Aussenkante. */
.chat-msg {
    display: flex;
    align-items: flex-end;
    gap: 0.45rem;
    max-width: 82%;
}
.chat-msg--me   { align-self: flex-end;   flex-direction: row-reverse; }
.chat-msg--them { align-self: flex-start; }
.chat-msg-col {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.chat-msg--me   .chat-msg-col { align-items: flex-end; }
.chat-msg--them .chat-msg-col { align-items: flex-start; }
/* Das Bild steht nur an der letzten Nachricht einer Folge; die übrigen halten
   den Platz frei (versteckt statt weggelassen), damit die Blasenkante steht. */
.chat-msg-avatar {
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: 50%;
    object-fit: cover;
    background: var(--surface-light);
    margin-bottom: 1.1rem;   /* auf die Blasenkante, nicht auf die Zeitzeile */
}
.chat-msg-avatar.is-spacer { visibility: hidden; }

/* Eigene Blase türkis mit SCHWARZER Schrift (DS §2 „Schwarz auf Türkis" —
   weiss läge bei ≈2:1), fremde in der eingelassenen Spur-Farbe. Die zur
   Person zeigende Ecke bleibt eckig, das ist die Richtungsangabe. */
.chat-bubble {
    padding: 0.6rem 0.85rem;
    border-radius: var(--radius-md);
    font-size: 0.95rem;
    font-weight: 500;
    line-height: 1.35;
    word-break: break-word;
    box-shadow: none;
    border: none;
}
.chat-msg--me .chat-bubble {
    background: var(--accent-turkis);
    color: #000;
    border-bottom-right-radius: 6px;
}
.chat-msg--them .chat-bubble {
    background: var(--surface-light);
    color: var(--text-primary);
    border-bottom-left-radius: 6px;
}
/* Die Zeit steht UNTER der Blase, nicht darin: als Meta gehört sie in
   `--text-muted` (DS §11), und Grau auf Türkis wäre nicht zu lesen. */
.chat-msg-time {
    margin-top: 0.15rem;
    padding: 0 0.2rem;
    color: var(--text-muted);
    font-size: 0.72rem;
    font-weight: 600;
}

/* --- Sprachnachricht (Optik, ohne Audio) --- */
.chat-bubble--voice { padding: 0.55rem 0.75rem; }
.chat-voice {
    display: flex;
    align-items: center;
    gap: 0.55rem;
}
.chat-voice-play {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.75);   /* Play-Knopf auf der Sprachblase — kein Bevel */
}
.chat-voice-play .ic { width: 0.85rem; height: 0.85rem; }
.chat-msg--them .chat-voice-play { background: var(--surface-color); }
.chat-wave {
    display: flex;
    align-items: center;
    gap: 2px;
    height: 22px;
    width: 108px;
    flex-shrink: 0;
}
.chat-wave-bar {
    flex: 1;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.35);
}
.chat-msg--them .chat-wave-bar { background: rgba(var(--shadow-rgb), 0.35); }
.chat-voice-dur {
    font-size: 0.75rem;
    font-weight: 700;
    flex-shrink: 0;
}

/* --- Verschicktes Update ---
   Wird angezeigt, das Verschicken selbst ist nicht gebaut (CHAT-01). Das Bild
   ist leicht gerundet, weil es in einer gerundeten Fläche sitzt — dieselbe
   Ausnahme wie die Kacheln im Updates-Raster des Profils (FEED-07). */
.chat-bubble--update { padding: 0.5rem; cursor: pointer; }
.chat-update {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.chat-update-label {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0 0.2rem;
    font-size: 0.78rem;
    font-weight: 700;
}
.chat-update-img {
    display: block;
    width: 100%;
    max-width: 220px;
    aspect-ratio: 4 / 5;
    object-fit: cover;
    border-radius: var(--radius-sm);
}
/* Die Zeile, die der Absender dazugeschrieben hat — in der Blase, unter dem
   Bild: sie ist der Grund für das Bild, keine eigene Nachricht. */
.chat-update-text {
    display: block;
    max-width: 220px;
    padding: 0 0.2rem;
    font-size: 0.95rem;
    font-weight: 500;
    line-height: 1.35;
}

/* --- Composer ---
   Sprache zuerst: grosses türkises Mikrofon MITTIG, rechts davon klein und in
   der grauen Button-Optik das Text-Symbol. Die Grösse ist die Botschaft.

   EINE Zeile, drei Spalten — und JEDES Kind sitzt explizit in `grid-row: 1`.
   Ohne das packt die Auto-Platzierung des Grids Elemente, die weiter links
   stehen als der Platzierungs-Cursor, in eine ZWEITE Reihe (der Cursor läuft
   nur vorwärts): der Zeitzähler landete unter der Leiste, die Leiste wurde
   höher, alles sprang. Neue Zeilen gibt es hier nicht. */
.chat-composer {
    flex: 0 0 auto;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    grid-template-rows: 64px;      /* feste Höhe: kein Zustand darf sie ändern */
    align-items: center;
    gap: 0.7rem;
    padding: 0.7rem 1rem;
    padding-bottom: calc(0.7rem + env(safe-area-inset-bottom, 0px));
    width: 100%;
    max-width: 600px;
    margin: 0 auto;
    background: var(--surface-color);
    border-top: 1px solid var(--hairline);
}
.chat-composer > * { grid-row: 1; }

/* Die kleinen grauen Nebenknöpfe (Text/Papierkorb · Rückweg zur Sprache). */
.chat-mode-btn {
    grid-column: 3;
    justify-self: start;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border: none;
    border-radius: 50%;
    background: var(--surface-light);
    color: var(--text-secondary);
    cursor: pointer;
    box-shadow: none;
}
.chat-mode-btn .ic { width: 1.2rem; height: 1.2rem; }
/* Der grosse Knopf in der Mitte: erst Mikrofon, nach dem Loslassen Senden.
   Derselbe Platz, dieselbe Grösse — er beendet, was er begonnen hat. */
.chat-mic-btn {
    grid-column: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border: none;
    border-radius: 50%;
    background: var(--accent-turkis);
    color: #000;                  /* Schwarz auf Türkis (DS §2) */
    cursor: pointer;
    box-shadow: none;
    transition: transform 0.18s ease, background-color 0.18s ease;
}
.chat-mic-btn .ic { width: 1.7rem; height: 1.7rem; }
/* Gehalten und gezogen wird auf dem Mikrofon — der Griff darf dabei weder
   scrollen noch das Auswahl-/Kontextmenü auslösen. */
.chat-mic-btn,
#chat-voice-btn {
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
}

/* --- Aufnahme-Zustand ---
   Drei Stufen, und keine ändert die Höhe der Leiste:

   1. `.is-pressing` — der Finger liegt. Das Mikrofon BLEIBT stehen (es ist das
      Element, auf dem die Geste liegt; blendet man es unter dem Finger aus,
      schickt Touch ein `pointercancel` und die Geste ist tot), es pulsiert,
      links davon läuft die Zeit. Sonst passiert nichts — ein kurzer Tap soll
      keine Bewegung auslösen, die er gleich wieder zurücknimmt.
   2. `.is-holding` — nach 600 ms erkanntes Halten. JETZT erst fährt der
      Papierkorb an den Rand: die Bewegung ist die Ansage „du hältst".
   3. ohne beides — angetippt, Finger weg: aus dem Mikrofon wird Senden.

   `.is-cancel-armed`: die gezogene Kugel steht über dem Papierkorb — sie wird
   rot (Destruktiv-Rot, nicht Rosa; Rosa gehört den Pins) und Loslassen wirft weg. */
.chat-rec-time,
#chat-rec-send-btn { display: none; }

.chat-composer.is-recording .chat-rec-time {
    display: block;
    grid-column: 1;
    justify-self: end;
    color: var(--text-primary);
    font-size: 0.95rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}
@keyframes chat-rec-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(82, 205, 167, 0.55); }
    100% { box-shadow: 0 0 0 22px rgba(82, 205, 167, 0); }
}

/* Der Text-/Papierkorb-Knopf: ein Element, zwei Symbole übereinander, die
   ineinander überblenden. Die Fahrt an den Rand setzt JS als translateX —
   die Strecke hängt an der Breite der Leiste. */
#chat-text-btn {
    position: relative;
    transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.15s ease, background-color 0.18s ease;
}
.chat-btn-icon {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1.2rem;
    height: 1.2rem;
    margin: -0.6rem 0 0 -0.6rem;
    transition: opacity 0.18s ease, transform 0.18s ease, color 0.18s ease;
}
.chat-btn-icon--trash { opacity: 0; transform: scale(0.7); }
.chat-composer.is-recording .chat-btn-icon--text  { opacity: 0; transform: scale(0.7); }
.chat-composer.is-recording .chat-btn-icon--trash { opacity: 1; transform: none; }

/* 1./2. Finger liegt: Mikrofon bleibt und pulsiert. */
.chat-composer.is-recording.is-pressing #chat-mic-btn {
    display: flex;
    animation: chat-rec-pulse 1.4s ease-out infinite;
}
/* Beim Ziehen setzt JS das transform Bild für Bild — eine Transition darauf
   würde nachhinken. */
.chat-composer.is-holding #chat-mic-btn { transition: background-color 0.18s ease; }

/* Kugel über dem Papierkorb: sie wird rot UND wird selbst zum Papierkorb —
   der Knopf darunter blendet weg. Zwei Papierkörbe übereinander an derselben
   Stelle lesen sich als Matsch; einer sagt klar, was das Loslassen tut. */
#chat-mic-btn { position: relative; z-index: 1; }
.chat-ball-icon {
    position: absolute;
    top: 50%;
    left: 50%;
    margin: -0.85rem 0 0 -0.85rem;
    transition: opacity 0.15s ease, transform 0.15s ease;
}
.chat-ball-icon--trash { opacity: 0; transform: scale(0.7); }
/* ACHTUNG Spezifität: die Puls-Regel oben trägt `.is-recording.is-pressing`
   plus die ID und liegt damit bei (1,3,0). Ein Opt-out mit nur
   `.is-cancel-armed #chat-mic-btn` (1,2,0) verliert — der türkise Ring lief
   unter der roten Kugel weiter. Darum hier dieselben Klassen mitgeführt. */
.chat-composer.is-recording.is-pressing.is-cancel-armed #chat-mic-btn {
    animation: none;
}
.chat-composer.is-cancel-armed #chat-mic-btn {
    background: var(--danger);
    color: #fff;
}
.chat-composer.is-cancel-armed .chat-ball-icon--mic   { opacity: 0; transform: scale(0.7); }
.chat-composer.is-cancel-armed .chat-ball-icon--trash { opacity: 1; transform: none; }
.chat-composer.is-cancel-armed #chat-text-btn { opacity: 0; }

/* 3. Angetippt (Finger weg): Mikrofon → Senden. Der Papierkorb ist derselbe
   Knopf wie eben, an seinem Platz — anklickbar statt Ziel. */
.chat-composer.is-recording:not(.is-pressing) #chat-mic-btn { display: none; }
.chat-composer.is-recording:not(.is-pressing) #chat-rec-send-btn {
    display: flex;
    animation: chat-rec-pulse 1.4s ease-out infinite;
}

/* --- Text-Modus ---
   Feld und Senden treten an, das Mikrofon wird zum kleinen Rückweg — links,
   weil rechts jetzt der Senden-Knopf steht. */
#chat-voice-btn,
.chat-text-input,
.chat-send-btn { display: none; }
.chat-composer.is-text-mode { grid-template-columns: auto 1fr auto; }
.chat-composer.is-text-mode #chat-text-btn,
.chat-composer.is-text-mode .chat-mic-btn,
.chat-composer.is-text-mode .chat-rec-time { display: none; }
.chat-composer.is-text-mode #chat-voice-btn {
    display: flex;
    grid-column: 1;
    justify-self: start;
}
.chat-composer.is-text-mode .chat-text-input {
    display: block;
    grid-column: 2;
    width: 100%;
    min-width: 0;
    padding: 0.7rem 0.9rem;
    border: none;
    outline: none;
    border-radius: var(--radius-md);
    background: var(--surface-light);
    box-shadow: inset 0 2px 6px rgba(var(--shadow-rgb), 0.20);   /* DS §5 */
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--text-primary);
}
.chat-composer.is-text-mode .chat-send-btn {
    display: flex;
    grid-column: 3;
    justify-self: end;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border: none;
    border-radius: 50%;
    background: var(--accent-turkis);
    color: #000;
    cursor: pointer;
    box-shadow: none;
}
.chat-composer.is-text-mode .chat-send-btn .ic { width: 1.2rem; height: 1.2rem; }

/* ===========================================================================
   BOARD-SWIPE (BOARD-01) — Einstiegs-Card + Swipe-Ansicht
   ===========================================================================
   Der Einstieg sitzt als letzte Zeile im Entdecken-Abschnitt und ist dasselbe
   erhabene Tile wie jeder Vorschlag darüber (BOARD-02): weiss, gerundet,
   Elevation + Bevel, KEIN Top-Akzent — der Streifen unterscheidet im Feed
   Updates von Pins, im Boards-Tab ist alles ein Board. Er erbt seine Optik
   darum aus `.people-list-item` und setzt hier nur, was ihm eigen ist. */
.swipe-entry-row {
    cursor: pointer;
    /* Eine Zeile, kein Umbruch: die Kopfzeile eines Vorschlags bricht um, weil
       darunter der „Was verbindet uns"-Streifen sitzt. Hier gibt es keinen. */
    flex-wrap: nowrap;
}
/* Die Grafik: zwei gestapelte, leicht gekippte Karten — das geläufige Zeichen
   für einen Swipe-Modus. Flach auf der eingelassenen Spur (DS §3: Inhalt auf
   einer Card ist flach), quadratisch und leicht gerundet wie die Kacheln im
   Updates-Raster. Die Grösse ist die eines Zeilen-Avatars (40px, DS §11),
   damit die Zeile auf derselben Achse liegt wie die Vorschläge darüber. */
.swipe-entry-art {
    position: relative;
    flex: none;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-sm);
    background: var(--surface-light);
    overflow: hidden;
}
.swipe-entry-art-card {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 14px;
    height: 20px;
    border-radius: 3px;
}
/* Die hintere Karte in Meta-Grau, die vordere in Rosa: Rosa gehört den Boards
   (DS §2), und die vordere ist die, um die es geht. */
.swipe-entry-art-card--back {
    transform: translate(-50%, -50%) rotate(-17deg) translateX(-4px);
    background: var(--text-muted);
    opacity: 0.45;
}
.swipe-entry-art-card--front {
    transform: translate(-50%, -50%) rotate(10deg) translateX(4px);
    background: var(--accent-pink);
}
.swipe-entry-chevron {
    flex: none;
    width: 1.15rem;
    height: 1.15rem;
    color: var(--text-muted);
}

/* --- Die Swipe-Ansicht --------------------------------------------------
   Fullscreen unter dem Detail-Header, ohne Bottom-Nav. Der Stapel steht
   mittig; darunter eine leise Zeile, die die Geste benennt. Gerechnet wird
   gegen die Header-Höhe, damit die Card nicht unter ihn rutscht. */
#board-swipe-view {
    /* Der Screen scrollt nicht — er hat genau einen Inhalt. Ohne das käme
       beim Ziehen der Card die Seite mit ins Rutschen. */
    overflow: hidden;
}
#board-swipe-content {
    /* Fest im Bild statt im Fluss. Der Screen hat genau EINEN Inhalt und
       scrollt nicht — im Fluss müsste die Mitte gegen zwei Polster gerechnet
       werden, die hier gar nicht gelten: `main` gibt oben und unten je 1.5rem,
       und `#app` hält unten Platz für die schwebende Nav frei, die auf diesem
       Screen ausgeblendet ist (Detail-Ansicht-Regel). Fest positioniert ist die
       Mitte einfach die Mitte des freien Raums unter dem Header.
       Der Header liegt auf z-index 100 und bleibt damit oben. */
    position: fixed;
    top: var(--header-h, 75px);
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 100%;
    max-width: 600px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.4rem;
    /* Oben und unten gleich — sonst sitzt der Stapel um die Differenz neben
       der Mitte, und genau das sieht man auf einem sonst leeren Screen. */
    padding: 1rem;
    z-index: 1;
}
.swipe-stage {
    position: relative;
    width: 100%;
    max-width: 330px;
    /* Feste Höhe, nicht inhaltsabhängig: ein Stapel, dessen Cards je nach
       Füllstand verschieden hoch sind, wackelt bei jedem Wisch. Nicht jedes
       Board hat einen Beispiel-Beitrag — die Card behält trotzdem ihr Mass.
       Das Mass ist am vollsten Fall gemessen (Kopf 52 + zwei Gründe + ein
       Beitrag mit drei Zeilen = 284px Inhalt) und nicht gegriffen.
       Die Bühne ist 24px höher als die Card: darin lugen die zwei dahinter
       hervor. Rechnete man sie nicht mit, sässe der Stapel um diesen Betrag
       neben der Mitte des Screens. */
    height: 316px;
}
.swipe-card {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 292px;
    /* Von oben skalieren, nicht aus der Mitte: so ist der sichtbare Rand einer
       hinteren Card genau ihr `translateY`, unabhängig von ihrer Skalierung.
       Aus der Mitte müsste man beides gegeneinander rechnen, und der Stapel
       sähe bei jeder Höhenänderung anders aus. */
    transform-origin: top center;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    padding: 1.1rem 1.1rem 1.3rem;
    background: var(--surface-color);
    border: none;
    border-radius: var(--radius-lg);
    /* Ebene 1 wie eine Post-Card — die Cards sind die Hauptelemente dieses
       Screens. Werte 1:1 aus `.post-card`. KEIN Top-Akzent: ein Board gehört
       zu keiner Rubrik (DS §11). */
    box-shadow: 0 12px 28px rgba(var(--shadow-rgb), 0.14),
                inset 0 1px 0 var(--bevel-light),
                inset 0 -3px 6px rgba(var(--shadow-rgb), 0.07);
    /* Die vorderste Card hängt am Finger — für sie darf der Browser die
       Geste nicht als Scrollen deuten. */
    touch-action: pan-y;
    /* Ohne das rutscht beim Ziehen die Textauswahl mit. */
    user-select: none;
    -webkit-user-select: none;
    overflow: hidden;
}
/* Nur die hinteren Cards bewegen sich weich beim Nachrücken; die vorderste
   hängt am Finger und darf keine Transition tragen, sonst schleppt sie
   hinterher. */
.swipe-card:not(.is-front) {
    transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.swipe-card-head {
    display: flex;
    align-items: center;
    gap: 0.8rem;
}
.swipe-card-avatar {
    flex: none;
    width: 52px;
    height: 52px;
    font-size: 0.95rem;
}
.swipe-card-title {
    font-size: 1.25rem;
    font-weight: 800;
    color: var(--text-primary);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Der Body füllt die Card und zentriert seinen Inhalt. Beides ist nötig, weil
   die Cardhöhe fest ist, der Inhalt aber nicht: nicht jedes Board hat einen
   Beispiel-Beitrag. Zentriert sitzt der Rest dann mittig statt oben, und die
   verbleibende Luft steht als Rand oben UND unten — das liest sich als Card mit
   Raum, während dieselbe Luft an einer Kante nach fehlendem Inhalt aussieht.
   (Der Beitrag hing vorher per `margin-top: auto` an der Unterkante; dann klafft
   bei kurzen Karten ein Loch mitten in der Card.) */
.swipe-card-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.7rem;
    min-height: 0;
}
/* Ein Grund je Zeile: links das Sichtbare (Gesichter bzw. `#tag`-Pillen),
   rechts der Satz dazu — dieselbe Lesart wie die Streifen auf den Board- und
   Freundschaftsvorschlags-Cards. */
.swipe-card-reason {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
}
.swipe-card-reason-text {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-secondary);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Der Beispiel-Beitrag: flach auf der eingelassenen Spur, keine zweite Card
   (DS §1 — Tiefe stapelt sich nicht). Er ist ein Zitat von dort, kein Post
   zum Anfassen: keine Aktions-Leiste, kein Klickziel. */
.swipe-card-pin {
    background: var(--surface-light);
    border-radius: var(--radius-md);
    padding: 0.7rem 0.8rem;
    min-width: 0;
}
.swipe-card-pin-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.35rem;
    min-width: 0;
}
.swipe-card-pin-avatar {
    flex: none;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    object-fit: cover;
}
.swipe-card-pin-name {
    font-weight: 800;
    font-size: 0.85rem;
    color: var(--text-primary);
    white-space: nowrap;
}
/* Meta auf einer Card: eine Behandlung (DS §11). */
.swipe-card-pin-time {
    margin-left: auto;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-muted);
    white-space: nowrap;
    flex: none;
}
.swipe-card-pin-text {
    font-size: 0.9rem;
    font-weight: 500;
    line-height: 1.35;
    color: var(--text-primary);
    /* Drei Zeilen reichen als Aufhänger — der ganze Beitrag steht im Board. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Die zwei Stempel. Sie sagen, was das Loslassen TÄTE, und wachsen mit dem
   Zug herein (`--verdict-*` setzt das Gesten-Modul).
   FOLGEN ist rosa (Boards-Farbe, DS §2), WEITER ist Meta-Grau — bewusst NICHT
   rot: Rot gehört Löschen und Melden, und einen Vorschlag zu übergehen ist
   nichts davon. Die App kennt kein „nicht interessiert" (BOARD-02); wer weiter
   wischt, entscheidet nichts, was gespeichert würde. */
.swipe-verdict {
    position: absolute;
    top: 1.1rem;
    padding: 0.3rem 0.7rem;
    border-radius: var(--radius-md);
    font-size: 0.9rem;
    font-weight: 800;
    letter-spacing: 0.02em;
    pointer-events: none;
}
.swipe-verdict--follow {
    right: 1.1rem;
    background: var(--accent-pink);
    color: #fff;                       /* Weiss auf Rosa (DS §2) */
    opacity: var(--verdict-follow, 0);
}
.swipe-verdict--skip {
    left: 1.1rem;
    background: var(--surface-light);
    color: var(--text-secondary);
    opacity: var(--verdict-skip, 0);
}

/* Die Hinweiszeile. Dieselbe leise Schrift wie die Schluss-Sätze der App
   (`.unlock-outro` / `.chat-outro`) — sie erklärt eine Geste, sie wirbt nicht.
   Der Screen hat bewusst keine Knöpfe: die Geste IST die Bedienung, und zwei
   Knöpfe daneben wären eine zweite Mechanik für dieselbe Entscheidung. */
.swipe-hint {
    color: var(--text-muted);
    font-size: 0.82rem;
    font-weight: 600;
    text-align: center;
    padding: 0 1.5rem;
}

/* Stapel leer — flach, keine Card: die Tiefe auf diesem Screen gehört den
   Vorschlägen, und die sind gerade alle. */
.swipe-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
    padding: 3rem 2rem;
    text-align: center;
}
.swipe-empty-title {
    font-size: 1.05rem;
    font-weight: 800;
    color: var(--text-primary);
}
.swipe-empty-text {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-muted);
    line-height: 1.4;
}
