/* Основний CSS застосунку — ВЕСЬ, крім tokens.css (токени) і fonts.css.

   Жив інлайном у base.html (~161 КБ у КОЖНІЙ відповіді, техборг №8):
   Cloudflare стискав це до ~50 КБ на дроті, але браузер парсив усе заново на
   кожній навігації, і кешувати між сторінками було нічого. Тут — файл зі
   content-хешем у ?v= (static_url), тобто кеш браузера + едж Cloudflare, а
   інвалідація приходить сама з новим хешем після деплою.

   Правити стилі тепер ТУТ. Порядок підключення в base.html незмінний:
   fonts → tokens → app.css — легасі-палітра нагорі цього файла аліасить
   семантичні токени, і зміна порядку зламала б темізацію. */
    /* Legacy palette — now ALIASED to the theme-aware semantic tokens instead of
       hardcoded dark hex, so every not-yet-migrated surface follows light/dark
       automatically. Per-surface migration still replaces these with the right
       --color-* directly; this is what lets the theme toggle work before that's
       done. color-scheme comes from tokens.css (light dark). */
    :root { --bg: var(--color-bg-canvas); --card: var(--color-bg-surface);
            --text: var(--color-text-primary); --muted: var(--color-text-muted);
            --accent: var(--color-bg-accent); --border: var(--color-border-default); }
    * { box-sizing: border-box; }
    /* Атрибут `hidden` мусить ховати. Браузерне правило — `[hidden]{display:none}`
       у стилі АГЕНТА, а будь-який наш клас із `display:` сильніший за нього
       просто за походженням: `.chat-attach-chip { display:inline-flex }` і
       порожній чип стоїть на екрані, хоча в розмітці `hidden`.

       Це не теорія: так на сторінці рецепта весь час висів оверлей «Готуємо
       копію із заміною», а в чаті — ряд порожніх чипів вкладень. Обидва
       ховались `hidden` і обидва мали клас із `display`. Ловити це по одному
       класу означає чекати наступного; `!important` тут — рівно те, що робить
       normalize.css, і рівно з цієї причини. */
    [hidden] { display: none !important; }
    /* Смуга прокрутки — ЗАВЖДИ, а не «коли є що гортати». `scrollbar-gutter`
       тут недостатньо: він резервує місце лише для вмісту документа, а
       position:fixed-елементи (нижня навігація, верхня панель) міряються від
       вьюпорта, який звужується рівно тоді, коли зʼявляється смуга, — і
       навігація зʼїжджала на її ширину між короткими й довгими сторінками.
       Постійний трек тримає і вміст, і fixed-панелі на місці; на телефонах
       смуги накладні (overlay), тож там це нічого не змінює. */
    html { overflow-y: scroll; scrollbar-gutter: stable; }
    /* На мобільній ширині смуги прокрутки немає ЗОВСІМ. Причини дві.
       По-перше, нижня навігація тут `position:fixed`, і смуга документа —
       завжди на всю висоту вікна — малювалась просто поверх неї: вкоротити
       її «до навігації» не можна, бо це смуга ДОКУМЕНТА, а не блока.
       По-друге, стрічка безшовна: далекі партії згортаються в спейсери й
       повертаються (див. initFeedScroll), тож висота сторінки синтетична, і
       повзунок на ній показував не те місце, де людина насправді є.
       На справжньому телефоні смуги й так накладні, тож видимої втрати
       немає; на десктопі (≥768) смуга лишається — там нею КЕРУЮТЬ мишею, а
       навігація там sticky зверху й під смугу не потрапляє.
       Обидві властивості потрібні: `scrollbar-width` — стандарт (Firefox,
       свіжий Chrome), `::-webkit-scrollbar` — старіші WebKit/Blink. */
    @media (max-width: 767px) {
      html { scrollbar-width: none; }
      html::-webkit-scrollbar { width: 0; height: 0; }
    }
    body { margin:0; font-family: var(--font-ui);
           background: var(--bg); color: var(--text); line-height: 1.5; }
    header { border-bottom: 1px solid var(--border); background: var(--card);
             position: sticky; top: 0; z-index: 40; }
    .bar { max-width: var(--page-max); margin: 0 auto; padding: 14px 24px 0;
           display: flex; align-items: flex-end; justify-content: space-between; }
    .logo { font-size: 20px; font-weight: 700; color: var(--text); text-decoration: none; }
    .tab-link { display: inline-flex; align-items: center; justify-content: center; gap: 6px;
           padding: 9px 16px; border-radius: 8px; text-decoration: none; white-space: nowrap;
           color: var(--muted); font-weight: 600; font-size: 14px; border: 1px solid var(--border);
           background: transparent; position: relative; }
    .tabs { display: flex; gap: 8px; }
    .tabs .tab-link { min-width: 112px; }
    /* Нижня навігація — з'являється лише на мобільному (див. @media 767). */
    .bottom-nav { display: none; }
    .bnav-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
           text-decoration: none; color: var(--color-text-muted); font: var(--text-label-s);
           letter-spacing: var(--tracking-label-s); padding: 6px 0; min-width: 0; }
    .bnav-item span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .bnav-item .icon { width: 22px; height: 22px; }
    .bnav-item.active { color: var(--color-text-primary); }
    .bnav-item.active .icon { color: var(--color-text-accent); }
    /* Десктоп: та сама 5-слотова навігація, що й на мобільному, але зверху
       (замість старого хедера лого+вкладки+аватар). Іконки+підписи, горизонтально.
       Йде ПІСЛЯ базових .bnav-item, щоб перекрити flex/column. */
    .bnav-brand, .bnav-slot { display: none; }
    .bnav-inner { display: flex; width: 100%; }
    /* Плавні переходи між сторінками (MPA view transitions). Кожен тап у
       навігації — повне перезавантаження документа, і без цього вся розмітка
       перемальовується з нуля: в Android-застосунку це виглядає як «стрибок»
       усіх кнопок на кадр. З опт-іном браузер сам знімає стару сторінку і
       перетікає в нову, а панелі з власним view-transition-name у знімках
       ідентичні — тож стоять нерухомо, як у SPA. Браузери без підтримки
       правило просто ігнорують. */
    @media not (prefers-reduced-motion: reduce) {
      @view-transition { navigation: auto; }
      ::view-transition-group(root) { animation-duration: .18s; }
    }
    header { view-transition-name: topbar; }
    .bottom-nav { view-transition-name: bnav; }
    @media (min-width: 768px) {
      header { display: none; }
      .bottom-nav { display: block;
             position: sticky; top: 0; z-index: 40; padding: var(--space-sm) var(--space-base);
             background: var(--color-bg-surface); border-bottom: 1px solid var(--color-border-default); }
      /* Вміст панелі стоїть у тій самій колонці, що й контент сторінки —
         інакше навігація й сітка під нею починались би з різних місць. */
      /* Висота рядка ФІКСОВАНА, бо його вміст різний: на стрічці сюди
         переїжджають поле пошуку (44px) і кнопка «Новий рецепт», а на решті
         сторінок слот порожній. Без цього панель була то 44, то 38 — і вся
         сторінка сіпалась на кожному переході між розділами. */
      .bnav-inner { max-width: var(--page-max); margin: 0 auto; align-items: center;
             min-height: 44px; gap: var(--space-xs); padding: 0 var(--space-xl); }
      .bnav-brand { display: inline-flex; align-items: center; text-decoration: none;
             color: var(--color-text-primary); font: var(--text-heading-m);
             letter-spacing: var(--tracking-heading-m); margin-right: var(--space-lg); }
      .bnav-item { flex: none; flex-direction: row; gap: var(--space-sm); padding: var(--space-sm) var(--space-base);
             min-width: 0; border-radius: var(--radius-full); }
      .bnav-item span { font: var(--text-label-m); }
      .bnav-item.active { background: var(--color-bg-subtle); }
      .bnav-item .notif-badge { position: static; margin-left: var(--space-2xs); }
      /* Правий слот: притискаємо до правого краю, пошук тягнеться, кнопка ні. */
      .bnav-slot { display: flex; align-items: center; gap: var(--space-md);
             margin-left: auto; min-width: 0; flex: 1 1 auto; justify-content: flex-end; }
      /* min-width не косметика: без нього поле стискалось у кружок ~40px і
         переставало бути полем. Якщо навіть цього не влазить — значить ми в
         тому ярусі, де підписи вже прибрані. */
      .bnav-slot .feed-search-input { flex: 1 1 auto; min-width: 260px; max-width: 420px; }
    }
    .tab-link.active { background: var(--bg); color: var(--text); border-color: var(--border);
           border-bottom-color: var(--bg); border-radius: 10px 10px 0 0; margin-bottom: -1px; z-index: 1; }
    .logo.tab-link { border: none; background: none; padding: 9px 4px; }
    .logo.tab-link.active { border: 1px solid var(--border); border-bottom-color: var(--bg);
           background: var(--bg); padding: 9px 16px; border-radius: 10px 10px 0 0; margin-bottom: -1px; }
    .btn.secondary.avatar-link { padding: 0; width: 38px; height: 38px; display: flex; align-items: center;
           justify-content: center; overflow: hidden; flex-shrink: 0; position: relative; }
    /* ОДНА ширина полотна на весь застосунок. Раніше вона залежала від
       ВМІСТУ сторінки — `main:has(.recipe-grid)`, `:has(#shopping-items)` і
       т.д., — тож порожня стрічка (сітки немає) ставала вужчою за повну, а
       покупки — вужчими за стрічку. Ширина стрибала при кожному переході, і
       це неможливо було передбачити з розмітки.

       Тепер ширину задає лише ширина ЕКРАНА: одна змінна, один контейнер,
       усі сторінки в одну лінію. Скільки колонок у сітці — окреме питання,
       і воно контейнера більше не рухає. */
    :root { --page-max: 760px; }
    main { max-width: var(--page-max); margin: 0 auto; padding: 24px; }
    /* Посилання беруть СЕМАНТИЧНИЙ токен, а не легасі `--accent`: той
       глобально аліасований у лайм, і в світлій темі лаймовий текст на
       кремовому тлі майже не читається. `--color-text-accent` уже має
       правильні значення для обох тем — форест у світлій, лайм у темній. */
    a { color: var(--color-text-accent); }
    .btn { display:inline-flex; align-items:center; justify-content:center; gap: var(--space-sm);
           background: var(--accent); color: var(--color-text-on-accent); padding: 9px 16px;
           border-radius: 8px; text-decoration: none; font-weight: 600; border: none; cursor: pointer; font-size: 15px; }
    .btn .icon { width:16px; height:16px; flex:none; }
    .btn.secondary { background: transparent; color: var(--muted); border: 1px solid var(--border); }
    .btn.danger { background: transparent; color: #ef5350; border: 1px solid #ef5350; }
    .grid { display: grid; gap: 14px; }
    /* Елемент ґріду за замовчуванням має `min-width: auto`, тобто НЕ може
       стати вужчим за свій найширший нерозривний вміст. Через це колонка
       1fr роздувалась, щойно в картці з'являвся довгий рядок (список «бракує»
       у фільтрі «можу приготувати») — колонки ставали різної ширини, а сітка
       вилазила за межі. `min-width: 0` повертає їм рівність. */
    .grid > * { min-width: 0; }
    .recipe-card-missing { overflow-wrap: anywhere; }
    .card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg);
            padding: 18px; text-decoration: none; color: var(--text); display: block; }
    .card:hover { border-color: var(--color-border-strong); }
    /* Match the .swipe-wrap radius (--radius-lg) so the wrap's overflow:hidden
       clip and the card's own hover/selected border share the same corner —
       otherwise the 16px clip slices the 12px border square at each corner. */
    .recipe-card { padding: 0; overflow: hidden; border-radius: var(--radius-lg);
      /* Пропуск рендеру офскрін-карток: браузер не малює те, чого не видно,
         а contain-intrinsic-size тримає приблизну висоту, щоб скролбар не
         сіпався. Пара до віконного вивантаження партій у base.html. */
      content-visibility: auto; contain-intrinsic-size: auto 340px; }
    /* Спейсер на місці вивантаженої партії стрічки (див. initFeedScroll):
       тримає рівно ту саму висоту, щоб скрол не стрибав. */
    .feed-batch-spacer { grid-column: 1 / -1; }
    .recipe-card-photo { position: relative; min-height: 230px; height: 100%; box-sizing: border-box; display: flex; align-items: flex-end; }
    @media (min-width: 768px) {
      .recipe-grid { grid-template-columns: 1fr 1fr; }
      .recipe-grid .recipe-card-photo { aspect-ratio: 1 / 1; min-height: 0; }
    }
    /* Desktop feed — ширший полотно + 3 колонки. Прив'язано до сторінок, де
       реально є сітка рецептів (стрічка/профіль/пошук) через :has(), щоб
       сторінки для читання (рецепт, форми) лишались вузькими й зручними. */
    /* Верхня навігація має однакову ширину на ВСІХ сторінках (не стрибає між
       вкладками). Ширшає лише контент стрічки/профілю (де є сітка). */
    /* M1 — бейдж наявності інгредієнтів на плитці страви (сигнальні кольори —
       дозволене для інвентаря використання). */
    .mp-stock { display:inline-flex; align-items:center; gap:3px; flex:none; white-space:nowrap;
           font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           border-radius: var(--radius-full); padding:1px 8px; background: var(--color-bg-subtle); }
    .mp-stock.ok { color: var(--color-signal-good); }
    .mp-stock.warn { color: var(--color-signal-moderate); cursor:pointer; }
    .mp-stock .icon { width:12px; height:12px; }
    /* Значок запасів — сам по собі лише сигнал; число «скільки бракує» це
       подробиця, за якою треба навести чи тапнути. Текст поруч із назвою
       з'їдав ширину й ламав заголовок на одну літеру в рядок. */
    .mp-stock-text { display:none; }
    .mp-stock:hover .mp-stock-text, .mp-stock:focus .mp-stock-text,
    .mp-stock.is-open .mp-stock-text { display:inline; }
    /* M1 — рядок страви: назва, під нею мета (запаси · БЖВ). */
    .mp-entry-body { display:flex; flex-direction:column; align-items:flex-start; gap:2px; }
    .mp-entry-meta { display:flex; flex-wrap:wrap; align-items:center; gap: var(--space-sm); }
    .mp-entry-meta .qty { white-space:normal; }
    /* M4 — рядок пікера рецепта: назва + мета (час · складність). */
    .rp-item { display:flex; flex-direction:column; align-items:flex-start; gap:2px; }
    .rp-item .rp-item-title { font: var(--text-body-m); font-weight:600; }
    .rp-item .rp-item-meta { font: var(--text-body-s); color: var(--color-text-muted);
           display:flex; align-items:center; gap: var(--space-md); flex-wrap:wrap; }
    /* Складність — крапка І слово (правило дизайн-системи): сама крапка нічого
       не каже тому, хто не знає легенди. */
    .rp-item-meta .rp-diff { display:inline-flex; align-items:center; gap:6px; }
    .rp-item-meta .rp-diff::before { content:""; width:8px; height:8px; flex:none;
           border-radius: var(--radius-full); background: var(--color-text-muted); }
    .rp-item-meta .rp-diff.is-easy::before { background: var(--color-signal-good); }
    .rp-item-meta .rp-diff.is-medium::before { background: var(--color-signal-moderate); }
    .rp-item-meta .rp-diff.is-hard::before { background: var(--color-signal-poor); }

    /* Пікер рецепта (`_recipe_picker.html`). Кнопка-тригер живе там, де раніше
       стояв <select>, тож і бокс у неї той самий — інакше форма «стрибала» б
       між екранами. Стрілка праворуч каже, що це вибір, а не дія. */
    .recipe-pick-trigger { display:flex; align-items:center; gap: var(--space-sm);
           width:100%; flex:1; min-width:160px; padding:10px 12px; cursor:pointer; text-align:left;
           background: var(--bg); color: var(--color-text-muted);
           border:1px solid var(--border); border-radius:8px; font-size:15px; font-family:inherit; }
    .recipe-pick-trigger .rpick-label { flex:1; min-width:0; overflow:hidden;
           text-overflow:ellipsis; white-space:nowrap; }
    .recipe-pick-trigger .icon { flex:none; color: var(--color-text-muted); }
    .recipe-pick-trigger.has-value { color: var(--color-text-primary); font-weight:600; }
    .rpick-search-row { padding: var(--space-2xs) var(--space-xl) var(--space-sm); }
    .rpick-search-row input { box-sizing:border-box; }
    /* Три групи чипів поспіль читаються як один блок фільтрів, тож між ними
       відступ менший, ніж у аркуші стрічки з її підписами секцій. */
    /* `border:none` — бо чипи тут <button>, а браузер малює кнопці власну
       рамку: у світлій темі вона читалась як зайвий обвід навколо пігулки. */
    .rpick-chips { padding-top:0; padding-bottom: var(--space-sm); }
    .rpick-chips .filter-chip { border:none; }
    .rpick-list { max-height:52vh; overflow-y:auto; padding: 0 var(--space-xl); }
    .rpick-empty { display:none; padding: var(--space-md); text-align:center; }
    /* Меню на тиждень — дні стосом на мобільному, у 2 колонки на десктопі (D5). */
    .mp-days { display:flex; flex-direction:column; gap:14px; }
    @media (min-width: 1024px) {
      :root { --page-max: 960px; }
      .recipe-grid { grid-template-columns: repeat(2, 1fr); }
      /* D5 — тиждень на десктопі: розкритий лише обраний день, решта тижня
         йде компактним списком нижче. Сітка 2×N із семи повних карток
         змушувала читати весь тиждень, щоб знайти сьогоднішній день. */
      .mp-days .card { display:none; }
      .mp-days .card.is-selected { display:block; }
      /* Показ .mp-rest — нижче, поруч із його базовим правилом: тут воно
         програло б пізнішому `.mp-rest { display:none }` за порядком. */
      .mp-days { display:grid; grid-template-columns: 1fr 1fr; align-items:start; }
    }
    /* 3 колонки з 1440 — і НЕ більше: на 4-х заголовок рецепта клинить у 2
       рядки й картка перестає читатись з першого погляду (спец десктоп-ґріду). */
    @media (min-width: 1440px) {
      /* Ширину задаємо РАЗОМ із полями, щоб сам контент вийшов рівно 1280 як
         у D1: інакше поля з'їдали 48px і сітка була вужча за макет. */
      /* Поля `main` — 24px, тобто --space-xl. З --space-lg (20px) контент
         виходив 1272 замість 1280: перевірено вимірами на живій сторінці. */
      :root { --page-max: calc(1280px + 2 * var(--space-xl)); }
      .recipe-grid { grid-template-columns: repeat(3, 1fr);
             /* D1: проміжок між картками ~30px, а не 14 — при 14 три картки
                читаються як одна суцільна смуга. */
             gap: var(--space-2xl); }
    }
    .recipe-card-photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; }
    .mp-longpress-card, .mp-longpress-card img { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
    .recipe-card-overlay { position: relative; z-index: 1; width: 100%; padding: 16px;
           background: linear-gradient(to top, rgba(0,0,0,.85) 0%, rgba(0,0,0,.5) 60%, rgba(0,0,0,0) 100%); }
    /* Текст на фото бере СВІЙ токен, а не білий колір руками: у Figma для
       цього зʼявилось окреме сімейство (`color/text/on-media`, scrim), і воно
       навмисно однакове в обох темах — під написом не наша поверхня, а чужий
       кадр. Було rgba(255,255,255,…) у чотирьох правилах поспіль. */
    .recipe-card-overlay h3 { color: var(--color-text-on-media); text-shadow: 0 1px 4px rgba(0,0,0,.7); }
    .recipe-card-overlay .muted { color: color-mix(in srgb, var(--color-text-on-media) 80%, transparent); }
    /* Text over a photo can't use the theme's muted colour — it has to stay
       light whatever the theme. The system has no on-media token yet, so this
       follows the rgba pattern the overlay already uses for .muted and h3. */
    .recipe-card-overlay .meta-note,
    .recipe-card-overlay .card-foot,
    .recipe-card-overlay .meta-marker { color: color-mix(in srgb, var(--color-text-on-media) 85%, transparent); }
    /* Плитка без фото: контент притискаємо ДО НИЗУ (як текст поверх фото),
       щоб у сітці однакової висоти текст не «висів» угорі. */
    .recipe-card-body { position: relative; z-index: 1; width: 100%; height: 100%; box-sizing: border-box;
           padding: 16px; display: flex; flex-direction: column; justify-content: flex-end; }
    .card h3 { margin: 0 0 6px; }
    .muted { color: var(--muted); font-size: 14px; }
    .empty { text-align: center; color: var(--muted); padding: 60px 0; }
    label { display:block; margin: 14px 0 6px; font-weight: 600; }
    input, textarea, select { width:100%; padding: 10px 12px; background: var(--bg); color: var(--text);
                      border: 1px solid var(--border); border-radius: 8px; font-size: 15px; font-family: inherit; }
    textarea { min-height: 90px; resize: vertical; }
    .row { display:flex; gap: 10px; align-items:center; margin-top: 20px; flex-wrap: wrap; }
    pre { white-space: pre-wrap; word-break: break-word; overflow-x: auto; max-width: 100%;
          background: var(--card); border:1px solid var(--border);
          padding: 14px; border-radius: 8px; font-family: inherit; }
    /* Заголовок сторінки — один розмір скрізь. Частина сторінок мала клас
       `.page-head-title` (Heading L), а решта — голий <h1> із браузерним
       розміром 2em, тож «Кабінет» був помітно більший за «Комору».
       Правило на елементі, а не в кожному з двох десятків шаблонів: власні
       заголовки (`.recipe-title`, `.profile-name`) мають свої класи й
       перебивають його за специфічністю. */
    h1 { margin-top: 0; font: var(--text-heading-l); letter-spacing: var(--tracking-heading-l); }
    .tags { display:flex; flex-wrap:wrap; gap:8px; }
    .tag { display:inline-block; background: var(--card); border:1px solid var(--border);
           color: var(--text); padding: 7px 14px; border-radius: 20px; font-size: 14px; }
    .ingredient-tag { display:inline-flex; align-items:center; gap:6px; cursor: help; }
    .allergen-icon-wrap { position:relative; display:inline-block; cursor:help; outline:none; }
    .allergen-tooltip { display:none; position:absolute; bottom:100%; left:50%; transform:translateX(-50%);
           margin-bottom:6px; background:#ef5350; color:#fff; padding:4px 8px; border-radius:6px;
           font-size:11px; white-space:nowrap; z-index:10; }
    .allergen-icon-wrap:hover .allergen-tooltip, .allergen-icon-wrap:focus .allergen-tooltip { display:block; }
    .bar-actions { display:flex; gap:10px; align-items:center; }
    .inv-add { display:flex; gap:8px; margin: 16px 0; flex-wrap:wrap; }
    .inv-add input, .inv-add select, .inv-add textarea { width:auto; flex:1; min-width:160px; }
    .inv-add textarea { resize:vertical; font-family:inherit; }
    .inv-add .ing-qty, .inv-add .ing-unit { flex:none; width:80px; min-width:80px; }
    .inv-add-actions { display:flex; gap:8px; flex:none; align-items:center; }
    /* Однаковий провідний елемент (scope/owner) у Покупках та Коморі, щоб
       перехід між вкладками не «стрибав»: мобільно — на всю доступну ширину,
       десктоп — та сама фіксована ширина на обох сторінках. */
    #scope-select { flex:1 1 auto; min-width:0; }
    #scope-select > button { width:100%; }
    .inv-add .inv-scope-row { flex-basis:100%; display:flex; }
    /* Той самий бокс, що й у кнопки-скоупу Покупок (.btn.secondary): висота,
       рамка, радіус, падінги, шрифт — щоб контроль не «стрибав» між вкладками. */
    .inv-add .inv-scope-row select { flex:1; min-width:0; width:auto;
        border:1px solid var(--border); border-radius:8px; padding:9px 16px;
        font-size:15px; font-weight:600; background:transparent; color:var(--text); }
    @media (min-width: 768px) {
      #scope-select { flex:0 0 260px; }
      .inv-add .inv-scope-row select { flex:none; width:260px; }
    }
    /* Експорт/Імпорт — лише іконки на мобільному (текст і стрілку ховаємо). */
    @media (max-width: 767px) {
      #export-select .btn-label, #export-select .btn-chev,
      #import-select .btn-label, #import-select .btn-chev { display:none; }
      #export-select > button, #import-select > button {
        width:38px; height:38px; padding:0; justify-content:center; }
    }
    /* R3 — рядок інгредієнта як підпис: «Назва · уточнення» ліворуч, кількість
       праворуч, роздільник знизу. Поля лишились у розмітці (`.ing-fields`) —
       вони й далі сабмітяться і їх правлять сторонні обробники, — але з очей
       прибрані: правка йде через аркуш R4. Чотири поля в ряд на телефоні
       переносились у два рядки на кожен інгредієнт. */
    .ingredient-row .ing-fields { display:none; }
    .ing-display { flex:1; min-width:0; display:flex; align-items:center; justify-content:space-between;
           gap: var(--space-md); background:none; border:none; cursor:pointer; text-align:left;
           padding: var(--space-md) 0; font: var(--text-body-m); color: var(--color-text-primary); }
    .ing-display-name { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .ing-display-name.is-empty { color: var(--color-text-muted); }
    .ing-display-qty { flex:none; color: var(--color-text-secondary); white-space:nowrap; }
    .ingredient-row { display:flex; gap:8px; margin-bottom:8px; align-items:center; }
    #ingredient-rows .ingredient-row { margin-bottom:0; border-bottom:1px solid var(--color-border-default); }
    #ingredient-rows .ingredient-row:last-child { border-bottom:none; }
    .step-display { flex:1; min-width:0; display:flex; align-items:flex-start; gap: var(--space-md);
           background:none; border:none; cursor:pointer; text-align:left; padding: var(--space-md) 0;
           font: var(--text-body-m); color: var(--color-text-primary); }
    .step-display-body { min-width:0; display:flex; flex-direction:column; gap:2px; }
    /* Крок показуємо ЦІЛИМ — рядок росте під текст. Було обрізання на двох
       рядках із трикрапкою, і в редакторі це коштувало дорого: половина
       кроків закінчувалась на «…», тобто щоб просто перечитати, що ти сам
       написав, доводилось відкривати кожен. Довжина кроку тут — не шум,
       а зміст. `overflow-wrap` лишає захист від нерозривного полотна
       (посилання, довге слово) — воно б розсунуло рядок за екран. */
    .step-display-text { min-width:0; overflow-wrap:anywhere; }
    .step-display-text.is-empty { color: var(--color-text-muted); }
    .step-display-marks { font: var(--text-body-s); color: var(--color-text-muted); }
    .step-display-marks:empty { display:none; }
    #step-sheet textarea { width:100%; box-sizing:border-box; }
    /* R9 — попередження кроку на сторінці рецепта. Сигнальний колір тут не
       виняток: у tokens.css `--color-signal-poor` так і підписаний. Рамки
       нема — це репліка автора всередині кроку, а не окрема картка. */
    .step-warning-note { display:flex; align-items:flex-start; gap: var(--space-sm);
           margin: var(--space-2xs) 0 0 40px; font: var(--text-body-s);
           color: var(--color-signal-poor); }
    .step-warning-note .icon { flex:none; margin-top:2px; }
    .step-marks { display:flex; flex-wrap:wrap; gap: var(--space-sm);
           margin: var(--space-xs) 0 0 40px; }
    .ingredient-row .ing-name { flex:2; width:auto; }
    .ingredient-row .ing-brand { flex:1; width:auto; min-width:90px; }
    .ingredient-row .ing-qty { flex:1; width:auto; min-width:70px; }
    .ingredient-row .ing-unit { flex:1; width:auto; min-width:70px; }
    .ing-remove { background:none; border:none; color: var(--muted); cursor:pointer; font-size:18px; padding:0 6px; }
    .ing-remove:hover { color:#ef5350; }
    .step-photo-btn { display:inline-flex; align-items:center; justify-content:center; width:38px; height:38px;
           flex-shrink:0; border:1px solid var(--border); border-radius:8px; cursor:pointer; font-size:16px;
           background: transparent; margin-top:0; overflow:hidden; padding:0; position:relative; }
    .step-photo-btn.has-file { border-color: var(--accent); }
    .step-photo-btn input[type="file"] { display:none; }
    .step-photo-remove { position:absolute; top:2px; right:2px; width:16px; height:16px; padding:0;
           display:flex; align-items:center; justify-content:center; border-radius:50%; border:none;
           background:rgba(0,0,0,.6); color:#fff; font-size:12px; line-height:1; cursor:pointer; z-index:1; }
    /* Кнопка "замінити фото" кроку — дзеркало хрестика, у лівому кутку;
       видима лише коли фото вже є (.has-file), суто через CSS. Клік по ній
       (клас не .step-photo-remove) відкриває вибір файлу через спільний
       обробник .step-photo-btn у base.html — окремий JS не потрібен. */
    .step-photo-change { position:absolute; top:2px; left:2px; width:16px; height:16px; padding:0;
           display:none; align-items:center; justify-content:center; border-radius:50%; border:none;
           background:rgba(0,0,0,.6); color:#fff; font-size:12px; line-height:1; cursor:pointer; z-index:1; }
    .step-photo-btn.has-file .step-photo-change { display:flex; }
    /* Кнопка «посилання на рецепт» у кроці. Була <select> з усіма рецептами:
       на телефоні це список на пів екрана без пошуку. Тепер кнопка відкриває
       аркуш із пошуком, тож і виглядати має кнопкою. */
    .step-recipe-link { flex-shrink:0; width:38px; height:38px; padding:0;
           display:inline-flex; align-items:center; justify-content:center;
           border:1px solid var(--border); border-radius:8px; background:transparent;
           color: var(--color-text-secondary); cursor:pointer; }
    .step-photo-thumb { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
    /* Коли фото вже є — показуємо його прев'ю ПРЯМО в кнопці, а іконку камери
       лишаємо поверх: інакше кнопка перестає виглядати кнопкою й незрозуміло,
       що по ній можна клікнути ще раз. Підкладка під іконкою світла, а сама
       іконка темна — щоб її було видно на будь-якому фото. */
    .step-photo-btn .step-photo-icon { position:relative; z-index:1; display:flex;
           align-items:center; justify-content:center; }
    .step-photo-btn.has-file .step-photo-icon { color:#14140F; background:rgba(255,255,255,.72);
           border-radius:50%; width:24px; height:24px; }
    .step-photo-btn.has-file .step-photo-icon .icon { width:14px; height:14px; }
    .step-photo-lightbox { display:none; position:fixed; inset:0; background:rgba(0,0,0,.85);
           z-index:100; align-items:center; justify-content:center; padding:24px; cursor:zoom-out; }
    .step-photo-lightbox.open { display:flex; }
    .step-photo-lightbox img { max-width:100%; max-height:100%; border-radius:8px; }
    /* Сканер штрихкоду відкривається ПОВЕРХ шита додавання (той має z-index
       120 і заввишки буває до 88vh), тому дві речі обов'язкові: бути вище за
       шит і не залазити під нього. Вільну зону рахує JS — він знає реальну
       висоту шита, а вона залежить від того, скільки полів у формі. */
    #barcode-scanner-modal { z-index:130; align-items:flex-start; padding:16px;
           padding-bottom: calc(var(--scanner-reserved, 0px) + 16px); }
    #barcode-scanner-modal > div { max-height:100%; overflow-y:auto; }
    #barcode-video { max-height: 42vh; object-fit: cover; }
    .mp-day-modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,.6);
           z-index:100; align-items:center; justify-content:center; padding:24px; }
    .mp-day-modal-overlay.open { display:flex; }
    .mp-day-modal { background: var(--card); border:1px solid var(--border); border-radius:14px;
           padding:16px; width:100%; max-width:360px; max-height:80vh; overflow-y:auto; }
    /* Recipe action sheet — long-press / "More". A bottom sheet that slides up. */
    .sheet-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,.5); z-index:120;
           align-items:flex-end; justify-content:center; }
    .sheet-overlay.open { display:flex; }
    .sheet { background: var(--color-bg-surface); width:100%; max-width:520px;
           border-radius: var(--radius-2xl) var(--radius-2xl) 0 0; overflow-y:auto; max-height:88vh;
           padding-bottom: calc(env(safe-area-inset-bottom) + var(--space-base));
           transform: translateY(100%); transition: transform .22s ease; }
    .sheet-overlay.open .sheet { transform: translateY(0); }
    /* На широкому екрані bottom-sheet читається дивно: він приліплений до
       низу вікна, далеко від того, що ти щойно натиснув угорі. Там це
       звичайна модалка по центру — з усіма заокругленими кутами й без
       «ручки», бо тягнути пальцем нема чого. */
    /* 768, а не 1024: це та сама межа, на якій застосунок скрізь стає
       «десктопним» (верхня панель, сітка). На 800px аркуш усе ще приліплювався
       до низу вікна — далеко від кнопки, яку щойно натиснули вгорі. */
    @media (min-width: 768px) {
      .sheet-overlay { align-items: center; }
      .sheet { border-radius: var(--radius-2xl); max-height: 82vh;
             padding-bottom: var(--space-base);
             transform: translateY(10px) scale(.98); opacity: 0;
             transition: transform .18s ease, opacity .18s ease; }
      .sheet-overlay.open .sheet { transform: none; opacity: 1; }
      .sheet-handle { display: none; }
    }
    /* is-centered — аркуш, що поводиться модалкою по центру на ВСІХ ширинах
       (не лише ≥768): для вікон, які стосуються елемента вгорі екрана
       (вибір обкладинки в редакторі), а не дій над списком унизу. Той самий
       вигляд і анімація, що в десктопних аркушів. Специфічність вища за
       media-правила вище, тож порядок тут не грає. */
    .sheet-overlay.is-centered { align-items:center; padding: var(--space-xl); }
    .sheet-overlay.is-centered .sheet { border-radius: var(--radius-2xl); max-height:82vh;
           padding-bottom: var(--space-base);
           transform: translateY(10px) scale(.98); opacity:0;
           transition: transform .18s ease, opacity .18s ease; }
    .sheet-overlay.is-centered.open .sheet { transform:none; opacity:1; }
    .sheet-overlay.is-centered .sheet-handle { display:none; }
    /* Перегляд фото на весь екран. Підкладка — `--color-bg-scrim`, той самий
       токен, що й під текстом на медіа: під шаром чуже фото, а не наша
       поверхня, тож і хрестик бере кольори з `on-media`, а не з теми. */
    .photo-viewer { position:fixed; inset:0; z-index:1200; display:none;
           align-items:center; justify-content:center; padding: var(--space-base);
           background: var(--color-bg-scrim); opacity:0; transition: opacity .18s ease; }
    .photo-viewer.open { display:flex; opacity:1; }
    .photo-viewer img { max-width:100%; max-height:100%; border-radius: var(--radius-lg);
           object-fit:contain; display:block; }
    /* Рамка — тільки коли під фото є дії. Без них це порожня коробка навколо
       кадру, а перегляд фото має показувати фото, а не нашу поверхню. */
    .photo-viewer-frame { display:flex; flex-direction:column; gap: var(--space-md);
           min-width:0; max-width:100%; max-height:100%; }
    .photo-viewer-frame.has-actions { padding: var(--space-md);
           background: var(--color-bg-surface); border:1px solid var(--color-border-default);
           border-radius: var(--radius-2xl); }
    /* Висота кадру рахується від рамки, інакше фото плюс смуга дій разом
       вилазять за екран: 100% у дитини міряється від висоти БАТЬКА, а не
       від того, скільки місця лишилось. */
    .photo-viewer-frame.has-actions img { max-height: calc(100vh - 180px); }
    .photo-viewer-actions { display:flex; gap: var(--space-sm); flex-wrap:wrap;
           justify-content:flex-end; }
    .photo-viewer-actions .btn { flex:1 1 auto; justify-content:center; }
    .photo-viewer-close { position:absolute; top: var(--space-base); right: var(--space-base);
           width:40px; height:40px; border:none; border-radius: var(--radius-full);
           background: var(--color-bg-on-media-chip); color: var(--color-text-on-media);
           cursor:pointer; display:inline-flex; align-items:center; justify-content:center; }
    /* Курсор — єдина підказка, що фото клікабельне. Рамки чи іконки поверх
       кадру тут зайві: вони псують саме те, на що прийшли дивитись. */
    img.zoomable { cursor: zoom-in; }
    .sheet-handle { width:40px; height:4px; border-radius: var(--radius-full);
           background: var(--color-border-strong); margin: var(--space-sm) auto var(--space-sm); }
    .sheet-head { padding: var(--space-xs) var(--space-xl) var(--space-sm);
           display:flex; align-items:baseline; justify-content:space-between; gap: var(--space-md); }
    /* Аркуші З ПОЛЯМИ (редактор інгредієнта, кроку, чіпи) — рівні поля з усіх
       чотирьох боків, як у звичайної модалки: 24px, той самий `--space-xl`,
       що вже стоїть ліворуч і праворуч.
       Було асиметрично й помітно оком: зверху 4px (заголовок притискався до
       краю), знизу 24px від тіла ПЛЮС 16px від самого аркуша — тобто 40px
       порожнечі під кнопкою. Через `:has` правило чіпає лише аркуші з
       формою; списки дій (`.sheet-item`) і їхній нижній відступ лишаються як
       були — там 40px унизу читаються нормально, бо останній пункт — це
       рядок, а не кнопка.
       `env(safe-area-inset-bottom)` лишається: на телефоні під аркушем ще
       індикатор home, і без нього кнопка тулилась би до нього впритул. */
    .sheet:has(.chip-sheet-body) .sheet-head { padding-top: var(--space-xl); }
    .sheet:has(.chip-sheet-body) { padding-bottom: env(safe-area-inset-bottom); }
    /* На десктопі й у центрованих модалках безпечної зони немає взагалі. */
    @media (min-width: 768px) {
      .sheet:has(.chip-sheet-body) { padding-bottom: 0; }
    }
    .sheet-overlay.is-centered .sheet:has(.chip-sheet-body) { padding-bottom: 0; }
    /* Ручка (яку тягнуть пальцем) сама займає місце зверху, тож там, де вона
       ВИДНА, заголовку вистачає половини відступу — інакше виходило б 24px
       плюс сама ручка. Правило свідомо в media-запиті, а не на сусідстві
       `.sheet-handle + .sheet-head`: ручка лишається в розмітці й на
       десктопі (просто `display:none`), і сусідство скорочувало б відступ
       там, де компенсувати нічого — саме це й дало 12px замість 24. */
    @media (max-width: 767px) {
      .sheet-overlay:not(.is-centered) .sheet:has(.chip-sheet-body) .sheet-head {
        padding-top: var(--space-md);
      }
    }
    /* Підзаголовок — продовження заголовка, а не сусід праворуч. Базове
       правило розводить дітей по краях (там праворуч буває «Скинути»), і в
       аркушах із `.sheet-sub` через це «Додати фото» стискалось у вузьку
       колонку й ламалось на два рядки. */
    .sheet-head:has(.sheet-sub) { flex-direction: column; align-items: flex-start;
           gap: var(--space-2xs); }
    /* Другорядна дія в шапці шита («Сканувати» в R4) — текстом, не кнопкою:
       у шита вже є одна головна дія внизу. */
    .sheet-head-link { background:none; border:none; padding:0; cursor:pointer; flex:none;
           font: var(--text-label-m); font-weight:600; color: var(--color-text-accent); }
    .sheet-title { font: var(--text-editorial-h2); letter-spacing: var(--tracking-editorial-h2);
           color: var(--color-text-primary); }
    .sheet-sub { font: var(--text-body-s); color: var(--color-text-muted); margin-top:2px; }
    .sheet-label { font: var(--text-overline); letter-spacing: var(--tracking-overline); text-transform:uppercase;
           color: var(--color-text-muted); padding: var(--space-md) var(--space-xl) var(--space-2xs); }
    /* Працює і для <button>, і для <a> (навігаційні пункти шита) — звідси
       text-decoration і box-sizing, які кнопці не потрібні, а посиланню так. */
    .sheet-item { display:flex; align-items:center; gap: var(--space-md); width:100%; text-align:left;
           padding: var(--space-md) var(--space-xl); background:none; border:none; cursor:pointer;
           font: var(--text-body-l); color: var(--color-text-primary);
           text-decoration:none; box-sizing:border-box; }
    .sheet-item:hover, .sheet-item:active { background: var(--color-bg-subtle); }
    .sheet-item .icon { width:22px; height:22px; flex:none; color: var(--color-text-secondary); }
    .sheet-item.is-danger, .sheet-item.is-danger .icon { color: var(--color-signal-poor); }
    .sheet-item.is-on { color: var(--color-text-accent); }
    .sheet-item.is-on .icon { color: var(--color-text-accent); fill: currentColor; }
    /* Filter Chip — Default / Selected (selected inverts to the forest CTA). */
    .filter-chip { display:inline-flex; align-items:center; gap:6px; padding:8px 14px; cursor:pointer;
           border-radius: var(--radius-full); background: var(--color-bg-subtle); color: var(--color-text-secondary);
           font: var(--text-label-m); white-space:nowrap; }
    .filter-chip input { position:absolute; opacity:0; width:1px; height:1px; }
    .filter-chip:has(input:checked), .filter-chip.is-active { background: var(--color-bg-inverse); color: var(--color-text-inverse); border:none; }
    .filter-chip .icon { width:14px; height:14px; }
    .filter-group { display:flex; flex-wrap:wrap; gap: var(--space-sm); padding: var(--space-2xs) var(--space-xl) var(--space-sm); }
    /* Honesty callout (tags never hide) — lime-soft, not plain grey text. */
    .filter-note { display:flex; align-items:flex-start; gap: var(--space-sm); margin: var(--space-2xs) var(--space-xl) var(--space-sm);
           background: var(--color-bg-accent-soft); border-radius: var(--radius-md); padding: var(--space-sm) var(--space-md);
           font: var(--text-body-s); color: var(--color-text-secondary); }
    .filter-note .icon { flex:none; color: var(--color-text-accent); margin-top:1px; }
    /* Time presets — quick chips that fill the "time ≤" field. */
    .time-presets { display:flex; flex-wrap:wrap; gap: var(--space-sm); padding: var(--space-2xs) var(--space-xl) var(--space-sm); }
    .time-preset { border:1px solid var(--color-border-default); background: var(--color-bg-surface); cursor:pointer;
           border-radius: var(--radius-full); padding: var(--space-2xs) var(--space-md); font: var(--text-label-m); color: var(--color-text-secondary); }
    .time-preset.active { background: var(--color-bg-inverse); color: var(--color-text-inverse); border-color: var(--color-bg-inverse); }
    /* Source filters as labelled toggle rows (FL1). */
    .toggle-row { display:flex; align-items:center; gap: var(--space-md); cursor:pointer;
           padding: var(--space-md) var(--space-xl); border-bottom:1px solid var(--color-border-default); }
    .toggle-icon { display:inline-flex; flex:none; color: var(--color-text-secondary); }
    .toggle-icon .icon { width:18px; height:18px; }
    .toggle-body { flex:1; min-width:0; }
    .toggle-title { display:block; font: var(--text-body-m); color: var(--color-text-primary); }
    .toggle-desc { display:block; font: var(--text-body-s); color: var(--color-text-muted); }
    .toggle-row input { position:absolute; opacity:0; width:1px; height:1px; }
    .toggle-switch { flex:none; width:44px; height:26px; border-radius: var(--radius-full);
           background: var(--color-border-strong); position:relative; transition: background .15s ease; }
    .toggle-switch::after { content:""; position:absolute; top:3px; left:3px; width:20px; height:20px;
           border-radius:50%; background:#fff; transition: transform .15s ease; }
    .toggle-row input:checked ~ .toggle-switch { background: var(--color-bg-accent); }
    .toggle-row input:checked ~ .toggle-switch::after { transform: translateX(18px); }
    /* Кільцева діаграма адмінських дашбордів (див. _donut.html). Сітка, а не
       flex: на вузькому екрані легенда лягає під кільце сама, без медіазапиту
       з магічною шириною. */
    .chart-card { border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           background: var(--color-bg-surface); padding: var(--space-base); }
    .chart-title { font: var(--text-body-m); font-weight:600; color: var(--color-text-primary);
           margin-bottom: var(--space-md); }
    .chart-body { display:flex; flex-wrap:wrap; align-items:center; gap: var(--space-lg); }
    .donut { width:160px; height:160px; flex:none; transform: rotate(-90deg); }
    /* Сегмент трохи «виїжджає» на наведення — підказка не єдиний зворотний
       звʼязок: миша може стояти нерухомо, поки очі шукають, що саме підсвітилось. */
    .donut-seg { transition: stroke-width .12s ease, opacity .12s ease; cursor:default; }
    .donut-seg:hover, .donut-seg:focus { stroke-width:7.5; outline:none; }
    .donut:hover .donut-seg:not(:hover) { opacity:.45; }
    /* Текст у центрі кільця повертаємо назад: саме кільце обернуте на -90°,
       щоб перший сегмент починався згори, і без цього підпис лежав би боком. */
    .donut-hint-label, .donut-hint-value { transform: rotate(90deg); transform-origin: 21px 21px;
           fill: var(--color-text-primary); font-family: var(--font-ui); }
    .donut-hint-label { font-size:3px; font-weight:600; }
    .donut-hint-value { font-size:2.6px; fill: var(--color-text-secondary); }
    .chart-legend { list-style:none; margin:0; padding:0; flex:1 1 200px; min-width:0;
           display:flex; flex-direction:column; gap: var(--space-xs); }
    .chart-legend li { display:flex; align-items:center; gap: var(--space-sm);
           font: var(--text-body-s); color: var(--color-text-secondary); }
    .chart-dot { width:10px; height:10px; flex:none; border-radius:2px; }
    .chart-legend-label { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis;
           white-space:nowrap; color: var(--color-text-primary); }
    .chart-legend-value { flex:none; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
    /* Кілька діаграм поруч — рівними колонками, поки вміщуються. */
    .chart-grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
           gap: var(--space-base); margin-bottom: var(--space-xl); }
    /* Смужка «пристрій і тип доступу»: один рядок — один пристрій, поділений
       у ТИХ САМИХ кольорах, що й кільце типів поруч. Тому власної легенди тут
       немає: друга легенда з тими самими підписами читалась би як інший набір
       даних. Частки задаються через flex, а не проценти, — ділити самі проценти
       в шаблоні означало б повторити арифметику, яку вже робить кільце. */
    .xbar-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column;
           gap: var(--space-md); }
    .xbar-row { display:flex; align-items:center; gap: var(--space-md); font: var(--text-body-s); }
    .xbar-label { flex:none; width:88px; color: var(--color-text-primary);
           overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .xbar { flex:1; min-width:0; display:flex; height:10px; overflow:hidden;
           border-radius: var(--radius-full); background: var(--color-bg-subtle); }
    /* Мінімальна ширина — щоб один-єдиний юзер у типі не зникав зовсім:
       нуль пікселів читається як «таких немає», а це різні твердження. */
    .xbar-seg { min-width:3px; }
    .xbar-value { flex:none; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
    /* Рядок «Звідки» над полем посилання: підпис, список сайтів і кнопка
       «Відкрити» в один рядок, доки влазять. `min-width:0` на списку —
       обовʼязковий: без нього select не дає собі стиснутись менше за
       найдовшу назву й виштовхує кнопку за край на вузькому екрані. */
    .import-sources { display:flex; align-items:center; gap: var(--space-sm); flex-wrap:wrap; }
    .import-sources-label { flex:none; margin:0; font: var(--text-body-s); color: var(--color-text-muted); }
    /* Базис 120, а не «скільки треба»: на 375 px рядок «Звідки · список ·
       Відкрити» саме на цьому числі лишається одним рядком, а не роняє
       кнопку на власний. Ширші назви список ріже сам. */
    .import-sources select { flex:1 1 120px; min-width:0; width:auto; }
    .import-sources .btn { flex:none; }
    /* Смужка днів щоденника переїхала на спільний макрос `_week_strip.html`
       (ті самі `.mp-*`, що в меню на тиждень), тож власних правил `.food-week`
       / `.food-day` більше немає — разом із ними зникла й пара дрібних
       розбіжностей, через які два «календарики» виглядали спорідненими, але
       не однаковими.

       Крок тижня без адресата (у щоденнику вперед за сьогодні йти нема куди):
       видно, що кнопка на місці, але вона мертва. Прибрати її зовсім не можна
       — смужка тоді стрибала б на ширину стрілки. */
    .mp-weekstep.is-off { opacity:.25; pointer-events:none; }
    /* Settings menu — clean divider rows instead of boxed cards (P2). */
    .settings-label { font: var(--text-label-s); letter-spacing:.1em; text-transform:uppercase;
           color: var(--color-text-muted); margin: var(--space-xl) 0 var(--space-sm); }
    .settings-list { display:flex; flex-direction:column; margin-top: var(--space-lg);
           border:1px solid var(--color-border-default); border-radius: var(--radius-lg); overflow:hidden; }
    .settings-row { display:flex; align-items:center; gap: var(--space-md); padding: var(--space-base);
           text-decoration:none; color: var(--color-text-primary); background: var(--color-bg-surface);
           border-bottom:1px solid var(--color-border-default); }
    .settings-row:last-child { border-bottom:none; }
    /* P2 — іконка рядка налаштувань сидить у мʼякій плитці, як у макеті:
       так ряд читається як список розділів, а не як текст із значками.
       Плитку малюємо на самій іконці, щоб не чіпати розмітку всіх сторінок. */
    /* Дочірній селектор ловив лише прості рядки: у рядках із перемикачем
       («Сповіщення», «Темна тема») іконка лежить глибше — всередині
       `.sr-main`, — і лишалась без плитки. Виходило, що два пункти списку
       виглядають інакше за решту й ще й зсунуті, бо плитка додає 10px
       відступу з боків. Тому селектор перелічує обидва місця явно, а не
       ловить будь-яку іконку в рядку: `.sr-chev` (стрілка праворуч) плитки
       не має й мати не повинна. */
    .settings-row > .icon,
    .settings-row.is-split > .sr-main > .icon { flex:none; box-sizing:content-box; width:20px; height:20px;
           padding:10px; border-radius: var(--radius-md);
           background: var(--color-bg-subtle); color: var(--color-text-secondary); }
    .sr-body { flex:1; min-width:0; }
    .sr-title { display:flex; align-items:center; gap: var(--space-sm); font: var(--text-body-m); font-weight:600; }
    .sr-desc { display:block; font: var(--text-body-s); color: var(--color-text-muted); margin-top:2px; }
    /* P2 — рядок із перемикачем праворуч. Сам рядок перестає бути посиланням
       (контрол усередині `<a>` — невалідна розмітка), натомість посилання
       розтягується на весь вільний простір: тап мимо перемикача веде вглиб,
       тап по перемикачу перемикає. Падінг переїжджає на половинки, щоб
       область натискання не мала мертвих полів по краях. */
    .settings-row.is-split { padding:0; }
    .settings-row.is-split > .sr-main { flex:1; min-width:0; display:flex; align-items:center;
           gap: var(--space-md); padding: var(--space-base); text-decoration:none; color:inherit; }
    .settings-row.is-split > .sr-toggle { flex:none; display:flex; align-items:center;
           margin:0; padding: var(--space-base); padding-left:0; }
    /* Перемикач-кнопка: та сама пігулка, що й `.toggle-switch` у чекбоксів,
       але сама собі контрол — стан приходить із сервера класом `is-on`, бо
       перемикання тут це POST, а не локальна галочка. */
    .toggle-switch.as-button { border:none; padding:0; cursor:pointer; }
    .toggle-switch.as-button.is-on { background: var(--color-bg-accent); }
    .toggle-switch.as-button.is-on::after { transform: translateX(18px); }
    .sr-chev { flex:none; color: var(--color-text-muted); }
    /* Profile header (P1 · You): identity, stat row, plan progress. */
    .profile-head { display:flex; align-items:center; gap: var(--space-base); margin: var(--space-lg) 0 var(--space-base); }
    .profile-avatar { width:64px; height:64px; border-radius:50%; object-fit:cover; flex:none;
           background: var(--color-bg-subtle); display:flex; align-items:center; justify-content:center; color: var(--color-text-secondary); }
    /* Преміум-кільце навколо аватара: тонкий зазор кольору канви + лаймове
       кільце — читається як рамка-нагорода, а не як обвідка картинки. Клас
       вішають шаблони (профіль, кабінет, налаштування), коли в юзера є
       будь-який преміум. box-shadow, а не border: не зсуває розмір і працює
       однаково на <img> і на div-заглушці без фото. */
    .avatar-premium { box-shadow: 0 0 0 2px var(--color-bg-canvas), 0 0 0 4px var(--color-bg-accent); }
    .profile-name { font: var(--text-heading-l); letter-spacing: var(--tracking-heading-l); margin:0; }
    /* P4 — на чужому профілі аватар стоїть НАД імʼям: сторінка починається з
       людини, а не з рядка «аватар + текст». На своєму лишаємо компактний
       рядок, бо вище вже є перемикач стрічки. */
    .profile-head.is-public { flex-direction:column; align-items:flex-start; position:relative; }
    .profile-head.is-public .profile-avatar { width:88px; height:88px; }
    .profile-head.is-public > button { position:absolute; top:0; right:0; margin:0 !important; }
    .profile-premium { display:inline-flex; align-items:center; gap: var(--space-2xs); margin-top: var(--space-2xs);
           background: var(--color-bg-accent); color: var(--color-text-on-accent); border-radius: var(--radius-full);
           padding: 2px var(--space-sm); font: var(--text-label-s); font-weight:600; }
    /* Ачівки. У профілі — тихий рядок пігулок під імʼям: преміум лишається
       найгучнішим (лаймова заливка), ачівки нейтральні, і лише найвища
       здобута підсвічена акцентним текстом. Кольорів поза токенами нема. */
    .profile-badges { display:flex; flex-wrap:wrap; align-items:center; gap: var(--space-2xs);
           margin-top: var(--space-2xs); }
    .profile-badges .profile-premium { margin-top:0; }
    /* Рядок автора у «Мої підписки»: аватар + імʼя ліворуч, «Відписатись»
       праворуч. Уся ліва частина — одне посилання на профіль. */
    .follow-row { gap: var(--space-md); }
    .follow-main { display:flex; align-items:center; gap: var(--space-md); flex:1; min-width:0;
           text-decoration:none; color: var(--color-text-primary); }
    .follow-avatar { width:40px; height:40px; flex:none; border-radius:50%; object-fit:cover;
           background: var(--color-bg-subtle); display:flex; align-items:center; justify-content:center;
           color: var(--color-text-secondary); }
    .follow-unfollow { flex:none; padding: var(--space-xs) var(--space-md); font: var(--text-label-m); }
    /* Бейдж підтримки: помітний, але тихіший за лаймовий преміум — це подяка,
       а не рівень доступу. */
    .profile-supporter { display:inline-flex; align-items:center; gap: var(--space-2xs);
           background: var(--color-bg-subtle); color: var(--color-text-accent);
           border-radius: var(--radius-full); padding: 2px var(--space-sm);
           font: var(--text-label-s); font-weight:600; }
    .profile-supporter .icon { fill: currentColor; }
    .ach-badge { display:inline-flex; align-items:center; gap: var(--space-2xs);
           background: var(--color-bg-subtle); color: var(--color-text-secondary);
           border-radius: var(--radius-full); padding: 2px var(--space-sm);
           font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           font-weight:600; text-decoration:none; }
    .ach-badge.is-top { color: var(--color-text-accent); }
    /* Сторінка ачівок: медаль + назва + умова, замкнені — приглушені. */
    /* Аватар-ініціал. Розмір приходить змінною, бо той самий кружок стоїть і
       в пігулці області, і в рядках «хто додав», де він має бути дрібнішим. */
    .avatar { width: var(--avatar-size, 22px); height: var(--avatar-size, 22px); flex:none;
           border-radius:50%; display:inline-flex; align-items:center; justify-content:center;
           font: var(--text-label-s); font-weight:700; letter-spacing:0; text-transform:uppercase; }
    .avatar-1 { background: var(--avatar-1-bg); color: var(--avatar-1-fg); }
    .avatar-2 { background: var(--avatar-2-bg); color: var(--avatar-2-fg); }
    .avatar-3 { background: var(--avatar-3-bg); color: var(--avatar-3-fg); }
    .avatar-4 { background: var(--avatar-4-bg); color: var(--avatar-4-fg); }
    .avatar-5 { background: var(--avatar-5-bg); color: var(--avatar-5-fg); }
    .avatar-6 { background: var(--avatar-6-bg); color: var(--avatar-6-fg); }
    .avatar-more { background: var(--color-bg-subtle); color: var(--color-text-secondary);
           font-size:10px; }
    /* Фото приходить квадратним 400×400 — обрізаємо по колу, а не тиснемо. */
    .avatar-photo { object-fit:cover; }
    .avatar-stack { display:inline-flex; align-items:center; flex:none; }
    /* Обвідка кольору поверхні — саме вона робить нахлест читабельним. Колір
       підставляє поверхня, бо стос стоїть і на лаймовій пігулці, і на картці. */
    .avatar-stack .avatar { box-shadow: 0 0 0 2px var(--avatar-ring, var(--color-bg-surface)); }
    .avatar-stack .avatar + .avatar { margin-left:-7px; }
    .scope-pill .avatar-stack { --avatar-ring: var(--color-bg-accent); margin-right:2px; }
    /* Три плитки «що це тобі дало». auto-fit, бо «12 год 30 хв» довше за
       решту й на вузькому екрані має право перенестись, а не стиснути сусідів. */
    .fun-stats { display:grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
           gap: var(--space-sm); margin-top: var(--space-sm); }
    .fun-stat { border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           padding: var(--space-md); text-align:center; }
    .fun-stat-num { display:block; font: var(--text-heading-m); color: var(--color-text-primary); }
    .fun-stat-cap { display:block; margin-top: var(--space-2xs);
           font: var(--text-body-s); color: var(--color-text-secondary); }
    .ach-list { display:flex; flex-direction:column; gap: var(--space-sm); margin: var(--space-base) 0 var(--space-xl); }
    /* Категорія ачівок — картка-акордеон (принцип «Нового рецепта», див.
       .new-choice-panel нижче: grid-rows 0fr→1fr, бо вміст невідомої висоти).
       Згорнуто: поточна ачівка зліва, наступна ціль справа, смужка прогресу.
       Тап по шапці розкриває драбину «пройдено/ні». */
    .ach-cat { border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           background: var(--color-bg-surface); overflow:hidden; margin-bottom: var(--space-base);
           transition: border-color .2s ease; }
    .ach-cat.is-open { border-color: var(--color-bg-accent); }
    .ach-cat-head { display:block; width:100%; text-align:left; border:none; background:none;
           color:inherit; font:inherit; cursor:pointer; padding: var(--space-base); }
    .ach-cat-row { display:flex; align-items:center; justify-content:space-between;
           gap: var(--space-md); margin-bottom: var(--space-sm); }
    .ach-cat-now { display:flex; align-items:center; gap: var(--space-md); min-width:0; }
    .ach-cat-now-text { min-width:0; }
    .ach-cat-next { font: var(--text-body-s); color: var(--color-text-muted);
           text-align:right; flex:none; }
    .ach-cat-chev { display:inline-block; vertical-align:-2px; margin-left: var(--space-2xs);
           transition: transform .2s ease; }
    .ach-cat.is-open .ach-cat-chev { transform: rotate(180deg); }
    .ach-cat-panel { display:grid; grid-template-rows: 0fr;
           transition: grid-template-rows .26s cubic-bezier(.22,.61,.36,1); }
    .ach-cat-panel-in { overflow:hidden; }
    .ach-cat.is-open .ach-cat-panel { grid-template-rows: 1fr; }
    .ach-cat-panel-in .ach-list { margin:0; padding: 0 var(--space-base) var(--space-base); }
    /* Медаль поточної ачівки в шапці — та сама лаймова, що в списку. */
    .ach-medal.is-earned { background: var(--color-bg-accent); color: var(--color-text-on-accent); }
    .ach-item { display:flex; align-items:center; gap: var(--space-base);
           border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           padding: var(--space-base); }
    .ach-item.is-locked { opacity:.55; }
    .ach-medal { width:44px; height:44px; flex:none; border-radius:50%;
           display:flex; align-items:center; justify-content:center;
           background: var(--color-bg-subtle); color: var(--color-text-muted); }
    .ach-item.is-earned .ach-medal { background: var(--color-bg-accent); color: var(--color-text-on-accent); }
    .ach-title { font: var(--text-body-m); font-weight:600; color: var(--color-text-primary); }
    .ach-req { font: var(--text-body-s); color: var(--color-text-muted); }
    .ach-check { margin-left:auto; flex:none; color: var(--color-text-accent); }
    .profile-bio { font: var(--text-body-m); color: var(--color-text-secondary); margin: 0 0 var(--space-base); }
    .profile-caption { font: var(--text-label-s); letter-spacing:.1em; text-transform:uppercase;
           color: var(--color-text-muted); margin: var(--space-xl) 0 var(--space-sm); }
    .profile-stats { display:flex; gap: var(--space-2xl); margin: var(--space-base) 0 var(--space-lg); flex-wrap:wrap; }
    .pstat { display:flex; flex-direction:column; }
    .pstat-n { font: var(--text-heading-m); font-weight:700; color: var(--color-text-primary); line-height:1.1; }
    .pstat-l { font: var(--text-body-s); color: var(--color-text-muted); }
    .plan-card { border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           padding: var(--space-base); margin-bottom: var(--space-lg); }
    .plan-top { display:flex; justify-content:space-between; align-items:center; gap: var(--space-md); margin-bottom: var(--space-sm); }
    .plan-label { font: var(--text-body-m); font-weight:600; }
    .plan-link { font: var(--text-label-m); font-weight:600; color: var(--color-text-accent); text-decoration:none; white-space:nowrap; }
    .plan-track { height:8px; border-radius: var(--radius-full); background: var(--color-bg-subtle); overflow:hidden; }
    .plan-fill { height:100%; background: var(--color-bg-accent); border-radius: var(--radius-full); transition: width .3s ease; }
    /* Profile content sub-tabs (Recipes · Saved · Menus). */
    .profile-tabs { display:flex; gap: var(--space-lg); border-bottom:1px solid var(--color-border-default); margin: var(--space-sm) 0 var(--space-base); }
    .profile-tab { padding: var(--space-sm) 0; text-decoration:none; color: var(--color-text-muted);
           font: var(--text-body-m); font-weight:600; border-bottom:2px solid transparent; margin-bottom:-1px; }
    .profile-tab.active { color: var(--color-text-primary); border-bottom-color: var(--color-text-primary); }
    /* F1 — вітальний екран. Один блок, притиснутий до низу екрана: угорі
       порожньо не тому, що бракує вмісту, а тому що великий заголовок і дві
       кнопки під великим пальцем — це і є весь екран. */
    .welcome { min-height: 70vh; display:flex; flex-direction:column; justify-content:flex-end;
           padding-bottom: var(--space-2xl); }
    .welcome-brand { font: var(--text-overline); letter-spacing: var(--tracking-overline);
           text-transform:uppercase; color: var(--color-text-accent); margin-bottom: var(--space-lg); }
    .welcome-title { font: var(--text-editorial-display); letter-spacing: var(--tracking-editorial-display);
           font-family: var(--font-editorial); margin: 0 0 var(--space-base); }
    .welcome-sub { font: var(--text-body-l); color: var(--color-text-secondary);
           margin: 0 0 var(--space-3xl); max-width: 34ch; }
    .welcome-actions { display:flex; flex-direction:column; gap: var(--space-md); }
    .welcome-actions .btn { width:100%; height:52px; border-radius: var(--radius-full);
           font: var(--text-label-l); }
    .welcome-primary { background: var(--color-bg-inverse); color: var(--color-text-inverse); }
    /* Селектор із ДВОМА класами обовʼязковий: `.btn.secondary` важить більше
       за `.welcome-secondary`, і текст лишався приглушеним — на екрані з двох
       кнопок друга читалась як вимкнена. */
    .btn.welcome-secondary { border-color: var(--color-border-default); color: var(--color-text-primary); }
    .welcome-note { font: var(--text-body-s); color: var(--color-text-muted);
           margin: var(--space-base) 0 0; text-align:center; }
    /* F8 — порожня стрічка першого запуску: три виходи, у кожного підпис із
       наслідком. Рядки, а не картки: це список шляхів, а не вітрина. */
    .first-run { margin-top: var(--space-2xl); }
    .first-run-title { font: var(--text-editorial-h2); letter-spacing: var(--tracking-editorial-h2);
           font-family: var(--font-editorial); margin: 0 0 var(--space-sm); }
    .first-run-sub { font: var(--text-body-m); color: var(--color-text-secondary);
           margin: 0 0 var(--space-xl); }
    .first-run-item { display:flex; align-items:center; gap: var(--space-base);
           padding: var(--space-base) 0; text-decoration:none; color: var(--color-text-primary);
           border-bottom:1px solid var(--color-border-default); }
    .first-run-item:last-child { border-bottom:none; }
    .first-run-item > .icon { flex:none; color: var(--color-text-secondary); }
    .first-run-body { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
    .first-run-name { font: var(--text-body-l); font-weight:600; }
    .first-run-desc { font: var(--text-body-s); color: var(--color-text-muted); }
    /* F6/F7 — перший запуск. Два кроки, на кожному одне питання: лічильник і
       «Пропустити» вгорі, заголовок, чесний підпис під ним, пігулки, одна
       кнопка внизу. Жодних рамок і карток — на екрані й так рівно одне, на
       чому можна зупинитись. */
    .onb-head { display:flex; align-items:center; justify-content:space-between;
           gap: var(--space-md); margin-bottom: var(--space-2xl); }
    .onb-step { font: var(--text-overline); letter-spacing: var(--tracking-overline);
           text-transform:uppercase; color: var(--color-text-muted); }
    .onb-skip { font: var(--text-label-m); color: var(--color-text-secondary); text-decoration:none; }
    .onb-title { font: var(--text-editorial-h1); letter-spacing: var(--tracking-editorial-h1);
           font-family: var(--font-editorial); margin: 0 0 var(--space-sm); }
    .onb-sub { font: var(--text-body-m); color: var(--color-text-secondary);
           margin: 0 0 var(--space-2xl); }
    .onb-label { display:block; font: var(--text-overline); letter-spacing: var(--tracking-overline);
           text-transform:uppercase; color: var(--color-text-muted); margin-bottom: var(--space-sm); }
    .onb-actions { margin-top: var(--space-2xl); }
    .onb-actions .btn { width:100%; height:52px; border-radius: var(--radius-full);
           background: var(--color-bg-inverse); color: var(--color-text-inverse);
           font: var(--text-label-l); }
    /* Diet & allergies pills (P3). Diet = single (forest fill); avoid = multi (lime-soft). */
    .pill-wrap { display:flex; flex-wrap:wrap; gap: var(--space-sm); margin-bottom: var(--space-lg); }
    .diet-pill { display:inline-flex; align-items:center; gap: var(--space-xs); padding: var(--space-sm) var(--space-base);
           border-radius: var(--radius-full); border:1px solid var(--color-border-default); background: var(--color-bg-surface);
           color: var(--color-text-primary); font: var(--text-body-m); cursor:pointer; }
    .diet-pill input { position:absolute; opacity:0; width:1px; height:1px; }
    .diet-pill .icon { width:16px; height:16px; }
    .diet-pill:has(input:checked) { background: var(--color-bg-inverse); color: var(--color-text-inverse); border-color: var(--color-bg-inverse); }
    /* Колір тексту тут ОБОВ'ЯЗКОВИЙ, хоч фон уже перевизначено: правило вище
       ставить інверсний (у світлій темі — білий) під темну пігулку, а тут фон
       світло-лаймовий. Без цього рядка обраний алерген був білим по світлому й
       читався гірше за необраний. Стоїть ПІСЛЯ `.diet-pill:has(...)`
       навмисно — специфічність однакова, вирішує порядок. */
    .avoid-pill:has(input:checked) { background: var(--color-bg-accent-soft);
           border-color: var(--color-bg-accent); color: var(--color-text-primary); }
    /* Шапка екрана (M1 «Меню», G7b «Покупки»): назва + підзаголовок-зведення
       ліворуч, круглі кнопки дій праворуч. Решта керування живе в
       bottom-sheet'ах, тож над контентом лишається два рядки тексту, а не
       панель кнопок. */
    /* Заголовок стрічки — лише на мобільному (C1). На десктопі його роль
       виконує підсвічений пункт верхньої панелі (D1). */
    /* RD2 — попередження про алергени. Колір НЕ винятковий: у tokens.css
       --color-signal-poor так і підписаний — «Hard / allergen warning», тобто
       це його пряме призначення. Сирий #ef5350 з danger-кнопок тут був би
       гіршим: він не токен і не переживає зміну теми. */
    .allergen-alert { display:flex; gap: var(--space-md); align-items:flex-start;
           border:1px solid var(--color-signal-poor); border-radius: var(--radius-lg);
           padding: var(--space-md) var(--space-base); margin: var(--space-base) 0;
           font: var(--text-body-m); }
    .allergen-alert-icon { flex:none; color: var(--color-signal-poor); display:flex; }
    /* RD1 — БЖВ карткою. Підпис ліворуч, цифри праворуч: так «на порцію» і
       «загалом» стоять одне під одним і читаються порівнянням, а не текстом. */
    .nutri-card { border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           padding: var(--space-base); margin: var(--space-sm) 0 var(--space-lg); }
    .nutri-row { display:flex; gap: var(--space-md); justify-content:space-between;
           align-items:baseline; padding: var(--space-2xs) 0; flex-wrap:wrap; }
    .nutri-label { color: var(--color-text-secondary); font: var(--text-body-m); }
    .nutri-val { color: var(--color-text-primary); font: var(--text-body-m); font-weight:600;
           text-align:right; }
    .nutri-note { margin: var(--space-sm) 0 0; padding-top: var(--space-sm);
           border-top:1px solid var(--color-border-default);
           font: var(--text-body-s); color: var(--color-text-muted); }
    /* RD2 — кругла кнопка поверх обкладинки (назад). Напівпрозоре темне тло,
       бо фото під нею може бути будь-яким: токен поверхні тут не врятує. */
    /* RD2 — рядок кругових кнопок для рецепта БЕЗ обкладинки: та сама пара
       (назад / улюблене), лише без фото під нею. */
    .recipe-top-row { display:flex; align-items:center; justify-content:space-between;
           margin: var(--space-base) 0; }
    .photo-round-btn { position:absolute; top:var(--space-md); width:40px; height:40px;
           border-radius:50%; display:inline-flex; align-items:center; justify-content:center;
           background:rgba(0,0,0,.45); color:#fff; text-decoration:none; backdrop-filter:blur(2px); }
    /* RD2 — головна дія лишається під рукою на довгій сторінці. Липка, а не
       фіксована: фіксована перекривала б контент і на десктопі, де вона
       зайва. Знизу лишаємо місце під мобільну навігацію — інакше кнопка сіла б
       рівно на неї. */
    .cook-cta { position:sticky; bottom:0; z-index:20; margin:var(--space-lg) 0;
           padding: var(--space-md) 0 calc(var(--space-md) + env(safe-area-inset-bottom));
           background: var(--color-bg-canvas); box-shadow: var(--shadow-sticky); }
    /* RD2 — головна дія рецепта: широка пігулка кольору «inverse», а не
       лаймовий прямокутник із радіусом 8. Лайм тут був загальним стилем
       `.btn` (тимчасовий псевдонім `--accent`), а не рішенням для цієї
       кнопки. `--color-bg-inverse` сам перевертається в темній темі — там
       кнопка стає кремовою, і це навмисно, а не збій контрасту. */
    .cook-cta .btn { height:56px; padding:0 var(--space-xl); border-radius: var(--radius-full);
           background: var(--color-bg-inverse); color: var(--color-text-inverse);
           font: var(--text-body-l); font-weight:600; }
    @media (max-width: 767px) { .cook-cta { bottom:56px; } }
    /* ── RD4/RD5 — три рівні дій рецепта ──────────────────────────────────
       Сторінка закінчувалась рядком із пʼяти однакових бордерних кнопок
       (покупки, меню, поділитися, папка, дублювати) і червоної «Видалити».
       Жодна з них не читалась головною, а «Видалити» стояла в ряду рівних —
       за один промах від решти. RD4 розкладає їх за тим, КОЛИ виникає
       намір: що робиш зі списком продуктів — біля списку; що робиш із
       рецептом — після методу; що може лише автор — у верхньому рядку
       і в ··· . */
    /* Верхній рядок однаковий і з обкладинкою, і без неї. Раніше наявність
       фото вирішувала, буде тут кругла кнопка чи текстове посилання, — той
       самий екран виглядав по-різному через те, чи автор доклав знімок. */
    .recipe-actionbar { display:flex; align-items:center; gap: var(--space-sm);
           margin: var(--space-base) 0; }
    .recipe-actionbar > :first-child { margin-right:auto; }
    .recipe-actionbar-title { display:none; }
    /* «Редагувати» — часта дія автора, тож заслуговує на слот у рядку. Але
       заливка `subtle`, не акцент: головна дія на екрані одна — «Почати
       готувати». */
    .edit-pill { display:inline-flex; align-items:center; justify-content:center; gap: var(--space-sm);
           height:44px; padding: 0 var(--space-lg); border-radius: var(--radius-full);
           background: var(--color-bg-subtle); color: var(--color-text-primary);
           font: var(--text-label-m); text-decoration:none; border:none; cursor:pointer; }
    .edit-pill .icon { width:18px; height:18px; }
    /* RD7 — чернетку видно з першого екрана. Лайм-софт, а не сигнальний
       колір: це стан рецепта, а не помилка в ньому. */
    .draft-banner { display:flex; gap: var(--space-md); align-items:flex-start;
           background: var(--color-bg-accent-soft); border-radius: var(--radius-lg);
           padding: var(--space-md) var(--space-base); margin-bottom: var(--space-base); }
    .draft-banner .icon { flex:none; width:20px; height:20px; color: var(--color-text-primary); }
    .draft-banner-title { font: var(--text-body-m); font-weight:600; }
    .draft-banner-sub { font: var(--text-body-s); color: var(--color-text-secondary); }
    /* RD6 — картка власника: хто це бачить і що з рецептом сталося. Двома
       рядками в одній рамці, бо це одна відповідь на одне питання — «як він
       живе». Переглядів у застосунку не рахуємо, тож у мокапі їх чотири, а
       тут три: вигадана цифра гірша за відсутню. */
    .owner-card { border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           margin: var(--space-base) 0; overflow:hidden; }
    .owner-vis { display:flex; align-items:center; gap: var(--space-md);
           padding: var(--space-md) var(--space-base); }
    .owner-vis > .icon { flex:none; width:20px; height:20px; color: var(--color-text-secondary); }
    .owner-vis-text { flex:1; min-width:0; }
    .owner-vis-title { font: var(--text-body-m); font-weight:600; }
    .owner-vis-sub { font: var(--text-body-s); color: var(--color-text-secondary); }
    .owner-vis-change { flex:none; background:none; border:none; padding:0; cursor:pointer;
           font: var(--text-label-m); color: var(--color-text-accent); }
    .owner-stats { display:flex; border-top:1px solid var(--color-border-default); }
    .owner-stat { flex:1; min-width:0; padding: var(--space-md) var(--space-base); }
    .owner-stat-num { font: var(--text-heading-s); letter-spacing: var(--tracking-heading-s); }
    .owner-stat-label { font: var(--text-body-s); color: var(--color-text-muted); }
    /* Tier 1 — біля інгредієнтів, бо саме над списком виникає намір «купити»
       або «поставити в меню». Підпис називає результат: «Додати 6 у покупки»,
       а не «У список покупок». */
    .action-tier1 { display:flex; flex-wrap:wrap; gap: var(--space-md);
           margin: var(--space-base) 0 var(--space-2xl); }
    .action-pill { display:inline-flex; align-items:center; justify-content:center; gap: var(--space-sm);
           height:44px; padding: 0 var(--space-lg); border-radius: var(--radius-full);
           background: var(--color-bg-surface); border:1px solid var(--color-border-default);
           color: var(--color-text-primary); font: var(--text-label-m); text-decoration:none; cursor:pointer; }
    .action-pill .icon { width:18px; height:18px; }
    /* «Усе вже є» — кнопка лишається робочою (список покупок нікуди не дівся),
       але вже нічого не обіцяє, тож і ваги в ній менше. */
    .action-pill.is-quiet { color: var(--color-text-muted); }
    .action-pill.is-quiet .icon { color: var(--color-text-muted); }
    /* Звідки взялось число на кнопці: скільки позицій відняли й чому. Тихий
       рядок під дією, не другий контрол. */
    .action-tier1-note { margin: calc(-1 * var(--space-xl)) 0 var(--space-2xl);
           font: var(--text-body-s); color: var(--color-text-muted); }
    /* Tier 2 (RD5) — одна поверхня, поділена лініями, а не три окремі кнопки:
       дії рівнозначні, і рамка навколо кожної робила б із них вибір, якого
       нема. Стовпчик на мобільному, рядок на десктопі. */
    .action-block { display:flex; flex-direction:column; margin: var(--space-2xl) 0 var(--space-sm);
           border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           background: var(--color-bg-surface); overflow:hidden; }
    .action-block-item { display:flex; align-items:center; gap: var(--space-md); width:100%;
           padding: var(--space-base); background:none; border:none; cursor:pointer;
           font: var(--text-body-m); color: var(--color-text-primary); text-decoration:none;
           text-align:left; border-bottom:1px solid var(--color-border-default); }
    .action-block-item:last-child { border-bottom:none; }
    .action-block-item .icon { width:20px; height:20px; flex:none; color: var(--color-text-secondary); }
    .action-block-item:hover { background: var(--color-bg-subtle); }
    /* Скарга — не дія одного рівня з «Поділитися»: тихий рядок під блоком. */
    .action-report { display:inline-flex; align-items:center; gap: var(--space-xs);
           background:none; border:none; padding:0; cursor:pointer;
           font: var(--text-body-s); color: var(--color-text-muted); }
    .share-wrap.is-block { display:block; }
    @media (min-width: 768px) {
      /* RD3 — на десктопі рядок дій несе ще й назву та головну кнопку, а
         липка нижня смуга стає зайвою: сторінка й так починається з цього
         рядка, і дві «Почати готувати» на одному екрані — це дві головні
         дії. */
      .recipe-actionbar-title { display:block; flex:1; min-width:0; font: var(--text-heading-s);
             letter-spacing: var(--tracking-heading-s); overflow:hidden;
             text-overflow:ellipsis; white-space:nowrap; }
      .recipe-actionbar > :first-child { margin-right:0; }
      .recipe-actionbar .cook-pill { display:inline-flex; align-items:center; gap: var(--space-sm);
             height:44px; padding: 0 var(--space-lg); border-radius: var(--radius-full);
             background: var(--color-bg-inverse); color: var(--color-text-inverse);
             font: var(--text-label-l); text-decoration:none; }
      .recipe-actionbar .cook-pill .icon { width:18px; height:18px; }
      .cook-cta { display:none; }
      .action-block { flex-direction:row; }
      .action-block-item { justify-content:center; flex:1;
             border-bottom:none; border-right:1px solid var(--color-border-default); }
      .action-block-item:last-child { border-right:none; }
    }
    @media (max-width: 767px) { .recipe-actionbar .cook-pill { display:none; } }
    /* C1/D1 — рядок під вкладками. «Можу приготувати» ліворуч, фільтри
       праворуч; те, що між ними, тулиться до правого краю. */
    .feed-quick-row { display:flex; align-items:center; gap: var(--space-sm);
           margin: var(--space-base) 0; flex-wrap:wrap; }
    .feed-quick-row > .cook-now-pill { margin-right:auto; }
    /* Єдиний гучний елемент рядка — решта тут нейтральні тригери. */
    /* Висота саме 44 — як у `.sort-trigger` поруч. З padding-висотою пігулка
       виходила 35.6px, і два сусідні елементи одного рядка не збігались ані
       висотою, ані вертикальним центром (виміряно на живій сторінці). */
    /* margin:0 обовʼязково: це <label>, а глобальне правило форм дає лейблам
       вертикальні відступи (14px згори, 6px знизу) — через них пігулка стояла
       на 4px нижче сусідніх кнопок того самого рядка. Виміряно, не вгадано. */
    .cook-now-pill { display:inline-flex; align-items:center; gap: var(--space-sm);
           margin:0; height:44px; padding: 0 var(--space-base); border-radius: var(--radius-full);
           background: var(--color-bg-accent); color: var(--color-text-on-accent);
           font: var(--text-label-m); text-decoration:none; cursor:pointer;
           border:1px solid transparent; }
    .cook-now-pill.is-on { border-color: var(--color-text-primary); }
    .filter-chip.as-button { background:none; cursor:pointer; font: var(--text-label-m); }
    /* Категорійні чипи (продукти/інвентар) — той самий вигляд, що й чипи
       алергенів (`.diet-pill.avoid-pill`): обведена пігулка на поверхні, а
       вибрана — мʼякий акцентний фон з акцентною рамкою. До цього вибраний
       стан був суцільним інверсним, майже чорним, тобто однакові за роллю
       фільтри виглядали у двох місцях по-різному.

       Правило стоїть ПІСЛЯ `.as-button` навмисно: специфічність у них
       однакова (два класи), тож вирішує порядок — вище воно б програло. */
    /* `text-decoration` — бо в адмінці ці чипи це <a> (фільтр серверний), а в
       Покупках і Коморі <button>. Підкреслення робило з пігулки посилання. */
    .filter-chip.cat-chip { background: var(--color-bg-surface); color: var(--color-text-primary);
           text-decoration:none;
           border: 1px solid var(--color-border-default);
           font: var(--text-body-m); padding: var(--space-sm) var(--space-base); }
    .filter-chip.cat-chip.is-active { background: var(--color-bg-accent-soft);
           border-color: var(--color-bg-accent); color: var(--color-text-primary); }
    @media (max-width: 767px) {
      /* На мобільному сортування живе в аркуші фільтрів (FL0), а не в рядку. */
      .feed-quick-row .feed-sort-trigger { display:none; }
    }
    /* D5 — кнопка ШІ в меню: кружок на мобільному, лаймова пігулка з підписом
       на десктопі. Два класи, бо `.round-btn` оголошений нижче з тією ж
       специфічністю і виграв би за порядком. */
    .mp-ai-label { display:none; }
    @media (min-width: 1024px) {
      .round-btn.mp-ai-btn { width:auto; padding:0 var(--space-base); gap: var(--space-sm);
             border-radius: var(--radius-full); background: var(--color-bg-accent);
             color: var(--color-text-on-accent); border-color:transparent; }
      .round-btn.mp-ai-btn .mp-ai-label { display:inline; font: var(--text-label-m); white-space:nowrap; }
    }
    /* Текстова дія праворуч у рядку (G3: «Керувати»/«Прибрати»). Кнопка, а не
       посилання: вона сабмітить форму, і виглядати має як дія, не як навігація. */
    .link-action { background:none; border:none; cursor:pointer; padding:0;
           color: var(--color-text-accent); font: var(--text-label-m); white-space:nowrap; }
    /* G3 — шапка вкладеної сторінки: кнопка назад ліворуч, заголовок по
       центру. Кнопка має свою ширину, тож заголовок центруємо по всій смузі,
       а не «по залишку» — інакше він з'їжджав би вліво на довгих назвах. */
    .sheet-head-row { position:relative; display:flex; align-items:center;
           min-height:44px; margin: var(--space-base) 0; }
    .sheet-head-row .icon-btn { position:relative; z-index:1; }
    .sheet-head-title { position:absolute; left:0; right:0; text-align:center;
           margin:0; font: var(--text-heading-m); letter-spacing: var(--tracking-heading-m);
           padding: 0 52px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    /* Довідкові виноски в кабінеті — одним рядком; переносяться самі, коли
       ширини не стає, а не заздалегідь. */
    .account-legal { display:flex; flex-wrap:wrap; align-items:center;
           gap: var(--space-sm); row-gap: var(--space-2xs); }
    .account-legal > span { color: var(--color-text-muted); }
    /* Картка-підказка з дією (перенести комору, додати техніку). Не банер:
       у неї завжди є кнопка, інакше їй тут не місце. */
    .prompt-card { display:flex; flex-wrap:wrap; gap: var(--space-base);
           align-items:flex-start; justify-content:space-between;
           padding: var(--space-base); margin: var(--space-base) 0;
           border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           background: var(--color-bg-surface); }
    .prompt-body { flex:1 1 260px; min-width:0; }
    .prompt-title { display:flex; align-items:center; gap: var(--space-sm);
           font: var(--text-label-l); color: var(--color-text-primary); }
    .prompt-desc { font: var(--text-body-s); color: var(--color-text-secondary);
           margin: var(--space-2xs) 0 0; }
    .prompt-actions { display:flex; gap: var(--space-sm); flex-wrap:wrap; align-items:center; }
    /* Підсумок над сіткою (D1) — той самий overline, що й підписи секцій. */
    .feed-summary { font: var(--text-overline); letter-spacing: var(--tracking-overline);
           text-transform:uppercase; color: var(--color-text-muted);
           margin: var(--space-base) 0 var(--space-sm); }
    .page-head { display:flex; align-items:flex-start; justify-content:space-between;
           gap: var(--space-md); margin: var(--space-lg) 0 var(--space-base); }
    .page-head-text { min-width:0; }
    .page-head-title { font: var(--text-heading-l); letter-spacing: var(--tracking-heading-l); margin:0; }
    .page-head-sub { font: var(--text-body-s); color: var(--color-text-muted); margin: var(--space-2xs) 0 0; }
    .page-head-actions { display:flex; gap: var(--space-sm); flex:none; }
    /* Працює і для <button>, і для <a> (напр. «Історія») — звідси text-decoration. */
    .round-btn { width:44px; height:44px; flex:none; border-radius:50%; cursor:pointer;
           border:1px solid var(--color-border-default); background: var(--color-bg-surface);
           color: var(--color-text-primary); display:flex; align-items:center; justify-content:center;
           padding:0; text-decoration:none; }
    /* G7b — перемикач області як лаймова пігулка (а не звичайний селект):
       це не дія, а «де я зараз», тож він читається як стан. */
    /* Вертикальний падінг xs, а не sm: з аватарками пігулка виросла до ~40px
       і виглядала кнопкою-банером; 4px повертає їй висоту чипа (~28px). */
    /* space-between: назва притиснута до лівого краю, стрілка — до правого.
       На компактній пігулці (hug) різниці нема, а на розтягнутій (класичний
       екран, мобільний повний ряд) стрілка більше не висить одразу за
       текстом посеред лаймової смуги. */
    .scope-pill { display:inline-flex; align-items:center; justify-content:space-between;
           gap: var(--space-sm); cursor:pointer;
           background: var(--color-bg-accent); color: var(--color-text-on-accent);
           border:none; border-radius: var(--radius-full); padding: var(--space-xs) var(--space-base);
           font: var(--text-label-m); font-weight:600; max-width:100%; }
    .scope-pill > .icon { flex:none; }
    .scope-pill .scope-pill-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    /* Глобальний .icon — display:block (лінія нижче в цьому файлі), і
       всередині інлайнового .scope-pill-name блокова svg ламала рядок:
       іконка ставала НАД текстом, пігулка виростала вдвічі. Інлайн повертає
       їх в один рядок, не чіпаючи ellipsis на тексті. */
    .scope-pill-name .icon { display:inline-block; vertical-align:-2px; }
    /* G7c — рядок-тригер «Додати позицію»: сам ввід живе в bottom-sheet, тут
       лише вигляд поля, щоб екран не носив форму з чотирьох полів. */
    /* G7c: сканер і камера — окремі натискні цілі, а не частина одного
       великого тригера. Тому .add-trigger тепер КОНТЕЙНЕР (вигляд поля), а
       клікабельне «Додати позицію» — окрема кнопка всередині: вкладати
       <button> у <button> не можна, і саме це раніше змушувало тримати
       іконки декоративними. */
    .add-trigger { display:flex; align-items:center; gap: var(--space-sm); width:100%;
           box-sizing:border-box; margin: var(--space-base) 0;
           background: var(--color-bg-surface); border:1px solid var(--color-border-default);
           border-radius: var(--radius-lg); padding: var(--space-2xs) var(--space-sm);
           color: var(--color-text-muted); font: var(--text-body-m); }
    .add-trigger-main { flex:1; min-width:0; display:flex; align-items:center; gap: var(--space-md);
           background:none; border:none; cursor:pointer; text-align:left; font:inherit; color:inherit;
           padding: var(--space-sm) var(--space-2xs); }
    .add-trigger .add-trigger-label { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .add-trigger .icon { flex:none; color: var(--color-text-secondary); }
    /* Дрібніші за .round-btn у шапці — вони сидять усередині поля, а не поруч
       із заголовком, і не мають з ним конкурувати. */
    .add-trigger-act { width:36px; height:36px; flex:none; border-radius:50%; cursor:pointer;
           border:none; background: var(--color-bg-subtle); color: var(--color-text-secondary);
           display:flex; align-items:center; justify-content:center; padding:0; }
    /* Форма додавання всередині шита — колонка з підписами, як у G7c,
       замість інлайн-рядка, яким вона є на широких екранах. */
    .inv-add.is-sheet { flex-direction:column; align-items:stretch; gap: var(--space-md);
           margin:0; padding: 0 var(--space-xl) var(--space-xl); }
    .inv-add.is-sheet input, .inv-add.is-sheet select { width:100%; min-width:0; flex:none; }
    .inv-add.is-sheet .sheet-field-row { display:flex; gap: var(--space-md); }
    .inv-add.is-sheet .sheet-field-row > div { flex:1; min-width:0; }
    .inv-add.is-sheet .ing-qty, .inv-add.is-sheet .ing-unit { width:100%; min-width:0; }
    .sheet-field-label { font: var(--text-label-s); letter-spacing:.08em; text-transform:uppercase;
           color: var(--color-text-muted); margin: 0 0 var(--space-2xs); display:block; }
    /* M1/M2 — поля додавання в дні згорнуті за «+ Додати»: у макеті порожній
       день це рядок із датою, а не форма. Розкриваються на самій картці дня. */
    .mp-day-add { display:none; }
    .card.is-adding .mp-day-add { display:flex; }
    .card.is-adding button.mp-day-add { display:inline-flex; }
    .mp-day-add-btn { background:none; border:none; cursor:pointer; padding:0; flex:none;
           font: var(--text-label-m); font-weight:600; color: var(--color-text-accent); }
    .mp-day-empty { font: var(--text-body-s); color: var(--color-text-muted); margin: 0 0 var(--space-2xs); }
    /* M5 — час нагадування на нотатці. */
    .mp-note-time { display:inline-flex; align-items:center; gap:3px; flex:none;
           font: var(--text-label-s); color: var(--color-text-secondary); }
    .mp-note-time .icon { width:12px; height:12px; }
    /* M9 — панель перегляду ШІ-пропозиції: лаймова, бо це стан «очікує на
       твоє рішення», а не звичайний контент плану. */
    .mp-review { background: var(--color-bg-accent-soft); border:1px solid var(--color-bg-accent);
           border-radius: var(--radius-lg); padding: var(--space-base); margin-bottom: var(--space-base); }
    .mp-review-head { display:flex; align-items:baseline; justify-content:space-between;
           gap: var(--space-md); margin-bottom: var(--space-2xs); }
    .mp-review-row { display:flex; align-items:center; gap: var(--space-md);
           padding: var(--space-sm) 0; border-top:1px solid var(--color-border-default); }
    .mp-review-day { flex:none; width:74px; font: var(--text-label-m); font-weight:600; color: var(--color-text-secondary); }
    .mp-review-title { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
           font: var(--text-body-m); color: var(--color-text-primary); text-decoration:none; }
    /* D5 — «решта тижня»: тільки для широких екранів (на мобільному видно всі
       сім карток поспіль, тож список був би дублем). */
    .mp-rest { display:none; margin-top: var(--space-xl); }
    @media (min-width: 1024px) { .mp-rest { display:block; } }
    .mp-rest-row { display:flex; align-items:center; gap: var(--space-base);
           padding: var(--space-md) var(--space-base); border-bottom:1px solid var(--color-border-default); }
    .mp-rest-row:last-child { border-bottom:none; }
    .mp-rest-row.is-selected { background: var(--color-bg-subtle); }
    /* M7 — рядок «решти тижня» як ціль перетягування: на десктопі це єдиний
       спосіб перекинути страву в день, картка якого зараз згорнута. */
    .mp-rest-row.drag-over { background: var(--color-bg-accent-soft); outline:2px solid var(--color-bg-accent); }
    .mp-rest-day { flex:none; width:64px; font: var(--text-body-m); font-weight:600; }
    .mp-rest-title { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
           font: var(--text-body-m); cursor:pointer; }
    .mp-rest-title.is-empty { color: var(--color-text-muted); }
    .mp-rest-count { flex:none; font: var(--text-label-s); color: var(--color-text-muted); }
    .mp-rest-add { flex:none; background:none; border:none; cursor:pointer; padding:0;
           font: var(--text-label-m); font-weight:600; color: var(--color-text-accent); }
    /* Скільки бракує на тиждень — і одразу дія (D5). */
    .mp-needs { display:flex; align-items:center; justify-content:space-between; gap: var(--space-base);
           flex-wrap:wrap; margin-top: var(--space-xl); padding: var(--space-base);
           background: var(--color-bg-accent-soft); border-radius: var(--radius-lg);
           font: var(--text-body-m); color: var(--color-text-primary); }
    .mp-needs-cta { background:none; border:none; cursor:pointer; padding:0; flex:none;
           font: var(--text-label-m); font-weight:600; color: var(--color-text-accent); }
    /* Порожній прийом їжі не має займати місце, поки день не редагують:
       у згорнутому стані це були б 24px порожнечі на кожному з семи днів. */
    .card:not(.is-adding) .mp-course:not(:has(.inv-item)) { display:none; }
    /* Меню на тиждень (M1): горизонтальна смужка днів. Активний день —
       темна пігулка, крапка знизу = у дні є страви. Клік прокручує до дня. */
    .mp-weekstrip { display:flex; gap:6px; overflow-x:auto; padding: 4px 0 12px; -webkit-overflow-scrolling:touch; }
    .mp-weekstrip .mp-weekstep { flex:0 0 auto; min-width:0; }
    /* Вікно, у якому видно рівно один тиждень, і доріжка з трьох тижнів.
       `will-change` тут не косметика: без нього перший кадр перетягування
       підвисає на слабкому телефоні. */
    .mp-weekdays-viewport { flex:1 1 auto; min-width:0; overflow:hidden; }
    /* Зсув у ВІДСОТКАХ рахується від власної ширини елемента, а не від
       батька. Доріжка вшир 300%, тож один тиждень — це 33.3333% ЇЇ ширини.
       З -100% вона з'їжджала на всю свою довжину, і смужка ставала порожньою:
       саме так дні й зникли. */
    .mp-weekdays-track { display:flex; width:300%; will-change: transform;
           transform: translate3d(-33.3333%,0,0); }
    .mp-weekdays { display:flex; gap:6px; width:33.3333%; flex:0 0 33.3333%;
           min-width:0; }
    .mp-weekstrip { overflow-x:hidden; }
    /* Картки днів їдуть слідом за датами — паралакс. */
    .mp-days { will-change: transform, opacity; }
    /* flex-basis саме 0, а не auto: з `auto` пігулка стартує від ширини
       свого тексту, тож тиждень із однознач­ними числами («3») давав вужчі
       таблички, ніж із двознач­ними («17»), і смужка мінялася при кожному
       перемиканні тижня. З нульовою базою всі сім однакові завжди. */
    .mp-weekstrip a { flex:1 1 0; min-width:46px; display:flex; flex-direction:column; align-items:center; gap:3px;
           padding:8px 6px 6px; border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           text-decoration:none; background: var(--color-bg-surface); }
    .mp-weekstrip a .wd { font: var(--text-label-s); letter-spacing: var(--tracking-label-s); text-transform:uppercase; color: var(--color-text-muted); }
    .mp-weekstrip a .dn { font: var(--text-heading-m); letter-spacing: var(--tracking-heading-m); color: var(--color-text-primary); }
    /* Обраний день — лаймовий контур. Це НЕ те саме, що «сьогодні» (заливка):
       можна дивитися четвер, стоячи у вівторку, і смужка має це показувати. */
    .mp-weekstrip a.is-picked { border-color: var(--color-bg-accent);
           box-shadow: 0 0 0 1px var(--color-bg-accent); }
    /* Той самий лаймовий контур і на КАРТЦІ обраного дня внизу: верх і низ
       називають один день однією мовою. На десктопі is-selected ще й
       лишає розкритою єдину картку (media-правило вище в файлі), на
       мобільному — лише обводка серед семи видимих. */
    .mp-days .card.is-selected { border-color: var(--color-bg-accent);
           box-shadow: 0 0 0 1px var(--color-bg-accent); }
    .mp-weekstrip a.today { background: var(--color-bg-inverse); border-color: var(--color-bg-inverse); }
    .mp-weekstrip a.today .wd, .mp-weekstrip a.today .dn { color: var(--color-text-inverse); }
    /* padding:0 і flex:none — навмисно: крапка мусить лишатися крапкою, навіть
       якщо на неї колись знову впаде якийсь глобальний клас. */
    .mp-weekstrip a .dot { width:5px; height:5px; padding:0; flex:none; border-radius:50%; background: var(--color-bg-accent); }
    .mp-weekstrip a .dot.is-empty { background: transparent; }
    /* Перехід між тижнями — такі самі плитки, як дні, лише вужчі. На
       мобільному їх нема: там тиждень гортається свайпом по смужці.

       Селектор саме `.mp-weekstep`, а НЕ `a.mp-weekstep`: коли за краєм
       тижнів нема (щоденник не пускає в майбутнє), макрос малює `span`, і з
       прив'язкою до тега той лишався без рамки, без ширини й без ховання на
       мобільному — тобто голим значком, що звисав за край смужки. Виглядало
       це не як «сюди не можна», а як зламана верстка. Плитка тепер спільна,
       а різницю несе `.is-off` (тьмяність і відсутність натиску). */
    .mp-weekstrip .mp-weekstep { flex:0 0 auto; min-width:0; width:30px; padding:0;
           display:flex; align-items:center; justify-content:center;
           border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           background: var(--color-bg-surface); color: var(--color-text-secondary); }
    .mp-weekstrip .mp-weekstep .icon { width:16px; height:16px; }
    @media (max-width: 1023px) {
      .mp-weekstrip .mp-weekstep { display:none; }
      /* Без стрілок дні мають рівно заповнити ширину — тоді горизонтального
         скролу нема й свайп однозначно означає «інший тиждень». */
      .mp-weekstrip { overflow-x:hidden; touch-action: pan-y; }
      .mp-weekstrip a { flex:1 1 0; min-width:0; }
    }
    .mp-weekstrip a.today .dot { background: var(--color-bg-accent); }
    /* Смужки меню-курсу. Беруть вигляд плиток у меню на тиждень (`.mp-weekstrip`
       вище), але живуть за іншим правилом: тут вони не гортають тижні, а ведуть
       до картки дня на цій самій сторінці.

       Тому й прокрутка тут дозволена: днів буває п'ятнадцять, і втиснути їх у
       ширину екрана — значить зробити плитку завузькою для числа. Сім видимих
       за раз (тиждень) — це те, що перемикає рядок тижнів. */
    .course-days, .course-weeks { overflow-x:auto; }
    .course-days a, .course-weeks a { min-width:56px; text-decoration:none; }
    /* Підпис «ДЕНЬ»/«ТИЖДЕНЬ» дрібніший за число — читають тут число. */
    .course-days .wd, .course-weeks .wd { font-size:9px; }

    /* Соцмережі на профілі — пігулки з назвою мережі. Не логотипи: іконки в
       застосунку приходять з Figma одним стилем, і намальований від руки
       значок TikTok стояв би тут чужим (правило дизайн-системи про іконки). */
    .profile-socials { display:flex; flex-wrap:wrap; gap: var(--space-sm);
           margin: var(--space-sm) 0 var(--space-base); }
    /* Кругла кнопка з логотипом. 36 — та сама натискна ціль, що в решти
       круглих кнопок застосунку: менше було б гарно й непопадально. */
    .social-btn { display:inline-flex; align-items:center; justify-content:center;
           width:36px; height:36px; border-radius: var(--radius-full);
           border:1px solid var(--color-border-default); background: var(--color-bg-surface);
           color: var(--color-text-primary); text-decoration:none; }
    .social-btn:hover { border-color: var(--color-text-secondary); background: var(--color-bg-subtle); }
    /* Логотипи намальовані ЗАЛИВКОЮ, тож `stroke` на них не діє — колір несе
       `color`, який успадковує `fill="currentColor"` усередині. */
    .social-btn .icon { width:18px; height:18px; }
    /* Поле соцмережі в налаштуваннях: логотип ліворуч, ввід на решту ширини. */
    .social-field { display:flex; align-items:center; gap: var(--space-sm); margin-top:6px; }
    .social-field-ic { flex:none; display:inline-flex; color: var(--color-text-secondary); }
    .social-field input { margin:0; }

    /* Список фактів на «Твоїх даних»: значок і речення в рядок, без маркерів
       списку — маркер поруч зі значком читався б як два різні маркери. */
    .data-facts { list-style:none; margin:0; padding:0; display:flex; flex-direction:column;
           gap: var(--space-md); }
    .data-facts li { display:flex; align-items:flex-start; gap: var(--space-sm);
           font: var(--text-body-s); color: var(--color-text-secondary); }
    .data-facts .icon { flex:none; margin-top:2px; color: var(--color-text-secondary); }
    /* Картки третього кроку онбордингу — ті самі, що на «Новому рецепті»,
       лише посиланнями: людина має впізнати їх, дійшовши туди вдруге. */
    .onb-magic { display:flex; flex-direction:column; gap: var(--space-md); }
    .onb-magic .new-choice { text-decoration:none; width:100%; }

    /* Галочки в попапі синхронізації меню зі списком. `accent-color` — рівно
       те, для чого він і є: перефарбовує РІДНУ галочку в наш акцент, не
       підміняючи її самописною (а отже й не втрачаючи клавіатуру, читалки й
       рідну поведінку на телефоні). */
    #plan-sync-sheet input[type="checkbox"] { accent-color: var(--color-bg-accent); width:18px; height:18px; }
    #plan-sync-sheet .inv-item { align-items:center; }
    /* Редактор рецепта (R3): верхня панель — Закрити · заголовок · Зберегти. */
    .editor-topbar { display:flex; align-items:center; justify-content:space-between; gap:10px;
           padding-bottom: var(--space-sm); margin-bottom: var(--space-base);
           border-bottom:1px solid var(--color-border-default); }
    .editor-topbar > a { display:inline-flex; color: var(--color-text-primary); flex:none; }
    .editor-topbar-title { text-align:center; display:flex; flex-direction:column; line-height:1.15; min-width:0; }
    .editor-topbar-title strong { font: var(--text-heading-m); }
    .editor-topbar-title span { font: var(--text-body-s); color: var(--color-text-muted); }
    .editor-topbar .btn { flex:none; }
    /* R3 — «Ще про рецепт»: відео й вихід-вага. У макеті їх на видноті нема,
       але поля потрібні тим, хто ними користується, тож не викидаємо, а
       складаємо. Рамки навколо навмисно нема — це не картка, а розділ. */
    .editor-more { margin: var(--space-base) 0; }
    .editor-more > summary { cursor:pointer; font: var(--text-label-m);
           color: var(--color-text-secondary); padding: var(--space-sm) 0;
           list-style:none; display:flex; align-items:center; gap: var(--space-xs); }
    .editor-more > summary::-webkit-details-marker { display:none; }
    .editor-more > summary::before { content:"+"; font: var(--text-body-m); line-height:1;
           color: var(--color-text-muted); }
    .editor-more[open] > summary::before { content:"−"; }
    /* R3 — липка кнопка кроку. Тінь дивиться ВГОРУ (єдиний тіньовий токен у
       системі саме для цього), знизу лишаємо місце під мобільну навігацію —
       інакше кнопка сіла б рівно на неї. */
    .editor-sticky-add { position:sticky; bottom:0; z-index:20;
           padding: var(--space-md) 0 calc(var(--space-md) + env(safe-area-inset-bottom));
           background: var(--color-bg-canvas); box-shadow: var(--shadow-sticky); }
    .editor-sticky-add .btn { width:100%; height:48px; border-radius: var(--radius-full);
           background: var(--color-bg-inverse); color: var(--color-text-inverse);
           font: var(--text-label-l); }
    .editor-sticky-add.is-hidden { visibility:hidden; box-shadow:none; background:none; }
    @media (max-width: 767px) { .editor-sticky-add { bottom:56px; } }
    /* S2 — публічна сторінка рецепта: шапка з маркою та «поділитися», і
       липка кнопка збереження внизу (єдина дія на цій сторінці). */
    .share-topbar { display:flex; align-items:center; gap: var(--space-md);
           padding-bottom: var(--space-md); margin-bottom: var(--space-base);
           border-bottom:1px solid var(--color-border-default); }
    .share-wordmark { font: var(--text-body-l); font-weight:700; text-decoration:none;
           color: var(--color-text-primary); margin-right:auto; }
    .share-sticky { position:sticky; bottom:0; z-index:20; margin: var(--space-xl) 0 0;
           padding: var(--space-md) 0 calc(var(--space-md) + env(safe-area-inset-bottom));
           background: var(--color-bg-canvas); box-shadow: var(--shadow-sticky); }
    /* Дві дії стоять В РЯД і ділять ширину порівну. Стосом вони з'їдали б
       понад сто пікселів висоти на телефоні — на сторінці, де головне все ж
       рецепт. `min-width:0` обов'язковий: без нього flex-елемент не дає собі
       стиснутись менше за власний текст, і довший підпис виштовхує сусіда. */
    /* Дві дії: в ряд, доки обидві влазять рядком тексту, і стосом, коли ні.
       Робить це `flex-basis:200px` разом із `wrap`, а не медіазапит: ламаються
       тут не пікселі екрана, а ПІДПИСИ — «Почати готувати» на 375 px ставало
       трьома рядками й кнопка виростала до 65 px. Стос — той самий патерн, що
       в `.welcome-actions`. Прозора рамка на первинній вирівнює висоти: у
       `.btn.secondary` рамка є, без цього вона стояла на два пікселі вища. */
    /* Перемикач списків обʼєднаної «Кухні». За макетом це не пігулки й не
       підкреслення, а два підписи в рядок: активний — темний і жирний,
       другий — приглушений. Різниця тільки в кольорі й вазі, бо перемикач
       тут не панель керування, а відповідь на питання «який із двох списків
       я зараз дивлюсь». */
    .kitchen-tabs { display:flex; gap: var(--space-2xl); margin: var(--space-md) 0 var(--space-lg);
           align-items:center; flex-wrap:wrap; }
    .kitchen-tab { text-decoration:none; color: var(--color-text-muted);
           font: var(--text-body-m); font-weight:600; padding: var(--space-2xs) 0; }
    .kitchen-tab.active { color: var(--color-text-primary); }
    /* Пігулка області в ряду вкладок «Кухні» — праворуч і компактна, а не
       смуга на всю ширину окремим рядком (рішення власника 2026-08-17).
       margin-left:auto тримає її біля правого краю; на вузькому екрані, де
       поруч із вкладками вона не вміщується, flex-wrap зносить її на другий
       рядок тим самим правим краєм. Меню відкривається від правого краю —
       інакше воно вилазило б за екран (той самий прийом, що в Імпорту). */
    .kitchen-tabs-aside { margin-left:auto; min-width:0; display:flex; justify-content:flex-end; }
    .kitchen-tabs-aside #scope-select { flex:none; max-width:100%; }
    .kitchen-tabs-aside #scope-select-toggle { min-width:0; width:auto; }
    .kitchen-tabs-aside .scope-select-menu { left:auto; right:0; }
    /* На телефоні пігулка все ж на всю ширину (рішення власника 2026-08-18).
       Компактна вона там вигравала рівно нічого: сусідів у рядку немає —
       вкладки й так забирають його цілком, — а лаймовий острівець із назвою
       посеред порожнього рядка читався випадковим. Повна ширина повертає
       їй роль рядка стану: «оце список, який ти дивишся».

       Медіазапит стоїть НИЖЧЕ базових правил і повторює їхні селектори з
       `#scope-select`: за рівної ваги виграє те, що нижче, а з ідентифікатором
       у базовому правилі коротший селектор програв би мовчки. */
    @media (max-width: 767px) {
      .kitchen-tabs-aside { flex:1 0 100%; justify-content:stretch; }
      .kitchen-tabs-aside #scope-select { flex:1 1 auto; display:block; width:100%; }
      .kitchen-tabs-aside #scope-select-toggle { width:100%; }
      /* Меню теж на всю ширину — інакше воно висіло б вузьким стовпчиком
         біля правого краю під смугою на весь екран. */
      .kitchen-tabs-aside .scope-select-menu { left:0; right:0; }
    }
    .share-sticky { display:flex; flex-wrap:wrap; gap: var(--space-md); }
    .share-sticky .btn { flex:1 1 200px; min-width:0; justify-content:center; text-align:center;
           min-height:44px; }
    /* Саме `:not(.secondary)`: рівна вага селекторів означає, що правило нижче
       по файлу перемагає, і проста `.share-sticky .btn { border:… }` знімала
       рамку з вторинної — та переставала читатись кнопкою взагалі. */
    .share-sticky .btn:not(.secondary) { border:1px solid transparent; }
    /* R3 — підписи полів редактора: дрібні великі літери, як у макеті.
       Тільки label[for]: .photo-dropzone і чекбокси теж <label>, їх не чіпаємо. */
    .recipe-editor > label[for] { font: var(--text-label-s); letter-spacing:.08em;
           text-transform:uppercase; color: var(--color-text-muted); font-weight:600;
           margin: var(--space-base) 0 var(--space-2xs); }
    /* R3 — метадані рецепта пігулками: пігулка показує значення й відкриває
       свій bottom-sheet. Нейтральна заливка, як у Meta Chip; сигнальний колір
       живе тільки в крапці складності, сама пігулка не фарбується. */
    .r3-chips { display:flex; flex-wrap:wrap; gap: var(--space-sm); margin: var(--space-md) 0 var(--space-lg); }
    .r3-chip { display:inline-flex; align-items:center; gap: var(--space-sm); cursor:pointer;
           background: var(--color-bg-subtle); color: var(--color-text-secondary);
           border:none; border-radius: var(--radius-full); padding: var(--space-sm) var(--space-base);
           font: var(--text-label-m); font-weight:600; max-width:100%; }
    .r3-chip .icon { width:14px; height:14px; flex:none; color: var(--color-text-secondary); }
    /* R3 — у макеті рядок пігулок неоднорідний, і це не випадковість:
       заповнена пігулка = у рецепта вже є це значення (час, складність,
       видимість), обведена = або кількість, яку читають числом («24 порції»),
       або запрошення додати («+ Теги»). Ми малювали всі шість однаково
       заливкою, тож «Порції» без числа виглядали як заповнене поле, а «Теги»
       — як стан, а не як дія. */
    .r3-chip.is-outline { background:none; border:1px solid var(--color-border-default);
           color: var(--color-text-primary); }
    .r3-chip.is-outline .icon { color: var(--color-text-primary); }
    .r3-chip-plus { color: var(--color-text-muted); font-weight:600; }
    .r3-chip-val { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .r3-dot { width:9px; height:9px; flex:none; padding:0; border-radius:50%; background: var(--color-text-muted); }
    .r3-dot.easy { background: var(--color-signal-good); }
    .r3-dot.medium { background: var(--color-signal-moderate); }
    .r3-dot.hard { background: var(--color-signal-poor); }
    .chip-sheet-body { padding: 0 var(--space-xl) var(--space-xl); display:flex; flex-direction:column; gap: var(--space-md); }
    .chip-presets { display:flex; flex-wrap:wrap; gap: var(--space-sm); }
    /* R4 — дії над списком інгредієнтів: один рядок, що переноситься. */
    .ing-actions { display:flex; flex-wrap:wrap; gap: var(--space-sm); margin: var(--space-sm) 0; }
    /* R8 — поле розділу в рядку інгредієнта: вужче за назву, бо заповнюється
       рідко й здебільшого повторюється з попереднього рядка. */
    .ingredient-row .ing-section { flex:0 1 120px; min-width:90px; }
    /* R5 — картка пари-дублікатів у підтвердженні злиття. */
    .merge-group { border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           padding: var(--space-md); margin-bottom: var(--space-sm); display:flex;
           flex-direction:column; gap: var(--space-2xs); }
    .merge-line { font: var(--text-body-s); color: var(--color-text-muted); }
    .merge-becomes { font: var(--text-body-m); color: var(--color-text-primary); margin-top: var(--space-2xs); }
    .merge-skip { display:flex; align-items:center; gap: var(--space-sm); margin:0;
           font: var(--text-body-s); font-weight:400; color: var(--color-text-secondary); }
    .merge-skip input { width:auto; }
    /* R9 — «які інгредієнти тут використані»: чипи під текстом кроку.
       Обране заливається лісовим, як і решта вибраних пігулок у системі. */
    .step-ings { display:flex; flex-wrap:wrap; gap: var(--space-2xs); flex-basis:100%; margin-top: var(--space-2xs); }
    .step-ing-chip { border:1px solid var(--color-border-default); background: var(--color-bg-surface);
           color: var(--color-text-secondary); border-radius: var(--radius-full);
           padding: 2px var(--space-sm); font: var(--text-label-s); cursor:pointer; }
    .step-ing-chip.is-on { background: var(--color-bg-inverse); color: var(--color-text-inverse);
           border-color: var(--color-bg-inverse); }
    /* R7 — перегляд розібраного списку: під кожним рядком видно оригінал,
       а рядки без розпізнаної кількості підсвічені (їх треба глянути). */
    .paste-review-item { border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           padding: var(--space-md); margin-bottom: var(--space-sm); }
    .paste-review-item.is-warn { background: var(--color-bg-accent-soft); border-color: var(--color-bg-accent); }
    .paste-review-head { font: var(--text-body-m); font-weight:600; color: var(--color-text-primary); }
    .paste-review-src { font: var(--text-body-s); color: var(--color-text-muted); margin-top:2px; }
    /* Дубль у розібраному списку: рішення про злиття приймається тут же, під
       рядком, а не в окремому аркуші — видно, ЩО саме зливається (R5+R7). */
    .paste-review-dup { display:flex; flex-wrap:wrap; align-items:center; gap: var(--space-sm);
           margin-top: var(--space-sm); }
    .paste-review-dup input { flex:1; min-width:140px; font-size:13px; padding:6px 10px; }
    /* Підказка для чужого ШІ — навмисно тиха: на «Новому рецепті» головна дія
       одна, і це «Розпізнати», а не запасний шлях унизу сторінки. */
    .ai-prompt-block { border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           padding: var(--space-md); margin-top: var(--space-2xl); }
    .ai-prompt-summary { display:flex; align-items:center; gap: var(--space-sm); cursor:pointer;
           font: var(--text-body-m); font-weight:600; color: var(--color-text-primary); list-style:none; }
    .ai-prompt-summary::-webkit-details-marker { display:none; }
    .ai-prompt-text { font-family:inherit; font-size:13px; width:100%; }
    /* D4 — покупки на десктопі: список ліворуч, колонка з формою й довідками
       праворуч. До 1024px колонки нема, форма живе в шиті (G7c). */
    #add-form-holder { display:none; }
    .list-side { display:none; }
    .list-side-card { padding: var(--space-base); margin:0; }
    .list-side-row { display:flex; align-items:baseline; justify-content:space-between;
           gap: var(--space-md); padding: var(--space-2xs) 0; font: var(--text-body-m); }
    .list-side-row a { color: var(--color-text-primary); text-decoration:none; }
    @media (min-width: 1024px) {
      .list-layout { display:grid; grid-template-columns: minmax(0, 1fr) 340px;
             gap: var(--space-2xl); align-items:start; }
      .list-side { display:flex; flex-direction:column; gap: var(--space-base);
             position:sticky; top: var(--space-base); }
      /* Форма вже стоїть у колонці — тригер шита тут зайвий. */
      .add-trigger { display:none; }
      /* У картці колонки їй не потрібні відступи шита. */
      .list-side .inv-add.is-sheet { padding:0; }
    }
    /* Редактор рецепта (R3): ключові метадані — компактним рядком пігулок. */
    .editor-chips { display:flex; flex-wrap:wrap; gap:8px; margin: 10px 0 16px; }
    .editor-chip { display:flex; flex-direction:column; gap:2px; flex:1 1 130px; min-width:0;
           border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           padding:8px 12px; background: var(--color-bg-surface); }
    .editor-chip > label { font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           text-transform:uppercase; color: var(--color-text-muted); margin:0; }
    .editor-chip > input, .editor-chip > select { border:none; background:none; padding:0; margin:0;
           width:100%; font: var(--text-body-m); color: var(--color-text-primary); }
    .editor-chip > input:focus, .editor-chip > select:focus { outline:none; }
    /* Редактор рецепта (R3): фото як велика дропзона замість голого file-input. */
    /* R10 — це вже <button>, а не <label>: тап відкриває аркуш із джерелами,
       а не системний діалог. Звідси width/font — кнопка їх не успадковує. */
    .photo-dropzone { display:flex; flex-direction:column; align-items:center; justify-content:center;
           gap:6px; text-align:center; padding: var(--space-2xl) var(--space-base);
           margin: var(--space-sm) 0 var(--space-base); border:1.5px dashed var(--color-border-strong);
           border-radius: var(--radius-lg); background: var(--color-bg-subtle); cursor:pointer;
           color: var(--color-text-secondary); width:100%; box-sizing:border-box; font: var(--text-body-m); }
    .photo-dropzone .icon { width:28px; height:28px; color: var(--color-text-secondary); }
    .photo-dropzone-title { font: var(--text-body-l); font-weight:600; color: var(--color-text-primary); }
    .photo-dropzone-sub { font: var(--text-body-s); color: var(--color-text-muted); }
    .photo-dropzone.has-file { border-style:solid; border-color: var(--color-bg-accent); padding: var(--space-md); }
    /* Прев'ю обкладинки. Розмір ЯВНИЙ, а не `auto`: у цьому колонковому
       флекс-контейнері картинка з auto-розміром отримувала бокс 0×0 —
       завантажена (naturalWidth ≠ 0), видима за стилями, і нульова на екрані.
       Саме тому після вибору файлу було видно лише його ім'я. */
    .photo-dropzone img { display:none; width:100%; height:200px; flex:none;
           object-fit:cover; border-radius: var(--radius-md); }
    /* Новий рецепт — вибір способу (R1): чисті картки-варіанти. */
    .new-choices { display:flex; flex-direction:column; gap: var(--space-sm); margin: var(--space-base) 0; }
    .new-choice { display:flex; align-items:center; gap: var(--space-base); width:100%; text-align:left;
           padding: var(--space-base); border:1.5px solid var(--color-border-default); border-radius: var(--radius-lg);
           background: var(--color-bg-surface); color: var(--color-text-primary); cursor:pointer; }
    /* Акордеон вибору способу. Картка — це `.new-choice-item`: заголовок
       (`.new-choice`) плюс панель, що розкривається. Висота анімується через
       grid-template-rows 0fr→1fr: це єдиний спосіб плавно розкрити вміст
       НЕВІДОМОЇ висоти, не вимірюючи його в JS (max-height доводиться
       вгадувати, і на довгому тексті анімація або рветься, або гальмує). */
    .new-choice-item { border:1.5px solid var(--color-border-default);
           border-radius: var(--radius-lg); background: var(--color-bg-surface);
           overflow:hidden; transition: border-color .2s ease; }
    .new-choice-item .new-choice { border:none; background:none; border-radius:0; width:100%; }
    .new-choice-panel { display:grid; grid-template-rows: 0fr;
           transition: grid-template-rows .26s cubic-bezier(.22,.61,.36,1); }
    .new-choice-panel-in { overflow:hidden; }
    .new-choice-item.is-open { border-color: var(--color-bg-accent); }
    .new-choice-item.is-open .new-choice-panel { grid-template-rows: 1fr; }
    .new-choice-item.is-open .new-choice-panel-in { padding: 0 var(--space-base) var(--space-base); }
    /* Сусіди стискаються, поки одна картка відкрита: підпис ховається, картка
       стає нижчою — саме це й читається як «одна виштовхує інші». */
    .new-choices.has-open .new-choice-item:not(.is-open) .new-choice { padding: var(--space-md) var(--space-base); }
    .new-choices.has-open .new-choice-item:not(.is-open) .new-choice-sub { display:none; }
    .new-choices.has-open .new-choice-item:not(.is-open) .new-choice-ic { width:34px; height:34px; }
    .new-choice { transition: padding .24s cubic-bezier(.22,.61,.36,1); }
    .new-choice-ic { transition: width .24s ease, height .24s ease; }
    .new-choice-item:first-child { border-color: var(--color-bg-accent); background: var(--color-bg-accent-soft); }
    .new-choice-item:first-child .new-choice-ic { background: var(--color-bg-accent); color: var(--color-text-on-accent); }

    .new-choice.active { border-color: var(--color-bg-accent); }
    .new-choice-ic { flex:none; width:44px; height:44px; border-radius: var(--radius-md); display:flex;
           align-items:center; justify-content:center; background: var(--color-bg-subtle); color: var(--color-text-primary); }

    .new-choice-ic .icon { width:22px; height:22px; }
    .new-choice-body { display:flex; flex-direction:column; gap:2px; min-width:0; }
    .new-choice-title { font: var(--text-body-l); font-weight:600; }
    .new-choice-sub { font: var(--text-body-s); color: var(--color-text-muted); }
    .choice-panel { margin: 0 0 var(--space-base); }
    /* Groups list (G1). */
    .group-card { display:block; text-decoration:none; color: var(--color-text-primary);
           border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           padding: var(--space-base); margin-bottom: var(--space-md); background: var(--color-bg-surface); }
    .group-card.is-owner { background: var(--color-bg-accent-soft); border-color: var(--color-bg-accent); }
    .group-card-top { display:flex; align-items:center; justify-content:space-between; gap: var(--space-md); }
    .group-card-name { font: var(--text-heading-m); letter-spacing: var(--tracking-heading-m); }
    .group-card-count { font: var(--text-body-s); color: var(--color-text-muted); white-space:nowrap; }
    .group-card-shares { font: var(--text-body-s); color: var(--color-text-secondary); margin-top: var(--space-2xs); }
    /* Сільпо-експорт: лаймовий аутлайн, щоб виділявся поруч з History. */
    .silpo-btn { display:inline-flex; align-items:center; gap: var(--space-xs); background:transparent;
           border:1.5px solid var(--color-bg-accent); color: var(--color-text-accent); text-decoration:none;
           padding: 8px 14px; border-radius: var(--radius-full); font: var(--text-label-m); font-weight:600; height:38px; }
    .silpo-btn .icon { width:16px; height:16px; }
    .filter-apply { display:block; width:calc(100% - 2*var(--space-xl)); margin: var(--space-md) var(--space-xl) 0; }
    /* Липкий низ аркуша: головна дія їде разом зі скролом, а на самому низу
       стає на своє місце в потоці. `sticky` (а не `fixed`) саме тому — fixed
       вирвав би кнопку з аркуша й лишив висіти поверх сторінки навіть там, де
       аркуш закінчився.
       Працює за рахунок того, що скролиться сам `.sheet`, а обгортка — його
       нащадок: sticky меряється відносно найближчого предка зі скролом.
       Фон обовʼязковий і саме на всю ширину — інакше опції пропливали б під
       кнопкою й читались крізь її бокові відступи. Тінь спрямована ВГОРУ
       (`--shadow-sticky`, 0 -4px 20px) — це той самий токен і та сама роль,
       що в решти липких панелей: сказати, що під кнопкою ще є вміст. */
    .sheet-apply { position:sticky; bottom:0; z-index:2;
           background: var(--color-bg-surface);
           padding-bottom: var(--space-base);
           /* Компенсуємо власний `margin-top` кнопки: без цього між вмістом і
              липкою смугою лишалась прозора щілина, крізь яку було видно, як
              під нею проїжджає текст. */
           padding-top: var(--space-2xs);
           box-shadow: var(--shadow-sticky); }
    /* М'який перехід над смугою: без нього рядок, що наполовину заїхав під
       кнопку, обрізається ножем — виглядає як зламана верстка, а не як
       панель поверх списку. Градієнт малюємо ПСЕВДОЕЛЕМЕНТОМ над смугою,
       щоб не чіпати її власний фон (він мусить лишатись щільним). */
    .sheet-apply::before { content:""; position:absolute; left:0; right:0; bottom:100%;
           height: var(--space-base); pointer-events:none;
           background: linear-gradient(to top, var(--color-bg-surface), transparent); }
    /* `is-conditional` — кнопки нема, поки нічого не змінили. Так у фільтрах:
       вони застосовуються НАЖИВО, тож до першої зміни кнопка нічого не робила
       б — лише затуляла опції, заради яких аркуш і відкрили (рішення власника
       2026-08-17). В інших аркушах (вибір техніки) кнопка — саме підтвердження
       вибору, тож там вона видима завжди, і модифікатора не отримує.
       `visibility`, а не `display:none`: місце під смугу лишається зайнятим,
       тому поява кнопки не смикає список опцій під нею. */
    .sheet-apply { transition: opacity .15s ease; }
    .sheet-apply.is-conditional:not(.is-on) { visibility:hidden; opacity:0; }
    /* Доскролили до кінця — тінь зникає: вона означає «під кнопкою ще є
       вміст», і на дні аркуша була б неправдою. Клас вішає скрипт аркуша:
       першу спробу зробили на `animation-timeline: scroll()`, і хоч браузер
       його підтримує, для sticky-елемента діапазон доводиться вгадувати —
       тінь не згасала. Два рядки JS роблять те саме передбачувано. */
    .sheet-apply.is-landed { box-shadow:none; }
    /* C1/D1 — пошук за круглою кнопкою в шапці на мобільному, полем у рядку
       на десктопі. Селектор із ДВОМА класами обовʼязковий: `.icon-btn`
       оголошений НИЖЧЕ, специфічність була б однакова, і його `display:grid`
       виграв би за порядком — кнопка світилася б і на десктопі. Та сама
       пастка вже одного разу зламала кнопку «Новий рецепт». */
    .icon-btn.feed-search-toggle { display:none; }
    /* D1 — на десктопі кнопка з підписом, на мобільному лишається кружком:
       там ширини на підпис немає, а вкладки важливіші. */
    .new-recipe-label { display:none; }
    @media (min-width: 768px) {
      /* Селектор із ДВОМА класами обовʼязковий: `.icon-btn` оголошений нижче
         за цей блок, а специфічність у них була б однакова — і його
         `display:grid` вигравав би за порядком. Саме тому підпис ставав
         другим рядком сітки й вилазив за межі кнопки (а grid ще й
         «блокифікує» дітей, через що `display:inline` не спрацьовував). */
      .icon-btn.new-recipe-btn { display:inline-flex; align-items:center; justify-content:center;
             width:auto; max-width:100%; border-radius: var(--radius-full);
             padding: 0 var(--space-base); gap: var(--space-sm); }
      .icon-btn.new-recipe-btn .new-recipe-label { display:inline; font: var(--text-label-m);
             white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
    }
    /* Проміжний ярус: місця вже мало, але до мобільного вигляду ще далеко.
       Замість того щоб душити пошук у кружок (а він так і робив — поле
       стискалось до 40px і ставало нечитабельним), прибираємо ПІДПИСИ:
       навігація лишається іконками, «Новий рецепт» стає круглою кнопкою.
       Пошук від цього отримує назад ~300px.

       Межа саме 1440, і вона ВИМІРЯНА, а не вгадана: при показаних підписах
       на 1300px пошуку лишалось рівно його мінімальні 200px (тобто «не
       схлопнувся» — але й не поле), і лише з 1440 він отримує 416. Перший
       раз я поставив сюди 1199 зі стелі — саме тому на 1300 усе ще було
       тісно.

       Блок стоїть ПІСЛЯ ярусу 768px+ навмисно: специфічність однакова, тож
       виграє те, що нижче. Це вже четвертий випадок цієї пастки в цьому
       файлі — перевіряй порядок, перш ніж писати перебиття. */
    @media (min-width: 768px) and (max-width: 1439px) {
      .bnav-item span { display: none; }
      .bnav-item { padding: var(--space-sm); }
      .icon-btn.new-recipe-btn { width: 44px; padding: 0; }
      .icon-btn.new-recipe-btn .new-recipe-label { display: none; }
      .bnav-brand { margin-right: var(--space-sm); }
    }
    @media (max-width: 767px) {
      .icon-btn.feed-search-toggle { display:grid; }
      .feed-search-input.is-collapsed { display:none; }
    }
    .feed-search-input { flex:1 1 260px; width:auto; min-width:0; height:44px; box-sizing:border-box;
           border-radius: var(--radius-full); padding:0 18px; }
    /* Sort trigger (feed header) + Sort sheet radio rows. */
    .sort-trigger { display:inline-flex; align-items:center; gap:6px; height:44px; padding:0 16px; border:none; cursor:pointer;
           border-radius: var(--radius-full); background: var(--color-bg-subtle); color: var(--color-text-primary);
           font: var(--text-label-m); white-space:nowrap; }
    .sort-trigger .icon { width:15px; height:15px; }
    /* Тригер фільтрів підсвічується лаймом, коли є активні фільтри (дизайн). */
    .sort-trigger.has-active { background: var(--color-bg-accent); color: var(--color-text-on-accent); }
    /* Той самий тригер, але рядком усередині шита («Обладнання» у фільтрах).
       Горизонтальний відступ рахуємо як у `.filter-apply`: у самого `.sheet`
       падінгу нема — його дає кожен блок окремо (`--space-xl`), тож голий
       `width:100%` розпирав пігулку від краю до краю, і вона єдина в шиті
       стояла ширшою за решту полів.

       Стоїть ПІСЛЯ `.sort-trigger` навмисно: специфічність однакова, і
       `display` вирішується порядком — вище правило програло б.  */
    .sheet-trigger { display:flex; justify-content:space-between;
           width: calc(100% - 2*var(--space-xl)); margin: 0 var(--space-xl) var(--space-sm); }
    .sort-option { display:flex; align-items:flex-start; gap: var(--space-md); padding: var(--space-md) var(--space-xl); cursor:pointer; }
    .sort-option input { position:absolute; opacity:0; width:1px; height:1px; }
    .sort-radio { width:20px; height:20px; border-radius:50%; flex:none; margin-top:2px; position:relative;
           border:2px solid var(--color-border-strong); }
    .sort-option input:checked ~ .sort-radio { border-color: var(--color-bg-accent); }
    .sort-option input:checked ~ .sort-radio::after { content:""; position:absolute; inset:3px; border-radius:50%; background: var(--color-bg-accent); }
    .sort-option:has(input:checked) { background: var(--color-bg-subtle); }
    .sort-o-title { display:block; font: var(--text-body-l); color: var(--color-text-primary); }
    .sort-o-desc { display:block; font: var(--text-body-s); color: var(--color-text-muted); }
    /* Active-filter chips shown above the results — each removable. */
    .active-filters { display:flex; flex-wrap:wrap; gap:6px; align-items:center; margin:0 0 10px; }
    .active-filters:empty { display:none; }
    .active-chip { display:inline-flex; align-items:center; gap:5px; padding:6px 10px; border:none; cursor:pointer;
           border-radius: var(--radius-full); background: var(--color-bg-inverse); color: var(--color-text-inverse);
           font: var(--text-label-s); letter-spacing: var(--tracking-label-s); }
    .active-chip .icon { width:13px; height:13px; }
    .active-chip-clear { background:none; border:none; color: var(--color-text-muted); cursor:pointer;
           font: var(--text-label-m); text-decoration:underline; padding:6px 4px; }
    /* Numeric range filters (max time / max kcal / min protein). */
    .range-row { display:flex; gap:8px; margin-bottom:12px; padding:0 var(--space-xl); }
    .range-field { flex:1; display:flex; flex-direction:column; gap:4px;
           font: var(--text-label-s); color: var(--color-text-secondary); }
    .range-field input { width:100%; padding:8px 10px; border-radius: var(--radius-md);
           border:1px solid var(--color-border-default); background: var(--color-bg-canvas);
           color: var(--color-text-primary); font: var(--text-body-m); }
    .range-field input:focus { outline:none; border-color: var(--color-border-focus); }
    /* Auth entry (login / register) — forest hero from the design system:
       Lora headline, lime value line, lime pill CTA. Dark panel regardless of
       theme (it's a brand surface), so it uses forest primitives directly. */
    .auth-panel { background: var(--forest-900); border-radius: var(--radius-2xl); padding: 40px 28px; }
    .auth-title { font: var(--text-editorial-display); letter-spacing:-.01em; color: var(--sand-50); margin:0 0 12px; }
    .auth-sub { font: var(--text-body-l); color: var(--lime-500); margin:0 0 28px; }
    .auth-panel label { display:block; color: var(--sand-300); font: var(--text-label-m); margin-top:14px; }
    .auth-panel input[type="email"], .auth-panel input[type="password"] {
           width:100%; box-sizing:border-box; padding:13px 14px; margin-top:6px;
           background: var(--forest-800); border:1px solid var(--forest-700); border-radius: var(--radius-md);
           color: var(--sand-50); font: var(--text-body-m); }
    .auth-panel input[type="email"]:focus, .auth-panel input[type="password"]:focus {
           outline:none; border-color: var(--lime-500); }
    .auth-cta { width:100%; padding:16px; margin-top:22px; border:none; border-radius: var(--radius-full);
           background: var(--lime-500); color: var(--forest-900); font: var(--text-label-l); font-weight:700; cursor:pointer; }
    .auth-google { display:flex; align-items:center; justify-content:center; gap:10px; width:100%;
           box-sizing:border-box; padding:13px; margin-top:14px; border:1px solid var(--forest-700);
           border-radius: var(--radius-full); background:transparent; color: var(--sand-50); text-decoration:none;
           font: var(--text-label-m); }
    /* Перемикач групування списку (покупки / комора). Той самий сегментний
       вигляд, що й у вкладок стрічки, лише вужчий: це не навігація, а спосіб
       дивитись на той самий список. */
    .group-switch { display:inline-flex; gap:2px; padding:2px; margin: var(--space-sm) 0 var(--space-base);
           background: var(--color-bg-subtle); border-radius: var(--radius-full); }
    .group-switch .seg { padding: var(--space-xs) var(--space-md); border-radius: var(--radius-full);
           font: var(--text-label-s); color: var(--color-text-secondary); text-decoration:none; }
    .group-switch .seg.active { background: var(--color-bg-surface); color: var(--color-text-primary); }
    /* Кнопка Apple — та сама форма, що й Google, лише щільніший відступ між
       ними: два способи входу читаються як пара, а не як два різні блоки. */
    .auth-apple { margin-top:10px; }
    .auth-alt { display:block; text-align:center; margin-top:22px; color: var(--lime-500);
           text-decoration:none; font: var(--text-label-m); }
    .auth-error { color:#ff9a9a; font: var(--text-body-s); margin:0 0 16px; }
    .auth-legal { text-align:center; margin-top:20px; font: var(--text-body-s); color: var(--color-text-muted); }
    .auth-legal a { color: var(--color-text-secondary); }
    /* F2/F3/F4 — вхід, реєстрація й скидання пароля за макетами «First run».
       Це НЕ брендова forest-панель вище (та лишається промо-блоком у share /
       group_join): макет малює звичайну канву, білі поля з uppercase-міткою,
       круглі соц-кнопки і темно-зелений CTA на всю ширину.
       Максимальна ширина — щоб на десктопі форма не розтягувалась на весь
       екран: макет мобільний (393px), і поле вводу шириною 1200px виглядало б
       поламаним, а не «адаптованим». */
    .auth-screen { max-width:393px; margin:0 auto; padding-bottom: var(--space-3xl); }
    .auth-bar { display:flex; align-items:center; gap: var(--space-sm);
           min-height:44px; margin: var(--space-sm) 0 var(--space-xl); }
    .auth-bar-back { width:44px; height:44px; flex:none; display:flex; align-items:center;
           justify-content:center; color: var(--color-text-primary); text-decoration:none; }
    /* Заголовок центрований у смузі, а не «після стрілки»: кнопка назад має
       фіксовану ширину, тож дзеркальний спейсер тримає текст рівно посередині
       незалежно від його довжини. */
    .auth-bar-title { flex:1; text-align:center; font: var(--text-body-l); font-weight:600;
           color: var(--color-text-primary); }
    .auth-bar-spacer { width:44px; flex:none; }
    .auth-social { display:flex; align-items:center; justify-content:center; gap: var(--space-md);
           width:100%; box-sizing:border-box; min-height:56px; padding: var(--space-base);
           border:1px solid var(--color-border-default); border-radius: var(--radius-full);
           background: var(--color-bg-surface); color: var(--color-text-primary);
           text-decoration:none; font: var(--text-label-l); }
    .auth-social + .auth-social { margin-top: var(--space-sm); }
    /* Роздільник «АБО»: дві лінії й слово між ними — саме так у макеті, і саме
       він відділяє «увійти чужим акаунтом» від «завести свій». */
    .auth-or { display:flex; align-items:center; gap: var(--space-base); margin: var(--space-xl) 0; }
    .auth-or::before, .auth-or::after { content:""; flex:1; height:1px; background: var(--color-border-default); }
    .auth-or span { font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           text-transform:uppercase; color: var(--color-text-muted); }
    .auth-field { margin-bottom: var(--space-base); }
    .auth-field label { display:block; font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           text-transform:uppercase; color: var(--color-text-muted); margin-bottom: var(--space-xs); }
    /* Селектор за типами, а не загальний `input`: у формі є ще чекбокс згоди,
       і розтягнутий на всю ширину чекбокс — класична аварія такого правила. */
    .auth-field input[type="email"], .auth-field input[type="password"],
    .auth-field input[type="text"] {
           width:100%; box-sizing:border-box; min-height:52px; padding: var(--space-md) var(--space-base);
           border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           background: var(--color-bg-surface); color: var(--color-text-primary);
           font: var(--text-body-m); }
    .auth-field input:focus { outline:none; border-color: var(--color-border-focus); }
    .auth-submit { display:flex; align-items:center; justify-content:center; width:100%;
           box-sizing:border-box; min-height:56px; margin-top: var(--space-lg); padding: var(--space-base);
           border:none; border-radius: var(--radius-full); cursor:pointer;
           background: var(--color-bg-inverse); color: var(--color-text-inverse);
           font: var(--text-label-l); text-decoration:none; }
    .auth-note { font: var(--text-body-s); color: var(--color-text-muted); margin: var(--space-base) 0 0; }
    .auth-note a { color: var(--color-text-accent); }
    .auth-link { display:block; text-align:center; margin-top: var(--space-lg);
           color: var(--color-text-primary); font: var(--text-label-m); }
    .auth-lead { font: var(--text-heading-l); letter-spacing: var(--tracking-heading-l);
           color: var(--color-text-primary); margin: var(--space-sm) 0 var(--space-base); }
    .auth-body { font: var(--text-body-l); color: var(--color-text-secondary); margin:0 0 var(--space-xl); }
    /* «Нічого не прийшло?» — тиха довідка, а не помилка: приглушена поверхня
       без кольорової рамки. Саме тут живе пояснення, чому ми не кажемо, чи
       існує акаунт; це не дрібний шрифт заради місця, а відповідь на питання,
       яке інакше читається як несправність. */
    .auth-help { background: var(--color-bg-subtle); border-radius: var(--radius-lg);
           padding: var(--space-base); margin-bottom: var(--space-xl); }
    .auth-help-label { display:block; font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           text-transform:uppercase; color: var(--color-text-muted); margin-bottom: var(--space-xs); }
    .auth-help p { margin:0; font: var(--text-body-s); color: var(--color-text-secondary); }
    /* Повторна відправка: та сама форма, що й соц-кнопка (обведена пігулка) —
       це дія-очікування, а не головна дія екрана. Заблокована, поки біжить
       відлік: кнопка, яку тиснуть щосекунди, лише витрачає ліміт. */
    .auth-resend { width:100%; box-sizing:border-box; min-height:56px;
           border:1px solid var(--color-border-default); border-radius: var(--radius-full);
           background:transparent; color: var(--color-text-primary); font: var(--text-label-l);
           cursor:pointer; }
    .auth-resend[disabled] { color: var(--color-text-muted); cursor:default; }
    .mp-day-btn { display:block; width:100%; text-align:left; padding:12px 14px; margin-bottom:8px;
           background: var(--bg); border:1px solid var(--border); border-radius:10px; color: var(--text);
           font-size:15px; cursor:pointer; }
    .mp-day-btn:hover { border-color: var(--accent); }
    .mp-day-btn.is-today { border-color: var(--color-text-accent); font-weight:600; }
    /* Курси/прийоми їжі в межах дня меню. */
    .mp-course { margin-bottom:14px; }
    .mp-course-head { display:flex; align-items:baseline; justify-content:space-between; gap:8px; margin:0 0 6px; }
    .mp-course-title { font: var(--text-label-s); letter-spacing: var(--tracking-label-s); text-transform:uppercase;
           color: var(--color-text-secondary); }
    /* Нотатка на день меню (не рецепт — нагадування). */
    .mp-note { display:flex; align-items:center; gap:8px; padding:8px 12px; margin-bottom:8px;
           background: color-mix(in srgb, var(--color-bg-accent) 9%, var(--card)); border:1px solid var(--border);
           border-radius: var(--radius-md); font: var(--text-body-s); color: var(--color-text-secondary); }
    .mp-note .icon { flex:none; color: var(--color-text-accent); }
    .mp-note-text { flex:1; word-break:break-word; }
    .drag-handle { cursor: grab; touch-action: none; padding: 4px 8px; margin: -4px 0; color: var(--muted);
           flex-shrink: 0; font-size: 15px; user-select: none; }
    /* Ручка перетягування на ВСЮ висоту блока.
       `align-self:stretch` тут не працює, і це не дрібниця: обидва рядки мають
       `flex-wrap`, а stretch тягне елемент лише до висоти СВОЄЇ лінії — у
       кроці з фото це виходило 57px при висоті блока 119. Тому ручка
       абсолютна, а рядок отримує ліве поле під неї.

       Крапки МАЛЮЄМО повторюваним фоном, а не розтягуємо гліф «⠿»:
       масштабований символ розмивається й на високому блоці виглядає як
       розтягнута картинка. */
    .ingredient-row, .step-row { position: relative; padding-left: 28px; }
    .ingredient-row > .drag-handle, .step-row > .drag-handle {
           position:absolute; left:0; top:0; bottom:0; width:20px;
           margin:0; padding:0; font-size:0; color: var(--color-text-muted);
           border:1px solid var(--color-border-default); border-radius: var(--radius-md);
           background-color: var(--color-bg-subtle);
           background-image: radial-gradient(currentColor 1.4px, transparent 1.5px);
           background-size: 7px 7px; background-position: center;
           background-repeat: repeat-y; background-clip: padding-box; }
    /* Дії під списком інгредієнтів починаються від тієї самої лівої межі, що
       й самі назви, а не від ручки. */
    .ing-actions, .steps-actions { padding-left: 28px; }
    .swipe-wrap.drag-active { position: relative; z-index: 10; }
    /* На час перетягування обрізання потрібне: інакше рядок, що їде вбік,
       вилазив би за межі своєї картки. */
    .swipe-wrap:has(.dragging), .swipe-wrap.drag-active { overflow: hidden; }
    .swipe-wrap.drag-active .swipe-row { box-shadow: 0 6px 16px rgba(0,0,0,.5); transition: none; }
    .inv-item.drag-active { position: relative; z-index: 10; box-shadow: 0 6px 16px rgba(0,0,0,.5); }
    .ingredient-row.drag-active, .step-row.drag-active { position: relative; z-index: 10;
           box-shadow: 0 6px 16px rgba(0,0,0,.5); background: var(--card); border-radius: 8px; }
    .product-photo-btn { cursor: pointer; padding: 4px 6px; margin: -4px 0; color: var(--muted); flex-shrink: 0;
           font-size: 15px; background: none; border: none; touch-action: none; -webkit-touch-callout: none;
           user-select: none; }
    .product-photo-btn.has-photo { color: var(--accent); }
    /* Кнопка 📷 у формі додавання — підсвічуємо, коли фото вже прикріплено. */
    .inv-add-photo-btn.photo-attached { color: var(--accent); border-color: var(--accent); }
    /* Смарт кукінг-таймер: кнопки на кроках + плаваюча панель активних таймерів. */
    .step-timer-btn { cursor:pointer; border:1px solid var(--border); background:var(--card); color:var(--muted);
           border-radius:14px; font-size:12px; padding:3px 9px; margin:4px 6px 0 0; white-space:nowrap; }
    .step-timer-btn:hover { border-color:var(--accent); color:var(--accent); }
    #cooking-timers-panel { position:fixed; right:14px; bottom:14px; z-index:120; display:flex;
           flex-direction:column; gap:8px; max-width:min(320px,90vw); touch-action:none; }
    #cooking-timers-panel.dragging { transition:none; user-select:none; }
    .cooking-timer-card { background:var(--card); border:1.5px solid var(--color-bg-accent); border-radius:12px;
           padding:9px 12px; box-shadow:0 6px 20px rgba(0,0,0,.4); display:flex; align-items:center; gap:10px;
           cursor:grab; }
    #cooking-timers-panel.dragging .cooking-timer-card { cursor:grabbing; }
    .cooking-timer-card.done { border-color:var(--accent); animation:timer-flash 1s ease-in-out infinite; }
    @keyframes timer-flash { 0%,100% { background:var(--card); } 50% { background:color-mix(in srgb, var(--color-bg-accent) 22%, transparent); } }
    .cooking-timer-remaining { font-variant-numeric:tabular-nums; font-weight:600; font-size:15px; min-width:52px; }
    .cooking-timer-label { flex:1; min-width:0; display:flex; flex-direction:column; overflow:hidden; }
    .cooking-timer-step { font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .cooking-timer-sub { font-size:11px; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .cooking-timer-x { cursor:pointer; background:none; border:none; color:var(--muted); font-size:17px; line-height:1; padding:0 2px; }
    .product-photo-btn:not(.has-photo) { display: none; }
    .inv-editing .product-photo-btn:not(.has-photo) { display: inline-flex; }
    .product-photo-peek-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.85);
           z-index: 110; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
           padding: 24px; cursor: zoom-out; }
    .product-photo-peek-overlay.open { display: flex; }
    .product-photo-peek-overlay img { max-width: 100%; max-height: 70vh; border-radius: 8px; }
    .product-photo-actions { pointer-events: auto; display: flex; gap: 12px; }
    .product-photo-delete { pointer-events: auto; background: rgba(239,83,80,.15); color: #ef5350;
           border: 1px solid #ef5350; border-radius: 8px; padding: 9px 16px; font-size: 15px;
           font-weight: 600; cursor: pointer; }
    .product-photo-change { pointer-events: auto; background: color-mix(in srgb, var(--color-bg-accent) 16%, transparent); color: var(--accent);
           border: 1px solid var(--accent); border-radius: 8px; padding: 9px 16px; font-size: 15px;
           font-weight: 600; cursor: pointer; }
    .step-thumb-inline { width:56px; height:56px; object-fit:cover; border-radius:8px; flex-shrink:0;
           cursor:zoom-in; border:1px solid var(--border); }
    /* Схожі рецепти — сітка міні-карток на сторінці рецепта. */
    /* RD3 — чотири в ряд. Мінімум 150 px замість 120: при колонці 760 px
       старий поріг давав п'ять по ~140 px, і плитки ставали дрібними. */
    .similar-recipes { display:grid; grid-template-columns:repeat(auto-fill, minmax(150px, 1fr));
           gap:12px; margin-top:10px; }
    .similar-card { display:block; text-decoration:none; color:inherit; }
    .similar-card-img { width:100%; aspect-ratio:1/1; border-radius:10px; overflow:hidden; background:var(--card);
           border:1px solid var(--border); display:flex; align-items:center; justify-content:center; font-size:32px; }
    .similar-card-img img { width:100%; height:100%; object-fit:cover; }
    .similar-card-title { margin-top:6px; font-size:13px; line-height:1.3; display:-webkit-box;
           -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
    .visibility-badge { display:inline-block; background: var(--card); border:1px solid var(--border);
           color: var(--muted); padding: 2px 10px; border-radius: 10px; font-size: 12px; }

    /* ── DESIGN SYSTEM ──────────────────────────────────────────────────────
       Everything below binds to semantic tokens from /static/tokens.css only —
       no raw hex, no raw px. Surfaces migrate one at a time; as each moves, its
       rules land here and the legacy block above shrinks. Migrated so far:
       the recipe card (_recipe_card_inner.html).

       Fonts are self-hosted and carry Cyrillic (see fonts.css), so the
       editorial scale is safe to bind now. */
    .icon { display: block; flex: none; }

    /* A recipe title is the one editorial moment on a card. On a photo-less
       card it does the work the missing photo can't, which is why it's the
       serif and not just a bigger sans. */
    .recipe-card h3 { font: var(--text-editorial-h2);
           letter-spacing: var(--tracking-editorial-h2);
           margin: 0 0 var(--space-sm); }

    /* Мета-чипи в один рядок; на вузьких картках переносимо цілими чипами
       (краще перенос, ніж обрізане на пів-слова). Чипи не стискаємо. */
    .meta-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-sm);
           margin-top: var(--space-md); }
    .meta-row > * { flex: none; }
    /* Футер картки — автор ліворуч, рейтинг праворуч (нутриціологію прибрано). */
    .card-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm);
           margin-top: var(--space-md); color: var(--color-text-muted); font: var(--text-label-s);
           letter-spacing: var(--tracking-label-s); }
    .card-foot-author, .card-foot-rating { display: inline-flex; align-items: center; gap: var(--space-xs);
           min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .card-foot .icon { width: 14px; height: 14px; flex: none; }
    .card-foot .icon.is-filled { fill: currentColor; }

    /* Meta Chip — Figma node 20:70. Neutral pill; the only colour it carries is
       the difficulty dot. Never tint the pill by level. */
    .meta-chip { display: inline-flex; align-items: center; gap: var(--space-sm);
           height: 28px; padding: 0 var(--space-md); border-radius: var(--radius-full);
           background: var(--color-bg-subtle); color: var(--color-text-secondary);
           font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           white-space: nowrap; }
    .meta-chip .icon { width: 14px; height: 14px; }
    /* The dot is 9px but sits in the same 14px slot as an icon, so chips with
       and without icons line their labels up. */
    .meta-dot { width: 14px; height: 14px; display: grid; place-items: center; flex: none; }
    .meta-dot::before { content: ""; width: 9px; height: 9px; border-radius: var(--radius-full);
           background: var(--color-text-muted); }
    .meta-chip.is-easy   .meta-dot::before { background: var(--color-signal-good); }
    .meta-chip.is-medium .meta-dot::before { background: var(--color-signal-moderate); }
    .meta-chip.is-hard   .meta-dot::before { background: var(--color-signal-poor); }
    .meta-chip.is-draft { background: var(--color-bg-accent-soft); color: var(--color-text-accent); }
    /* ШІ-БЖВ — та сама акцентна пара, що й «в розробці»: обидва чипи кажуть
       «це не звичайні дані, звідси й вигляд», і розводити їх у два різні
       кольори означало б вигадати другий сенс тому самому сигналу. */
    .meta-chip.is-ai { background: var(--color-bg-accent-soft); color: var(--color-text-accent); }
    /* Очікування довгої ШІ-дії. Не «красивість»: без нього сторінка на
       десятки секунд виглядає так, ніби нічого не сталось, і людина тисне
       кнопку вдруге — а це другий виклик моделі й другий рецепт. */
    .sub-wait { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center;
           background: var(--color-bg-scrim); padding: var(--space-base); }

    .sub-wait-box { display: flex; flex-direction: column; align-items: center;
           gap: var(--space-sm); text-align: center; max-width: 320px;
           padding: var(--space-2xl); border-radius: var(--radius-lg);
           background: var(--color-bg-surface); color: var(--color-text-primary); }
    .sub-wait-spin { width: 28px; height: 28px; border-radius: var(--radius-full);
           border: 3px solid var(--color-border-default);
           border-top-color: var(--color-bg-accent); animation: sub-spin .8s linear infinite; }
    @keyframes sub-spin { to { transform: rotate(360deg); } }
    /* Хто просив менше руху — тому лише статичне кільце, без обертання. */
    @media (prefers-reduced-motion: reduce) { .sub-wait-spin { animation: none; } }

    /* Diet tag — the Tag component's Diet type, not a Meta Chip. */
    .diet-tag { display: inline-flex; align-items: center; gap: var(--space-sm);
           height: 28px; padding: 0 var(--space-md); border-radius: var(--radius-full);
           background: var(--color-tag-diet-bg); color: var(--color-tag-diet-text);
           font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           white-space: nowrap; }
    .diet-tag .icon { width: 14px; height: 14px; }

    /* Icon-only markers (video, light dish). No Figma component covers these;
       they keep the icon-plus-tooltip treatment they had as emoji. */
    .meta-marker { display: inline-flex; align-items: center; color: var(--color-text-muted); }
    .meta-marker .icon { width: 16px; height: 16px; }

    .meta-note { display: inline-flex; align-items: center; gap: var(--space-xs);
           color: var(--color-text-muted); font: var(--text-label-s);
           letter-spacing: var(--tracking-label-s); }
    .meta-note .icon { width: 14px; height: 14px; }

    /* A stroked glyph becomes a solid one by filling it — the CSS `fill` beats
       the presentation attribute the icon ships with, so one icon covers both
       the on and off states. */
    .favorite-btn .icon, .card-menu-btn .icon { width: 24px; height: 24px; }
    .favorite-btn.filled .icon { fill: currentColor; }
    .meta-note .icon.is-filled { fill: currentColor; }
    .card-menu-item .icon { width: 18px; height: 18px; }
    .stars { display:inline-flex; gap:2px; }
    .star-btn { background:none; border:none; cursor:pointer; color: var(--muted); padding:0 1px;
           display:inline-flex; }
    .star-btn .icon { width:22px; height:22px; }
    .star-btn.filled { color: var(--color-text-accent); }
    .star-btn.filled .icon { fill: currentColor; }
    .favorite-btn { background:none; border:none; cursor:pointer; line-height:1; color: var(--muted); padding:0; }
    .favorite-btn.filled { color: var(--color-text-accent); }
    .favorite-btn.big .icon { width:30px; height:30px; }
    /* Reports chip — the one Meta Chip that carries a signal colour, because it
       is a moderation alert, not neutral metadata. */
    .meta-chip.is-alert { color: var(--color-signal-poor); }
    /* Recipe detail title — Editorial/H1 (Lora), matching the card's H2 title. */
    .recipe-title { font: var(--text-editorial-h1); letter-spacing: var(--tracking-editorial-h1); }
    /* Segment control — top-level content switch (World / For you / Yours / …).
       Scrolls horizontally rather than wrapping, so it stays one clean row. */
    /* Ширину задаємо КЛАСОМ, а не inline-стилем у шаблоні: inline перебивав
       десктопне правило нижче, і вкладки лишались на всю ширину попри нього. */
    .segments { display:flex; flex: 1 1 auto; min-width:0; gap: var(--space-xs); background: var(--color-bg-subtle);
           border-radius: var(--radius-full); padding:4px; overflow-x:auto; scrollbar-width:none; }
    .segments::-webkit-scrollbar { display:none; }
    .seg { flex:1 0 auto; white-space:nowrap; padding:8px 14px; border-radius: var(--radius-full);
           font: var(--text-label-m); font-weight:600; color: var(--color-text-secondary);
           text-decoration:none; display:inline-flex; align-items:center; justify-content:center;
           gap: var(--space-xs); }
    .seg .icon { width:15px; height:15px; }
    .seg.active { background: var(--color-bg-surface); color: var(--color-text-primary);
           box-shadow: 0 1px 2px rgba(0,0,0,.12); }
    /* D1 — на десктопі вкладки це компактна група ЛІВОРУЧ, а не смуга на всю
       ширину: розтягнута на 1280px «Світова | Для тебе | Твоя» читається як
       три величезні кнопки й забирає цілий рядок ні на що.

       Блок стоїть саме ТУТ, після базового `.segments`: у медіазапиті вище
       він мав однакову специфічність із базовим правилом, оголошеним нижче,
       і програвав за порядком — вкладки лишались на всю ширину попри нього. */
    @media (min-width: 1024px) {
      .segments { flex: none; width: auto; align-self: flex-start; }
      .seg { flex: none; padding: 8px 20px; }
    }
    /* C1 — на мобільному вкладки без контейнера і пігулки, рівномірно по
       ширині. Активну позначає 2px індикатор-підкреслення — звірено з ЖИВИМ
       компонентом Segment Item у Фігмі (не зі старим експортом, там його ще
       нема): текст 17 SemiBold, активна — text-primary, неактивні —
       text-muted, індикатор — bg-inverse (у темній темі стає кремовим).
       Раніше активну відрізняв лише колір тексту при однаковій вазі — її
       було не знайти оком. Пігулка-сегмент лишається на десктопі (D1), де
       вона намальована й добре читається.
       Правила звужені до .segments: у .group-switch свої сегменти, і
       глобальний .seg.active домалював би підкреслення і їм. */
    @media (max-width: 767px) {
      .segments { background:none; padding:0; gap:0; }
      .segments .seg { flex:1 1 0; padding:8px 4px; border-radius:0;
             font: var(--text-body-l); font-weight:600; color: var(--color-text-muted); }
      .segments .seg.active { background:none; color: var(--color-text-primary);
             box-shadow: inset 0 -2px 0 var(--color-bg-inverse); }
    }
    /* Circular icon button — 44px tap target (search, filter, create). */
    /* text-decoration:none обовʼязково: `.icon-btn` буває посиланням, а
       глобальне правило `a` лишало підкреслення — на кнопці «Новий рецепт»
       воно й вилізло, щойно вона отримала підпис. */
    .icon-btn { position:relative; text-decoration:none; width:44px; height:44px; flex:none; border-radius: var(--radius-full);
           border:1px solid var(--color-border-default); background: var(--color-bg-surface);
           color: var(--color-text-primary); display:grid; place-items:center; cursor:pointer; padding:0; }
    .icon-btn .icon { width:20px; height:20px; }
    .icon-btn.accent { background: var(--color-bg-accent); border-color:transparent; color: var(--color-text-on-accent); }
    /* Active-filter count on the filter trigger — the "Filters · 2" affordance. */
    .filter-count { position:absolute; top:-4px; right:-4px; min-width:18px; height:18px; padding:0 5px;
           border-radius: var(--radius-full); background: var(--color-bg-accent); color: var(--color-text-on-accent);
           font: var(--text-label-s); font-weight:700; display:grid; place-items:center; }
    /* Utility: an icon + text on one aligned baseline (headings, links, chips). */
    .icon-row { display:inline-flex; align-items:center; gap: var(--space-sm); }
    .member-chip { display:inline-flex; align-items:center; gap:6px; background: var(--card);
           border:1px solid var(--border); padding: 5px 12px; border-radius: 20px; font-size: 14px; }
    .inv-list { display:flex; flex-direction:column; gap:8px; margin-top: 14px; }
    .mp-list { min-height: 10px; transition: background .15s ease; border-radius: 8px; }
    .mp-list.drag-over { background: color-mix(in srgb, var(--color-bg-accent) 12%, transparent); outline: 1px dashed var(--accent); }
    .inv-item { display:flex; align-items:center; gap:10px; background: var(--card);
                border:1px solid var(--border); padding: 8px 12px; border-radius: var(--radius-lg); font-size: 14px; }
    /* `.have` більше не фарбує рамку: в інвентарі цей клас має КОЖНА плитка
       (усе, що там лежить, є вдома), тож лаймовий контур на 100% рядків не
       ніс інформації — лише робив інвентар несхожим на покупки. Рамка, що
       справді щось каже, лишилась одна: `.expired`. */
    /* Висота блока назви фіксована, а вміст у ньому центрований по вертикалі.
       Це дає обидві потрібні речі одразу: рядки з уточненням і без нього
       однакової висоти (список не стрибає), а сама назва без уточнення стоїть
       ПОСЕРЕДИНІ рядка, а не притиснута до верху з порожнечею під нею.
       37px — це рядок назви (22) плюс рядок уточнення (14) плюс відступ між
       ними (1); тримати порожній елемент заради тієї ж висоти не треба. */
    .inv-item .inv-name { flex:1; min-width:0; overflow-wrap:break-word;
           min-height:37px; display:flex; flex-direction:column; justify-content:center; }
    /* Атрибуція у спільних списках: «Оля · вівторок» під назвою (лише групи). */
    /* Другий рядок позиції (бренд). Місце під нього тримає САМ блок назви
       (`.inv-name` вище має фіксовану висоту), тож порожнього елемента тут
       більше немає: коли бренда нема, назва просто стоїть по центру. */
    .inv-added-by { display:block; font-size:11px; line-height:14px;
           color: var(--muted); font-weight:400; margin-top:1px; }
    /* G7b — заголовки секцій списку покупок (з меню / вручну / куплено). */
    .shopping-section-label { font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           text-transform:uppercase; color: var(--color-text-muted); margin: var(--space-md) 0 var(--space-2xs); padding:0 2px;
           display:flex; align-items:baseline; justify-content:space-between; gap: var(--space-md); }
    .shopping-section-label:first-child { margin-top: 0; }
    /* Скільки рецептів дало цю секцію — праворуч, акцентом (G7b). */
    .shopping-section-count { color: var(--color-text-accent); flex:none; text-transform:none; }
    .inv-item.bought .inv-added-by { text-decoration:none; }
    .inv-item .qty { color: var(--muted); font-size: 12px; }
    .inv-item form { display:flex; align-items:center; margin:0; }
    .inv-toggle { width:22px; height:22px; border-radius:50%; border:1px solid var(--border);
                  background: transparent; color: var(--text); cursor:pointer; font-size:12px;
                  line-height:1; padding:0; }
    .inv-item.have .inv-toggle, .inv-item.bought .inv-toggle { background: var(--accent); border-color: var(--accent); color: var(--color-text-on-accent); }
    /* Десктоп: у комори немає тогла «куплено», і назви її плиток починались
       на 32px лівіше, ніж у покупках. Невидимий спейсер тої самої ширини, що
       тогл (22px), тримає структуру плиток однаковою на обох вкладках.
       ::before на display:contents-елементі легальний — бокс стає дитиною
       флекс-рядка. Лише ≥768: на мобільному тогл схований і в покупках. */
    @media (min-width: 768px) {
      #inventory-items .inv-item .inv-editable::before { content:""; width:22px; flex:none; }
    }
    .inv-item.bought { border-color: var(--accent); opacity:.6; }
    .inv-item.bought .inv-name { text-decoration: line-through; }
    .inv-item.expired { border-color:#ef5350; }
    .bbf-badge { white-space:nowrap; }
    .bbf-badge.bbf-expired { color:#ef5350; font-weight:600; }
    /* Інпут дати — невидимий оверлей ПОВЕРХ іконки-календарика: сама іконка
       завжди лишається іконкою (не перетворюється на mm/dd/yyyy), а нативний
       пікер прив'язується до позиції іконки (десктоп) / центрується OS
       (мобільний). */
    /* Специфічність .bbf-date-wrap .bbf-input (0,2,0) навмисно вища за
       `.inv-add input` (0,1,1), інакше воно нав'язало б width:auto/min-width:160px
       і оверлей розпух би / схлопнувся. */
    .bbf-date-wrap .bbf-input { position:absolute; inset:0; width:100%; height:100%;
           min-width:0; opacity:0; margin:0; padding:0; border:0; cursor:pointer; }
    .inv-del { background:none; border:none; color: var(--muted); cursor:pointer; font-size:15px; padding:0 2px; }
    .inv-del:hover { color:#ef5350; }
    .inv-editable { display:contents; cursor:pointer; }
    .inline-edit-form { display:flex; align-items:center; gap:6px; flex-wrap:wrap; flex:1; }
    .inline-edit-form input, .inline-edit-form select { padding:5px 8px; font-size:13px; min-width:0; }
    .inline-edit-form .ing-name { flex:2; min-width:80px; }
    .inline-edit-form .ing-brand { flex:1; min-width:60px; }
    .inline-edit-form .ing-qty { width:38px; flex:none; padding-left:4px; padding-right:2px; }
    .inline-edit-form .ing-unit { width:46px; flex:none; padding-left:2px; padding-right:0; }
    .inline-edit-form .btn, .inline-edit-form .btn.secondary { padding:5px 10px; font-size:13px; }
    .inline-edit-icon-btn { width:30px; height:30px; padding:0 !important; font-size:15px; flex:none;
                             display:inline-flex; align-items:center; justify-content:center; line-height:1; }
    .bbf-date-wrap { position:relative; display:inline-flex; align-items:center; flex:none; }
    .bbf-date-btn.has-value { border-color: var(--accent); color: var(--accent); }
    .list-chip { display:inline-flex; align-items:center; gap:4px; background: var(--card);
           border:2px solid var(--border); padding:6px 12px; border-radius:20px; font-size:14px;
           text-decoration:none; color: var(--text); user-select:none; -webkit-user-select:none; }
    .icon-picker { display:flex; flex-wrap:wrap; gap:4px; margin:6px 0; }
    /* `color` тут ОБОВ'ЯЗКОВИЙ, і це не косметика. Іконка малюється
       currentColor, а <button> без свого кольору бере системний `buttontext`,
       який слухає `color-scheme: light dark`, тобто тему ОПЕРАЦІЙНОЇ СИСТЕМИ,
       а не застосунку. У кого ОС темна, а в застосунку обрано світлу тему,
       палітра ставала білою по кремовому — тобто порожні квадратики. Решта
       кнопок задають колір явно, тому й не ламались. */
    .icon-picker-btn { width:34px; height:34px; border-radius:8px; border:1px solid var(--border);
           background:transparent; font-size:16px; cursor:pointer; display:flex; align-items:center;
           justify-content:center; padding:0; color: var(--color-text-secondary); }
    .icon-picker-btn:hover { color: var(--color-text-primary); }
    .icon-picker-btn.active { border-color: var(--accent); background: var(--card);
           color: var(--color-text-primary); }
    .scope-select { position:relative; display:inline-block; }
    #scope-select-toggle { min-width:200px; }
    .scope-select-menu { position:absolute; top:calc(100% + 4px); left:0; z-index:20; background: var(--card);
           border:1px solid var(--border); border-radius:10px; padding:6px; min-width:220px; max-height:340px;
           overflow-y:auto; display:flex; flex-direction:column; gap:2px; box-shadow:0 8px 24px rgba(0,0,0,.35); }
    /* Дропдауни біля правого краю (Імпорт/Експорт) відкриваємо вліво, щоб не
       давати горизонтальний скрол. */
    #import-select-menu, #export-select-menu { left:auto; right:0; }
    .scope-select-item { display:block; padding:8px 10px; border-radius:6px; text-decoration:none;
           color: var(--text); font-size:14px; }
    .scope-select-item:hover { background: var(--bg); }
    .scope-select-item.active { background: var(--accent); color: var(--color-text-on-accent); }
    .scope-select-label { font-size:11px; text-transform:uppercase; color: var(--muted); padding:8px 10px 2px; }
    /* Рядок вибору scope із чекбоксом видимості (показувати список у покупках). */
    .scope-vis-row { display:flex; align-items:center; gap:4px; }
    .scope-vis-cb { flex:none; width:18px; height:18px; margin-right:6px; cursor:pointer; accent-color: var(--color-bg-accent); }
    .scope-select-list-row { position:relative; }
    .scope-select-list-row .inline-edit-form { padding:4px; flex-wrap:nowrap; }

    /* ── Чат «Нутриціолога» ──────────────────────────────────────────────
       Один набір правил на всі три види розмови (ШІ, група, приватно):
       вимога власника — вони мають виглядати майже однаково, а різниця
       має бути там, де вона щось означає (ім'я автора в групі, значок
       «ШІ» на репліці моделі), а не в кольорі бульбашки.

       Рядки пігулки області тут ширші за кухонні: до назви додається вид
       розмови й крапка «є нове», і без flex вони просто налазили б.

       `.is-chat` тут ОБОВʼЯЗКОВИЙ, а не косметика: `.scope-select-item` —
       спільний клас із покупками й коморою, і безумовний `display:flex`
       перебудував би їхні меню теж (там значок і назва — інлайновий текст,
       а не флекс-елементи). Один зайвий селектор проти тихої зміни двох
       чужих екранів. */
    .scope-select-item.active .chat-scope-kind { color: var(--color-text-on-accent); opacity:.8; }
    .scope-select-menu.is-chat .scope-select-item { display:flex; align-items:center; gap: var(--space-sm); }
    .chat-scope-name { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .chat-scope-kind { flex:none; font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           color: var(--color-text-secondary); }
    .chat-scope-dot { flex:none; width:8px; height:8px; border-radius: var(--radius-full);
           background: var(--color-bg-accent); }

    /* Панель розмови: стрічка реплік + рядок вводу однією поверхнею. Рамка,
       а не тінь — тінь у цій системі належить липким панелям (є рівно один
       токен, `--shadow-sticky`, і він їхній). */
    .chat-panel { display:flex; flex-direction:column; margin: var(--space-md) 0 0;
           border:1px solid var(--color-border-default); border-radius: var(--radius-lg);
           background: var(--color-bg-surface); overflow:hidden; }
    /* Висота розмови обмежена навмисно: рядок вводу мусить лишатись видимим,
       інакше на довгій розмові до нього доводиться гортати через увесь чат.
       `min-height` тримає панель від сплющування на порожній розмові. */
    .chat-log { display:flex; flex-direction:column; gap: var(--space-xs);
           padding: var(--space-base); min-height:180px; max-height:52vh; overflow-y:auto; }
    .chat-foot { border-top:1px solid var(--color-border-default); padding: var(--space-sm) var(--space-base);
           background: var(--color-bg-canvas); }
    .chat-empty { font: var(--text-body-s); margin:auto 0; padding: var(--space-md) 0; text-align:center; }
    .chat-author { display:flex; align-items:center; gap: var(--space-xs); margin-top: var(--space-sm);
           font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           color: var(--color-text-secondary); }
    .chat-msg { position:relative; align-self:flex-start; max-width:85%;
           display:flex; flex-direction:column; gap: var(--space-2xs);
           padding: var(--space-sm) var(--space-base); border-radius: var(--radius-lg);
           background: var(--color-bg-subtle); color: var(--color-text-primary);
           font: var(--text-body-m); white-space:pre-wrap; word-break:break-word; }
    /* Свої репліки праворуч і акцентом — єдине, чим бульбашки різняться.
       Репліка ШІ лишається «чужою»: вона й є чужа, а особливий колір для
       неї означав би, що порада від моделі й порада від людини — різного
       ґатунку. Різницю несе значок, а не фон. */
    .chat-msg.is-mine { align-self:flex-end; background: var(--color-bg-accent);
           color: var(--color-text-on-accent); }
    .chat-msg-badge { display:inline-flex; align-items:center; gap: var(--space-2xs);
           font: var(--text-label-s); letter-spacing: var(--tracking-label-s);
           color: var(--color-text-secondary); }
    .chat-msg-time { align-self:flex-end; font: var(--text-label-s);
           letter-spacing: var(--tracking-label-s); color: var(--color-text-secondary); }
    .chat-msg.is-mine .chat-msg-time { color: var(--color-text-on-accent); opacity:.7; }
    .chat-photo { display:block; margin:0 0 var(--space-2xs); }
    .chat-photo img { display:block; max-width:100%; max-height:260px; border-radius: var(--radius-md);
           object-fit:cover; }
    .chat-msg-text { white-space:pre-wrap; }

    /* Прикріплене — під бульбашкою, не в ній: це органи керування. */
    .chat-offer { display:flex; flex-wrap:wrap; gap: var(--space-xs);
           max-width:85%; margin: 0 0 var(--space-xs); align-self:flex-start; }
    .chat-offer.is-mine { align-self:flex-end; justify-content:flex-end; }
    .chat-offer form { margin:0; }
    .chat-offer .btn { font: var(--text-label-m); padding: var(--space-xs) var(--space-md);
           border-radius: var(--radius-full); }
    .chat-recipe { display:inline-flex; align-items:center; gap: var(--space-xs);
           max-width:100%; padding: var(--space-xs) var(--space-sm) var(--space-xs) var(--space-md);
           border:1px solid var(--color-border-default); border-radius: var(--radius-full);
           background: var(--color-bg-surface); color: var(--color-text-primary);
           font: var(--text-body-s); text-decoration:none; }
    .chat-recipe:hover { border-color: var(--color-bg-accent); }
    .chat-recipe > span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .chat-recipe .chat-recipe-chev { flex:none; color: var(--color-text-secondary); }
    .chat-recipe.is-gone { color: var(--color-text-secondary); border-style:dashed; }
    .chat-done { display:inline-flex; align-items:center; gap: var(--space-xs); height:28px;
           padding:0 var(--space-md); border-radius: var(--radius-full);
           background: var(--color-bg-subtle); color: var(--color-text-secondary);
           font: var(--text-label-s); letter-spacing: var(--tracking-label-s); }
    .chat-note { display:flex; align-items:center; gap: var(--space-xs);
           font: var(--text-body-s); color: var(--color-text-secondary); margin: var(--space-xs) 0; }

    /* Дії над своєю реплікою. Кнопка навмисно тьмяна й дрібна: нею
       користуються раз на сто повідомлень, а стоїть вона біля КОЖНОГО
       свого повідомлення. На пристроях із мишею проявляється при наведенні
       на репліку; на сенсорних (де наведення не буває) лишається видимою
       завжди — інакше до неї не дістатись. */
    .chat-edit-btn { align-self:flex-end; margin:-2px 0 var(--space-2xs);
           background:none; border:none; padding:2px 4px; cursor:pointer; line-height:0;
           color: var(--color-text-secondary); opacity:.55; }
    .chat-edit-btn:hover, .chat-edit-btn:focus-visible { opacity:1; }
    @media (hover: hover) {
      .chat-edit-btn { opacity:0; transition: opacity .12s ease; }
      .chat-log:hover .chat-edit-btn { opacity:.55; }
    }
    /* Позначене на зняття фото — приглушене, а не зникле: доки не збережено,
       рух зворотний, і картинка має лишатись видимою. */
    .chat-edit-photo { display:flex; align-items:flex-start; gap: var(--space-md); flex-wrap:wrap; }
    .chat-edit-photo img { width:96px; height:96px; object-fit:cover; flex:none;
           border-radius: var(--radius-md); }
    .chat-edit-photo img.is-dropping { opacity:.35; filter:grayscale(1); }
    .chat-edit-photo .btn { flex:none; }
    #chat-edit-text { width:100%; resize:vertical; min-height:76px; }
    /* Єдина руйнівна дія аркуша — тьмяним текстом, а не кнопкою: головна
       дія тут «Зберегти», і рівнятись у вазі видаленню нема за чим. */
    .link-action.is-danger { color:#ef5350; }
    /* «твоє» біля числа норми — позначка походження, а не прикраса: цифра
       з формули й цифра від лікаря мусять читатись як різні речі. */
    .target-own { display:inline-block; padding:0 var(--space-xs); border-radius: var(--radius-full);
           background: var(--color-bg-accent); color: var(--color-text-on-accent);
           font: var(--text-label-s); letter-spacing: var(--tracking-label-s); text-transform:none; }

    .chat-composer { margin:0; }
    .chat-composer-row { display:flex; align-items:center; gap: var(--space-xs); }
    /* Поле — пігулка, як і кнопки поруч: у ряду з трьох круглих контролів
       прямокутний ввід читався як чужий елемент. */
    .chat-input { flex:1; min-width:0; margin:0; border-radius: var(--radius-full);
           padding-left: var(--space-base); padding-right: var(--space-base); }
    .chat-icon-btn { flex:none; display:inline-flex; align-items:center; justify-content:center;
           width:44px; height:44px; border-radius: var(--radius-full);
           border:1px solid var(--color-border-default); background:transparent;
           color: var(--color-text-secondary); cursor:pointer; padding:0; }
    .chat-icon-btn:hover { color: var(--color-text-primary); border-color: var(--color-text-secondary); }
    .chat-send { flex:none; display:inline-flex; align-items:center; justify-content:center;
           width:44px; height:44px; border-radius: var(--radius-full); border:none;
           background: var(--color-bg-accent); color: var(--color-text-on-accent); cursor:pointer; padding:0; }
    /* «+» — та сама кнопка, що «Надіслати», лише на іншому кінці рядка
       (рішення власника 2026-08-18). До неї тут стояли два-три тьмяні кружки
       зі значками, і разом вони важили більше за саме поле вводу.

       Заливка навмисно НЕ акцентна: два лаймові кола обабіч поля читались би
       як дві головні дії, а головна тут одна — відправити. Форма, розмір і
       вага ті самі, колір розводить ролі. */
    .chat-add { flex:none; display:inline-flex; align-items:center; justify-content:center;
           width:44px; height:44px; border-radius: var(--radius-full); border:none;
           background: var(--color-bg-subtle); color: var(--color-text-primary);
           cursor:pointer; padding:0; }
    .chat-add:hover { background: var(--color-border-default); }
    /* Картка меню-курсу: посилання (назва + зведення) і дія під ним. Дія
       окремим рядком, а не значком у кутку, бо вона підписана словами — на
       картці, де все інше теж текст, значок довелось би вгадувати. */
    .menu-card { display:flex; flex-direction:column; gap: var(--space-md); cursor:default; }
    .menu-card-main { display:block; text-decoration:none; color:inherit; }
    .menu-card-actions { display:flex; gap: var(--space-sm); flex-wrap:wrap; }
    .chat-attach-row { display:flex; flex-wrap:wrap; gap: var(--space-xs); margin-bottom: var(--space-xs); }
    .chat-attach-chip { display:inline-flex; align-items:center; gap: var(--space-xs);
           padding: var(--space-2xs) var(--space-sm); border-radius: var(--radius-full);
           background: var(--color-bg-subtle); color: var(--color-text-secondary);
           font: var(--text-label-s); letter-spacing: var(--tracking-label-s); max-width:100%; }
    .chat-attach-chip > span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .chat-attach-x { background:none; border:none; padding:0; cursor:pointer; line-height:0;
           color: var(--color-text-secondary); }
    .chat-hint { display:flex; align-items:flex-start; gap: var(--space-xs);
           font: var(--text-body-s); color: var(--color-text-secondary); margin: var(--space-xs) 0 0; }
    .chat-hint .icon { flex:none; margin-top:2px; }

    /* Пігулка розмови в шапці. `page-head-actions` розрахований на круглі
       кнопки, тож ширину пігулці задаємо тут, а не глобально: 200px із
       #scope-select-toggle на вузькому екрані виштовхували б заголовок. */
    .page-head-actions .scope-select { min-width:0; }
    .page-head-actions #scope-select-toggle { min-width:0; max-width:min(52vw, 260px); }
    @media (max-width: 767px) {
      /* На телефоні пігулка йде під заголовок на всю ширину: у рядку з ним
         вона лишала назві розмови десяток пікселів. */
      .page-head:has(.scope-select) { flex-wrap: wrap; }
      .page-head-actions:has(.scope-select) { flex: 1 0 100%; }
      .page-head-actions #scope-select-toggle { max-width:none; width:100%; }
      .chat-log { max-height:46vh; }
      .chat-msg { max-width:92%; }
    }
    .share-wrap { position:relative; display:inline-block; }
    .share-btn-icon { vertical-align:-3px; margin-right:2px; }
    .share-menu { display:none; position:absolute; top:calc(100% + 8px); left:0; z-index:30;
           background: var(--card); border:1px solid var(--border); border-radius:14px; padding:14px;
           min-width:250px; box-shadow:0 10px 30px rgba(0,0,0,.4); }
    .share-menu.open { display:block; }
    .share-menu-icons { display:flex; gap:14px; margin-bottom:12px; flex-wrap:wrap; }
    .share-menu-icon { display:flex; flex-direction:column; align-items:center; gap:6px; text-decoration:none;
           color: var(--text); font-size:11px; width:54px; background:none; border:none; cursor:pointer;
           font-family:inherit; padding:0; }
    .share-menu-icon-circle { width:44px; height:44px; border-radius:50%; display:flex; align-items:center;
           justify-content:center; flex-shrink:0; transition: transform .15s ease; }
    .share-menu-icon:hover .share-menu-icon-circle { transform: scale(1.08); }
    .share-menu-copy { display:flex; align-items:center; gap:10px; width:100%; padding:10px 12px;
           border-radius:8px; border:1px solid var(--border); background:transparent; color: var(--text);
           cursor:pointer; font-size:13px; font-weight:600; font-family:inherit; }
    .share-menu-copy:hover { background: var(--bg); }
    .step-recipe-card { display:inline-flex; align-items:center; gap:6px; background: var(--card);
                         border:1px solid var(--border); border-radius:20px; padding:3px 10px 3px 3px;
                         margin:4px 4px 0 0; font-size:13px; color: var(--text); text-decoration:none; vertical-align:middle; }
    .step-recipe-card:hover { border-color: var(--accent); }
    .step-recipe-card-img { width:22px; height:22px; border-radius:50%; object-fit:cover; }
    .card-menu { position:absolute; bottom:8px; right:8px; z-index:3; }
    .card-menu-btn { background: rgba(0,0,0,.45); border:none; color:#fff; width:30px; height:30px;
           border-radius:50%; font-size:18px; cursor:pointer; display:flex; align-items:center;
           justify-content:center; line-height:1; padding:0; }
    .card-menu-dropdown { display:none; position:absolute; bottom:36px; right:0; background: var(--card);
           border:1px solid var(--border); border-radius:10px; min-width:190px; overflow:hidden;
           z-index:4; box-shadow:0 6px 20px rgba(0,0,0,.45); }
    .card-menu-dropdown.open { display:block; }
    .card-menu-item { display:flex; align-items:center; gap:10px; width:100%; text-align:left;
           padding:10px 14px; background:none;
           border:none; color: var(--text); cursor:pointer; font-size:14px; font-family:inherit; margin:0; }
    .card-menu-item:hover { background: var(--bg); }
    .recipe-card.card-menu-open, .swipe-wrap.card-menu-open { overflow: visible; }
    /* Веб: контекстне меню (три крапки) — біля сердечка вгорі-праворуч, тим
       самим легким стилем. Мобільний: ховаємо — там дії викликає свайп. */
    @media (min-width: 768px) {
      /* Крапки праворуч, сердечко ліворуч від них. Було навпаки: меню, яким
         користуються зрідка, стояло на краю — найзручнішому місці, — а часта
         дія тулилась усередину. */
      .card-menu { top: 10px; right: 10px; bottom: auto; }
      .recipe-card .favorite-form { right: 44px !important; }
      .card-menu-btn { background: none; color: var(--muted); width: auto; height: auto; border-radius: 0; }
      .card-menu-dropdown { top: 40px; bottom: auto; }
    }
    @media (max-width: 767px) {
      .card-menu { display: none; }
    }
    /* Заголовок не має заповзати під кнопки правого верхнього кута картки.
       Серце стоїть на right:10px (займає до 34px від краю) і є завжди;
       «три крапки» — на right:44px (до 68px) і лише з 768px.

       Місце під кнопки резервує САМЕ ЗАГОЛОВОК, а не контейнер: кнопки стоять
       на рівні заголовка, а не чипів чи футера. Раніше .recipe-card-body тримав
       44px padding-right на всій колонці — і тоді на плитці без фото весь текст
       (чипи, автор, «Немає оцінок») стояв на 28px лівіше, ніж на плитці з фото,
       де overlay має рівні 16px. Обидва варіанти тепер мають однакову рамку
       16px і однакову поправку на заголовку. */
    .recipe-card-overlay h3,
    .recipe-card-body h3 { padding-right: 28px; }
    @media (min-width: 768px) {
      .recipe-card-overlay h3,
      .recipe-card-body h3 { padding-right: 60px; }
    }
    .floating-save-btn { display:none; position:fixed; left:16px; bottom:16px; z-index:200;
           align-items:center; gap:8px; padding:12px 20px; border-radius:30px; border:none;
           background: var(--accent); color: var(--color-text-on-accent); font-weight:600; font-size:15px; cursor:pointer;
           box-shadow:0 6px 20px rgba(0,0,0,.5); }
    .floating-save-btn.show { display:flex; }
    /* Плаваюча «мітла» — прибрати куплене. Кріпиться до низу (над навігацією),
       заходить баунсом, коли зʼявляється куплене, і виходить баунсом після
       чистки. */
    /* Нижня дія списку покупок. За макетом «Kitchen (merged)» це широка пігулка
       на всю ширину контенту кольору `bg-inverse` (форест у світлій темі,
       крем у темній — так і задумано), а не маленький лаймовий кружок:
       лайм у системі — найгучніший колір, і віддавати його дії, що просто
       прибирає куплене зі списку, означає перекричати нею сам список. */
    .floating-broom { position:fixed; left:50%; bottom:16px; z-index:200; transform:translateX(-50%);
           display:none; align-items:center; justify-content:center; gap:8px;
           width: min(calc(100vw - 2 * var(--space-base)), var(--page-max));
           min-height:56px; padding:12px var(--space-xl); border-radius: var(--radius-full);
           border:none; background: var(--color-bg-inverse); color: var(--color-text-inverse);
           font: var(--text-body-l); font-weight:600; cursor:pointer; box-shadow: var(--shadow-sticky); }
    .floating-broom .icon { width:18px; height:18px; }
    .floating-broom.show { display:flex; animation: broom-in .34s cubic-bezier(.34,1.56,.64,1); }
    .floating-broom.out { animation: broom-out .3s cubic-bezier(.5,0,.75,0) forwards; }
    @keyframes broom-in {
      0%   { transform: translateX(-50%) translateY(24px) scale(.6); opacity:0; }
      60%  { transform: translateX(-50%) translateY(0) scale(1.08); opacity:1; }
      100% { transform: translateX(-50%) translateY(0) scale(1); opacity:1; }
    }
    @keyframes broom-out {
      0%   { transform: translateX(-50%) scale(1); opacity:1; }
      30%  { transform: translateX(-50%) scale(1.12); opacity:1; }
      100% { transform: translateX(-50%) translateY(24px) scale(.5); opacity:0; }
    }
    /* Те саме для правого краю — без translateX(-50%), інакше кнопка
       під'їжджала б на пів ширини вбік просто через анімацію. */
    @keyframes broom-in-right {
      0%   { transform: translateY(24px) scale(.6); opacity:0; }
      60%  { transform: translateY(0) scale(1.08); opacity:1; }
      100% { transform: translateY(0) scale(1); opacity:1; }
    }
    @keyframes broom-out-right {
      0%   { transform: scale(1); opacity:1; }
      30%  { transform: scale(1.12); opacity:1; }
      100% { transform: translateY(24px) scale(.5); opacity:0; }
    }
    .recipe-card-overlay { padding-bottom: 44px !important; }
    .recipe-card-body { padding-bottom: 44px !important; }
    .recipe-card-missing { padding-bottom: 44px !important; }
    /* Обрізання вмикається ЛИШЕ на час свайпу.
       Причина конкретна: екран із `devicePixelRatio: 1.25` рахує рамку картки
       в 1px як 0.8px, тож вона не лягає на піксельну сітку; а коли заокруглену
       картку ще й обрізає заокруглена обгортка, на дугах виходять світлі
       цятки. У спокої обрізати нема чого — фон свайпу має `opacity: 0`, — тож
       просто не обрізаємо. Радіус лишається на піксель більшим: він потрібен
       уже під час самого свайпу.

       Спроба №1 (сам лише більший радіус) артефакт не прибрала — лишаю це
       записаним, щоб ніхто не «полагодив» його втретє тим самим способом. */
    .swipe-wrap { position:relative; overflow:visible;
                border-radius: calc(var(--radius-lg) + 1px);
                transition: max-height .28s cubic-bezier(.4,0,.2,1), opacity .24s ease, transform .28s cubic-bezier(.4,0,.2,1); }
    /* Плавне «згортання» рядка після коміту — трохи стискаємо, щоб вихід був живий. */
    .swipe-wrap.collapsing { transform: scale(.96); }
    /* Swipe reveal — leading (right) = Save, trailing (left) = Hide. The bar
       carries both actions; the drag direction lights the relevant one and
       tints the bar. Save is lime + dark text; hide is the signal red. */
    .swipe-bg { position:absolute; inset:0; display:flex; align-items:center;
                justify-content:space-between; opacity:0; border-radius: var(--radius-lg);
                pointer-events:none; background: var(--color-bg-subtle); }
    .swipe-action { display:inline-flex; align-items:center; gap: var(--space-sm);
                padding:0 var(--space-lg); font: var(--text-label-m); font-weight:700; opacity:0;
                transition: transform .16s ease; }
    .swipe-action .icon { width:20px; height:20px; transition: transform .2s cubic-bezier(.34,1.56,.64,1); }
    /* «Заряджено» — свайп перетнув поріг: іконка стрибає, фон яскравішає,
       підказка «відпусти, щоб застосувати». Плюс вібро-відгук у JS. */
    .swipe-bg.armed { filter: saturate(1.2) brightness(1.06); }
    .swipe-bg.armed .swipe-action .icon { transform: scale(1.4); }
    .swipe-bg.armed .swipe-action { transform: translateX(0); }
    @keyframes swipe-commit-flash { 0% { filter: brightness(1.35); } 100% { filter: brightness(1); } }
    .swipe-bg.flash { animation: swipe-commit-flash .35s ease; }
    /* Куплено/Save — приглушений лісовий зелений із білим текстом (яскравий
       лайм на всю плитку «глушив» напис). */
    .swipe-bg.dir-save { background: var(--forest-600); color:#fff; }
    .swipe-bg.dir-save .swipe-save { opacity:1; }
    /* Мобільний: лише іконка дії; на вебі — іконка + підпис. */
    @media (max-width: 767px) { .swipe-text { display:none; } }
    .swipe-bg.dir-hide { background: var(--color-signal-poor); color:#fff; }
    .swipe-bg.dir-hide .swipe-hide { opacity:1; }
    /* "Show again" on the hidden-recipes page — a restore, not a removal, so a
       soft accent tint rather than the red hide treatment. */
    .swipe-bg.dir-show { background: var(--color-bg-accent-soft); color: var(--color-text-accent); }
    .swipe-bg.dir-show .swipe-show { opacity:1; }
    /* Trailing (left-swipe) reveal — a strip of held-open actions behind the
       row: shopping / day menu / more. The row slides over them and snaps to
       an open detent; tapping an action fires it and closes. */
    .swipe-actions { position:absolute; top:0; right:0; bottom:0; display:flex; align-items:stretch;
                border-radius:0 var(--radius-lg) var(--radius-lg) 0; overflow:hidden; }
    .swipe-act { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px;
                width:76px; border:none; cursor:pointer; font: var(--text-label-s);
                letter-spacing: var(--tracking-label-s); }
    .swipe-act .icon { width:20px; height:20px; }
    .swipe-act.act-list { background: var(--color-bg-accent-soft); color: var(--color-text-accent); }
    .swipe-act.act-menu { background: var(--color-bg-subtle); color: var(--color-text-primary); }
    .swipe-act.act-more { background: var(--color-bg-inverse); color: var(--color-text-inverse); }
    .swipe-row { position:relative; touch-action:pan-y; user-select:none;
                transition: transform .3s cubic-bezier(.22,1,.36,1); }
    .swipe-row.dragging { transition:none; }
    /* Виліт рядка при коміті — швидкий розгін «в кінець», перш ніж згорнути. */
    .swipe-row.flung { transition: transform .26s cubic-bezier(.5,0,.75,0); }
    /* Пружний «поп» + плавне зникнення: плитка підстрибує й тане (ease-out),
       а слот згортається по висоті. */
    @keyframes swipe-pop-out {
      0%   { transform: scale(1);    opacity: 1; }
      30%  { transform: scale(1.05); opacity: 1; }
      100% { transform: scale(.72);  opacity: 0; }
    }
    .swipe-wrap.popping .swipe-row { animation: swipe-pop-out .34s cubic-bezier(.34,1.56,.64,1) forwards; }
    .undo-toast { display:none; position:fixed; left:50%; bottom:20px; transform:translateX(-50%);
                   background: var(--card); border:1px solid var(--border); border-radius:10px;
                   padding:10px 16px; align-items:center; gap:14px; z-index:50; box-shadow:0 4px 16px rgba(0,0,0,.4); }
    /* flex-wrap обовʼязковий: рядок кроку містить ще й смужку чипів
       інгредієнтів (R9) з flex-basis:100%. Без переносу вона ставала сусідом
       текстового поля на тому ж рядку й тиснула його до мінімуму. */
    .step-row { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:8px; align-items:flex-start; }
    /* Поле росте під текст (JS нижче міряє scrollHeight), але не менше
       min-height — порожній крок має лишатись зручною ціллю для кліку. */
    .step-row .step-text { flex:1 1 240px; min-height:56px; resize:vertical; overflow-y:hidden; }
    /* Дії кроку (фото, посилання, прибрати) тиснуться до ПРАВОГО краю: ліворуч
       під текстом вони читались як продовження самого кроку, хоч стосуються
       рядка. `margin-left:auto` на першій з них відсуває всю трійку разом. */
    .step-row .step-photo-btn { margin-left:auto; }
    /* R9 — рядок кроку став підписом: номер, початок тексту, позначки
       прикріпленого. Поля лишились у розмітці (форма їх сабмітить, а
       перетягування між розділами перейменовує), але сховані — правка йде в
       аркуші «Крок N · Розділ».
       Правила стоять САМЕ ТУТ, нижче за `.step-photo-btn` (390) і `.step-ings`
       (1100): при рівній вазі виграє те, що оголошене пізніше, і вгорі файлу
       вони мовчки програвали — рядок і далі показував textarea з чипами.
       Це та сама пастка, що вже тричі била в цьому файлі. */
    .step-row .step-text, .step-row .step-ings,
    .step-row .step-photo-btn, .step-row .step-recipe-link { display:none; }
    /* R3 — заголовки розділів форми й підписи секцій методу. */
    .editor-section { font: var(--text-heading-m); letter-spacing: var(--tracking-heading-m);
           margin: var(--space-2xl) 0 var(--space-sm); }
    .editor-section-row { display:flex; align-items:baseline; justify-content:space-between;
           gap: var(--space-md); }
    .editor-section-row .editor-section { margin-bottom: var(--space-sm); }
    .editor-section-add, .editor-step-add { background:none; border:none; padding:0; cursor:pointer;
           font: var(--text-label-m); color: var(--color-text-accent); }
    .editor-step-add { margin: var(--space-sm) 0 var(--space-lg); }
    .editor-step-label { font: var(--text-overline); letter-spacing: var(--tracking-overline);
           text-transform:uppercase; color: var(--color-text-muted); margin: var(--space-base) 0 var(--space-2xs); }
    /* Чекбокси розділів лишаються у формі — їх тисне «+ Розділ». */
    .recipe-editor > label.is-hidden-control { position:absolute; width:1px; height:1px;
           overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
    /* R3/R8 — окремих вушок перетягування нема ні в інгредієнтів, ні в кроків:
       номер рядка вже стоїть ліворуч, і саме він стає ручкою в режимі
       «Порядок». Стовпчики крапок поруч із номером були б другою міткою на
       тому самому місці; у макеті їх нема взагалі. */
    #ingredient-rows .ingredient-row, .step-row { padding-left:0; }
    /* Перетягування доступне В РЕДАКТОРІ ЗАВЖДИ (рішення власника,
       2026-08-14) — окремого режиму «Порядок» більше нема. Номер рядка і є
       ручка: `touch-action:none`, щоб на телефоні жест тягнув рядок, а не
       гортав сторінку.

       Рядок при цьому НЕ перестає відкривати свій аркуш: перетягування
       починається лише після зсуву на кілька пікселів (поріг у JS), тож тап
       по номеру лишається тапом. Саме тому тут нема `pointer-events:none`
       на .ing-display / .step-display — воно було потрібне режиму, який
       вимикав звичайну поведінку рядка цілком. */
    .row-num { cursor: grab; touch-action: none; }
    .ingredient-row.drag-active .row-num, .step-row.drag-active .row-num { cursor: grabbing; }
    /* Поки рядок тягнеться — прибираємо його звичайні реакції, щоб під
       пальцем нічого не відкривалось. Клас знімається на pointerup. */
    .ingredient-row.drag-active .ing-display,
    .step-row.drag-active .step-display { pointer-events: none; }
    /* Номер рядка — однаковий кружечок для інгредієнтів і кроків. */
    .row-num { flex:none; width:28px; height:28px; border-radius: var(--radius-full);
           background: var(--color-bg-subtle); color: var(--color-text-secondary);
           display:inline-flex; align-items:center; justify-content:center;
           font: var(--text-label-m); }
    .ing-display { gap: var(--space-md); }
    .ing-display-name { flex:1; }
    /* R4 — компактні пігулки дій під списком замість чотирьох бордерних
       кнопок у два ряди. */
    .ing-action { display:inline-flex; align-items:center; gap: var(--space-xs);
           height:36px; padding: 0 var(--space-md); border-radius: var(--radius-full);
           border:1px solid var(--color-border-default); background: var(--color-bg-surface);
           color: var(--color-text-primary); font: var(--text-label-m); cursor:pointer; }
    .ing-action .icon { width:16px; height:16px; }
    .ing-action.is-quiet { border-color:transparent; background:none; color: var(--color-text-muted); }
    /* Степер порцій — компактна пігулка «− N порц. +» біля заголовка. */
    .servings-stepper { display:inline-flex; align-items:center; gap: var(--space-xs); flex:none;
           border:1px solid var(--color-border-default); border-radius: var(--radius-full); padding:2px; }
    .servings-step { display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px;
           border-radius: var(--radius-full); color: var(--color-text-primary); text-decoration:none; }
    .servings-step:hover { background: var(--color-bg-subtle); }
    .servings-step .icon { width:16px; height:16px; }
    .servings-count { font: var(--text-label-m); color: var(--color-text-primary); white-space:nowrap; padding:0 var(--space-xs); }
    /* Інгредієнти — чисті рядки з роздільником (без плашок), чекбокс зліва,
       кількість вирівняна праворуч (див. дизайн-фрейм рецепта). */
    .checklist { display:flex; flex-direction:column; margin-top:10px; }
    .checklist-item { display:flex; align-items:center; gap: var(--space-md); padding: var(--space-md) 2px;
           font: var(--text-body-m); cursor:pointer; width:auto; margin:0; position:relative;
           border-bottom:1px solid var(--color-border-default); }
    .checklist-item:last-child { border-bottom:none; }
    .checklist-item input[type="checkbox"] { position:absolute; opacity:0; width:1px; height:1px; }
    .checklist-box { width:22px; height:22px; flex:none; border:1.5px solid var(--color-border-strong);
           border-radius: var(--radius-sm); display:inline-flex; align-items:center; justify-content:center;
           color:transparent; }
    .checklist-box .icon { width:14px; height:14px; }
    .checklist-item:has(input:checked) .checklist-box { background: var(--color-bg-accent);
           border-color: var(--color-bg-accent); color: var(--color-text-on-accent); }
    .checklist-text { flex:1; min-width:0; }
    .checklist-qty { flex:none; color: var(--color-text-secondary); white-space:nowrap; }
    /* «За смаком» — не виміряна кількість, а її відсутність, тож і ваги в
       рядку має менше, ніж «800 г» поруч. */
    .checklist-qty.is-to-taste { color: var(--color-text-muted); }
    .checklist-item:has(input:checked) .checklist-text { text-decoration: line-through; color: var(--color-text-muted); }
    ol.step-list { padding-left:0; list-style:none; }
    ol.step-list li { margin-bottom:8px; counter-increment: step-counter; }
    ol.step-list { counter-reset: step-counter; }
    /* RD1 — крок методу без плашки: номер у кружку, далі текст. Рамка навколо
       кожного кроку робила з методу список однакових коробок, у якому нема
       ієрархії, — а номер сам по собі вже достатній маркер. Клік лишається:
       позначений крок гасне, замість того щоб заливатись лаймом на всю
       ширину (це читалось як «виділено», а не як «зроблено»). */
    .step-check { display:flex; align-items:flex-start; gap: var(--space-md); cursor:pointer;
           background:none; border:none; border-radius:0; padding: var(--space-md) 0;
           position:relative; font: var(--text-body-l); }
    .step-check::before { content: counter(step-counter); font: var(--text-label-m);
           color: var(--color-text-secondary); flex-shrink:0; width:28px; height:28px;
           border-radius: var(--radius-full); background: var(--color-bg-subtle);
           display:inline-flex; align-items:center; justify-content:center; }
    .step-check input[type="checkbox"] { position:absolute; opacity:0; width:1px; height:1px; }
    .step-check:has(input:checked) span { text-decoration: line-through; color: var(--color-text-muted); }
    .step-check:has(input:checked)::before { color: var(--color-text-muted); }
    .step-check:has(input:checked) .step-ingredient { background:none; color: var(--color-text-muted); }
    /* Inline ingredient mention in a step — a <mark>, so it MUST override the
       browser's default yellow highlight. A faint ~12% accent wash (not the old
       orange box, not loud yellow) + accent bold text: noticeable but calm. */
    .step-ingredient { color: var(--color-text-accent); font-weight:600;
           background: color-mix(in srgb, var(--color-text-accent) 12%, transparent);
           padding: 0 3px; border-radius: 4px; }
    .report-body { padding: 0 var(--space-xl) var(--space-xl); }
    .report-body select, .report-body textarea { width:100%; box-sizing:border-box; }
    .report-file { display:flex; align-items:center; gap: var(--space-md); flex-wrap:wrap; }
    .report-file input[type="file"] { position:absolute; opacity:0; width:1px; height:1px; }
    .report-file .btn { flex:none; }
    .report-file:focus-within .btn { outline:2px solid var(--color-border-focus); outline-offset:2px; }
    #report-photo-name { font: var(--text-body-s); overflow:hidden; text-overflow:ellipsis; }
    .report-actions { display:flex; gap: var(--space-md); margin-top: var(--space-lg); }
    .report-actions .btn { flex:1; justify-content:center; }
    .pill-check { display:inline-flex; align-items:center; gap:6px; background: var(--card);
           border:1px solid var(--border); padding:7px 14px; border-radius:20px; font-size:14px;
           font-weight:400; cursor:pointer; width:auto; margin:0; position:relative;
           transition: background .15s ease, border-color .15s ease, color .15s ease; }
    .pill-check input[type="checkbox"] { position:absolute; opacity:0; width:1px; height:1px; }
    .pill-check:has(input:checked) { background: var(--accent); border-color: var(--accent); color: var(--color-text-on-accent); }
    ol { padding-left: 20px; }
    ol li { margin-bottom: 8px; }
    .lang-switch { display:flex; gap:4px; font-size:12px; }
    .lang-switch a { color: var(--muted); text-decoration:none; padding:2px 6px; border-radius:6px; }
    .lang-switch a.active { color: var(--text); background: var(--card); font-weight:600; }
    .offline-banner { display:none; background: color-mix(in srgb, var(--color-bg-accent) 16%, transparent); color: var(--accent); border:1px solid var(--accent);
           padding: 8px 14px; border-radius: 8px; font-size: 13px; margin-bottom: 14px; }
    .push-prompt-banner { display:none; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap;
           background: var(--card); border:1px solid var(--border); border-radius:8px; padding:10px 14px;
           font-size: 13px; }
    .push-prompt-actions { display:flex; gap:8px; flex-shrink:0; }
    img { max-width: 100%; height: auto; }
    .notif-bell { position:relative; }
    .notif-badge { position:absolute; top:2px; right:2px; background:#ef5350; color:#fff; font-size:10px;
           font-weight:700; line-height:1; min-width:15px; height:15px; border-radius:8px;
           display:flex; align-items:center; justify-content:center; padding:0 3px; }

    /* Мобільний: нижня навігація замінює верхні вкладки (дизайн-система,
       п.6 — 5 підписаних слотів завжди з підписами). Десктоп лишає верхню. */
    @media (max-width: 767px) {
      .bottom-nav { display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
             background: var(--color-bg-surface); border-top: 1px solid var(--color-border-default);
             padding: 4px 4px calc(4px + env(safe-area-inset-bottom)); }
      .tabs { display: none; }
      /* Верхній хедер (лого + аватар) на мобільному прибрано — його функції вже
         дублює нижня навігація (Готувати = домівка, Кабінет = аватар). Контент
         піднімається вгору; лишаємо відступ під статус-бар/виріз. */
      header { display: none; }
      main { padding-top: calc(env(safe-area-inset-top) + var(--space-base)); }
      /* Нижня навігація фіксована й заввишки ~60px (іконка+підпис+падінги) плюс
         safe-area. Лишаємо запас, щоб останній рядок/кнопки картки не ховались
         за плашкою. */
      main { padding-bottom: calc(88px + env(safe-area-inset-bottom)); }
      .bnav-item { position: relative; }
      .bnav-item .notif-badge { position: absolute; top: 0; right: 50%; margin-right: -18px; }
      /* Фіксовані знизу елементи піднімаємо над нижньою навігацією. */
      .floating-save-btn { bottom: calc(76px + env(safe-area-inset-bottom)); }
      /* На телефоні мітла живе в правому нижньому куті — під великий палець
         і подалі від центру, де вона перекривала рядок списку. На десктопі
         лишається по центру: там немає ані великого пальця, ані нижньої
         навігації, від якої треба відсуватись. */
      .floating-broom { bottom: calc(76px + env(safe-area-inset-bottom));
             left:auto; right: max(16px, env(safe-area-inset-right)); transform:none; }
      .floating-broom.show { animation-name: broom-in-right; }
      .floating-broom.out { animation-name: broom-out-right; }
      #cooking-timers-panel { bottom: calc(76px + env(safe-area-inset-bottom)); }
      .undo-toast, #mp-toast { bottom: calc(80px + env(safe-area-inset-bottom)); }
    }

    @media (max-width: 640px) {
      .bar { padding: 10px 14px 0; }
      .bar-actions { gap: 6px; }
      .logo.tab-link { font-size: 17px; }
      .tabs { gap: 4px; }
      .tabs .tab-link { min-width: 0; padding: 8px 10px; font-size: 16px; }
      .notif-bell { padding: 8px 10px; font-size: 16px; }
      .tab-text { display: none; }
      .tab-link.active .tab-text { display: inline; }
      .section-title { display: none; }
      .avatar-link { width: 34px; height: 34px; }
      /* Лишаємо верхній/нижній відступи з safe-area (інакше цей вужчий брейкпоінт
         затирав би padding-bottom під нижню навігацію → плитки ховались за нею). */
      main { padding: calc(env(safe-area-inset-top) + 16px) 16px calc(88px + env(safe-area-inset-bottom)); }
      .ingredient-row { flex-wrap: wrap; }
      .ingredient-row .ing-name { flex: 1 1 100%; min-width: 0; }
      .ingredient-row .ing-brand { flex: 1 1 0; min-width: 0; }
      .ingredient-row .ing-qty,
      .ingredient-row .ing-unit { flex: 0 0 44px; width: 44px; min-width: 0; padding-left: 6px; padding-right: 2px; }
      /* Хрестик ховався, доки назва порожня, бо на телефоні рядок із чотирьох
         полів і так не влазив. Полів у рядку більше нема (R3) — місце є,
         і кнопка потрібна саме на порожньому рядку теж. */
      .ingredient-row .ing-remove { display: inline-block; }
      .step-row .step-text { min-width: 0; }
      /* Ховаємо не самі кнопки, а їхні ФОРМИ: порожня форма шириною 0 все
         одно займала слот у flex і збирала gap з обох боків — назва на
         «До купівлі» (де є форма тогла) починалась на 10px правіше, ніж
         «У наявності», і текст «скакав» при перемиканні вкладок. */
      .swipe-row form:has(.inv-toggle), .swipe-row form:has(.inv-del) { display: none; }
      .mp-day-header { flex-direction: column; align-items: flex-start; gap: 4px; }
      .mp-list .inv-item { flex-wrap: wrap; }
      .mp-list .inv-item .qty { flex: 1 1 100%; }
      #shopping-scope-row { flex-wrap: nowrap; }
      #scope-select { flex: 1; min-width: 0; }
      #scope-select-toggle { min-width: 0; width: 100%; }
      #new-shopping-list-btn { flex: none; white-space: nowrap; }
    }
