/* ==================== КАНОН ОФОРМЛЕНИЯ — «Тёплый премиум» ====================
   База выросла из ai-factory.silpo.ua: тёплый кремовый фон, чернильный текст,
   оранжевый primary, всё скруглено. 08.2026 — эволюция по утверждённому
   референсу redesign-a-warm («Тёплый премиум»): тёплый канон остаётся, сверху
   добавились два новых слоя канона — типографика и глубина.

   ТИПОГРАФИКА — два голоса:
   · --font-text — Onest (variable, OFL, полная кириллица): весь интерфейсный
     текст. Golos Text заменён — Onest ровнее и у него один файл на все веса.
   · --font-display — Piazzolla (variable, OFL): заголовки страниц и КРУПНЫЕ
     ЧИСЛА (плитки, центры колец, донаты) — антиквенная «банковская» подача
     цифр из референса. Приветствие на дашборде — Piazzolla italic.
   · Колонки цифр — tabular-nums (таблицы уже размечены), крупные одиночные
     числа — lining-nums.

   ГЛУБИНА — слоистость вместо плоских рамок:
   · Подложка страницы (--bg) ГЛУБЖЕ карточек (--surface): карточка светлее
     и потому «ближе». Тени --shadow-card (покой) и --shadow-raised
     (модалка, hover кликабельных плиток) — многослойные и ТЁПЛЫЕ
     (коричневый 74,52,28): чёрная тень на кремовом читается грязью.
     Жёсткие 1px-рамки карточек ослаблены — контур теперь держит тень.
   · Сигнатура — «утренний свет»: шафрановое и терракотовое свечение
     радиальными градиентами у верхней кромки body.

   АКЦЕНТЫ: --accent (оранжевый) остаётся главным; --accent-2 (терракота) —
   микро-акценты второго плана (hover текстовых ссылок, надзаголовки).
   Статусные --ok/--warn/--bad/--alarm неприкосновенны: это язык данных.
   ============================================================================ */

/* Piazzolla + Onest: variable-файлы, порезанные по алфавитам, самохостинг под
   CSP (font-src 'self'). Piazzolla есть и курсивом — им набрано приветствие.
   font-display: swap + preload четырёх главных файлов в base.html: текст
   виден сразу, перескок на догрузившуюся гарнитуру короткий. */
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/onest-normal-cyrillic-ext.woff2") format("woff2");
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/onest-normal-cyrillic.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/onest-normal-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/onest-normal-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Piazzolla";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/piazzolla-italic-cyrillic-ext.woff2") format("woff2");
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: "Piazzolla";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/piazzolla-italic-cyrillic.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Piazzolla";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/piazzolla-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Piazzolla";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/piazzolla-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Piazzolla";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/piazzolla-normal-cyrillic-ext.woff2") format("woff2");
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: "Piazzolla";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/piazzolla-normal-cyrillic.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Piazzolla";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/piazzolla-normal-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Piazzolla";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/piazzolla-normal-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
:root {
  /* база из референса redesign-a-warm: подложка страницы ГЛУБЖЕ карточки —
     на этой разнице держится слоистость (карточка светлее и потому «ближе») */
  --bg: #f6efe3;
  --surface: #fdfaf3;
  --border: oklch(90% 0.02 75);
  --text: oklch(22% 0.02 55);
  --muted: oklch(48% 0.03 60);
  --accent: oklch(74% 0.18 55);       /* фирменный оранжевый */
  --accent-dark: oklch(62% 0.18 50);  /* он же под курсором */
  /* терракота референса — акцент ВТОРОГО плана: hover текстовых ссылок,
     надзаголовки. Не соперник --accent, а его тень. */
  --accent-2: #bc4f27;
  --accent-soft: oklch(90% 0.17 92);  /* фирменный жёлтый — активные чипы, подложки */
  /* вторичная заливка референса (--secondary у ai-factory.silpo.ua): тёплая
     светлая, а не серая. Серый в этой палитре выглядит грязным пятном. */
  --soft: #f6f0e2;      /* внутренние плашки референса (card-soft) */
  --soft-dark: #ece2cc;
  --blue: oklch(60% 0.18 258);
  /* сон синий, активность оранжевая — язык ленты суток и полосок недели */
  --sleep: #6f8fc4;
  --sleep-dark: #4d6c9e;
  --nap: #9db4e8;
  --ok: oklch(58% 0.13 155);
  --warn: oklch(66% 0.14 75);
  --bad: oklch(58% 0.24 27);
  /* ⚠️ Заливка тревожной строки — отдельный цвет, а не разбавленный --bad.
     У --bad тон 27, чистый красный: разлитый по всей строке он выпадает из
     тёплой палитры холодным розовым пятном. Тон 32 и меньше цветность — тот же
     сигнал, но он живёт рядом с оранжевым, а не спорит с ним. Само число
     остаётся --bad: акцент должен быть в цифре, а не в подложке. */
  --alarm: oklch(60% 0.20 32);
  --radius: 20px;
  /* Глубина по референсу: тени многослойные и ТЁПЛЫЕ (коричневый 74,52,28) —
     чёрная тень на кремовом читается грязью. --shadow-card — покой карточки,
     --shadow-raised — приподнятость: модалка, hover кликабельной плитки. */
  --shadow-card:
    0 1px 2px rgb(74 52 28 / .05),
    0 5px 14px rgb(74 52 28 / .05),
    0 26px 48px -22px rgb(74 52 28 / .17);
  --shadow-raised:
    0 2px 3px rgb(74 52 28 / .05),
    0 10px 22px rgb(74 52 28 / .07),
    0 34px 60px -22px rgb(74 52 28 / .22);
  /* старое имя живёт как алиас: им пользуются подражатели карточки (.chart-tip) */
  --shadow: var(--shadow-card);
  /* «утренний свет» — свечение верхней кромки страницы (сигнатура референса);
     интенсивность своя на каждую тему, сам градиент задан один раз у body */
  --glow-saffron: rgb(223 160 50 / .17);
  --glow-terra: rgb(188 79 39 / .07);
  /* два голоса типографики — канон в шапке файла */
  --font-text: "Onest", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-display: "Piazzolla", Georgia, "Times New Roman", serif;
  /* Градиенты колец Whoop: светлый конец дуги → тёмный. Семантика зон
     восстановления сохранена — зелёная/жёлтая/красная зоны различимы,
     градиент живёт ВНУТРИ своего цвета, а не поверх него. */
  --ring-ok-1: #84a76c;     --ring-ok-2: #4f7442;   /* шалфей */
  --ring-warn-1: #e8c25b;   --ring-warn-2: #c08a1f;
  --ring-bad-1: #e07a5f;    --ring-bad-2: #b03a2e;
  --ring-sleep-1: #8fa3d3;  --ring-sleep-2: #47608f; /* барвинок */
  --ring-strain-1: #dfa032; --ring-strain-2: #bc4f27; /* шафран → терракота */
  /* высота липкой панели навигации: к ней привязана липкая шапка таблицы,
     поэтому значение задано один раз и здесь */
  --topbar-h: 60px;
  /* Моушен-токены: три роли вместо россыпи магических чисел (.1/.12/.15/.25/.3),
     которые уже разъехались между соседними карточками. --t-fast — наведения и
     мелкие состояния, --t-base — появления (модалки, подсказки, баннеры),
     --t-slow — крупные раскрытия (карточка приёма). --ease-out тормозит к концу:
     ответ на клик стартует сразу, а не разгоняется. --ease-spring — лёгкий
     перелёт для единственного места с характером (галочка чекбокса). */
  --t-fast: .12s;
  --t-base: .2s;
  --t-slow: .3s;
  --ease-out: cubic-bezier(.23, 1, .32, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
  /* опт-ин интерполяции к auto/max-content: без него не анимируется
     раскрытие нативных <details> (block-size 0 → auto) */
  interpolate-size: allow-keywords;
}

@media (prefers-color-scheme: dark) {
  /* тот же тёплый характер, но чернильный: не сине-серый, а коричневый */
  :root {
    --bg: oklch(20% 0.02 55);
    --surface: oklch(25% 0.02 55);
    --border: oklch(34% 0.02 60);
    --text: oklch(96% 0.02 80);
    --muted: oklch(70% 0.03 70);
    --accent: oklch(76% 0.17 55);
    --accent-dark: oklch(86% 0.15 60);  /* в тёмной теме «ярче», а не «темнее» */
    --accent-soft: oklch(45% 0.09 92);
    /* в тёмной теме «мягкая» заливка светлее фона, а не темнее — иначе кнопка
       проваливается в подложку и перестаёт читаться кнопкой */
    --soft: oklch(30% 0.02 55);
    --soft-dark: oklch(35% 0.02 55);
    --blue: oklch(68% 0.15 258);
    --sleep: #7d9dd0;
    --sleep-dark: #9db4e8;
    --nap: #6f8fc4;
    --ok: oklch(70% 0.13 155);
    --warn: oklch(75% 0.13 75);
    --bad: oklch(68% 0.19 27);
    --alarm: oklch(70% 0.17 32);
    --accent-2: #d8794f;  /* терракота светлее: оригинал тонет в тёмном фоне */
    /* тёплого света меньше, но он остаётся — иначе тёмная тема теряет
       сигнатуру направления */
    --glow-saffron: rgb(223 160 50 / .07);
    --glow-terra: rgb(188 79 39 / .05);
    /* тень на тёмном — чёрная и плотнее: коричневую здесь не видно вовсе */
    --shadow-card:
      0 1px 2px rgb(0 0 0 / .35),
      0 5px 14px rgb(0 0 0 / .28),
      0 26px 48px -22px rgb(0 0 0 / .55);
    --shadow-raised:
      0 2px 3px rgb(0 0 0 / .35),
      0 10px 22px rgb(0 0 0 / .32),
      0 34px 60px -22px rgb(0 0 0 / .6);
    /* стопы колец светлее: тёмные концы градиентов тонули в фоне */
    --ring-ok-1: #9cbf83;     --ring-ok-2: #6d9a5c;
    --ring-warn-1: #eecd72;   --ring-warn-2: #d3a139;
    --ring-bad-1: #e8917a;    --ring-bad-2: #c9564a;
    --ring-sleep-1: #a6b8de;  --ring-sleep-2: #6d85b5;
    --ring-strain-1: #e7ae4d; --ring-strain-2: #d06f42;
  }
}

/* ==================== КАНОН РАСКЛАДКИ — един для ВСЕХ страниц ====================
   1. Один контейнер: main.content, max-width 1120px, по центру. Никаких своих
      ширин у отдельных вкладок.
   2. html { overflow-y: scroll } — место под скроллбар занято всегда, иначе
      короткие страницы шире длинных на ширину полосы и контент дёргается
      вбок при переходе между вкладками и при смене фильтра.
      ⚠️ Одного scrollbar-gutter: stable мало — он не действует при
      overflow: visible, а это значение корня по умолчанию.
   3. Карточки .card на всю ширину контейнера, паддинг 1.1rem; в карточках
      графиков тексты стоят на той же линии (~19px от края), что и везде.
   4. Прокручивается страница, а не блоки внутри (кроме горизонтальной
      у широких таблиц). Второй вертикальный скроллбар — запрещён.
   5. Кнопки: primary — оранжевая пилюля .btn; secondary — .btn-quiet/.btn-ghost;
      опасные — .btn-danger с корзинкой и data-confirm; третичная текстовая —
      .btn-link (только «Выйти» в шапке и крестик модалки). Внутри .upload и
      .actions кнопка опознаётся по контейнеру и класса не требует.
   6. Шапка таблицы — строка текста над данными: цвет --muted, вес 600, снизу
      линия. Без фоновой плашки: внутри скруглённой рамки она читается врезкой.
   ================================================================================= */

* { box-sizing: border-box; }

/* ⚠️ Одного scrollbar-gutter мало: он не действует при overflow: visible,
   то есть на корне по умолчанию — и место под скроллбар не резервировалось.
   Из-за этого страница дёргалась вбок на 15px, когда лента становилась
   короче окна (переключение фильтра, короткая вкладка). overflow-y: scroll
   держит полосу всегда; на телефонах скроллбар накладной и ширины не занимает. */
html { overflow-y: scroll; scrollbar-gutter: stable; }

body {
  margin: 0;
  font-family: var(--font-text);
  font-size: 16px;
  line-height: 1.5;
  color: var(--text);
  /* сигнатура «утренний свет»: два тёплых радиальных пятна у верхней кромки —
     шафран слева, терракота справа. Деликатно: свечение видно на подложке,
     но карточки не подкрашивает. */
  background:
    radial-gradient(1000px 460px at 14% -8%, var(--glow-saffron), transparent 62%),
    radial-gradient(760px 400px at 96% 2%, var(--glow-terra), transparent 58%),
    var(--bg);
  -webkit-text-size-adjust: 100%;
}

/* ссылки в тексте — оранжевые без подчёркивания, как у оригинала */
.content a { color: var(--accent); text-decoration: none; }
/* hover — терракота (--accent-2): микро-акцент второго плана из референса */
.content a:hover { color: var(--accent-2); text-decoration: underline; }
/* ⚠️ Ссылка-кнопка — не ссылка: правило выше (специфичность 0,1,1) перебивало
   цвет у .btn-google и .btn-soft (0,1,0), и подпись на них выходила оранжевой
   вместо чернильной. Свой цвет и отсутствие подчёркивания им возвращаем здесь,
   рядом с причиной; любая новая кнопка-ссылка добавляется в этот список. */
.content a.btn-google, .content a.btn-soft, .content a.btn { color: var(--text); }
.content a.btn { color: #fff; }
.content a.btn-google:hover, .content a.btn-soft:hover,
.content a.btn:hover { text-decoration: none; }
/* чипы периодов и фильтров — не текстовые ссылки, оранжевый им не положен */
.content .periods a { color: var(--muted); }
.content .periods a:hover { text-decoration: none; }

body.centered {
  display: grid;
  /* ⚠️ safe: форма регистрации выше экрана, и при обычном center её верх
     уезжает за границу окна — до логотипа и заголовка не доскроллить.
     safe при переполнении прижимает к началу вместо центра. */
  place-items: safe center;
  min-height: 100dvh;
  padding: 1rem;
}
/* ⚠️ Вертикальный ритм этих страниц задаёт центрирование, а не отступы обёртки.
   У .content они несимметричны (20px сверху, 48 снизу) — из-за этого карточка
   стояла на 14px выше середины экрана. */
body.centered .content { padding: 0; }

/* --- шапка --- */
.topbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: .75rem 1rem;
  min-height: var(--topbar-h);
  box-sizing: border-box;
  /* полупрозрачная шапка с блюром (референс): при прокрутке под ней
     просвечивает контент — ещё один слой глубины. Фолбэк background
     непрозрачный: без поддержки backdrop-filter текст под шапкой
     не должен просвечивать кашей. */
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 10;
}
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .topbar {
    background: color-mix(in srgb, var(--bg) 82%, transparent);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    border-bottom-color: color-mix(in srgb, var(--border) 70%, transparent);
  }
}

.brand {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-weight: 650;
  letter-spacing: -.01em;
  text-decoration: none;
  color: var(--text);
}

/* высота задана явно, ширина auto — иначе атрибуты width/height в разметке
   растянут картинку до 160px и разорвут шапку */
.brand img {
  height: 26px;
  width: auto;
  flex: none;
}

.nav {
  display: flex;
  gap: .25rem;
  flex: 1;
  overflow-x: auto;
  scrollbar-width: none;
}
.nav::-webkit-scrollbar { display: none; }

