feat: synchronize theme browser chrome
This commit is contained in:
@@ -93,7 +93,7 @@ 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`, а переключатель в header позволяет выбрать системную, светлую или тёмную палитру. Выбор сохраняется в cookie и применяется Astro при SSR без localStorage-only flash и ослабления CSP. Публичные и административные поверхности, формы, таблицы, provenance/status-плашки и фирменная SVG/CSS-графика используют семантические light/dark-токены и базовый forced-colors layer; ограничения и оставшаяся визуальная приёмка описаны в [dark-theme.md](docs/dark-theme.md).
|
Тема по умолчанию следует системному `prefers-color-scheme`, а переключатель в header позволяет выбрать системную, светлую или тёмную палитру. Выбор сохраняется в cookie и применяется Astro при SSR без localStorage-only flash и ослабления CSP; browser chrome синхронизируется парными `theme-color`. Публичные и административные поверхности, формы, таблицы, provenance/status-плашки и фирменная SVG/CSS-графика используют семантические light/dark-токены и базовый forced-colors layer; ограничения и оставшаяся визуальная приёмка описаны в [dark-theme.md](docs/dark-theme.md).
|
||||||
|
|
||||||
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-артефакты.
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"scope": "/",
|
"scope": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#f2f5ee",
|
"background_color": "#071719",
|
||||||
"theme_color": "#082226",
|
"theme_color": "#071719",
|
||||||
"icons": [
|
"icons": [
|
||||||
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
|
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
|
||||||
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
|
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ const jsonLd = JSON.stringify({
|
|||||||
<link rel="icon" href="/favicon-32.png" sizes="32x32" type="image/png" />
|
<link rel="icon" href="/favicon-32.png" sizes="32x32" type="image/png" />
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<meta name="theme-color" content="#082226" />
|
<meta name="theme-color" content="#f2f5ee" media={theme === "system" ? "(prefers-color-scheme: light)" : theme === "light" ? "all" : "not all"} data-theme-color="light" />
|
||||||
|
<meta name="theme-color" content="#071719" media={theme === "system" ? "(prefers-color-scheme: dark)" : theme === "dark" ? "all" : "not all"} data-theme-color="dark" />
|
||||||
<meta property="og:locale" content="ru_RU" />
|
<meta property="og:locale" content="ru_RU" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:site_name" content="RF4 Spotter" />
|
<meta property="og:site_name" content="RF4 Spotter" />
|
||||||
@@ -94,6 +95,7 @@ const jsonLd = JSON.stringify({
|
|||||||
<script>
|
<script>
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
const themeButtons = document.querySelectorAll<HTMLButtonElement>("[data-theme-option]");
|
const themeButtons = document.querySelectorAll<HTMLButtonElement>("[data-theme-option]");
|
||||||
|
const themeColors = document.querySelectorAll<HTMLMetaElement>("[data-theme-color]");
|
||||||
const allowedThemes = new Set(["system", "light", "dark"]);
|
const allowedThemes = new Set(["system", "light", "dark"]);
|
||||||
|
|
||||||
const applyTheme = (value: string) => {
|
const applyTheme = (value: string) => {
|
||||||
@@ -103,6 +105,10 @@ const jsonLd = JSON.stringify({
|
|||||||
themeButtons.forEach((button) => {
|
themeButtons.forEach((button) => {
|
||||||
button.setAttribute("aria-pressed", String(button.dataset.themeOption === theme));
|
button.setAttribute("aria-pressed", String(button.dataset.themeOption === theme));
|
||||||
});
|
});
|
||||||
|
themeColors.forEach((meta) => {
|
||||||
|
const color = meta.dataset.themeColor;
|
||||||
|
meta.media = theme === "system" ? `(prefers-color-scheme: ${color})` : color === theme ? "all" : "not all";
|
||||||
|
});
|
||||||
const secure = location.protocol === "https:" ? "; Secure" : "";
|
const secure = location.protocol === "https:" ? "; Secure" : "";
|
||||||
document.cookie = `rf4-theme=${theme}; Max-Age=31536000; Path=/; SameSite=Lax${secure}`;
|
document.cookie = `rf4-theme=${theme}; Max-Age=31536000; Path=/; SameSite=Lax${secure}`;
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@
|
|||||||
- [x] **D03 · Переключатель темы.** В header добавлен доступный трёхпозиционный выбор «Системная / Светлая / Тёмная»; на узких экранах он сохраняет три понятные иконки и доступные названия. Кнопки работают с клавиатуры, имеют видимый focus и синхронизируют `aria-pressed`.
|
- [x] **D03 · Переключатель темы.** В header добавлен доступный трёхпозиционный выбор «Системная / Светлая / Тёмная»; на узких экранах он сохраняет три понятные иконки и доступные названия. Кнопки работают с клавиатуры, имеют видимый focus и синхронизируют `aria-pressed`.
|
||||||
- [x] **D04 · Сохранение и SSR-согласование.** Выбор хранится год в allowlist-cookie `rf4-theme` с `SameSite=Lax` и `Secure` на HTTPS; Astro SSR выставляет `data-theme` до отрисовки, а системный режим оставляет выбор браузеру. Собранный Astro client script переключает тему без inline-кода, localStorage и ослабления CSP.
|
- [x] **D04 · Сохранение и SSR-согласование.** Выбор хранится год в allowlist-cookie `rf4-theme` с `SameSite=Lax` и `Secure` на HTTPS; Astro SSR выставляет `data-theme` до отрисовки, а системный режим оставляет выбор браузеру. Собранный Astro client script переключает тему без inline-кода, localStorage и ослабления CSP.
|
||||||
- [ ] **D05 · Темизация компонентов и графики.** На семантические light/dark-токены переведены базовые и admin-поверхности, header, каталог, breadcrumb-леска, ссылки сущностей, таблицы, формы, moderation-карточки, provenance/quality/status badges, паспорта данных, pagination, empty/loading-состояния, радар, timeline, силуэты и глифы; добавлен базовый forced-colors layer. Осталась визуальная проверка фотографий, прозрачных PNG, screenshots, теней и градиентов в обеих темах. Источники и статусы сохраняют текстовые подписи и не различаются только цветом.
|
- [ ] **D05 · Темизация компонентов и графики.** На семантические light/dark-токены переведены базовые и admin-поверхности, header, каталог, breadcrumb-леска, ссылки сущностей, таблицы, формы, moderation-карточки, provenance/quality/status badges, паспорта данных, pagination, empty/loading-состояния, радар, timeline, силуэты и глифы; добавлен базовый forced-colors layer. Осталась визуальная проверка фотографий, прозрачных PNG, screenshots, теней и градиентов в обеих темах. Источники и статусы сохраняют текстовые подписи и не различаются только цветом.
|
||||||
- [ ] **D06 · Метаданные браузера и CSP.** Добавить парные `theme-color` для light/dark media queries, проверить manifest/PWA chrome и отсутствие новых inline script/style/attributes. Сохранить `inlinedScripts: []`, `style-src 'self'`, `style-src-attr 'none'` и не возвращать `unsafe-inline` ради инициализации темы.
|
- [x] **D06 · Метаданные браузера и CSP.** Добавлены парные `theme-color` для системной light/dark схемы; явный cookie-выбор согласуется с SSR и мгновенно переключает активный meta-тег. PWA manifest использует устойчивый тёмный brand chrome и splash background. Production build сохраняет `inlinedScripts: []`; новые inline script/style/attributes не появились, ослабление CSP не потребовалось.
|
||||||
- [ ] **D07 · Визуальная и accessibility-приёмка.** Проверить light/dark/system на 320/390/768/1280 px для главной, каталогов, detail, records, report, status и всех admin-экранов; покрыть normal/hover/focus/disabled/error/loading/empty и длинные данные. Для обеих тем обеспечить WCAG AA, отсутствие горизонтального scroll и CLS, корректную печать, reduced motion и переключение без потери введённых данных; сохранить эталонные screenshots и краткий отчёт.
|
- [ ] **D07 · Визуальная и accessibility-приёмка.** Проверить light/dark/system на 320/390/768/1280 px для главной, каталогов, detail, records, report, status и всех admin-экранов; покрыть normal/hover/focus/disabled/error/loading/empty и длинные данные. Для обеих тем обеспечить WCAG AA, отсутствие горизонтального scroll и CLS, корректную печать, reduced motion и переключение без потери введённых данных; сохранить эталонные screenshots и краткий отчёт.
|
||||||
|
|
||||||
- [ ] **Q01 · Документы источников — реестр готов, нужны первичные подтверждения.** Создан единый production-gate с атрибуцией, общим лимитом 30 минут, хранением и процедурой отзыва для RF4DB, RF4-STAT, RF4MAP, RF4 Posts и официального RF4. До открытой публикации приложить устойчивые ссылки/копии первичных разрешений, контакты, даты и отдельно подтвердить право на изображения; пустое поле блокирует соответствующий источник.
|
- [ ] **Q01 · Документы источников — реестр готов, нужны первичные подтверждения.** Создан единый production-gate с атрибуцией, общим лимитом 30 минут, хранением и процедурой отзыва для RF4DB, RF4-STAT, RF4MAP, RF4 Posts и официального RF4. До открытой публикации приложить устойчивые ссылки/копии первичных разрешений, контакты, даты и отдельно подтвердить право на изображения; пустое поле блокирует соответствующий источник.
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
По умолчанию сайт следует `prefers-color-scheme` через CSS. Переключатель в header позволяет явно выбрать системную, светлую или тёмную тему; выбранное значение хранится в allowlist-cookie `rf4-theme` (`SameSite=Lax`, год, `Secure` на HTTPS). Astro читает cookie на сервере и сразу выставляет `data-theme="light|dark"`; для системного режима атрибут отсутствует. Client script собирается Astro во внешний asset, поэтому inline-код, localStorage и ослабление CSP не требуются.
|
По умолчанию сайт следует `prefers-color-scheme` через CSS. Переключатель в header позволяет явно выбрать системную, светлую или тёмную тему; выбранное значение хранится в allowlist-cookie `rf4-theme` (`SameSite=Lax`, год, `Secure` на HTTPS). Astro читает cookie на сервере и сразу выставляет `data-theme="light|dark"`; для системного режима атрибут отсутствует. Client script собирается Astro во внешний asset, поэтому inline-код, localStorage и ослабление CSP не требуются.
|
||||||
|
|
||||||
|
Цвет browser chrome задаётся двумя `theme-color` meta-тегами с media query в системном режиме. При явном выборе Astro активирует нужный meta-тег на SSR, а переключатель синхронизирует его без перезагрузки. Manifest использует тёмный фирменный цвет как стабильный fallback для splash screen и установленного PWA.
|
||||||
|
|
||||||
## Дальнейшая миграция
|
## Дальнейшая миграция
|
||||||
|
|
||||||
Файл `theme.css` временно содержит точечные dark overrides для основных публичных и административных поверхностей. На этапе D05 жёсткие цвета последовательно заменяются семантическими токенами в исходных component styles; source/status различия сохраняют текст или форму и никогда не полагаются только на оттенок.
|
Файл `theme.css` временно содержит точечные dark overrides для основных публичных и административных поверхностей. На этапе D05 жёсткие цвета последовательно заменяются семантическими токенами в исходных component styles; source/status различия сохраняют текст или форму и никогда не полагаются только на оттенок.
|
||||||
|
|||||||
Reference in New Issue
Block a user