/* ==========================================================================
   App shell / sidebar navigation
   ========================================================================== */
.app-shell {
  display: flex;
  min-height: 100vh;
}
@media (min-width: 681px) {
  /* Fixed to exactly the viewport height (not just min-height) so the flex
     column below it (.app-main-col) has a definite height to distribute
     between the topbar and .workspace-shell — required for the shell's
     `flex: 1; min-height: 0` to resolve to a real, bounded box instead of
     growing with its content. */
  .app-shell { height: 100vh; overflow: hidden; }
}

/* The sidebar now floats at the same tone as the page's cards (--sidebar-bg
   tracks --color-surface for each theme) instead of reading as a separately
   tinted instrument rail — wayfinding comes entirely from the active pill and
   hover states, not from a background-color boundary. */
.sidebar {
  width: var(--sidebar-width);
  flex-shrink: 0;
  background: var(--sidebar-bg);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: clamp(0.92rem, 1.4vh, 1.1rem) 1.1rem;
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--color-text);
}
.sidebar-brand-logo {
  /* Reserved layout box stays small — this is what the row/siblings actually see and
     react to — while `transform: scale()` renders the image visually much bigger
     without changing that box, so growing it further never pushes "Gargona", the
     nav below, or anything else out of place. */
  width: 2rem;
  height: 2rem;
  object-fit: contain;
  flex-shrink: 0;
  /* Порядок множителей важен: браузер применяет transform справа налево, поэтому
     сначала масштаб, а потом сдвиг — и -3px остаются тремя экранными пикселями.
     В обратном порядке сдвиг тоже масштабировался бы и дал бы 5.4px. */
  transform: translateY(-3px) scale(1.8);
}

/* Логотип по теме: чёрный знак на светлой, белый на тёмной.

   Раньше это делалось тайлвиндовскими `dark:hidden` и `hidden dark:block`, и
   именно поэтому в тёмной теме рядом с «Gargona» не было НИЧЕГО: tokens.css
   объявляет собственный `.hidden { display: none !important }` (обоснованно —
   см. комментарий там), а !important бьёт `dark:block` при любой специфичности.
   Белый логотип оставался скрыт в обеих темах, чёрный — скрыт в тёмной.

   Здесь тема читается из `:root.dark` — того же переключателя, которым живут все
   цвета панели, — поэтому правило не зависит ни от Tailwind, ни от порядка
   загрузки его CDN. */
.sidebar-brand-logo.for-dark { display: none; }
:root.dark .sidebar-brand-logo.for-light { display: none; }
:root.dark .sidebar-brand-logo.for-dark { display: block; }

.sidebar-nav {
  flex: 1;
  min-width: 0;
  padding: clamp(0.55rem, 0.8vh, 0.65rem) 0.75rem;
  display: flex;
  flex-direction: column;
  gap: clamp(0.12rem, 0.18vh, 0.15rem);
  /* Отступы сжимаются только на низком окне: на высоком clamp'ы стоят на своих
     максимумах, то есть ровно на исходных значениях макета. 17 пунктов + два
     заголовка + бренд + баннер пробного доступа + «Выход» в сумме чуть выше
     ноутбучной высоты, и эта пара десятых рема — вся разница между «влезло» и
     полосой прокрутки. overflow-y остаётся auto: на совсем низком окне полоса
     честнее, чем срезанные пункты. */
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  box-sizing: border-box;
}

.sidebar-section-title {
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sidebar-text-muted);
  padding: clamp(0.55rem, 0.85vh, 0.7rem) 0.6rem clamp(0.24rem, 0.35vh, 0.3rem);
}