.nav a {
  padding: .35rem .7rem;
  border-radius: 999px;
  text-decoration: none;
  color: var(--muted);
  white-space: nowrap;
}
.nav a:hover { color: var(--text); }
/* активный пункт — оранжевая пилюля с белым текстом, как кнопки у Сільпо */
.nav a.is-active { background: var(--accent); color: #fff; font-weight: 600; }

.logout { margin: 0; }

/* --- контент --- */
.content {
  /* 1120: гарнитура шире системной, и 8 колонок дат в 960 уже не влезали —
     таблица анализов вылезала за край карточки (выяснено ещё на Golos;
     Onest по ширине сопоставим, ширина остаётся) */
  max-width: 1120px;
  margin: 0 auto;
  padding: 1.25rem 1rem 3rem;
}

/* заголовки — Piazzolla (канон типографики в шапке файла). Вес умеренный:
   переменной антикве 750 даёт «жирную газету», а не премиум */
h1 { font-family: var(--font-display); font-size: 2rem; font-weight: 640; margin: .5rem 0 1rem; letter-spacing: -.015em; }
h2 { font-family: var(--font-display); font-size: 1.2rem; font-weight: 630; margin: 0 0 .75rem; letter-spacing: -.01em; }
/* h2-разделители уровня страницы («Лекарства», «Добавки», «Хронология»):
   глобальный h2 спроектирован для шапки карточки, а между карточками он
   отличается от h3 внутри них на полтора пикселя и групп не строит */
.content > h2, .page-head h2 { font-size: 1.35rem; letter-spacing: -.015em; }
.content > h2 { margin: 1.75rem 0 .75rem; }

.card {
  background: var(--surface);
  /* рамка ослаблена до полутона: по канону глубины контур карточки теперь
     держит тень, а жёсткая линия рядом с ней читалась бы двойной обводкой */
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 1.1rem;
  margin-bottom: 1rem;
}

.muted { color: var(--muted); }
.error {
  color: var(--bad);
  background: color-mix(in srgb, var(--bad) 10%, transparent);
  border-radius: 8px;
  padding: .5rem .7rem;
  margin: 0;
  font-size: .9rem;
}

/* --- формы --- */
/* ⚠️ margin-inline: auto, а не margin: 0. Обёртка main.content шире карточки
   ровно настолько, насколько её растянул самый широкий блок внутри, и карточка
   с нулевым отступом прижималась к её левому краю — на экране это читалось
   как «блок не по центру». */
.login-card { width: min(380px, 100%); margin: 0 auto; }
.login-logo { display: block; height: 64px; width: auto; margin: 0 auto .6rem; }
.login-card h1 { margin: 0; text-align: center; }
/* отступ под заголовком обычно даёт подзаголовок; там, где его нет,
   заголовок отбивается сам — иначе он прилипает к первому полю */
.login-card h1:not(:has(+ p.muted)) { margin-bottom: 1.25rem; }
.login-card p.muted { margin: .15rem 0 1.25rem; font-size: .9rem; text-align: center; }
/* пояснения внутри формы регистрации живут в потоке, а не под заголовком */
.login-card p.muted.small { margin: .15rem 0 .9rem; }
/* подпись под кнопкой — своей строкой, ссылка кнопкой: «Зарегистрироваться»
   текстом посреди фразы теряется, а это второй по важности путь со страницы */
.login-card .login-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  flex-wrap: wrap;
  margin: 1.1rem 0 0;
  text-align: center;
}

label { display: block; margin-bottom: .85rem; font-size: .85rem; color: var(--muted); }

/* ⚠️ Правило от обратного, а не перечисление типов. Список из семи типов уже
   один раз промахнулся: `input[type="email"]` в него не попал, и поле почты
   в кабинете рисовалось системным прямоугольником. Новый тип поля не должен
   выпадать из оформления только потому, что его забыли дописать. Исключены
   те, у кого своя внешность: галочки, файл, кнопки и невидимые поля. */
input:not([type="checkbox"], [type="radio"], [type="file"], [type="submit"],
          [type="button"], [type="image"], [type="reset"], [type="hidden"],
          [type="range"], [type="color"]),
select, textarea {
  display: block;
  width: 100%;
  margin-top: .3rem;
  padding: .6rem .7rem;
  font: inherit;
  font-size: 16px; /* меньше 16px — iOS зумит поле при фокусе */
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent);
  outline-offset: 1px;
  border-color: var(--accent);
}
/* тот же фирменный фокус у кнопок, чипов и плиток: без своего правила
   клавиатурный обход рисовал системный чёрно-белый обвод, чужой в тёплой
   палитре. Сегменты и галочки со своими обводами специфичнее — не задвоится. */
:is(a, button, summary, [role="button"]):focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent);
  outline-offset: 2px;
}

/* Primary-пилюля. Селекторов три, потому что кнопки внутри .upload и .actions
   класса не имеют: они опознаются по контейнеру. Определение — одно, иначе
   одна и та же кнопка на трёх страницах выходила трёх разных размеров. */
.btn,
.upload button,
.actions button[type="submit"] {
  padding: .55rem 1.4rem;
  font: inherit;
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  /* hover primary плавный, как у вторичных поверхностей рядом (.dropzone, .wd):
     главная кнопка не должна ощущаться дешевле второстепенных */
  transition: filter var(--t-fast) ease, transform var(--t-fast) ease;
}
.btn:hover,
.upload button:hover,
.actions button[type="submit"]:hover { filter: brightness(1.06); }
/* отклик нажатия: формы уходят на сервер и ждут ответа — кнопка обязана
   подтвердить, что нажатие случилось. Масштаб в пределах .97: press-feedback,
   а не спор с плоским каноном. */
.btn:active,
.upload button:active,
.actions button[type="submit"]:active,
.btn-soft:active,
.btn-quiet:active,
.btn-ghost:active { transform: scale(.97); }

/* Журнал ошибок в админке: строка-заголовок раскрывается в трассировку */
.errors { list-style: none; margin: .6rem 0 0; padding: 0; display: grid; gap: .35rem; }
.error-item { border: 1px solid var(--border); border-radius: 10px; padding: .5rem .7rem; }
.error-item summary { cursor: pointer; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.error-item code { font-size: .8rem; }
/* ⚠️ Трассировка длинная и в ней строки любой ширины: прокручиваем внутри,
   иначе она растянет карточку и уведёт страницу вбок */
.trace {
  max-height: 22rem;
  overflow: auto;
  margin: .5rem 0 0;
  padding: .6rem .7rem;
  border-radius: 8px;
  background: var(--soft);
  font-size: .74rem;
  line-height: 1.45;
  white-space: pre;
}

/* Зона перетаскивания файла. Строится скриптом вокруг обычного input[type=file]:
   без JS остаётся системное поле выбора, и оно работает. */
.dropzone {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  /* ростом с кнопку и во всю ширину: зона — это поле формы, а не картинка,
     и половину карточки под неё отдавать незачем */
  min-height: 44px;
  width: 100%;
  margin: 0 0 .75rem;
  padding: .6rem 1.25rem;
  border: 1.5px dashed var(--border);
  border-radius: var(--radius);
  background: var(--soft);
  color: var(--muted);
  font-size: .85rem;
  text-align: center;
  cursor: pointer;
  transition: border-color var(--t-fast) ease, background var(--t-fast) ease;
}
.dropzone:hover { border-color: var(--accent); }
/* форма загрузки — строкой: зона тянется, кнопка «Разобрать» справа от неё;
   на узком экране кнопка переносится вниз сама */
form.upload { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
form.upload .dropzone { flex: 1 1 260px; }
form.upload button { flex: none; margin-top: 0; }
/* файл, брошенный в любое место карточки, попадает в её форму: целиться
   в пунктирный прямоугольник — лишняя точность там, где она не нужна */
.card.is-drop-target { outline: 2px dashed var(--accent); outline-offset: 6px; }
/* приёмник в поп-апе — весь поп-ап; рамка внутрь, за краем окна её не видно */
dialog.modal.is-drop-target { outline: 2px dashed var(--accent); outline-offset: -10px; }
/* ⚠️ Прячем нативное поле, но не через display:none — так оно теряет фокус
   с клавиатуры. Обёртка <label> открывает выбор файла кликом сама. */
.dropzone input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.dropzone:focus-within { border-color: var(--accent); outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 2px; }
/* файл над зоной — заливка фирменным, чтобы «отпускай здесь» читалось сразу */
.dropzone.is-over {
  border-color: var(--accent);
  border-style: solid;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--text);
}
.dropzone.is-filled { border-style: solid; border-color: var(--accent); color: var(--text); font-weight: 600; }
.dropzone.is-wrong { border-color: var(--bad); color: var(--bad); }

/* .btn растягивается: это главная кнопка формы (логин, модалка) */
.btn { width: 100%; margin-top: .5rem; }

/* Приглушённая кнопка тех же размеров, что .btn: уход назад, отмена.
   ⚠️ Заливка тёплая (--soft), а не серая: серый в этой палитре читается
   грязным пятном. Взято с вторичной кнопки референса ai-factory.silpo.ua —
   светлый тёплый фон, чернильный текст, волосяная рамка. */
.btn-soft {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  margin-top: .5rem;
  /* минус пиксель по вертикали на рамку — рост тот же, что у .btn */
  padding: calc(.55rem - 1px) 1.4rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--soft);
  color: var(--text);
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--t-fast) ease, border-color var(--t-fast) ease, transform var(--t-fast) ease;
}
.btn-soft:hover { background: var(--soft-dark); border-color: var(--muted); }
/* та же пилюля, но по содержимому: кнопка формы внутри карточки. Во всю ширину
   карточки она читается как призыв к действию всей страницы, а это лишь
   «сохранить вот эти поля» */
.btn-inline { width: auto; margin-top: 0; }

/* третичная — текстом, без пилюли: «Выйти» в шапке, крестик модалки */
.btn-link {
  font: inherit;
  color: var(--muted);
  background: none;
  border: 0;
  padding: .35rem .5rem;
  cursor: pointer;
}
.btn-link:hover { color: var(--text); }

/* код-блок как у Сільпо: чернильный на кремовом */
pre {
  background: var(--text);
  color: var(--bg);
  border: 0;
  border-radius: 16px;
  padding: .9rem 1.1rem;
  overflow-x: auto;
  font-size: .85rem;
}

/* ================== статусы значений ================== */
.is-ok    { --tone: var(--ok); }
.is-warn  { --tone: var(--warn); }
/* is-bad и is-high — одно и то же «плохо»: первое пришло из зон Whoop, второе из
   анализов. Без общего правила чип `is-bad` оставался бесцветным. */
.is-bad   { --tone: var(--bad); }
.is-high  { --tone: var(--bad); }
.is-low   { --tone: var(--accent); }
.is-none  { --tone: var(--muted); }
.is-invalid { --tone: var(--muted); }

/* ================== отчёт по сдаче: тона секций ==================
   Имена секций (attention/border/changed/unrated/ok) — своя шкала, отдельная
   от статусов значения выше: секция «Изменилось» не про плохо/хорошо, а
   «В норме»/«Нельзя оценить» не совпадают по смыслу с is-none/is-ok. Поэтому
   не переиспользуем is-* классы, а заводим свой набор переменных. */
:root {
  --tone-attention: var(--bad);
  --tone-border: var(--warn);
  --tone-changed: var(--accent);
  --tone-unrated: var(--muted);
  --tone-ok: var(--ok);
}

.lead { font-size: 1.05rem; font-weight: 600; margin: 0 0 .75rem; }

/* заметка про протухание: то же цветовое устройство, что у .banner, но
   строкой внутри карточки, а не отдельной секцией */
.note {
  border-left: 3px solid var(--tone, var(--border));
  background: color-mix(in srgb, var(--tone, transparent) 8%, transparent);
  border-radius: 8px;
  padding: .5rem .7rem;
  margin: 0 0 .75rem;
  font-size: .9rem;
}

.summary-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }
.summary-list li {
  border-left: 3px solid var(--tone, var(--border));
  background: color-mix(in srgb, var(--tone, transparent) 6%, transparent);
  border-radius: 8px;
  padding: .45rem .65rem;
}
.summary-list a { font-weight: 600; }
.summary-list span { display: block; color: var(--muted); font-size: .9rem; }

/* «В норме» и «Нельзя оценить» свёрнуты (details): заголовок — тот же вес,
   что у h2 везде на сайте, плюс курсор-подсказка, что по нему можно кликнуть */
[data-bucket] summary { cursor: pointer; font-size: 1.1rem; font-weight: 700; margin-bottom: .75rem; }

td.v, .history td.v {
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
  /* при фиксированной раскладке длинный текст («не виявлено») не должен
     наползать на соседнюю колонку; полный текст и так есть в title */
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--tone, var(--text));
}
/* Отклонения — цветом и весом текста, без заливки ячейки: крупные цветные
   плашки в строках выглядели грубо (канон дозировки цвета, п.6). */
td.v.is-ok { color: var(--text); }
td.v.is-none { color: var(--text); }
td.v.is-invalid { text-decoration: line-through; opacity: .75; }
td.v.is-warn, td.v.is-high, td.v.is-low { font-weight: 600; }

/* ================== таблица анализов ================== */
.table-hint { font-size: .85rem; margin-top: -.5rem; }

/* Прокручивается страница, а не блок внутри неё: второй скроллбар — это ловушка,
   особенно на телефоне. Поэтому по умолчанию контейнер ничего не прокручивает,
   и липкая шапка считает «верхом» окно, цепляясь под панель навигации.
   Боковая прокрутка включается ниже — только там, где таблица не помещается. */
.table-wrap {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  /* сдач больше, чем влезает в 1120px: контейнер прокручивается вбок на любом
     экране. Размен прежний, как на телефоне: липкость шапки дат к окну
     теряется, зато обе крайние колонки липкие и видны всегда. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* таблицу можно схватить мышью и потащить вбок, как пальцем на телефоне;
   во время драга гасим выделение, иначе тянется синяя простыня текста */
.table-wrap.is-dragging { cursor: grabbing; user-select: none; }

/* «Ничего не найдено» под таблицей: без него пустой результат поиска
   читается как поломка страницы */
.labs-no-match { margin: .6rem 0 0; }

/* сортируемые заголовки списков: курсор и стрелка направления у активного */
th[data-sort] { cursor: pointer; user-select: none; }
th[data-sort]:hover { color: var(--text); }
th[data-sort].is-active::after { content: ' ↓'; color: var(--accent); }
th[data-sort].is-active[data-dir="asc"]::after { content: ' ↑'; }

table.labs {
  border-collapse: separate;
  border-spacing: 0;
  /* Ширина — от ЧИСЛА сдач (--cols ставит шаблон), а не от контента и не от
     контейнера: контент раздувал колонку под длинное качественное значение,
     контейнер сжимал даты до наложения. Фиксированная раскладка делит даты
     ровно по --col-date, длинный текст переносится внутри своей колонки.
     min-width растягивает таблицу во весь контейнер при паре сдач —
     лишнее fixed раздаёт датам поровну, ровность не страдает. */
  --col-metric: 240px;
  --col-date: 104px;
  table-layout: fixed;
  width: calc(var(--col-metric) + var(--last-col-w, 132px) + var(--cols, 8) * var(--col-date));
  min-width: 100%;
  font-size: .875rem;
}

table.labs th, table.labs td { padding: .55rem .7rem; border-bottom: 1px solid var(--border); }

table.labs thead th {
  /* ⚠️ Вертикальной липкости у шапки больше нет: контейнер прокручивается
     вбок, значит он и есть scrollport, и sticky top: 64px означал бы
     «всегда стой в 64px от верха таблицы» — шапка сползала вниз, оставляя
     дыру, под которой просвечивали строки. Липкость к окну внутри
     прокручиваемого контейнера невозможна — это цена бокового скролла. */
  /* фон непрозрачный: шапка липкая, под ней проезжают строки. Оттенка accent,
     как у table.history, здесь нет намеренно — плашка внутри рамки читается
     врезкой (решение пользователя 06.08), а цвет и вес взяты те же */
  background: var(--surface);
  color: var(--muted);
  text-align: right;
  font-weight: 600;
  font-size: .8rem;
  white-space: nowrap;
}
table.labs thead th.latest { color: var(--accent); }
/* одинаковый минимум держит колонки дат ровными при авто-раскладке:
   реальные подписи («Ноя 2025» + число под ней) почти одной ширины */

.sticky-col {
  position: sticky;
  left: 0;
  z-index: 2;
  /* при table-layout: fixed колонка без явной ширины делила бы остаток с датами */
  width: var(--col-metric, 240px);
  background: var(--surface);
  box-shadow: 8px 0 10px -8px color-mix(in srgb, var(--text) 25%, transparent);
  text-align: left !important;
  font-weight: 450;
  width: 240px; /* table-layout: fixed читает width, а не min-width */
  box-shadow: 1px 0 0 var(--border);
}
table.labs thead .sticky-col { z-index: 4; }

/* Подсказка живёт внутри липкой ячейки, а у той свой stacking-контекст:
   ячейка следующей строки (такой же sticky, позже в DOM) рисовалась ПОВЕРХ
   подсказки, и текст обрезало. Ячейку с открытой подсказкой поднимаем. */
.sticky-col:hover,
.sticky-col:focus-within,
.sticky-col:has(.info.active) { z-index: 5; }
.sticky-col a { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--border); }
.sticky-col a:hover { color: var(--accent); border-bottom-color: currentColor; }

