/* ╔═══════════════════════════════════════════════════════════════╗
   ║  AgPen' Organizer — веб-панель. Стили и система тем.          ║
   ╚═══════════════════════════════════════════════════════════════╝

   Палитры перенесены один-в-один из десктопного клиента
   (clients/desktop/src/ui.rs, static CYBERPUNK/WIN95/WINXP/WIN11/FUSION).
   Роли переменных совпадают с полями struct Palette, чтобы правки цвета
   в одном клиенте можно было механически повторить в другом.

   Тема выбирается атрибутом data-theme="cyberpunk|win95|winxp|win11|fusion" на <html>.
   Селектор без тега [data-theme=...] нарочно: тот же атрибут на любом вложенном
   элементе (например .tp-swatch в js/theme-picker.js) локально переопределяет
   переменные внутри себя — так превью темы рисуется без переключения всей страницы.
   Масштаб интерфейса — CSS-свойством zoom на <html> (см. js/theme.js).           */

/* ── Cyberpunk (по умолчанию) ─────────────────────────────────── */
:root,
[data-theme="cyberpunk"] {
  --accent:          rgb(0, 220, 175);
  --accent2:         rgb(255, 50, 150);
  --bg-void:         rgb(0, 0, 0);
  --bg-panel:        rgb(0, 0, 0);
  --bg-widget:       rgb(0, 0, 0);
  --input-bg:        rgb(0, 0, 0);
  --text-main:       rgb(185, 255, 230);
  --text-dim:        rgb(75, 135, 105);
  --border-dim:      rgb(0, 70, 52);
  --warn:            rgb(255, 255, 0);
  --ok:              rgb(0, 255, 0);
  --selection-bg:    rgb(0, 75, 60);
  --inactive-border: rgb(0, 110, 80);
  --hover-bg:        rgb(0, 32, 25);
  --active-bg:       rgb(0, 58, 44);
  --open-bg:         rgb(0, 42, 32);
  --card-bg:         rgb(0, 0, 0);
  --card-local-bg:   rgb(0, 30, 40);
  --active-text:     rgb(255, 255, 255);
  --rounding: 2px;
  --shadow: 0 6px 26px rgba(0, 0, 0, .75);
  color-scheme: dark;
}

/* ── Windows 95 ───────────────────────────────────────────────── */
[data-theme="win95"] {
  --accent:          rgb(0, 0, 128);
  --accent2:         rgb(128, 0, 0);
  --bg-void:         rgb(0, 128, 128);
  --bg-panel:        rgb(192, 192, 192);
  --bg-widget:       rgb(212, 208, 200);
  --input-bg:        rgb(255, 255, 255);
  --text-main:       rgb(0, 0, 0);
  --text-dim:        rgb(70, 70, 70);
  --border-dim:      rgb(128, 128, 128);
  --warn:            rgb(160, 95, 0);
  --ok:              rgb(0, 110, 0);
  --selection-bg:    rgb(178, 180, 191);
  --inactive-border: rgb(100, 100, 100);
  --hover-bg:        rgb(224, 221, 214);
  --active-bg:       rgb(176, 176, 176);
  --open-bg:         rgb(200, 200, 200);
  --card-bg:         rgb(255, 255, 255);
  --card-local-bg:   rgb(225, 240, 250);
  --active-text:     rgb(0, 0, 0);
  --rounding: 0px;
  --shadow: 2px 2px 0 rgba(0, 0, 0, .45);
  color-scheme: light;
}

/* ── Windows XP ───────────────────────────────────────────────── */
[data-theme="winxp"] {
  --accent:          rgb(49, 106, 197);
  --accent2:         rgb(210, 105, 30);
  --bg-void:         rgb(93, 136, 218);
  --bg-panel:        rgb(236, 233, 216);
  --bg-widget:       rgb(245, 243, 231);
  --input-bg:        rgb(255, 255, 255);
  --text-main:       rgb(30, 30, 30);
  --text-dim:        rgb(105, 100, 90);
  --border-dim:      rgb(145, 155, 156);
  --warn:            rgb(175, 115, 0);
  --ok:              rgb(55, 135, 55);
  --selection-bg:    rgb(182, 205, 240);
  --inactive-border: rgb(172, 168, 153);
  --hover-bg:        rgb(252, 244, 220);
  --active-bg:       rgb(222, 215, 190);
  --open-bg:         rgb(240, 236, 222);
  --card-bg:         rgb(255, 255, 255);
  --card-local-bg:   rgb(224, 238, 255);
  --active-text:     rgb(0, 0, 0);
  --rounding: 3px;
  --shadow: 0 4px 14px rgba(0, 0, 0, .3);
  color-scheme: light;
}

/* ── Windows 11 ───────────────────────────────────────────────── */
[data-theme="win11"] {
  --accent:          rgb(0, 103, 192);
  --accent2:         rgb(196, 43, 28);
  --bg-void:         rgb(243, 243, 243);
  --bg-panel:        rgb(251, 251, 251);
  --bg-widget:       rgb(255, 255, 255);
  --input-bg:        rgb(255, 255, 255);
  --text-main:       rgb(28, 28, 28);
  --text-dim:        rgb(96, 96, 96);
  --border-dim:      rgb(224, 224, 224);
  --warn:            rgb(157, 93, 0);
  --ok:              rgb(15, 123, 15);
  --selection-bg:    rgb(204, 228, 247);
  --inactive-border: rgb(208, 208, 208);
  --hover-bg:        rgb(246, 246, 246);
  --active-bg:       rgb(238, 238, 238);
  --open-bg:         rgb(249, 249, 249);
  --card-bg:         rgb(255, 255, 255);
  --card-local-bg:   rgb(235, 244, 255);
  --active-text:     rgb(0, 0, 0);
  --rounding: 6px;
  --shadow: 0 8px 28px rgba(0, 0, 0, .12);
  color-scheme: light;
}

/* ── Fusion ───────────────────────────────────────────────────── */
[data-theme="fusion"] {
  --accent:          rgb(42, 130, 218);
  --accent2:         rgb(230, 120, 60);
  --bg-void:         rgb(37, 37, 37);
  --bg-panel:        rgb(53, 53, 53);
  --bg-widget:       rgb(66, 66, 66);
  --input-bg:        rgb(42, 42, 42);
  --text-main:       rgb(220, 220, 220);
  --text-dim:        rgb(150, 150, 150);
  --border-dim:      rgb(80, 80, 80);
  --warn:            rgb(230, 200, 80);
  --ok:              rgb(100, 200, 100);
  --selection-bg:    rgb(42, 90, 140);
  --inactive-border: rgb(100, 100, 100);
  --hover-bg:        rgb(70, 70, 70);
  --active-bg:       rgb(85, 85, 85);
  --open-bg:         rgb(60, 60, 60);
  --card-bg:         rgb(45, 45, 45);
  --card-local-bg:   rgb(40, 55, 70);
  --active-text:     rgb(255, 255, 255);
  --rounding: 2px;
  --shadow: 0 6px 22px rgba(0, 0, 0, .55);
  color-scheme: dark;
}

/* ── Claude ───────────────────────────────────────────────────── */
[data-theme="claude"] {
  --accent:          rgb(204, 120, 92);
  --accent2:         rgb(178, 74, 59);
  --bg-void:         rgb(246, 244, 235);
  --bg-panel:        rgb(255, 253, 246);
  --bg-widget:       rgb(240, 237, 225);
  --input-bg:        rgb(255, 255, 255);
  --text-main:       rgb(38, 35, 32);
  --text-dim:        rgb(120, 113, 104);
  --border-dim:      rgb(223, 216, 202);
  --warn:            rgb(180, 130, 40);
  --ok:              rgb(107, 142, 90);
  --selection-bg:    rgb(240, 220, 208);
  --inactive-border: rgb(205, 197, 182);
  --hover-bg:        rgb(238, 233, 220);
  --active-bg:       rgb(230, 223, 208);
  --open-bg:         rgb(243, 238, 227);
  --card-bg:         rgb(255, 255, 255);
  --card-local-bg:   rgb(250, 234, 224);
  --active-text:     rgb(30, 26, 22);
  --rounding: 10px;
  --shadow: 0 6px 20px rgba(60, 45, 30, .12);
  color-scheme: light;
}

/* ── Claude (тёмная) ──────────────────────────────────────────── */
[data-theme="claude-dark"] {
  --accent:          rgb(214, 138, 111);
  --accent2:         rgb(224, 106, 84);
  --bg-void:         rgb(28, 26, 24);
  --bg-panel:        rgb(38, 35, 32);
  --bg-widget:       rgb(48, 44, 40);
  --input-bg:        rgb(32, 29, 27);
  --text-main:       rgb(237, 233, 225);
  --text-dim:        rgb(150, 143, 133);
  --border-dim:      rgb(64, 58, 52);
  --warn:            rgb(214, 168, 90);
  --ok:              rgb(133, 173, 113);
  --selection-bg:    rgb(72, 45, 36);
  --inactive-border: rgb(80, 73, 65);
  --hover-bg:        rgb(46, 42, 38);
  --active-bg:       rgb(58, 52, 47);
  --open-bg:         rgb(40, 37, 34);
  --card-bg:         rgb(38, 35, 32);
  --card-local-bg:   rgb(58, 38, 32);
  --active-text:     rgb(255, 250, 245);
  --rounding: 10px;
  --shadow: 0 8px 26px rgba(0, 0, 0, .45);
  color-scheme: dark;
}

/* ── «Я попросил Клода сделать тему, и вот что вышло» ────────────
   Своя тема, без ограничений. Взял то, над чем в интернете шутят —
   что все ИИ-интерфейсы по умолчанию тянутся к фиолетовому градиенту —
   и сделал это осознанным выбором, а не багом.                    */
[data-theme="askclaude"] {
  --accent:          rgb(139, 92, 246);
  --accent2:         rgb(236, 72, 153);
  --bg-void:         rgb(13, 10, 24);
  --bg-panel:        rgb(20, 16, 35);
  --bg-widget:       rgb(28, 22, 48);
  --input-bg:        rgb(24, 19, 40);
  --text-main:       rgb(230, 225, 245);
  --text-dim:        rgb(150, 140, 175);
  --border-dim:      rgb(55, 45, 85);
  --warn:            rgb(250, 190, 80);
  --ok:              rgb(110, 230, 180);
  --selection-bg:    rgb(60, 40, 100);
  --inactive-border: rgb(80, 65, 120);
  --hover-bg:        rgb(35, 27, 58);
  --active-bg:       rgb(48, 36, 80);
  --open-bg:         rgb(30, 23, 50);
  --card-bg:         rgb(20, 16, 35);
  --card-local-bg:   rgb(45, 25, 60);
  --active-text:     rgb(255, 255, 255);
  --rounding: 10px;
  --shadow: 0 10px 34px rgba(139, 92, 246, .28);
  color-scheme: dark;
}

/* ── Dracula ─────────────────────────────────────────────────── */
[data-theme="dracula"] {
  --accent:          rgb(189, 147, 249);
  --accent2:         rgb(255, 121, 198);
  --bg-void:         rgb(30, 32, 42);
  --bg-panel:        rgb(40, 42, 54);
  --bg-widget:       rgb(55, 58, 75);
  --input-bg:        rgb(45, 47, 62);
  --text-main:       rgb(248, 248, 242);
  --text-dim:        rgb(130, 140, 175);
  --border-dim:      rgb(75, 80, 105);
  --warn:            rgb(255, 184, 108);
  --ok:              rgb(80, 250, 123);
  --selection-bg:    rgb(68, 71, 90);
  --inactive-border: rgb(90, 95, 120);
  --hover-bg:        rgb(50, 52, 68);
  --active-bg:       rgb(65, 68, 88);
  --open-bg:         rgb(55, 58, 75);
  --card-bg:         rgb(40, 42, 54);
  --card-local-bg:   rgb(55, 48, 75);
  --active-text:     rgb(248, 248, 242);
  --rounding: 4px;
  --shadow: 0 6px 24px rgba(0, 0, 0, .5);
  color-scheme: dark;
}

/* ── Гламур ─────────────────────────────────────────────────────
   «Розовый — это цвет настоящих мужиков». Бархатный тёмный фон,
   неоновый Hot Pink и золотые акценты. Брутальная гламурность. */
[data-theme="glamour"] {
  --accent:          rgb(255, 20, 147);
  --accent2:         rgb(255, 215, 0);
  --bg-void:         rgb(15, 5, 10);
  --bg-panel:        rgb(25, 10, 20);
  --bg-widget:       rgb(45, 15, 35);
  --input-bg:        rgb(10, 0, 5);
  --text-main:       rgb(255, 230, 240);
  --text-dim:        rgb(180, 110, 140);
  --border-dim:      rgb(100, 30, 60);
  --warn:            rgb(255, 105, 180);
  --ok:              rgb(0, 250, 154);
  --selection-bg:    rgb(80, 20, 50);
  --inactive-border: rgb(120, 40, 70);
  --hover-bg:        rgb(55, 15, 40);
  --active-bg:       rgb(75, 20, 55);
  --open-bg:         rgb(35, 10, 25);
  --card-bg:         rgb(25, 10, 20);
  --card-local-bg:   rgb(40, 15, 35);
  --active-text:     rgb(255, 255, 255);
  --rounding: 12px;
  --shadow: 0 10px 30px rgba(255, 20, 147, .3), 0 0 15px rgba(255, 215, 0, .15);
  color-scheme: dark;
}

/* ── Гламур (светлый) ──────────────────────────────────────────
   Дневной гламур: розовый зефир, золото и жёсткая мужская элегантность. */
[data-theme="glamour-light"] {
  --accent:          rgb(219, 0, 110);
  --accent2:         rgb(191, 132, 0);
  --bg-void:         rgb(255, 221, 237);
  --bg-panel:        rgb(255, 225, 237);
  --bg-widget:       rgb(255, 197, 226);
  --input-bg:        rgb(255, 255, 255);
  --text-main:       rgb(70, 20, 45);
  --text-dim:        rgb(160, 95, 130);
  --border-dim:      rgb(240, 180, 210);
  --warn:            rgb(184, 90, 0);
  --ok:              rgb(0, 150, 90);
  --selection-bg:    rgb(255, 182, 213);
  --inactive-border: rgb(220, 150, 185);
  --hover-bg:        rgb(255, 224, 238);
  --active-bg:       rgb(255, 205, 228);
  --open-bg:         rgb(255, 232, 243);
  --card-bg:         rgb(255, 250, 252);
  --card-local-bg:   rgb(255, 238, 246);
  --active-text:     rgb(70, 20, 45);
  --rounding: 12px;
  --shadow: 0 10px 30px rgba(219, 0, 110, .18), 0 0 18px rgba(212, 175, 55, .25);
  color-scheme: light;
}

/* ── CRT Terminal ────────────────────────────────────────────────
   Старый зелёный терминал: phosphor green, янтарные предупреждения,
   почти чёрный фон и лёгкое свечение. */
