/* ---- Theme tokens ---------------------------------------------------------
   Light is the base (:root). Dark applies via prefers-color-scheme when the
   user hasn't chosen explicitly, and via [data-theme] when they have (the
   theme control sets/removes that attribute). Attribute selectors outrank the
   media query, so an explicit choice always wins over the system setting. */
:root {
  --bg:#f4f5fa; --panel:#ffffff; --panel-2:#eef1f8;
  --text:#1b1e2a; --muted:#616776; --border:#dde1ec;
  --accent:#3a5ce0; --accent-hover:#2f4fce; --danger:#d23b4e; --accent-2:#0f9c80; --warn:#b26b00;
  --w-face:#ffffff; --w-ring:#b9c0d4; --w-ring2:#e6e9f2; --w-cusp:#d3d8e6;
  --w-num:#8a90a2; --w-glyph:#2a2e3d; --w-angle:#d9603a;
  --asp-hard:#e23b4e; --asp-soft:#15a86f; --asp-neutral:#8890a3; --asp-minor:#c2c7d4;
  --el-fire:#e0562f; --el-earth:#2f9e57; --el-air:#c99a1e; --el-water:#2f74d0;
  /* T-signfill: base sign-wedge fill alpha (see .w-sign-hit.w-el-* below).
     Higher than dark's — a colour mixed over the near-white light panel
     lightens toward the background, so it needs a larger mix percentage to
     reach the same perceived tint that a smaller percentage gives over the
     near-black dark panel. */
  --el-fill-a: 20%;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg:#0e0f14; --panel:#161822; --panel-2:#1e2130;
    --text:#e8eaf2; --muted:#9297ad; --border:#2b2f40;
    --accent:#6c8cff; --accent-hover:#5a7bf0; --danger:#ff6b6b; --accent-2:#3fd0b0; --accent-2:#3fd0b0; --warn:#e0a458;
    --w-face:#12141d; --w-ring:#464c63; --w-ring2:#262a39; --w-cusp:#333a50;
    --w-num:#8b92a8; --w-glyph:#e6e9f5; --w-angle:#ff8f6b;
    --asp-hard:#ff5c6c; --asp-soft:#38c99a; --asp-neutral:#9aa0b5; --asp-minor:#464c60;
    --el-fire:#ff7a5c; --el-earth:#57c07d; --el-air:#f2c65a; --el-water:#5aa2ff;
    /* Lower than light's — a colour mixed over the near-black dark panel
       darkens toward the background and reads strongly, so a smaller mix
       percentage lands at the same perceived tint the light theme needs a
       larger one for (see the light --el-fill-a note above). */
    --el-fill-a: 16%;
  }
}
:root[data-theme="light"] {
  --bg:#f4f5fa; --panel:#ffffff; --panel-2:#eef1f8;
  --text:#1b1e2a; --muted:#616776; --border:#dde1ec;
  --accent:#3a5ce0; --accent-hover:#2f4fce; --danger:#d23b4e; --accent-2:#0f9c80; --warn:#b26b00;
  --w-face:#ffffff; --w-ring:#b9c0d4; --w-ring2:#e6e9f2; --w-cusp:#d3d8e6;
  --w-num:#8a90a2; --w-glyph:#2a2e3d; --w-angle:#d9603a;
  --asp-hard:#e23b4e; --asp-soft:#15a86f; --asp-neutral:#8890a3; --asp-minor:#c2c7d4;
  --el-fire:#e0562f; --el-earth:#2f9e57; --el-air:#c99a1e; --el-water:#2f74d0;
  --el-fill-a: 20%;
}
:root[data-theme="dark"] {
  --bg:#0e0f14; --panel:#161822; --panel-2:#1e2130;
  --text:#e8eaf2; --muted:#9297ad; --border:#2b2f40;
  --accent:#6c8cff; --accent-hover:#5a7bf0; --danger:#ff6b6b; --warn:#e0a458;
  --w-face:#12141d; --w-ring:#464c63; --w-ring2:#262a39; --w-cusp:#333a50;
  --w-num:#8b92a8; --w-glyph:#e6e9f5; --w-angle:#ff8f6b;
  --asp-hard:#ff5c6c; --asp-soft:#38c99a; --asp-neutral:#9aa0b5; --asp-minor:#464c60;
  --el-fire:#ff7a5c; --el-earth:#57c07d; --el-air:#f2c65a; --el-water:#5aa2ff;
  --el-fill-a: 16%;
}

* { box-sizing: border-box; }

/* Task interp-ux hang-fix root cause: the browser's UA stylesheet hides any
   element carrying the `hidden` attribute via `[hidden] { display: none }`,
   but several rules below (#verify, #interpretation, .interpret-status) set
   `display` unconditionally on the SAME element - since those are AUTHOR
   rules, they win over the UA rule regardless of selector specificity, so
   toggling `el.hidden = true/false` from JS silently stopped hiding the
   element. This is what actually made the "Разбор карты" spinner LOOK like
   it never stopped: interpretChart() was resolving and the click handler's
   `finally` really did run (interpretStatusEl.hidden = true fired on time),
   the browser just kept rendering the status row anyway. `!important` here
   guarantees `[hidden]` always wins over any current or future `display`
   rule, rather than patching each conflicting selector one at a time. */
[hidden] { display: none !important; }

html { color-scheme: light dark; }

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
  transition: background-color .25s ease, color .25s ease;
}

/* FLEX, а не grid: в сетке правая колонка живёт строками, привязанными к
   левой, и если боковая панель длиннее карты, справа под ней остаётся
   пустая полоса до начала следующей строки. Во flex правая часть — свой
   непрерывный поток: секции идут одна за другой независимо от высоты
   панели. */
.layout {
  display: flex; flex-wrap: wrap; align-items: flex-start;
  gap: 24px; padding: 24px; min-height: 100vh;
}
.layout > .topbar, .layout > .tabs { flex: 1 1 100%; }
.panel { flex: 0 0 clamp(280px, 26%, 360px); }
.content {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; gap: 24px;
}
@media (max-width: 900px) {
  .panel, .content { flex: 1 1 100%; }
  /* В одну колонку колесо занимает всю ширину, а карточка встаёт под ним. */
  #wheel { flex: 1 1 100%; width: 100%; }
}

.panel { display: flex; flex-direction: column; gap: 16px; }

/* Вкладки: работа с картой, прогноз и обучение — разные занятия, и мешать
   их в одну ленту значит заставлять читателя пролистывать чужое. Скрытие
   идёт своим классом, а не атрибутом hidden: у секций уже есть собственная
   логика hidden (например, разбор появляется только после построения карты),
   и два механизма на одном атрибуте перетирали бы друг друга. */
.tabs {
  grid-column: 1 / -1; display: flex; gap: 6px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border); padding-bottom: 8px;
}
/* Вкладки заметнее (жалоба со скриншота: сливались с фоном): крупнее и
   плотнее по весу, активная несёт акцентную полосу снизу и акцентный цвет
   текста — один сигнал «ты здесь» вместо едва отличимой рамки. */
.tab-btn {
  padding: 9px 18px; font-size: .95rem; font-weight: 600;
  border-radius: 8px 8px 0 0;
  background: transparent; color: var(--muted); border: 1px solid transparent;
  border-bottom: 2px solid transparent;
}
.tab-btn:hover { color: var(--text); background: var(--panel-2); }
.tab-btn[aria-selected="true"] {
  background: var(--panel); color: var(--accent);
  border-color: var(--border);
  border-bottom-color: var(--accent);
}
.tab-hidden { display: none !important; }
.transits-hint { font-size: .8rem; margin: 6px 0 0; }
.transits-hint .link-btn { font-size: .8rem; padding: 0; }

/* На вкладках «Прогноз» и «Обучение» нет колеса, а левая панель по-прежнему
   занимает первую колонку сетки — секции шириной 1/-1 вставали ПОД ней, и
   справа от панели зияла пустая полоса во всю её высоту. Здесь секции
   переводятся во ВТОРУЮ колонку: они встают рядом с панелью и заполняют
   строку. Селекторы с id + атрибутом, потому что у самих секций
   grid-column:1/-1 задан по id и иначе не перебивается. */


/* Task layout: right-hand grid column, holding #chart with #legend stacked
   right under it. A dedicated wrapper (rather than #chart and #legend as two
   separate direct children of .layout) keeps this column's total height a
   simple sum of its own content, independent of the left .panel's height —
   plain flow stacking, not a grid row shared with .panel, so it can't be
   inflated by track-sizing rules meant for the other column. Cross-axis
   (width) stretches to fill the grid cell via the default align-items:
   stretch on a column flex container; main-axis (height) stays content-sized
   for both children, which is what lets #legend sit directly under #chart
   instead of being pushed down by .panel's own height. */
