diff --git a/DESIGN.md b/DESIGN.md index 00cd4b6..ba411dd 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -317,7 +317,7 @@ const martian = Martian_Mono({ subsets: ['cyrillic', 'latin'], variable: '--font - **Mobile-first.** Брейкпоинты Tailwind по умолчанию: `md` 768 (две колонки), `lg` 1024 (12-колоночная сетка), `xl` 1280. - **Сетка:** 390 — 4 колонки, гаттер 16px, поля 20px. ≥1024 — 12 колонок, гаттер 24px, поля 48px, контейнер `max-w-page` 1280px по центру (на 1440 поля ≈ 80px). -- **Ритм секций:** padding-y 72px (390) → 128px (1440). Отступ над H2 больше, чем под ним: H2 → лид 16px, лид → контент 40px (390) / 64px (1440). +- **Ритм секций:** padding-y 48px (390) → 80px (1440), между соседними секциями 96 → 160px. Отступ над H2 больше, чем под ним: H2 → лид 16px, лид → контент 40px (390) / 64px (1440). - **Разделение секций:** не фоном-плашкой, а линейкой 2px `ink` по ширине контейнера сверху секции + сменой поверхности только у тёмного блока и формы. - **Шкала отступов:** 4, 8, 12, 16, 20, 24, 32, 48, 72, 128. diff --git a/apps/landing/src/app/globals.css b/apps/landing/src/app/globals.css index 064ce11..bc01ee4 100644 --- a/apps/landing/src/app/globals.css +++ b/apps/landing/src/app/globals.css @@ -140,11 +140,11 @@ max-width: var(--container-page); } .section-y { - padding-block: 72px; + padding-block: 48px; } @media (min-width: 1024px) { .section-y { - padding-block: 128px; + padding-block: 80px; } } @@ -483,3 +483,106 @@ } } } + +/* Моушн (DESIGN.md → «Моушн»). Без JS и при reduced-motion — сразу финальное состояние. */ +@keyframes stamp-in { + from { + opacity: 0; + transform: rotate(calc(var(--stamp-rotate, -6deg) - 3deg)) scale(1.18); + } + to { + opacity: 0.92; + transform: rotate(var(--stamp-rotate, -6deg)) scale(1); + } +} +@keyframes busy-dot { + 0%, + 100% { + opacity: 0.3; + } + 33% { + opacity: 1; + } +} +@keyframes fade-in { + from { + opacity: 0; + } +} + +@media (prefers-reduced-motion: no-preference) { + [data-motion] .stamp[data-stamp='load'] { + animation: stamp-in 380ms var(--ease-out-expo) 250ms both; + } + .stamp[data-stamp='event'] { + animation: stamp-in 380ms var(--ease-out-expo) 80ms both; + } + .busy-dots i { + animation: busy-dot 480ms steps(1) infinite; + } + .busy-dots i:nth-child(2) { + animation-delay: 160ms; + } + .busy-dots i:nth-child(3) { + animation-delay: 320ms; + } + [data-lead-notice] > * { + animation: fade-in 160ms ease-out; + } + + /* лента недели */ + [data-motion] .week .week-rule { + transition: transform 600ms var(--ease-out-quart); + } + [data-motion] .week .week-mark { + transition: transform 320ms var(--ease-out-quart) 300ms; + } + [data-motion] .week .week-note { + transition: + opacity 320ms var(--ease-out-quart) 300ms, + transform 320ms var(--ease-out-quart) 300ms; + } + [data-motion] .week .week-day:nth-child(5) .week-mark, + [data-motion] .week .week-day:nth-child(5) .week-note { + transition-delay: 480ms; + } + [data-motion] .week .week-bracket-line { + transition: transform 500ms var(--ease-out-quart) 680ms; + } + [data-motion] .week .week-bracket-note { + transition: opacity 240ms ease-out 1000ms; + } + [data-motion] .week:not([data-inview]) .week-rule, + [data-motion] .week:not([data-inview]) .week-mark { + transform: scaleX(0); + } + [data-motion] .week:not([data-inview]) .week-note { + opacity: 0; + transform: translateY(6px); + } + [data-motion] .week:not([data-inview]) .week-bracket-line { + transform: scaleY(0); + } + [data-motion] .week:not([data-inview]) .week-bracket-note { + opacity: 0; + } + @media (min-width: 1024px) { + [data-motion] .week:not([data-inview]) .week-bracket-line { + transform: scaleX(0); + } + } + + /* липкая CTA */ + .sticky-cta { + transition: transform 180ms var(--ease-in-quart); + } + .sticky-cta[data-visible] { + transition: transform 240ms var(--ease-out-quart); + } +} + +.sticky-cta:not([data-visible]) { + transform: translateY(100%); + visibility: hidden; + transition-property: transform, visibility; +} diff --git a/apps/landing/src/app/layout.tsx b/apps/landing/src/app/layout.tsx index bd95dcb..368dd5e 100644 --- a/apps/landing/src/app/layout.tsx +++ b/apps/landing/src/app/layout.tsx @@ -1,6 +1,8 @@ import type { Metadata, Viewport } from 'next'; import { Golos_Text, Martian_Mono } from 'next/font/google'; +import { MOTION_BOOT, RevealObserver } from '@/components/RevealObserver'; import { StampInkFilter } from '@/components/Stamp'; +import { StickyCta } from '@/components/StickyCta'; import { TouchRecorder } from '@/components/TouchRecorder'; import { site } from '@/content/site'; import './globals.css'; @@ -19,11 +21,17 @@ export const viewport: Viewport = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + + + {/* до первой отрисовки: иначе блоки мигнут в начальном состоянии анимации */} +