[data-theme="crt-terminal"] {
  --accent:          rgb(80, 255, 120);
  --accent2:         rgb(255, 190, 70);
  --bg-void:         rgb(3, 8, 5);
  --bg-panel:        rgb(7, 15, 9);
  --bg-widget:       rgb(11, 23, 14);
  --input-bg:        rgb(5, 12, 7);
  --text-main:       rgb(185, 255, 195);
  --text-dim:        rgb(85, 150, 95);
  --border-dim:      rgb(35, 85, 45);
  --warn:            rgb(255, 190, 70);
  --ok:              rgb(80, 255, 120);
  --selection-bg:    rgb(20, 90, 35);
  --inactive-border: rgb(45, 105, 55);
  --hover-bg:        rgb(13, 35, 18);
  --active-bg:       rgb(17, 55, 25);
  --open-bg:         rgb(10, 28, 14);
  --card-bg:         rgb(7, 15, 9);
  --card-local-bg:   rgb(8, 32, 16);
  --active-text:     rgb(225, 255, 230);
  --rounding: 2px;
  --shadow: 0 0 12px rgba(80, 255, 120, .12),
             0 6px 24px rgba(0, 0, 0, .7);
  color-scheme: dark;
}

/* ── Чёрная ───────────────────────────────────────────────────────
   Чистый OLED-чёрный: на таких экранах нулевой пиксель не светится
   вовсе. Монохром — акцент отличается от текста только яркостью. */
[data-theme="black"] {
  --accent:          rgb(255, 255, 255);
  --accent2:         rgb(170, 170, 170);
  --bg-void:         rgb(0, 0, 0);
  --bg-panel:        rgb(0, 0, 0);
  --bg-widget:       rgb(10, 10, 10);
  --input-bg:        rgb(0, 0, 0);
  --text-main:       rgb(235, 235, 235);
  --text-dim:        rgb(130, 130, 130);
  --border-dim:      rgb(55, 55, 55);
  --warn:            rgb(230, 200, 110);
  --ok:              rgb(140, 210, 150);
  --selection-bg:    rgb(48, 48, 48);
  --inactive-border: rgb(80, 80, 80);
  --hover-bg:        rgb(20, 20, 20);
  --active-bg:       rgb(36, 36, 36);
  --open-bg:         rgb(14, 14, 14);
  --card-bg:         rgb(0, 0, 0);
  --card-local-bg:   rgb(18, 18, 18);
  --active-text:     rgb(255, 255, 255);
  --rounding: 2px;
  --shadow: 0 6px 24px rgba(0, 0, 0, .9);
  color-scheme: dark;
}

/* ── Белая ────────────────────────────────────────────────────────
   Зеркало «Чёрной»: чистая бумага без единого цветного пятна. */
[data-theme="white"] {
  --accent:          rgb(0, 0, 0);
  --accent2:         rgb(90, 90, 90);
  --bg-void:         rgb(255, 255, 255);
  --bg-panel:        rgb(255, 255, 255);
  --bg-widget:       rgb(247, 247, 247);
  --input-bg:        rgb(255, 255, 255);
  --text-main:       rgb(20, 20, 20);
  --text-dim:        rgb(110, 110, 110);
  --border-dim:      rgb(205, 205, 205);
  --warn:            rgb(150, 100, 0);
  --ok:              rgb(30, 110, 45);
  --selection-bg:    rgb(220, 220, 220);
  --inactive-border: rgb(175, 175, 175);
  --hover-bg:        rgb(242, 242, 242);
  --active-bg:       rgb(230, 230, 230);
  --open-bg:         rgb(250, 250, 250);
  --card-bg:         rgb(255, 255, 255);
  --card-local-bg:   rgb(240, 240, 240);
  --active-text:     rgb(0, 0, 0);
  --rounding: 2px;
  --shadow: 0 4px 16px rgba(0, 0, 0, .1);
  color-scheme: light;
}

/* ── Nord ─────────────────────────────────────────────────────────
   Холодная арктическая палитра: приглушённый сине-серый. */
[data-theme="nord"] {
  --accent:          rgb(136, 192, 208);
  --accent2:         rgb(191, 97, 106);
  --bg-void:         rgb(38, 44, 56);
  --bg-panel:        rgb(46, 52, 64);
  --bg-widget:       rgb(59, 66, 82);
  --input-bg:        rgb(42, 48, 60);
  --text-main:       rgb(216, 222, 233);
  --text-dim:        rgb(143, 155, 175);
  --border-dim:      rgb(67, 76, 94);
  --warn:            rgb(235, 203, 139);
  --ok:              rgb(163, 190, 140);
  --selection-bg:    rgb(67, 76, 94);
  --inactive-border: rgb(86, 95, 114);
  --hover-bg:        rgb(52, 59, 73);
  --active-bg:       rgb(67, 76, 94);
  --open-bg:         rgb(48, 55, 68);
  --card-bg:         rgb(46, 52, 64);
  --card-local-bg:   rgb(56, 68, 82);
  --active-text:     rgb(236, 239, 244);
  --rounding: 5px;
  --shadow: 0 6px 22px rgba(0, 0, 0, .45);
  color-scheme: dark;
}

/* ── Solarized (тёмная) ───────────────────────────────────────── */
[data-theme="solarized-dark"] {
  --accent:          rgb(38, 139, 210);
  --accent2:         rgb(203, 75, 22);
  --bg-void:         rgb(0, 43, 54);
  --bg-panel:        rgb(7, 54, 66);
  --bg-widget:       rgb(14, 65, 78);
  --input-bg:        rgb(0, 38, 48);
  --text-main:       rgb(147, 161, 161);
  --text-dim:        rgb(101, 123, 131);
  --border-dim:      rgb(28, 79, 92);
  --warn:            rgb(181, 137, 0);
  --ok:              rgb(133, 153, 0);
  --selection-bg:    rgb(20, 75, 89);
  --inactive-border: rgb(45, 96, 108);
  --hover-bg:        rgb(10, 58, 70);
  --active-bg:       rgb(18, 72, 85);
  --open-bg:         rgb(6, 50, 61);
  --card-bg:         rgb(7, 54, 66);
  --card-local-bg:   rgb(12, 62, 74);
  --active-text:     rgb(253, 246, 227);
  --rounding: 3px;
  --shadow: 0 6px 22px rgba(0, 0, 0, .5);
  color-scheme: dark;
}

/* ── Solarized (светлая) ──────────────────────────────────────── */
[data-theme="solarized-light"] {
  --accent:          rgb(38, 139, 210);
  --accent2:         rgb(203, 75, 22);
  --bg-void:         rgb(238, 232, 213);
  --bg-panel:        rgb(253, 246, 227);
  --bg-widget:       rgb(245, 238, 219);
  --input-bg:        rgb(255, 252, 242);
  --text-main:       rgb(88, 110, 117);
  --text-dim:        rgb(131, 148, 150);
  --border-dim:      rgb(216, 208, 187);
  --warn:            rgb(181, 137, 0);
  --ok:              rgb(110, 128, 0);
  --selection-bg:    rgb(223, 214, 190);
  --inactive-border: rgb(200, 192, 172);
  --hover-bg:        rgb(245, 239, 221);
  --active-bg:       rgb(233, 226, 205);
  --open-bg:         rgb(249, 243, 226);
  --card-bg:         rgb(253, 246, 227);
  --card-local-bg:   rgb(238, 240, 222);
  --active-text:     rgb(7, 54, 66);
  --rounding: 3px;
  --shadow: 0 4px 16px rgba(101, 123, 131, .2);
  color-scheme: light;
}

/* ── Gruvbox ──────────────────────────────────────────────────────
   Тёплая ретро-палитра: горчица и терракот по кофейному фону. */
[data-theme="gruvbox"] {
  --accent:          rgb(250, 189, 47);
  --accent2:         rgb(251, 73, 52);
  --bg-void:         rgb(29, 32, 33);
  --bg-panel:        rgb(40, 40, 40);
  --bg-widget:       rgb(60, 56, 54);
  --input-bg:        rgb(32, 32, 32);
  --text-main:       rgb(235, 219, 178);
  --text-dim:        rgb(168, 153, 132);
  --border-dim:      rgb(80, 73, 69);
  --warn:            rgb(254, 128, 25);
  --ok:              rgb(184, 187, 38);
  --selection-bg:    rgb(80, 73, 69);
  --inactive-border: rgb(102, 92, 84);
  --hover-bg:        rgb(50, 48, 47);
  --active-bg:       rgb(68, 64, 60);
  --open-bg:         rgb(44, 42, 41);
  --card-bg:         rgb(40, 40, 40);
  --card-local-bg:   rgb(55, 52, 44);
  --active-text:     rgb(251, 241, 199);
  --rounding: 3px;
  --shadow: 0 6px 22px rgba(0, 0, 0, .55);
  color-scheme: dark;
}

/* ── Matrix ───────────────────────────────────────────────────────
   Зелёный люминофор P1: один цвет, только разная яркость. */
[data-theme="matrix"] {
  --accent:          rgb(0, 255, 65);
  --accent2:         rgb(0, 180, 45);
  --bg-void:         rgb(0, 0, 0);
  --bg-panel:        rgb(0, 8, 2);
  --bg-widget:       rgb(0, 16, 5);
  --input-bg:        rgb(0, 5, 1);
  --text-main:       rgb(120, 255, 150);
  --text-dim:        rgb(0, 150, 45);
  --border-dim:      rgb(0, 80, 25);
  --warn:            rgb(180, 255, 100);
  --ok:              rgb(0, 255, 65);
  --selection-bg:    rgb(0, 70, 22);
  --inactive-border: rgb(0, 110, 35);
  --hover-bg:        rgb(0, 24, 8);
  --active-bg:       rgb(0, 45, 15);
  --open-bg:         rgb(0, 18, 6);
  --card-bg:         rgb(0, 8, 2);
  --card-local-bg:   rgb(0, 28, 10);
  --active-text:     rgb(200, 255, 210);
  --rounding: 0px;
  --shadow: 0 0 14px rgba(0, 255, 65, .18), 0 6px 24px rgba(0, 0, 0, .8);
  color-scheme: dark;
}

/* ── Amber ────────────────────────────────────────────────────────
   Янтарный монитор 80-х — тот же принцип, что Matrix, другой люминофор. */
[data-theme="amber"] {
  --accent:          rgb(255, 176, 0);
  --accent2:         rgb(255, 120, 0);
  --bg-void:         rgb(10, 6, 0);
  --bg-panel:        rgb(18, 11, 0);
  --bg-widget:       rgb(28, 18, 2);
  --input-bg:        rgb(13, 8, 0);
  --text-main:       rgb(255, 200, 90);
  --text-dim:        rgb(170, 115, 20);
  --border-dim:      rgb(92, 60, 5);
  --warn:            rgb(255, 225, 120);
  --ok:              rgb(230, 190, 60);
  --selection-bg:    rgb(80, 50, 0);
  --inactive-border: rgb(120, 78, 8);
  --hover-bg:        rgb(35, 22, 2);
  --active-bg:       rgb(58, 37, 3);
  --open-bg:         rgb(26, 16, 1);
  --card-bg:         rgb(18, 11, 0);
  --card-local-bg:   rgb(40, 25, 2);
  --active-text:     rgb(255, 228, 160);
  --rounding: 0px;
  --shadow: 0 0 14px rgba(255, 176, 0, .15), 0 6px 24px rgba(0, 0, 0, .8);
  color-scheme: dark;
}

/* ── Сепия ────────────────────────────────────────────────────────
   Режим чтения: тёплая бумага, минимум контрастных пятен. */
[data-theme="sepia"] {
  --accent:          rgb(140, 90, 40);
  --accent2:         rgb(160, 70, 45);
  --bg-void:         rgb(240, 228, 205);
  --bg-panel:        rgb(248, 238, 219);
  --bg-widget:       rgb(236, 223, 199);
  --input-bg:        rgb(252, 245, 231);
  --text-main:       rgb(60, 48, 34);
  --text-dim:        rgb(133, 116, 92);
  --border-dim:      rgb(213, 197, 169);
  --warn:            rgb(165, 115, 20);
  --ok:              rgb(95, 120, 60);
  --selection-bg:    rgb(226, 210, 180);
  --inactive-border: rgb(196, 178, 148);
  --hover-bg:        rgb(240, 229, 208);
  --active-bg:       rgb(228, 214, 188);
  --open-bg:         rgb(245, 235, 216);
  --card-bg:         rgb(250, 242, 226);
  --card-local-bg:   rgb(240, 231, 206);
  --active-text:     rgb(45, 35, 24);
  --rounding: 4px;
  --shadow: 0 4px 16px rgba(90, 70, 45, .18);
  color-scheme: light;
}

/* ── СамГУ ────────────────────────────────────────────────────────
   Фирменные цвета университета: синь со страницы входа в ЛК, белые
   карточки нового кабинета и мятная подсветка «своего» — той самой,
   которой там помечены зачётка и согласование работ.                */
[data-theme="ssau"] {
  --accent:          rgb(23, 105, 199);
  --accent2:         rgb(224, 106, 55);
  --bg-void:         rgb(238, 243, 250);
  --bg-panel:        rgb(255, 255, 255);
  --bg-widget:       rgb(246, 249, 253);
  --input-bg:        rgb(233, 240, 250);
  --text-main:       rgb(26, 37, 54);
  --text-dim:        rgb(108, 124, 146);
  --border-dim:      rgb(213, 224, 238);
  --warn:            rgb(190, 120, 10);
  --ok:              rgb(24, 145, 105);
  --selection-bg:    rgb(206, 226, 250);
  --inactive-border: rgb(188, 204, 226);
  --hover-bg:        rgb(238, 245, 253);
  --active-bg:       rgb(219, 233, 250);
  --open-bg:         rgb(245, 249, 254);
  --card-bg:         rgb(255, 255, 255);
  --card-local-bg:   rgb(223, 241, 234);
  --active-text:     rgb(255, 255, 255);
  --rounding: 8px;
  --shadow: 0 6px 22px rgba(23, 60, 120, .14);
  color-scheme: light;
}

/* ── PS3 XMB ──────────────────────────────────────────────────────
   Пара к фоновому шейдеру «Волны XMB» (fx.js): ледяной синий на почти
   чёрной синей толще, тёплое золото под accent2 — как трофеи и подсветка
   выбранного пункта в настоящем интерфейсе PS3. Работает и без этого фона
   (шейдер — отдельная настройка), но задуман именно в паре с ним. */
[data-theme="ps3"] {
  --accent:          rgb(0, 174, 239);
  --accent2:         rgb(255, 176, 59);
  --bg-void:         rgb(4, 10, 20);
  --bg-panel:        rgb(9, 19, 36);
  --bg-widget:       rgb(15, 29, 52);
  --input-bg:        rgb(7, 15, 28);
  --text-main:       rgb(225, 236, 248);
  --text-dim:        rgb(120, 145, 175);
  --border-dim:      rgb(28, 48, 78);
  --warn:            rgb(255, 176, 59);
  --ok:              rgb(70, 210, 160);
  --selection-bg:    rgb(18, 55, 95);
  --inactive-border: rgb(38, 62, 96);
  --hover-bg:        rgb(13, 26, 48);
  --active-bg:       rgb(20, 42, 74);
  --open-bg:         rgb(11, 23, 43);
  --card-bg:         rgb(9, 19, 36);
  --card-local-bg:   rgb(13, 34, 58);
  --active-text:     rgb(255, 255, 255);
  --rounding: 4px;
  --shadow: 0 8px 30px rgba(0, 150, 220, .25);
  color-scheme: dark;
}

