@theme {
    --font-onest: "Onest", ui-sans-serif, system-ui, sans-serif;
    --animate-btn-glow: btn-glow 2.4s ease-in-out infinite;
}

/* Брендовый зелёный (--color-brand / --color-brand-dark) объявлен
   ИНЛАЙНОМ в base.html — Tailwind v4 CDN не парсит @theme из внешних
   подключённых стилей, только из inline <style type="text/tailwindcss">
   и из HTML-классов. Раньше тут были --color-brand* — классы bg-brand
   и т.п. не работали, кнопки теряли цвет. */

@keyframes btn-glow {
    0%, 100% { opacity: 0.45; transform: scale(1); }
    50%      { opacity: 0.85; transform: scale(1.08); }
}

body {
    font-family: "Onest", ui-sans-serif, system-ui, sans-serif;
}

/* Глобальный отлов горизонтального оверфлоу.
   Секции пока спроектированы под 1440px (фикс. паддинги/min-width); на
   мобильных это создаёт горизонтальный скролл всей страницы → sticky
   navbar уезжает влево вместе с контентом при панорамировании, а на
   реальных устройствах появляется некрасивый горизонтальный свайп.
   `overflow-x: clip` обрезает выход за границы, НЕ создавая новый scroll
   container (в отличие от `hidden`), поэтому `position: sticky` у
   потомков продолжает работать.
   Временная мера: убрать когда все секции получат полноценный мобильный
   адаптив. */
html, body {
    overflow-x: clip;
}

/* Подсветка совпадений из ES highlight. Браузерный дефолт у <mark> — жёлтый
   фон чёрным, на наших светло-серых карточках читается тяжело. Возвращаем
   нейтральный изумрудный акцент, без жирности (текст и так bold кое-где). */
mark {
    background-color: rgba(13, 167, 117, 0.18); /* brand / 18% */
    color: inherit;
    padding: 0 1px;
    border-radius: 2px;
}

/* ─── Glass-surface ─────────────────────────────────────────────────────
   Единая «стеклянная» поверхность для оверлеев навбара: сам navbar,
   мега-меню каталога, мобильное выпадающее меню, дропдаун поиска
   (мобильный и десктопный). Крутишь подложку/блюр ЗДЕСЬ — меняется
   во всех местах разом.

   Радиусы / тени / бордеры задаются на месте использования (они
   разные: rounded-[10px] у мобильных, [20px] у десктопных и т.п.). */
:root {
    /* Сила блюра для всех стеклянных поверхностей и модальных backdrop'ов.
       Меняешь здесь — меняется в navbar, мега-меню, мобильном меню,
       дропдауне поиска, backdrop'е поиска и contact_modal. */
    --glass-blur: 20px;
}

.glass-surface {
    background-color: rgba(255, 255, 255, 0.7);
    -webkit-backdrop-filter: blur(var(--glass-blur));
    backdrop-filter: blur(var(--glass-blur));
}

/* ─── Модалка поиска ────────────────────────────────────────────────────── */

/* По умолчанию обе версии скрыты */
.search-modal,
.search-modal-mobile,
.search-backdrop { display: none; }

/* Скролл не блокируем — навбар sticky, страница может прокручиваться при
   открытом поиске, JS-обработчик подстраивает позицию dropdown-а с подсказками. */

/* Бэкдроп */
#search-toggle:checked ~ .search-backdrop {
    display: block;
    animation: search-backdrop-in 200ms ease-out both;
}

/* Мобильная версия — только до lg */
@media (max-width: 1023px) {
    /* flex (не block): модалка — flex-col контейнер, чтобы список результатов
       (overflow-y-auto) сжимался под max-h экрана и скроллился, а строка поиска
       оставалась зафиксированной. См. search_dialog.html (мобильный блок). */
    #search-toggle:checked ~ .search-modal-mobile {
        display: flex;
        animation: search-modal-mobile-in 200ms ease-out both;
    }
}