.chart-col { display: flex; flex-direction: column; gap: 24px; min-width: 0; }

/* Поля формы не должны расширять колонку сеткой: ширина <select> по
   умолчанию равна самой длинной опции («Современная (с Ураном, Нептуном,
   Плутоном)»), и на узком экране это растягивало весь grid-трек — страница
   получала горизонтальную прокрутку, которую видно во всех блоках сразу. */
.panel select, .panel input, .panel textarea { width: 100%; min-width: 0; max-width: 100%; }
/* Чекбокс — не текстовое поле: width:100% из правила выше растягивал его на
   всю строку, и «Показать транзиты» выглядел сломанным (квадратик по центру,
   подпись отдельной строкой). Правило системное, а не точечное на панель
   транзитов: любой будущий чекбокс в .panel ломался бы так же. */
.panel input[type="checkbox"] { width: auto; }
/* Подпись с чекбоксом — одна строка: квадратик и текст рядом (тот же
   паттерн, что в «Дополнительных точках»). */
.panel label:has(> input[type="checkbox"]) {
  display: flex; align-items: center; gap: 8px;
}
#extra-points {
  border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; margin: 0;
  display: flex; flex-direction: column; gap: 6px;
}
#extra-points legend { font-size: .8rem; color: var(--muted); padding: 0 4px; }
#extra-points label { display: flex; align-items: center; gap: 6px; font-size: .88rem; }
#extra-points input[type="checkbox"] { width: auto; }

#library, #form, #verify, #transits-panel, #pair-panel {
  display: flex; flex-direction: column; gap: 8px;
  background: var(--panel);
  border: 1px solid var(--border);
  padding: 16px; border-radius: 10px;
}

/* Transit date/time controls + step buttons (±д/н/м). Buttons wrap onto
   multiple lines rather than forcing horizontal scroll on narrow viewports -
   important on mobile, where .t-steps' 6 buttons don't fit one row. */
#transits-controls { display: flex; flex-direction: column; gap: 8px; }
.t-steps { display: flex; flex-wrap: wrap; gap: 6px; }
.t-steps button { padding: 6px 10px; font-size: .85rem; }

/* Header row: title + theme control */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: -8px;
}
.topbar h1 { font-size: 1.15rem; letter-spacing: .01em; margin: 0; }

.theme-toggle {
  display: inline-flex; gap: 2px;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 9px; padding: 3px;
}
.theme-toggle button {
  appearance: none; border: none; background: none;
  width: 30px; height: 30px; padding: 0; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  font: inherit; color: var(--muted); cursor: pointer;
}
.theme-toggle button[aria-pressed="true"] {
  background: var(--panel); color: var(--text);
  box-shadow: 0 1px 2px rgba(0,0,0,.18);
}
.theme-toggle svg { width: 18px; height: 18px; display: block; }

#people-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
#people-list li {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 6px 6px 6px 8px; border-radius: 6px; cursor: pointer; color: var(--text);
}
#people-list li:hover { background: var(--panel-2); }
#people-list li.selected {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  outline-offset: -1px;
}
#people-list .person-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Per-row edit/delete controls; always visible (not hover-only) so they stay
   reachable on touch devices without a hover state. */
.people-actions { display: flex; gap: 2px; flex: 0 0 auto; }

.icon-btn {
  align-self: auto;
  width: 26px; height: 26px; padding: 0; border-radius: 6px;
  border: 1px solid transparent; background: transparent; color: var(--muted);
  font-size: .9rem; line-height: 1; display: inline-flex; align-items: center; justify-content: center;
}
.icon-btn:hover { background: var(--border); color: var(--text); }
.icon-btn-danger:hover { background: color-mix(in srgb, var(--danger) 18%, transparent); color: var(--danger); }

/* Plain inline-text link button, used for "exit edit mode" next to the indicator */
.link-btn {
  align-self: auto; padding: 0; border: none; background: none;
  color: var(--accent); font-size: inherit; text-decoration: underline; cursor: pointer;
}
.link-btn:hover { color: var(--accent-hover); }

h1 { font-size: 1.1rem; margin: 0 0 8px; }
h2 { font-size: 1rem; margin: 0 0 8px; }
h3 { font-size: 1rem; margin: 0 0 6px; }
label { font-size: .85rem; color: var(--muted); }

.muted { color: var(--muted); font-size: .85rem; margin: 0; }

/* Поля ввода. Правило от ОБРАТНОГО, а не перечислением типов: селектор
   `input[type="text"]` требует НАЛИЧИЯ атрибута и не совпадает с
   `<input id="auth-login">`, у которого типа нет вовсе, а `type="password"` в
   прежнем перечислении отсутствовал. Из-за этого четыре поля приложения
   оставались нативными — в тёмной теме белая полоса с чёрным текстом внутри
   тёмной карточки: логин и код приглашения на экране входа, подтверждение
   логина в диалоге удаления админки и текстовое поле «Сравнить со своим
   разбором». Исключения — поля, чей нативный вид и нужен (флажок, радио,
   ползунок, выбор файла и кнопки-инпуты). textarea сюда же: она отличается от
   input только размером. */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="image"]):not([type="submit"]):not([type="button"]):not([type="reset"]),
select, textarea {
  padding: 7px 9px; border-radius: 7px;
  border: 1px solid var(--border);
  background: var(--bg); color: var(--text);
  font-size: .95rem;
  font-family: inherit;
}
input:focus-visible, select:focus-visible, button:focus-visible, .gloss:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}

.checkbox-row { display: flex; align-items: center; gap: 8px; color: var(--text); }

/* read-only confirmation of derived timezone facts (offset + DST) */
.readout {
  font-size: .88rem; color: var(--text);
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 7px; padding: 8px 10px;
}
.readout b { font-weight: 600; }

button {
  align-self: flex-start;
  padding: 8px 16px; border: none; border-radius: 8px;
  background: var(--accent); color: #fff; font-size: .95rem; cursor: pointer;
}
button:hover { background: var(--accent-hover); }
button.secondary { background: var(--panel-2); color: var(--text); border: 1px solid var(--border); }
button.secondary:hover { background: var(--border); }

.error { color: var(--danger); font-size: .85rem; }

#chart { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
/* Task 4: #chart gained a leading <h2> so collapse.js has a header to wire
   (see index.html/makeCollapsible) - flex-basis:100% forces it onto its own
   row (flex-wrap above then wraps #wheel/#info below it) instead of sitting
   beside the wheel as a plain flex item. */
#chart > h2 { flex: 1 1 100%; margin: 0; }

#wheel {
  /* Ширина колеса задаётся ДОЛЕЙ строки, а не остатком после карточки: так
     она не зависит от того, сколько текста сейчас в #info, и карта перестала
     менять размер при переключении слоёв. Карточка справа забирает остаток —
     иначе строка не заполняется и справа зияет пустое место. */
  flex: 0 0 min(760px, 58%); width: min(760px, 58%);
  aspect-ratio: 1 / 1;
  background: var(--w-face);
  border: 1px solid var(--border); border-radius: 12px;
}
#wheel, #wheel * { transition: fill .25s ease, stroke .25s ease; }

#info {
  /* Карточка занимает ВЕСЬ остаток строки (min-width:0 — чтобы длинное слово
     не распирало её шире доступного). Ширину держит колесо, см. #wheel. */
  flex: 1 1 240px; min-width: 0; align-self: stretch;
  background: var(--panel); border: 1px solid var(--border);
  padding: 16px; border-radius: 12px; min-height: 200px;
  font-size: .9rem; color: var(--text); line-height: 1.5;
}
#info hr { border: none; border-top: 1px solid var(--border); margin: 8px 0; }
/* Task Info-B: each aspect line in the info panel is its own row
   (.info-asp, wired in interact.js for hover-highlight on the wheel). No
   cursor: pointer here — unlike .table-block tr.hoverable, this row has no
   click behavior (hover-only, by explicit user request), so a pointer
   cursor would misleadingly imply clickability. The background tint is
   purely an affordance that the row does something on hover. */
#info .info-asp { padding: 2px 4px; margin: 0 -4px; border-radius: 4px; }
#info .info-asp:hover { background: var(--panel-2); }

/* Карточка разбора закреплённой точки. Отделена от списка аспектов линией и
   отступом: выше — что у точки есть, ниже — что из этого следует. */