/* ═══════════ База ═══════════ */
* { box-sizing: border-box; }

/* Фон — только на html, не на body: у body своего быть не должно, иначе он,
   как обычный непозиционированный блок, перекрывает #fx-canvas (position:fixed;
   z-index:-1) — непозиционированное содержимое всегда рисуется поверх
   отрицательных z-index, независимо от самого значения z-index. */
html { background: var(--bg-void); }

body {
  margin: 0;
  color: var(--text-main);
  font-family: "Cascadia Code", "Consolas", ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 14px;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

::selection { background: var(--selection-bg); color: var(--active-text); }

.hidden { display: none !important; }
/* Дубликат ☰ в шапке нужен только там, где список превращается в шторку. */
.mobile-only { display: none; }
@media (max-width: 900px) { .mobile-only { display: inline-block; } }
.accent  { color: var(--accent); }
.accent2 { color: var(--accent2); }
.dim, .muted { color: var(--text-dim); }
.ok   { color: var(--ok); }
.warn { color: var(--warn); }
.muted { font-size: 11px; }
.mono { font-family: inherit; }
.strong { font-weight: 700; }
.nowrap { white-space: nowrap; }
.grow { flex: 1; }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.col { display: flex; flex-direction: column; gap: 6px; }
.sp8  { height: 8px; }
.sp16 { height: 16px; }

/* ═══════════ Управляющие элементы ═══════════ */
button {
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
  background: var(--bg-widget);
  color: var(--accent);
  border: 1px solid var(--border-dim);
  padding: 7px 12px;
  border-radius: var(--rounding);
  transition: background .12s ease, border-color .12s ease;
  /* Подписи вида "[ ТЕКСТ ]" не переносим внутри кнопки — иначе на узком
     экране закрывающая "]" может оторваться и уйти на вторую строку одна.
     Если кнопка целиком не влезает — пусть переносится сама кнопка (это уже
     умеют flex-контейнеры с flex-wrap, см. .modal-actions), а не текст внутри неё. */
  white-space: nowrap;
}
button:hover:not(:disabled) { border-color: var(--accent); background: var(--hover-bg); }
button:active:not(:disabled) { background: var(--active-bg); }
button:disabled { opacity: .45; cursor: not-allowed; }
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
button.mini { padding: 3px 9px; font-size: 11px; }
button.danger { color: var(--accent2); }
button.danger:hover:not(:disabled) { border-color: var(--accent2); }
button.primary { border-color: var(--accent); color: var(--accent); font-weight: 700; }
button.ghost { background: none; border-color: transparent; }
button.ghost:hover:not(:disabled) { border-color: var(--border-dim); }

input, select, textarea {
  font-family: inherit;
  font-size: 13px;
  background: var(--input-bg);
  color: var(--text-main);
  border: 1px solid var(--border-dim);
  border-radius: var(--rounding);
  padding: 8px;
  width: 100%;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); }
input::placeholder, textarea::placeholder { color: var(--text-dim); opacity: .8; }
textarea { min-height: 68px; resize: vertical; }
input[type="checkbox"] { width: auto; accent-color: var(--accent); }
input[type="date"], input[type="datetime-local"], input[type="number"] { color-scheme: inherit; }

label.field { display: block; color: var(--text-dim); font-size: 11px; margin: 10px 0 3px; }
label.chk { display: flex; align-items: center; gap: 8px; margin-top: 10px; cursor: pointer; font-size: 13px; }
label.chk input { flex: 0 0 auto; }

/* ═══════════ Иконки (js/icons.js) ═══════════ */
/* Размер в em, а не в px: иконка масштабируется вместе с текстом кнопки. */
.ico {
  width: 1.15em;
  height: 1.15em;
  flex: none;
  vertical-align: -.2em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Кнопка из одной иконки: подпись живёт в title, поэтому paddings симметричны. */
button.icon-only { padding-left: 7px; padding-right: 7px; }
/* Иконка перед подписью отбивается от неё — где угодно, а не только в кнопке:
   иконки стоят и в заголовках <summary>, и в бирках, и в строках расписания.
   Перечислять родителей по одному пришлось бы дописывать при каждом новом
   месте, и отступ забывался бы именно там.

   Через .icon-only, а не :only-child: подпись рядом с иконкой — текстовый
   узел, а :only-child считает только элементы, поэтому SVG для него всегда
   «единственный ребёнок» и отступ не применялся бы никогда. */
.ico:first-child { margin-right: 5px; }
button.icon-only > .ico { margin-right: 0; }

.badge {
  display: inline-block;
  font-size: 10px;
  border: 1px solid var(--border-dim);
  border-radius: 20px;
  padding: 1px 8px;
  margin-left: 6px;
  vertical-align: middle;
  white-space: nowrap;
}
.badge.b-accent  { color: var(--accent);  border-color: var(--accent); }
.badge.b-accent2 { color: var(--accent2); border-color: var(--accent2); }
.badge.b-warn    { color: var(--warn);    border-color: var(--warn); }

.frame {
  background: var(--bg-panel);
  border: 1px solid var(--border-dim);
  border-radius: var(--rounding);
  padding: 16px;
  margin: 12px 0;
  max-width: 460px;
}
.frame.wide { max-width: 100%; }
.frame > h3 {
  margin: 0 0 10px;
  color: var(--accent2);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .04em;
}

.sep { height: 1px; background: var(--border-dim); margin: 14px 0; border: 0; }

/* ═══════════ Таблицы ═══════════ */
.utable { border-collapse: collapse; width: 100%; font-size: 12px; }
.utable th {
  text-align: left;
  color: var(--text-dim);
  font-size: 10px;
  padding: 5px 8px;
  font-weight: 700;
  white-space: nowrap;
  border-bottom: 1px solid var(--border-dim);
}
.utable td { padding: 5px 8px; border-top: 1px solid var(--border-dim); vertical-align: middle; }
.utable tr:hover td { background: var(--hover-bg); }
.table-scroll { overflow-x: auto; }

/* ═══════════ Экран входа ═══════════ */
#login { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }
#login .box {
  width: 360px;
  max-width: 100%;
  /* Флекс-элементы по умолчанию не сжимаются меньше min-content своего
     содержимого, даже если max-width говорит иначе — из-за этого на узких
     экранах .box вылезал за viewport вместо того, чтобы ужаться. */
  min-width: 0;
  background: var(--bg-panel);
  border: 1.5px solid var(--accent);
  border-radius: var(--rounding);
  padding: 26px;
  box-shadow: var(--shadow);
}
#login h1 { margin: 0 0 2px; font-size: 30px; color: var(--accent); letter-spacing: .08em; }
#login .sub { color: var(--text-dim); font-size: 11px; margin-bottom: 20px; }
#login .mode { color: var(--accent2); font-size: 12px; margin-bottom: 10px; }
#login .msg { margin-top: 14px; font-size: 12px; min-height: 18px; }
#login .foot { margin-top: 22px; font-size: 9px; color: var(--border-dim); text-align: center; }

/* ─── Тема-пикер (нижний правый угол экрана входа) ─── */
#theme-picker { position: fixed; right: 16px; bottom: 16px; z-index: 50; }
.tp-trigger {
  display: flex; align-items: center; gap: 7px;
  padding: 7px 12px 7px 10px;
  border: 1px solid var(--border-dim);
  border-radius: 20px;
  background: var(--bg-panel);
  box-shadow: var(--shadow);
}
.tp-dot { width: 11px; height: 11px; border-radius: 50%; background: var(--accent); flex: none; }
.tp-trigger-label { font-size: 11px; color: var(--text-dim); }
#theme-picker.tp-open .tp-trigger { border-color: var(--accent); }

.tp-panel {
  display: none;
  position: absolute; right: 0; bottom: calc(100% + 8px);
  width: 260px;
  background: var(--bg-panel);
  border: 1px solid var(--accent);
  border-radius: var(--rounding);
  box-shadow: var(--shadow);
  padding: 6px;
}
/* Прокручивается только список — вкладки остаются на месте. */
.tp-list { max-height: 54vh; overflow-y: auto; }
#theme-picker.tp-open .tp-panel { display: block; }

.tp-group-label {
  font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-dim); padding: 6px 8px 4px;
}
.tp-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  text-align: left; padding: 6px 8px;
  border: 1px solid transparent; border-radius: var(--rounding);
  background: none; cursor: pointer;
}
.tp-item:hover { background: var(--hover-bg); }
.tp-item.active { border-color: var(--accent); background: var(--active-bg); }
.tp-name { flex: 1; font-size: 12px; }
/* Пометка происхождения темы — приглушённая: это справка, а не акцент. */
.tp-mark { flex: none; opacity: .5; margin-left: 4px; }

/* Вкладки внутри панели: Тема / Фон / Текстура / Эффект. */
.tp-tabs { display: flex; gap: 4px; padding: 2px 2px 6px; }
.tp-tabs button {
  flex: 1;
  padding: 4px 2px;
  font-size: 10px;
  letter-spacing: .04em;
  border-color: transparent;
  background: none;
  color: var(--text-dim);
}
.tp-tabs button:hover:not(:disabled) { background: var(--hover-bg); border-color: transparent; }
.tp-tabs button.active { color: var(--accent); border-color: var(--accent); background: var(--active-bg); }

/* Миниатюры режимов фона: приблизительные CSS-иконки (шесть WebGL-канвасов
   в списке рисовать слишком дорого — сам эффект виден на весь экран по наведению). */
.tp-fxicon {
  width: 34px; height: 20px; flex: none;
  position: relative; overflow: hidden;
  border: 1px solid var(--border-dim);
  border-radius: 2px;
  background: var(--bg-void);
}
.tp-fxicon::after { content: ""; position: absolute; inset: 0; }
.tp-fxicon[data-fx="haze"]::after {
  inset: -25%;
  background:
    radial-gradient(60% 80% at 30% 40%, var(--accent), transparent 70%),
    radial-gradient(50% 70% at 75% 65%, var(--accent2), transparent 70%);
  filter: blur(3px);
}
.tp-fxicon[data-fx="aurora"]::after {
  background: linear-gradient(100deg, transparent 8%, var(--accent) 35%, var(--accent2) 62%, transparent 88%);
  filter: blur(2px);
}
.tp-fxicon[data-fx="rain"]::after {
  background: repeating-linear-gradient(to right, var(--accent) 0 1px, transparent 1px 5px);
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000);
  mask-image: linear-gradient(to bottom, transparent, #000);
}
.tp-fxicon[data-fx="grid"]::after {
  left: -60%; right: -60%; top: 45%;
  background:
    repeating-linear-gradient(to right, var(--accent) 0 1px, transparent 1px 9px),
    repeating-linear-gradient(to bottom, var(--accent) 0 1px, transparent 1px 5px);
  transform: perspective(14px) rotateX(32deg);
  transform-origin: bottom center;
}
.tp-fxicon[data-fx="plasma"]::after {
  inset: -12%;
  background: radial-gradient(circle at 35% 45%, var(--accent2) 0 16%, var(--accent) 32%, var(--bg-void) 64%);
  filter: blur(1.5px);
}
.tp-fxicon[data-fx="stars"]::after {
  color: var(--accent);
  background-image: radial-gradient(currentColor .8px, transparent 1px);
  background-size: 7px 6px;
}
.tp-fxicon[data-fx="waves"]::after {
  background: repeating-radial-gradient(circle at 50% 120%,
    var(--accent) 0 1px, transparent 1px 4px);
  opacity: .9;
}
.tp-fxicon[data-fx="tunnel"]::after {
  background: repeating-radial-gradient(circle at 50% 50%,
    var(--accent) 0 1px, transparent 1px 4px);
}
.tp-fxicon[data-fx="cells"]::after {
  background:
    radial-gradient(circle at 25% 35%, var(--accent) 0 1px, transparent 1px),
    radial-gradient(circle at 70% 60%, var(--accent) 0 1px, transparent 1px),
    repeating-conic-gradient(from 20deg at 45% 50%,
      var(--bg-void) 0 22%, var(--accent) 22% 24%);
  opacity: .85;
}
.tp-fxicon[data-fx="bokeh"]::after {
  background:
    radial-gradient(circle at 28% 40%, var(--accent) 0 22%, transparent 55%),
    radial-gradient(circle at 66% 62%, var(--accent2) 0 18%, transparent 52%),
    radial-gradient(circle at 82% 28%, var(--accent) 0 12%, transparent 45%);
  filter: blur(1px);
}
.tp-fxicon[data-fx="flow"]::after {
  background: repeating-linear-gradient(to bottom, var(--accent) 0 1px, transparent 1px 4px);
  transform: skewY(-8deg) scale(1.3);
}
/* Волны XMB: несколько наклонных полос внахлёст — сам шейдер рисует
   переливающиеся синусоиды, точный узор в CSS-миниатюре не повторить,
   здесь только намёк на «слоистые диагональные полосы света». */
.tp-fxicon[data-fx="xmb"]::after {
  background:
    repeating-linear-gradient(8deg, var(--accent) 0 1.5px, transparent 1.5px 7px),
    repeating-linear-gradient(-6deg, var(--accent2) 0 1px, transparent 1px 9px);
  opacity: .85;
}
.tp-fxicon[data-fx="wallpaper"]::after {
  background: linear-gradient(135deg, var(--accent), var(--accent2));
}
.tp-fxicon[data-fx="embers"]::after {
  background:
    radial-gradient(1.5px 4px at 20% 70%, var(--accent) 60%, transparent),
    radial-gradient(1.5px 4px at 45% 30%, var(--accent2) 60%, transparent),
    radial-gradient(1.5px 4px at 68% 80%, var(--accent) 60%, transparent),
    radial-gradient(1.5px 4px at 85% 45%, var(--accent2) 60%, transparent);
}

/* Галерея обоев — раскрывается под списком «ФОН», когда выбрана «Своя
   картинка» (fx.js::renderWallpaperGallery). Общая вёрстка для пикера и
   для Настроек — контейнер один и тот же класс в обоих местах. */
.wp-gallery {
  display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 2px 2px;
  border-radius: var(--rounding);
  outline: 2px dashed transparent; outline-offset: 2px;
  transition: outline-color .12s;
}
/* Перетаскивают файл откуда угодно с диска — подсветка всей области, а не
   только тайла «+»: цель побольше проще не промахнуться мышью. */
