/* ============================================================
   <pb-sidebar> — the primary navigation rail
   ------------------------------------------------------------
   A floating island, fixed to the left. Collapsed = icon rail;
   expanded = icons + labels. Width (and content margin, see
   layout.css) animate; icon/avatar X+Y stay fixed via constant
   padding-left (no collapsed centering). Labels stay at their
   expanded layout position and clip/fade in place — never
   max-width. Toggle via the hover-revealed edge handle; state in
   localStorage.
   ============================================================ */

pb-sidebar { display: contents; }

/* ---- Sprunglink „Zum Inhalt springen" (WCAG 2.4.1, Audit 03.10.2026) ----
   Erster Tab-Stopp jeder Seite; unsichtbar, bis er den Fokus hat — dann
   eine Primär-Pille oben links über der Leiste. */
.pb-skip {
  position: fixed;
  left: var(--rail-gap, 0.75rem);
  top: var(--rail-gap, 0.75rem);
  z-index: calc(var(--z-sidebar) + 10);
  padding: 0.5rem 0.875rem;
  border-radius: var(--r-pill);
  background: var(--primary);
  color: var(--primary-text);
  font-weight: 600;
  font-size: var(--fs-sm);
  box-shadow: var(--shadow-md);
}
.pb-skip:not(:focus) {
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.side {
  position: fixed;
  left: var(--rail-gap);
  top: var(--rail-gap);
  bottom: var(--rail-gap);
  width: var(--rail-w);
  z-index: var(--z-sidebar);
  display: flex;
  flex-direction: column;
  /* Top uses shared --chrome-top-inset; L/R stay equal via --rail-pad
     (never animate padding — keeps expand X/Y stable). */
  padding: var(--chrome-top-inset) var(--rail-pad) var(--rail-pad);
  background: var(--surface);
  border: none;
  border-radius: var(--r-lg);
  transition: width var(--rail-dur) var(--rail-ease);
  /* overflow stays visible — edge toggle sits outside the rail */
}

/* ---- Edge toggle (hover-revealed) ---- */
.side-toggle {
  position: absolute;
  /* Centre on logo mid-Y (same formula as --chrome-mid-y, relative to .side). */
  top: calc(
    var(--chrome-top-inset)
    + var(--side-head-mt)
    + (var(--side-head-h) / 2)
    - (var(--side-toggle-h) / 2)
  );
  right: -0.8125rem;
  width: 1.5rem;
  height: var(--side-toggle-h);
  display: grid;
  place-items: center;
  border-radius: var(--r-lg);
  background: var(--surface);
  border: none;
  box-shadow: none;
  color: var(--text-soft);
  opacity: 0;
  transform: translateX(-0.5rem) scale(0.96);
  pointer-events: none;
  transition: opacity 0.18s var(--ease), transform 0.18s var(--ease), color 0.15s var(--ease);
  z-index: 2;
}
.side:hover .side-toggle,
/* Tastatur: der Umschalter ist auf fast jeder Seite der erste Tab-Stopp —
   mit Fokus muss er sichtbar sein (WCAG 2.4.7, Audit 03.10.2026, Punkt 6). */
.side-toggle:focus-visible,
.side:has(:focus-visible) .side-toggle { opacity: 1; transform: none; pointer-events: auto; }

/* Touch: ohne Hover gäbe es keinen Weg, die Rail je auszuklappen —
   der Umschalter bleibt dort dauerhaft sichtbar und wird auf ein
   volles Touch-Ziel verbreitert. Betrifft in der Praxis das Tablet;
   unter 768px ist die Rail ohnehin durch die Topbar ersetzt. */
@media (hover: none) {
  .side-toggle {
    opacity: 1;
    transform: none;
    pointer-events: auto;
    width: var(--touch);
    right: -1.4375rem;
  }
}
.side-toggle:hover { color: var(--text); }
.side-toggle svg {
  width: 14px;
  height: 14px;
  font-size: 0.875rem;
  transition: transform 0.2s var(--ease);
}
html.rail-expanded .side-toggle svg { transform: rotate(180deg); }

/* ---- Header (logo + wordmark) ----
   Fixed height in both states so logo/nav Y never shift when the
   wordmark appears. Expanded: Pingbook left, P logo right (logo X
   may move; Y must not). Wordmark is absolutely pinned at its
   expanded left so expand only clips/fades it — no max-width reflow. */
/* Logo + Wortmarke sind ein Link zur Startseite (05.10.2026): sieht aus wie vorher. */
a.side-head { color: inherit; text-decoration: none; }
.mtop-home { display: contents; color: inherit; text-decoration: none; }
.side-head {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0;
  height: var(--side-head-h);
  min-height: var(--side-head-h);
  max-height: var(--side-head-h);
  padding-left: var(--rail-logo-pl);
  flex: 0 0 auto;
  /* Shared with .main-top via --chrome-mid-y — identical in expand/collapse. */
  margin-top: var(--side-head-mt);
  box-sizing: border-box;
  overflow: hidden; /* clip wordmark as rail width eases; keep head height fixed */
}
html:not(.rail-expanded) .side-head {
  justify-content: center;
  padding-left: 0;
}
/* Expanded: logo right; wordmark sits absolute at nav glyph column. */
html.rail-expanded .side-head {
  justify-content: flex-end;
  padding-left: calc(var(--rail-btn-ml) + var(--rail-icon-pl));
  padding-right: var(--rail-btn-ml);
}
.pb-logo {
  flex: 0 0 auto;
  width: var(--rail-logo);
  height: var(--rail-logo);
  object-fit: contain;
  display: block;
  user-select: none;
  position: relative;
  z-index: 1; /* above fading wordmark while logo parks right */
}
/* Expanded only: slight optical inset from the flush-right park. */
html.rail-expanded .pb-logo {
  margin-right: 0.5rem;
}

/* ---- Reveal-on-expand text (labels, wordmark, account meta) ----
   Natural expanded layout always — no max-width (that reflows glyphs).
   Parents (.side-head / .nav-ic / .account-btn) clip via overflow as
   width eases; labels only fade opacity. Same timing as rail width. */
.side-word,
.nav-label,
.av-meta {
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--rail-dur) var(--rail-ease),
    visibility var(--rail-dur) var(--rail-ease);
}
.side-word {
  position: absolute;
  left: calc(var(--rail-btn-ml) + var(--rail-icon-pl));
  top: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: calc(var(--fs-h3) * 0.95);
  pointer-events: none;
}
html.rail-expanded .side-word {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
/* Label eine Textstufe dunkler als der Glyph (Wunsch Mathias, 29.08.) —
   die Zeile erbt --text-soft, das Wort zieht auf --text an. */
.nav-label { color: var(--text); }
html.rail-expanded .nav-label { opacity: 1; visibility: visible; }
html.rail-expanded .av-meta   { opacity: 1; visibility: visible; }

/* ---- Nav ----
   Icon column is a FIXED slot: same margin-left + padding-left in both
   states (equals the collapsed visual centre). Never justify-content:
   center when collapsed — that + padding transition made glyphs jump
   left mid-expand. Gap stays constant (expanded value) so labels keep
   the same left offset; rows grow ONLY rightward via width + clip. */
.side-nav { display: flex; flex-direction: column; align-items: flex-start; gap: 0.125rem; margin-top: 0; flex: 0 0 auto; }
/* Menüpunkte werden nie gestaucht — nur die offene Sitzungsliste gibt nach (Design „Neu“ #18) */
.side-nav > .nav-ic, .nav-cat > .nav-ic, .side-nav > a, .side-nav > button { flex-shrink: 0; }
/* Offene Liste nutzt den Platz bis zum Konto-Knopf (02.09., Mathias:
   „wenn genug Platz nach unten ist, nicht scrollen müssen") — statt
   einer festen 16rem-Kappe darf NUR die Nav mit der offenen Kategorie
   schrumpfen (min-height 0 die Kette hinunter), erst dann scrollt die
   Liste in sich. Die übrigen Navs und der Konto-Knopf bleiben stehen. */
.side-nav:has(.nav-cat.open) { flex: 0 1 auto; min-height: 0; }
.side-nav:has(.nav-cat.open) .nav-cat.open { min-height: 0; flex: 0 1 auto; }
/* Extra space below the logo — constant in both rail states. */
.side-head + .side-nav { margin-top: 1.875rem; }
/* ---- Mitteilungs- und Profil-Gruppe: dünner Strich darüber ----
   Der Strich nimmt exakt die Geometrie einer Nav-Zeile an (linker Versatz
   --rail-btn-ml, Breite wie .nav-ic) und fährt in derselben Zeit mit,
   wenn die Leiste auf- oder zugeht — sonst zöge er als starrer Stummel
   neben den wachsenden Zeilen her. */
.side-nav + .side-nav--win,
.side-nav + .side-nav--account {
  position: relative;
  margin-top: 0.625rem;
  padding-top: 0.625rem;
}
.side-nav--win::before,
.side-nav--account::before {
  /* Beidseitig eingerückt — der Strich bleibt kürzer als die Zeile. */
  --divider-inset: 0.5rem;
  content: "";
  position: absolute;
  top: 0;
  left: calc(var(--rail-btn-ml) + var(--divider-inset));
  width: calc(var(--rail-btn) - 2 * var(--divider-inset));
  height: 0.0625rem;
  background: var(--border);
  transition: width var(--rail-dur) var(--rail-ease);
}
html.rail-expanded .side-nav--win::before,
html.rail-expanded .side-nav--account::before {
  width: calc(var(--rail-w) - 2 * var(--rail-pad) - 2 * var(--rail-btn-ml) - 2 * var(--divider-inset));
}
.nav-ic {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.875rem; /* CONSTANT — label X must match fully-open layout */
  width: var(--rail-btn);
  height: var(--rail-btn);
  margin-left: var(--rail-btn-ml);           /* centres the square in the collapsed rail */
  padding-left: var(--rail-icon-pl);         /* CONSTANT glyph x — both states */
  padding-right: 0;
  border-radius: var(--r-lg);
  color: var(--text-soft);
  position: relative;
  box-sizing: border-box;
  overflow: hidden; /* clip labels while width eases open */
  /* Do NOT transition padding/margin/gap — those must stay fixed for X stability */
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              width var(--rail-dur) var(--rail-ease);
}
/* Account chevron only when expanded; labels fade via opacity (not display). */
html:not(.rail-expanded) .av-more { display: none; }
html.rail-expanded .nav-ic {
  /* grow ONLY rightward: left edge + glyph padding unchanged.
     Subtract btn-ml twice so right inset matches left (pad + btn-ml). */
  width: calc(var(--rail-w) - 2 * var(--rail-pad) - 2 * var(--rail-btn-ml));
  padding-right: var(--rail-btn-ml);   /* breathing room for the chevron circle on the right */
}
/* Icon + unread-dot share a fixed glyph box so the dot stays on the icon
   in both collapsed and expanded rail states. */
.nav-ic > .nav-glyph {
  position: relative;
  flex: 0 0 var(--rail-icon);
  line-height: 0;
  display: grid;
  place-items: center;
}
/* Die Icons der Einträge sind inline-SVGs aus nav-motion.js (Klasse
   .nm), seit 05.10.2026 statt <pb-icon> — sie tragen die Hover-Gesten. */
.nav-ic > .nm { flex: 0 0 var(--rail-icon); }
.nav-ic svg {
  width: var(--rail-icon);
  height: var(--rail-icon);
  font-size: var(--rail-icon);
  display: block;
}
/* 03.09. (Mathias): Glyphen eine Stufe zurück — Ruhe in --text-faint,
   aktiv/Hover/offen in --text-soft (dem bisherigen Ruheton). Nur das
   Icon; Label und Fläche behalten ihre Farben. */
.nav-ic .nm { color: color-mix(in srgb, var(--text-soft) 55%, var(--surface)); } /* deutlich leichter als --text-faint */
.nav-ic:hover .nm,
.nav-ic.on .nm,
.nav-cat.open .nav-ic .nm { color: color-mix(in srgb, var(--text-soft) 70%, var(--text)); } /* 03.09.: eine Stufe dunkler als --text-soft */
.nav-ic:hover { background: var(--fill-hover); color: var(--text); }
.nav-ic.on { background: var(--fill-active); color: var(--text); }   /* active ≈ hover fill */
/* open accordion category (Osmo-style): highlighted like the active row */
.nav-cat.open .nav-ic { background: var(--fill-active); color: var(--text); }
/* Hover AUF dem aktiven/offenen Punkt: eine Stufe kräftiger, damit die
   Zeile auch dort noch reagiert (13.08.) */
.nav-ic.on:hover,
.nav-cat.open .nav-ic:hover { background: var(--fill-strong); }
/* active / open row: glyph in --text-soft (03.09.), Label bleibt --text */
/* expanded menu labels: Diatype (display font), medium weight.
   px (not rem): fixed label size, independent of fluid root. */
.nav-label { font-family: var(--font-display); font-size: 16px; font-weight: 500; letter-spacing: var(--track-body); flex: 0 0 auto; }
.nav-ic.on .nav-label { font-weight: 500; }

/* chevron-in-a-circle on the right — appears on the ACTIVE row and on HOVER
   (expanded only; no room in the collapsed square). White circle on the row's
   grey fill, like the reference. */
.nav-go {
  margin-left: auto;
  flex: 0 0 auto;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--surface-bright); /* a reveal element — true white */
  color: var(--text-soft);
  display: grid;
  place-items: center;
  overflow: hidden;                       /* the chevron slides in from behind the left edge */
  opacity: 0;
  transform: scale(0.2);                  /* circle pops in from a small dot */
  /* EXIT (hover out): the fade LAGS the shrink slightly, so the circle is
     visibly animated back small instead of just fading away */
  transition: opacity 0.18s var(--ease) 0.06s, transform 0.26s var(--ease);
}
/* the chevron animates SEPARATELY: slides in from the circle's left edge to
   the centre (clipped by overflow:hidden), a touch after the circle pops —
   matching the reference video. */