.body-facts {
  margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 4px;
}
.body-facts h4 { margin: 0 0 4px; font-size: .95rem; }
/* Ровно два элемента в строке — подпись и значение (значение обёрнуто в свой
   span в body_facts_view.js): иначе флекс разложил бы каждый глоссарный span
   значения как отдельный элемент и вставил бы gap перед каждой запятой. */
.bf-row { display: flex; flex-wrap: wrap; gap: 0 8px; }
/* Подписи выстроены в колонку, значения — за ней: карточка читается сверху
   вниз по левому краю, а не пересобирается глазом на каждой строке. */
.bf-label { flex: 0 0 auto; min-width: 148px; color: var(--muted); }
.bf-value { flex: 1 1 200px; min-width: 0; }
.bf-note { margin: 6px 0 0; }
.bf-interpret { align-self: flex-start; margin-top: 10px; }
.bf-factcheck { align-self: flex-start; margin-top: 8px; }
/* Сводка проверки фактов внутри карточки. Прокрутка по обеим осям здесь не
   нужна, а переполнение по вертикали должно оставаться видимым: подсказки
   глоссария в сводке всплывают наружу. */
.bf-fc:not(:empty) { margin-top: 10px; border-top: 1px solid var(--border); }
.bf-fc h2 { font-size: .95rem; margin: 10px 0 4px; }
.bf-out { margin-top: 10px; line-height: 1.55; }
.bf-out.error { color: var(--danger); }
.bf-out p { margin: 0 0 8px; }
.bf-out h2, .bf-out h3 { font-size: .95rem; margin: 10px 0 4px; }
@media (max-width: 640px) {
  .bf-label { min-width: 0; flex-basis: 100%; }
}

/* Glossary tooltip (Task T-interact2b): a dotted underline marks an astro
   term (planet, sign, dignity, aspect type, retro marker, ASC/MC) that has a
   plain-language explanation in symbols.js's GLOSSARY. Hover/focus reveals it
   via a themed ::after popover rather than the native `title` attribute, so
   it reads cleanly in both light and dark (matching the rest of the app's
   token system) instead of an unstyled OS tooltip. The explanation text
   always comes from data-tip, itself always sourced from the fixed GLOSSARY
   map (never user input) — see interact.js's glossTerm() for how it's built.
   Terms already explained inline (angle codes in showAngleInfo, the element
   line in showSignInfo) are not wrapped in this class at all. */
.gloss {
  border-bottom: 1px dotted var(--muted);
  cursor: help;
  position: relative;
}
.gloss:hover, .gloss:focus-visible { border-bottom-color: var(--accent); }
.gloss:hover::before, .gloss:focus-visible::before,
.gloss:hover::after, .gloss:focus-visible::after {
  opacity: 1;
  visibility: visible;
}
/* Small caret pointing at the term, drawn as a rotated square sliver so it
   shares the popover's border/background exactly (a pure CSS-triangle arrow
   can't also carry a matching 1px border on the two visible edges). */
.gloss::before {
  content: "";
  position: fixed;
  left: var(--tip-caret-x, 0px); top: var(--tip-caret-y, 0px); right: auto;
  width: 8px; height: 8px;
  transform: rotate(45deg);
  background: var(--panel-2);
  border-left: 1px solid var(--border);
  border-top: 1px solid var(--border);
  opacity: 0; visibility: hidden;
  transition: opacity .12s ease;
  z-index: 21;
  pointer-events: none;
}
/* Right-anchored (Task Info-A fix; was left-anchored before). #info is the
   RIGHTMOST column of the page (see .layout above) — its right edge sits
   flush against the viewport, minus page padding. A popover growing
   RIGHTWARD from a term's left edge (the old rule, chosen back when only
   left-edge terms existed) overflows past the viewport's right edge for any
   term placed toward the right half of the panel — the aspect list, "куспид"/
   "орбис", terms near a longer heading — which is exactly the reported bug:
   the page gained a horizontal scrollbar. Anchoring the popover's RIGHT edge
   to the term's right edge instead means it only ever grows LEFTWARD/inward:
   its right edge can never sit further right than the term itself, and the
   term can never sit further right than the panel, which is already inside
   the viewport. That holds for every term in #info regardless of position,
   so this isn't a per-term fix — it categorically removes the overflow
   direction that caused the scrollbar. max-width keeps it from also growing
   past the LEFT edge of the viewport on a narrow/mobile layout (single-column
   below 900px, where #info can span nearly the full viewport width). */
.gloss::after {
  content: attr(data-tip);
  /* FIXED, а не absolute: попап должен переживать прокручиваемые контейнеры.
     Карточка таблицы (.table-block) имеет overflow-x:auto ради широких
     таблиц, а это по спецификации делает её скролл-контейнером по ОБЕИМ
     осям — absolute-попап внутри неё обрезался (репорт: пояснение к лоту
     срезано слева). Фиксированное позиционирование выводит попап из-под
     любого overflow; координаты считает interact.js::clampGlossElement и
     кладёт в --tip-x/--tip-y. Значения по умолчанию — на случай, если
     координаты ещё не посчитаны (первый кадр до pointerover). */
  position: fixed;
  left: var(--tip-x, 0px); top: var(--tip-y, 0px); right: auto;
  /* Mobile fix: right-anchoring alone lets the popover poke past the
     viewport's LEFT edge when the term itself sits near the left margin
     (single-column layout, e.g. the aspect heading's terms). interact.js's
     clampGlossTip() measures the rendered box on hover/focus and sets
     --tip-dx to push it right just enough to keep a 16px margin; 0 when no
     clamping is needed (desktop right-column case, where the old behavior
     was already correct). */
  margin-top: 0;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 10px;
  font-size: .8rem;
  font-weight: 400;
  line-height: 1.4;
  text-align: left;
  white-space: normal;
  width: max-content;
  max-width: min(240px, calc(100vw - 32px));
  box-shadow: 0 6px 18px rgba(0,0,0,.2);
  opacity: 0; visibility: hidden;
  transition: opacity .12s ease;
  z-index: 20;
  pointer-events: none;
}

/* ---- Wheel entities (themed via the tokens above) ---- */
.w-ring  { fill: none; stroke: var(--w-ring);  stroke-width: 1.5; }
.w-ring2 { fill: none; stroke: var(--w-ring2); stroke-width: 1; }
.w-cusp  { stroke: var(--w-cusp); stroke-width: 1; }
.w-num   { fill: var(--w-num); font-size: 12px; }
.w-deg   { fill: var(--w-num); font-size: 11px; }
/* pointer-events: none so the glyph never intercepts hover/click at its own
   pixel position — those events must fall through to the transparent
   `.w-sign-hit` wedge underneath (T-hittest), which carries the listener. */
.w-sign  { font-size: 25px; pointer-events: none; }
.w-el-fire  { fill: var(--el-fire); }
.w-el-earth { fill: var(--el-earth); }
.w-el-air   { fill: var(--el-air); }
.w-el-water { fill: var(--el-water); }
.w-badge { fill: var(--panel-2); stroke: var(--border); stroke-width: 1; }
.w-body:hover .w-badge { stroke: var(--accent); stroke-width: 1.5; }
.w-glyph { fill: var(--w-glyph); font-size: 26px; }
/* pointer-events: none on both the axis line and its label (T-hittest) —
   same reasoning as .w-sign above: the click/hover listener lives on the
   transparent `.w-angle-hit` circle underneath, and the thin decorative
   line otherwise also intercepts events along its length over houses/bodies. */
.w-angle { stroke: var(--w-angle); stroke-width: 1.6; pointer-events: none; }
.w-angle-label { fill: var(--w-angle); font-size: 13px; font-weight: 600; pointer-events: none; }
/* Radial connector from a declustered (radially displaced) body chip back to
   its true-longitude point on the planet ring; faint, like a cusp line. */
.w-tick { stroke: var(--w-ring2); stroke-width: 1; }

.w-aspect { fill: none; stroke-width: 1.5; stroke-linecap: round; }
.w-asp-hard    { stroke: var(--asp-hard); }
.w-asp-soft    { stroke: var(--asp-soft); }
.w-asp-neutral { stroke: var(--asp-neutral); }
.w-asp-minor   { stroke: var(--asp-minor); stroke-dasharray: 3 4; }
.w-aspect-hit  { stroke: transparent; stroke-width: 16; pointer-events: stroke; }
/* Task T-flicker: without this override, the generic `.hl { stroke-width:
   3.5 }` below (same 0,1,0 specificity, later in source order) wins on a
   highlighted hit line and shrinks its stroke 16px -> 3.5px. Since
   pointer-events is `stroke`, that shrink moves the hit region's edge
   inward from under a cursor that was resting near the original 16px
   boundary, causing an immediate mouseleave, which removes .hl and grows
   the hit line back to 16px under the cursor, causing another mouseenter —
   an oscillation loop (perceived as flicker/jumping highlight, worst where
   several aspect lines run close together). Same two-class specificity
   (0,2,0) fix as .w-sign-hit.hl / .w-house-hit.hl / .w-angle-hit.hl above:
   keep the hit geometry stable across highlight, independent of the
   visible line's own .hl thickening (still handled by the generic rule). */