.nav-link {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: clamp(0.42rem, 0.62vh, 0.48rem) 0.75rem;
  border-radius: var(--radius-full);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--sidebar-text);
  transition: background var(--transition-fast) var(--ease), color var(--transition-fast) var(--ease), transform var(--transition-fast) var(--ease);
}
/* :not(.nav-active) so this never competes with .nav-link.nav-active:hover below —
   before this, both rules matched a hovered active item at equal specificity
   (class+pseudo-class each) and this one, being a plain .nav-link:hover, was
   winning the `background` property by source order, washing the active pill
   out to a pale tint while its (still-white, unaffected) text/icon read as
   nearly invisible on top of it. */
.nav-link:not(.nav-active):hover { background: var(--sidebar-bg-hover); color: var(--color-text); }

/* Solid, gradient-filled active pill — the one bold color moment in an
   otherwise quiet sidebar, copied straight from the reference file's own
   `.nav a.active` rule. */
.nav-active {
  background: linear-gradient(135deg, var(--sidebar-accent), var(--color-primary-grad-end));
  color: var(--sidebar-on-accent);
  font-weight: 600;
  box-shadow: 0 8px 18px color-mix(in srgb, var(--color-primary-hover) 20%, transparent);
}
.nav-active .icon { color: var(--sidebar-on-accent); }

/* Active + hover — .nav-link.nav-active is a higher-specificity compound
   selector on purpose, so it always wins over .nav-link:not(.nav-active):hover
   regardless of source order. Only ever deepens the same gradient/shadow the
   active pill already has — never a lighter fill, never a color/opacity drop. */
.nav-link.nav-active:hover {
  color: var(--sidebar-on-accent);
  background: linear-gradient(135deg, var(--color-primary-hover), var(--sidebar-accent));
  box-shadow: 0 11px 22px color-mix(in srgb, var(--color-primary-hover) 30%, transparent);
  transform: translateY(-1px);
  opacity: 1;
}
.nav-link.nav-active:hover .icon { color: var(--sidebar-on-accent); opacity: 1; }
.nav-link.nav-active:focus-visible {
  color: var(--sidebar-on-accent);
  outline: 3px solid color-mix(in srgb, var(--color-primary) 45%, transparent);
  outline-offset: 2px;
}

/* Collapsible nav group ("Парсинг": Каналы/Чаты и группы/Спарсенные аккаунты) — the
   toggle is a <button class="nav-link"> so it inherits the exact same look/hover/
   collapsed-mode/tooltip behavior as a real link; only the extra reset below (no
   button chrome, full width, chevron) is new. */
.nav-group-toggle {
  width: 100%;
  background: none;
  border: none;
  /* Only the family: buttons do not inherit it, and the `font` shorthand that used to
     be here also wiped .nav-link's font-size/font-weight off this element — which is
     why "Парсинг" and "Модули" were set differently from the links under them. */
  font-family: inherit;
  cursor: pointer;
  text-align: left;
}
.nav-group-toggle .nav-chevron { margin-left: auto; flex-shrink: 0; transition: transform var(--transition-fast) var(--ease); }
.nav-group-toggle[aria-expanded="true"] .nav-chevron { transform: rotate(180deg); }
.nav-subgroup { display: flex; flex-direction: column; gap: clamp(0.12rem, 0.18vh, 0.15rem); }
.nav-sublink { padding-left: 2.6rem; }
html.sidebar-collapsed .nav-group-toggle .nav-chevron { display: none; }
html.sidebar-collapsed .nav-sublink { padding-left: 0; }

.sidebar-footer {
  padding: clamp(0.65rem, 0.9vh, 0.75rem);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.sidebar .icon-btn, .topbar-mobile .icon-btn, .mobile-nav .icon-btn, .mobile-nav .drawer-close-btn {
  color: var(--sidebar-text-muted);
}
.sidebar .icon-btn:hover, .topbar-mobile .icon-btn:hover,
.mobile-nav .icon-btn:hover, .mobile-nav .drawer-close-btn:hover {
  background: var(--sidebar-bg-hover);
  color: var(--color-text);
}

/* Mobile off-canvas nav — uses the same .drawer open/close JS (Escape, backdrop,
   click handling all key off ".drawer"), this class just mirrors it to the left. */
.drawer.mobile-nav {
  left: 0;
  right: auto;
  width: min(18rem, 82vw);
  transform: translateX(-100%);
  display: flex;
  flex-direction: column;
  padding: 0;
  background: var(--sidebar-bg);
}
.mobile-nav .border-b, .mobile-nav .border-t { border-color: var(--sidebar-border) !important; }
.mobile-nav > div:first-child span { color: var(--color-text); font-family: var(--font-display); }

.topbar-mobile {
  display: none;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--sidebar-border);
  background: var(--sidebar-bg);
  color: var(--color-text);
  position: sticky;
  top: 0;
  z-index: 35;
}
.topbar-mobile .font-semibold { font-family: var(--font-display); }

