/*
   Характер оформления: живой фон и свечение. Раскладка — в style.css,
   здесь только то, что двигается и светится.

   Видео на этом сайте нет по решению Александра: страницу открывают по
   двадцать раз в день с телефона, и десять мегабайт ролика на каждом входе
   — это счёт за мобильный интернет управляющей. Движение даём чистым CSS:
   он не грузится, не буферизуется и не греет батарею так, как декодер.

   Всё, что здесь анимировано, отключается при prefers-reduced-motion —
   см. конец файла. Человеку, которому от плавающих точек становится
   нехорошо, сайт должен оставаться рабочим.
*/

/* ---------- Живой фон ---------- */

.motes {
    position: fixed;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    pointer-events: none;   /* иначе слой ловит нажатия по всей странице */
}

/* Два медленных пятна света. Они не совпадают по периоду (47 с и 61 с),
   поэтому рисунок не повторяется — глаз не находит цикл и не устаёт. */
.motes-glow {
    position: absolute;
    width: 62vw;
    height: 62vw;
    border-radius: 50%;
    filter: blur(90px);
    opacity: 0.30;
}
.glow-sky {
    background: radial-gradient(circle, rgba(56, 189, 248, 0.55), transparent 68%);
    top: -18vw; left: -12vw;
    animation: drift-a 47s ease-in-out infinite;
}
.glow-teal {
    background: radial-gradient(circle, rgba(45, 212, 191, 0.5), transparent 68%);
    bottom: -22vw; right: -14vw;
    animation: drift-b 61s ease-in-out infinite;
}
@keyframes drift-a {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50%      { transform: translate(9vw, 7vh) scale(1.12); }
}
@keyframes drift-b {
    0%, 100% { transform: translate(0, 0) scale(1.06); }
    50%      { transform: translate(-8vw, -6vh) scale(0.94); }
}

/* Три слоя пыли на разной глубине. Дальний медленнее и тусклее ближнего —
   от этого у плоского фона появляется глубина без единой картинки. */
.motes-field {
    position: absolute;
    inset: -50%;
    background-repeat: repeat;
    opacity: 0.5;
}
.field-far {
    background-image:
        radial-gradient(1px 1px at 20% 30%, rgba(110, 231, 183, 0.5), transparent),
        radial-gradient(1px 1px at 70% 60%, rgba(56, 189, 248, 0.45), transparent),
        radial-gradient(1px 1px at 45% 85%, rgba(45, 212, 191, 0.4), transparent);
    background-size: 240px 240px;
    animation: rise 120s linear infinite;
    opacity: 0.28;
}
.field-mid {
    background-image:
        radial-gradient(1.4px 1.4px at 15% 55%, rgba(45, 212, 191, 0.55), transparent),
        radial-gradient(1.4px 1.4px at 80% 25%, rgba(110, 231, 183, 0.5), transparent);
    background-size: 320px 320px;
    animation: rise 84s linear infinite;
    opacity: 0.34;
}
.field-near {
    background-image:
        radial-gradient(2px 2px at 60% 40%, rgba(56, 189, 248, 0.5), transparent);
    background-size: 460px 460px;
    animation: rise 58s linear infinite;
    opacity: 0.30;
}
@keyframes rise {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-60px, -480px, 0); }
}

/* Крупные искры поверх пыли. Каждая всплывает и гаснет, покачиваясь вбок —
   дом со счётчиками воды, и движение снизу вверх здесь к месту. */
.mote {
    position: absolute;
    bottom: -12vh;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--teal);
    box-shadow: 0 0 10px 2px rgba(45, 212, 191, 0.55);
    opacity: 0;
    animation: float-up linear infinite;
}
@keyframes float-up {
    0%   { opacity: 0;    transform: translate(0, 0) scale(0.6); }
    12%  { opacity: 0.85; }
    50%  { transform: translate(26px, -55vh) scale(1); }
    88%  { opacity: 0.5; }
    100% { opacity: 0;    transform: translate(-14px, -112vh) scale(0.7); }
}