.w-aspect-hit.hl { stroke-width: 16; }

.w-body { cursor: pointer; }
.w-hit  { fill: transparent; pointer-events: all; }
.w-body:hover .w-glyph { font-weight: 700; }

/* House sectors (Task T-houses): transparent annular-wedge hit-zones drawn
   in the cusp-spoke band (250..336), under aspects/bodies in z-order so
   planet hit-discs and glyphs stay clickable on top of them. */
.w-house-hit { fill: transparent; pointer-events: fill; cursor: pointer; }
/* Faint accent wash on hover/pin — subtle enough to read the planets/glyphs
   drawn on top of it in both themes, unlike the .hl stroke-width bump below
   (which doesn't apply visibly to a fill-only, strokeless path). */
.w-house-hit.hl { fill: color-mix(in srgb, var(--accent) 14%, transparent); }

/* Zodiac sign hit-zones (Task T-interact2a): transparent wedge in the sign
   band (336..380), same fill-wash treatment on hover/pin as house sectors. */
.w-sign-hit { fill: transparent; pointer-events: fill; cursor: pointer; }
/* T-signfill: muted element-coloured wash, subordinate to the glyphs/planets
   drawn on top (--el-fill-a is tuned per theme above so both read as equally
   understated). Same two-class specificity (0,2,0) as .w-sign-hit.hl right
   below — placed BEFORE it in source order so .hl still wins the cascade
   when a wedge is both element-tinted and hovered/pinned. */
.w-sign-hit.w-el-fire  { fill: color-mix(in srgb, var(--el-fire)  var(--el-fill-a), transparent); }
.w-sign-hit.w-el-earth { fill: color-mix(in srgb, var(--el-earth) var(--el-fill-a), transparent); }
.w-sign-hit.w-el-air   { fill: color-mix(in srgb, var(--el-air)   var(--el-fill-a), transparent); }
.w-sign-hit.w-el-water { fill: color-mix(in srgb, var(--el-water) var(--el-fill-a), transparent); }
/* Pin/hover override. With the brighter base wash the accent fill alone no
   longer reads as clearly "more highlighted" for the water signs, whose
   --el-water is nearly the same hue as --accent — a 20%/16% water wash would
   sit right next to an accent wash of similar strength. So the pin gets both
   a stronger accent fill AND an accent stroke outline; the stroke is
   hue-independent, so the selected wedge reads for every element. Overrides
   the generic `.hl { stroke-width: 3.5 }` (0,2,0 beats 0,1,0) with a tighter
   2px so the outline traces the band cleanly. */
.w-sign-hit.hl {
  fill: color-mix(in srgb, var(--accent) 26%, transparent);
  stroke: var(--accent);
  stroke-width: 2;
}
/* The sign glyph itself is fill-only text, so the generic .hl stroke-width
   bump below doesn't make it visibly "highlighted" — bump weight/size
   directly instead (two-class selector, so specificity wins regardless of
   source order vs. the base .w-sign rule above). */
.w-sign.hl { font-weight: 700; font-size: 29px; }

/* Angle hit-circles (Task T-interact2a): transparent circle behind each
   ASC/DSC/MC/IC label (see wheel.js), same fill-wash treatment. */
.w-angle-hit { fill: transparent; pointer-events: fill; cursor: pointer; }
.w-angle-hit.hl { fill: color-mix(in srgb, var(--accent) 14%, transparent); }
/* The angle label is fill-only text (same reasoning as .w-sign.hl above);
   the angle *line* is a stroked path so it does respond to the generic .hl
   stroke-width bump below without any extra rule. */
.w-angle-label.hl { font-size: 15px; }

/* Interactivity (order matters: .hl must follow .w-aspect to win). */
.dim { opacity: .12; }
.hl  { stroke-width: 3.5; }

/* Выбранная точка и её партнёры по аспектам. Общий `.hl` задаёт только
   stroke-width, а у значка планеты собственный stroke-width (.w-badge) и глиф
   вовсе текстовый — до них он не доходит, поэтому нужны свои правила, как уже
   сделано для .w-sign.hl и .w-angle-label.hl выше. Фокус залит акцентом,
   партнёр — только обводка: разница между «на что смотрим» и «с чем связано»
   должна читаться без подписи. */
.w-body.hl-focus .w-badge {
  fill: color-mix(in srgb, var(--accent) 32%, var(--panel-2));
  stroke: var(--accent); stroke-width: 2;
}
.w-body.hl-focus .w-glyph { font-weight: 700; }
.w-body.hl-focus .w-deg { fill: var(--text); }
.w-body.hl .w-badge { stroke: var(--accent); stroke-width: 1.5; }
.w-body.hl .w-glyph { font-weight: 700; }

/* ---- Tables ----
   Task layout: two columns instead of three equal-width flex siblings —
   Positions+Dignities stacked in one column, Aspects (much longer) alone in
   the other (see tables.js's colMain/colAspects wrapper divs). Each column
   is its own independent flow (.tables-col below), so the short Positions/
   Dignities pair doesn't get stretched to Aspects' height the way three
   flex siblings with the default align-items: stretch used to (that stretch
   was the actual cause of the empty space under the short tables — the grid
   row/column tracks themselves aren't shared between .tables-col, so one
   column being much taller than the other never inflates the shorter one). */
#tables {
  display: grid;
  grid-template-columns: repeat(2, minmax(260px, 1fr));
  gap: 16px;
  align-items: start;
}
@media (max-width: 900px) { #tables { grid-template-columns: 1fr; } }

/* .chart-meta (tables.js) is a direct child of #tables, so it falls into
   the grid's auto-placement like any other item — without an explicit
   grid-column it lands in the first column's cell only, pushing the two
   table columns beside/below it instead of staying a single full-width
   line above them. Span both tracks explicitly. */
.chart-meta { grid-column: 1 / -1; margin: 0 0 4px; }

.tables-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

.table-block {
  background: var(--panel);
  border: 1px solid var(--border); padding: 16px; border-radius: 10px;
  /* Task 6: the transits table's "Фаза" column (сходящийся/расходящийся) is
     wider than the narrow-viewport columns above it and, being a single
     unbreakable word, cannot wrap - table-layout:auto then grows the table
     past 100% of its own container instead of shrinking. overflow-x:auto here
     lets a too-wide table scroll WITHIN its own card (a no-op when content
     fits, i.e. every existing table today) rather than pushing #tables - and
     with it the whole page - wider than the viewport. */
  overflow-x: auto;
}
/* Legend cards (.legend-block.table-block, see #legend below) still live in
   a row-wrap flex container (.legend-groups) and rely on this flex-basis to
   size themselves there; tables.js's columns are a plain flex column instead,
   where a horizontal flex-basis would misapply to the vertical main axis, so
   it's scoped to .legend-groups' children rather than kept on .table-block
   itself. */
.legend-groups > .table-block { flex: 1 1 260px; }
.table-block table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.table-block th, .table-block td { text-align: left; padding: 4px 6px; border-bottom: 1px solid var(--border); }
.table-block tr.hoverable { cursor: pointer; }
.table-block tr.hoverable:hover { background: var(--panel-2); }
.table-block tr.hl { background: color-mix(in srgb, var(--accent) 22%, transparent); }

/* Обвязка секции контентной колонки — ОДНО место на все секции.
   До 2026-08-05 она была скопирована по восьми id, и шесть секций своей копии
   не получили: «Синастрия», «Карта пары», «Транзиты и события», «Прогноз
   текстом», «Изменения транзитов» и «Разбор изменений» висели прямо на фоне
   страницы, без рамки и без отбивки, — то есть новая секция по умолчанию
   оказывалась вне дизайна, и заметить это можно было только глазами.
   Класс несёт ТОЛЬКО внешность: раскладку внутренностей (flex, gap,
   align-items, своя прокрутка) каждая секция по-прежнему задаёт себе сама,
   иначе перевод шести секций на общую обвязку менял бы им и внутренний поток.
   overflow: visible обязателен — прокручиваемый контейнер обрезает всплывающие
   пояснения глоссария (та же ловушка, что с карточками легенды). */
.section-card {
  background: var(--panel); border: 1px solid var(--border);
  padding: 16px; border-radius: 10px;
  overflow: visible; min-width: 0;
}

/* ---- Interpretation panels (natal + transit share the layout) ---- */
#interpretation, #transit-interpretation {
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
}
.interpret-status { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: .9rem; }