.wp-gallery.wp-drag { outline-color: var(--accent); }
.wp-thumb {
  position: relative;
  width: 56px; height: 40px; flex: none;
  border: 1.5px solid var(--border-dim); border-radius: var(--rounding);
  overflow: hidden; cursor: pointer; background: var(--bg-widget);
}
.wp-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wp-thumb.active { border-color: var(--accent); }
.wp-thumb .wp-badge {
  position: absolute; left: 2px; bottom: 2px;
  font-size: 9px; line-height: 1;
  text-shadow: 0 0 2px #000, 0 0 2px #000;
}
.wp-thumb .wp-del {
  position: absolute; top: 0; right: 0; margin: 0; padding: 0;
  width: 16px; height: 16px; line-height: 16px; text-align: center;
  font-size: 12px; color: #fff;
  background: rgba(0, 0, 0, .55); border: none;
  border-radius: 0 0 0 var(--rounding);
}
.wp-thumb .wp-del:hover { background: var(--accent2); }
.wp-thumb.wp-upload {
  display: flex; align-items: center; justify-content: center;
  border-style: dashed; color: var(--text-dim); font-size: 20px;
}
.wp-thumb.wp-upload:hover { border-color: var(--accent); color: var(--accent); }

/* Миниатюра текстуры: тот же .fx-texture, что и полноэкранный слой, но
   на своей подложке — иначе полупрозрачную фактуру не разглядеть. */
.tp-texbox {
  width: 34px; height: 20px; flex: none;
  position: relative; overflow: hidden;
  border: 1px solid var(--border-dim);
  border-radius: 2px;
  /* Нейтральная полутоновая подложка. На «родном» фоне темы (часто чисто
     чёрном) фактуру было бы не разглядеть: overlay поверх чёрного даёт чёрное. */
  background: linear-gradient(135deg, var(--text-dim), var(--bg-widget));
}
.tp-texbox .fx-texture {
  position: absolute; inset: 0;
  /* В миниатюре смешивание обычное, а не overlay — иначе рисунок съедается
     подложкой. Полноэкранный слой при этом остаётся деликатным. */
  mix-blend-mode: normal;
}
/* Текстуры рассчитаны на весь экран и в окошке 34×20 почти не видны —
   поднимаем контраст только для миниатюры. */
.tp-texbox .fx-texture[data-tex="grain"]  { opacity: .55; }
.tp-texbox .fx-texture[data-tex="paper"]  { opacity: .6; }
.tp-texbox .fx-texture[data-tex="dither"] { opacity: .5; }
.tp-texbox .fx-texture[data-tex="grid"]   { opacity: .9; }
.tp-texbox .fx-texture[data-tex="crt"]    { opacity: .8; }
.tp-texbox .fx-texture[data-tex="scanlines"] { opacity: .7; }
.tp-texbox .fx-texture[data-tex="hatch"]    { opacity: .6; }
.tp-texbox .fx-texture[data-tex="dots"]     { opacity: .6; }
.tp-texbox .fx-texture[data-tex="carbon"]   { opacity: .75; }
.tp-texbox .fx-texture[data-tex="vignette"] { opacity: .85; }

/* Миниатюра эффекта появления: маленький квадрат крутит свою анимацию по
   кругу, пока панель открыта — единственный способ показать ДВИЖЕНИЕ в
   списке, не наводя каждый раз на весь экран (в отличие от цвета/фактуры,
   эффект появления статичным превью не передать). Элементов немного (шесть),
   само поле 34×20px — дёшево даже на слабом устройстве, и всё равно гасится
   общим prefers-reduced-motion ниже по файлу. */
.tp-moticon {
  width: 34px; height: 20px; flex: none;
  position: relative; overflow: hidden;
  border: 1px solid var(--border-dim);
  border-radius: 2px;
  background: var(--bg-void);
}
.tp-moticon::after {
  content: ""; position: absolute; inset: 4px;
  border-radius: 1px; background: var(--accent);
}
.tp-moticon[data-motion="none"]::after { opacity: .55; }
.tp-moticon[data-motion="glitch"]::after { animation: tp-mot-glitch 1.6s steps(6) infinite; }
.tp-moticon[data-motion="flicker"]::after { animation: tp-mot-flicker 2.4s ease-in-out infinite; }
.tp-moticon[data-motion="soft"]::after { animation: tp-mot-soft 2s ease-in-out infinite; }
/* Параметры полосы и блендинга — те же, что у реального sheen-in (см. его
   комментарий): иначе превью врало бы о том, как эффект выглядит на деле. */
.tp-moticon[data-motion="sheen"] {
  background-image: linear-gradient(115deg, transparent 38%, rgba(255,255,255,.7) 50%, transparent 62%);
  background-size: 170% 170%;
  background-blend-mode: screen;
  animation: tp-mot-sheen 2.6s ease-in-out infinite;
}
@keyframes tp-mot-glitch {
  0%, 60%, 100% { transform: translateX(0); filter: none; }
  65% { transform: translateX(-3px); filter: drop-shadow(2px 0 var(--accent2)); }
  72% { transform: translateX(2px); }
  78% { transform: translateX(-1px); filter: drop-shadow(1px 0 var(--accent2)); }
  84% { transform: translateX(0); filter: none; }
}
/* Раньше steps(10) infinite — по сути бесконечный стробоскоп, пока открыта
   панель. По прямому фидбеку («режет глаза») переделано на волну без резких
   ступеней и с полом не ниже .55, той же формулой, что и сам flicker-in. */
@keyframes tp-mot-flicker {
  0%, 30%, 100% { opacity: 1; }
  15% { opacity: .6; }
  45% { opacity: .8; }
}
@keyframes tp-mot-soft {
  0%, 60%, 100% { opacity: 1; transform: none; filter: none; }
  75% { opacity: .3; transform: translateY(2px) scale(.9); filter: blur(1.5px); }
}
/* Была уже полоса и почти весь путь за один проход — на глаз читалась как
   вспышка, а не блик. Полоса шире, путь короче (см. sheen-in), пауза перед
   стартом добавлена, чтобы движение было видно, а не мелькало на границе
   зацикливания. */
@keyframes tp-mot-sheen {
  0%, 15%   { background-position: -40% -40%; }
  65%, 100% { background-position: 130% 130%; }
}

.tp-swatch { display: flex; gap: 2px; flex: none; }
.tp-swatch i { display: block; width: 6px; height: 18px; border-radius: 1px; font-style: normal; }
.tp-swatch .tp-sw-void    { background: var(--bg-void); }
.tp-swatch .tp-sw-panel   { background: var(--bg-panel); }
.tp-swatch .tp-sw-accent  { background: var(--accent); }
.tp-swatch .tp-sw-accent2 { background: var(--accent2); }

@media (max-width: 480px) {
  #theme-picker { right: 10px; bottom: 10px; }
  .tp-trigger-label { display: none; }
  .tp-panel { width: min(260px, calc(100vw - 20px)); }
}

/* ─── Пасхалка: логотип в левом нижнем углу экрана входа (js/easter.js) ─── */
/* Зеркало тема-пикера: тот же отступ от края, та же высота посадки. */
.egg { position: fixed; left: 16px; bottom: 16px; z-index: 50; }
.egg-trigger {
  display: block;
  padding: 6px;
  border: 1px solid transparent;
  border-radius: 50%;
  background: none;
  cursor: pointer;
  /* Фирменный оранжевый берётся не из темы: логотип узнают по цвету,
     и перекрашивать его под каждую палитру — значит его потерять. */
  color: #d97757;
  opacity: .55;
  transition: opacity .18s, transform .18s;
}
.egg-trigger:hover { opacity: 1; transform: scale(1.12); border-color: transparent; background: none; }
.egg-trigger:active { transform: scale(.94); }
/* Лучи теперь закрашенные <path> (лепестки), а не <line> с обводкой —
   fill вместо stroke, см. easter.js::petalPath. */
.egg-logo { display: block; width: 26px; height: 26px; fill: currentColor; }
/* Крутится сам логотип, а не кнопка: иначе вместе с ним уезжало бы облачко. */
.egg-spin .egg-logo { animation: egg-turn 14s linear infinite; }
@keyframes egg-turn { to { transform: rotate(360deg); } }

.egg-bubble {
  position: absolute; left: 0; bottom: calc(100% + 8px);
  width: max-content; max-width: min(260px, calc(100vw - 40px));
  padding: 7px 10px;
  font-size: 12px; line-height: 1.35;
  background: var(--bg-panel);
  color: var(--text-main);
  border: 1px solid var(--accent);
  border-radius: var(--rounding);
  box-shadow: var(--shadow);
  /* Не display:none — иначе фраза не дочитывалась бы скринридером
     (role="status" объявляет только изменения видимого содержимого). */
  opacity: 0; transform: translateY(4px); pointer-events: none;
  transition: opacity .18s, transform .18s;
}
.egg-say .egg-bubble { opacity: 1; transform: none; }

@media (max-width: 480px) { .egg { left: 10px; bottom: 10px; } }

/* ─── Брифинг (модалка ⓘ на экране входа, js/app.js) ─── */
.brief-img { display: block; width: 100%; border-radius: var(--rounding); margin-bottom: 12px; }
.brief-lead {
  margin-bottom: 10px;
  font-size: 15px; line-height: 1.3; letter-spacing: .05em;
  color: var(--accent2);
  text-shadow: 0 0 10px currentColor;
}
.brief-spec { margin: 12px 0 14px; font-size: 12px; line-height: 1.5; }
.brief-row { display: flex; gap: 7px; margin-bottom: 6px; }
.brief-row b { color: var(--accent); font-weight: 600; letter-spacing: .04em; }
.brief-mark { color: var(--accent2); flex: none; }
.brief-foot {
  font-size: 11px; letter-spacing: .06em;
  color: var(--warn);
  border-top: 1px solid var(--border-dim);
  padding-top: 10px;
}

/* ─── Фоновые эффекты (js/fx.js) ─── */
/* Канвас — за всем содержимым (z-index:-1), но перед html/body-фоном:
   так тема просвечивает только там, где сверху нет непрозрачной панели. */
#fx-canvas { position: fixed; inset: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none; }

/* Текстура поверх всего экрана, включая модалки — как плёночное зерно поверх
   готового кадра. Целиком CSS: WebGL не нужен, работает даже там, где шейдер
   не завёлся.

   Сам вид задаётся классом .fx-texture + data-tex (его ставит js/fx.js), а не
   селектором по #fx-grain: те же правила переиспользуют миниатюры в списке
   выбора (.tp-texicon), поэтому превью гарантированно совпадает с результатом. */
#fx-grain { position: fixed; inset: 0; z-index: 80; pointer-events: none; display: none; }
#fx-grain.fx-texture { display: block; }
#fx-grain[data-tex="none"] { display: none; }

/* Мелкое плёночное зерно. */
.fx-texture[data-tex="grain"] {
  opacity: .035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='180'%20height='180'%3E%3Cfilter%20id='n'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.85'%20numOctaves='2'%20stitchTiles='stitch'/%3E%3C/filter%3E%3Crect%20width='100%25'%20height='100%25'%20filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

/* Крупное мягкое волокно — фактура бумаги, а не шум камеры. */
.fx-texture[data-tex="paper"] {
  opacity: .09;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='300'%20height='300'%3E%3Cfilter%20id='p'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.045'%20numOctaves='4'%20stitchTiles='stitch'/%3E%3C/filter%3E%3Crect%20width='100%25'%20height='100%25'%20filter='url(%23p)'/%3E%3C/svg%3E");
  background-size: 300px 300px;
}

/* Горизонтальные строки развёртки. */
.fx-texture[data-tex="scanlines"] {
  opacity: .18;
  background-image: repeating-linear-gradient(to bottom,
    rgba(0, 0, 0, .75) 0 1px, transparent 1px 3px);
}

/* Апертурная решётка ЭЛТ: цветная триада по горизонтали + строки развёртки. */
.fx-texture[data-tex="crt"] {
  opacity: .16;
  mix-blend-mode: overlay;
  background-image:
    repeating-linear-gradient(to right,
      rgba(255, 0, 0, .55) 0 1px,
      rgba(0, 255, 0, .55) 1px 2px,
      rgba(0, 0, 255, .55) 2px 3px),
    repeating-linear-gradient(to bottom,
      rgba(0, 0, 0, .8) 0 1px, transparent 1px 3px);
}

/* Шахматный дизеринг 1-битной эпохи. */
.fx-texture[data-tex="dither"] {
  opacity: .05;
  mix-blend-mode: overlay;
  background-image: conic-gradient(#fff 0 25%, #000 0 50%, #fff 0 75%, #000 0);
  background-size: 4px 4px;
}

/* Диагональная штриховка, как на гравюре. */
.fx-texture[data-tex="hatch"] {
  opacity: .06;
  color: var(--text-main);
  background-image: repeating-linear-gradient(45deg, currentColor 0 1px, transparent 1px 7px);
}

/* Полутоновая точечная сетка — газетная печать. */
.fx-texture[data-tex="dots"] {
  opacity: .07;
  color: var(--text-main);
  background-image: radial-gradient(currentColor .9px, transparent 1px);
  background-size: 8px 8px;
}

/* Карбоновое плетение: две встречные диагонали, свет и тень. */
.fx-texture[data-tex="carbon"] {
  opacity: .1;
  mix-blend-mode: overlay;
  background-image:
    repeating-linear-gradient(45deg, rgba(255, 255, 255, .5) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(-45deg, rgba(0, 0, 0, .5) 0 1px, transparent 1px 3px);
  background-size: 6px 6px;
}

/* Виньетка — единственная «текстура», которая не тайлится: один градиент
   на весь экран, притемняющий углы. Собирает внимание к центру. */
.fx-texture[data-tex="vignette"] {
  opacity: .5;
  background-image: radial-gradient(ellipse at center, transparent 45%, rgba(0, 0, 0, .6) 100%);
}

/* Миллиметровка/чертёж — линии в акцентном цвете темы через currentColor. */
.fx-texture[data-tex="grid"] {
  opacity: .09;
  color: var(--accent);
  background-image:
    repeating-linear-gradient(to right, currentColor 0 1px, transparent 1px 24px),
    repeating-linear-gradient(to bottom, currentColor 0 1px, transparent 1px 24px);
}

@media (prefers-reduced-motion: reduce) { #fx-canvas { display: none !important; } }

/* ═══════════ Каркас приложения ═══════════ */
header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--border-dim);
  background: var(--bg-panel);
  position: sticky;
  top: 0;
  /* Выше шторки предметов (30) и её подложки (29): шапка с дубликатом ☰
     остаётся видимой и кликабельной, когда шторка выдвинута. */
  z-index: 35;
  flex-wrap: wrap;
}
header .brand { color: var(--accent); font-weight: 700; font-size: 16px; letter-spacing: .05em; }
header .role {
  color: var(--accent2);
  font-size: 10px;
  border: 1px solid var(--border-dim);
  border-radius: 20px;
  padding: 2px 10px;
  white-space: nowrap;
}
header .spacer { flex: 1; }

.tabs {
  display: flex;
  gap: 6px;
  padding: 10px 18px 0;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--border-dim);
}
.tabs button { border-bottom-left-radius: 0; border-bottom-right-radius: 0; margin-bottom: -1px; color: var(--text-main); }
.tabs button.active { color: var(--accent); border-color: var(--accent); border-bottom-color: var(--bg-void); background: var(--open-bg); }

main { padding: 12px 18px 70px; max-width: 1240px; }

.toolbar { display: flex; align-items: center; gap: 10px; margin: 6px 0 16px; flex-wrap: wrap; }
h2.sec { color: var(--accent); font-size: 20px; margin: 0; font-weight: 700; }
.status-line { font-size: 12px; min-height: 16px; }

/* Индикатор потери связи с сервером */
#offline-bar {
  position: fixed; left: 0; right: 0; bottom: 0;
  background: var(--accent2); color: var(--active-text);
  text-align: center; padding: 7px; font-size: 12px; z-index: 60;
}

