From 6d064e53b9f41e0a25ae86435e1e2c64690c39ba Mon Sep 17 00:00:00 2001 From: root Date: Thu, 24 Sep 2026 21:42:41 +0200 Subject: [PATCH] =?UTF-8?q?feat(hero):=20fade-in=20hero=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8=20=D1=84=D0=BE=D0=BD=D0=B0-=D1=88=D1=8D?= =?UTF-8?q?=D0=B9=D0=B4=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .hero стартует скрытым, проявляется при html.mesh-bg-ready - markMeshReady ставится во всех ветках (успех/ошибка/reduced-motion/нет хоста) - reduced-motion: hero виден сразу без анимации --- public/css/main.css | 21 +++++++++++++++++++++ public/js/modules/ui/page-mesh-shader.js | 14 +++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/public/css/main.css b/public/css/main.css index 3f9002a..9849ee4 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -1349,6 +1349,27 @@ a.fab--secondary:visited { padding: calc(2 * clamp(2.5rem, 6vw, 4.5rem)) calc(2 * max(var(--space), env(safe-area-inset-right, 0px))) calc(2 * clamp(2rem, 5vw, 3.5rem)) calc(2 * max(var(--space), env(safe-area-inset-left, 0px))); } +/* Hero проявляется только после готовности фона-шэйдера (html.mesh-bg-ready), + чтобы текст не вскакивал раньше, чем подгрузится живой фон. */ +.hero { + opacity: 0; + transform: translateY(14px); + transition: opacity 0.8s ease 0.2s, transform 0.8s ease 0.2s; +} + +html.mesh-bg-ready .hero { + opacity: 1; + transform: none; +} + +@media (prefers-reduced-motion: reduce) { + .hero { + opacity: 1; + transform: none; + transition: none; + } +} + .hero__grid { max-width: var(--max); margin: 0 auto; diff --git a/public/js/modules/ui/page-mesh-shader.js b/public/js/modules/ui/page-mesh-shader.js index 2d4f8ea..1f1b88a 100644 --- a/public/js/modules/ui/page-mesh-shader.js +++ b/public/js/modules/ui/page-mesh-shader.js @@ -42,8 +42,8 @@ function colorVec4Array(colors) { export function initPageMeshShader() { const host = document.querySelector(".page-mesh-bg"); - if (!host) return; - if (prefersReducedMotion()) return; // статичный CSS-fallback остаётся виден + if (!host) return markMeshReady(); // нет хоста — hero всё равно проявляем + if (prefersReducedMotion()) return markMeshReady(); // статичный CSS-fallback, hero сразу виден const sizing = () => ({ u_fit: ShaderFitOptions[defaultObjectSizing.fit], @@ -86,10 +86,12 @@ export function initPageMeshShader() { try { mount.intersectionObserver?.disconnect(); } catch {} // Первый кадр уже отрисован в конструкторе — отмечаем готовность, - // чтобы CSS плавно проявил canvas (fade-in), без рывка. + // чтобы CSS плавно проявил canvas (fade-in) и hero. host.classList.add("is-ready"); + markMeshReady(); } catch (e) { console.warn("Mesh shader mount failed, fallback to static bg", e); + markMeshReady(); return; } @@ -110,3 +112,9 @@ export function initPageMeshShader() { try { mount.dispose(); } catch {} }, { once: true }); } + +// Сигнал готовности фона: проявляем hero (см. .hero в main.css). +// Ставится во всех ветках инициализации, чтобы hero никогда не висел скрытым. +function markMeshReady() { + document.documentElement.classList.add("mesh-bg-ready"); +}