.spinner {
  width: 16px; height: 16px; flex: 0 0 auto; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: natal-spin .8s linear infinite;
}
@keyframes natal-spin { to { transform: rotate(360deg); } }

/* Rendered Markdown from the model (see js/markdown.js's SAFE renderer -
   HTML is escaped before any of these tags are (re)introduced). Kept in the
   same type scale as the rest of the app rather than browser heading
   defaults, so a "# Заголовок" from the model doesn't visually clash. */
.md-content {
  align-self: stretch;
  font-size: .92rem; line-height: 1.6; color: var(--text); max-width: 72ch;
}
.md-content h1, .md-content h2, .md-content h3 { margin: 16px 0 6px; line-height: 1.3; }
.md-content h1:first-child, .md-content h2:first-child, .md-content h3:first-child { margin-top: 0; }
.md-content h1 { font-size: 1.15rem; }
.md-content h2 { font-size: 1.05rem; }
.md-content h3 { font-size: 1rem; }
.md-content p { margin: 0 0 10px; }
.md-content ul { margin: 0 0 10px; padding-left: 20px; }
.md-content li { margin: 2px 0; }
.md-content strong { font-weight: 600; }

/* ---- Legend ----
   Task layout: #legend now lives inside .chart-col, right under #chart (see
   index.html), rather than as a full-width section at the very bottom of the
   page — no grid-column span needed here any more, it just takes its
   column's width like #chart does. */
.legend-title { margin: 4px 0 12px; }
/* align-items: flex-start (unlike a plain flex row's default stretch)
   because the four legend cards vary a lot in row count (12+4 sign/element
   rows vs. 4 angle rows) — stretching them to equal height would leave the
   shorter cards with a large empty footer. */
/* Cards stretch to the tallest one in the row (rather than each sizing to its
   own content) so a card with a footer strip can push that strip to its own
   bottom edge — see .legend-elements below. Without stretch the footer sits
   directly under the sign grid and leaves the card's lower half empty. */
.legend-groups { display: flex; flex-wrap: wrap; align-items: stretch; gap: 16px; }
/* Legend cards are not tables: they inherit .table-block for the panel look,
   but its `overflow-x: auto` (there to let a too-wide transit table scroll
   inside its own card) makes BOTH axes a scroll container per CSS overflow
   rules - which clipped the gloss popovers at the card edge and, once the
   cards were stretched to a shared height, added scrollbars to boot. The
   lists also must not shrink: as flex items they would otherwise compress
   below their content and produce the very overflow being clipped. */
.legend-groups > .table-block { display: flex; flex-direction: column; overflow: visible; }
.legend-block > ul { flex: 0 0 auto; }
.legend-block h3 { margin: 0 0 10px; }
.legend-block ul { list-style: none; margin: 0; padding: 0; }

/* Two-column glyph lists: planets/points, signs, angles. */
.legend-glyphs, .legend-signs, .legend-angles {
  display: grid; grid-template-columns: repeat(2, minmax(110px, 1fr)); gap: 4px 12px;
}
.legend-glyphs li, .legend-signs li, .legend-angles li {
  display: flex; align-items: center; gap: 6px; font-size: .85rem; padding: 2px 0;
}

.legend-chip, .legend-glyph {
  display: inline-block; flex: 0 0 22px; width: 22px; text-align: center; font-size: 1.05rem;
}
/* Planet/point glyphs: neutral ink, matching .w-glyph on the wheel (bodies
   aren't colour-coded there, so the legend shouldn't invent a colour either). */
.legend-chip { color: var(--w-glyph); }

/* Sign glyph+name tinted by element, matching .w-el-* fills on the wheel. */
.legend-tint-fire  { color: var(--el-fire); }
.legend-tint-earth { color: var(--el-earth); }
.legend-tint-air   { color: var(--el-air); }
.legend-tint-water { color: var(--el-water); }

/* Element key doubles as the card's footer: `margin-top: auto` pins it to the
   bottom of the stretched card, so it lines up with the neighbouring cards'
   lower edge instead of floating mid-card. The 10px it used to have is now
   the minimum gap, kept by padding-top. */
/* Selector carries `ul` on purpose: `.legend-block ul { margin: 0 }` above is
   more specific than a lone class, so a bare `.legend-elements { margin-top }`
   silently loses to it — which is why the strip used to sit right under the
   sign grid no matter what margin was declared here. */
.legend-block ul.legend-elements {
  display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: auto;
  padding-top: 10px; border-top: 1px solid var(--border);
}
.legend-elements li { display: flex; align-items: center; gap: 6px; font-size: .85rem; }

.legend-swatch { display: inline-block; width: 11px; height: 11px; border-radius: 3px; }
.legend-dot-fire  { background: var(--el-fire); }
.legend-dot-earth { background: var(--el-earth); }
.legend-dot-air   { background: var(--el-air); }
.legend-dot-water { background: var(--el-water); }

.legend-aspects li {
  display: flex; align-items: center; gap: 10px; padding: 5px 0;
  border-bottom: 1px solid var(--border);
}
.legend-aspects li:last-child { border-bottom: none; }
.legend-line-sample { flex: 0 0 auto; overflow: visible; }
.legend-aspect-text { display: flex; flex-direction: column; gap: 1px; font-size: .85rem; }
.legend-aspect-text strong { font-weight: 600; }
.legend-aspect-text .muted { font-size: .78rem; }

/* Angle code badge, coloured with the same token as the wheel's angle axes
   (.w-angle) and labels (.w-angle-label). */
.legend-angle-badge {
  display: inline-block; min-width: 32px; text-align: center;
  font-size: .75rem; font-weight: 600; letter-spacing: .02em;
  color: var(--w-angle); border: 1px solid var(--w-angle); border-radius: 6px;
  padding: 1px 5px;
}

@media (prefers-reduced-motion: reduce) {
  *, #wheel * { transition: none !important; }
  .spinner { animation: none !important; }
}

/* ---- Transit overlay (wheel.js, optional layer) ---- */
/* Dashed transit->natal aspect lines; colour comes from the shared
   w-asp-* classes, so only the dash pattern is added here. */
/* pointer-events: none on BOTH transit-line elements (T-hittest class of
   bug, reported live): they are appended AFTER the natal chips, so a
   transparent-stroke line (transparent is painted, unlike `none`) crossing
   a chip would sit on top and swallow its hover/click. No listeners are
   wired to these lines (they are lit up FROM the info-card rows, not
   hovered themselves), so they must be transparent to the pointer; the
   .hl/.dim highlight classes still apply - highlighting needs no events. */
.w-taspect { stroke-dasharray: 5 4; pointer-events: none; }
.w-taspect-hit { stroke: transparent; stroke-width: 12; pointer-events: none; }
/* Transit chips: same hover affordances as natal .w-body (reported:
   cursor/highlight were inconsistent between the two rings) - pointer
   cursor, accent ring on the badge, bold glyph. The (0,2,0) hover
   selectors outrank the (0,1,0) .w-tbadge/.w-tglyph base rules below. */
.w-tbody { cursor: pointer; }
.w-tbody:hover .w-badge { stroke: var(--accent); stroke-width: 1.5; }
.w-tbody:hover .w-glyph { font-weight: 700; }
/* Transit chips: slightly smaller and muted so the natal ring keeps
   visual priority. */
.w-tbadge { fill: var(--panel-2); stroke: var(--border); }
.w-tglyph { font-size: 13px; fill: var(--muted); }
.w-tdeg   { font-size: 10px; fill: var(--muted); }

/* ---- Пара: одна секция, две техники (#pair-panel в index.html) ---- */
/* Партнёр выбирается ОДИН раз на обе техники, поэтому селектор стоит над
   блоками, а не внутри каждого. Блоки разделены линией сверху: без неё две
   группы кнопок («Построить синастрию…» и «Построить карту пары…») читались
   одной свалкой из девяти штук. Заголовок блока — h3 того же кегля, что h2
   секции: это не более мелкий раздел, а половина одного. */