/* Десктопная версия — только от lg */
@media (min-width: 1024px) {
    #search-toggle:checked ~ .search-modal {
        display: block;
        animation: search-modal-in 200ms ease-out both;
    }
    /* Переключение navbar: nav-ссылки ↔ поисковая строка через opacity-fade.
       Оба элемента в одной grid-ячейке (row-start-1 col-start-1) и стэкаются.
       Default: nav-ссылки видимы, search-форма прозрачна и не кликабельна.
       Active: наоборот.
       pointer-events отключаем у скрытого, чтобы клики проходили на видимый.
       Поиск-форма часть navbar-а → navbar (z-65) поверх бэкдропа (z-60),
       иначе инпут был бы размыт. */
    .search-mode-hide,
    .search-mode-show {
        transition: opacity 150ms ease-out;
    }
    .search-mode-show {
        opacity: 0;
        pointer-events: none;
    }
    body:has(#search-toggle:checked) .search-mode-hide {
        opacity: 0;
        pointer-events: none;
    }
    body:has(#search-toggle:checked) .search-mode-show {
        opacity: 1;
        pointer-events: auto;
    }
}

@keyframes search-modal-in {
    from { opacity: 0; transform: translateY(-10px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes search-modal-mobile-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
}
/* На закрытии — fade + лёгкое сжатие к центру (компенсирует артефакт
   backdrop-blur, который без transform визуально «распирает» модалку). */
@keyframes search-modal-mobile-out {
    from { opacity: 1; transform: scale(1); }
    to   { opacity: 0; transform: scale(0.96); }
}
@keyframes search-backdrop-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes search-backdrop-out {
    from { opacity: 1; }
    to   { opacity: 0; }
}

/* display:block !important удерживает элементы видимыми пока играет анимация закрытия.
   ВАЖНО: правила обёрнуты в media queries — иначе на десктопе мобильный модал
   (left-[37px] right-[36px], шире .search-modal с max-w-[1240px]) на is-closing
   получал бы display:block!important и «распирал» сцену. И наоборот на мобиле. */
.search-backdrop.is-closing {
    display: block !important;
}
/* Бэкдроп фейдится быстрее модала (80ms vs 160ms): на десктопе он перекрывает
   navbar (z-index:66 над z-65), и пока бэкдроп не угас — navbar остаётся
   блюрнутым/затемнённым. Быстрый фейд = navbar открывается сразу. */
#search-toggle:checked ~ .search-backdrop.is-closing {
    animation: search-backdrop-out 80ms ease both;
}
@media (max-width: 1023px) {
    .search-modal-mobile.is-closing {
        display: flex !important;
    }
    /* Селектор с #id чтобы перебить анимацию открытия (у неё специфичность 1,2,0) */
    #search-toggle:checked ~ .search-modal-mobile.is-closing {
        animation: search-modal-mobile-out 160ms ease both;
    }
}
@media (min-width: 1024px) {
    .search-modal.is-closing {
        display: block !important;
    }
    /* 80ms (как у backdrop) — чтобы navbar открывался резво,
       без лишнего хвоста из полупрозрачного модала, который его прикрывает. */
    #search-toggle:checked ~ .search-modal.is-closing {
        animation: search-modal-out 80ms ease both;
    }
}
/* На закрытии — быстрый чистый fade. На десктопе модал лежит поверх navbar
   (top:40px перекрывает navbar 15-73px), поэтому затягивать close = задержка
   появления navbar. Без scale — он тут не нужен (на десктопе нет backdrop-blur
   на самом модале, артефакта «распирания» не будет). */
@keyframes search-modal-out {
    from { opacity: 1; }
    to   { opacity: 0; }
}

/* ─── Фильтр-дропдауны ──────────────────────────────────────────────────── */

/* Поворот шеврона при открытом <details> (чип и секция) */
details[open] > summary .filter-chevron,
details[open] > summary .section-chevron {
    transform: rotate(180deg);
}

