Compare commits

...
5 Commits
Author SHA1 Message Date
ik aed541c70c feat: synchronize theme browser chrome
CI / backend-and-migrations (push) Canceled after 0s
CI / astro-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
CI / compose-e2e (push) Canceled after 0s
2026-09-13 16:17:22 +07:00
ik 54a1e6c042 data: approve hijacker lure asset 2026-09-13 16:12:18 +07:00
ik d8b0c08aaa feat: theme fishing data graphics 2026-09-13 16:09:56 +07:00
ik 99c1498810 data: validate official guide media 2026-09-13 16:07:37 +07:00
ik 7567ac9191 feat: theme forms tables and data states 2026-09-13 15:54:45 +07:00
13 changed files with 105 additions and 20 deletions
+1 -1
View File
@@ -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).
Тема по умолчанию следует системному `prefers-color-scheme`, а переключатель в header позволяет выбрать системную, светлую или тёмную палитру. Выбор сохраняется в cookie и применяется Astro при SSR без localStorage-only flash и ослабления CSP. Базовые поверхности, каталог, навигационная леска, паспорта данных и состояния загрузки уже используют семантические light/dark-токены; ограничения дальнейшей миграции описаны в [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-артефакты.
+2 -2
View File
@@ -6,8 +6,8 @@
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#f2f5ee",
"theme_color": "#082226",
"background_color": "#071719",
"theme_color": "#071719",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
+1 -1
View File
@@ -14,5 +14,5 @@ const { catches } = Astro.props as { catches: Catch[] };
})}
</div>
<style>
time{margin-top:7px;color:#496357;font-size:10px;font-weight:750}
time{margin-top:7px;color:var(--text-secondary);font-size:10px;font-weight:750}
</style>
+1 -1
View File
@@ -31,6 +31,6 @@ const groups = [...signals.reduce((map, signal) => {
})}</div>
</section>
<style>
.repeat-note{position:relative;z-index:1;margin:-8px 0 10px!important;padding:6px 9px;border-left:2px solid #6b8d74;color:#496357!important;font-size:10px!important}
.repeat-note{position:relative;z-index:1;margin:-8px 0 10px!important;padding:6px 9px;border-left:2px solid var(--border-strong);color:var(--text-secondary)!important;font-size:10px!important}
.signal-card__top .source-strip{margin:0}
</style>
+7 -1
View File
@@ -52,7 +52,8 @@ const jsonLd = JSON.stringify({
<link rel="icon" href="/favicon-32.png" sizes="32x32" type="image/png" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<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:type" content="website" />
<meta property="og:site_name" content="RF4 Spotter" />
@@ -94,6 +95,7 @@ const jsonLd = JSON.stringify({
<script>
const root = document.documentElement;
const themeButtons = document.querySelectorAll<HTMLButtonElement>("[data-theme-option]");
const themeColors = document.querySelectorAll<HTMLMetaElement>("[data-theme-color]");
const allowedThemes = new Set(["system", "light", "dark"]);
const applyTheme = (value: string) => {
@@ -103,6 +105,10 @@ const jsonLd = JSON.stringify({
themeButtons.forEach((button) => {
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" : "";
document.cookie = `rf4-theme=${theme}; Max-Age=31536000; Path=/; SameSite=Lax${secure}`;
};
+2 -2
View File
@@ -1,3 +1,3 @@
.activity-timeline{width:min(1180px,calc(100% - 64px));margin:20px auto 0;padding:25px 28px;border-radius:14px;background:#fff;border:1px solid #d9e1da}.activity-timeline header{display:flex;align-items:end;justify-content:space-between;gap:20px}.activity-timeline h2{font:400 29px Georgia,serif;margin:5px 0 0}.activity-timeline header p{margin:0;color:#71817e;font-size:10px}
.timeline-chart{height:145px;display:grid;grid-template-columns:repeat(6,1fr);gap:8px;margin-top:18px;padding-top:8px;border-bottom:1px solid #a9bbb1;background:repeating-linear-gradient(to top,transparent 0,transparent 35px,#dce5de 36px)}.timeline-slot{position:relative;display:grid;grid-template-rows:1fr auto;place-items:center}.timeline-line{position:relative;align-self:end;width:2px;height:var(--meter-level);min-height:10px;background:#3f7779}.timeline-line i{position:absolute;left:0;top:0;width:12px;height:18px;margin:-4px 0 0 -5px;border-radius:7px 7px 9px 9px;background:linear-gradient(#ff785a 0 35%,var(--lime) 35%);box-shadow:0 0 0 4px #c9f45b18}.timeline-slot strong{position:absolute;top:2px;font:400 14px Georgia,serif;color:#365c5e}.timeline-slot small{position:absolute;bottom:-20px;color:#71817e;font-size:8px;white-space:nowrap}.activity-timeline+*{margin-top:42px}
.activity-timeline{width:min(1180px,calc(100% - 64px));margin:20px auto 0;padding:25px 28px;border-radius:14px;background:var(--surface);border:1px solid var(--border-soft)}.activity-timeline header{display:flex;align-items:end;justify-content:space-between;gap:20px}.activity-timeline h2{font:400 29px Georgia,serif;margin:5px 0 0}.activity-timeline header p{margin:0;color:var(--text-muted);font-size:10px}
.timeline-chart{height:145px;display:grid;grid-template-columns:repeat(6,1fr);gap:8px;margin-top:18px;padding-top:8px;border-bottom:1px solid var(--border);background:repeating-linear-gradient(to top,transparent 0,transparent 35px,var(--graphic-grid) 36px)}.timeline-slot{position:relative;display:grid;grid-template-rows:1fr auto;place-items:center}.timeline-line{position:relative;align-self:end;width:2px;height:var(--meter-level);min-height:10px;background:var(--graphic-line)}.timeline-line i{position:absolute;left:0;top:0;width:12px;height:18px;margin:-4px 0 0 -5px;border-radius:7px 7px 9px 9px;background:linear-gradient(#ff785a 0 35%,var(--lime) 35%);box-shadow:0 0 0 4px color-mix(in srgb,var(--lime) 12%,transparent)}.timeline-slot strong{position:absolute;top:2px;font:400 14px Georgia,serif;color:var(--text)}.timeline-slot small{position:absolute;bottom:-20px;color:var(--text-muted);font-size:8px;white-space:nowrap}.activity-timeline+*{margin-top:42px}
@media(max-width:720px){.activity-timeline{width:calc(100% - 28px);padding:20px 15px}.activity-timeline header{display:block}.activity-timeline header p{margin-top:6px}.timeline-slot small{font-size:7px}}
+1 -1
View File
@@ -1 +1 @@
.coordinate-radar{position:relative;flex:0 0 180px;width:180px;height:180px;display:grid;place-items:center;color:var(--lime)}.coordinate-radar svg{position:absolute;inset:0;width:100%;height:100%;fill:none;stroke:#ffffff20;stroke-width:1}.coordinate-radar .radar-sweep{fill:#c9f45b0d;stroke:#c9f45b38}.coordinate-radar .radar-mark{fill:var(--lime);stroke:none}.coordinate-radar .radar-pulse{stroke:var(--lime);stroke-width:2;opacity:.55}.coordinate-radar strong{position:relative;display:grid;grid-template-columns:auto auto 1px auto auto;align-items:baseline;gap:4px;padding:7px 10px;border-radius:7px;background:#082226d9;font:400 20px Georgia,serif}.coordinate-radar strong small{font:700 8px Inter,sans-serif;color:#9eb0ad}.coordinate-radar strong i{width:1px;height:14px;background:#ffffff35}.radar-north{position:absolute;top:3px;font-size:8px;letter-spacing:.15em}.radar-caption{position:absolute;bottom:1px;color:#91a5a1;font-size:8px;text-transform:uppercase;letter-spacing:.12em}@media(max-width:720px){.coordinate-radar{display:none}}
.coordinate-radar{position:relative;flex:0 0 180px;width:180px;height:180px;display:grid;place-items:center;color:var(--lime)}.coordinate-radar svg{position:absolute;inset:0;width:100%;height:100%;fill:none;stroke:color-mix(in srgb,var(--text-on-dark) 13%,transparent);stroke-width:1}.coordinate-radar .radar-sweep{fill:color-mix(in srgb,var(--lime) 5%,transparent);stroke:color-mix(in srgb,var(--lime) 22%,transparent)}.coordinate-radar .radar-mark{fill:var(--lime);stroke:none}.coordinate-radar .radar-pulse{stroke:var(--lime);stroke-width:2;opacity:.55}.coordinate-radar strong{position:relative;display:grid;grid-template-columns:auto auto 1px auto auto;align-items:baseline;gap:4px;padding:7px 10px;border-radius:7px;background:color-mix(in srgb,var(--deep) 85%,transparent);font:400 20px Georgia,serif}.coordinate-radar strong small{font:700 8px Inter,sans-serif;color:color-mix(in srgb,var(--text-on-dark) 65%,transparent)}.coordinate-radar strong i{width:1px;height:14px;background:color-mix(in srgb,var(--text-on-dark) 22%,transparent)}.radar-north{position:absolute;top:3px;font-size:8px;letter-spacing:.15em}.radar-caption{position:absolute;bottom:1px;color:color-mix(in srgb,var(--text-on-dark) 60%,transparent);font-size:8px;text-transform:uppercase;letter-spacing:.12em}@media(max-width:720px){.coordinate-radar{display:none}}
+1 -1
View File
@@ -1 +1 @@
.fish-silhouette{fill:currentColor;color:#315f63;pointer-events:none}.fish-silhouette .fish-eye{fill:var(--paper)}.fish-silhouette .fish-detail{fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}.spot-main{position:relative}.spot-main>.fish-silhouette{position:absolute;right:2px;top:38px;opacity:.075}.catalog-grid>a{position:relative;overflow:hidden}.catalog-grid>a>.fish-silhouette{position:absolute;right:20px;bottom:12px;opacity:.12;transform:rotate(-4deg);transition:opacity var(--motion-base) var(--ease-out),transform var(--motion-base) var(--ease-out)}.catalog-grid>a:hover>.fish-silhouette{opacity:.2;transform:rotate(-2deg) translateX(-2px)}@media(max-width:720px){.spot-main>.fish-silhouette{width:68px;right:0;opacity:.055}}
.fish-silhouette{fill:currentColor;color:var(--decorative-water);pointer-events:none}.fish-silhouette .fish-eye{fill:var(--paper)}.fish-silhouette .fish-detail{fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}.spot-main{position:relative}.spot-main>.fish-silhouette{position:absolute;right:2px;top:38px;opacity:.075}.catalog-grid>a{position:relative;overflow:hidden}.catalog-grid>a>.fish-silhouette{position:absolute;right:20px;bottom:12px;opacity:.12;transform:rotate(-4deg);transition:opacity var(--motion-base) var(--ease-out),transform var(--motion-base) var(--ease-out)}.catalog-grid>a:hover>.fish-silhouette{opacity:.2;transform:rotate(-2deg) translateX(-2px)}@media(max-width:720px){.spot-main>.fish-silhouette{width:68px;right:0;opacity:.055}}
+1 -1
View File
@@ -1 +1 @@
.tackle-glyph{--tackle-accent:#dd8b42;flex:none;color:var(--tackle-accent);stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.tackle-glyph[data-tone="1"]{--tackle-accent:#759d35}.tackle-glyph[data-tone="2"]{--tackle-accent:#3f8588}.tackle-glyph[data-tone="3"]{--tackle-accent:#9a6a9d}.tackle-glyph[data-tone="4"]{--tackle-accent:#bd6250}.tackle-glyph__fill{fill:color-mix(in srgb,currentColor 78%,var(--surface));stroke:currentColor}.tackle-glyph__stroke{stroke-width:3}.tackle-label{display:inline-flex;align-items:center;gap:8px;min-width:0}.tackle-label>span{min-width:0}.best-lure .tackle-glyph{color:var(--lime)}.bait-line>.tackle-glyph{width:25px;height:25px}.catch-list .tackle-label{margin-top:3px;color:var(--text-muted);font-size:12px}.detail-grid aside li .tackle-label{width:100%}
.tackle-glyph{--tackle-accent:var(--tackle-orange);flex:none;color:var(--tackle-accent);stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.tackle-glyph[data-tone="1"]{--tackle-accent:var(--tackle-green)}.tackle-glyph[data-tone="2"]{--tackle-accent:var(--tackle-teal)}.tackle-glyph[data-tone="3"]{--tackle-accent:var(--tackle-violet)}.tackle-glyph[data-tone="4"]{--tackle-accent:var(--tackle-red)}.tackle-glyph__fill{fill:color-mix(in srgb,currentColor 78%,var(--surface));stroke:currentColor}.tackle-glyph__stroke{stroke-width:3}.tackle-label{display:inline-flex;align-items:center;gap:8px;min-width:0}.tackle-label>span{min-width:0}.best-lure .tackle-glyph{color:var(--lime)}.bait-line>.tackle-glyph{width:25px;height:25px}.catch-list .tackle-label{margin-top:3px;color:var(--text-muted);font-size:12px}.detail-grid aside li .tackle-label{width:100%}
+63 -1
View File
@@ -35,6 +35,22 @@
--decorative-water: light-dark(#315f63, #72a9ad);
--skeleton: light-dark(#e7ede7, #1c3537);
--skeleton-strong: light-dark(#d8e2d8, #294548);
--status-healthy: light-dark(#5f8c26, #a8d85e);
--status-warning: light-dark(#9a741d, #e8c768);
--status-changed: light-dark(#a44236, #ff9587);
--source-official: light-dark(#815713, #e3b65b);
--source-rf4db: light-dark(#286581, #74bee3);
--source-rf4stat: light-dark(#60499a, #b7a2ed);
--source-rf4map: light-dark(#2f704f, #79c79e);
--source-rf4posts: light-dark(#984a33, #ee9a7e);
--source-players: light-dark(#526b1c, #add066);
--graphic-line: light-dark(#3f7779, #79b5b8);
--graphic-grid: light-dark(#dce5de, #294244);
--tackle-orange: light-dark(#b96b27, #f0a55f);
--tackle-green: light-dark(#648b2b, #a8cf62);
--tackle-teal: light-dark(#34777a, #78bec1);
--tackle-violet: light-dark(#845787, #c795ca);
--tackle-red: light-dark(#a95041, #ed8e7c);
}
:root[data-theme="light"] { color-scheme: light; }
@@ -84,7 +100,13 @@
.signal-card dt { color: light-dark(#776f56, #b9ae82); }
.signal-card .missing-note { background: light-dark(#efe5bd, #453c20); color: light-dark(#6b550f, #f0d889); }
.signal-card .data-passport { background: light-dark(#fffdf7, #172628); border-color: light-dark(#ddd3af, #59605a); }
.source-chip { background: color-mix(in srgb, var(--chip) 24%, var(--surface-elevated)); }
.source-chip { background: color-mix(in srgb, var(--chip) 24%, var(--surface-elevated)); box-shadow: 0 5px 14px var(--shadow-color); }
.source-chip[data-source="rf4-official"] { --chip: var(--source-official); }
.source-chip[data-source="rf4db"] { --chip: var(--source-rf4db); }
.source-chip[data-source^="rf4stat"] { --chip: var(--source-rf4stat); }
.source-chip[data-source="rf4map"] { --chip: var(--source-rf4map); }
.source-chip[data-source="rf4posts-spot"] { --chip: var(--source-rf4posts); }
.source-chip[data-source="players"] { --chip: var(--source-players); }
.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); }
@@ -120,6 +142,46 @@
.state::before { border-color: var(--border-strong); }
.state::after { border-color: var(--decorative-water); box-shadow: 0 -7px 0 -6px var(--decorative-water),0 7px 0 -6px var(--decorative-water); }
.data-passport header strong[data-passport-status="unverified"] { background: var(--info-soft); color: var(--info); }
.record-table, .report-form, .moderation-card { background: var(--surface-elevated); border-color: var(--border-subtle); box-shadow: 0 30px 80px var(--shadow-color); }
.record-row, .optional-fields, .moderation-summary dl div, .moderation-actions { border-color: var(--border-subtle); }
.record-head, .no-proof { background: var(--surface-raised); }
.record-head span, .record-row > span, .record-row > time, .report-form label,
.field-help, .privacy, .moderation-summary > p, .moderation-summary dt,
.moderation-actions label { color: var(--text-secondary); }
.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); }
.report-form input:focus, .report-form textarea:focus, .report-form select:focus { border-color: var(--focus); box-shadow: 0 0 0 3px color-mix(in srgb,var(--focus) 28%,transparent); }
.report-form input:user-invalid, .report-form select:user-invalid { border-color: var(--danger); }
.moderation-summary blockquote { background: var(--surface-raised); color: var(--text-secondary); }
.data-legend { border-color: var(--border-strong); }
.data-legend__intro p, .legend-sources > div > span, .legend-quality span { color: var(--text-secondary); }
.legend-sources > div { border-color: var(--border-subtle); }
.legend-quality > div { background: var(--surface-elevated); }
.legend-quality i { background: var(--success-soft); color: var(--success); }
.legend-quality i[data-quality="unverified"] { background: var(--info-soft); color: var(--info); }
.legend-quality i[data-quality="incomplete"] { background: var(--warning-soft); color: var(--warning); }
.health-state i { background: var(--status-warning); }
.source-health-grid [data-status="healthy"] .health-state i { background: var(--status-healthy); }
.source-health-grid [data-status="source_changed"] .health-state i { background: var(--status-changed); }
.activity-timeline header p { color: var(--text-secondary); }
.timeline-line { background: var(--graphic-line); }
.timeline-line i { box-shadow: 0 0 0 4px color-mix(in srgb,var(--lime) 12%,transparent); }
.coordinate-radar svg { stroke: color-mix(in srgb,var(--text-on-dark) 13%,transparent); }
.coordinate-radar .radar-sweep { fill: color-mix(in srgb,var(--lime) 5%,transparent); stroke: color-mix(in srgb,var(--lime) 22%,transparent); }
.admin-dashboard-grid > section, .admin-kpis > a, .admin-kpis > article,
.source-health-grid article { background: var(--surface-elevated); border-color: var(--border-subtle); }
.admin-history li small, .source-health-grid p, .health-state { color: var(--text-secondary); }
@media print {
:root { color-scheme: light; }
}
@media (forced-colors: active) {
.theme-switcher, .theme-switcher button, .spot-card, .record-table,
.report-form, .moderation-card, .catalog-grid > a, .data-passport,
.source-chip, .notice, .state { border: 1px solid CanvasText; }
.theme-switcher button[aria-pressed="true"] { background: Highlight; color: HighlightText; }
.source-chip i, .health-state i, .quality-chip i, .activity-pill i { forced-color-adjust: none; }
.fish-silhouette, .waterbody-mark, .tackle-glyph, .coordinate-radar { color: CanvasText; }
}
+21 -6
View File
@@ -287,9 +287,19 @@
"source_pages": [
"https://rf4map.ru/fishes"
],
"status": "queued",
"status": "approved",
"first_seen_at": "2026-09-12T14:59:53.013377+00:00",
"last_seen_at": "2026-09-12T14:59:53.013377+00:00"
"last_seen_at": "2026-09-12T14:59:53.013377+00:00",
"sha256": "143929ed5f4dc4ffbd585f009073d0cc107e1f0e2c77b0e3d81d92b2d3aff842",
"local_path": "files/14/143929ed5f4dc4ffbd585f009073d0cc107e1f0e2c77b0e3d81d92b2d3aff842.png",
"content_type": "image/png",
"bytes": 2266,
"width": 48,
"height": 48,
"fetched_at": "2026-09-13T09:11:46.504621+00:00",
"entity_key": "rf4map-bait-101",
"reviewed_at": "2026-09-13T09:12:06.537034+00:00",
"review_note": "Visual review: Hijacker slim 7SP-003 minnow lure with orange flank and hooks; transparent PNG; RF4MAP catalog provenance"
},
{
"source_page": "https://rf4map.ru/fishes",
@@ -5029,7 +5039,7 @@
"entity_type": "reference",
"label": "Zig-Rig Guide Unterschiedliche Wetterbedingungen führen zu Veränderungen der Wassertemperatur. Bei niedrigen Temperaturen können Karpfen in die sonnenerwärmten Oberflächenschichten des Gewässers aufsteigen. In solchen Fällen können Zig-Rig Montagen die besten Ergebnisse erzielen. Beim Zig-Rig wird a",
"external_id": null,
"status": "stored",
"status": "approved",
"first_seen_at": "2026-09-12T08:50:41.276898+00:00",
"last_seen_at": "2026-09-12T08:50:41.276898+00:00",
"sha256": "48b4e4f2cdca2d65bc17feee61a36a9eaf979ea6a83756f3805750d99dd6388e",
@@ -5041,7 +5051,10 @@
"fetched_at": "2026-09-12T14:24:59.865233+00:00",
"source_pages": [
"https://rf4game.de/userguide/"
]
],
"entity_key": "rf4game-de-zig-rig-depth-diagram",
"reviewed_at": "2026-09-13T09:07:24.362599+00:00",
"review_note": "Visual review: Zig-Rig depth placement diagram in a lake cross-section; official RF4 German user guide provenance"
},
{
"source_page": "https://rf4game.de/userguide/",
@@ -5256,12 +5269,14 @@
"entity_type": "reference",
"label": "RATSCHLÄGE Wurfweite und Wurfgenauigkeit Wurfweite hängt ab von: Geschicklichkeit des Spielers Fähigkeiten dieser Angelrutenart Fähigkeiten, diese Rollenart zu verwenden Länge der Angelrute Grad der Trunkenheit Beeinflussung durch den Wind Als allererstes geben Sie acht auf Ihre Fähigkeiten für dies",
"external_id": null,
"status": "queued",
"status": "invalid",
"first_seen_at": "2026-09-12T08:50:41.276898+00:00",
"last_seen_at": "2026-09-12T08:50:41.276898+00:00",
"source_pages": [
"https://rf4game.de/userguide/"
]
],
"last_attempt_at": "2026-09-13T09:06:54.770934+00:00",
"last_error": "expected image, got text/html"
},
{
"source_page": "https://rf4game.de/userguide/",
+2 -2
View File
@@ -44,8 +44,8 @@
- [x] **D02 · Системный режим без вспышки.** Первый визит следует `prefers-color-scheme` полностью через CSS: серверный HTML сразу совместим с системной темой, состояние не хранится, inline bootstrap не добавлен и CSP не ослаблена. Явное переопределение появится только вместе с D03–D04.
- [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.
- [ ] **D05 · Темизация компонентов и графики.** На семантические light/dark-токены уже переведены базовые поверхности, header, каталог, breadcrumb-леска, ссылки сущностей, паспорта данных, pagination, empty/loading-состояния и часть декоративной графики. Следующий пакет: таблицы и формы, provenance/quality badges, admin-панель, радар, timeline, силуэты и глифы; затем проверить фотографии, прозрачные PNG, screenshots, тени, градиенты и forced-colors. Источники и статусы не должны различаться только цветом.
- [ ] **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` ради инициализации темы.
- [ ] **D05 · Темизация компонентов и графики.** На семантические light/dark-токены переведены базовые и admin-поверхности, header, каталог, breadcrumb-леска, ссылки сущностей, таблицы, формы, moderation-карточки, provenance/quality/status badges, паспорта данных, pagination, empty/loading-состояния, радар, timeline, силуэты и глифы; добавлен базовый forced-colors layer. Осталась визуальная проверка фотографий, прозрачных PNG, screenshots, теней и градиентов в обеих темах. Источники и статусы сохраняют текстовые подписи и не различаются только цветом.
- [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 и краткий отчёт.
- [ ] **Q01 · Документы источников — реестр готов, нужны первичные подтверждения.** Создан единый production-gate с атрибуцией, общим лимитом 30 минут, хранением и процедурой отзыва для RF4DB, RF4-STAT, RF4MAP, RF4 Posts и официального RF4. До открытой публикации приложить устойчивые ссылки/копии первичных разрешений, контакты, даты и отдельно подтвердить право на изображения; пустое поле блокирует соответствующий источник.
+2
View File
@@ -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 не требуются.
Цвет browser chrome задаётся двумя `theme-color` meta-тегами с media query в системном режиме. При явном выборе Astro активирует нужный meta-тег на SSR, а переключатель синхронизирует его без перезагрузки. Manifest использует тёмный фирменный цвет как стабильный fallback для splash screen и установленного PWA.
## Дальнейшая миграция
Файл `theme.css` временно содержит точечные dark overrides для основных публичных и административных поверхностей. На этапе D05 жёсткие цвета последовательно заменяются семантическими токенами в исходных component styles; source/status различия сохраняют текст или форму и никогда не полагаются только на оттенок.