.pair-partner { display: flex; flex-direction: column; gap: 4px; }
.pair-block { display: flex; flex-direction: column; gap: 8px;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.pair-block h3 { margin: 0; font-size: 1rem; }
.pair-block .muted { margin: 0; }

/* ---- Synastry (synastry.js; ring reuses the transit-overlay geometry) ---- */
/* The same outer ring shows either transits or the partner's chart, never
   both; the syn-ring class on the <svg> is what tells them apart visually.
   Solid partner->base lines (transits stay dashed) + accent-tinted badge:
   the reader must see at a glance this is a person, not a moment. */
.syn-ring .w-taspect { stroke-dasharray: none; }
.syn-ring .w-tbadge { stroke: var(--accent); }
/* Панель техники — колонка с ритмом: строки кнопок и пояснения шли вплотную,
   потому что зазор был задан только ВНУТРИ строки (gap у .syn-controls), а
   между строками — ничем. То же у карты пары. */
.syn, .cp { display: flex; flex-direction: column; gap: 8px; }
.syn-controls { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* Кегль и линии строк — как у натальных таблиц (.table-block table): это те
   же данные о тех же сущностях, и разный размер шрифта в соседних разделах
   читался как разные части приложения. */
.syn-table { border-collapse: collapse; margin: 8px 0; width: 100%;
  font-size: .85rem; }
.syn-table th, .syn-table td { padding: 4px 6px; text-align: left;
  border-bottom: 1px solid var(--border); }
.syn-overlay h3, .syn-out > h3, .cp-out > h3 { margin: 12px 0 4px;
  font-size: 1rem; }
.syn-pair { margin: 4px 0 8px; }

/* ---- Карта пары (composite.js) ---- */
/* Те же правила, что у синастрии: таблицы со своим горизонтальным скроллом
   (их min-content ширина иначе растягивает страницу), кнопки в ряд. Колесо
   секции — обычное колесо, просто во второй <svg>. */
.cp-controls { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.cp-table { border-collapse: collapse; margin: 8px 0; width: 100%;
  font-size: .85rem; }
.cp-table th, .cp-table td { padding: 4px 6px; text-align: left;
  border-bottom: 1px solid var(--border); }
.cp-scroll { overflow-x: auto; }
.cp-pair { margin: 4px 0 8px; }
.cp-wheel { width: 100%; max-width: 620px; height: auto; display: block; }
/* Шапка пары несёт два полных имени, поэтому таблица не ужимается ниже
   ~390 px и на мобильной ширине растягивала ВСЮ страницу (замер: scrollWidth
   415 при вьюпорте 390). Скролл — на своём контейнере, а не на секции. */
.syn-scroll { overflow-x: auto; }
/* Вывод пары — колонка карточек с тем же зазором, что у натальных таблиц
   (.tables-col): каждый блок отдельной карточкой, между ними просвет. */
.syn-out, .cp-out { display: flex; flex-direction: column; gap: 16px; }
.syn-block, .cp-block { overflow: visible; }
.syn-block h2, .cp-block h2 { margin: 0 0 8px; font-size: 1rem; }

/* ---- Collapsible sections (collapse.js) ---- */
/* Шапка раздела: стрелка сворачивания стоит у ПРАВОГО края, поэтому шапка
   обязана быть во всю ширину секции. Без stretch/width она сжималась по тексту
   в секциях с `align-items: flex-start` (разбор карты, тренажёр, прогноз,
   сила планет, выкладка, ректификация) — и стрелка там прилипала к заголовку,
   тогда как в остальных разделах стояла справа. Именно это и выглядело как
   «стрелки разъехались»: одно правило, два разных положения. */
.collapse-header { cursor: pointer; user-select: none;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  align-self: stretch; width: 100%; box-sizing: border-box; }
.collapse-btn { appearance: none; border: none; background: none; color: var(--muted);
  font: inherit; font-size: .9rem; cursor: pointer; padding: 0 4px; }
/* Hide everything in a collapsed section except its WIRED header (the one
   makeCollapsible marked with .collapse-header). display:none (not height
   tricks): SVG/tables reflow correctly on re-expand. Excluding all h1/h2/h3
   here instead used to leave INNER sub-headers visible under a collapsed
   section - «Если сменить систему домов» floated as orphan bold text below
   a collapsed «Качество данных рождения» (reported with a screenshot).
   Safe because every render calls makeCollapsible synchronously right
   after, so a fresh header carries .collapse-header before any paint. */
/* !important for the same reason as the [hidden] rule at the top of this
   file: ID rules like `#people-list { display: flex }` (1,0,0) outrank this
   class-chain child selector (0,3,0), so without !important a collapsed
   section's list stayed visible while the chevron toggled (reported with a
   screenshot). */
.collapsible.collapsed > *:not(.collapse-header) { display: none !important; }

/* Transit-delta change panel + saved-moments history (spec 2026-07-16). */
/* Two endpoint slots (От/До), each a filterable dropdown to pick a moment plus
   a "Текущий" (now) button. От = colour A, До = colour B (matching row tags). */
.delta-slots { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.delta-slot {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 6px 4px 8px; border: 1px solid var(--border); border-radius: 8px;
  border-left-width: 3px;
}
.delta-slot[data-side="from"] { border-left-color: var(--accent); }
.delta-slot[data-side="to"]   { border-left-color: var(--accent-2); }
.slot-head { min-width: 2.2em; font-weight: 600; }
.delta-slot[data-side="from"] .slot-head { color: var(--accent); }
.delta-slot[data-side="to"] .slot-head { color: var(--accent-2); }
.slot-combo { position: relative; flex: 1 1 auto; min-width: 0; }
.slot-select {
  display: flex; align-items: center; gap: 6px; width: 100%;
  padding: 4px 8px; font-size: .9rem; text-align: left;
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
}
.slot-select:hover { background: var(--border); }
.slot-value { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.slot-caret { color: var(--muted); font-size: .8rem; }
.moment-menu {
  position: absolute; top: calc(100% + 3px); left: 0; right: 0; z-index: 30;
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,.28); padding: 6px;
}
.moment-filter {
  width: 100%; box-sizing: border-box; margin-bottom: 6px;
  padding: 5px 8px; font-size: .85rem;
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border); border-radius: 6px;
}
.moment-options { list-style: none; margin: 0; padding: 0; max-height: 220px; overflow-y: auto; }
.moment-options li { padding: 5px 8px; border-radius: 6px; cursor: pointer; font-size: .88rem; }
.moment-options li:hover { background: var(--panel-2); }
.moment-options li.moment-empty { cursor: default; }
.slot-now {
  padding: 4px 10px; font-size: .82rem; white-space: nowrap;
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
}
.slot-now:hover { background: var(--border); }
.delta-hint { margin: 0 0 6px; }
/* Пояснение к панели прогноза и ряд её кнопок: подсказка объясняет, что за
   техники в блоке, кнопки идут строкой и переносятся на узком экране. */
.forecast-hint { margin: 0 0 8px; max-width: 70ch; }

/* Экран входа: перекрывает приложение целиком, пока нет сессии. */
.auth-screen {
  position: fixed; inset: 0; z-index: 50;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg); padding: 16px;
}
.auth-card {
  width: min(360px, 100%);
  display: flex; flex-direction: column; gap: 10px;
  background: var(--panel); border: 1px solid var(--border);
  /* Радиус — как у карточек главной страницы (.section-card): страница входа
     и админка держали свои 12px, и переход между страницами был заметен. */
  border-radius: 10px; padding: 20px;
}
.auth-card h1 { margin: 0; font-size: 1.25rem; }
.auth-card label { display: flex; flex-direction: column; gap: 4px; font-size: .9rem; }
.auth-tabs { display: flex; gap: 6px; }
.auth-tab {
  flex: 1; padding: 6px 10px; border: 1px solid var(--border);
  border-radius: 8px; background: transparent; color: inherit; cursor: pointer;
}
.auth-tab.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }

/* Страница администрирования кабинетов (/admin). Своя раскладка: .layout здесь
   не нужна — левой колонки с библиотекой людей на этой странице нет, и .panel
   с её фиксированной шириной тоже не подходит. */
/* Своя раскладка целиком, без .layout: та выложена в строку с переносом (левая
   панель + контент) и на этой странице давала лишние отступы и сжатые по
   содержимому карточки. */
.admin-layout {
  display: flex; flex-direction: column; align-items: stretch;
  gap: 16px; padding: 24px; max-width: 1200px; margin: 0 auto;
}
.admin-layout .topbar { margin-bottom: 0; }
.admin-panel {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 10px; padding: 16px;
  display: flex; flex-direction: column; gap: 12px;
}
/* Прокрутка широкой таблицы — на своей обёртке, а не на карточке: иначе
   таблица вылезает за её скруглённую рамку. min-width: 0 обязателен: обёртка —
   флекс-элемент, а у него min-width по умолчанию auto, то есть по содержимому,
   и overflow-x просто не включается — таблица распирает карточку изнутри. */
.admin-table-wrap { overflow-x: auto; min-width: 0; width: 100%; }
.admin-panel h2 { margin: 0; font-size: 1rem; }
.admin-back { color: var(--accent); text-decoration: none; font-size: .9rem; }
.admin-back:hover { text-decoration: underline; }
.admin-note { max-width: 80ch; }
.admin-users { width: 100%; border-collapse: collapse; font-size: .88rem; }
.admin-users th, .admin-users td {
  text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--border);
  vertical-align: middle; white-space: nowrap;
}
.admin-users th { color: var(--muted); font-weight: 500; font-size: .82rem; }
/* Итог по всем кабинетам: отделён линией и весом, иначе читается как ещё
   один кабинет с пустыми полями. */