/* Уберём фокус-рамку у кастомного <summary> — она появляется в Firefox/Safari */
.filter-dropdown > summary,
.filter-dropdown details > summary {
    -webkit-tap-highlight-color: transparent;
}

/* Если контент попапа — список секций, у первой секции снимаем top-padding
   у summary, чтобы не дублировать pt-4 контейнера. Итог: 16px от линии-
   разделителя «Фильтры» до label первой секции — как и для прямого контента. */
.filter-dropdown form .dropdown-content > details:first-of-type > summary {
    padding-top: 0;
}

/* ─── Скроллбар в попапах фильтров ──────────────────────────────────────
   По дизайну: вертикальная тонкая «жилка» (1px белая полоска по центру
   дорожки) + узкий белый pill-thumb поверх. Применяется к .filter-scroll
   (списки чекбоксов) и .dropdown-content (длинные попапы «Все фильтры»).

   Webkit (Chrome/Edge/Safari) получает полный вид через ::-webkit-scrollbar-*:
   track = linear-gradient с прозрачными краями и белой полоской в центре,
   thumb = сплошной белый, полностью скруглён. Firefox через scrollbar-color
   умеет красить только цельный thumb/track — там будет упрощённый, но
   близкий по духу вид. */
.filter-scroll,
.filter-dropdown .dropdown-content {
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.85) transparent;
}
.filter-scroll::-webkit-scrollbar,
.filter-dropdown .dropdown-content::-webkit-scrollbar {
    width: 6px;
}
.filter-scroll::-webkit-scrollbar-track,
.filter-dropdown .dropdown-content::-webkit-scrollbar-track {
    /* Тонкая 1px белая «жилка» по центру 6-пиксельной дорожки. */
    background: linear-gradient(
        to right,
        transparent calc(50% - 0.5px),
        rgba(255, 255, 255, 0.55) calc(50% - 0.5px) calc(50% + 0.5px),
        transparent calc(50% + 0.5px)
    );
}
.filter-scroll::-webkit-scrollbar-thumb,
.filter-dropdown .dropdown-content::-webkit-scrollbar-thumb {
    /* По Figma: 6×23 white pill (full radius). */
    background: #ffffff;
    border-radius: 9999px;
    min-height: 23px;
}

/* ─── Кастомные чекбокс/радио в попапах фильтров ────────────────────────
   Дефолтный <input type=checkbox> с accent-color на тёмном фоне рисуется
   маленьким (13px) и слабо-зелёным. Прячем нативный и рисуем сами:
   16px квадрат с белой обводкой; в :checked — заливка emerald + галка
   ::after. Чек-таргет — вся строка-label (см. шаблон), сюда лишь нужна
   визуальная пилюля. */
