From 29a2bae7d9897b7a06a45e3f64e29fee552cda03 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 10:31:05 +0300 Subject: [PATCH] =?UTF-8?q?feat(phys8=20hub):=20Phase=205=20=E2=80=94=20hu?= =?UTF-8?q?b=20polish=20+=20cross-cutting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hub улучшения: - .ch-card: подъём на hover (-6px scale 1.01) с тематической box-shadow по цвету главы. - .ch-cover::after: shimmer-overlay при наведении (diagonal sweep). - .ch-cover-wm: micro-перемещение и scale на hover. - .ch-action svg: стрелка едет вправо на hover. - .po-xp: пульсирующая тень для overall progress XP-badge (3s loop). Accessibility: - aria-label на каждой ch-card с понятным названием темы. - :focus-visible с 3px outline в brand-цвете для chapter cards. - :focus-visible с белым outline для hdr-btn на градиенте. - prefers-reduced-motion: блокирует все анимации. Mobile responsiveness: - @media ≤580px: уменьшение шрифта h1 1.4rem, ch-cover-wm 3.8rem. Footer: '40 параграфов, 7 ЛР, 47 IV-6 интерактивов'. --- backend/scripts/redesign_p8_hub.cjs | 79 +++++++++++++++++++++++++++ frontend/textbooks/physics_8_hub.html | 46 ++++++++++++++-- 2 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 backend/scripts/redesign_p8_hub.cjs diff --git a/backend/scripts/redesign_p8_hub.cjs b/backend/scripts/redesign_p8_hub.cjs new file mode 100644 index 0000000..d94d50d --- /dev/null +++ b/backend/scripts/redesign_p8_hub.cjs @@ -0,0 +1,79 @@ +// Phase 5 — Hub polish: добавить тематические SVG watermarks для каждой +// chapter-карточки, hover-микро-анимации, live-индикатор прогресса. +'use strict'; +const fs = require('fs'); +const path = require('path'); + +const DST = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_8_hub.html'); +let h = fs.readFileSync(DST, 'utf8'); + +// === 1. Add hub-level animations CSS (inline into ', HUB_EXTRA_CSS + '\n'); + console.log('Hub polish CSS injected'); +} + +// === 2. Make chapter card titles accessibility-friendly + add aria-labels === +// Add aria-label на каждую .ch-card если её нет +const cardLabels = { + 'ch1-card': 'Глава 1: Тепловые явления', + 'ch2-card': 'Глава 2: Электромагнитные явления', + 'ch3-card': 'Глава 3: Световые явления', + 'ch4-card': 'Лабораторный практикум — 7 ЛР' +}; +for (const [cls, label] of Object.entries(cardLabels)) { + const regex = new RegExp(`(]*class="ch-card ${cls}"[^>]*)>`, 'g'); + h = h.replace(regex, (match, before) => { + if (/aria-label=/.test(before)) return match; + return `${before} aria-label="${label}">`; + }); +} + +// === 3. Footer "Created with..." note (or replace existing if any) === +// Optional — add a soft footer +const FOOTER_HTML = ` +
+ Физика 8 · LearnSpace · 40 параграфов, 7 ЛР, 47 IV-6 интерактивов +
+`; +if (!h.includes('47 IV-6 интерактивов')) { + h = h.replace('', FOOTER_HTML + '\n'); + console.log('Footer added'); +} + +fs.writeFileSync(DST, h); +console.log('hub size:', h.length); diff --git a/frontend/textbooks/physics_8_hub.html b/frontend/textbooks/physics_8_hub.html index 366a365..e92f2ac 100644 --- a/frontend/textbooks/physics_8_hub.html +++ b/frontend/textbooks/physics_8_hub.html @@ -199,6 +199,39 @@ html.dark .final-cta-sub{color:#c4b5fd} .final-cta-btn{padding:10px 18px;border-radius:10px;background:linear-gradient(135deg,var(--pri),#a78bfa);color:#fff;text-decoration:none;font-weight:800;font-size:.9rem;display:inline-flex;align-items:center;gap:7px;transition:filter .15s} .final-cta-btn:hover{filter:brightness(1.1)} .final-cta-btn svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round} + +/* === Phase 5 hub polish === */ +.ch-card{transition:transform .35s cubic-bezier(.16,1,.3,1),box-shadow .35s,border-color .25s} +.ch-card:hover{transform:translateY(-6px) scale(1.01);box-shadow:0 18px 44px rgba(124,58,237,.22)} +.ch-cover{position:relative;overflow:hidden} +.ch-cover::after{content:'';position:absolute;inset:0;background:linear-gradient(135deg,transparent 40%,rgba(255,255,255,.10) 50%,transparent 60%);background-size:300% 300%;background-position:200% 200%;transition:background-position .85s ease-out;pointer-events:none} +.ch-card:hover .ch-cover::after{background-position:-100% -100%} +.ch-card .ch-cover-wm{transition:transform .5s cubic-bezier(.16,1,.3,1),opacity .35s} +.ch-card:hover .ch-cover-wm{transform:translateX(-6px) translateY(8px) scale(1.08);opacity:.30} +.ch-action svg{transition:transform .25s} +.ch-action:hover svg{transform:translateX(4px)} +/* Topic-themed glow on action button */ +.ch-card.ch1-card:hover .ch-action{box-shadow:0 6px 16px rgba(220,38,38,.35)} +.ch-card.ch2-card:hover .ch-action{box-shadow:0 6px 16px rgba(217,119,6,.35)} +.ch-card.ch3-card:hover .ch-action{box-shadow:0 6px 16px rgba(8,145,178,.35)} +.ch-card.ch4-card:hover .ch-action{box-shadow:0 6px 16px rgba(16,185,129,.35)} +/* Overall progress XP-pulse */ +@keyframes po-xp-pulse{0%,100%{box-shadow:0 4px 12px rgba(124,58,237,.24)}50%{box-shadow:0 4px 16px rgba(124,58,237,.40),0 0 0 4px rgba(124,58,237,.08)}} +.po-xp{animation:po-xp-pulse 3s ease-in-out infinite} +/* Mobile responsiveness */ +@media(max-width:580px){ + .hdr{padding:24px 16px 20px} + .hdr h1{font-size:1.4rem} + .ch-cover-wm{font-size:3.8rem;right:-4px;top:-12px} +} +/* Focus-visible accessibility */ +.ch-card:focus-visible{outline:3px solid var(--p8-brand,#7c3aed);outline-offset:3px;border-radius:18px} +.hdr-btn:focus-visible,.hdr-back:focus-visible{outline:2px solid #fff;outline-offset:2px} +/* Reduced motion */ +@media (prefers-reduced-motion: reduce){ + .ch-card,.ch-card .ch-cover-wm,.ch-cover::after,.po-xp,.ch-action svg{animation:none!important;transition:none!important} +} + @@ -238,7 +271,7 @@ html.dark .final-cta-sub{color:#c4b5fd}
- +
Q
Глава 1
@@ -258,7 +291,7 @@ html.dark .final-cta-sub{color:#c4b5fd}
- +
I
Глава 2
@@ -278,7 +311,7 @@ html.dark .final-cta-sub{color:#c4b5fd}
- +
F
Глава 3
@@ -298,7 +331,7 @@ html.dark .final-cta-sub{color:#c4b5fd}
- +
ЛР
Лаборатория
@@ -858,5 +891,10 @@ if (document.readyState === 'loading') { window.addEventListener('focus', loadProgress); + + +