.nav-go svg {
  width: 15px;
  height: 15px;
  font-size: 0.9375rem;
  opacity: 0;
  transform: translateX(-0.625rem);
  transition: opacity 0.16s var(--ease) 0.05s, transform 0.28s var(--ease) 0.05s;
}
/* Dunkel (Vorlage Osmo, 12.08.): der Kreis liegt DUNKLER als die Zeile,
   das Zeichen hell — nicht heller Kreis mit gedaempftem Zeichen. */
[data-theme="dark"] .nav-go {
  background: var(--bg);
  color: var(--text);
}
html:not(.rail-expanded) .nav-go { display: none; }   /* collapsed square: no room */
html.rail-expanded .nav-ic.on .nav-go,
html.rail-expanded .nav-cat.open .nav-ic .nav-go,
html.rail-expanded .nav-ic:hover .nav-go {
  opacity: 1;
  transform: scale(1);
  /* ENTER (hover in): the dot shows up almost immediately, THEN grows —
     the small→large pop stays visible instead of hiding behind the fade */
  transition: opacity 0.1s var(--ease), transform 0.26s var(--ease);
}
html.rail-expanded .nav-ic.on .nav-go svg,
html.rail-expanded .nav-cat.open .nav-ic .nav-go svg,
html.rail-expanded .nav-ic:hover .nav-go svg {
  opacity: 1;
  transform: translateX(0);
}