/* ═══════════ Тосты ═══════════ */
#toasts {
  position: fixed;
  right: 14px;
  bottom: 14px;
  z-index: 70;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: min(380px, 92vw);
}
.toast {
  background: var(--bg-panel);
  border: 1px solid var(--border-dim);
  border-left-width: 3px;
  border-radius: var(--rounding);
  padding: 9px 12px;
  font-size: 12px;
  box-shadow: var(--shadow);
  cursor: pointer;
  animation: toast-in .16s ease-out;
  word-break: break-word;
}
.toast.t-ok   { border-left-color: var(--ok); }
.toast.t-err  { border-left-color: var(--accent2); }
.toast.t-warn { border-left-color: var(--warn); }
.toast.t-info { border-left-color: var(--accent); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ═══════════ Модалки ═══════════ */
#overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .6);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  z-index: 40;
}
#overlay .modal {
  width: 460px;
  max-width: 100%;
  max-height: 90vh;
  overflow: auto;
  background: var(--bg-panel);
  border: 1px solid var(--accent);
  border-radius: var(--rounding);
  padding: 20px;
  box-shadow: var(--shadow);
}
#overlay .modal.wide { width: 640px; }
.modal h3 { margin: 0 0 8px; color: var(--accent); font-size: 16px; padding-right: 30px; }
.modal .close { float: right; margin: -6px -6px 0 0; }
.kv { color: var(--text-dim); font-size: 13px; margin: 2px 0; word-break: break-word; }
.modal-actions { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
/* Кнопки в ряду действий — одного размера, растягиваются поровну (и вместе,
   и когда переносятся каждая на свою строку). Свой контент каждая кнопка
   всё равно не сожмёт меньше своей ширины (min-width:auto по умолчанию) —
   так flex-wrap на .modal-actions по-прежнему сам решает, влезают ли они в ряд. */
.modal-actions button { flex: 1 1 0; }

/* ═══════════ Расписание ═══════════ */
/* Широкий экран — сетка «время × дни», как в десктопе. */
.sched-grid { display: grid; gap: 4px; min-width: 0; }
.sched-grid .hcell {
  color: var(--accent);
  font-size: 10px;
  font-weight: 700;
  padding: 4px 6px;
  text-transform: uppercase;
  border-bottom: 1px solid var(--border-dim);
}
.sched-grid .hcell.today { color: var(--active-text); background: var(--selection-bg); border-radius: var(--rounding); }
.sched-grid .tcell {
  color: var(--text-main);
  font-size: 11px;
  font-weight: 700;
  padding: 8px 4px 0 0;
  white-space: nowrap;
}
.sched-grid .slot { min-height: 44px; padding-top: 4px; }
.sched-grid .row-sep { grid-column: 1 / -1; height: 1px; background: var(--border-dim); }

/* Узкий экран — колонки по дням. */
.sched-days { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
.day-col {
  background: var(--bg-panel);
  border: 1px solid var(--border-dim);
  border-radius: var(--rounding);
  padding: 8px;
  min-width: 0;
}
.day-col.today { border-color: var(--accent); }
.day-col h4 { margin: 0 0 8px; color: var(--accent); font-size: 12px; }
.day-col h4 .dnum { color: var(--text-dim); font-weight: 400; margin-left: 6px; }

.lesson {
  background: var(--card-bg);
  border: 2px solid var(--accent);
  border-radius: var(--rounding);
  padding: 6px 8px;
  margin-bottom: 5px;
  cursor: pointer;
  min-width: 0;
}
/* Пара ИОТа: рамка по типу занятия (её ставит JS), плюс бирюзовое кольцо
   снаружи — «это моё». Два контура вместо одного: раньше бирюзовый цвет
   занимал единственную рамку и вытеснял тип, приходилось выбирать что-то
   одно. --iot-ring выставляет JS тем же бирюзовым, что и в боте. */
.lesson.local {
  background: var(--card-local-bg);
  box-shadow: 0 0 0 2px var(--iot-ring, var(--accent));
  /* Кольцо рисуется снаружи рамки — оставляем ему место, иначе соседние
     карточки в плотной клетке начинают его срезать. */
  margin: 2px 2px 7px;
}
.lesson.local.now { box-shadow: 0 0 0 2px var(--iot-ring, var(--accent)), 0 0 0 2px var(--warn) inset; }
/* «ДОП» — пары нет в расписании вуза. Цвет предупреждения, а не акцента:
   это не «моё», а «откуда это вообще взялось». */
.lesson .extra-tag {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .06em;
  color: var(--warn);
  margin-left: 6px;
}
.lesson .iot-tag {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .06em;
  color: var(--iot-ring, var(--accent));
  margin-left: 6px;
}
.lesson.now { box-shadow: 0 0 0 2px var(--warn) inset; }
.lesson:hover { background: var(--hover-bg); }
.lesson .tm { font-size: 10px; color: var(--warn); font-weight: 700; }
.lesson .t { font-size: 12px; font-weight: 700; word-break: break-word; }
.lesson .m { font-size: 10px; color: var(--text-dim); word-break: break-word; }
.lesson .skipmark { font-size: 10px; color: var(--accent2); }

/* Замок переноса пар группы (schedule.js, #sched-lock). Открытый — цветом
   предупреждения: пока он открыт, любой промах мышью правит расписание всем. */
#sched-lock.lock-open { color: var(--warn); border-color: var(--warn); }

/* Правка админа (schedule_changes.rs): отменённая и уехавшая пара остаются на
   месте, но гаснут и зачёркиваются — пары не будет, но видно, что она была и
   куда делась. Метка и строка «→ куда» не гаснут вместе с ней: их и читают. */
.lesson.off { border-style: dashed; }
.lesson.off .t,
.lesson.off .m { opacity: .5; }
.lesson.off .t { text-decoration: line-through; }
.lesson .chg-tag {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .06em;
  color: var(--accent);
  margin-left: 6px;
}
.lesson .chg-tag.off { color: var(--accent2); }
.lesson .chg-line { font-size: 10px; color: var(--accent); word-break: break-word; }
.lesson.off .chg-line { color: var(--accent2); }
.chg-info {
  margin: 8px 0;
  padding: 6px 8px;
  border-left: 3px solid var(--accent);
  font-size: 12px;
}
.chg-info.off { border-left-color: var(--accent2); color: var(--accent2); }

/* Личный блок можно тащить мышью — курсор об этом и говорит. */
.lesson[draggable="true"] { cursor: grab; }
.lesson.dragging { opacity: .4; cursor: grabbing; }

/* Окно в колонке дня (узкий экран): та же пустая клетка, что в сетке ПК-вида.
   Ни подписи, ни времени — только место, которого хватает, чтобы дырку в дне
   было видно, не вычитая её из времени соседних пар. Пунктир едва заметен:
   он говорит «здесь ничего», а не рисует ещё одну карточку. */
.lesson-gap {
  min-height: 34px;
  margin: 0 2px 5px;
  border: 1px dashed var(--border-dim);
  border-radius: var(--rounding);
  opacity: .55;
}

/* Имя преподавателя — ссылка на его расписание на ssau.ru. */
.teacher-link { color: inherit; text-decoration: underline dotted; text-underline-offset: 2px; }
.teacher-link:hover { color: var(--accent); }

/* Выбор своих преподавателей по предмету (модалка «Мои преподаватели»). */
.teacher-pick { margin-top: 10px; }
.teacher-pick .kv { margin-bottom: 2px; }

/* Фишка, зовущая что-то решить: предметы с несколькими преподавателями, по
   которым выбор ещё не сделан. */
.iot-chip.attention { border-color: var(--warn); color: var(--warn); }

/* ── Панель «Мои ИОТы» над сеткой ── */
/* Фишка предмета: её тащат в клетку расписания или нажимают, чтобы выбрать
   день и время в модалке (тач-экраны, где HTML5 drag не работает). */
.iot-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 0 0 14px;
  padding: 8px 10px;
  background: var(--bg-panel);
  border: 1px solid var(--border-dim);
  border-radius: var(--rounding);
}
.iot-chip {
  border: 1.5px solid var(--accent);
  border-radius: var(--rounding);
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 700;
  cursor: grab;
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.iot-chip:active { cursor: grabbing; }
.iot-chip.dragging { opacity: .4; }
/* «+ ИОТ» — не перетаскивается, поэтому и курсор обычный, и рамка пунктиром:
   это действие, а не предмет. */
.iot-chip.new { cursor: pointer; border-style: dashed; border-color: var(--border-dim); color: var(--text-dim); }
.iot-chip.new:hover { border-color: var(--accent); color: var(--accent); }
.iot-repeat { font-size: 11px; padding: 3px 6px; }
.iot-repeat:first-of-type { margin-left: auto; }

/* Клетка под курсором с перетаскиваемым предметом. Пунктир, а не заливка:
   в клетке уже могут лежать карточки пар, их нельзя перекрашивать. */
.drop-hot {
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
  border-radius: var(--rounding);
  background: var(--selection-bg);
}

/* ── Линеечка времени, выезжающая из дня при перетаскивании ──
   Прицелиться мышью в клетку сетки трудно, а в узком виде (колонка = день)
   времени в разметке нет вовсе. Линеечка — ДОЧЕРНИЙ элемент дня, а не сосед:
   только так `host.contains(e.relatedTarget)` в dragleave отличает «курсор
   ушёл совсем» от «курсор перешёл на саму линеечку». Отсюда relative у дня.
   Префикс tl-, а не tp-: tp- занят пикером тем. */
.tl-host { position: relative; }

.time-pop {
  position: absolute;
  z-index: 30;
  background: var(--bg-widget);
  border: 1.5px solid var(--accent);
  border-radius: var(--rounding);
  box-shadow: var(--shadow);
  padding: 6px;
  min-width: 128px;
}
/* Вбок от колонки дня. У правой половины недели — в другую сторону, иначе
   линеечка уедет за край окна и до неё не дотянуться. */
.time-pop.side       { top: 0; left: calc(100% + 6px); }
.time-pop.side.flip  { left: auto; right: calc(100% + 6px); }
/* Вниз из шапки дня — широкая сетка, где у клеток время и так своё. */
.time-pop.below      { top: calc(100% + 4px); left: 0; }
.time-pop.below.flip { left: auto; right: 0; }

.tl-colors { display: flex; gap: 4px; flex-wrap: wrap; }
.tl-more { margin-top: 4px; }
.tl-sw {
  width: 22px;
  height: 22px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--rounding);
  cursor: pointer;
}
/* Взведённый тип. Рамка цветом текста: она обязана читаться поверх любого
   свотча, а --accent на бирюзовом свотче исчез бы. */
.tl-sw.on { border-color: var(--text-main); }
.tl-expand {
  background: transparent;
  border-color: var(--border-dim);
  color: var(--text-dim);
  display: flex;
  align-items: center;
  justify-content: center;
}
.tl-expand:hover { border-color: var(--accent); color: var(--accent); }

.tl-times { margin-top: 6px; }
.tl-row {
  padding: 3px 8px;
  font-size: 11px;
  white-space: nowrap;
  border-radius: var(--rounding);
  cursor: pointer;
}
.tl-row:hover { background: var(--hover-bg); color: var(--accent); }
/* «Своё время» отделено чертой: это не слот сетки, а открывалка формы —
   часы на весу, не отпуская груза, всё равно не введёшь. */
.tl-row.custom {
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid var(--border-dim);
  border-radius: 0;
  color: var(--text-dim);
}

/* ═══════════ Задачи ═══════════ */
.task-layout { display: flex; gap: 12px; align-items: flex-start; }
.subj-list {
  width: 220px;
  flex: 0 0 220px;
  background: var(--bg-panel);
  border: 1px solid var(--border-dim);
  border-radius: var(--rounding);
  padding: 6px;
  max-height: 74vh;
  /* Только по вертикали: горизонтальная прокрутка тут означала бы, что
     название предмета не поместилось и уехало за край панели. */
  overflow-y: auto;
  overflow-x: hidden;
}
.subj-list.collapsed { display: none; }
.subj-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-left: 2px solid transparent;
  color: var(--text-main);
  padding: 8px;
  border-radius: 0 var(--rounding) var(--rounding) 0;
  font-size: 12px;
  word-break: break-word;
  /* Глобальное `button { white-space: nowrap }` защищает подписи вида
     «[ ТЕКСТ ]» от разрыва, но здесь в кнопке — название предмета:
     «Метрология, стандартизация и сертификация» одной строкой уезжала
     за край панели. Пункту списка перенос нужен. */
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.35;
}
.subj-item:hover { background: var(--hover-bg); border-color: transparent; border-left-color: var(--border-dim); }
.subj-item.active { background: var(--open-bg); color: var(--accent); border-left-color: var(--accent); }
.subj-item.personal { color: var(--accent2); }
.subj-item.active.personal { color: var(--accent); }

/* Заголовок раздела бокового списка: общие предметы отдельно, ИОТы отдельно. */
.subj-group {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 8px 4px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-dim);
  border-bottom: 1px solid var(--border-dim);
  margin-bottom: 2px;
}
.subj-group.iot { color: var(--accent2); margin-top: 6px; }
.subj-group .subj-add {
  margin-left: auto;
  background: none;
  border: 1px solid var(--border-dim);
  border-radius: var(--rounding);
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1;
  padding: 2px 7px;
}
.subj-group .subj-add:hover { border-color: var(--accent2); color: var(--accent2); }
.subj-list > .muted { padding: 6px 8px; font-size: 11px; }
.task-main { flex: 1; min-width: 0; }

/* ── Поиск по заданиям всех предметов (views/task-search.js) ──
   Поле прижато вправо панели; на узком экране панель переносится, и поле
   встаёт отдельной строкой во всю ширину. Префикс ts- (tp- и tl- заняты). */
.ts-box { display: block; margin-left: auto; position: relative; flex: 0 1 280px; min-width: 180px; }
/* Иконку подставляет app.js ВНУТРЬ <span data-icon>, поэтому позиционируем
   сам span, а не .ico — `.ts-box > .ico` не находил ничего. */
.ts-box > [data-icon] { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); line-height: 0; color: var(--text-dim); pointer-events: none; }
.ts-box input { padding: 6px 8px 6px 28px; font-size: 12px; }
@media (max-width: 900px) { .ts-box { flex: 1 1 100%; margin-left: 0; } }