/* The layout bug this fixes: .app-shell is flex-row at every width, so without
   this the mobile topbar and .workspace-shell used to sit side by side as two
   narrow columns instead of the topbar spanning full width above the content.
   Threshold is phone-only (680px, below every common tablet width in either
   orientation) — a 15rem sidebar still leaves comfortable room for content on
   a real tablet, so collapsing it there just to a hamburger wasted the extra
   width instead of using it. */
@media (max-width: 680px) {
  .app-shell { flex-direction: column; }
  .sidebar { display: none; }
  .topbar-mobile { display: flex; }
}

.app-main-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
@media (min-width: 681px) {
  .app-main-col { min-height: 0; }
}

/* Persistent desktop topbar — page identity on the left (collapse toggle + title/
   description, sourced from each page's page_title/page_description blocks so
   nothing here is hardcoded per-route), utility cluster on the right (theme,
   language, account). Sticky so it stays reachable while a long page scrolls. */
.app-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem clamp(0.7rem, 2vw, 1.1rem);
  position: sticky;
  top: 0;
  /* Above .dm-workflow-nav (30), which is also sticky-to-top on every Компоненты page.
     This element has a z-index, so it forms a stacking context — its own dropdowns'
     z-index:60 is resolved INSIDE that context and can never lift them past the value
     set here. Raising this is what keeps the language and account menus on top when the
     step nav scrolls up under them. */
  z-index: 45;
  background: color-mix(in srgb, var(--color-bg) 92%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid transparent;
}
@media (max-width: 680px) {
  .app-topbar { display: none; }
}

.topbar-left {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}
.topbar-page { min-width: 0; }
.topbar-page-title {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--color-text);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar-page-desc {
  font-size: 0.8rem;
  color: var(--color-text-muted);
  margin-top: 0.05rem;
}
.topbar-inline-extra {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.35rem;
}
.topbar-inline-extra:empty { display: none; }

.topbar-right {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
}

/* Up to two real status chips (never fake/decorative) — small enough to sit
   inline with the page title without competing with it. */
.topbar-status-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.72rem;
  font-weight: 500;
  padding: 0.2rem 0.55rem;
  border-radius: var(--radius-full);
  background: var(--color-surface-muted);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.topbar-status-dot { width: 0.4rem; height: 0.4rem; border-radius: var(--radius-full); background: var(--color-text-faint); flex-shrink: 0; }
.topbar-status-chip.topbar-status-success .topbar-status-dot { background: var(--color-accent); }
.topbar-status-chip.topbar-status-warning .topbar-status-dot { background: var(--color-warning); }
.topbar-status-chip.topbar-status-danger .topbar-status-dot { background: var(--color-danger); }

.topbar-quick-action { padding: 0.4rem 0.9rem; font-size: 0.8rem; }

/* ---------- Sidebar collapse (icon-only mode) ---------- */
/* .sidebar's own width (line ~501) already reads --sidebar-width, and
   html.sidebar-collapsed re-points that one variable at the collapsed value
   (see :root above) — no separate width override needed here. */