@media (prefers-reduced-motion: reduce) {
  .nav-go { transform: none; transition: opacity 0.16s var(--ease); }
  .nav-go svg { transform: none; transition: opacity 0.16s var(--ease); }
}
/* unread dot — same top-right corner of the glyph box for every row
   (Nachrichten + Neuigkeiten share this exact position). */
.nav-ic .nav-dot {
  position: absolute;
  /* Ganze Pixel für Lage und Größe: Bruchteile zeichneten den Punkt als
     leicht schiefes Oval (Mathias 05.10.2026). */
  right: -3px;
  top: -1px;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--brand);
  /* Ohne Ring (Mathias 05.10.2026, ersetzt Audit 03.10.2026 Punkt 17):
     der Punkt ist Zierde — den Zustand nennt der Name des Knopfs
     („Nachrichten, 3 ungelesen“). */
  pointer-events: none;
}

/* ---- Icon-Gesten (05.10.2026, Mathias) ----
   Hover oder Tastaturfokus auf einem Eintrag spielt die Geste seines
   Icons einmal ganz ab (nav-motion.js setzt .is-play und räumt nach dem
   Ende ab). Regeln: Ruhebild = icons.js, eine Farbe (Teile trennen nur
   Aussparungen, so breit wie die Lücke der Report-Balken: Ring 3,25 =
   sichtbar 1,625), gedämpftes Ausschwingen, nur transform/opacity/
   stroke-width. Der Ordner (Anfragen) ist ein Zustand (.is-open), kein
   Abspielen. Bewegung reduzieren: base.css nimmt Dauern auf ~0, die
   Gesten startet nav-motion.js dann gar nicht erst. */
.nav-ic svg.nm { overflow: visible; }
.nm .nm-lens, .nm .nm-glint, .nm .nm-flap, .nm .nm-ribbon, .nm .nm-bubble,
.nm .nm-bell-body, .nm .nm-clap, .nm .nm-board, .nm .nm-head, .nm .nm-shoulders { transform-box: view-box; }
/* In Ruhe das Original, in Bewegung die Fassung aus Teilen (Masken). */
.nm .nm-moving { display: none; }
.nm.is-play .nm-moving, .nm.is-moving .nm-moving { display: inline; }
.nm.is-play .nm-rest, .nm.is-moving .nm-rest { display: none; }
.nm-instant, .nm-instant * { transition: none !important; }

