diff --git a/docs/components/landing/hooks.js b/docs/components/landing/hooks.js index febd96f..7570ec3 100644 --- a/docs/components/landing/hooks.js +++ b/docs/components/landing/hooks.js @@ -99,14 +99,21 @@ export function useGithubStars(repo) { return stars; } -/** Fades sections in as they scroll into view. Without JS, everything stays visible. */ +/** + * Fades sections in as they scroll into view. Content stays visible until this runs, so a page whose scripts are + * slow to load is never blank: what's already on screen (and the hero, which animates in with CSS) stays shown, and + * only what's further down is hidden until it scrolls in. + */ export function useReveal() { useEffect(() => { - const els = document.querySelectorAll(".dk-reveal"); - if (typeof IntersectionObserver === "undefined") { - els.forEach((el) => el.classList.add("in")); - return; - } + if (typeof IntersectionObserver === "undefined") return; + const root = document.documentElement; + const later = []; + document.querySelectorAll(".dk-reveal").forEach((el) => { + if (el.closest(".dk-hero") || el.getBoundingClientRect().top < window.innerHeight) el.classList.add("in"); + else later.push(el); + }); + root.setAttribute("data-dk-js", ""); const io = new IntersectionObserver( (entries) => { entries.forEach((e) => { @@ -118,8 +125,11 @@ export function useReveal() { }, { rootMargin: "0px 0px -8% 0px", threshold: 0.08 } ); - els.forEach((el) => io.observe(el)); - return () => io.disconnect(); + later.forEach((el) => io.observe(el)); + return () => { + io.disconnect(); + root.removeAttribute("data-dk-js"); + }; }, []); } diff --git a/docs/components/landing/landing.css b/docs/components/landing/landing.css index 4d08472..edfee6c 100644 --- a/docs/components/landing/landing.css +++ b/docs/components/landing/landing.css @@ -2420,6 +2420,19 @@ html[data-dk-js] .dk-reveal.in { transform: none; } +/* The hero animates in with CSS alone, so it plays on first paint instead of waiting for the scripts */ +@keyframes dk-rise { + from { + opacity: 0; + transform: translateY(24px); + } +} + +.dk-hero .dk-reveal { + animation: dk-rise 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) both; + animation-delay: var(--delay, 0s); +} + /* ---------- Responsive ---------- */ @media (max-width: 1024px) { .dk-hero-grid { diff --git a/docs/pages/_document.tsx b/docs/pages/_document.tsx index 9fc5aad..d5c1f33 100644 --- a/docs/pages/_document.tsx +++ b/docs/pages/_document.tsx @@ -1,8 +1,8 @@ import { Head, Html, Main, NextScript } from 'next/document' // Landing page theme, applied before first paint: the saved theme (next-themes' "theme" key, shared with the -// docs) or the OS preference. data-dk-js lets the landing page's scroll-reveal styles apply. -const landingThemeScript = `(function(){try{var d=document.documentElement;d.setAttribute('data-dk-js','');var t=localStorage.getItem('theme');if(t!=='light'&&t!=='dark'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'dark'}d.setAttribute('data-dk-theme',t)}catch(e){}})();` +// docs) or the OS preference. +const landingThemeScript = `(function(){try{var d=document.documentElement;var t=localStorage.getItem('theme');if(t!=='light'&&t!=='dark'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'dark'}d.setAttribute('data-dk-theme',t)}catch(e){}})();` export default function Document() { return (