.filter-checkbox,
.filter-radio {
    appearance: none;
    -webkit-appearance: none;
    width: 16px;
    height: 16px;
    margin: 0;
    border: 1.5px solid rgba(255, 255, 255, 0.55);
    background: rgba(255, 255, 255, 0.08);
    cursor: pointer;
    transition: background-color 120ms ease, border-color 120ms ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.filter-checkbox { border-radius: 4px; }
.filter-radio { border-radius: 999px; }

.filter-checkbox:hover,
.filter-radio:hover {
    border-color: rgba(255, 255, 255, 0.85);
}

.filter-checkbox:checked,
.filter-radio:checked {
    background: #0DA775;
    border-color: #0DA775;
}

/* Галочка чекбокса — SVG как маска, чтобы оставаться чётким на любом DPI. */
.filter-checkbox:checked::after {
    content: "";
    width: 10px;
    height: 10px;
    background: white;
    -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5l3 3 6-6.5'/></svg>") center/contain no-repeat;
            mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5l3 3 6-6.5'/></svg>") center/contain no-repeat;
}

/* Точка внутри радио в :checked. */
.filter-radio:checked::after {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 999px;
    background: white;
}

.filter-checkbox:focus-visible,
.filter-radio:focus-visible {
    outline: 2px solid rgba(13, 167, 117, 0.5);
    outline-offset: 1px;
}

/* ─── Переключатель вида каталога (сетка / список) ──────────────────────── */

/* Иконки внутри .view-toggle-btn используют currentColor → меняем text-color.
   Default — серый, активная (.is-active) — emerald.
   Используем класс вместо [aria-pressed] селектора: в некоторых браузерах
   динамическая смена aria-pressed не всегда триггерит пересчёт стилей по
   attribute-selector — класс надёжнее. aria-pressed остаётся для a11y. */
.view-toggle-btn {
    color: #888D98;
    transition: color 150ms ease;
}
.view-toggle-btn:hover {
    color: #6c7280;
}
.view-toggle-btn.is-active {
    color: #0DA775;
}

/* ─── Сетка каталога: переключение grid ↔ list ──────────────────────────── */

/* Grid-режим (дефолт). На мобиле — 2 колонки, gap 5px (по дизайну). На lg+
   возвращаемся к 4-колоночной сетке десктопа (gap 20). */
[data-products-grid][data-view="grid"] {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 5px;
}
@media (min-width: 1024px) {
    [data-products-grid][data-view="grid"] {
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 10px;
    }
}
/* На мобиле list-режим отключён — переключателя нет, всегда grid. Если в
   localStorage сохранён "list" с десктопа — форсим grid-разметку CSS'ом
   (атрибут data-view="list" останется, но визуально как grid). */
@media (max-width: 1023px) {
    [data-products-grid][data-view="list"] {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 5px;
    }
    [data-products-grid][data-view="list"] .product-row {
        display: contents;
    }
    [data-products-grid][data-view="list"] .product-list-extras {
        display: none;
    }
}

/* В grid-режиме .product-row — прозрачная обёртка (display: contents):
   её дочерние элементы участвуют в parent grid напрямую. Карточка занимает
   grid-cell, extras скрыты. */
[data-products-grid][data-view="grid"] .product-row {
    display: contents;
}
[data-products-grid][data-view="grid"] .product-list-extras {
    display: none;
}

/* List-режим. Каждая строка — горизонтальный layout: карточка слева
   (W=299 по Figma), серый контейнер с specs+описанием справа. Между ними
   gap=10, серый contains внутреннюю белую карточку описания. */
[data-products-grid][data-view="list"] {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
/* Row — relative-блок: высоту задаёт ТОЛЬКО карточка слева (нормальный поток).
   Extras вынут из потока (position:absolute) — его собственная intrinsic
   height на размер row не влияет, поэтому количество спеков не растягивает
   ни row, ни карточку. */
[data-products-grid][data-view="list"] .product-row {
    display: block;
    position: relative;
}
@media (min-width: 1024px) {
    [data-products-grid][data-view="list"] .product-row > .product-card {
        width: 299px;
    }
}

/* Серый контейнер extras — absolute, привязан к bounding-box row.
   left = 299 (card width) + 10 (gap). Высота = высота row = высота карточки.
   По Figma: radius 30, fill #EAEFF5, padding 10, gap 10.
   Внутри: specs (слева) + desc-card (справа). */
[data-products-grid][data-view="list"] .product-list-extras {
    position: absolute;
    top: 0;
    left: 309px;
    right: 0;
    bottom: 0;
    display: flex;
    background: #EAEFF5;
    border-radius: 30px;
    padding: 10px;
    gap: 10px;
    min-width: 0;
}

/* Specs: занимают левую часть серого контейнера; внутренний padding отделяет
   текст от краёв. Двухколоночная сетка key→value (right-aligned зелёный). */
.product-list-specs {
    flex: 1;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px 40px;
    align-content: start;
    padding: 16px 18px;
    min-width: 0;
    /* Спеков передаём весь список (без лимита). Высоту экстраса задаёт
       карточка слева; overflow:hidden + min-height:0 не дают спекам
       растянуть колонку и через align-stretch потянуть за собой row → card.
       Что не влезло в карточку — обрезается по нижней границе. */
    min-height: 0;
    overflow: hidden;
    /* Fade-out маска на нижние ~28px — частично-видимая последняя строка
       аккуратно растворяется, а не режется пополам. Намёк, что снизу
       есть ещё характеристики (полный список — на детальной). */
    -webkit-mask-image: linear-gradient(to bottom, black calc(100% - 28px), transparent);
    mask-image: linear-gradient(to bottom, black calc(100% - 28px), transparent);
}
/* Label — по контенту (может ужаться/перенестись если очень длинный).
   Value — забирает всё оставшееся, прижимается вправо. Так короткие пары
   («Назначение / для кофе») не плодят пустоту, а длинные value
   получают почти всю ширину строки вместо жёстких 50%. */
.product-list-spec-row {
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: 13px;
    line-height: 1.4;
}
/* max-width 60% на label обязателен: иначе длинный label
   («Максимальная скорость вращения») съедает строку, value уходит в 40px и
   break-word режет «15000 об/мин» посимвольно. С капом label переносится на
   2-3 строки, а value сохраняет цельность чисел/единиц. */
.product-list-spec-label {
    color: #6b7280;
    flex: 0 1 auto;
    max-width: 60%;
    min-width: 0;
    overflow-wrap: break-word;
}
.product-list-spec-value {
    color: #0DA775;
    font-weight: 500;
    text-align: right;
    flex: 1 1 0;
    min-width: 0;
    overflow-wrap: break-word;
}

/* Описание — белая карточка W=300 radius=25 справа внутри серого.
   Высота равна .product-card слева (≈360px) — длинный текст клампается. */
.product-list-desc {
    width: 300px;
    flex-shrink: 0;
    background: white;
    border-radius: 25px;
    padding: 18px 20px;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}
.product-list-desc-title {
    font-size: 14px;
    color: #4b5563;
    font-weight: 500;
    margin-bottom: 10px;
    flex-shrink: 0;
}
.product-list-desc-body {
    font-size: 13px;
    line-height: 1.5;
    color: #6b7280;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 14;
    overflow: hidden;
    text-overflow: ellipsis;
}


/* ─── Слайдер товара ────────────────────────────────────────────────────── */

/* Все слайды скрыты по умолчанию. JS добавляет transition через data-slider. */
.slide {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.35s ease;
}

/* Активный слайд через CSS radio-hack (NoJS совместимость).
   Суффиксы -d / -m нужны, чтобы mobile и desktop инстансы слайдера
   на одной странице не конфликтовали по id. */
#slide-d-0:checked ~ .slides-track #panel-d-0,
#slide-d-1:checked ~ .slides-track #panel-d-1,
#slide-d-2:checked ~ .slides-track #panel-d-2,
#slide-d-3:checked ~ .slides-track #panel-d-3,
#slide-m-0:checked ~ .slides-track #panel-m-0,
#slide-m-1:checked ~ .slides-track #panel-m-1,
#slide-m-2:checked ~ .slides-track #panel-m-2,
#slide-m-3:checked ~ .slides-track #panel-m-3 {
    opacity: 1;
    pointer-events: auto;
}

/* Одиночная фотография: radio-хака нет (radio-инпуты не рендерятся,
   у .slide нет id="panel-N"), и слайд остался бы невидимым с
   `opacity: 0` из дефолта выше. Делаем видимым через :only-child —
   это срабатывает ровно когда в slides-track ровно один .slide. */
.slides-track > .slide:only-child {
    opacity: 1;
    pointer-events: auto;
}

/* Swipe-режим (мобильный слайдер): горизонтальный flex-track + translateX.
   В отличие от opacity-хака, слайды лежат в строку и видно соседние
   при перетаскивании пальцем. JS драйвит transform через .slides-track
   стиль `--swipe-x` (см. product_slider.js). */
[data-swipe] .slides-track {
    display: flex;
    transform: translateX(var(--swipe-x, 0px));
    transition: transform 320ms cubic-bezier(0.4, 0, 0.2, 1);
    will-change: transform;
    touch-action: pan-y;
}
[data-swipe].dragging .slides-track {
    transition: none;
}
[data-swipe] .slides-track > .slide {
    position: relative;
    inset: auto;
    opacity: 1;
    pointer-events: auto;
    width: 100%;
    flex-shrink: 0;
}

/* Точки-индикаторы */
.slider-dot {
    display: block;
    height: 4px;
    width: 20px;
    border-radius: 2px;
    background: #D1D5DB;
    cursor: pointer;
    transition: background-color 0.25s ease, width 0.25s ease;
}

/* Активная точка через CSS radio-hack */
#slide-d-0:checked ~ .slider-dots label[for="slide-d-0"],
#slide-d-1:checked ~ .slider-dots label[for="slide-d-1"],
#slide-d-2:checked ~ .slider-dots label[for="slide-d-2"],
#slide-d-3:checked ~ .slider-dots label[for="slide-d-3"],
#slide-m-0:checked ~ .slider-dots label[for="slide-m-0"],
#slide-m-1:checked ~ .slider-dots label[for="slide-m-1"],
#slide-m-2:checked ~ .slider-dots label[for="slide-m-2"],
#slide-m-3:checked ~ .slider-dots label[for="slide-m-3"] {
    background-color: #0DA775;
    width: 32px;
}