/* Suchen · Umschauen: Lupe schwenkt links und rechts, Lichtbogen übers Glas */
.nm-search .nm-lens { transform-origin: 11px 11px; }
.nm-search .nm-glint { stroke-dasharray: 1 1; stroke-dashoffset: 1; opacity: 0; }
.nm-search.is-play .nm-lens  { animation: nm-lens 760ms cubic-bezier(.45, 0, .55, 1) both; }
.nm-search.is-play .nm-glint { animation: nm-glint 760ms cubic-bezier(.45, 0, .55, 1) both; }
@keyframes nm-lens {
  0%   { transform: translate(0, 0) rotate(0deg); animation-timing-function: cubic-bezier(.3, 0, .2, 1); }
  26%  { transform: translate(-1.6px, -.7px) rotate(-12deg); }
  56%  { transform: translate(1.4px, -.9px) rotate(9deg); }
  80%  { transform: translate(-.3px, .15px) rotate(-2deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes nm-glint {
  0%, 20%   { stroke-dashoffset: 1; opacity: 0; }
  24%       { stroke-dashoffset: .85; opacity: 1; }
  48%       { stroke-dashoffset: 0; opacity: 1; }
  70%       { stroke-dashoffset: -1; opacity: 1; }
  72%, 100% { stroke-dashoffset: -1; opacity: 0; }
}

/* Anfragen · Aufscheren (Zustand): die Vorderseite bleibt unten stehen
   und schert oben nach rechts; der Ring in der Maske schneidet den Spalt */
.nm-inbox .nm-ring { fill: none; stroke: #000; stroke-width: 0; }
.nm-inbox .nm-flap { transform-origin: 12px 21.5px; transition: transform 300ms var(--ease), stroke-width 200ms var(--ease) 100ms; }
.nm-inbox.is-open .nm-flap { transform: skewX(-12deg) scaleY(.93); transition: transform 480ms cubic-bezier(.34, 1.56, .64, 1), stroke-width 140ms var(--ease); }
.nm-inbox.is-open .nm-ring { stroke-width: 3.25; }

/* Merkliste · Zupfen: das Band wird gezogen, schnellt zurück, pendelt aus */
.nm-bookmark .nm-ribbon { transform-origin: 12px 2.5px; }
.nm-bookmark.is-play .nm-ribbon { animation: nm-ribbon 820ms cubic-bezier(.45, 0, .55, 1) both; }
@keyframes nm-ribbon {
  0%   { transform: rotate(0deg) scale(1, 1); animation-timing-function: cubic-bezier(.3, 0, .15, 1); }
  18%  { transform: rotate(0deg) scale(.9, 1.13); }
  38%  { transform: rotate(-5deg) scale(1.05, .95); }
  56%  { transform: rotate(3.5deg) scale(.98, 1.035); }
  73%  { transform: rotate(-1.6deg) scale(1.01, .99); }
  88%  { transform: rotate(.5deg) scale(1, 1); }
  100% { transform: rotate(0deg) scale(1, 1); }
}

/* Freelancer Report · Nachzählen: Balken fallen und steigen als Welle */
.nm-chart .nm-bar { transform-box: fill-box; transform-origin: 50% 100%; }
.nm-chart.is-play .nm-b1 { animation: nm-b1 960ms cubic-bezier(.45, 0, .55, 1) both; }
.nm-chart.is-play .nm-b2 { animation: nm-b2 960ms cubic-bezier(.45, 0, .55, 1) both; }
.nm-chart.is-play .nm-b3 { animation: nm-b3 960ms cubic-bezier(.45, 0, .55, 1) both; }
@keyframes nm-b1 {
  0%        { transform: scaleY(1); animation-timing-function: cubic-bezier(.3, 0, .2, 1); }
  13%       { transform: scaleY(.5); }
  33%       { transform: scaleY(1.24); }
  50%       { transform: scaleY(.9); }
  65%       { transform: scaleY(1.04); }
  78%, 100% { transform: scaleY(1); }
}
@keyframes nm-b2 {
  0%, 9%    { transform: scaleY(1); animation-timing-function: cubic-bezier(.3, 0, .2, 1); }
  23%       { transform: scaleY(.42); }
  43%       { transform: scaleY(1.1); }
  59%       { transform: scaleY(.95); }
  73%       { transform: scaleY(1.02); }
  86%, 100% { transform: scaleY(1); }
}
@keyframes nm-b3 {
  0%, 18%   { transform: scaleY(1); animation-timing-function: cubic-bezier(.3, 0, .2, 1); }
  33%       { transform: scaleY(1.9); }
  52%       { transform: scaleY(.72); }
  68%       { transform: scaleY(1.14); }
  82%       { transform: scaleY(.97); }
  94%, 100% { transform: scaleY(1); }
}

/* Nachrichten · Sprechen: Punkte erscheinen nacheinander, die Blase holt
   bei jedem Punkt Luft (Silbentakt), zum Schluss eine leise Tipp-Welle */
.nm-chat .nm-bubble { transform-origin: 5px 20.5px; }
.nm-chat .nm-dot { transform-box: fill-box; transform-origin: 50% 50%; }
.nm-chat.is-play .nm-bubble { animation: nm-bubble 1050ms cubic-bezier(.45, 0, .55, 1) both; }
.nm-chat.is-play .nm-d1 { animation: nm-d1 1050ms cubic-bezier(.45, 0, .55, 1) both; }
.nm-chat.is-play .nm-d2 { animation: nm-d2 1050ms cubic-bezier(.45, 0, .55, 1) both; }
.nm-chat.is-play .nm-d3 { animation: nm-d3 1050ms cubic-bezier(.45, 0, .55, 1) both; }
@keyframes nm-bubble {
  0%   { transform: scale(1, 1); }
  9%   { transform: scale(.965, 1.03); }
  22%  { transform: scale(1.055, .955); }
  30%  { transform: scale(.985, 1.015); }
  38%  { transform: scale(1.045, .965); }
  46%  { transform: scale(.99, 1.01); }
  54%  { transform: scale(1.04, .97); }
  65%  { transform: scale(.985, 1.012); }
  78%  { transform: scale(1.006, .996); }
  100% { transform: scale(1, 1); }
}
@keyframes nm-d1 {
  0%        { transform: translateY(0) scale(1); animation-timing-function: cubic-bezier(.5, 0, .75, 0); }
  8%, 15%   { transform: translateY(0) scale(0); animation-timing-function: cubic-bezier(.2, .8, .3, 1); }
  22%       { transform: translateY(-1px) scale(1.25); }
  30%, 70%  { transform: translateY(0) scale(1); }
  77%       { transform: translateY(-.7px) scale(1); }
  85%, 100% { transform: translateY(0) scale(1); }
}
@keyframes nm-d2 {
  0%        { transform: translateY(0) scale(1); animation-timing-function: cubic-bezier(.5, 0, .75, 0); }
  8%, 31%   { transform: translateY(0) scale(0); animation-timing-function: cubic-bezier(.2, .8, .3, 1); }
  38%       { transform: translateY(-1px) scale(1.25); }
  46%, 75%  { transform: translateY(0) scale(1); }
  82%       { transform: translateY(-.7px) scale(1); }
  90%, 100% { transform: translateY(0) scale(1); }
}
@keyframes nm-d3 {
  0%        { transform: translateY(0) scale(1); animation-timing-function: cubic-bezier(.5, 0, .75, 0); }
  8%, 47%   { transform: translateY(0) scale(0); animation-timing-function: cubic-bezier(.2, .8, .3, 1); }
  54%       { transform: translateY(-1px) scale(1.25); }
  62%, 80%  { transform: translateY(0) scale(1); }
  87%       { transform: translateY(-.7px) scale(1); }
  95%, 100% { transform: translateY(0) scale(1); }
}

/* Neuigkeiten · Läuten: die Glocke schwingt aus, der Klöppel hängt nach
   und schwingt weiter als sie */
.nm-bell .nm-bell-body, .nm-bell .nm-clap { transform-origin: 12px 3px; }
.nm-bell.is-play .nm-bell-body { animation: nm-bell 1150ms cubic-bezier(.45, 0, .55, 1) both; }
.nm-bell.is-play .nm-clap { animation: nm-clap 1150ms cubic-bezier(.45, 0, .55, 1) both; }
@keyframes nm-bell {
  0%   { transform: rotate(0deg); animation-timing-function: cubic-bezier(.3, 0, .2, 1); }
  12%  { transform: rotate(15deg); }
  28%  { transform: rotate(-12deg); }
  43%  { transform: rotate(8.5deg); }
  57%  { transform: rotate(-5.5deg); }
  70%  { transform: rotate(3deg); }
  82%  { transform: rotate(-1.2deg); }
  92%  { transform: rotate(.4deg); }
  100% { transform: rotate(0deg); }
}
@keyframes nm-clap {
  0%, 5% { transform: rotate(0deg); animation-timing-function: cubic-bezier(.3, 0, .2, 1); }
  19%  { transform: rotate(20deg); }
  35%  { transform: rotate(-16deg); }
  50%  { transform: rotate(10.5deg); }
  64%  { transform: rotate(-6deg); }
  77%  { transform: rotate(3deg); }
  89%  { transform: rotate(-.9deg); }
  100% { transform: rotate(0deg); }
}

/* Freelancer-Profil · Hallo sagen: Klemmbrett hebt sich, der Kopf neigt
   sich gegenläufig, die Schultern zucken kurz */
.nm-user .nm-board { transform-origin: 12px 22.1px; }
.nm-user .nm-head { transform-origin: 12px 14.2px; }
.nm-user .nm-shoulders { transform-origin: 12px 18.7px; }
.nm-user.is-play .nm-board     { animation: nm-board 900ms cubic-bezier(.45, 0, .55, 1) both; }
.nm-user.is-play .nm-head      { animation: nm-head 900ms cubic-bezier(.45, 0, .55, 1) both; }
.nm-user.is-play .nm-shoulders { animation: nm-shoulders 900ms cubic-bezier(.45, 0, .55, 1) both; }
@keyframes nm-board {
  0%   { transform: translateY(0) rotate(0deg); animation-timing-function: cubic-bezier(.3, 0, .2, 1); }
  22%  { transform: translateY(-1.1px) rotate(-5deg); }
  44%  { transform: translateY(0) rotate(2.5deg); }
  62%  { transform: translateY(0) rotate(-1deg); }
  80%  { transform: translateY(0) rotate(.3deg); }
  100% { transform: translateY(0) rotate(0deg); }
}
@keyframes nm-head {
  0%, 10% { transform: rotate(0deg); animation-timing-function: cubic-bezier(.3, 0, .2, 1); }
  32%  { transform: rotate(16deg); }
  52%  { transform: rotate(-7deg); }
  68%  { transform: rotate(3.5deg); }
  84%  { transform: rotate(-1deg); }
  100% { transform: rotate(0deg); }
}
@keyframes nm-shoulders {
  0%, 12%   { transform: translateY(0); }
  28%       { transform: translateY(-.6px); }
  44%       { transform: translateY(.15px); }
  58%, 100% { transform: translateY(0); }
}

/* ---- Kategorie-Submenu (Osmo dash-cat): Profil-Abschnitte,
   Pingbook-AI-Sitzungen ---- */
.nav-cat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
}
.nav-sub {
  display: grid;
  min-height: 0;
  grid-template-rows: 0fr;
  /* Wächst MIT der Leiste (05.10.2026, Mathias: beim Ausklappen stand die
     Liste samt Trennlinie sofort in voller Breite über der noch schmalen
     Leiste). Die Hülle folgt der animierten Breite von .nav-cat (100 %)
     und schneidet ab; die Liste darin liegt schon in Endbreite, damit
     kein Text umbricht. --rail-w springt dagegen sofort aufs Ziel. */
  width: calc(100% - 2 * var(--rail-btn-ml));
  overflow: hidden;
  margin-left: var(--rail-btn-ml);
  transition: grid-template-rows 0.32s var(--ease);
}
.nav-sub-in { width: calc(var(--rail-w) - 2 * var(--rail-pad) - 2 * var(--rail-btn-ml)); }
.nav-cat.open .nav-sub { grid-template-rows: 1fr; }
/* Offen: die Sitzungsliste scrollt in sich, statt die Rail zu sprengen —
   so sind auch aeltere Suchlaeufe erreichbar (12.08.). Feste Höhe (Design
   „Neu“ #18, 01.10.2026): die Menüpunkte darunter werden nicht gestaucht. */
.nav-cat.open .nav-sub-in { overflow-y: auto; scrollbar-width: thin; max-height: min(40vh, 22rem); }
.nav-sub-in {
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 0.0625rem;
  /* NO vertical padding here — padding wouldn't collapse with the 0fr track
     and would leave a rest gap under the closed row. Spacing lives on the
     children (margins clip together with the content). */
  padding: 0;
}
.nav-sub-in > .nav-sub-item:first-child { margin-top: 0.75rem; }
/* Weicher Abschluss überm Trennstrich (20.08., Mathias): läuft die
   Liste über, deckt ein Verlauf zum Leisten-Grund das angeschnittene
   Item ab. Sticky am Boden des Scrollports; sichtbar nur mit .has-more
   (pb-sidebar.js setzt die Klasse bei Overflow, bis ans Ende gescrollt
   ist). Negative margin-top holt den Verlauf ÜBER die letzten Zeilen —
   er nimmt selbst keinen Platz ein. */
.nav-sub-in::after {
  content: "";
  flex: none;
  position: sticky;
  bottom: 0;
  height: 1.75rem;
  margin-top: -1.8125rem; /* 1.75rem + Flex-Gap */
  background: linear-gradient(to bottom, transparent, var(--surface) 88%);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
}
.nav-sub-in.has-more::after { opacity: 1; }
/* hairline under the open list (Osmo reference) — inside the clipped
   container, so it collapses together with the accordion */
/* Luft unter der letzten Pille INNERHALB der Scrollflaeche — die
   Trennlinie selbst sitzt unten auf der stehenden Huelle (.nav-sub::after),
   damit sie beim Scrollen der Liste stehen bleibt (Vorlage, 12.08.). */
.nav-cat.open .nav-sub-in { padding-bottom: 0.75rem; }
.nav-cat.open .nav-sub::after {
  content: "";
  height: 1px;
  margin: 0 0.625rem 0.5rem;
  background: var(--border);
}
html:not(.rail-expanded) .nav-sub { display: none; }

.nav-sub-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* pill hugs its content — equal padding L/R so the active capsule
     reads balanced (number wasn’t right-aligned in a wide box anymore).
     max-width kappt lange Sitzungstitel an der Rail-Kante (die Ellipsis
     sitzt auf .nav-sub-label). */
  width: auto;
  max-width: calc(100% - var(--rail-btn-ml));
  box-sizing: border-box;
  align-self: flex-start;
  margin-left: var(--rail-btn-ml);
  padding: 0.4375rem 1.125rem 0.4375rem 0.75rem;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-soft);
  font-family: var(--font-body);
  font-size: 15px; /* px: root rem ≠ 16px — see .nav-label */
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.nav-sub-item:hover { color: var(--text); background: var(--fill-hover); }
/* active: compact pill, same weight as siblings (Osmo reference) */
.nav-sub-item.on { color: var(--text); background: var(--fill-active); }
/* Die frühere Osmo-Branch-Line (Hover-Strich, der die Zeile einschob)
   ist entfallen (20.08., Mathias): ALLE Untermenü-Zeilen verhalten sich
   wie die Suchen-Liste — nur heller werden, nichts wandert. */

.nav-sub-count {
  flex: none;
  width: 1.125rem;
  display: grid;
  place-items: center;
  color: var(--text-faint);
  font-family: var(--font-display); /* Zahlen in der Diatype (Design „Neu“ #96) */
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  text-align: center;
}
.nav-sub-item.on .nav-sub-count { color: var(--text-soft); }
/* Icon statt Nummer in derselben Spalte (Agent-Sitzungen) — gleiche
   Breite, damit die Labels fluchten. */
.nav-sub-ic {
  flex: none;
  width: 1.125rem;
  display: grid;
  place-items: center;
  color: var(--text-faint);
  line-height: 0;
}
.nav-sub-ic svg { width: 15px; height: 15px; display: block; }
.nav-sub-item.on .nav-sub-ic,
.nav-sub-item:hover .nav-sub-ic { color: var(--text-soft); }
.nav-sub-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.side-grow { flex: 1 1 auto; }

/* ---- Account (bottom) ----
   Same fixed avatar slot as nav glyphs: constant padding-left, grow right. */
.side-bottom { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-start; margin-bottom: var(--rail-btn-ml); }
.account-btn {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.875rem; /* CONSTANT — account name X matches fully-open layout */
  width: var(--rail-btn);
  height: var(--rail-btn);
  margin-left: var(--rail-btn-ml);
  padding-left: var(--rail-av-pl);             /* CONSTANT avatar x — both states */
  padding-right: 0;
  border-radius: var(--r-lg);
  box-sizing: border-box;
  overflow: hidden; /* clip meta while width eases open */
  font-family: var(--font-display); /* ABC Diatype — UA button font would be system */
  transition: background var(--dur) var(--ease),
              width var(--rail-dur) var(--rail-ease);
}
html.rail-expanded .account-btn {
  width: calc(var(--rail-w) - 2 * var(--rail-pad) - 2 * var(--rail-btn-ml));
  padding-right: var(--rail-btn-ml);
}
.account-btn:hover { background: var(--fill-hover); }
.av-ini {
  flex: 0 0 auto;
  width: var(--rail-av);
  height: var(--rail-av);
  border-radius: 50%;
  background: var(--avatar);
  color: var(--text-soft);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: var(--fs-sm);
  overflow: hidden;
}
.av-meta {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  min-width: 0;
  overflow: hidden; /* ellipsis when fully open; clip mid-expand */
  text-align: left;
  flex: 1 1 auto;
}
.av-name {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
}
.av-more {
  flex: 0 0 auto;
  margin-left: auto;
  width: 1.25rem;
  height: 1.25rem;
  display: grid;
  place-items: center;
  color: var(--text-soft);
}
.av-more svg { width: 18px; height: 18px; display: block; }

/* ---- Mobile (≤720px) ----
   The rail drops out; a fixed top-bar island (logo + burger) replaces it.
   The burger opens <pb-mobile-menu> (styles/components/mobile-menu.css). */
.mtop { display: none; }
.pb-pblur, .pb-edge, .pb-tint { display: none; }
@media (max-width: 767.98px) {
  .side { display: none; }
  /* Kanten mobil (Mathias 07.10.2026): je Kante drei Ebenen.
     0. Progressive Blur (.pb-pblur, Osmo Supply; Mathias 07.10.2026 nach Vorschau A/B gewählt):
        fünf Weichzeichner-Ebenen, je mit einem Verlauf maskiert und
        gestaffelt, am Rand am stärksten — unter der Abdunkelung, damit
        Safaris Kantenprobe nie eine Blur-Ebene trifft.
     1. Abdunkelung (.pb-edge): --bg als background-color, deckend nur als
        Band am Rand (oben bis zur Oberkante der Kopfleiste, unten bis zur
        Unterkante der Auswahlleiste), dann weich ausgeblendet (Smoothstep)
        bis dorthin, wo der Inhalt in Ruhe beginnt (oben 1rem unter der
        Insel) bzw. endet (unten die 4.75rem-Reserve aus layout.css) — in Ruhe
        dunkelt nichts ab; im Dunkelmodus dunkel.
     2. Safari-Farbhinweis (.pb-tint, unsichtbar, ganz oben im Stapel).
     Oben immer (mit Hinweis-Streifen wachsen die Flächen mit), unten nur,
     wenn die Auswahlleiste unten steht (nicht im Agent-Gespräch).

     Safari (iOS 26) füllt Statusleiste und untere Leiste mit der Farbe des
     obersten festen Elements an der Kante (WebKit: Punkt 4px innerhalb, in
     der Mitte; pointer-events werden ignoriert, visibility nicht). Es liest
     nur dessen eigene background-color — kein Verlauf, kein Pseudo-Element;
     backdrop-filter heißt „mehrere Farben“ und damit keine Füllung (darum
     lief beim Blur der Inhalt ungedämpft unter die Leisten). Ein
     bildschirmfüllendes Fenster (Talent-Fenster, Vorhang, Mobil-Menü) behält
     dagegen die vorige Farbe. Der Hinweis liegt deshalb über allen Fenstern:
     fest, 0px am Rand, volle Breite, höher als 10px, mit der Farbe, die an
     der Kante gerade zu sehen ist (Zustände unten), und ist per Maske
     unsichtbar — Safari wertet die Maske nicht aus. */
  .pb-pblur, .pb-edge, .pb-tint {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    pointer-events: none;
  }
  .pb-pblur {
    z-index: calc(var(--z-sidebar) - 2);
    isolation: isolate;
    contain: paint;
    overflow: hidden;
    transform: translateZ(0);
  }
  /* rund doppelt so hoch wie Insel samt Abstand: der Verlauf blurt nur die
     äußere Hälfte und läuft knapp hinter der Insel aus */
  .pb-pblur--top { top: 0; height: calc(2 * (env(safe-area-inset-top, 0px) + var(--strips-h, 0px) + var(--rail-gap) + var(--mtop-h)) + 1.5rem); }
  .pb-pblur--bottom { bottom: 0; height: calc(2 * (env(safe-area-inset-bottom, 0px) + var(--rail-gap) + 3.5rem) + 1.5rem); }
  .pb-pblur-l { position: absolute; inset: 0; --pb-dir: to bottom; }
  .pb-pblur--top .pb-pblur-l { --pb-dir: to top; }
  .pb-pblur-l.is--1 {
    /* 07.10.2026 (Mathias): Unschärfe je Stufe 1,5-fach (vorher 0,094–1,5em) */
    -webkit-backdrop-filter: blur(0.140625em); backdrop-filter: blur(0.140625em);
    -webkit-mask: linear-gradient(var(--pb-dir), transparent 50%, black 62.5% 75%, transparent 87.5%);
    mask: linear-gradient(var(--pb-dir), transparent 50%, black 62.5% 75%, transparent 87.5%);
  }
  .pb-pblur-l.is--2 {
    -webkit-backdrop-filter: blur(0.28125em); backdrop-filter: blur(0.28125em);
    -webkit-mask: linear-gradient(var(--pb-dir), transparent 62.5%, black 75% 87.5%, transparent 100%);
    mask: linear-gradient(var(--pb-dir), transparent 62.5%, black 75% 87.5%, transparent 100%);
  }
  .pb-pblur-l.is--3 {
    -webkit-backdrop-filter: blur(0.5625em); backdrop-filter: blur(0.5625em);
    -webkit-mask: linear-gradient(var(--pb-dir), transparent 75%, black 87.5% 100%);
    mask: linear-gradient(var(--pb-dir), transparent 75%, black 87.5% 100%);
  }
  .pb-pblur-l.is--4 {
    -webkit-backdrop-filter: blur(1.125em); backdrop-filter: blur(1.125em);
    -webkit-mask: linear-gradient(var(--pb-dir), transparent 82%, black 92% 100%);
    mask: linear-gradient(var(--pb-dir), transparent 82%, black 92% 100%);
  }
  .pb-pblur-l.is--5 {
    -webkit-backdrop-filter: blur(2.25em); backdrop-filter: blur(2.25em);
    -webkit-mask: linear-gradient(var(--pb-dir), transparent 88%, black 100%);
    mask: linear-gradient(var(--pb-dir), transparent 88%, black 100%);
  }
  .pb-edge {
    z-index: calc(var(--z-sidebar) - 1);
    background-color: var(--bg);
    --edge-dir: to bottom;
    --edge-s: 50%;   /* deckender Teil ab der Kante */
    /* die Maske trägt allein Deckkraft, keine Farbe */
    --edge-mask: linear-gradient(var(--edge-dir),
      black var(--edge-s),
      rgb(0 0 0 / 0.84) calc(var(--edge-s) + (100% - var(--edge-s)) * 0.25),
      rgb(0 0 0 / 0.5) calc(var(--edge-s) + (100% - var(--edge-s)) * 0.5),
      rgb(0 0 0 / 0.16) calc(var(--edge-s) + (100% - var(--edge-s)) * 0.75),
      transparent);
    -webkit-mask-image: var(--edge-mask);
    mask-image: var(--edge-mask);
  }
  .pb-edge--top {
    top: 0;
    height: calc(env(safe-area-inset-top, 0px) + var(--strips-h, 0px) + var(--rail-gap) + var(--mtop-h) + 1rem);
    --edge-s: calc(env(safe-area-inset-top, 0px) + var(--strips-h, 0px) + var(--rail-gap));
  }
  .pb-edge--bottom {
    bottom: 0;
    height: calc(env(safe-area-inset-bottom, 0px) + 4.75rem);
    --edge-dir: to top;
    --edge-s: calc(env(safe-area-inset-bottom, 0px) + var(--rail-gap));
  }
  .pb-tint {
    z-index: calc(var(--z-toast) - 1);
    height: max(1rem, 12px);
    background-color: var(--bg);
    -webkit-mask-image: linear-gradient(transparent, transparent);
    mask-image: linear-gradient(transparent, transparent);
  }
  .pb-tint--top { top: 0; }
  .pb-tint--bottom { bottom: 0; }
  .pb-pblur--bottom, .pb-edge--bottom, .pb-tint--bottom { display: none; }
  html:has(pb-selection-bar .sel-zone):not(:has(.app-main.is-agent)) :is(.pb-pblur--bottom, .pb-edge--bottom, .pb-tint--bottom) { display: block; }
  /* Farbe an der Kante je Zustand (in dieser Reihenfolge, Spezifität beachten):
     Popover mit 5-%-Schleier · Talent-Fenster (unten sein Blatt) · Fenster mit
     Vorhang (--scrim über --bg) · deckender Vorhang des Anmeldefensters ·
     Mobil-Menü (Blatt in --surface). */
  html.overlay-open .pb-tint { background-color: color-mix(in srgb, black calc(var(--dim-amount, 0.05) * 100%), var(--bg)); }
  html:has(.td-stage .td2) .pb-tint--bottom { background-color: var(--surface); }
  html:is(.scroll-locked, :has(.pb-lightbox.open)) .pb-tint { background-color: var(--scrim-edge); }
  html:has(pb-auth-gate .am-scrim--curtain.open) .pb-tint { background-color: var(--bg); }
  html:has(pb-mobile-menu .mm.open) .pb-tint { background-color: var(--surface); }
  .mtop {
    position: fixed;
    left: max(var(--rail-gap), env(safe-area-inset-left, 0px));
    right: max(var(--rail-gap), env(safe-area-inset-right, 0px));
    /* Notch / Dynamic Island: sonst liegt die Insel im Querformat
       unter der Kamera-Aussparung. */
    top: calc(var(--rail-gap) + env(safe-area-inset-top, 0px));
    height: var(--mtop-h);
    z-index: var(--z-sidebar);
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0 0.375rem 0 1rem;
    background: var(--surface);
    border-radius: var(--r-lg);
  }
  .mtop .pb-logo {
    flex: none;
    width: 1.5rem;
    height: 1.5rem;
    object-fit: contain;
    user-select: none;
  }
  .mtop-word {
    font-family: var(--font-display);
    font-weight: 800;
    font-size: calc(var(--fs-h3) * 0.95);
    white-space: nowrap;
  }
  .mtop-burger {
    position: relative;
    margin-left: auto;
    flex: none;
    width: var(--touch);
    height: var(--touch);
    display: grid;
    place-items: center;
    border-radius: var(--r-lg);
    color: var(--text);
    transition: background var(--dur) var(--ease);
  }
  .mtop-burger:active { background: var(--surface-2); }
  /* Osmo toggle: two bars; on hover they swap positions. The morph to X
     happens on the twin button inside <pb-mobile-menu> (same geometry). */
  .mtop-burger .mtop-bar {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 1.375rem;
    height: 0.125rem;
    margin: -0.0625rem 0 0 -0.6875rem;
    background: currentColor;
    transition: transform 0.6s var(--ease);
    transform: translateY(-0.25rem) rotate(0.001deg);
  }
  .mtop-burger .mtop-bar:nth-child(2) { transform: translateY(0.15rem) rotate(0.001deg); }
  .mtop-burger:hover .mtop-bar { transform: translateY(0.25rem) rotate(0.001deg); }
  .mtop-burger:hover .mtop-bar:nth-child(2) { transform: translateY(-0.15rem) rotate(0.001deg); }
  .mtop-burger .mtop-dot {
    position: absolute;
    right: 0.625rem;
    top: 0.625rem;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--brand); /* wie .nav-dot: ganze Pixel, kein Ring */
    pointer-events: none;
  }
}
