Add tasteful motion starter set (reviewable, gated, LCP-safe)
Progressive-enhancement motion, all gated on a single html.has-motion line so it's trivially on/off: - Section reveal-on-scroll (fade + 10px rise), below the fold only — hero stays instant so LCP is untouched. Animates opacity/transform only (CLS 0). - Staggered reveal on the three "how we work" blocks. - Grow-on-hover underline for prose links (clearer affordance). - An open-o divider that settles in just before the contact CTA. - Persistent "Book a conversation" that returns after the hero scrolls off and steps aside at the contact section. ~1KB inline vanilla JS (IntersectionObserver), try/catch + reduced-motion guard so content is never left hidden. No animation library. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -279,3 +279,47 @@ a:focus-visible, .btn:focus-visible { outline: 2px solid var(--amber); outline-o
|
||||
.lf-err { font-family: 'DM Sans', sans-serif; font-size: 0.85rem; color: #b4452f; margin: 1rem 0 0; }
|
||||
.lf-err a { color: #b4452f; }
|
||||
.lf-alt { font-family: 'DM Sans', sans-serif; font-size: 0.82rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-soft); text-align: center; margin: 0.5rem 0 0.8rem; }
|
||||
|
||||
/* ── Motion — progressive enhancement ──────────────────────────────────────
|
||||
Gated on html.has-motion (one inline <head> line is the on/off switch).
|
||||
Hero is never animated (LCP stays instant). Only opacity/transform animate
|
||||
(CLS 0). All off under prefers-reduced-motion. JS adds .in; a timeout
|
||||
fallback guarantees content is never left hidden. */
|
||||
.has-motion .reveal { opacity: 0; transform: translateY(12px); transition: opacity .6s ease, transform .6s ease; }
|
||||
.has-motion .reveal.in { opacity: 1; transform: none; }
|
||||
.has-motion .stagger > * { opacity: 0; transform: translateY(12px); transition: opacity .55s ease, transform .55s ease; }
|
||||
.has-motion .stagger.in > * { opacity: 1; transform: none; }
|
||||
.has-motion .stagger.in > *:nth-child(2) { transition-delay: .1s; }
|
||||
.has-motion .stagger.in > *:nth-child(3) { transition-delay: .2s; }
|
||||
.has-motion .stagger.in > *:nth-child(4) { transition-delay: .3s; }
|
||||
|
||||
/* Underline that grows on hover — replaces the static border on prose links */
|
||||
.bridge-link a, .person a {
|
||||
border-bottom: 0;
|
||||
background-image: linear-gradient(var(--amber-deep), var(--amber-deep));
|
||||
background-size: 0 1.5px; background-position: 0 100%; background-repeat: no-repeat;
|
||||
transition: background-size .3s ease, color .2s ease;
|
||||
}
|
||||
.bridge-link a:hover, .person a:hover { background-size: 100% 1.5px; }
|
||||
|
||||
/* Open-o divider — a quiet brand beat that settles in right before the ask */
|
||||
.o-divider { text-align: center; margin: 8px 0 -10px; }
|
||||
.o-divider svg { width: 30px; height: 30px; }
|
||||
.o-divider path { fill: var(--amber); }
|
||||
.has-motion .o-divider { opacity: 0; transform: scale(.7); transition: opacity .6s ease, transform .6s cubic-bezier(.2,.8,.2,1); }
|
||||
.has-motion .o-divider.in { opacity: 1; transform: none; }
|
||||
|
||||
/* Persistent CTA — returns after the hero scrolls off, steps aside at the contact */
|
||||
.float-cta { position: fixed; right: 22px; bottom: 22px; z-index: 80; opacity: 0; transform: translateY(12px); pointer-events: none; transition: opacity .35s ease, transform .35s ease; }
|
||||
.float-cta.show { opacity: 1; transform: none; pointer-events: auto; }
|
||||
.float-cta .btn { box-shadow: 0 6px 22px rgba(15, 23, 41, 0.18); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.has-motion .reveal, .has-motion .stagger > * { opacity: 1; transform: none; }
|
||||
.has-motion .o-divider { opacity: 1; transform: none; }
|
||||
.float-cta { transition: none; }
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.float-cta { right: 12px; left: 12px; bottom: 12px; }
|
||||
.float-cta .btn { display: block; text-align: center; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user