.sidebar { transition: width var(--transition-base) var(--ease); }
html.sidebar-collapsed .sidebar-brand span { display: none; }
html.sidebar-collapsed .sidebar-section-title { display: none; }
html.sidebar-collapsed .nav-link { justify-content: center; position: relative; padding-left: 0; padding-right: 0; }
html.sidebar-collapsed .sidebar-footer .nav-link { justify-content: center; }
/* Collapsed-mode: the label no longer doubles as its own tooltip. `.sidebar`
   has `position: sticky`, which — per spec — always establishes a stacking
   context, regardless of z-index. A tooltip built *inside* it (even
   `position: fixed` with z-index:60) still paints as part of that context,
   and since #app-nav (the sidebar) comes before .app-main-col in the DOM with
   neither side declaring a competing stacking order, .app-main-col's entire
   subtree — every card/input/select in the workspace — painted on top of it
   regardless of the tooltip's own z-index. That's the actual root cause of
   the tooltip rendering underneath page content. Fixed below by using a
   single shared node living directly under <body> instead (see
   #global-tooltip further down and its base.html markup) — a sibling
   of #toast-container/.modal, never inside .sidebar's stacking context. The
   label here just becomes visually hidden but still screen-reader-reachable
   (so the icon-only link keeps an accessible name); it no longer moves or
   shows itself. */
/* Подписи групп компонентов: тонкие «бровки» внутри раздела КОМПОНЕНТЫ.
   Кодируют реальную таксономию (аудитория → активность → ИИ-общение →
   рассылки → воронки), а не украшают список. */
.nav-group-label {
  padding: 0.8rem 0.75rem 0.25rem 0.95rem;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-text-faint);
  opacity: 0.72;
  user-select: none;
}
html.sidebar-collapsed .nav-group-label { display: none; }

html.sidebar-collapsed .nav-link .sidebar-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.sidebar-toggle-btn .icon { transition: transform var(--transition-base) var(--ease); }

/* ---------- Global tooltip ---------- */
/* Single shared element, a direct child of <body> (see base.html) — always above
   workspace/cards/inputs/dropdowns, always below an open modal (z-index 95/96 above),
   populated and positioned from JS (initSidebarTooltips in app.js) using the hovered/
   focused element's getBoundingClientRect(), since `left`/`top` here are viewport-
   relative under `position: fixed`. Started as the collapsed-sidebar-icon tooltip only;
   now also backs every data-tooltip="..." element app-wide (replacing the native
   title="..." attribute's slow, OS-styled browser tooltip). Colors intentionally match
   the current theme (light tooltip on light theme, dark on dark) rather than inverting
   — separation from whatever surface it floats over comes from a shadow noticeably
   stronger than an ordinary card's (--shadow-xl, not --shadow-md), the same way a
   raised card reads as "above" a same-toned background purely through elevation.
   white-space is `normal` (not `nowrap`) since tooltip text now includes full sentences
   (role descriptions, error messages), not just short nav labels — max-width wraps
   those instead of running off-screen. */
#global-tooltip {
  position: fixed;
  top: 0;
  left: 0;
  max-width: 16rem;
  padding: 0.4rem 0.7rem;
  border-radius: var(--radius-md);
  background: var(--tooltip-bg);
  color: var(--tooltip-text);
  font-size: 0.78rem;
  font-weight: 500;
  white-space: normal;
  box-shadow: var(--shadow-xl);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-50%) scale(0.96);
  transition: opacity var(--transition-fast) var(--ease), transform var(--transition-fast) var(--ease), visibility 0s linear var(--transition-fast);
  z-index: 80;
}
#global-tooltip.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(-50%) scale(1);
  transition: opacity var(--transition-fast) var(--ease), transform var(--transition-fast) var(--ease), visibility 0s linear 0s;
}

/* ---------- Language selector ---------- */
/* ---------- Topbar control cluster ----------
   Theme toggle, language picker and account menu are one component set: the three
   triggers share .tb-trigger, the two dropdowns share .tb-menu. The old per-control
   rules (.lang-trigger / .user-menu-trigger / .lang-menu / .user-menu-dropdown) are
   gone; the legacy class names are kept on the elements only because the open/close
   JS and the language-change toast still key off them. */