.date-sub { display: block; font-weight: 400; font-size: .7rem; color: var(--muted); }
.th-note { font-weight: 400; color: var(--muted); }

/* норма живёт под названием показателя, отдельной колонки больше нет */
.sticky-col .ref {
  display: block;
  margin-top: .1rem;
  font-size: .75rem;
  font-weight: 400;
  color: var(--muted);
}

/* последняя сдача — единственная колонка с фоном, чтобы взгляд шёл к свежему */
td.latest-col { box-shadow: inset 0 0 0 999px color-mix(in srgb, var(--accent) 5%, transparent); }

.section-row td {
  background: var(--bg);
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--muted);
  padding: .35rem .6rem;
}
/* липким делаем текст: у ячейки с colspan sticky обрезает заголовок при прокрутке вбок */
.section-row td span {
  position: sticky;
  left: .6rem;
  display: inline-block;
}

/* всплывающее пояснение показателя: фирменный кружок-«i» */
.info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  margin-left: .35em;
  border: 0;
  border-radius: 50%;
  font-size: .68rem;
  font-weight: 700;
  font-style: normal;
  line-height: 1;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  cursor: help;
  position: relative;
  /* фикс. размер + лёгкий подъём: кружок стоит по центру строки,
     а не пляшет от базовой линии соседней ссылки */
  vertical-align: middle;
  top: -1px;
  flex: none;
}
/* тач-зона ~38px при видимых 16: кружок стоит вплотную к ссылке-названию,
   и попадание пальцем ровно в 16px требовало снайперской точности */
.info::before { content: ""; position: absolute; inset: -11px; }
.info:hover, .info.active { background: var(--accent); color: #fff; }
.info .tip {
  /* тот же фейд, что у [data-hint]: три подсказки сервиса — один язык.
     display остаётся (невидимая коробка не должна считаться прокруткой),
     allow-discrete держит его до конца фейда; браузер без поддержки
     отбрасывает transition целиком и показывает мгновенно, как раньше. */
  display: none;
  opacity: 0;
  transition: opacity var(--t-fast) ease, display var(--t-fast) allow-discrete;
  position: absolute;
  left: 50%;
  top: calc(100% + .4rem);
  transform: translateX(-50%);
  width: min(260px, 60vw);
  padding: .5rem .6rem;
  font-size: .78rem;
  line-height: 1.35;
  text-align: left;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .18);
  z-index: 20;
}
.info:hover .tip, .info:focus .tip, .info.active .tip { display: block; opacity: 1; }
@starting-style {
  .info:hover .tip, .info:focus .tip, .info.active .tip { opacity: 0; }
}

/* ================== общая всплывающая подсказка ================== */
/* Один вид на весь сервис — тот же, что у пояснений показателей. Нативный `title`
   выглядит системной плашкой, появляется через секунду и не поддаётся стилю;
   `[data-hint]` рисуется на CSS, без единой строчки JS. */
[data-hint] { position: relative; }
[data-hint]::after {
  content: attr(data-hint);
  position: absolute;
  left: 50%;
  /* ⚠️ снизу, а не сверху: таблицы лежат в контейнере с горизонтальной прокруткой,
     а он обрезает и по вертикали — подсказка над верхними строками пропадала бы */
  top: calc(100% + .4rem);
  transform: translateX(-50%);
  z-index: 5;
  width: max-content;
  max-width: min(260px, 60vw);
  padding: .4rem .55rem;
  font-size: .78rem;
  font-weight: 400;
  line-height: 1.35;
  text-align: left;
  white-space: normal;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 6px 18px rgb(20 15 10 / .13);
  /* ⚠️ Спрятанная подсказка убирается из потока совсем (display), а не только
     с глаз (visibility): невидимая коробка 260px всё равно считается прокруткой
     контейнера — в поп-апе тренировки от неё внизу появилась горизонтальная
     полоса. allow-discrete со @starting-style сохраняет плавное появление. */
  display: none;
  opacity: 0;
  transition: opacity var(--t-fast) ease, display var(--t-fast) allow-discrete;
}
[data-hint]:hover::after, [data-hint]:focus-visible::after { display: block; opacity: 1; }
@starting-style {
  [data-hint]:hover::after, [data-hint]:focus-visible::after { opacity: 0; }
}
/* у самого края таблицы подсказка прижимается, иначе уезжает за экран */
[data-hint].hint-start::after { left: 0; transform: none; }
[data-hint].hint-end::after { left: auto; right: 0; transform: none; }

.legend { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .75rem; font-size: .78rem; }
/* ⚠️ фолбэк обязателен: чип без is-класса (разделы сдач, слоты приёма) при
   голом var(--tone) терял и цвет, и подложку — висели слова вместо пилюль */
.chip {
  padding: .12rem .5rem;
  border-radius: 999px;
  color: var(--tone, var(--muted));
  background: color-mix(in srgb, var(--tone, var(--muted)) 14%, transparent);
}

/* ================== страница показателя ================== */
.crumbs { margin: 0; font-size: .85rem; }
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--accent); }

.chart-card { padding: .75rem .5rem .25rem; }
/* паддинг карточки ужат ради ширины графика — но тексты должны стоять
   на той же линии, что и в обычных карточках (~1.1rem от края) */
/* ⚠️ Новые блоки внутри карточки графика тоже отступают: у карточки паддинг ужат
   ради ширины графика, и без этого легенда-распределение стояла на .5rem, а текст
   рядом — на 1.1rem. Каждый новый элемент карточки добавлять сюда. */
.chart-card h2, .chart-card h3, .chart-card p,
.chart-card .dist, .chart-card .bucket-row, .chart-card .chart-toggle {
  margin-left: .6rem;
  margin-right: .6rem;
}
.chart-card h2 { margin-top: .35rem; }
.chart { width: 100%; height: auto; display: block; }
.chart-band { fill: color-mix(in srgb, var(--ok) 12%, transparent); }
.chart-grid { stroke: var(--border); stroke-width: 1; }
.chart-axis { fill: var(--muted); font-size: 11px; }
.chart-value { fill: var(--text); font-size: 11px; font-variant-numeric: tabular-nums; }
.chart-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; }
.chart-dot { fill: var(--tone, var(--accent)); }
.chart-dot.is-invalid { fill: var(--surface); stroke: var(--muted); stroke-dasharray: 2 2; }

table.history { width: 100%; border-collapse: collapse; font-size: .875rem; }
/* горизонтальный отступ у КАЖДОЙ ячейки: без него колонки слипаются,
   как только контенту становится тесно */
table.history th {
  text-align: left;
  font-weight: 550;
  font-size: .78rem;
  color: var(--muted);
  padding: .35rem 1rem .45rem 0;
}
table.history td { padding: .55rem 1rem .55rem 0; border-top: 1px solid var(--border); }
table.history th:last-child, table.history td:last-child { padding-right: .6rem; }
/* первая колонка не липнет к краю карточки */
table.history th:first-child, table.history td:first-child { padding-left: .5rem; }
table.history td.v { border-radius: 6px; }

/* ================== дашборд ================== */
.tasks { list-style: none; margin: 0; padding: 0; }
.tasks li { padding: .35rem 0 .35rem 1.4rem; position: relative; border-top: 1px solid var(--border); }
.tasks li:first-child { border-top: 0; }
.tasks li::before { content: "•"; position: absolute; left: .3rem; color: var(--muted); }
.tasks li.is-urgent { color: var(--bad); }
.tasks li.is-urgent::before { content: "!"; color: var(--bad); font-weight: 700; }

.tiles { display: grid; gap: .6rem; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
.tile {
  display: block;
  padding: .6rem .7rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--tone, var(--border));
  border-radius: 10px;
  text-decoration: none;
  color: var(--text);
  background: color-mix(in srgb, var(--tone, transparent) 6%, transparent);
  /* «рамка теплеет к accent» — один жест на все карточки: без transition
     плитка щёлкала рядом с плавными .wd в том же экране */
  transition: border-color var(--t-fast) ease;
}
.tile:hover { border-color: var(--tone, var(--accent)); }
.tile-name { display: block; font-size: .75rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
/* число плитки — антиквой и крупнее: «банковская» подача цифр из референса.
   tabular остаётся: плитки стоят сеткой, разнобой ширин цифр был бы заметен */
.tile-value {
  display: block;
  font-family: var(--font-display);
  font-size: 1.65rem;
  font-weight: 640;
  line-height: 1.2;
  letter-spacing: -.01em;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--tone, var(--text));
}
.tile-sub { display: block; font-size: .72rem; color: var(--muted); }

.bar { height: 8px; border-radius: 999px; background: var(--bg); border: 1px solid var(--border); overflow: hidden; }
.bar span { display: block; height: 100%; background: var(--accent); }
.small { font-size: .8rem; }

/* ================== периоды и импорт ================== */
.periods { display: flex; flex-wrap: wrap; gap: .4rem; margin: -.25rem 0 1rem; }
.periods a {
  padding: .25rem .7rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: .82rem;
  text-decoration: none;
  color: var(--muted);
}
.periods a:hover { color: var(--text); border-color: var(--muted); }
/* активный чип — жёлтая подложка с чернильным текстом, как плашки Сільпо */
.periods a.is-active {
  color: var(--text);
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 25%, transparent);
  font-weight: 600;
}

.upload { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; margin-top: .75rem; }
.upload input[type="file"] { flex: 1 1 260px; font-size: .85rem; color: var(--muted); }

/* ================== планшет и пол-окна ================== */
/* меню на своей строке: полное меню + «Кабинет» + «Выйти» укладываются в ряд
   только к ~1080px, а уже хвост («Импорт», «Админка») уезжал в боковую
   прокрутку со спрятанным скроллбаром — и о нём было не узнать вовсе */
@media (max-width: 1080px) {
  .topbar { flex-wrap: wrap; gap: .5rem .75rem; }
  .nav { order: 3; flex-basis: 100%; flex-wrap: wrap; overflow-x: visible; }
  /* шапка выросла второй строкой меню — от переменной считаются и её
     min-height, и scroll-margin якорей разделов анализов: без переопределения
     тап по чипу раздела прятал заголовок под липкой панелью */
  :root { --topbar-h: 104px; }
}

/* ================== телефон ================== */
@media (max-width: 640px) {

  /* два липких края на 375px оставляют данным щель: сужаем оба и даты */
  table.labs { --col-metric: 120px; --col-date: 88px; --last-col-w: 100px; font-size: .8rem; }
  table.labs th, table.labs td { padding: .4rem .45rem; }
  .sticky-col .ref { font-size: .68rem; }
}

/* Боковая прокрутка теперь на любой ширине — задана у .table-wrap выше:
   сдач стало больше, чем влезает и в 1120px. */

