/* ============================================================
   <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 { display: none; }
@media (max-width: 767.98px) {
  .side { display: none; }
  /* Progressive Blur (Osmo Supply, Mathias 05.10.2026): fünf Weichzeichner-
     Ebenen, je mit einem Verlauf maskiert und gestaffelt — der Inhalt, der
     unter den schwebenden Inseln durchscrollt, lenkt nicht ab. Oben hinter
     der Kopfleiste (umgedreht: oben am stärksten), unten hinter der
     Auswahlleiste (Osmo-Richtung: unten am stärksten; nur wenn die Leiste
     unten steht, nicht im Agent-Gespräch mit eigener Eingabe-Bar). Die Fläche
     ist 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 {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    z-index: calc(var(--z-sidebar) - 1);
    pointer-events: none;
    isolation: isolate;
    contain: paint;
    overflow: hidden;
    transform: translateZ(0);
  }
  /* Über die Kante hinaus (Mathias 06.10.2026: in Safari hörte der Blur oben
     und unten zu früh auf): Safari zeichnet die Seite unter Statusleiste und
     schwebender Adressleiste weiter, feste Elemente enden aber am
     Anzeigebereich. --pb-ext ragt deshalb hinaus und ist dort voll
     weichgezeichnet (.pb-pblur-x); der sichtbare Verlauf (die fünf Ebenen)
     bleibt unverändert — in Chrome liegt der Überstand außerhalb des Bilds. */
  .pb-pblur { --pb-ext: 6rem; }
  .pb-pblur--top {
    top: calc(-1 * var(--pb-ext));
    height: calc(2 * (env(safe-area-inset-top, 0px) + var(--rail-gap) + var(--mtop-h)) + 1.5rem + var(--pb-ext));
  }
  html.has-strips .pb-pblur--top {
    height: calc(2 * (env(safe-area-inset-top, 0px) + var(--rail-gap) + var(--mtop-h) + var(--strips-h, 0px)) + 1.5rem + var(--pb-ext));
  }
  .pb-pblur--bottom {
    bottom: calc(-1 * var(--pb-ext));
    height: calc(2 * (env(safe-area-inset-bottom, 0px) + var(--rail-gap) + 3.5rem) + 1.5rem + var(--pb-ext));
    display: none;
  }
  .pb-pblur--top .pb-pblur-l { inset: var(--pb-ext) 0 0 0; }
  .pb-pblur--bottom .pb-pblur-l { inset: 0 0 var(--pb-ext) 0; }
  .pb-pblur-x {
    position: absolute; left: 0; right: 0; height: var(--pb-ext);
    -webkit-backdrop-filter: blur(1.5em); backdrop-filter: blur(1.5em);
  }
  .pb-pblur--top .pb-pblur-x { top: 0; }
  .pb-pblur--bottom .pb-pblur-x { bottom: 0; }
  html:has(pb-selection-bar .sel-zone):not(:has(.app-main.is-agent)) .pb-pblur--bottom { display: block; }
  .pb-pblur-l { position: absolute; --pb-dir: to bottom; }
  .pb-pblur--top .pb-pblur-l { --pb-dir: to top; }
  .pb-pblur-l.is--1 {
    -webkit-backdrop-filter: blur(0.09375em); backdrop-filter: blur(0.09375em);
    -webkit-mask: linear-gradient(var(--pb-dir), #0000 50%, #000 62.5% 75%, #0000 87.5%);
    mask: linear-gradient(var(--pb-dir), #0000 50%, #000 62.5% 75%, #0000 87.5%);
  }
  .pb-pblur-l.is--2 {
    -webkit-backdrop-filter: blur(0.1875em); backdrop-filter: blur(0.1875em);
    -webkit-mask: linear-gradient(var(--pb-dir), #0000 62.5%, #000 75% 87.5%, #0000 100%);
    mask: linear-gradient(var(--pb-dir), #0000 62.5%, #000 75% 87.5%, #0000 100%);
  }
  .pb-pblur-l.is--3 {
    -webkit-backdrop-filter: blur(0.375em); backdrop-filter: blur(0.375em);
    -webkit-mask: linear-gradient(var(--pb-dir), #0000 75%, #000 87.5% 100%);
    mask: linear-gradient(var(--pb-dir), #0000 75%, #000 87.5% 100%);
  }
  .pb-pblur-l.is--4 {
    -webkit-backdrop-filter: blur(0.75em); backdrop-filter: blur(0.75em);
    -webkit-mask: linear-gradient(var(--pb-dir), #0000 82%, #000 92% 100%);
    mask: linear-gradient(var(--pb-dir), #0000 82%, #000 92% 100%);
  }
  .pb-pblur-l.is--5 {
    -webkit-backdrop-filter: blur(1.5em); backdrop-filter: blur(1.5em);
    -webkit-mask: linear-gradient(var(--pb-dir), #0000 88%, #000 100%);
    mask: linear-gradient(var(--pb-dir), #0000 88%, #000 100%);
  }
  .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;
  }
}