.ts-head { margin: 2px 0 8px; font-size: 12px; }
.ts-subject { color: var(--accent2); font-size: 11px; text-transform: uppercase; margin: 12px 0 4px; }
/* Строка выдачи — кнопка (клик и Enter с клавиатуры), но с содержимым, а не
   подписью-командой: снимаем глобальный nowrap (раздел 7 CLAUDE.md). */
.ts-row {
  display: block; width: 100%; text-align: left;
  white-space: normal; overflow-wrap: anywhere;
  padding: 7px 10px; margin: 4px 0;
  background: var(--bg-widget); border: 1px solid var(--border-dim); border-radius: var(--rounding);
  color: var(--text-main); cursor: pointer;
}
.ts-row:hover, .ts-row:focus-visible { border-color: var(--accent); }
.ts-row.done .ts-title { text-decoration: line-through; opacity: .7; }
.ts-title { color: var(--accent); font-weight: 700; font-size: 13px; }
.ts-meta { color: var(--text-dim); font-size: 10px; margin-top: 2px; }
.ts-snip { font-size: 11px; margin-top: 3px; opacity: .85; }
.ts-row mark { background: var(--selection-bg); color: inherit; border-radius: 2px; padding: 0 1px; }

/* ── Шпаргалка горячих клавиш (hotkeys.js) ── */
.hk-list { display: grid; grid-template-columns: max-content 1fr; gap: 6px 14px; align-items: baseline; margin-bottom: 12px; font-size: 12px; }
.hk-list kbd {
  font-family: inherit; font-size: 11px; text-align: center; min-width: 26px;
  padding: 2px 6px; border: 1px solid var(--border-dim); border-bottom-width: 2px;
  border-radius: 4px; color: var(--accent);
}

/* ── Свайп недели (schedule.js::playSwipe) ── Новая неделя въезжает с той
   стороны, куда листали. prefers-reduced-motion гасит её общим правилом. */
@keyframes swipe-from-right { from { opacity: 0; transform: translateX(18px); } }
@keyframes swipe-from-left  { from { opacity: 0; transform: translateX(-18px); } }
#sched.swipe-l { animation: swipe-from-right .2s ease; }
#sched.swipe-r { animation: swipe-from-left .2s ease; }
/* Браузер сам понимает горизонтальный свайп как «Назад/Вперёд» по истории
   (overscroll history navigation в Chrome, в том числе на Android) — и
   листание недели вправо уводило бы на прошлую вкладку. Пойман CDP-щупом.
   Гасим это только на сенсорных экранах и только пока открыто расписание:
   на остальных вкладках браузерный жест «Назад» остаётся как был, а
   трекпад на компьютере свайпом неделю не листает и не должен ничего терять.
   Системный жест края экрана (жестовая навигация Android) это не трогает. */
@media (pointer: coarse) {
  html:has(#tab-sched:not(.hidden)) { overscroll-behavior-x: none; }
}
/* touch-action: pan-y здесь НЕ ставим намеренно: на увеличенной щипком
   странице он не дал бы сдвинуть её вбок пальцем по сетке. Свайп и так
   получает touchend — браузер горизонтальный жест без прокрутки не съедает. */

#subj-backdrop { position: fixed; left: 0; right: 0; bottom: 0; top: var(--header-h, 54px); background: rgba(0, 0, 0, .45); z-index: 29; }

/* ═══ Режим «действую от чужого имени» ═══════════════════════════
   Подмена должна быть заметна боковым зрением, иначе суперадмин рано или
   поздно отметит домашку не в том окне. Отсюда три уровня: рамка по краю
   всего окна, полоса над шапкой и переименованная кнопка выхода (app.js).
   Цвет — --warn: не ошибка, но и не норма. */

#imp-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 8px 18px;
  background: var(--bg-panel);
  border-bottom: 1px solid var(--warn);
  color: var(--warn);
  font-size: 12px;
  /* Над шапкой (35) — полоса не должна уезжать под неё при прокрутке. */
  position: sticky;
  top: 0;
  z-index: 36;
}
#imp-banner .spacer { flex: 1; }
/* Полоса липкая и стоит над шапкой, поэтому шапка прилипает не к нулю, а под
   неё. --imp-h считает app.js: высота полосы плавает вместе с переносом строк. */
body.impersonating header { top: var(--imp-h, 0px); }
#imp-banner .imp-tag {
  border: 1px solid var(--warn);
  border-radius: 20px;
  padding: 2px 10px;
  font-size: 10px;
  letter-spacing: .08em;
  white-space: nowrap;
}

/* Шапка липкая и своя, поэтому рамку вешаем не на body, а отдельным слоем
   поверх всего окна — так она не сдвигает вёрстку и не прокручивается. */
body.impersonating::after {
  content: "";
  position: fixed;
  inset: 0;
  border: 2px solid var(--warn);
  pointer-events: none;   /* сквозь рамку кликается интерфейс под ней */
  z-index: 100;
}

/* Экран после выхода из режима: вводить здесь можно только чужой пароль,
   которого у суперадмина нет, а собственный вход перебил бы куку соседней
   вкладки. Поэтому от формы остаётся только сообщение (см. app.js). */
#login.imp-ended .mode,
#login.imp-ended .field,
#login.imp-ended input,
#login.imp-ended .modal-actions,
#login.imp-ended #rg-code-wrap { display: none; }

#view-banner {
  background: var(--bg-panel);
  border: 1px solid var(--accent2);
  border-radius: var(--rounding);
  padding: 8px 12px;
  margin-bottom: 10px;
  color: var(--accent2);
  font-size: 12px;
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}

/* Задание, к которому перешли по ссылке или из сводки. Обводкой, а не
   анимацией: у .node анимацию появления переопределяют эффекты тем
   ([data-motion]), и своя здесь с ними бы спорила. */
.node.flash, .claim-row.flash {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Узел дерева. Отступ вложенности задаётся инлайн через margin-left. */
.node {
  border: 1px solid var(--border-dim);
  border-radius: var(--rounding);
  padding: 9px 11px;
  margin: 6px 0;
  background: var(--bg-widget);
  min-width: 0;
}
.node.pool { border-color: var(--border-dim); }
.node.pool.wasted { border-color: var(--accent2); }
.node.folder { background: none; border-style: dashed; }
/* Отмеченное «сделано» приглушается, но остаётся читаемым: в него ещё
   заглядывают — что было задано, какие файлы. */
.node.done > .node-head .nt,
.node.done > .nd,
.node.done > .meta { opacity: .6; }
.node .nt { font-weight: 700; color: var(--accent); font-size: 14px; word-break: break-word; }
.node.pool .nt { font-size: 15px; }
.node .nd { font-size: 12px; color: var(--text-main); white-space: pre-wrap; word-break: break-word; margin-top: 4px; }
.node .meta { font-size: 10px; color: var(--text-dim); margin-top: 3px; }
.node .meta.slots { font-size: 11px; }
.node .meta.slots.wasted { color: var(--accent2); }
.ops { margin-top: 7px; display: flex; gap: 6px; flex-wrap: wrap; }
.node-head { display: flex; gap: 8px; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; }

/* Строка-тема внутри пула бронирования */
.claim-row {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  padding: 5px 0;
  border-bottom: 1px dotted var(--border-dim);
  font-size: 12px;
}
.claim-row:last-child { border-bottom: none; }
.claim-row .ct { flex: 1; min-width: 120px; word-break: break-word; }

.assignee { font-size: 11px; color: var(--accent); margin-right: 4px; white-space: nowrap; }
.attach-line { font-size: 11px; margin-top: 3px; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.attach-line .sol { color: var(--accent2); }
/* Запасная ссылка «скачать» рядом с просмотром (util.js::needsDownloadFallback) —
   приглушена: это подстраховка на случай, если инлайн-показ не сработает,
   а не равноценная вторая кнопка. */
.attach-fallback { display: inline-flex; opacity: .6; }
.attach-fallback:hover { opacity: 1; }

.req {
  border: 1px solid var(--accent2);
  border-radius: var(--rounding);
  padding: 10px;
  margin: 6px 0;
  background: var(--bg-widget);
}

/* ═══════════ Доска услуг ═══════════ */
.svc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.svc-card {
  background: var(--card-bg);
  border: 1px solid var(--border-dim);
  border-radius: var(--rounding);
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-width: 0;
}
.svc-card.mine { border-color: var(--accent); }
.svc-card:hover { border-color: var(--inactive-border); }
.svc-card.mine:hover { border-color: var(--accent); }
/* flex-wrap, а не жёсткий ряд: цена — свободный текст (пользователь мог
   вписать «500 Рублей за Доклад (+150Р за презентацию).», а не короткий тег
   вроде «500 ₽/час»), и она переносится на свою строку, если не влезает
   рядом с заголовком. */
.svc-head { display: flex; gap: 6px 10px; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; }
.svc-title {
  font-weight: 700;
  color: var(--accent);
  font-size: 14px;
  overflow-wrap: anywhere;
  /* Ключевая строка: без min-width:0 flex-элемент с переносом текста не
     сжимается по правилам обычного текста, а схлопывается до ширины
     min-content — а с переносом «где угодно» это ширина ОДНОГО символа.
     Ряд из соседнего nowrap-элемента (цены) забирает всё место, и заголовок
     рушится в вертикальный столбик из одной буквы на строку. */
  flex: 1 1 160px;
  min-width: 0;
}
.svc-price {
  color: var(--warn);
  font-weight: 700;
  font-size: 13px;
  border: 1px solid var(--border-dim);
  border-radius: 20px;
  padding: 1px 10px;
  flex: 0 1 auto;
  min-width: 0;
  max-width: 100%;
  /* Не nowrap: длинная «цена»-фраза переносится по словам вместо того,
     чтобы вжимать заголовок в соседнюю колонку. */
  white-space: normal;
  overflow-wrap: anywhere;
}
.svc-desc {
  font-size: 12px;
  color: var(--text-main);
  white-space: pre-wrap;
  /* anywhere, а не break-word: спасает от «слова» на 500 символов без пробелов. */
  overflow-wrap: anywhere;
}
/* Свёрнутое описание — как в объявлениях: несколько строк и «показать полностью». */
.svc-desc.clamped {
  display: -webkit-box;
  -webkit-line-clamp: 7;
  line-clamp: 7;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.svc-more {
  align-self: flex-start;
  padding: 0;
  font-size: 11px;
  background: none;
  border: none;
  color: var(--accent);
  text-decoration: underline;
}
.svc-more:hover { background: none; border: none; }
.svc-contact-line { display: block; }
.svc-contact { font-size: 12px; word-break: break-word; }
.svc-foot {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  font-size: 10px;
  border-top: 1px dotted var(--border-dim);
  padding-top: 7px;
  margin-top: auto;
}

/* Сводка */
.dash-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 12px; }
/* min-width: 0 — иначе grid-колонка раздувается под самое длинное слово
   внутри и панель уезжает за пределы сетки. */
.dash-grid .frame { max-width: 100%; margin: 0; min-width: 0; overflow-wrap: anywhere; }
.dash-item { font-size: 12px; padding: 4px 0; border-bottom: 1px dotted var(--border-dim); min-width: 0; }
/* Заголовок задачи — кнопка-ссылка: снимаем с неё общий для кнопок nowrap,
   иначе длинная тема («Советская внешняя политика в 1919 — начале 1941 гг.
   Лига Наций») вылезает за правый край карточки «Ближайшие сроки». */
.dash-item > button {
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: left;
  max-width: 100%;
}
.dash-item:last-child { border-bottom: none; }
/* Строка срока с галочкой «сделано» слева. Заголовок теперь внутри
   .dash-main, и правило про перенос выше его не достаёт — повторяем. */
.dash-item.with-check { display: flex; gap: 6px; align-items: flex-start; }
.dash-item.with-check > button { flex: none; }
.dash-main { flex: 1 1 auto; min-width: 0; }
.dash-main > button { white-space: normal; overflow-wrap: anywhere; text-align: left; max-width: 100%; }
.dash-item.done .dash-main > button { text-decoration: line-through; opacity: .7; }
.dash-done { margin-top: 6px; }
.dash-done > summary { cursor: pointer; font-size: 11px; }
.dash-item .when { color: var(--text-dim); font-size: 10px; overflow-wrap: anywhere; }
/* Подпись «предмет · срок» по сроку: сегодня-завтра — тревожная, дальше —
   акцентом. Прошедшее остаётся тусклым: оно свёрнуто в «Прошедшие», и
   красный там только кричал бы о том, что галочку не поставили. */
.dash-item.soon .when { color: var(--warn); }
.dash-item.future .when { color: var(--accent); }
.dash-item .when .hw-subj { font-weight: 700; }

/* «Все домашки» во вкладке задач: группы по дням. */
.hw-head { margin: 2px 0 6px; font-size: 12px; }
.hw-day { margin: 0 0 10px; }
.hw-day-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--accent);
  border-bottom: 1px solid var(--border-dim);
  padding: 6px 0 3px;
}
.hw-day.soon .hw-day-title { color: var(--warn); }
.subj-item.all { font-weight: 700; color: var(--accent); }