/* ================== сверка черновика ================== */
.fields { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.fields label { display: flex; flex-direction: column; gap: .25rem; font-size: .82rem; color: var(--muted); }
.fields label.wide { grid-column: 1 / -1; }
.fields input,
.fields select,
.fields textarea {
  margin-top: 0; /* глобальный стиль даёт .3rem — в сетке отступ делает gap */
  padding: .4rem .55rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
}

/* сегмент под курсором чуть светлеет — видно, о какой стадии подсказка */
.bar-seg { transition: filter var(--t-fast) ease; }
[data-bars] .bar-seg:hover { filter: brightness(1.12) saturate(1.15); }

/* ================== слои и зоны whoop-графиков ================== */
.chart-line.is-soft { stroke: var(--muted); stroke-width: 1.5; opacity: .55; }
/* стадии сна: от глубокого к бодрствованию */
/* ⚠️ fill красит SVG-сегменты столбиков, а те же классы висят и на HTML-спанах
   полосы стадий (макрос stage_bar). Спану fill безразличен — без background полоса
   на странице дня рисовалась пустой серой пилюлей всё время, что она существует. */
.seg-deep { fill: #3555a8; }
.seg-rem { fill: #7c6cf6; }
.seg-light { fill: #9db4e8; }
.seg-awake { fill: color-mix(in srgb, var(--muted) 45%, transparent); }
.zones .seg-deep { background: #3555a8; }
.zones .seg-rem { background: #7c6cf6; }
.zones .seg-light { background: #9db4e8; }
.zones .seg-awake { background: color-mix(in srgb, var(--muted) 45%, transparent); }
.chip.seg-deep { background: #3555a8; color: #fff; border-color: transparent; }
.chip.seg-rem { background: #7c6cf6; color: #fff; border-color: transparent; }
.chip.seg-light { background: #9db4e8; color: #1c2733; border-color: transparent; }
.chip.seg-awake { background: color-mix(in srgb, var(--muted) 40%, transparent); border-color: transparent; }

/* леденцы: ножка от нуля + кружок сверху, вместо гусеницы из точек на полугоде */
.pop-stem { stroke: color-mix(in srgb, var(--muted) 35%, transparent); stroke-width: 1; }
.chart-axis.is-right { fill: color-mix(in srgb, var(--muted) 80%, transparent); }

/* ================== легенда-распределение ================== */
/* Одна полоса, поделённая по долям, вместо четырёх столбиков: у столбиков было
   три уровня текста, а ведро на два дня выглядело обломком. В полосе редкое ведро —
   тонкий срез, и это честно. Подписи ушли вниз чипами, к каждому свой счётчик. */
/* полоса во всю ширину карточки: она высотой 12px, крупной подложкой не станет,
   а обрубок посреди карточки читался как недорисованный график */
.dist { margin: .1rem 0 .8rem; }
.dist-bar {
  display: flex;
  height: 12px;
  border-radius: 999px;
  overflow: hidden;
  background: color-mix(in srgb, var(--muted) 12%, transparent);
}
/* --tone приходит от is-ok/is-warn/is-bad: зоны восстановления обязаны оставаться
   красной/жёлтой/зелёной, перекрашивать их в фирменный нельзя */
.dist-bar > span {
  display: block;
  min-width: 3px;  /* редкое ведро иначе исчезает совсем */
  background: color-mix(in srgb, var(--tone, var(--accent)) 55%, transparent);
  transition: filter var(--t-fast) ease;
}
/* кусок под курсором светлеет — видно, о каком ведре подсказка */
.dist-bar > span:hover { filter: brightness(1.15) saturate(1.25); }
/* четыре ступени нагрузки — одним оттенком разной плотности, не радугой */
.dist-bar > .is-s1 { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.dist-bar > .is-s2 { background: color-mix(in srgb, var(--accent) 40%, transparent); }
.dist-bar > .is-s3 { background: color-mix(in srgb, var(--accent) 58%, transparent); }
.dist-bar > .is-s4 { background: color-mix(in srgb, var(--accent) 78%, transparent); }

.dist-legend { display: flex; flex-wrap: wrap; gap: .15rem .9rem; margin-top: .45rem; }
.dist-item {
  display: inline-flex;
  align-items: center;
  gap: .32rem;
  font-size: .72rem;
  color: var(--muted);
  white-space: nowrap;
}
.dist-item i {
  width: 9px;
  height: 9px;
  border-radius: 3px;
  flex: none;
  background: color-mix(in srgb, var(--tone, var(--accent)) 55%, transparent);
}
.dist-item.is-s1 i { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.dist-item.is-s2 i { background: color-mix(in srgb, var(--accent) 40%, transparent); }
.dist-item.is-s3 i { background: color-mix(in srgb, var(--accent) 58%, transparent); }
.dist-item.is-s4 i { background: color-mix(in srgb, var(--accent) 78%, transparent); }
.dist-item b { color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; }
/* пустое ведро остаётся в списке — провал в распределении тоже факт, — но приглушено */
.dist-item.is-empty { opacity: .45; }
.dist-item.is-empty b { color: var(--muted); font-weight: 400; }

.bucket-row { display: flex; gap: 1.6rem; flex-wrap: wrap; }
.bucket-row > div { flex: 1 1 240px; min-width: 0; }
.bucket-row p { margin: 0 0 .1rem; }
/* ⚠️ Отступ карточки графика колонка уже получила — второй раз он не нужен:
   подпись сбрасывает margin правилом выше, а полоса оставалась сдвинутой на 10px. */
.bucket-row .dist { margin-left: 0; margin-right: 0; }

/* вторая строка под кольцом сна: сколько спал из скольких было нужно */
.ring-sub { display: block; font-size: .72rem; color: var(--muted); }
.ring-header h2 { margin-top: 0; }

/* строка, чей день сейчас подсвечен на графике */
tr.is-linked > td { background: color-mix(in srgb, var(--accent) 9%, transparent); }

/* ================== лента суток «как прошёл день» ================== */
/* Плашки стоят НАД дорожкой внутри зарезервированного отступа.
   ⚠️ `top: 0`, а не `bottom: 100%`: последнее выносит плашку над контейнером,
   прямо в текст карточки. */
.ribbon { --track-top: 4.4rem; position: relative; padding-top: var(--track-top); margin: .3rem 0 0; }
/* события вплотную друг к другу — плашки разведены по двум ярусам (ярус считает
   сервер: браузер сам не знает, что подписи наедут) */
.ribbon.is-staggered { --track-top: 7.4rem; }
.ribbon.is-staggered .seg.row-0 .pin { top: 3rem; }
.ribbon-track {
  position: relative;
  height: 12px;
  border-radius: 3px;
  background: repeating-linear-gradient(90deg,
    color-mix(in srgb, var(--muted) 22%, transparent) 0 5px, transparent 5px 11px);
}
/* Отрезок и его плашка — один элемент: :hover красит оба, JS не нужен.
   У тренировки это <button> (открывает поп-ап), у сна — <div>; сбрасываем
   кнопочные умолчания, чтобы оба выглядели одинаково. */
.seg {
  position: absolute; top: 0; bottom: 0; left: var(--left); width: var(--width);
  padding: 0; border: 0; background: none; color: inherit; font: inherit;
}
button.seg { cursor: pointer; }
button.seg:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.seg .ev {
  position: absolute; top: var(--track-top); left: 0; right: 0; height: 12px; border-radius: 3px;
  background: var(--tone); transition: background var(--t-fast) ease, box-shadow var(--t-fast) ease;
}
.seg.cut-left .ev { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.seg.cut-right .ev { border-top-right-radius: 0; border-bottom-right-radius: 0; }
/* границы по времени: засечки от краёв отрезка вниз, к шкале */
.seg .ev::before, .seg .ev::after {
  content: ""; position: absolute; top: 100%; width: 1px; height: 10px;
  background: var(--tone); opacity: .5; transition: height var(--t-fast) ease, opacity var(--t-fast) ease;
}
.seg .ev::before { left: 0; }
.seg .ev::after { right: 0; }
/* у среза границы нет: это край суток, а не край события */
.seg.cut-left .ev::before, .seg.cut-right .ev::after { display: none; }

.seg .pin { position: absolute; top: 0; left: 50%; transform: translateX(-50%);
            text-align: center; white-space: nowrap; }
.seg .pin-badge {
  display: block; width: 32px; height: 32px; margin: 0 auto .25rem; border-radius: 10px;
  background: var(--tone); color: #fff; font-size: 16px; line-height: 32px;
  transition: background var(--t-fast) ease, transform var(--t-fast) ease;
}
.seg .pin-value { display: block; font-size: .82rem; font-weight: 700; color: var(--tone); }
.seg .dur, .seg .range {
  position: absolute; top: calc(var(--track-top) + 12px + 1.4rem); left: 50%; transform: translateX(-50%);
  font-size: .78rem; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--muted);
}
.seg .range { opacity: 0; color: var(--tone); font-weight: 600; }
/* у края суток подпись прижимается к краю: по центру она вылезала за экран */
.seg.at-start .pin, .seg.at-start .dur, .seg.at-start .range { left: 0; transform: none; text-align: left; }
.seg.at-end .pin, .seg.at-end .dur, .seg.at-end .range { left: auto; right: 0; transform: none; text-align: right; }
.seg:hover .dur { opacity: 0; }
.seg:hover .range { opacity: 1; }
.seg:hover .ev { background: var(--tone-dark); box-shadow: 0 0 0 2px color-mix(in srgb, var(--tone) 30%, transparent); }
.seg:hover .ev::before, .seg:hover .ev::after { height: 18px; opacity: 1; }
.seg:hover .pin-badge { background: var(--tone-dark); transform: translateY(-2px); }
.seg:hover .pin-value { color: var(--tone-dark); }
.seg.is-sleep { --tone: var(--sleep); --tone-dark: var(--sleep-dark); }
.seg.is-nap { --tone: var(--nap); --tone-dark: var(--sleep); }
.seg.is-workout { --tone: var(--accent); --tone-dark: var(--accent-dark); }

.ribbon .axis { position: relative; height: 1.5rem; margin-top: 3.6rem; }
.ribbon .axis span { position: absolute; transform: translateX(-50%); font-size: .72rem; color: var(--muted); }
.ribbon .axis i { position: absolute; top: -.5rem; width: 1px; height: .35rem;
                  background: color-mix(in srgb, var(--muted) 35%, transparent); }
.ribbon .axis .edge { transform: none; }
.ribbon .axis .edge.is-end { transform: translateX(-100%); }

/* ================== неделя вокруг ================== */
.week { display: grid; grid-template-columns: repeat(7, 1fr); gap: .45rem; }
.wd {
  display: block; min-width: 0; padding: .5rem; border: 1px solid var(--border);
  border-radius: 12px; text-align: center; text-decoration: none; color: inherit;
  background: var(--surface);
  transition: border-color var(--t-fast) ease, background var(--t-fast) ease,
              transform var(--t-fast) ease, box-shadow var(--t-fast) ease;
}
/* день недели кликабелен — лёгкая приподнятость, тише плиток (-1px, не -2) */
.wd:hover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 5%, transparent);
  transform: translateY(-1px);
  box-shadow: var(--shadow-card);
}
.wd.is-current { border-color: var(--accent); border-width: 2px; padding: calc(.5rem - 1px); }
.wd-date { display: block; font-size: .7rem; color: var(--muted); margin-bottom: .4rem; }
.wd:hover .wd-date { color: var(--accent); }
.bars { display: flex; flex-direction: column; gap: .28rem; }
/* ⚠️ .bars .bar, а не голый .bar: то же имя носит трек прогресса курса
   (рамка + фон --bg, см. выше), и два глобальных правила склеивались
   каскадом — полоски недели получали чужую рамку, трек курса терял фон */
.bars .bar { display: block; height: 8px; border: 0; border-radius: 999px; overflow: hidden;
       background: color-mix(in srgb, var(--muted) 13%, transparent); }
.bars .bar span { display: block; height: 100%; border-radius: 999px; transition: filter var(--t-fast) ease; }
.bar-sleep span { background: var(--sleep); }
.bar-work span { background: var(--accent); }
.bar-rec span { background: var(--tone); }
.wd:hover .bar span { filter: saturate(1.25) brightness(.92); }
/* ⚠️ Место под числа занято всегда: появляйся строка только при наведении,
   блок подпрыгивал бы под курсором. Прячем прозрачностью, не display. */
.wd-nums { display: block; margin-top: .45rem; font-size: .68rem; line-height: 1.25;
           font-variant-numeric: tabular-nums; opacity: 0; transition: opacity var(--t-fast) ease; }
.wd:hover .wd-nums { opacity: 1; }
.wd-nums b { font-weight: 700; }
.n-sleep { color: var(--sleep); }
.n-work { color: var(--accent); }
.n-rec { color: var(--tone); }
/* На тач-устройствах наведения не существует — иначе до чисел не добраться вовсе */
@media (hover: none) { .wd-nums { opacity: 1; } }
@media (max-width: 620px) {
  .week { gap: .25rem; }
  .wd-month { display: none; }
  .wd { padding: .4rem .2rem .35rem; }
  .wd.is-current { padding: calc(.4rem - 1px) calc(.2rem - 1px) calc(.35rem - 1px); }
  .wd-date { font-size: .6rem; }
  .wd-nums { font-size: .6rem; }
  .bars .bar { height: 6px; }
}

.legend-sleep { color: var(--sleep); font-weight: 600; }
.legend-work { color: var(--accent); font-weight: 600; }

/* ================== ряд карточек одной ширины ================== */
/* Три сводки про сон — распределение ночей, структура и время отбоя — про одно
   и то же и раньше шли в столбик, оставляя половину карточки доната пустой.
   На узком экране ряд рассыпается обратно в столбик. */
/* ⚠️ Порог 460px, а не 330: в три колонки таблица из четырёх столбцов не влезает —
   колонка «Восстановление» уезжала под обрез, а карточки выходили разной высоты.
   На 1120px это два столбца, третья карточка занимает следующий ряд целиком. */
.card-row {
  display: grid;
  /* ⚠️ min(460px, 100%), а не 460px: в гриде минимум minmax — жёсткий пол,
     и на экране 375 колонка всё равно осталась бы 460 с горизонтальной прокруткой
     всей страницы. min() снимает пол, когда места меньше самого минимума. */
  grid-template-columns: repeat(auto-fit, minmax(min(460px, 100%), 1fr));
  gap: 1.1rem;
  align-items: stretch;  /* соседи в ряду одной высоты, иначе ряд выглядит рваным */
  /* ⚠️ Тот же нижний отступ, что у одиночной карточки. Вертикальный ритм страницы
     задаёт `.card { margin-bottom: 1rem }`, а внутри ряда он снят — и ряд
     прилипал к следующему блоку. Любая обёртка над карточками обязана вернуть
     этот отступ за них. */
  margin-bottom: 1rem;
}
.card-row > .card { margin: 0; display: flex; flex-direction: column; }
/* нечётная последняя карточка занимает ряд целиком: половина ряда пустотой
   выглядит как обрыв вёрстки. Колонок максимум две (1120 < 3×460), поэтому
   nth-child(odd) надёжно означает «одна в ряду». */
.card-row > .card:last-child:nth-child(odd) { grid-column: 1 / -1; }
/* Кольцо слева, легенда справа — одной строкой и на странице дня, и в колонке
   .card-row: раньше в колонке было принудительное «кольцо над легендой», и один
   и тот же блок выглядел на двух страницах по-разному. Под кольцо легенда
   уходит только на узком экране, когда рядом ей физически негде встать. */
.card-row .donut-box { gap: 1rem; }

/* ================== хронология сдач анализов ================== */
.lab-timeline { list-style: none; margin: 0; padding: 0; }
.lab-entry { padding: .75rem 0; border-top: 1px solid var(--border); }
.lab-entry:first-child { border-top: 0; padding-top: .2rem; }
.lab-entry-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
/* действие сдачи — у правого края строки: и кнопка «Сделать отчёт», и ссылка */
.lab-entry-head .actions,
.lab-entry-head > .btn-link { margin-left: auto; }
.lab-entry-head .actions { margin-top: 0; }
/* «Сделать отчёт» повторяется в каждой сдаче без отчёта: колонка оранжевых
   primary в списке кричит. Кнопке — вид .btn-quiet; селектор с [type=submit],
   потому что контейнер .actions по умолчанию красит её в primary. */
.lab-entry-head .actions button[type="submit"] {
  padding: .32rem .8rem;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-weight: 400;
  font-size: .8rem;
}
.lab-entry-head .actions button[type="submit"]:hover {
  border-color: var(--accent);
  color: var(--accent);
  filter: none;
}
.lab-date { font-weight: 700; }
.lab-lab { color: var(--muted); }
.lab-sections { margin: .35rem 0 .2rem; }
.lab-files { display: flex; flex-wrap: wrap; gap: .5rem; margin: .35rem 0 0; }
.lab-file {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .25rem .6rem; border: 1px solid var(--border); border-radius: 999px;
  font-size: .82rem; text-decoration: none; color: var(--text);
  transition: border-color var(--t-fast) ease, color var(--t-fast) ease;
}
.lab-file:hover { border-color: var(--accent); color: var(--accent); }

/* ================== поп-ап тренировки ================== */
/* ⚠️ Никакого `position` тут быть не должно. Модальный <dialog> браузер держит
   `fixed` — поверх страницы, там же, где ты был. Любое своё значение это ломает:
   диалог становится куском документа у самого верха, и showModal() прокручивает
   страницу к нему. Кнопке-крестику `fixed` — такой же якорь, как `relative`. */
.workout-modal h3 { margin: 1.3rem 0 .6rem; font-size: .95rem; }
/* крестик поп-апа — та же круглая кнопка, что гасит уведомления: раньше это
   была голая текстовая ссылка размером с заголовок и без области нажатия */
.modal-x {
  position: absolute;
  top: .8rem;
  right: .9rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}
.modal-x:hover { color: var(--text); background: color-mix(in srgb, var(--text) 8%, transparent); }
.wk-head { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; padding-right: 2.4rem; }
.wk-head h2 { margin: 0; }
/* название чуть ниже, дата чуть выше: они стоят в колонке между иконкой и
   нагрузкой, и без этого читаются двумя одинаково важными строками */
.wk-title { margin-top: 2px; }
.wk-when { display: block; margin-top: -2px; }
.wk-icon {
  width: 42px; height: 42px; border-radius: 12px; flex: none;
  background: var(--accent); color: #fff; font-size: 21px;
  display: inline-flex; align-items: center; justify-content: center;
}
/* Нагрузка — не строчка текста, а такой же знак, как иконка активности: та же
   высота, то же скругление, только светлая плашка вместо заливки. Иначе крупное
   число висело в воздухе и спорило с названием за внимание. */
.wk-strain {
  margin-left: auto;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 74px;
  height: 42px;
  padding: 0 .7rem;
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  text-align: center;
}
.wk-strain b { display: block; font-family: var(--font-display); font-size: 1.3rem; font-weight: 640; color: var(--accent); line-height: 1.05; }
.wk-strain span { font-size: .62rem; letter-spacing: .04em; text-transform: uppercase; }
/* название тренировки в таблице — кнопка, но выглядит ссылкой */
.workout-open { padding: 0; font: inherit; text-align: left; color: var(--accent); }
.workout-open:hover { text-decoration: underline; }
/* строка «Порядок: …» — чипы и подпись на одной линии, с ровными промежутками */
.sort-row { display: flex; align-items: center; flex-wrap: wrap; gap: .4rem; }
.sort-note { color: var(--muted); }

/* ================== кольцо долей со щёлкаемой легендой ================== */
.donut-box { display: flex; align-items: center; gap: 1.6rem; flex-wrap: wrap; }
.donut { width: 168px; height: 168px; flex: none; }
.donut-track { fill: none; stroke: color-mix(in srgb, var(--muted) 16%, transparent); }
.donut-arc { fill: none; transition: opacity var(--t-fast) ease, filter var(--t-fast) ease; cursor: pointer; }
.donut-arc.is-muted { opacity: .18; }
/* сектор под курсором светлеет вместе со своей строкой легенды — до этого
   кольцо на наведение не отзывалось вовсе */
.donut-box:has(.donut-arc[data-seg="seg-deep"]:hover) .donut-row[data-seg="seg-deep"],
.donut-box:has(.donut-arc[data-seg="seg-rem"]:hover) .donut-row[data-seg="seg-rem"],
.donut-box:has(.donut-arc[data-seg="seg-light"]:hover) .donut-row[data-seg="seg-light"],
.donut-box:has(.donut-arc[data-seg="seg-awake"]:hover) .donut-row[data-seg="seg-awake"] {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}
.donut-arc:hover { filter: brightness(1.12) saturate(1.2); }
.donut-box:has(.donut-row[data-seg="seg-deep"]:hover) .donut-arc[data-seg="seg-deep"],
.donut-box:has(.donut-row[data-seg="seg-rem"]:hover) .donut-arc[data-seg="seg-rem"],
.donut-box:has(.donut-row[data-seg="seg-light"]:hover) .donut-arc[data-seg="seg-light"],
.donut-box:has(.donut-row[data-seg="seg-awake"]:hover) .donut-arc[data-seg="seg-awake"] {
  filter: brightness(1.12) saturate(1.2);
}
.donut-arc.seg-deep { stroke: #3555a8; }
.donut-arc.seg-rem { stroke: #7c6cf6; }
.donut-arc.seg-light { stroke: #9db4e8; }
.donut-arc.seg-awake { stroke: color-mix(in srgb, var(--muted) 45%, transparent); }
/* центр доната — антиквой, как остальные крупные числа */
.donut-value { fill: var(--text); font-family: var(--font-display); font-size: 22px; font-weight: 640; }
.donut-label { fill: var(--muted); font-size: 11px; }
/* ⚠️ Легенда не растягивается на всю карточку: при `flex: 1 1 220px` строка
   расползалась во всю ширину, и между «глубокий» и «1:50» зияла пустота
   в полкарточки. Ширина по содержимому — имя и числа стоят рядом. */
.donut-legend { display: flex; flex-direction: column; gap: .15rem; flex: 1 1 200px; min-width: 0; }
.donut-row {
  display: grid;
  grid-template-columns: 12px minmax(7.5rem, auto) 3.2rem 2.4rem;
  align-items: center;
  gap: .55rem;
  width: 100%;
  padding: .3rem .45rem;
  border: 0;
  border-radius: 8px;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.donut-row:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.donut-row.is-muted { opacity: .45; }
.donut-chip { width: 12px; height: 12px; border-radius: 3px; }
.donut-chip.seg-deep { background: #3555a8; }
.donut-chip.seg-rem { background: #7c6cf6; }
.donut-chip.seg-light { background: #9db4e8; }
.donut-chip.seg-awake { background: color-mix(in srgb, var(--muted) 45%, transparent); }
.donut-time { font-variant-numeric: tabular-nums; font-weight: 600; text-align: right; }
.donut-share { color: var(--muted); font-size: .8rem; font-variant-numeric: tabular-nums; text-align: right; }

/* полоска HR-зон: спокойное → предельное.
   Во всю ширину колонки — узкая полоска рядом с широкой колонкой выглядела обрубком,
   а куски зон были неразличимы. `overflow: visible`, чтобы подсказка куска не обрезалась. */
.zones {
  display: flex;
  width: 100%;
  min-width: 90px;
  height: 12px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--muted) 18%, transparent);
  vertical-align: middle;
}
.zones span {
  display: block;
  height: 100%;
  transition: filter var(--t-fast) ease;
}
.zones span:first-child { border-radius: 999px 0 0 999px; }
.zones span:last-child { border-radius: 0 999px 999px 0; }
/* кусок под курсором светлеет — видно, о какой зоне подсказка */
.zones span[data-hint]:hover { filter: brightness(1.15) saturate(1.2); }
.col-zones { min-width: 150px; }
.zones .z1 { background: #8fb7e6; }
.zones .z2 { background: #6ec6a8; }
.zones .z3 { background: #e5c463; }
.zones .z4 { background: #e59a4f; }
.zones .z5 { background: #d95d5d; }

/* переключатель линий у графиков со скользящим средним */
.chart-toggle { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .3rem; }
/* небольшие кнопки-переключатели: и у линий графика, и в заголовке карточки */
.chart-toggle .chip, .switch .chip {
  border: 1px solid var(--border);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: .76rem;
  cursor: pointer;
  text-decoration: none;
}
.chart-toggle .chip.is-active, .switch .chip.is-active { border-color: transparent; }
/* «выбран этот чип» — жёлтая подложка, как у .periods: канон палитры (шапка,
   --accent-soft). Оранжевый актив остаётся только у chart-toggle ниже — там
   цвет совпадает с цветом линии графика и потому семантический */
.switch .chip.is-active {
  color: var(--text);
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 25%, transparent);
  font-weight: 600;
}
.chart-toggle .chip[data-line="main"].is-active { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.chart-toggle .chip[data-line="soft"].is-active { color: var(--text); background: color-mix(in srgb, var(--muted) 18%, transparent); }
/* Линия гаснет фейдом, а не display:none за кадр: глазу проще связать нажатый
   чип с тем, что именно ушло с поля. visibility вместо display: невидимая
   линия не ловит курсор, а поиск ближайшей точки в charts.js всё равно идёт
   по данным JSON-острова, не по видимым узлам. */
.chart-line, .chart-dot { transition: opacity var(--t-fast) ease, visibility var(--t-fast); }
.chart-box.hide-main .chart-line:not(.is-soft) { opacity: 0; visibility: hidden; }
/* ⚠️ У обычного графика точки принадлежат основной линии, у двойного — правой.
   Поэтому гасим их по собственному классу, а не «все точки заодно». */
.chart-box.hide-main .chart-dot:not(.is-second) { opacity: 0; visibility: hidden; }
.chart-box.hide-soft .chart-line.is-soft, .chart-box.hide-soft .chart-dot.is-second { opacity: 0; visibility: hidden; }

/* ================== интерактивный слой графиков ================== */
.chart-box { position: relative; }
.chart-box .chart { touch-action: pan-y; } /* тап ведёт курсор, страница скроллится */
.chart-cursor { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 4 3; opacity: .7; }
.chart-focus { fill: var(--accent); stroke: var(--surface); stroke-width: 2 }
.chart-tip {
  position: absolute;
  top: -.35rem;
  transform: translateX(-50%);
  padding: .25rem .6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  box-shadow: var(--shadow);
  font-size: .78rem;
  white-space: nowrap;
  pointer-events: none;
  z-index: 5;
}

/* ================== whoop-блок дашборда ================== */
.rings { display: flex; gap: 1.5rem; justify-content: space-around; flex-wrap: wrap; margin: .8rem 0 1rem; }
.ring-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
  text-decoration: none;
  color: var(--text);
}
.ring { width: 108px; height: 108px; }
.ring-track { fill: none; stroke: color-mix(in srgb, var(--text) 8%, transparent); }
/* ⚠️ У дуги больше НЕТ CSS-свойства stroke: цвет приходит атрибутом
   stroke="url(#…)" из ring_chart() — градиент референса. Любое CSS-правило
   stroke перебило бы атрибут и вернуло сплошной цвет. */
.ring-arc { fill: none; stroke-linecap: round; }
/* Стопы градиентов красятся отсюда (пары --ring-*-1/2 в :root — там же
   переопределение для тёмной темы). У восстановления цвет пары выбирает
   зона (is-ok/is-warn/is-bad) — семантика зелёный/жёлтый/красный на месте,
   у сна и нагрузки — тона референса (барвинок, шафран→терракота). */
.ring .rg-a { stop-color: var(--accent); }       /* фолбэк: кольцо без типа */
.ring .rg-b { stop-color: var(--accent-dark); }
.ring-sleep .rg-a { stop-color: var(--ring-sleep-1); }
.ring-sleep .rg-b { stop-color: var(--ring-sleep-2); }
.ring-strain .rg-a { stop-color: var(--ring-strain-1); }
.ring-strain .rg-b { stop-color: var(--ring-strain-2); }
.ring.is-ok .rg-a { stop-color: var(--ring-ok-1); }
.ring.is-ok .rg-b { stop-color: var(--ring-ok-2); }
.ring.is-warn .rg-a { stop-color: var(--ring-warn-1); }
.ring.is-warn .rg-b { stop-color: var(--ring-warn-2); }
.ring.is-bad .rg-a { stop-color: var(--ring-bad-1); }
.ring.is-bad .rg-b { stop-color: var(--ring-bad-2); }
/* число в центре — антиквой, как все крупные числа сервиса */
.ring-center { font-family: var(--font-display); font-size: 1.5rem; font-weight: 640; fill: var(--text); }
.ring-title { font-size: .8rem; color: var(--muted); }

/* Шапка дня: [← пред] [дата + календарь] [след →].
   Крайние колонки одной ширины — иначе дата не стоит по центру, когда с одной
   стороны кнопки нет (первый и последний день выгрузки). */
.day-nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: .75rem;
  margin: .5rem 0 1.25rem;
}
.day-nav h1 { margin: 0; }
.day-step {
  justify-self: start;
  padding: .38rem .85rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font-size: .85rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  /* база hover-а одна на весь сервис: --t-fast, а не свои .15s */
  transition: border-color var(--t-fast) ease, color var(--t-fast) ease;
}
.day-step.is-next { justify-self: end; }
.day-step:hover { border-color: var(--accent); color: var(--accent); }
.day-step.is-empty { border: 0; background: none; padding: 0; }
.day-step-date { margin: 0 .3rem; }

/* На узком экране три подписи в ряд не помещаются: кнопки уезжали за край,
   и страница ехала вбок — канон запрещает. Оставляем стрелку, дата у кнопки
   остаётся в aria-label и в подсказке. */
@media (max-width: 620px) {
  .day-step-date { display: none; }
  .day-step { padding: .38rem .7rem; font-size: 1rem; line-height: 1; }
  .day-nav h1 { font-size: 1.5rem; }
}

/* ⚠️ Иконка вынута из потока намеренно. Пока она была обычным соседом даты,
   по центру вставала ПАРА «дата + иконка», и сама дата уезжала влево на половину
   иконки с зазором (22px — заметно на глаз). Теперь ширина блока равна ширине
   даты, центрируется именно она, а иконка висит справа и ни на что не влияет. */
.day-today { position: relative; justify-self: center; margin: 0; }
.day-cal {
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-left: .55rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
  /* база hover-а одна на весь сервис: --t-fast, а не свои .15s */
  transition: border-color var(--t-fast) ease, color var(--t-fast) ease;
}
.day-cal:hover { border-color: var(--accent); color: var(--accent); }
.day-cal svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; }
.day-cal .cal-dot { fill: currentColor; stroke: none; }
/* ⚠️ Поле не прячем display:none и не убираем из потока: календарь ставит попап
   по его прямоугольнику, а на телефоне нативный пикер открывается тапом именно
   по полю. Оно лежит прозрачным поверх иконки и занимает всю кнопку. */
.day-cal input[type="date"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  cursor: pointer;
}
.day-cal:focus-within { border-color: var(--accent); color: var(--accent); }

/* переносим по строкам: на телефоне два поля даты с кнопкой не помещались в ряд
   и тянули страницу вбок */
.day-picker { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin: 0 0 1rem; }
.day-picker input[type="date"] { width: auto; margin: 0; }
.zones.stages { width: 100%; max-width: 420px; height: 14px; }

.baseline-tiles { margin-bottom: .4rem; }
.tile-unit { font-size: .7rem; font-weight: 400; color: var(--muted); }
.delta { font-weight: 600; }
.delta.is-good { color: var(--ok); }
.delta.is-bad { color: var(--bad); }
.delta.is-flat { color: var(--muted); }

.sparks { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.spark-box {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding: .55rem .7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  transition: border-color var(--t-fast) ease, transform var(--t-fast) ease,
              box-shadow var(--t-fast) ease;
}
/* спарк — ссылка на показатель: та же тихая приподнятость, что у дней недели */
.spark-box:hover { border-color: var(--accent); transform: translateY(-1px); box-shadow: var(--shadow-card); }
.spark-label { font-size: .72rem; color: var(--muted); }
.spark { width: 100%; height: auto; }
.spark-line { fill: none; stroke: var(--accent); stroke-width: 2; }
.spark-dot { fill: var(--accent); }

/* заметка приёма хранит анамнез и обследования — переносы строк значимы */
.visit-notes { white-space: pre-line; }

/* ================== настройка дашборда ================== */
.widget-edit { float: right; font-weight: 400; text-decoration: none; }
.widget-controls { white-space: nowrap; }
.widget-controls form { display: inline-flex; gap: .25rem; }
.metric-picker details { margin: .35rem 0; }
.metric-picker summary { cursor: pointer; font-size: .9rem; }
.picker-grid {
  display: grid;
  gap: .15rem .9rem;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  padding: .5rem 0 .5rem 1.1rem;
}
.pick { display: flex; gap: .45rem; align-items: center; margin: 0; font-size: .85rem; color: var(--text); }
.pick input[type="checkbox"] { width: auto; margin: 0; }

/* ================== курсы ================== */
.card.course h3 { margin: 0 0 .35rem; font-size: 1rem; }
.card.course .bar { margin: .4rem 0 .3rem; }
.card.course .doses { margin: .5rem 0; }
.card.course .doses ul { margin: .4rem 0 0; padding-left: 1.1rem; }

/* действия курса — второстепенны рядом с цифрами, поэтому тише и мельче */
.course-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  margin-top: .8rem;
  padding-top: .8rem;
  border-top: 1px solid var(--border);
}
/* каждая форма — своя подписанная группа, между ними разделитель */
.inline-form { display: flex; flex-direction: column; gap: .35rem; }
.inline-form + .inline-form { border-left: 1px solid var(--border); padding-left: 1.5rem; }
.inline-form .form-title { font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.inline-form .form-row { display: flex; align-items: flex-end; gap: .6rem; flex-wrap: wrap; }
.inline-form label {
  display: flex;
  flex-direction: column;
  gap: .2rem;
  margin: 0;
  font-size: .75rem;
  color: var(--muted);
}
.inline-form input {
  display: block;
  width: auto;
  margin: 0;
  padding: .35rem .55rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: .85rem;
  max-width: 150px;
}
/* кнопка стоит на одной линии с полями, а не болтается между ними */
.inline-form .form-row .btn-quiet { margin-bottom: 3px; }
.btn-quiet {
  padding: .32rem .8rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: .8rem;
  cursor: pointer;
  transition: border-color var(--t-fast) ease, color var(--t-fast) ease, transform var(--t-fast) ease;
}
.btn-quiet:hover { border-color: var(--accent); color: var(--accent); }
/* stroke-иконка в строчной кнопке стоит по центру строки, а не на базовой линии */
.btn-quiet svg, .btn-ghost svg { vertical-align: -3px; }

table.review select {
  max-width: 260px;
  padding: .25rem .4rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: .82rem;
}
/* раздел для нового показателя — вторым рядом, тише основного выбора */
table.review select.sub-select {
  margin-top: .3rem;
  font-size: .74rem;
  color: var(--muted);
}
table.review tr.is-unmatched td { background: color-mix(in srgb, var(--bad) 10%, transparent); }
table.review tr.is-guess td { background: color-mix(in srgb, var(--warn) 10%, transparent); }

.actions { display: flex; gap: .75rem; align-items: center; }

.banner { position: relative; border-left: 3px solid var(--tone, var(--border)); padding-right: 3rem; }
.banner p { margin: 0; }
.banner.is-ok { --tone: var(--ok); }
.banner.is-bad { --tone: var(--bad); }
/* крестик добавляет JS каждому уведомлению — см. app.js. Без JS оно просто
   остаётся на месте до следующего перехода, как было раньше */
.banner-x {
  position: absolute;
  top: 50%;
  right: .9rem;
  transform: translateY(-50%);
  /* тач-зона шире видимого крестика: кнопка absolute, раскладку не двигает */
  padding: .5rem .7rem;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}
.banner-x:hover { color: var(--text); background: color-mix(in srgb, var(--text) 8%, transparent); }
/* Закрытие крестиком: баннер схлопывается и гаснет, страница съезжает плавно —
   remove() за кадр подпрыгивал всем контентом ровно под взглядом. Стартовую
   высоту в px выставляет app.js перед классом: auto не анимируется.
   !important перебивает этот inline-стиль (важное авторское сильнее inline). */
.banner.is-closing {
  height: 0 !important;
  margin-top: 0;
  margin-bottom: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0;
  opacity: 0;
  overflow: hidden;
  transition:
    height var(--t-base) var(--ease-out),
    margin var(--t-base) var(--ease-out),
    padding var(--t-base) var(--ease-out),
    border-width var(--t-base) var(--ease-out),
    opacity var(--t-base) var(--ease-out);
}

/* ================== приёмы: хронология, поп-ап, файлы ================== */
.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin: 1.5rem 0 .75rem;
}
.page-head h2 { margin: 0; }

/* заголовок раздела и фильтр типов — одной строкой, кнопка остаётся справа */
.head-filters { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem .75rem; }
.head-filters .periods { margin: 0; }
/* счётчик в чипе: тише подписи, но читается */
.chip-count { font-size: .78em; opacity: .7; }
/* та же строка, но с заголовком страницы: сохраняем отступы h1, а не h2 */
.page-head-h1 { margin: .5rem 0 .6rem; }
.page-head-h1 h1 { margin: 0; }

/* та же строка внутри карточки: заголовок слева, переключатель справа.
   Отступы — ровно те, что были у одинокого h2, иначе шаг до таблицы плывёт. */
.card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .4rem 1rem;
  flex-wrap: wrap;
  margin: 0 0 .75rem;
}
.card-head h2 { margin: 0; }
.switch { display: flex; flex-wrap: wrap; gap: .35rem; }

dialog.modal {
  width: min(680px, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  margin: auto;
  padding: 1.5rem 1.75rem;
  border: 0;
  border-radius: 20px;
  background: var(--surface);
  color: var(--text);
  /* верхний слой интерфейса — токен приподнятости; затемнение даёт backdrop */
  box-shadow: var(--shadow-raised);
  /* Появление и закрытие — фейд с лёгким подъёмом, а не вспышка на весь экран.
     display и overlay через allow-discrete: окно остаётся в верхнем слое, пока
     доигрывает фейд закрытия. Браузер без allow-discrete отбрасывает transition
     целиком — остаётся прежний мгновенный показ, JS не участвует.
     dialog.workout-modal[open] с display:flex не мешает: display в переходе
     дискретный, конкретное значение не важно. */
  opacity: 0;
  transform: translateY(6px) scale(.98);
  transition:
    opacity var(--t-base) var(--ease-out),
    transform var(--t-base) var(--ease-out),
    display var(--t-base) allow-discrete,
    overlay var(--t-base) allow-discrete;
}
dialog.modal[open] { opacity: 1; transform: none; }
/* стартовое состояние первого кадра после showModal(): из него и едет фейд */
@starting-style {
  dialog.modal[open] { opacity: 0; transform: translateY(6px) scale(.98); }
}
dialog.modal::backdrop {
  background: rgb(20 15 10 / .45);
  opacity: 0;
  transition:
    opacity var(--t-base) var(--ease-out),
    display var(--t-base) allow-discrete,
    overlay var(--t-base) allow-discrete;
}
dialog.modal[open]::backdrop { opacity: 1; }
@starting-style {
  dialog.modal[open]::backdrop { opacity: 0; }
}

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.modal-head h2 { margin: 0; }
.modal-close { font-size: 1.05rem; line-height: 1; }
.modal-split { border: 0; border-top: 1px solid var(--border); margin: 1.1rem 0; }

.visit-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  flex-wrap: wrap;
  margin-top: .9rem;
  padding-top: .75rem;
  border-top: 1px solid var(--border);
}

/* миниатюры приложенных страниц: сразу видно, снимок это или PDF */
.visit-thumbs { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .9rem; }
.visit-thumb {
  display: block;
  width: 88px;
  height: 88px;
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--bg);
}
.visit-thumb:hover { border-color: var(--accent); }
/* снимок кадрируется, а не сплющивается: страница заключения узнаётся по шапке */
.visit-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb-pdf {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .25rem;
  height: 100%;
  color: var(--muted);
  font-size: .7rem;
  letter-spacing: .06em;
}

/* операции и снимки в общей ленте: метка в надзаголовке и полоса слева.
   У приёма полосы нет — их большинство, и лента не должна рябить */
.visit.is-surgery { border-left: 3px solid var(--accent); }
.visit.is-imaging { border-left: 3px solid var(--blue); }

/* когда принимать: галочки в ряд под общей подписью, как одно поле формы */
.slot-picker { display: flex; flex-direction: column; gap: .25rem; }
.slot-title { font-size: .82rem; color: var(--muted); }
.slot-row { display: flex; flex-wrap: wrap; gap: .25rem 1rem; }
.slot-row label.check { margin: 0; }

/* время приёма и стоимость дня — одной строкой под дозировкой */
.course-facts { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-top: .4rem; }

.btn-ghost {
  display: inline-block;
  padding: .45rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text) !important;
  font-weight: 500;
  font-size: .9rem;
  transition: border-color var(--t-fast) ease, color var(--t-fast) ease, transform var(--t-fast) ease;
}
.btn-ghost:hover { border-color: var(--accent); color: var(--accent) !important; }

.btn-danger {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  /* ⚠️ Ровно те же отступы, шрифт и рамка, что у .btn-quiet: они стоят рядом
     в одном ряду, и разница в 2px по высоте (рамка против её отсутствия)
     читается как съехавшая вёрстка. Рамка прозрачная — вид тот же, рост общий. */
  padding: .32rem .8rem;
  border: 1px solid transparent;
  border-radius: 999px;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: .8rem;
  cursor: pointer;
}
.btn-danger:hover { color: var(--bad); background: color-mix(in srgb, var(--bad) 10%, transparent); }
.btn-danger svg { flex: none; }
/* одна корзинка без подписи: боковая колонка таблицы не должна тянуться вширь */
.btn-danger.is-icon { padding: .45rem; border-radius: 50%; }
/* залитая опасная кнопка — для главного действия модалки («Удалить навсегда»):
   тихий .btn-danger задуман для рядов таблиц, и в паре с .btn он гасил
   primary-кнопку до вида задизейбленной. Красный — по прямому назначению
   статусного цвета: действие необратимо. */
.btn.btn-danger {
  background: var(--bad);
  color: #fff;
  border: 0;
  padding: .55rem 1.4rem;
  font-size: inherit;
  font-weight: 600;
}
.btn.btn-danger:hover { color: #fff; background: var(--bad); }

.doc-file .doc-page { display: block; width: 100%; height: auto; border-radius: 12px; }
.doc-file .doc-pdf { width: 100%; height: 80vh; border: 0; border-radius: 12px; }

/* ================== кнопка выбора файла — как остальные пилюли ================== */
input[type="file"] {
  font: inherit;
  font-size: .85rem;
  color: var(--muted);
}
input[type="file"]::file-selector-button {
  font: inherit;
  font-size: .9rem;
  font-weight: 500;
  color: var(--text);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: .45rem 1.1rem;
  margin-right: .75rem;
  cursor: pointer;
}
input[type="file"]:hover::file-selector-button { border-color: var(--accent); color: var(--accent); }

/* кнопка в заголовке секции не должна тянуться на всю ширину */
.page-head .btn { width: auto; margin-top: 0; }

/* ================== календарь-попап ================== */
/* нативная иконка спрятана только там, где вместо неё работает наш попап */
@media (pointer: fine) {
  input[type="date"]::-webkit-calendar-picker-indicator { display: none; }
}

.cal {
  position: absolute;
  z-index: 40;
  padding: .9rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: 0 16px 40px rgb(0 0 0 / .18);
  user-select: none;
}

.cal-head { display: flex; align-items: center; gap: .15rem; margin-bottom: .5rem; }
.cal-title { flex: 1; text-align: center; font-weight: 600; font-size: .95rem; }
.cal-nav {
  width: 30px; height: 30px;
  border: 0; border-radius: 8px;
  background: none;
  color: var(--muted);
  font: inherit; font-size: 1rem;
  cursor: pointer;
}
.cal-nav:hover { background: var(--accent-soft); color: var(--text); }

.cal-grid { display: grid; grid-template-columns: repeat(7, 36px); gap: 2px; }
.cal-wd { text-align: center; font-size: .72rem; color: var(--muted); padding: .2rem 0 .35rem; }
.cal-day {
  position: relative;
  height: 34px;
  border: 0; border-radius: 10px;
  background: none;
  color: var(--text);
  font: inherit; font-size: .88rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.cal-day:hover { background: var(--accent-soft); }
.cal-day.is-outside { color: var(--muted); opacity: .55; }
.cal-day.is-today { outline: 1.5px solid var(--accent); outline-offset: -1.5px; }
.cal-day.is-selected { background: var(--accent); color: #fff; font-weight: 600; }
.cal-day:disabled { color: var(--muted); opacity: .3; cursor: default; background: none; }
.cal-day.has-data::after {
  content: '';
  position: absolute;
  left: 50%; bottom: 3px;
  width: 4px; height: 4px;
  margin-left: -2px;
  border-radius: 50%;
  background: var(--accent);
}
.cal-day.is-selected.has-data::after { background: #fff; }

.cal-foot { display: flex; justify-content: space-between; gap: .5rem; margin-top: .5rem; }
.cal-action {
  border: 0; border-radius: 8px;
  padding: .3rem .6rem;
  background: none;
  color: var(--accent);
  font: inherit; font-size: .82rem; font-weight: 500;
  cursor: pointer;
}
.cal-action:hover { background: var(--accent-soft); }

/* ================== спиннер долгих форм ================== */
.spinner {
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-right: .5em;
  vertical-align: -.15em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
button.is-busy { opacity: .85; cursor: progress; }

/* точка графика кликабельна — ведёт на страницу дня */
[data-chart] svg.chart { cursor: pointer; }

/* на десктопе поле даты readonly (cal.js): выбор только календарём,
   без посегментного редактирования и его системной синей подсветки */
@media (pointer: fine) {
  input[type="date"] { cursor: pointer; }
}

/* верхняя строка вкладки: календарь слева, быстрые периоды справа */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem 1.25rem;
  margin: 0 0 1rem;
}
.toolbar .day-picker { margin: 0; }
.toolbar .periods { margin: 0; }

/* числовые колонки таблиц: по правому краю, табличные цифры — столбики ровные */
table.history th.num, table.history td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* col-zones — колонка таблицы; НЕ .zones: так называется сама полоска зон */
table.history th.col-zones, table.history td.col-zones { width: 150px; }

/* подчёркивание ссылки-плитки при наведении — цветом статуса, не чернилами.
   Плюс приподнятость: глубина отвечает курсору только там, где плитка —
   ссылка; div-плиткам подниматься не с чего, клика за ними нет */
a.tile { transition: border-color var(--t-fast) ease, transform var(--t-fast) ease, box-shadow var(--t-fast) ease; }
a.tile:hover {
  text-decoration: underline;
  text-decoration-color: var(--tone, var(--accent));
  transform: translateY(-2px);
  box-shadow: var(--shadow-card);
}

/* карточка курса: картинка товара слева от названия и дозы, текст обтекает */
.course-img {
  float: left;
  width: 76px;
  height: 76px;
  object-fit: contain;
  margin: 0 .9rem .5rem 0;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: #fff;
}
/* блок действий начинается ниже картинки, а не режется об неё */
.course-actions { clear: both; }

/* ================== анализы: фильтр, чипы разделов, липкие секции ================== */
/* кнопка и поиск — одной строкой справа от заголовка. Без flex кнопка и поле
   вставали друг под другом: оба блочные, и шапка расползалась на три этажа. */
.labs-tools { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin: 0; }
/* поиск — пилюля в стиле чипов, а не системный прямоугольник */
.labs-tools input[type="search"] {
  width: 15rem;
  max-width: 100%;
  margin-top: 0;
  border-radius: 999px;
  padding: .5rem 1.1rem;
  background: var(--surface);
}
/* кнопка в шапке — по содержимому: общий .btn растянут на всю ширину формы */
.labs-tools .btn { width: auto; margin-top: 0; }
/* «скрыть пустые» — служебная галочка, а не действие: голосом тише остального */
.labs-empty {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin: 0;
  color: var(--muted);
  font-size: .8rem;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--t-fast) ease;
}
.labs-empty input { margin: 0; accent-color: var(--accent); }
.labs-empty:hover { color: var(--text); }

/* поп-ап показателя: тот же <dialog>, что у тренировки, — и та же оговорка про
   position (см. «поп-ап тренировки»). Шире обычного: внутри график и история. */
/* ⚠️ с `dialog`: правило ширины у `dialog.modal` весит больше голого класса */
dialog.metric-modal { width: min(760px, calc(100vw - 2rem)); }
/* высота под карточку с графиком занята сразу: без резерва окно открывалось
   строкой «Загружаю…» и рывком вырастало, когда приезжал ответ сервера */
.metric-card-body { min-height: 320px; }
/* приехавший контент — коротким фейдом. Keyframe, а не transition: узлы
   вставляются innerHTML один раз, перезапуск анимации не грозит. */
.metric-card-body > * { animation: fade-in var(--t-base) ease-out; }
@keyframes fade-in { from { opacity: 0; } }
.metric-modal .metric-sub { margin: -.4rem 0 1rem; }
.metric-modal .chart-card { margin-bottom: 1rem; }
.metric-modal .metric-note { margin-bottom: 1rem; font-size: .9rem; }
.metric-modal .metric-more { margin: 1rem 0 0; }

/* кабинет в шапке: пилюля рядом с «Выйти», но не пункт меню — это про аккаунт,
   а не про данные, поэтому в навигацию она не входит */
/* спокойное состояние — тихая пилюля: оранжевая заливка в шапке — язык
   «ты здесь» активной вкладки, и вечно залитый «Кабинет» с ней спорил.
   Минус пиксель паддинга компенсирует рамку — рост прежний. */
.account-btn {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: calc(.4rem - 1px) calc(.9rem - 1px);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: .85rem;
  font-weight: 600;
  color: var(--text);
  background: var(--surface);
  text-decoration: none;
  white-space: nowrap;
}
.account-btn:hover { border-color: var(--accent); color: var(--accent); }
/* на своей странице — та же оранжевая заливка, что у активного пункта .nav */
.account-btn.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }
/* иконка человечка нарисована обводкой — заливка сплошным пятном на пилюле
   превращается в кляксу */
.account-btn svg { fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; }
/* на телефоне остаётся только значок: подпись съедает всю строку шапки */
@media (max-width: 600px) {
  .account-btn span { display: none; }
  .account-btn { padding: calc(.4rem - 1px) calc(.55rem - 1px); }
}

/* Вход через Google — вторичная кнопка по канону (п.5 шапки): та же пилюля,
   что у .btn-quiet, но ростом с основную .btn, потому что стоит с ней в паре.
   Белой с цветным логотипом её узнают, и она не спорит с оранжевой за внимание. */
.btn-google {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .55rem;
  width: 100%;
  /* ⚠️ Минус пиксель по вертикали: у основной .btn рамки нет, и при одинаковом
     паддинге кнопки выходили разного роста — 42 против 41. */
  padding: calc(.55rem - 1px) 1.4rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  /* ⚠️ Размер шрифта НЕ уменьшать: рост кнопки задаёт строка, и при .9rem
     она выходила на три пикселя ниже основной. Метрики те же, что у .btn. */
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
.content a.btn-google:hover { border-color: var(--accent); color: var(--accent); }
/* ⚠️ Логотип — инлайновый SVG. Картинку с CDN Google не взять: CSP запрещает
   внешние источники. Цвет буквы не наследуется от кнопки намеренно — фирменные
   четыре цвета остаются фирменными и при наведении. */
.google-mark { display: block; width: 18px; height: 18px; flex: none; }
/* разделитель «или» — линия с подписью посередине */
.or-line {
  display: flex;
  align-items: center;
  gap: .7rem;
  margin: .9rem 0;
  color: var(--muted);
  font-size: .78rem;
}
.or-line::before, .or-line::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}
/* кто именно вошёл — на странице дозаполнения и в кабинете */
.google-who {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 0 0 1rem;
  padding: .6rem .8rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg);
  line-height: 1.35;
}

/* согласия при регистрации: галочка и текст в строку, текст не подлезает
   под галочку второй строкой */
.check {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: .55rem;
  margin-bottom: .7rem;
  font-size: .82rem;
  line-height: 1.4;
  color: var(--text);
}
.check input[type="checkbox"] { width: 16px; height: 16px; margin: .15rem 0 0; accent-color: var(--accent); }

/* Соглашение — поп-ап. ⚠️ Раньше оно раскрывалось <details> прямо в форме
   и сдвигало всё, что ниже: страница прыгала под курсором. Кнопка открытия
   стоит на своей строке, ничего не двигая. */
.terms-link { margin: 0 0 .9rem; }
.terms-modal .terms-body { max-height: 60vh; overflow-y: auto; padding-right: .4rem; }
.terms-modal h3 { margin: 1rem 0 .3rem; font-size: .9rem; }
.terms-modal p { margin: 0 0 .5rem; line-height: 1.5; }

/* Подсказка внутри поля — тише значения, но читаемая: на .45 плейсхолдеры,
   несущие формат ввода («1 или 0.5»), падали до контраста ~1.5:1 и исчезали */
::placeholder { color: var(--muted); opacity: .65; }

/* отдельная страница соглашения: колонка для чтения, а не во всю ширину */
.terms { max-width: 44rem; margin: 2rem auto; padding: 0 1rem; }
.terms h2 { margin: 1.6rem 0 .5rem; }
.terms p { line-height: 1.6; margin: 0 0 .7rem; }
.terms-foot { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: 2rem; }

/* стартовый экран нового профиля: список шагов, у каждого своя кнопка */
.welcome .steps { margin: .9rem 0 .6rem; padding-left: 1.3rem; display: grid; gap: .8rem; }
.welcome .steps li { line-height: 1.5; }
/* кнопка шага — с новой строки: в потоке текста она сливается со ссылками */
.welcome .steps .btn-quiet { display: inline-block; margin-top: .35rem; }

/* поиск над длинным списком показателей: сверка бланка и склейка дублей */
.pick { display: flex; flex-direction: column; gap: .25rem; min-width: 0; }
.pick select { margin: 0; }

/* ================== выпадающий поиск (комбобокс) ================== */
/* ⚠️ Родной <select> не выбрасывается, а прячется: он хранит значение и
   отправляется формой. Без JS он же и остаётся видимым и рабочим. */
.pick.is-combo { position: relative; }
.pick.is-combo select { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.pick-field { margin: 0 !important; cursor: text; }
.pick-list {
  /* fixed и координаты от JS: внутри поп-апа абсолютный список входил в его
     прокручиваемую высоту и отращивал полосу прокрутки на пустом месте */
  position: fixed;
  z-index: 60;
  max-height: 16rem;
  margin: 0;
  padding: .3rem;
  overflow-y: auto;
  list-style: none;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  box-shadow: 0 16px 36px rgb(0 0 0 / .14);
}
.pick-list li {
  padding: .4rem .6rem;
  border-radius: 10px;
  font-size: .85rem;
  line-height: 1.3;
  cursor: pointer;
}
.pick-list li:hover, .pick-list .is-cursor { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.pick-list .is-chosen { font-weight: 600; }
/* «завести новый» — действие, а не запись справочника: и выглядит иначе */
.pick-list .is-service { color: var(--accent); font-weight: 600; }
.pick-list .pick-empty { color: var(--muted); cursor: default; }
.pick-list .pick-empty:hover { background: none; }
.labs-tools input[type="search"]::-webkit-search-cancel-button { cursor: pointer; }
/* шаг между строками шапки анализов один и тот же: заголовок → «8 сдач» →
   чипы разделов → таблица. Общий .table-hint подтягивается вверх под обычный
   h1 — здесь заголовок сидит в .page-head-h1 и подтягивать нечего */
.page-head-h1 + .table-hint { margin: 0 0 .6rem; }
/* .periods подтягивает себя вверх на 4px — под чипами разделов это ломает шаг */
/* оглавление разделов — смысловыми группами: подпись тихим капсом, чипы за ней */
.section-chips { margin-top: 0; margin-bottom: .6rem; gap: .45rem 1.4rem; }
.chip-group { display: flex; align-items: center; flex-wrap: wrap; gap: .4rem; }
/* ⚠️ свой display сильнее UA-правила для [hidden] — группу без чипов прячем явно */
.chip-group[hidden] { display: none; }
.chip-group-label {
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* вертикальная липкость заголовков разделов снята вместе с шапкой дат —
   внутри бокового scrollport'а она превращалась в сдвиг вниз (см. thead) */
/* якорь чипа встаёт под липкую панель навигации, а не прячется за ней */
table.labs tr.section-row { scroll-margin-top: calc(var(--topbar-h) + .5rem); }

/* галочки в формах: подпись сбоку, не столбиком */
label.check {
  display: flex;
  flex-direction: row !important;
  align-items: center;
  gap: .45rem;
  cursor: pointer;
}
label.check input[type="checkbox"] {
  width: auto;
  margin: 0;
  accent-color: var(--accent);
}

/* кнопки всех групп действий курса — на одной нижней линии:
   заголовок сверху, ряд с кнопкой прижат к низу группы */
.course-actions .inline-form .form-row { margin-top: auto; }
.course-actions .inline-form .form-row label.check { margin-bottom: 7px; white-space: nowrap; }

/* галочка в сетке формы стоит на линии полей, а не болтается по центру */
.fields label.check { align-self: end; padding-bottom: 1rem; }

/* поле ссылки на товар: кнопка «Подтянуть» и превью справа */
.link-row { display: flex; align-items: center; gap: .6rem; }
.link-row input { flex: 1; }
.link-row .link-pull { flex: none; white-space: nowrap; }
.link-status { flex: none; display: inline-flex; align-items: center; gap: .4rem; font-size: .85rem; color: var(--muted); }
.link-status img {
  height: 40px;
  width: 40px;
  object-fit: contain;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
}

/* кнопки карточки курса: напротив картинки, по вертикали по её центру */
.course-head-actions {
  float: right;
  display: flex;
  align-items: center;
  gap: .35rem;
  margin: .35rem 0 .5rem .75rem;
  min-height: 56px;
}
.course-head-actions form { margin: 0; }

/* чекбокс в стиле сайта: скруглённый квадрат, оранжевая заливка с галочкой */
label.check input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 19px;
  height: 19px;
  margin: 0;
  border: 1.5px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  cursor: pointer;
  display: inline-grid;
  place-content: center;
  flex: none;
  transition: background var(--t-fast), border-color var(--t-fast);
}
label.check input[type="checkbox"]::before {
  content: '';
  width: 11px;
  height: 11px;
  /* старт из .4 с прозрачностью, а не рост из нулевой точки: галочка с лёгким
     перелётом «садится» в квадрат — единственное место, где капля характера
     уместна и ничего не стоит */
  transform: scale(.4);
  opacity: 0;
  transition: transform .16s var(--ease-spring), opacity .1s ease;
  clip-path: polygon(14% 44%, 0 63%, 42% 100%, 100% 18%, 82% 2%, 40% 68%);
  background: #fff;
}
label.check input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
}
label.check input[type="checkbox"]:checked::before { transform: scale(1); opacity: 1; }
label.check input[type="checkbox"]:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent);
  outline-offset: 1px;
}

/* ================== таблицы: воздух в строках ==================
   Заголовки — просто строка текста над данными: плашка с фоном внутри
   скруглённой рамки выглядела врезкой (решение пользователя 06.08). */
table.history th {
  color: var(--muted);
  font-weight: 600;
  padding-top: .6rem;
  padding-bottom: .6rem;
  border-bottom: 1px solid var(--border);
}
table.history td { padding-top: .7rem; padding-bottom: .7rem; }
table.history th:first-child, table.history td:first-child { padding-left: 1rem; }

/* режим отрезка в календаре: границы залиты, середина — светлым */
.cal-day.in-range { background: color-mix(in srgb, var(--accent-soft) 55%, transparent); border-radius: 0; }
.cal-hint { justify-content: center; font-size: .78rem; color: var(--muted); padding-top: .2rem; }

/* тулбар: равные промежутки между календарём, кнопкой и пилюлями периодов */
.toolbar { gap: .5rem .4rem; }
.toolbar .day-picker { gap: .4rem; }

/* наведение на строку таблицы — тёплая едва заметная подложка */
table.history tbody tr:hover td {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}
table.labs tbody tr:not(.section-row):hover td,
table.labs tbody tr:not(.section-row):hover th.sticky-col {
  background: color-mix(in srgb, var(--accent) 6%, var(--surface));
}

/* лёгкий внутренний отступ: таблица не упирается в скруглённую рамку */
.table-wrap:has(> table.history) { padding: .35rem .2rem; }

/* ================== карточка приёма: реф «хакатон Сільпо» ==================
   Оранжевая дата капсом, жирный заголовок, превью заключения; детали
   раскрываются по клику. Анимация — трюк grid-template-rows 0fr→1fr:
   плавно и без измерения высоты скриптом. */
.visit { padding: 1.35rem 1.5rem; transition: border-color var(--t-fast) ease; }
.visit:hover { border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); }

.visit-head {
  position: relative;
  display: block;
  width: 100%;
  padding: 0 2.2rem 0 0;
  border: 0;
  background: none;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.visit-eyebrow {
  display: block;
  margin-bottom: .3rem;
  /* надзаголовок — терракотой: акцент второго плана, дата не спорит
     с оранжевыми кнопками за внимание */
  color: var(--accent-2);
  font-size: .72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .08em;
}
.visit-head h3 { margin: 0 0 .25rem; font-size: 1.05rem; }
.visit-preview {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: .9rem;
  /* превью прячется тем же языком, что растёт тело: схлопывание высоты, а не
     display:none — тот выдёргивал две строки за кадр, и шапка подпрыгивала
     посреди плавного раскрытия. max-height с запасом над фактическими двумя
     строками (кламп выше), поэтому мёртвой зоны в переходе почти нет. */
  max-height: 3em;
  transition: max-height var(--t-slow) var(--ease-out), opacity var(--t-slow) var(--ease-out);
}
.visit.open .visit-preview { max-height: 0; opacity: 0; } /* полный текст уже в деталях */

.visit-chevron {
  position: absolute;
  top: .35rem;
  right: 0;
  color: var(--muted);
  /* та же длительность и кривая, что у тела: карточка движется как одно целое */
  transition: transform var(--t-slow) var(--ease-out);
}
.visit.open .visit-chevron { transform: rotate(180deg); }

.visit-body {
  display: grid;
  grid-template-rows: 0fr;
  /* ease-out вместо ease: раскрытие по клику стартует сразу, а не разгоняется */
  transition: grid-template-rows var(--t-slow) var(--ease-out);
}
.visit-body-inner { overflow: hidden; }
.visit.open .visit-body { grid-template-rows: 1fr; }
.visit-body-inner > :first-child { margin-top: .6rem; }

/* страница настройки: кнопки включения встроенных блоков */
.builtin-grid { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.builtin-grid form { margin: 0; }

/* форма добавления виджета: блок настроек выбранного типа */
.widget-config { margin: .25rem 0 .5rem; }

/* итоговая колонка «Последний результат» — липнет справа, видна всегда */
table.labs th.last-col,
table.labs td.last-col {
  position: sticky;
  right: 0;
  /* переменная, чтобы телефонное сужение не проигрывало этому правилу каскад.
     max-width обязателен: в авто-раскладке width — лишь пожелание, и длинное
     текстовое значение (ПЛР «не виявлено в дослідженому…») раздувало липкую
     колонку до полутора экранов */
  width: var(--last-col-w, 132px);
  max-width: var(--last-col-w, 132px);
  background: var(--surface);
  /* тень на липком крае говорит «под ней проезжают колонки» */
  box-shadow: -8px 0 10px -8px color-mix(in srgb, var(--text) 25%, transparent);
  box-shadow: -1px 0 0 var(--border);
  z-index: 2;
}
table.labs thead th.last-col { z-index: 4; color: var(--accent); }
table.labs td.last-col { font-weight: 600; }
table.labs td.last-col .date-sub { font-weight: 400; }

/* ================== содержание тренировки ================== */
.wk-sets h3 { margin: 1.3rem 0 .6rem; font-size: .95rem; }
/* подходы одного упражнения — строкой чипов, а не отдельными строками таблицы:
   «80×8 80×8 75×10» читается одним взглядом, десять строк — нет */
.sets-list { text-align: left; }
.sets-list .set {
  display: inline-flex;
  align-items: center;
  gap: .15rem;
  margin: 0 .35rem .3rem 0;
  padding: .15rem .5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* крестик у строки убирает упражнение целиком: по крестику на каждый подход —
   это десяток кнопок в одной строке, шум вместо управления */
.sets td form { margin: 0; }
/* паддинг шире видимого крестика, отрицательный margin возвращает место:
   тач-зона растёт, строка подходов не разъезжается */
.sets .btn-danger.is-icon { padding: .45rem .5rem; margin: -.35rem -.15rem; font-size: 1.05rem; line-height: 1; }

.set-add { margin-top: 1rem; }
.set-nums { display: flex; gap: .8rem; }
.set-nums label { flex: 1; }
.wk-drop { margin-top: 1.4rem; }

/* заголовок и служебный переключатель рядом: «скрыть пустые» относится к таблице,
   а не к кнопкам справа, и стоять должен у названия раздела */
.head-group { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap; }
.head-group h1 { margin: 0; }

/* Переключатель в стиле сервиса: нативный квадратик системного цвета выбивался
   из палитры на каждой теме. Сам input остаётся на месте — он ловит клавиатуру
   и отдаёт состояние, — но рисуется своей плашкой. */
.switch {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  margin: 0;
  padding: .3rem .8rem .3rem .55rem;
  border-radius: 999px;
  background: var(--soft);
  color: var(--muted);
  font-size: .8rem;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition: background var(--t-fast), color var(--t-fast);
}
.switch:hover { color: var(--text); }
.switch input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.switch-box {
  width: 15px;
  height: 15px;
  flex: none;
  border: 1.5px solid var(--border);
  border-radius: 5px;
  background: var(--surface);
  transition: background var(--t-fast), border-color var(--t-fast);
}
.switch input:checked + .switch-box {
  border-color: var(--accent);
  background:
    /* галочка рисуется фоном: отдельный элемент ради двух линий не нужен */
    linear-gradient(45deg, transparent 46%, #fff 46%, #fff 60%, transparent 60%) 3px 6px / 6px 6px no-repeat,
    linear-gradient(-45deg, transparent 46%, #fff 46%, #fff 60%, transparent 60%) 6px 3px / 8px 8px no-repeat,
    var(--accent);
}
.switch:has(input:checked) { color: var(--text); }
.switch:has(input:focus-visible) { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 2px; }

/* приветствие: имя и под ним сводка о человеке — возраст, пол, группа крови.
   Piazzolla italic и крупно — главный «тёплый» жест референса; сводка под
   ним остаётся тихой строкой обычного текста */
.greeting h1 {
  margin: 0;
  font-style: italic;
  font-weight: 560;
  font-size: clamp(2.1rem, 4.4vw, 2.9rem);
  line-height: 1.08;
  letter-spacing: -.015em;
}
.greeting p { margin: .15rem 0 0; }

/* ================== курс: картинка, действия, напоминание ================== */
/* картинка крупнее: на ней читается упаковка, а по ней курс и узнают в списке */
.course-img { width: 108px; height: 108px; }
/* кнопки поднимаются к самому верху карточки, а не висят на середине высоты */
.course-head-actions { align-items: flex-start; min-height: 0; margin-top: 0; }

.pack-line { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.remind-form { margin: 0; }
.btn-mini { padding: .25rem .7rem; font-size: .78rem; }

/* ================== виджет напоминаний ================== */
.reminders { margin: .5rem 0 0; padding: 0; list-style: none; }
.reminders li {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .45rem 0;
  border-bottom: 1px solid var(--border);
}
.reminders li:last-child { border-bottom: 0; }
/* название занимает всё свободное место — дата и крестик прижаты вправо */
.reminder-what { flex: 1; min-width: 0; }
.reminders form { margin: 0; }
/* крестик удаляет напоминание сразу, без подтверждения — тач-зона шире
   видимой, отрицательный margin компенсирует паддинг, чтобы строка не росла */
.reminders form button { padding: .55rem .6rem; margin: -.55rem -.3rem; color: var(--muted); font-size: 1.1rem; line-height: 1; }
.reminders form button:hover { color: var(--bad); }

/* подпись даты под точкой: разрежённые видны всегда, остальные зажигаются
   курсором — так внизу видно, к какой именно сдаче он относится */
.chart-day { transition: opacity var(--t-fast), fill var(--t-fast); }
.chart-day.is-faded { opacity: 0; }
.chart-day.is-hot { opacity: 1; fill: var(--accent); font-weight: 700; }

/* ================== история показателя в поп-апе ================== */
/* значение — по центру своей колонки: глаз ищет его между датой и лабораторией,
   и прижатое вправо оно вечно оказывалось не там, где его ждут */
/* ⚠️ с `table` и `td`: правило `.history td.v { text-align: right }` весит
   больше голой пары классов и молча оставляло значение прижатым вправо */
table.metric-history td.mid, table.metric-history th.mid { text-align: center; }
/* колонка корзинки — по содержимому, а не по трети таблицы */
table.metric-history td.drop, table.metric-history th.drop {
  width: 1%; padding-left: 0; text-align: right; white-space: nowrap;
}
.metric-history .drop form { margin: 0; }
.metric-history td, .metric-history th { padding-top: .45rem; padding-bottom: .45rem; }

/* склейка внутри поп-апа: свёрнута, пока не понадобится */
.metric-merge { margin-top: 1.2rem; padding-top: .9rem; border-top: 1px solid var(--border); }
.metric-merge summary { color: var(--accent); font-size: .9rem; cursor: pointer; }
.metric-merge summary::marker { color: var(--border); }
.metric-merge p { margin: .5rem 0 .8rem; }

/* Название и «i» одной строкой: иконка не должна съезжать на собственную
   строку под длинным названием и висеть там в одиночестве. */
.metric-head { display: flex; align-items: flex-start; gap: .3rem; }
.metric-head > a { min-width: 0; }
.metric-head .info { flex: none; margin-top: 1px; }

/* Подробный блок норм выходит наведением на саму норму: у лаборатории он
   расписан по возрасту, полу и особым группам и в строку не помещается. */
.sticky-col .ref.has-more {
  position: relative;
  display: inline-block;
  cursor: help;
  border-bottom: 1px dotted var(--border);
}
.sticky-col .ref.has-more:hover { color: var(--text); border-bottom-color: var(--accent); }
/* тот же вид, что у пояснения под «i», только шире и с переносами по строкам
   бланка: у лаборатории каждый интервал напечатан отдельной строкой */
.ref.has-more .tip {
  /* фейд как у остальных подсказок — см. комментарий у .info .tip */
  display: none;
  opacity: 0;
  transition: opacity var(--t-fast) ease, display var(--t-fast) allow-discrete;
  position: absolute;
  z-index: 40;
  left: 0;
  top: calc(100% + .4rem);
  width: min(22rem, 70vw);
  padding: .5rem .6rem;
  font-size: .78rem;
  line-height: 1.45;
  white-space: pre-line;
  text-align: left;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 10px 24px rgb(0 0 0 / .14);
}
.ref.has-more:hover .tip, .ref.has-more:focus .tip { display: block; opacity: 1; }
@starting-style {
  .ref.has-more:hover .tip, .ref.has-more:focus .tip { opacity: 0; }
}

/* Плавная прокрутка к разделу анализов: чипы-оглавление прыгали рывком, и было
   непонятно, куда именно уехала страница. Уважаем системную настройку: у кого
   анимации выключены, прыжок остаётся мгновенным. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* ================== плавающая подсказка [data-hint] ================== */
/* ⚠️ CSS-вариант через ::after обрезался родителем: у ячеек таблицы стоит
   overflow: hidden ради многоточия, и подсказка длинного значения не
   показывалась вовсе. Пока JS не сработал, ::after остаётся как был. */
.js-hints [data-hint]::after { content: none; }
.hint-pop {
  position: fixed;
  z-index: 80;
  width: max-content;
  max-width: min(320px, 70vw);
  padding: .4rem .55rem;
  font-size: .78rem;
  line-height: 1.35;
  text-align: left;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 10px 24px rgb(0 0 0 / .14);
  pointer-events: none;
  /* при переезде подсказки на fixed-плашку потерялся фейд старого ::after —
     возвращаем ровно тот же рецепт: opacity + display через allow-discrete.
     JS по-прежнему только переключает hidden. */
  opacity: 1;
  transition: opacity var(--t-fast) ease, display var(--t-fast) allow-discrete;
}
.hint-pop[hidden] { display: none; opacity: 0; }
@starting-style {
  .hint-pop:not([hidden]) { opacity: 0; }
}

/* на узком экране кольцу и легенде в строку не поместиться — тогда столбиком */
@media (max-width: 560px) {
  .donut-box { flex-direction: column; align-items: flex-start; gap: .8rem; }
  .donut-legend { width: 100%; flex: none; }
}

/* ================== поп-ап тренировки: шапка, тело, содержание ================== */
/* ⚠️ Прокрутка переехала с окна на его части. Раньше окно скроллилось целиком:
   шапка с названием и нагрузкой уезжала вверх, и было непонятно, что открыто,
   а форма добавления подхода пряталась в самом низу длинной страницы. */
/* ⚠️ Только с [open]. Закрытый <dialog> прячет браузерное `display: none`, а
   любой авторский `display` его перебивает — независимо от веса селектора.
   Без этого условия все поп-апы страницы вываливались в неё простынёй. */
dialog.workout-modal[open] {
  display: flex;
  flex-direction: column;
}
dialog.workout-modal {
  /* видимое переполнение нужно крестику за углом — прокручивают теперь дети */
  overflow: visible;
  padding-bottom: 1rem;
}
.workout-modal .wk-head { flex: none; }
.workout-modal .wk-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* полоса прокрутки не должна налезать на цифры плиток */
  padding-right: .4rem;
  margin-right: -.4rem;
}
/* содержание — отдельным блоком, как карточка: список подходов растёт, а форма
   под ним остаётся на виду */
.workout-modal .wk-sets {
  flex: none;
  display: flex;
  flex-direction: column;
  max-height: 44vh;
  margin-top: 1rem;
  padding: .2rem .9rem .9rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: color-mix(in srgb, var(--soft) 55%, transparent);
}
.workout-modal .wk-sets:empty { display: none; }
/* прокручивается список упражнений, а форма под ним стоит на месте: дописывать
   подход, каждый раз догоняя её прокруткой, — то ещё удовольствие */
.workout-modal .wk-sets .table-wrap { overflow-y: auto; min-height: 0; }
.workout-modal .wk-sets .set-add { flex: none; }

/* крестик за углом окна: внутри он спорил за место с нагрузкой, а прокрутка
   содержимого его увозила */
.modal-x.is-outside {
  top: -14px;
  right: -14px;
  width: 34px;
  height: 34px;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: 0 6px 18px rgb(0 0 0 / .18);
}
.modal-x.is-outside:hover { color: var(--bad); border-color: var(--bad); background: var(--surface); }
/* на узком экране за углом места нет — возвращаем внутрь */
@media (max-width: 560px) {
  .modal-x.is-outside { top: .6rem; right: .6rem; box-shadow: none; }
}

/* ================== сверка бланка: колонка показателя ================== */
/* Названия в справочнике длинные («Мікроскопія осаду сечі: Циліндри…»), и в
   поле на 260px они не помещались — обрезались на полуслове ровно там, где
   человек и решает, тот это показатель или нет. Колонка шире, шрифт мельче. */
table.review .pick { min-width: 260px; }
table.review .pick-field {
  width: 100%;
  padding: .3rem .55rem;
  font-size: .78rem;
}
table.review .pick-list li { font-size: .78rem; padding: .32rem .5rem; }
/* колонка «Показатель» забирает свободное место — остальные по содержимому */
table.review th:last-child, table.review td:last-child { width: 34%; }

/* ================== строка с тревожным последним результатом ================== */
/* Красная цифра в ряду из десяти колонок теряется: глаз идёт по строкам, а не
   по клеткам. Заливка мягкая (6%) — по правилу палитры крупные подложки берут
   5–10% тона, — а метку даёт полоска слева у названия. */
table.labs tbody tr.is-alarm td { background: color-mix(in srgb, var(--alarm) 6%, transparent); }
/* ⚠️ Липкая колонка обязана оставаться непрозрачной: под ней проезжают строки.
   Поэтому не «тон на прозрачном», а тон, замешанный в её собственный фон. */
table.labs tbody tr.is-alarm th.sticky-col {
  background: color-mix(in srgb, var(--alarm) 6%, var(--surface));
  /* полоса тревоги + та же краевая тень, что у обычной липкой колонки */
  box-shadow: inset 3px 0 0 var(--alarm),
    8px 0 10px -8px color-mix(in srgb, var(--text) 25%, transparent);
}
/* итоговая колонка и так выделена фоном — здесь она берёт тот же тон погуще */
table.labs tbody tr.is-alarm td.last-col { background: color-mix(in srgb, var(--alarm) 13%, var(--surface)); }
/* ⚠️ На наведении тревога не должна пропадать: общий hover перекрасил бы строку
   в оранжевый, и она переставала бы отличаться от соседних. */
table.labs tbody tr.is-alarm:hover td { background: color-mix(in srgb, var(--alarm) 12%, transparent); }
table.labs tbody tr.is-alarm:hover th.sticky-col { background: color-mix(in srgb, var(--alarm) 12%, var(--surface)); }
table.labs tbody tr.is-alarm:hover td.last-col { background: color-mix(in srgb, var(--alarm) 19%, var(--surface)); }

/* «у границы» — та же механика, что у тревоги, но жёлтым и на тон тише:
   warn — не повод, а напоминание, кричать ему не положено */
table.labs tbody tr.is-caution td { background: color-mix(in srgb, var(--warn) 7%, transparent); }
table.labs tbody tr.is-caution th.sticky-col {
  background: color-mix(in srgb, var(--warn) 7%, var(--surface));
  box-shadow: inset 3px 0 0 var(--warn),
    8px 0 10px -8px color-mix(in srgb, var(--text) 25%, transparent);
}
table.labs tbody tr.is-caution td.last-col { background: color-mix(in srgb, var(--warn) 14%, var(--surface)); }
table.labs tbody tr.is-caution:hover td { background: color-mix(in srgb, var(--warn) 13%, transparent); }
table.labs tbody tr.is-caution:hover th.sticky-col { background: color-mix(in srgb, var(--warn) 13%, var(--surface)); }
table.labs tbody tr.is-caution:hover td.last-col { background: color-mix(in srgb, var(--warn) 20%, var(--surface)); }

/* ================== движение: details, FLIP, reduced-motion ================== */
/* Нативные <details> (секции сводки, выбор показателей, склейка) раскрываются
   тем же жестом, что карточка приёма, а не за кадр. Прогрессивное улучшение:
   ::details-content + interpolate-size (в :root) понимает Chrome 131+,
   остальные остаются с прежним мгновенным раскрытием — ничего не ломается. */
details::details-content {
  block-size: 0;
  overflow-y: clip;
  transition: block-size var(--t-base) var(--ease-out), content-visibility var(--t-base) allow-discrete;
}
details[open]::details-content { block-size: auto; }

/* FLIP-сортировка таблиц (класс вешает app.js): строка доезжает на новое место,
   а не телепортируется — видно, куда ушла та, на которой стоял взгляд */
tbody[data-sortable] tr.is-flip { transition: transform var(--t-base) var(--ease-out); }

/* ⚠️ Системное «меньше движения» — одним общим правилом, а не россыпью точечных:
   любое новое transition/animation гейтится автоматически. Длительность .01ms,
   а не 0: transitionend продолжает стрелять, и JS, ждущий его (закрытие
   баннера), не зависает. Фейды тоже почти мгновенны — reduced motion здесь
   трактуем строго, «без движения», как и просит настройка. Smooth-scroll
   гейтится своим @media (no-preference) выше. */
@media (prefers-reduced-motion: reduce) {
  *, ::before, ::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
  }
}
