feat: add system dark theme foundation

This commit is contained in:
ik
2026-09-13 15:23:33 +07:00
parent b7b49a3a63
commit 8ffbd7f9ec
5 changed files with 130 additions and 2 deletions
+2
View File
@@ -93,6 +93,8 @@ FastAPI ─ PostgreSQL 17
Production CSP ограничивает browser-запросы текущим доменом и отдельным files-доменом для изображений, запрещает plugins, frames, inline handlers и attributes, eval, wildcard и HTTP. Page scripts и scoped styles принудительно выпускаются отдельными same-origin `_astro`-ассетами; единственное временное inline-исключение остаётся для динамического JSON-LD и описано в [CSP inventory](docs/csp-inventory.md). Production CSP ограничивает browser-запросы текущим доменом и отдельным files-доменом для изображений, запрещает plugins, frames, inline handlers и attributes, eval, wildcard и HTTP. Page scripts и scoped styles принудительно выпускаются отдельными same-origin `_astro`-ассетами; единственное временное inline-исключение остаётся для динамического JSON-LD и описано в [CSP inventory](docs/csp-inventory.md).
Первый этап тёмной темы следует системному `prefers-color-scheme` без JavaScript, сохранённого состояния и вспышки неверной палитры. Семантические роли и ограничения миграции описаны в [dark-theme.md](docs/dark-theme.md); ручной выбор и SSR-cookie запланированы следующими этапами D03–D04.
Gitea Actions workflow `.gitea/workflows/ci.yml` на каждый push и pull request проверяет Python, миграции на чистой PostgreSQL, Astro build и полный Compose/Playwright-сценарий. При падении E2E сохраняются логи контейнеров и Playwright-артефакты. Gitea Actions workflow `.gitea/workflows/ci.yml` на каждый push и pull request проверяет Python, миграции на чистой PostgreSQL, Astro build и полный Compose/Playwright-сценарий. При падении E2E сохраняются логи контейнеров и Playwright-артефакты.
Актуальная инвентаризация источников и правила подключения адаптеров находятся в [`docs/data-source-audit.md`](docs/data-source-audit.md). Разрешённый технический пилот RF4DB/RF4-STAT описан в [`docs/community-source-pilot.md`](docs/community-source-pilot.md), а статус разрешений и лимитов — в [`docs/data-permissions.md`](docs/data-permissions.md). Все наблюдения сначала попадают в staging. Полные записи с уже подтверждёнными алиасами могут публиковаться автоматически; новые соответствия требуют модерации, а неполные записи показываются отдельно и не влияют на индекс. Актуальная инвентаризация источников и правила подключения адаптеров находятся в [`docs/data-source-audit.md`](docs/data-source-audit.md). Разрешённый технический пилот RF4DB/RF4-STAT описан в [`docs/community-source-pilot.md`](docs/community-source-pilot.md), а статус разрешений и лимитов — в [`docs/data-permissions.md`](docs/data-permissions.md). Все наблюдения сначала попадают в staging. Полные записи с уже подтверждёнными алиасами могут публиковаться автоматически; новые соответствия требуют модерации, а неполные записи показываются отдельно и не влияют на индекс.
+1
View File
@@ -13,6 +13,7 @@ import "../styles/alpha-banner.css";
import "../styles/signal-pagination.css"; import "../styles/signal-pagination.css";
import "../styles/dashboard-polish.css"; import "../styles/dashboard-polish.css";
import "../styles/loading-states.css"; import "../styles/loading-states.css";
import "../styles/theme.css";
import FishingIcon from "../components/FishingIcon.astro"; import FishingIcon from "../components/FishingIcon.astro";
import AlphaBanner from "../components/AlphaBanner.astro"; import AlphaBanner from "../components/AlphaBanner.astro";
const { const {
+108
View File
@@ -0,0 +1,108 @@
:root {
color-scheme: light dark;
--canvas: #f2f5ee;
--surface-elevated: #ffffff;
--surface-raised: #fbfcf9;
--surface-control: #f3f6f1;
--text-primary: #092226;
--text-secondary: #526662;
--text-tertiary: #647572;
--text-on-dark: #f5f8f3;
--border-strong: #cbd6ce;
--border-subtle: #d6dfd7;
--shadow-color: #14333812;
}
@media (prefers-color-scheme: dark) {
:root {
--canvas: #071719;
--surface-elevated: #0d2325;
--surface-raised: #10282a;
--surface-control: #0a1d1f;
--text-primary: #edf5ef;
--text-secondary: #b8c7c3;
--text-tertiary: #93a7a2;
--text-on-dark: #f4f8f5;
--border-strong: #3a5658;
--border-subtle: #294244;
--shadow-color: #00000052;
--paper: var(--canvas);
--surface: var(--surface-elevated);
--surface-soft: var(--surface-raised);
--surface-field: var(--surface-control);
--ink: var(--text-primary);
--text: var(--text-primary);
--text-muted: var(--text-secondary);
--text-subtle: var(--text-tertiary);
--line: var(--border-strong);
--border: var(--border-strong);
--border-soft: var(--border-subtle);
--focus: #c9f45b;
--success: #c9ec8a;
--success-soft: #243e25;
--warning: #f3ce7a;
--warning-soft: #46391d;
--danger: #ffafa3;
--danger-soft: #4b2928;
}
body { background: var(--canvas); color: var(--text-primary); }
.brand-name span, .topbar nav a, .live-badge, .eyebrow, .overline,
.intro-copy > p, .active-filters a, .result-count, .spot-rank,
.spot-topline, .spot-meta, .bait-line div span, .spot-stats span,
.data-note, .principles p, .principles article > span, .official-note,
.privacy, .field-help, .record-row > span, .record-row > time,
.report-form label, .screenshot-retry label, .back, .note,
.moderation-summary > p, .moderation-summary dt,
.moderation-provenance summary, .legal-page p, .legal-page li {
color: var(--text-secondary);
}
.topbar nav a.active, .eyebrow b, .state h2, .admin-kpis strong,
.data-passport, .data-passport dd, .legal-page h1, .legal-page h2 {
color: var(--text-primary);
}
.active-filters span, .spot-card, .record-table, .report-form,
.periods, .detail-grid aside, .screenshot-retry, .moderation-card,
.admin-kpis > a, .admin-kpis > article, .admin-dashboard-grid > section,
.source-health-grid article, .state, .load-more, .signal-more {
background: var(--surface-elevated);
border-color: var(--border-subtle);
}
.alpha-banner { background: linear-gradient(90deg, #132a26, #102426); color: var(--text-secondary); border-color: var(--border-strong); }
.alpha-banner strong { color: var(--text-primary); }
.alpha-banner__cta { background: var(--lime); color: #082226; }
.spot-card:hover { background: var(--surface-raised); border-color: #6f8988; box-shadow: 0 18px 40px var(--shadow-color); }
.report-form input, .report-form textarea, .report-form select,
.moderation-actions textarea { background: var(--surface-control); color: var(--text-primary); border-color: var(--border-strong); }
.record-head, .no-proof { background: var(--surface-raised); }
.record-row, .periods > div, .catch-list article, .detail-grid aside li,
.bait-line, .spot-stats, .moderation-summary dl div,
.moderation-actions, .how-it-works, .signal-heading {
border-color: var(--border-subtle);
}
.signal-card { background: linear-gradient(145deg, #20291f, #151f20); border-color: #756b3d; }
.signal-card > p, .signal-card dd, .signal-heading p { color: var(--text-secondary); }
.signal-card dt { color: #b9ae82; }
.signal-card .missing-note { background: #453c20; color: #f0d889; }
.signal-card .data-passport { background: #172628; border-color: #59605a; }
.source-chip { background: color-mix(in srgb, var(--chip) 24%, var(--surface-elevated)); }
.notice.success { background: var(--success-soft); color: var(--success); }
.notice.warning, .data-quality { background: var(--warning-soft); color: var(--warning); }
.notice.error { background: var(--danger-soft); color: var(--danger); }
.moderation-summary blockquote { background: var(--surface-raised); color: var(--text-secondary); }
.moderation-actions .reject { background: var(--danger-soft); color: var(--danger); }
.moderation-actions .delete { color: var(--danger); border-color: #82504c; }
.activity-timeline { background: var(--surface-elevated); border-color: var(--border-subtle); }
.timeline-chart { border-color: var(--border-strong); background: repeating-linear-gradient(to top, transparent 0, transparent 35px, #294244 36px); }
.timeline-slot strong { color: var(--text-primary); }
.timeline-slot small, .activity-legend { color: var(--text-tertiary); }
}
@media print {
:root { color-scheme: light; }
}
+2 -2
View File
@@ -40,8 +40,8 @@
Реализовывать последовательно: сначала семантическая палитра и системный режим, затем ручное управление и полировка компонентов. Тёмная тема должна сохранять полевую эстетику RF4 Spotter, а не быть механической инверсией светлой. Реализовывать последовательно: сначала семантическая палитра и системный режим, затем ручное управление и полировка компонентов. Тёмная тема должна сохранять полевую эстетику RF4 Spotter, а не быть механической инверсией светлой.
- [ ] **D01 · Семантическая палитра и color-scheme.** Провести инвентаризацию жёстко заданных цветов во всех CSS-файлах и SVG-компонентах; дополнить токены ролями canvas/surface/elevated/text/border/interactive/status/source для light и dark. Задать `color-scheme: light dark`, проверить нативные form controls и сохранить лаймовый акцент без свечения и кислотного контраста. - [x] **D01 · Семантическая палитра и color-scheme.** Жёсткие цвета и конфликтующая роль исторического `--deep` проинвентаризированы; добавлены независимые canvas/surface/elevated/control, text, border, shadow и контрастные status-роли для light/dark. Базовые public/admin поверхности получили первый dark-layer, нативные controls используют `color-scheme: light dark`; стратегия миграции зафиксирована в [dark-theme.md](dark-theme.md).
- [ ] **D02 · Системный режим без вспышки.** Первый визит следует `prefers-color-scheme`; серверный HTML и CSS сразу отдают правильную тему без inline bootstrap script. До появления ручного выбора не хранить лишнее состояние и не ослаблять CSP. - [x] **D02 · Системный режим без вспышки.** Первый визит следует `prefers-color-scheme` полностью через CSS: серверный HTML сразу совместим с системной темой, состояние не хранится, inline bootstrap не добавлен и CSP не ослаблена. Явное переопределение появится только вместе с D03–D04.
- [ ] **D03 · Переключатель темы.** Добавить доступный трёхпозиционный выбор «Системная / Светлая / Тёмная» в header и компактный вариант для мобильной навигации. Управление должно иметь текстовое accessible name, видимый focus, корректные `aria-pressed`/состояние и работать с клавиатуры. - [ ] **D03 · Переключатель темы.** Добавить доступный трёхпозиционный выбор «Системная / Светлая / Тёмная» в header и компактный вариант для мобильной навигации. Управление должно иметь текстовое accessible name, видимый focus, корректные `aria-pressed`/состояние и работать с клавиатуры.
- [ ] **D04 · Сохранение и SSR-согласование.** Хранить явный выбор в cookie с `SameSite=Lax`, безопасным сроком жизни и значениями allowlist; Astro SSR читает cookie и выставляет `data-theme` на `<html>`. Внешний client module мгновенно переключает тему и синхронизирует изменение системной схемы только в режиме «Системная», без localStorage-only flash и без inline-кода. - [ ] **D04 · Сохранение и SSR-согласование.** Хранить явный выбор в cookie с `SameSite=Lax`, безопасным сроком жизни и значениями allowlist; Astro SSR читает cookie и выставляет `data-theme` на `<html>`. Внешний client module мгновенно переключает тему и синхронизирует изменение системной схемы только в режиме «Системная», без localStorage-only flash и без inline-кода.
- [ ] **D05 · Темизация компонентов и графики.** Перевести header/footer, hero, карточки активности, таблицы, формы, provenance/quality badges, состояния, admin-панель, радар, timeline, силуэты, глифы и декоративные водные мотивы на семантические токены. Отдельно проверить фотографии, прозрачные PNG, screenshots, тени, градиенты и режим forced-colors; источники и статусы не должны различаться только цветом. - [ ] **D05 · Темизация компонентов и графики.** Перевести header/footer, hero, карточки активности, таблицы, формы, provenance/quality badges, состояния, admin-панель, радар, timeline, силуэты, глифы и декоративные водные мотивы на семантические токены. Отдельно проверить фотографии, прозрачные PNG, screenshots, тени, градиенты и режим forced-colors; источники и статусы не должны различаться только цветом.
+17
View File
@@ -0,0 +1,17 @@
# Тёмная тема: палитра и ограничения
Базовая ревизия выполнена 13 сентября 2026 года. Старый CSS содержит много жёстких цветов, а исторический `--deep` используется одновременно как фирменный тёмный фон и как текст на светлых карточках. Поэтому менять его значение для dark mode нельзя: роли разделяются постепенно без визуальной инверсии бренда.
## Семантические роли
- `--canvas` — фон страницы;
- `--surface-elevated`, `--surface-raised`, `--surface-control` — карточки, hover и поля;
- `--text-primary`, `--text-secondary`, `--text-tertiary`, `--text-on-dark` — иерархия текста;
- `--border-strong`, `--border-subtle`, `--shadow-color` — разделители и глубина;
- существующие status/source-токены сохраняют смысл, но получают контрастные dark-значения там, где используются как текст.
Первый этап следует `prefers-color-scheme` только через CSS и поэтому не создаёт вспышку неверной темы, не записывает состояние и не требует inline script. Явный выбор появится на этапе D03–D04 и будет согласован с SSR через allowlist-cookie.
## Дальнейшая миграция
Файл `theme.css` временно содержит точечные dark overrides для основных публичных и административных поверхностей. На этапе D05 жёсткие цвета последовательно заменяются семантическими токенами в исходных component styles; source/status различия сохраняют текст или форму и никогда не полагаются только на оттенок.