.admin-users tfoot th, .admin-users tfoot td {
  border-bottom: none; border-top: 2px solid var(--border);
  font-weight: 600; color: var(--text);
}
.admin-login { font-weight: 600; }
.admin-badge {
  margin-left: 6px; padding: 1px 6px; border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  color: var(--accent); font-size: .72rem; font-weight: 500;
}
.admin-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .8rem; }
.admin-limit { width: 64px; }
/* Кнопки переносятся на вторую строку вместо того, чтобы распирать таблицу
   вширь: горизонтальный скролл на ШИРОКОМ экране появлялся из-за самого
   длинного нескладываемого ряда «Пароль · Отозвать сессии · Удалить»
   (жалоба со скриншота). overflow-x на обёртке остаётся фолбэком для
   действительно узких экранов. */
.admin-actions { display: flex; gap: 6px; flex-wrap: wrap; max-width: 150px; }
.admin-actions button { padding: 4px 9px; font-size: .8rem; }
/* Разрушительное действие выглядит разрушительным: удаление кабинета уносит
   его персоны, историю и разборы. */
button.danger {
  background: color-mix(in srgb, var(--danger) 16%, transparent);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  color: var(--danger);
}
button.danger:hover { background: color-mix(in srgb, var(--danger) 28%, transparent); }

/* Диалог приглашений. Нативный <dialog>: подложка, модальность и Esc — от
   браузера; ::backdrop подкрашивается, потому что по умолчанию он почти
   прозрачный и в тёмной теме диалог висел бы без границы контекста. */
.invite-dialog {
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--panel); color: var(--text);
  padding: 0; width: min(460px, calc(100vw - 24px));
}
.invite-dialog::backdrop { background: rgba(0,0,0,.5); }
.invite-form { display: flex; flex-direction: column; gap: 10px; padding: 20px; }
.invite-form h2 { margin: 0; font-size: 1.1rem; }
.invite-form h3 { margin: 6px 0 0; font-size: .95rem; }
.invite-params { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
.invite-params label { display: flex; flex-direction: column; gap: 4px; }
/* Глобальное `button { align-self: flex-start }` перебивает align-items строки,
   и «Выпустить» висела на 28 px выше своих полей — кнопка стояла на уровне
   ПОДПИСЕЙ, а не самих выпадающих списков. */
.invite-params button { align-self: flex-end; }
/* Свежий код — главное в диалоге, поэтому он крупный и в своей рамке. */
.invite-fresh {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  padding: 12px; border-radius: 10px;
  background: var(--panel-2); border: 1px solid var(--accent);
}
.invite-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.25rem; letter-spacing: .06em; user-select: all;
}
.invite-note { margin: 0; }
.invite-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.invite-row {
  display: flex; align-items: baseline; gap: 10px; justify-content: space-between;
  padding: 5px 8px; border-radius: 6px; background: var(--panel-2);
}
.invite-row code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; user-select: all; }
/* Израсходованный или истёкший код остаётся в списке, но приглушён: он больше
   не работает, а вычёркивать строку — терять след, кому он выдавался. */
.invite-row.is-spent { opacity: .55; }
.invite-state { white-space: nowrap; font-size: .8rem; }
.invite-actions { display: flex; justify-content: flex-end; margin-top: 4px; }

/* Кабинет в шапке: кнопка с логином, под ней меню с лимитом и действиями.
   Прежде это были три ссылки подряд прямо в .topbar — у неё три ребёнка и
   justify-content: space-between, поэтому блок кабинета вставал в середину
   шапки и читался как случайный текст, а не как элемент управления. */
.topbar-right { display: flex; align-items: center; gap: 10px; }
.account { position: relative; font-size: .85rem; }
.account-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: 9px;
  border: 1px solid var(--border); background: var(--panel-2);
  color: var(--text); font: inherit; font-size: .85rem; cursor: pointer;
}
.account-btn:hover { border-color: var(--accent); }
.account-btn[aria-expanded="true"] { border-color: var(--accent); background: var(--panel); }
.account-name { font-weight: 600; max-width: 14ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-caret { color: var(--muted); font-size: .7rem; }

.account-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
  min-width: 220px; padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,.28);
}
.account-head {
  padding: 6px 8px 8px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.account-login { font-weight: 600; }
.account-role, .account-quota { font-size: .8rem; margin: 0; }
/* Пункты меню: и кнопка, и ссылка выглядят одинаково — разница между ними
   техническая (админка это переход на страницу), а не смысловая. */
.account-menu [role="menuitem"] {
  display: block; width: 100%; text-align: left;
  padding: 7px 8px; border: none; border-radius: 6px;
  background: none; color: var(--text); font: inherit; font-size: .85rem;
  text-decoration: none; cursor: pointer;
}
.account-menu [role="menuitem"]:hover { background: var(--panel-2); }
/* Меню шире экрана на узких устройствах быть не может, но и обрезаться не
   должно: остаток лимита теперь живёт здесь, и прятать его больше нечем. */
@media (max-width: 640px) {
  .account-menu { min-width: min(260px, calc(100vw - 24px)); }
}
.btn-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }

/* Панель «Год и лунации»: обвязка — общая (.section-card), здесь внутренности. */
#year-context .year-block { margin: 0 0 1rem; overflow: visible; }
#year-context .year-block:last-child { margin-bottom: 0; }
#year-context .year-head { font-size: 1.05rem; margin: 0 0 .25rem; }
#year-context h3 { font-size: .95rem; margin: .6rem 0 .2rem; }
#year-context ul { margin: .25rem 0 .5rem; padding-left: 1.2rem; }
#year-context li { font-size: .9rem; margin: 2px 0; }
#year-context .year-eclipse-mark { font-weight: 600; }

/* Пошаговое раскрытие слоёв. Гашение прозрачностью, а не display:none —
   элемент остаётся в потоке и сохраняет свои слушатели (см. layers.js). */
.layer-off { opacity: .08; pointer-events: none; transition: opacity .2s ease; }
/* Панель слоёв — на всю ширину под колесом, а не третьим элементом в одной
   строке с ним: её подпись меняет длину на каждом шаге, и в общей строке
   это перекраивало раскладку. */
#layers { flex: 1 1 100%; margin-top: 8px; }
.layers-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.layers-bar button { padding: 4px 10px; font-size: .85rem; }
#layers-hint { margin: 6px 0 0; font-size: .85rem; }

/* Тренажёр: вопросы, повторение глоссария, учебные карты. */
#trainer-panel {
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
}
.trainer-bar { display: flex; gap: 8px; flex-wrap: wrap; }
#quiz, #srs, #study { align-self: stretch; }
.quiz-q { margin: 10px 0; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
.quiz-question { font-weight: 600; font-size: .95rem; margin-bottom: 6px; }
.quiz-options { display: flex; gap: 6px; flex-wrap: wrap; }
.quiz-option {
  padding: 5px 10px; font-size: .85rem; background: var(--panel-2);
  color: var(--text); border: 1px solid var(--border);
}
.quiz-option.quiz-right { background: color-mix(in srgb, var(--asp-soft) 30%, transparent); }
.quiz-option.quiz-wrong { background: color-mix(in srgb, var(--danger) 30%, transparent); }
.quiz-explain { font-size: .85rem; margin-top: 6px; }
.srs-card { border: 1px solid var(--border); border-radius: 8px; padding: 12px; max-width: 640px; }
.srs-term { font-weight: 600; margin-bottom: 6px; }
.srs-answer { font-size: .9rem; margin-bottom: 8px; }
.srs-bar { display: flex; gap: 8px; }
.study-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.study-list li { border: 1px solid var(--border); border-radius: 8px; padding: 10px; }
.study-when, .study-bio { font-size: .87rem; margin: 2px 0 6px; }
.study-note { font-size: .82rem; }
#my-reading { width: 100%; max-width: 900px; font: inherit; font-size: .9rem; padding: 8px; }
.compare-table { border-collapse: collapse; font-size: .88rem; margin: 8px 0; }
.compare-table th, .compare-table td {
  text-align: left; padding: 4px 10px; border-bottom: 1px solid var(--border);
}

#forecast-panel {
  display: flex; flex-direction: column; gap: 8px; align-items: flex-start;
}
#forecast { align-self: stretch; }
#forecast h3 { font-size: .95rem; margin: .7rem 0 .2rem; }
#forecast p { margin: .2rem 0; font-size: .9rem; }
#forecast ul { margin: .2rem 0 .4rem; padding-left: 1.2rem; font-size: .88rem; }