/* ─── Мобильное меню — анимации ────────────────────────────────────────── */

@keyframes menu-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes menu-out {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(-6px); }
}
@keyframes view-slide-right {
    from { opacity: 0; transform: translateX(16px); }
    to   { opacity: 1; transform: translateX(0); }
}
@keyframes view-slide-left {
    from { opacity: 0; transform: translateX(-16px); }
    to   { opacity: 1; transform: translateX(0); }
}

.mobile-menu-in  { animation: menu-in  200ms ease both; }
.mobile-menu-out { animation: menu-out 160ms ease both; }
.view-from-right { animation: view-slide-right 220ms cubic-bezier(0.25,0.46,0.45,0.94) both; }
.view-from-left  { animation: view-slide-left  220ms cubic-bezier(0.25,0.46,0.45,0.94) both; }

/* ─── График price_history ─────────────────────────────────────────────── */

/* Морфинг кривой и точек при смене периода. Нативная CSS-интерполяция
   атрибутов d/cy: Chrome 78+, Firefox 129+ (Сен 2024), Safari 16+.
   В старых браузерах будет мгновенный snap (приемлемый fallback). */
.chart-line {
    transition: d 600ms cubic-bezier(0.4, 0, 0.2, 1);
}
.chart-dots circle {
    transition: cy 600ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Цвет дельты в шапке (рост/падение/0) плавно перетекает при смене периода.
   color наследуется внутрь span с цифрой → меняется и текст числа тоже. */
[data-stat-color] {
    transition: color 400ms ease;
}

/* Бейдж текущей цены на графике: пульс-кольцо на точке «сегодня».
   Переезд по Y при смене периода ведёт JS (price_history.js) — он каждый кадр
   приклеивает кольцо и пилюлю к живой (морфящейся) позиции точки-данных. НЕ
   вешаем сюда transition на cy/transform: CSS-transition на SVG-атрибут
   transform в Chrome перезапускается лишь однажды («первый раз плавно, потом
   скачок»), а двойная анимация (CSS + JS) конфликтует. */
.chart-today-pulse {
    transform-box: fill-box;
    transform-origin: center;
    animation: chart-today-pulse 1.9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
    pointer-events: none;
}
@keyframes chart-today-pulse {
    0%   { transform: scale(1);   opacity: 0.5; }
    70%  { opacity: 0; }
    100% { transform: scale(3.6); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .chart-today-pulse { animation: none; opacity: 0; }
}