.tb-dropdown { position: relative; }

.tb-trigger {
  display: inline-flex; align-items: center; gap: 0.4rem;
  height: 2.35rem; padding: 0 0.6rem;
  border: 1px solid var(--color-border); border-radius: var(--radius-full);
  background: var(--color-surface); color: var(--color-text-muted);
  font-size: 0.8rem; font-weight: 600; cursor: pointer;
  transition: background var(--transition-fast) var(--ease),
              border-color var(--transition-fast) var(--ease),
              color var(--transition-fast) var(--ease),
              box-shadow var(--transition-fast) var(--ease);
}
.tb-trigger:hover {
  color: var(--color-text);
  border-color: color-mix(in srgb, var(--color-primary) 40%, var(--color-border));
  box-shadow: 0 3px 10px color-mix(in srgb, var(--color-primary) 12%, transparent);
}
.tb-trigger:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.tb-dropdown.open > .tb-trigger {
  color: var(--color-text);
  border-color: color-mix(in srgb, var(--color-primary) 55%, var(--color-border));
  background: var(--color-surface-muted);
}
.tb-trigger-icon { width: 1rem; height: 1rem; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; }
.tb-trigger-label { letter-spacing: 0.03em; }
.tb-chevron {
  width: 0.85rem; height: 0.85rem; flex: none; fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform var(--transition-fast) var(--ease);
}
.tb-dropdown.open > .tb-trigger .tb-chevron { transform: rotate(180deg); }

/* Theme toggle: one round button whose sun and moon swap places on a rotation, so the
   switch reads as a single object turning rather than two icons blinking. */
/* Щит безопасности — круглая иконка-кнопка в кластере шапки, как тема/колокольчик. */
.tb-security { width: 2.35rem; padding: 0; justify-content: center; }
.tb-security .tb-trigger-icon { width: 1.2rem; height: 1.2rem; stroke-width: 1.8; }

.tb-theme { width: 2.35rem; padding: 0; justify-content: center; }
.tb-theme-orb {
  position: relative; display: block; width: 1.15rem; height: 1.15rem;
  transition: transform 420ms cubic-bezier(0.34, 1.3, 0.64, 1);
}
.tb-theme:hover .tb-theme-orb { transform: rotate(18deg); }
:root.dark .tb-theme-orb { transform: rotate(-180deg); }
:root.dark .tb-theme:hover .tb-theme-orb { transform: rotate(-162deg); }
.tb-theme-sun, .tb-theme-moon {
  position: absolute; inset: 0; width: 100%; height: 100%;
  fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round;
  transition: opacity 260ms var(--ease), transform 420ms cubic-bezier(0.34, 1.3, 0.64, 1);
}
.tb-theme-moon { opacity: 0; transform: rotate(180deg) scale(0.55); }
.tb-theme-sun { opacity: 1; transform: rotate(0deg) scale(1); }
:root.dark .tb-theme-sun { opacity: 0; transform: rotate(180deg) scale(0.55); }
:root.dark .tb-theme-moon { opacity: 1; transform: rotate(180deg) scale(1); }
/* The whole page's colour swap gets a short cross-fade, so flipping the theme doesn't
   snap. Scoped to the surfaces that actually change, and dropped entirely for anyone
   who asked for less motion. */
:root.theme-animating,
:root.theme-animating body,
:root.theme-animating .workspace-shell,
:root.theme-animating .sidebar,
:root.theme-animating .app-topbar,
:root.theme-animating .card,
:root.theme-animating .dm-section-card,
:root.theme-animating .an-card,
:root.theme-animating .tb-trigger,
:root.theme-animating .tb-menu {
  transition: background-color 320ms var(--ease), color 320ms var(--ease),
              border-color 320ms var(--ease) !important;
}
@media (prefers-reduced-motion: reduce) {
  .tb-theme-orb, .tb-theme-sun, .tb-theme-moon { transition: none; }
  :root.theme-animating, :root.theme-animating * { transition: none !important; }
}