/* Сила планет: широкая таблица, поэтому своя прокрутка по горизонтали. */
#strength-panel {
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
}
#strength { align-self: stretch; overflow-x: auto; }
.strength-table { width: 100%; border-collapse: collapse; font-size: .88rem; }
.strength-table th, .strength-table td {
  text-align: left; padding: 4px 8px; border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.strength-table .str-plus { color: var(--asp-soft); font-weight: 600; }
.strength-table .str-minus { color: var(--danger); font-weight: 600; }
.str-note { font-size: .82rem; }

/* Выкладка: дерево обоснования. Вложенность показана отступом и линией слева,
   чтобы шаг было видно глазами без раскрывающихся виджетов. */
#derivation-panel {
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
}
#derivation { align-self: stretch; }
.drv-node { margin: 8px 0; overflow: visible; }
.drv-level-1 { margin-left: 16px; padding-left: 12px; border-left: 2px solid var(--border); }
.drv-level-2 { margin-left: 16px; padding-left: 12px; border-left: 2px solid var(--border); }
.drv-head { font-weight: 600; font-size: .95rem; }
.drv-conclusion { font-size: .92rem; margin: 2px 0 4px; }
.drv-evidence {
  display: grid; grid-template-columns: auto 1fr; gap: 2px 10px;
  margin: 0 0 4px; font-size: .85rem; color: var(--muted);
}
.drv-evidence dt { grid-column: 1; }
.drv-evidence dd { grid-column: 2; margin: 0; }

/* Проверка фактов разбора: сводка под текстом и подсветка утверждений в нём.
   Подсветка — фон, а не цвет текста: разбор остаётся читаемым в обеих темах. */
#factcheck { align-self: stretch; }
#factcheck .fc-summary { font-size: .9rem; }
#factcheck .fc-mismatch-list { margin: .25rem 0 .5rem; padding-left: 1.2rem; }
#factcheck .fc-mismatch-list li { font-size: .9rem; margin: 4px 0; }
#factcheck .fc-quote { font-size: .82rem; }
#factcheck .fc-note { font-size: .82rem; }
.fc-claim { border-radius: 4px; padding: 0 2px; }
.fc-ok { background: color-mix(in srgb, var(--asp-soft) 18%, transparent); }
.fc-mismatch { background: color-mix(in srgb, var(--danger) 22%, transparent); }
.fc-unchecked { background: color-mix(in srgb, var(--muted) 14%, transparent); }

/* Пометка над восстановленным из кеша разбором, который сервер счёл
   устаревшим (loadPerson + person.interp_stale). */
.stale-note {
  margin: 0 0 12px; padding: 8px 10px; border-radius: 8px;
  font-size: .85rem; color: var(--warn);
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--warn) 35%, transparent);
}

/* Панель «Качество данных рождения» — обвязка общая, здесь внутренности. */
#data-quality p { margin: .25rem 0; }
#data-quality ul { margin: .25rem 0 .5rem; padding-left: 1.2rem; }
#data-quality li { font-size: .9rem; margin: 2px 0; }
#data-quality .dq-warning { color: var(--warn); }
#data-quality h3 { font-size: .95rem; margin: .7rem 0 .2rem; }
#data-quality .dq-house-diff { columns: 2; }
@media (max-width: 700px) { #data-quality .dq-house-diff { columns: 1; } }

#transit-delta { display: flex; flex-direction: column; gap: 10px; margin: 8px 0; }
.delta-group { border-left: 3px solid var(--border); padding-left: 10px; }
.delta-group-title { font-weight: 600; font-size: .9rem; margin-bottom: 2px; }
.delta-group ul { list-style: none; margin: 0; padding: 0; }
.delta-group li { font-size: .88rem; margin: 1px 0; }
.delta-group-muted { opacity: .7; }
.delta-group-muted .delta-group-title { color: var(--muted); }
.history-list { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.history-list li {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 8px; border: 1px solid var(--border); border-radius: 6px;
}
.history-list li .history-moment { flex: 1 1 auto; }
/* Delta endpoints highlighted in the list: От on the left edge (colour A),
   До on the right edge (colour B) - matching the selector highlights. */
.history-list li.role-from { border-left: 3px solid var(--accent); }
.history-list li.role-to { border-right: 3px solid var(--accent-2); }
.history-list li.role-both { border-left: 3px solid var(--accent); border-right: 3px solid var(--accent-2); }
.history-list li.selectable { cursor: pointer; }
.history-list li.selectable:hover { background: var(--panel-2); }
.history-list li.is-viewed { opacity: .82; }
.history-badge {
  font-size: .72rem; padding: 1px 7px; border-radius: 999px;
  background: var(--panel-2); color: var(--muted); border: 1px solid var(--border);
}
.moment-step { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 4px 0 6px; font-size: .85rem; }
.moment-step input { width: 3.4em; padding: 3px 6px; box-sizing: border-box; }
.moment-step select { padding: 3px 6px; font-size: .85rem; }
.moment-shift { font-weight: 700; }

/* «Объяснить» внутри информационных блоков (block_interp.js). */
.block-interp { margin-top: 10px; }
.block-interp-status { margin-left: 8px; }
.block-interp-out { margin-top: 8px; }
.block-interp-cached { font-size: .85rem; }

/* Ректификация (rectify.js). Карточка следует общей обвязке секций; здесь
   только внутренности: форма события в одну строку, кривая, карточки пиков. */
#rectify-panel { display: flex; flex-direction: column; gap: 8px; }
.rect-form { display: flex; gap: 6px; flex-wrap: wrap; }
.rect-form select, .rect-form input { width: auto; flex: 1 1 120px; }
.rect-events { margin: 0; padding-left: 18px; }
.rect-event { display: flex; gap: 8px; align-items: baseline; }
.rect-curve { width: 100%; height: 48px; }
.rect-curve polyline { stroke: var(--accent); stroke-width: 1.5; }
.rect-curve text { font-size: 8px; fill: var(--muted); }
/* Отметка сохранённого времени: пунктир, чтобы не спорить с кривой. */
.rect-curve .rect-known-line { stroke: var(--warn, #d0a215); stroke-width: 1.5;
  stroke-dasharray: 3 2; }
.rect-peak { border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 10px; margin-top: 6px; }
.rect-peak .rect-apply-btn { margin-left: 10px; }
.rect-contrib { margin: 4px 0 0; padding-left: 18px; font-size: .85rem; }
.rect-verdict-ok { color: var(--accent-2); }
.rect-verdict-no { color: var(--warn); }


/* Слепая проверка резонанса (/blind). Шкала — ряд кнопок, а не select:
   48 оценок за сессию, и каждый лишний клик по выпадающему списку
   умножается на 48. */
.blind-item { border: 1px solid var(--border); border-radius: 10px;
  padding: 12px 14px; margin-bottom: 12px; background: var(--panel-2); }
.blind-item h3 { margin: 0 0 8px; font-size: .95rem; }
.blind-text { margin: 0 0 10px; line-height: 1.5; }
.blind-scale { display: flex; flex-wrap: wrap; gap: 6px; }
.blind-score { align-self: auto; min-width: 34px; padding: 6px 0;
  text-align: center; }
.blind-score.is-picked { background: var(--accent); color: var(--bg); }
.blind-block { display: flex; gap: 8px; align-items: flex-start;
  margin-bottom: 8px; line-height: 1.45; cursor: pointer; }
.blind-block input { margin-top: 4px; }
.blind-stats { width: 100%; border-collapse: collapse; font-size: .85rem; }
.blind-stats th { text-align: left; font-weight: normal; padding: 4px 8px 4px 0;
  border-bottom: 1px solid var(--border); }
.blind-stats td { text-align: right; padding: 4px 0;
  border-bottom: 1px solid var(--border); }
.blind-stats-head th { font-weight: 600; padding-top: 12px; }
.linklike { background: none; border: none; padding: 2px 0; color: var(--accent);
  cursor: pointer; text-align: left; }