/* Разброс задан руками, а не случайными числами в скрипте: девять точек
   достаточно, чтобы фон не выглядел размеченным по линейке, а лишний
   килобайт JavaScript ради этого не нужен. */
.mote-1 { left:  6%; animation-duration: 26s; animation-delay:  0s; }
.mote-2 { left: 17%; animation-duration: 34s; animation-delay:  6s; width: 3px; height: 3px; background: var(--sky); box-shadow: 0 0 9px 2px rgba(56, 189, 248, 0.5); }
.mote-3 { left: 29%; animation-duration: 30s; animation-delay: 13s; }
.mote-4 { left: 41%; animation-duration: 41s; animation-delay:  3s; width: 5px; height: 5px; background: var(--mint); box-shadow: 0 0 13px 3px rgba(110, 231, 183, 0.45); }
.mote-5 { left: 54%; animation-duration: 28s; animation-delay: 17s; }
.mote-6 { left: 66%; animation-duration: 37s; animation-delay:  9s; width: 3px; height: 3px; background: var(--sky); box-shadow: 0 0 9px 2px rgba(56, 189, 248, 0.5); }
.mote-7 { left: 76%; animation-duration: 32s; animation-delay: 21s; }
.mote-8 { left: 86%; animation-duration: 44s; animation-delay:  2s; width: 5px; height: 5px; background: var(--mint); box-shadow: 0 0 13px 3px rgba(110, 231, 183, 0.45); }
.mote-9 { left: 94%; animation-duration: 29s; animation-delay: 11s; }

/* ---------- Свечение элементов ---------- */

/* Тонкая светящаяся линия по верхней кромке карточки. Она отделяет
   карточку от фона надёжнее рамки: рамка на тёмном почти не видна. */
.card, .flat-card, .stat, .login-box {
    position: relative;
}
.card::before, .flat-card::before, .stat::before, .login-box::before {
    content: "";
    position: absolute;
    top: -1px; left: 14px; right: 14px;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(45, 212, 191, 0.5), transparent);
    pointer-events: none;
}

/* Шапка страницы светится по нижней кромке — граница между шапкой и
   прокручивающимся под ней содержимым. */
header.topbar::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: -1px;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(45, 212, 191, 0.42) 25%,
                rgba(56, 189, 248, 0.42) 75%, transparent);
}
header.topbar { position: sticky; }

/* Строка с просрочкой — не красным фоном во всю ширину, а полоской слева.
   Красная заливка строки в списке из пятнадцати квартир читается как
   «всё сломалось», хотя опоздал один человек. */
tr.late td:first-child { box-shadow: inset 3px 0 0 var(--red); }
tr.paid td:first-child { box-shadow: inset 3px 0 0 var(--green); }

/* Пульс у числа непрочитанного и у долга: короткая вспышка раз в 4 секунды.
   Постоянно мигающий элемент раздражает, редкий — замечают. */
.nav-badge { animation: blip 4s ease-in-out infinite; }
@keyframes blip {
    0%, 88%, 100% { box-shadow: 0 0 0 0 rgba(251, 113, 133, 0); }
    94%           { box-shadow: 0 0 0 4px rgba(251, 113, 133, 0.28); }
}

/* Кнопка-градиент слегка «дышит» при наведении — подтверждение, что
   нажатие принято, до того как страница успела перерисоваться. */
button.primary, .btn.primary { background-size: 160% 160%; transition: background-position 0.5s, filter 0.15s; }
button.primary:hover, .btn.primary:hover { background-position: 100% 0; }

/* ---------- Уважение к настройкам человека ---------- */

@media (prefers-reduced-motion: reduce) {
    .motes-glow, .motes-field, .mote, .nav-badge {
        animation: none !important;
    }
    .mote { opacity: 0.5; }
    * { transition-duration: 0.01ms !important; }
}