/* Shared dropdown surface. */
.tb-menu {
  position: absolute; top: calc(100% + 0.5rem); right: 0; z-index: 60;
  min-width: 12.5rem; padding: 0.35rem;
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 0.1rem;
  opacity: 0; visibility: hidden; transform: translateY(-0.35rem) scale(0.97);
  transform-origin: top right; pointer-events: none;
  transition: opacity 160ms var(--ease), transform 160ms var(--ease), visibility 160ms;
}
.tb-dropdown.open > .tb-menu {
  opacity: 1; visibility: visible; transform: translateY(0) scale(1); pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .tb-menu { transition: none; }
}
.tb-menu-title {
  padding: 0.4rem 0.6rem 0.3rem; color: var(--color-text-faint);
  font-size: 0.66rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.07em;
}
.tb-menu-head {
  display: flex; align-items: center; gap: 0.55rem; padding: 0.55rem 0.6rem 0.6rem;
  border-bottom: 1px solid var(--color-border); margin-bottom: 0.25rem;
}
.tb-menu-avatar { width: 2.1rem; height: 2.1rem; font-size: 0.85rem; }
.tb-menu-head-copy { min-width: 0; }
.tb-menu-head-copy strong { display: block; font-size: 0.82rem; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.tb-menu-head-copy span { display: block; color: var(--color-text-faint); font-size: 0.7rem; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }

.tb-menu-item {
  display: flex; align-items: center; gap: 0.55rem; width: 100%;
  padding: 0.5rem 0.6rem; border: 0; border-radius: var(--radius-md);
  background: transparent; color: var(--color-text);
  font-size: 0.83rem; font-weight: 500; text-align: left; text-decoration: none; cursor: pointer;
  transition: background var(--transition-fast) var(--ease), color var(--transition-fast) var(--ease);
}
.tb-menu-item:hover { background: var(--color-surface-muted); }
.tb-menu-item.is-danger { color: var(--color-danger); }
.tb-menu-item.is-danger:hover { background: var(--color-danger-bg); }
.tb-menu-icon { width: 1.05rem; height: 1.05rem; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.tb-menu-text { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.tb-menu-badge {
  display: inline-grid; place-items: center; min-width: 1.75rem; height: 1.35rem; flex: none;
  padding: 0 0.3rem; border-radius: var(--radius-sm);
  background: var(--color-surface-muted); color: var(--color-text-muted);
  font-size: 0.64rem; font-weight: 800; letter-spacing: 0.04em;
}
.tb-menu-check {
  width: 0.95rem; height: 0.95rem; flex: none; opacity: 0;
  fill: none; stroke: var(--color-primary); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round;
  transition: opacity var(--transition-fast) var(--ease);
}
.tb-menu-item.active { color: var(--color-primary); font-weight: 650; }
.tb-menu-item.active .tb-menu-badge { background: color-mix(in srgb, var(--color-primary) 14%, transparent); color: var(--color-primary); }
.tb-menu-item.active .tb-menu-check { opacity: 1; }
.tb-menu-sep { height: 1px; margin: 0.25rem 0.3rem; background: var(--color-border); }

.lang-option-form { display: contents; }

/* ---------- User trigger ---------- */
.tb-trigger-user { padding-left: 0.3rem; max-width: 12rem; }
.user-avatar {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-full);
  background: linear-gradient(135deg, var(--color-primary), var(--color-primary-grad-end));
  color: var(--color-on-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.78rem;
  font-weight: 700;
  flex-shrink: 0;
}
.user-nickname {
  font-size: 0.83rem;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
@media (max-width: 900px) {
  .user-nickname { display: none; }
  .tb-trigger-user { padding-right: 0.4rem; }
}

/* The workspace shell — every page's content floats inside this one big
   rounded, violet-tinted zone instead of sitting directly on the plain page
   background. The radial sheen is a fixed highlight (not theme-varying,
   straight from the reference), the panel color itself is what shifts
   between light/dark via --color-work.

   Split in two on purpose: `.workspace-shell` is the part that carries the
   radius/border/background and NEVER scrolls, so its rounded corners stay on
   screen no matter how far down the page you are; `.workspace-scroll` is a
   plain rectangle nested inside it that does the actual scrolling. Content
   scrolled flush against the shell's curved top/bottom edge gets clipped by
   the shell's own `overflow: hidden` instead of visually poking a square
   corner out past the curve.

   Zero margin on every side — the shell sits flush against the sidebar and
   topbar (no gap to the thin border line between them) exactly as it already
   does against the viewport's right/bottom edges. The top-left corner still
   reads as rounded because the radius curves the shell's own corner away from
   that sharp intersection, opening a small quarter-circle gap right where the
   sidebar's border and the topbar's border cross — no margin needed for that,
   the curve alone does it. The other three corners stay square since they're
   flush with no adjacent border to curve away from. */
.workspace-shell {
  flex: 1;
  min-width: 0;
  min-height: 0;
  margin: 0;
  border-radius: 0;
  border-top-left-radius: var(--radius-3xl);
  background: radial-gradient(circle at 15% 0, rgba(255, 255, 255, 0.35), transparent 25rem), var(--color-work);
  box-shadow: inset 0 0 0 1px var(--color-border);
  width: auto;
  max-width: none;
}
@media (max-width: 650px) {
  /* No persistent sidebar/topbar to round into on mobile (just the drawer's
     hamburger topbar) — square instead of an orphaned, meaningless curve. */
  .workspace-shell { border-top-left-radius: 0; }
}

.workspace-scroll {
  height: 100%;
  min-width: 0;
  /* Top/left/right trimmed down from the old 1.75rem on all four sides — this box
     wraps every page's {% block content %}, so shrinking it here is what actually
     buys back screen space project-wide (not just around the topbar). Bottom stays
     generous since it borders empty scroll room, not the viewport edge. */
  --workspace-padding-x: 0.7rem;
  --workspace-padding-top: 0.7rem;
  --workspace-padding-bottom: 1.25rem;
  /* Right side trimmed by the scrollbar's own width so the thumb sits inside
     the existing padding instead of adding an extra gap on top of it — the
     scrollbar ends up reading as part of the padding, not a second margin. */
  padding: var(--workspace-padding-top) calc(var(--workspace-padding-x) - 4px) var(--workspace-padding-bottom) var(--workspace-padding-x);
}
@media (max-width: 650px) {
  .workspace-scroll { --workspace-padding-x: 0.6rem; --workspace-padding-top: 0.6rem; --workspace-padding-bottom: 0.8rem; }
}

@media (min-width: 681px) {
  .workspace-shell {
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  .workspace-scroll {
    /* Cancels the base rule's `height: 100%` — on desktop .workspace-shell is a
       column flex container, so flex-basis/flex-grow is what actually sizes this
       box; height:100% is only meaningful below 681px, where the shell isn't flex
       at all. Leaving both a percentage height AND flex:1 active on the same box
       gives the layout engine two simultaneously "authoritative" size inputs —
       harmless most of the time, but a restyle deep in the scrolled content (e.g.
       a :has(input:checked) match on a mode-card, which touches no height/DOM at
       all) can invalidate one without the other, leaving scrollHeight stale until
       something forces a full reflow (a zoom change, which is exactly what was
       observed "fixing" it temporarily). Making flex the only sizing authority
       here removes that ambiguity at the source. */
    height: auto;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-gutter: stable;
    overscroll-behavior: contain;
  }
}
@media (prefers-reduced-motion: reduce) {
  .app-shell, .sidebar, .workspace-shell { transition: none; }
  #global-tooltip, #global-tooltip.is-visible { transition: none; transform: none; }
}