/* ═══════════ Мобильная адаптация ═══════════ */
@media (max-width: 900px) {
  body { font-size: 15px; }
  button { padding: 10px 13px; }
  button.mini { padding: 5px 10px; }
  input, select, textarea { padding: 10px; font-size: 16px; } /* 16px — iOS не зумит поле */
  main { padding: 10px 12px 70px; }
  header { padding: 9px 12px; }
  .tabs { padding: 8px 12px 0; }
  .sched-days { grid-template-columns: 1fr; }
  #overlay { padding: 0; align-items: flex-end; }
  #overlay .modal { width: 100%; max-height: 92vh; border-radius: var(--rounding) var(--rounding) 0 0; }
  #toasts { left: 10px; right: 10px; max-width: none; bottom: 10px; }

  /* Список предметов превращается в выезжающую шторку. */
  .subj-list {
    /* --header-h измеряется в JS: шапка переносится на две строки при длинном
       логине, поэтому фиксированное значение здесь врало бы. */
    position: fixed; top: var(--header-h, 54px); left: 0; bottom: 0; right: auto;
    width: min(280px, 84vw);
    max-height: none;
    border-radius: 0 var(--rounding) var(--rounding) 0;
    flex-basis: auto;
    z-index: 30;
    padding-top: 8px;
    transform: translateX(-106%);
    transition: transform .22s ease;
    box-shadow: var(--shadow);
  }
  .subj-list.collapsed { display: block; transform: translateX(-106%); }
  .subj-list:not(.collapsed) { transform: translateX(0); }
  /* Бургер в тулбаре намеренно НЕ поднимается над шторкой: выдвинутая шторка
     его накрывает. Иначе он повисает посреди списка предметов и перекрывает
     их названия. Закрывают шторку дубликатом в шапке (#subj-toggle-top),
     подложкой или Esc. */
}
@media (min-width: 901px) { #subj-backdrop { display: none; } }

/* ═══════════ Нижняя панель навигации (телефон) ═══════════
   На телефоне шесть вкладок сверху — две строки мелких кнопок под большим
   пальцем недосягаемо. Четыре кнопки внизу: три частых раздела и «Ещё»
   (шторка через openModal, app.js::openMoreSheet). Переключает тот же
   openTab(), поэтому горячие клавиши, роутер и история не меняются: у
   hotkeys.js #main-tabs по-прежнему в DOM, скрыт только CSS. */
:root { --bnav-h: 56px; }
#bottom-nav { display: none; }
@media (max-width: 900px) {
  #main-tabs { display: none; }

  #bottom-nav {
    display: flex;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    /* Под модалкой (40), над шапкой (35) и шторкой предметов (30). */
    z-index: 38;
    height: calc(var(--bnav-h) + env(safe-area-inset-bottom, 0px));
    padding: 0 env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
    background: var(--bg-panel);
    border-top: 1px solid var(--border-dim);
  }
  #bottom-nav button {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    padding: 4px 2px;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: var(--text-dim);
    font-size: 11px;
    white-space: nowrap;
  }
  /* Залипающий :hover после тапа не должен красить кнопку фоном. */
  #bottom-nav button:hover:not(:disabled), #bottom-nav button:active:not(:disabled) { background: transparent; }
  #bottom-nav button > .ico { width: 22px; height: 22px; margin: 0; }
  #bottom-nav button > span { overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
  /* Активная — цветом и полоской сверху: одного цвета мало в темах, где
     --text-dim и --accent близки. */
  #bottom-nav button.active { color: var(--accent); box-shadow: inset 0 2px 0 var(--accent); }

  /* Место под панель: последний элемент страницы не должен прятаться под ней,
     тосты и полоса «нет связи» — всплывать поверх неё. */
  main { padding-bottom: calc(70px + var(--bnav-h) + env(safe-area-inset-bottom, 0px)); }
  #toasts { bottom: calc(var(--bnav-h) + 10px + env(safe-area-inset-bottom, 0px)); }
  #offline-bar { bottom: calc(var(--bnav-h) + env(safe-area-inset-bottom, 0px)); }
  .subj-list, #subj-backdrop { bottom: calc(var(--bnav-h) + env(safe-area-inset-bottom, 0px)); }

  /* Шапка в одну строку: логин уезжает в «Ещё», выход — туда же. #logout
     остаётся в DOM: его нажимают и «Ещё», и полоса режима подмены. */
  header { flex-wrap: nowrap; gap: 10px; }
  header #who, header #logout, header #help-btn { display: none; }
}

/* ── Расписание на телефоне: полоса чипов дней (schedule.js::dayChips) ──
   Липнет под шапкой: --header-h уже включает полосу подмены (app.js), а
   #main-tabs на телефоне скрыт — отдельный отступ под него не нужен. Два
   sticky с top:0 наехали бы друг на друга (CLAUDE.md, раздел 7). */
:root { --chips-h: 50px; }
.day-chips { display: none; }
.iot-fold { display: none; }
@media (max-width: 900px) {
  .day-chips {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 4px;
    position: sticky;
    top: var(--header-h, 54px);
    z-index: 20;
    height: var(--chips-h);
    box-sizing: border-box;
    padding: 5px 0;
    margin: 0 0 10px;
    background: var(--bg-void);
    border-bottom: 1px solid var(--border-dim);
  }
  .day-chip {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-width: 0;
    padding: 2px 0;
    line-height: 1.15;
    color: var(--text-main);
  }
  .day-chip .dc-name { font-size: 10px; color: var(--text-dim); }
  .day-chip .dc-num { font-size: 14px; font-weight: 700; }
  .day-chip.today { border-color: var(--accent); color: var(--accent); background: var(--open-bg); }
  .day-chip.today .dc-name { color: var(--accent); }
  .day-chip.empty { opacity: .45; }
  /* Прокрутка к дню (чип, «сегодня») останавливает колонку под полосой, а
     не под ней скрытой. */
  .sched-days .day-col { scroll-margin-top: calc(var(--header-h, 54px) + var(--chips-h) + 8px); }

  /* Панель ИОТов свёрнута в одну кнопку (schedule.js::iotFoldButton). */
  .iot-fold { display: inline-flex; align-items: center; gap: 4px; }
  .iot-fold > .ico { margin: 0; transition: transform .15s ease; }
  .iot-fold[aria-expanded="true"] > .ico { transform: rotate(180deg); }
  .iot-bar.folded { padding: 0; border: 0; background: none; }
  .iot-bar.folded > :not(.iot-fold) { display: none; }
}

/* ── Ряд действий карточки под «⋯» (tasks.js::foldOps) ──
   На десктопе обёртка прозрачна: кнопки «⋯» нет, ряд .ops как был. */
.ops-more { display: none; }
/* Точки «⋯» — отрезки нулевой длины, их размер задаёт толщина линии: при
   обычных 1.4 они выходят едва заметными крапинками. */
.ops-more > .ico, #bottom-nav [data-more] > .ico { stroke-width: 2.6; }
@media (max-width: 900px) {
  .ops-fold { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; }
  .ops-fold > .ops-more { display: inline-flex; align-items: center; padding: 4px 14px; }
  .ops-fold > .ops-more > .ico { width: 1.5em; height: 1.5em; }
  .ops-fold.open > .ops-more { border-color: var(--accent); }
  .ops-fold > .ops { flex-basis: 100%; margin-top: 0; }
  .ops-fold:not(.open) > .ops { display: none; }
}

/* Шторка «Ещё»: крупные строки во всю ширину, выход — отдельным цветом. */
.more-list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.more-list .more-item { display: flex; align-items: center; justify-content: flex-start; text-align: left; width: 100%; padding: 13px 14px; font-size: 14px; }
.more-list .more-item.active { color: var(--accent); border-color: var(--accent); }
.more-list .more-item.danger { margin-top: 6px; }

/* ═══════════ Базовые анимации входа ═══════════
   Только opacity/transform и 120-180мс — не тревожат layout, не грузят слабые
   устройства. Всё исполняется в браузере, сервер тут ни при чём. Глушится
   целиком через prefers-reduced-motion сразу под этим блоком. Тема может
   заменить этот слой своим эффектом — см. «Эффекты появления по теме» ниже. */
@keyframes fade-in { from { opacity: 0; } }
@keyframes rise-in { from { opacity: 0; transform: translateY(6px); } }
@keyframes pop-in  { from { opacity: 0; transform: translateY(6px) scale(.97); } }

#overlay { animation: fade-in .15s ease; }
#overlay .modal { animation: pop-in .18s ease; }
/* main > section переключает .hidden (display:none) — браузер сам заново
   проигрывает CSS-анимацию при каждом появлении элемента, JS не трогали. */
main > section:not(.hidden) { animation: rise-in .16s ease; }
.iot-chip { animation: fade-in .15s ease; }
.node { animation: rise-in .15s ease; }

/* ═══════════ Эффекты появления по теме (fx.js::MOTION_EFFECTS) ═══════════
   Выбор наравне с фоном и текстурой: пикер и Настройки ставят data-motion
   на <html> (fx.js::applyMotion), «Авто» берёт значение из THEMES[].motion
   (theme.js). Каждое правило переопределяет только `animation`/`background-*`
   у тех же четырёх точек, что и базовый слой выше (кроме глянца — он
   специально сужен до трёх, см. его комментарий) — специфичность атрибута
   в селекторе побеждает сама, менять базовые правила не пришлось.
   Не каждой теме назначен эффект: нейтральным dev-темам и официальным/плоским
   идёт голый базовый слой (data-motion="none" или атрибута нет вовсе). */

/* Глитч — Cyberpunk, Matrix, «Клод сделал тему». Дрожь по X и цветная кайма
   из uA/uB… то есть из --accent/--accent2 темы: раздвоение канала без готовой
   картинки. Без clip-path — на большой секции с длинным списком он резал бы
   контент отрезком в проценты собственной высоты, здесь только сдвиг и тень. */
@keyframes glitch-in {
  0%   { opacity: 0; transform: translateX(0); filter: none; }
  8%   { opacity: 1; transform: translateX(-3px); filter: drop-shadow(2px 0 var(--accent2)) drop-shadow(-2px 0 var(--accent)); }
  16%  { transform: translateX(2px); }
  24%  { transform: translateX(-1px); filter: drop-shadow(1px 0 var(--accent2)) drop-shadow(-1px 0 var(--accent)); }
  32%  { transform: translateX(1px); }
  42%  { transform: translateX(0); filter: none; }
  100% { opacity: 1; transform: translateX(0); }
}
/* Мерцание ЭЛТ — CRT Terminal, Amber. Прогрев старого монитора: волна
   яркости, а не строб. Первая версия шла через steps() и ныряла до .15 —
   по прямому фидбеку это резало глаза (буквально стробоскоп), переделано на
   плавные ease-переходы с полом не ниже .55 и всего двумя колебаниями. */
@keyframes flicker-in {
  0%   { opacity: 0; }
  25%  { opacity: 1; }
  40%  { opacity: .6; }
  60%  { opacity: 1; }
  75%  { opacity: .75; }
  100% { opacity: 1; }
}
/* Глянец — Windows XP, Windows 11. Один блик по диагонали, как на кнопке
   в стиле Luna/Fluent.
   Было два живых бага, оба по прямому фидбеку:
   — вешался и на main > section, то есть на весь контент вкладки целиком.
     На большом контейнере тот же процент ширины полосы даёт не блик, а
     размытое бледное пятно (видно на скриншоте пользователя — как раз
     такое пятно в углу сетки расписания). Эффект убран с секций совсем:
     они остаются на обычном rise-in, глянец — только на «предметных»
     элементах (модалка, фишка, узел), для которых он и задумывался;
   — после окончания анимации (fill-mode по умолчанию — none) свойство
     background-position откатывалось не к «чисто выметено», а к своему
     начальному 0% 0%, которое у нашего градиента как раз ПОПАДАЛО в
     светлую полосу — отсюда «доездила и осталось белое пятно». Лечится
     animation-fill-mode: forwards — застывает на последнем кадре пути,
     который специально уводит полосу за пределы видимой области.
   Ещё смена: чистый белый normal-blend просто высветлял всё подряд и на
   светлых темах превращался в мутную дымку без контраста. Первая попытка
   лечения — background-blend-mode: overlay — оказалась ХУЖЕ по другой
   причине: у overlay результат зависит от яркости базового цвета почти
   квадратично, и на тёмном --bg-panel/--bg-widget (а тёмных тем в списке
   большинство, и эффект можно выбрать поверх любой темы, не только
   светлых Windows) блик становился практически не виден. Отсюда и жалоба
   «глянец виден только на фишках»: у .iot-chip своего background-color нет
   (прозрачный), overlay блендить не с чем, и там полоса шла как есть, а на
   модалке и узле с их тёмным фоном почти пропадала. screen лечит оба
   случая разом: он всегда высветляет (никогда не темнит), поэтому на тёмном
   фоне даёт настоящий заметный блик, а на светлом — короткую яркую вспышку
   по узкой полосе, не более варварскую, чем блик на iot-chip. */
@keyframes sheen-in {
  0%   { opacity: 0; transform: translateY(6px); background-position: -40% -40%; }
  55%  { opacity: 1; transform: translateY(0); }
  100% { opacity: 1; transform: translateY(0); background-position: 130% 130%; }
}
/* Плавность — Claude, Гламур, Fusion, Сепия: тёплые/бумажные темы получают
   более мягкий вход через блёр вместо резкого проявления. */
@keyframes soft-in {
  from { opacity: 0; transform: translateY(4px) scale(.99); filter: blur(3px); }
  to   { opacity: 1; transform: none; filter: none; }
}

[data-motion="glitch"] #overlay .modal,
[data-motion="glitch"] main > section:not(.hidden),
[data-motion="glitch"] .iot-chip,
[data-motion="glitch"] .node { animation: glitch-in .26s ease-out; }

[data-motion="flicker"] #overlay .modal,
[data-motion="flicker"] main > section:not(.hidden),
[data-motion="flicker"] .iot-chip,
[data-motion="flicker"] .node { animation: flicker-in .34s ease-in-out; }

/* Только «предметные» элементы — не main > section, см. комментарий у
   @keyframes sheen-in. */
[data-motion="sheen"] #overlay .modal,
[data-motion="sheen"] .iot-chip,
[data-motion="sheen"] .node {
  background-image: linear-gradient(115deg, transparent 38%, rgba(255,255,255,.7) 50%, transparent 62%);
  background-repeat: no-repeat;
  background-size: 170% 170%;
  background-blend-mode: screen;
  animation: sheen-in .36s ease-out forwards;
}

[data-motion="soft"] #overlay .modal,
[data-motion="soft"] main > section:not(.hidden),
[data-motion="soft"] .iot-chip,
[data-motion="soft"] .node { animation: soft-in .22s ease-out; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

@media print {
  header, .tabs, .toolbar, #toasts, #overlay, .subj-list { display: none !important; }
  body { background: #fff; color: #000; }
}

/* Черновик карточки, который разбор ДЗ оставил админу на подтверждение
   (вкладка «Управление» → очередь сообщений из топика). Пунктирная рамка
   отделяет предложение модели от того, что реально написали в чате: спутать
   одно с другим — значит подтвердить не глядя. */
.review-draft {
  margin-top: 9px;
  padding: 8px 10px 10px;
  border: 1px dashed var(--accent2);
  border-radius: var(--rounding);
}
.review-draft label.field { margin-top: 7px; }
.review-draft textarea { min-height: 54px; }
.review-draft .row { margin-top: 9px; }

/* Превью картинки-вложения под строкой файла. Ограничение по высоте, а не по
   ширине: фотографии доски приходят и вертикальные, и горизонтальные, а карточка
   задания не должна из-за одной из них растянуться на весь экран. */
.attach-preview { display: block; margin-top: 5px; }
.attach-img {
  display: block;
  max-width: 100%;
  max-height: 240px;
  border: 1px solid var(--border-dim);
  border-radius: var(--rounding);
}

/* Предпросмотр раскладки «задание → пара» перед простановкой дат. */
.date-plan { margin-top: 10px; max-height: 320px; overflow-y: auto; }
.date-plan-line {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 4px 0;
  border-bottom: 1px solid var(--border-dim);
  font-size: 12px;
}

/* Второй путь в черновике — приложить файл к существующему заданию вместо
   создания нового. Отделён линией, чтобы не путался с основной формой. */
.review-alt { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--border-dim); }

/* Предупреждение у входа через ЛК ССАУ («на свой страх и риск», где лежат
   данные). Текст правит суперадмин, переносы строк из него сохраняются. */
.lk-disclaimer {
  margin-top: 10px; padding: 8px 10px;
  border-left: 3px solid var(--warn); background: color-mix(in srgb, var(--warn) 8%, transparent);
  font-size: 12px; line-height: 1.45; white-space: pre-wrap;
}

/* ═══════════ Выдвижная панель: заметки и справка ═══════════
   Панель — обычная модалка с data-kind (modal.js): «Назад», Esc и клик
   мимо закрывают её так же, как любое окно. Своё у неё только место: на
   компьютере она выезжает справа во всю высоту — запись или справка не
   должны закрывать середину экрана, на которую человек как раз смотрел. На
   телефоне — та же шторка снизу, что у всех модалок, только высокая: поле
   ввода наверху шторки остаётся над экранной клавиатурой.

   Блок стоит в самом конце файла намеренно: у эффектов появления тем
   ([data-motion] #overlay .modal) та же специфичность, и выезд панели
   справа побеждает их только порядком. :is() специфичность не повышает. */

#overlay:is([data-kind="notes"], [data-kind="help"]) .modal {
  display: flex;
  flex-direction: column;
  /* Прокручивается список, а не окно целиком: поле ввода остаётся наверху. */
  overflow: hidden;
  position: relative;
}
/* Крестик в колонке flex: float там не действует, ставим его в угол сами. */
#overlay:is([data-kind="notes"], [data-kind="help"]) .modal > .close { position: absolute; top: 12px; right: 12px; margin: 0; float: none; }
.notes-body, .help-body { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }

@media (min-width: 901px) {
  #overlay:is([data-kind="notes"], [data-kind="help"]) { justify-content: flex-end; align-items: stretch; padding: 0; background: rgba(0, 0, 0, .35); }
  #overlay:is([data-kind="notes"], [data-kind="help"]) .modal {
    width: min(440px, 100%);
    height: 100%;
    max-height: none;
    border-width: 0 0 0 1px;
    border-radius: 0;
    animation: drawer-in .2s ease;
    /* Глянец темы рисует блик фоном и уводит его за край своей анимацией.
       Анимацию мы заменили — без этой строки блик застыл бы на панели. */
    background-image: none;
  }
}
@media (max-width: 900px) {
  #overlay:is([data-kind="notes"], [data-kind="help"]) .modal { height: 88vh; height: 88dvh; }
}
@keyframes drawer-in { from { opacity: 0; transform: translateX(24px); } }

/* ── Быстрый ввод ── */
.note-compose { display: flex; gap: 6px; align-items: flex-end; }
.note-compose .note-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  max-height: 40vh;
  resize: none;
  overflow-y: auto;
  line-height: 1.4;
}
.note-compose .note-add { flex: none; }
.note-hint { margin-top: 4px; }

/* ── Метки-фильтры ── */
.note-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.note-chip.active { color: var(--accent); border-color: var(--accent); background: var(--open-bg); }

/* ── Список ── */
.note-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin-top: 10px; padding: 2px 2px 8px; }
.note-empty { padding: 18px 4px; text-align: center; }
.note-card {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  border: 1px solid var(--border-dim);
  border-radius: var(--rounding);
  background: var(--card-bg);
  padding: 8px 10px;
  margin-bottom: 8px;
}
.note-card.pinned { border-color: var(--inactive-border); }
/* Подсветка только что записанной — рамкой внутрь, а не outline снаружи:
   прокручиваемый список обрезает всё, что за краем карточки, и от outline
   оставалась бы одна случайная полоска. */
.note-card.flash { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.note-check { flex: none; }
.note-main { flex: 1 1 auto; min-width: 0; cursor: pointer; border-radius: var(--rounding); }
.note-main:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.note-title { color: var(--accent); font-weight: 700; font-size: 13px; overflow-wrap: anywhere; }
.note-body { font-size: 12px; white-space: pre-wrap; overflow-wrap: anywhere; margin-top: 2px; }
.note-body.clamped {
  display: -webkit-box;
  -webkit-line-clamp: 6;
  line-clamp: 6;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.note-more { padding: 0; border: none; background: none; font-size: 11px; color: var(--accent); text-decoration: underline; }
.note-more:hover:not(:disabled) { background: none; border: none; }
.note-meta { display: flex; flex-wrap: wrap; align-items: center; font-size: 10px; color: var(--text-dim); margin-top: 3px; }
.note-meta > span + span::before { content: "·"; margin: 0 6px; }
.note-tag { color: var(--accent2); }
.note-pinned { color: var(--accent); display: inline-flex; align-items: center; }
.note-card.done .note-title { text-decoration: line-through; opacity: .65; }
.note-card.done .note-body { opacity: .65; }
.note-done { margin-top: 6px; }
.note-done > summary { cursor: pointer; margin-bottom: 8px; }

/* ── Правка прямо в карточке ── */
.note-card.editing { display: block; border-color: var(--accent); }
.note-card.editing > * + * { margin-top: 6px; }
.note-edit-title { font-weight: 700; }
.note-edit-body { min-height: 90px; max-height: 50vh; resize: vertical; line-height: 1.4; }
.note-edit-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.note-edit-tag { flex: 0 1 200px; min-width: 0; }
.note-edit-row label.chk { margin-top: 0; }
.note-edit-actions, .note-confirm { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.note-edit-actions .spacer { flex: 1; }
.note-edit-msg:empty { display: none; }

/* ═══════════ Поиск по сайту (views/search.js) ═══════════
   Палитра — модалка с data-kind="search". На компьютере она у верхнего
   края, как привычные палитры по Ctrl+K: выдача растёт вниз, а не прыгает
   от центра с каждой буквой. На телефоне — высокая шторка, как у заметок:
   поле наверху остаётся над экранной клавиатурой. */

#overlay[data-kind="search"] .modal {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative;
  width: 640px;
  padding: 14px 16px 10px;
}
#overlay[data-kind="search"] .modal > .close { position: absolute; top: 14px; right: 12px; margin: 0; float: none; }
.search-body { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
@media (min-width: 901px) {
  #overlay[data-kind="search"] { align-items: flex-start; padding-top: 10vh; }
  #overlay[data-kind="search"] .modal { max-height: 76vh; }
}
@media (max-width: 900px) {
  #overlay[data-kind="search"] .modal { height: 88vh; height: 88dvh; }
}

/* Отступ справа — под крестик окна, он стоит в том же углу. */
.sr-box { position: relative; display: block; margin-right: 34px; }
.sr-box > .ico { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); margin: 0; color: var(--text-dim); pointer-events: none; }
.sr-input { padding: 9px 10px 9px 32px; font-size: 15px; }
.sr-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin-top: 10px; }
.sr-group { color: var(--accent2); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; margin: 12px 0 4px; }
.sr-group:first-child { margin-top: 2px; }
/* Строка выдачи — кнопка с содержимым, а не подпись-команда: глобальный
   nowrap кнопок ей противопоказан. */
.sr-row {
  display: block;
  width: 100%;
  text-align: left;
  white-space: normal;
  overflow-wrap: anywhere;
  padding: 6px 10px;
  margin: 2px 0;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--rounding);
  color: var(--text-main);
}
.sr-row:hover:not(:disabled) { background: var(--hover-bg); border-color: transparent; }
.sr-row.active, .sr-row.active:hover:not(:disabled) { background: var(--active-bg); border-color: var(--accent); }
.sr-row.more .sr-title { color: var(--text-dim); font-weight: 400; font-size: 12px; }
.sr-row.done .sr-title { text-decoration: line-through; opacity: .7; }
.sr-title { color: var(--accent); font-weight: 700; font-size: 13px; }
.sr-meta { color: var(--text-dim); font-size: 10px; margin-top: 1px; }
.sr-snip { font-size: 11px; margin-top: 2px; opacity: .85; }
.sr-row mark { background: var(--selection-bg); color: inherit; border-radius: 2px; padding: 0 1px; }
.sr-tip { margin: 2px 0 6px; }
.sr-empty { padding: 14px 4px; text-align: center; }
.sr-foot { margin-top: 8px; text-align: right; }
/* Стрелок и Enter на телефоне нет — и подсказки про них тоже. */
@media (pointer: coarse) { .sr-foot { display: none; } }

/* Куда привёл поиск: рамка раздела настроек и пара в сетке. */
.frame.flash, .lesson.flash { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── Тестеры и гости (Управление, admin.js) ── */
.tester-head { color: var(--accent2); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; margin: 10px 0 4px; }
.tester-row { border: 1px solid var(--border-dim); border-radius: var(--rounding); padding: 8px 10px; margin: 6px 0; }
.tester-row > * + * { margin-top: 6px; }
.tester-perms { display: flex; flex-wrap: wrap; gap: 2px 16px; }
.tester-perms label.chk { margin-top: 0; font-size: 12px; }
.invite-link { user-select: all; word-break: break-all; }

/* ═══════════ Справка (views/help.js) ═══════════
   Место — общее с заметками (выдвижная панель выше); здесь только чтение:
   строка длиннее и интервал шире, чем в карточках, — это текст, а не список. */
.help-view { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding-right: 4px; }
.help-lead { margin-bottom: 6px; }
.help-group { color: var(--accent2); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; margin: 14px 0 4px; }
/* Пункт оглавления — кнопка с содержимым: глобальный nowrap ей противопоказан. */
.help-toc-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  white-space: normal;
  padding: 7px 10px;
  margin: 2px 0;
  background: none;
  border: 1px solid transparent;
  color: var(--text-main);
  font-size: 13px;
}
.help-toc-item:hover:not(:disabled) { background: var(--hover-bg); border-color: transparent; }
.help-toc-item .badge { margin-left: auto; color: var(--text-dim); }
.help-nav { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.help-nav .help-copy { margin-left: auto; }
/* Контекстный «?» (help.js::helpButton): тише соседних кнопок — подсказка, а не действие. */
.help-ctx { opacity: .7; padding: 2px 4px; vertical-align: middle; }
.help-ctx:hover { opacity: 1; }
.help-title { color: var(--accent); font-size: 16px; margin: 6px 0 10px; }
.help-h { color: var(--accent2); font-size: 12px; letter-spacing: .04em; text-transform: uppercase; margin: 18px 0 6px; }
.help-p { margin: 0 0 9px; font-size: 13px; line-height: 1.6; }
.help-list { margin: 0 0 10px; padding-left: 22px; font-size: 13px; line-height: 1.6; }
.help-list li { margin: 3px 0; }
.help-list li::marker { color: var(--accent); }
.help-note { font-size: 12px; color: var(--text-dim); border-left: 2px solid var(--border-dim); padding: 4px 10px; margin: 0 0 10px; }
.help-warn {
  font-size: 12px; line-height: 1.5; margin: 0 0 10px; padding: 6px 10px;
  border-left: 3px solid var(--warn); background: color-mix(in srgb, var(--warn) 8%, transparent);
}
.help-draft { border: 1px dashed var(--border-dim); border-radius: var(--rounding); padding: 8px 12px; font-size: 12px; }
.help-draft .help-list { margin-top: 6px; font-size: 12px; }
.help-see { font-size: 12px; margin-top: 14px; }
.help-view kbd {
  font-family: inherit; font-size: 11px; padding: 0 5px;
  border: 1px solid var(--border-dim); border-bottom-width: 2px; border-radius: 4px; color: var(--accent);
}
/* Иконка посреди текста — в строку, с отступами с обеих сторон, а не только
   справа, как у иконки перед подписью кнопки. */
.help-view .help-ico, .help-view .help-ico:first-child { margin: 0 2px; vertical-align: -.25em; color: var(--accent); }
.help-link {
  padding: 0; border: none; background: none; font-size: inherit;
  color: var(--accent); text-decoration: underline; white-space: normal; text-align: left;
}
.help-link:hover:not(:disabled) { background: none; border: none; }
.help-pager { display: flex; justify-content: space-between; gap: 8px; margin-top: 18px; padding-top: 10px; border-top: 1px solid var(--border-dim); }
.help-pager button { white-space: normal; text-align: left; }
.help-next { margin-left: auto; text-align: right; }
.help-next > .ico { margin: 0 0 0 5px; }

/* ── Расписание других групп (views/group-peek.js) ── */
#overlay[data-kind="peek"] .modal.wide { width: min(980px, calc(100vw - 32px)); }
.peek-search input { flex: 1 1 200px; min-width: 0; }
.peek-results, .peek-pins { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.peek-pins .ico { margin-right: 4px; }
.peek-head { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border-dim); }
.peek-head .row { flex-wrap: wrap; align-items: center; gap: 8px; }
.peek-view { margin-top: 8px; }
.peek-day { margin: 14px 0 6px; color: var(--accent2); font-weight: bold; }
.peek-day.today { color: var(--accent); }
.peek-row { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 8px; margin-bottom: 6px; }
.peek-time { font-size: 12px; padding-top: 4px; }
.peek-card { border: 1px solid var(--border-dim); border-left-width: 3px; border-radius: var(--rounding);
  padding: 4px 8px; background: var(--card-bg); margin-bottom: 4px; min-width: 0; }
.peek-card .t { font-weight: bold; overflow-wrap: anywhere; }
.peek-card .m { font-size: 12px; color: var(--text-dim); overflow-wrap: anywhere; }
.peek-card.off { opacity: .45; }
.peek-card.off .t { text-decoration: line-through; }
.peek-cmp { display: grid; grid-template-columns: 96px minmax(0, 1fr) minmax(0, 1fr); gap: 4px 8px; }
.peek-cmp-h { font-size: 11px; text-transform: uppercase; }
.peek-cmp-t { font-size: 12px; padding-top: 4px; color: var(--text-dim); }
.peek-cmp-c { min-width: 0; }
.peek-cmp-c.free, .peek-cmp-t.free { background: var(--hover-bg); border-radius: var(--rounding); }
.peek-cmp-t.together { color: var(--ok); }
.peek-tag { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; }
.peek-cmp-t.free .peek-tag { color: var(--accent); }
.peek-sum { margin-top: 4px; font-size: 13px; cursor: help; }
@media (max-width: 600px) {
  .peek-row { grid-template-columns: 64px minmax(0, 1fr); }
  .peek-cmp { grid-template-columns: 58px minmax(0, 1fr) minmax(0, 1fr); gap: 4px; }
  .peek-card { padding: 3px 5px; }
  .peek-cmp .peek-card .m { display: none; }
}

/* ───────────── Питомцы (js/pets.js) ───────────── */
#pets { position: fixed; left: 0; right: 0; bottom: 0; height: 0; z-index: 60; pointer-events: none; }
.pet { position: absolute; left: 0; bottom: 2px; will-change: transform; cursor: pointer; }
.pet-body { line-height: 0; transform-origin: 50% 100%; }
.pet-body svg { display: block; }
.pet.walking .pet-body { animation: pet-bob .44s steps(2) infinite; }
@keyframes pet-bob { 50% { translate: 0 -2px; } }
.pet-body.jump { animation: pet-jump .45s ease-out !important; }
@keyframes pet-jump { 40% { translate: 0 -22px; } }
.pet-bubble { position: absolute; left: 50%; bottom: 100%; margin-bottom: 6px; transform: translateX(-50%); white-space: nowrap; pointer-events: none; padding: 3px 8px; font-size: 12px; opacity: 0; transition: opacity .15s; background: var(--bg-panel); color: var(--text-main, inherit); border: 1px solid var(--accent); }
.pet-bubble.show { opacity: 1; }
