Foundation for the adaline-based landing rebuild: - globals.css: warm-cream light theme + Material-3 dark theme mapped onto the existing CSS-var names; CSS-keyframe entrance + IntersectionObserver reveal motion (to replace framer-motion incrementally) - layout.tsx: self-host Instrument Sans/Serif, Newsreader and JetBrains Mono via next/font/google; drop the external Google Fonts <link> - _headers: tighten CSP style/font-src to 'self' now that fonts are local - components/ui: Container, Reveal, Eyebrow, Badge, Button, SectionHeading - lib/landing-content.ts: feature-section copy for the six product screens - public/images: six product screenshots optimized to WebP - add reicon-react/reicon-brands (bundled, CSP-safe SVG icons) Business logic (D1 download counter, platform metadata, theme, SEO) unchanged.
60 lines
1.4 KiB
TypeScript
60 lines
1.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
|
|
|
|
/**
|
|
* Scroll-reveal wrapper. Starts hidden (via the `.reveal` class) and marks
|
|
* itself in view the first time it intersects the viewport, letting CSS handle
|
|
* the rise-and-fade. Honours reduced-motion through the stylesheet.
|
|
*/
|
|
export function Reveal({
|
|
children,
|
|
delay = 0,
|
|
className = "",
|
|
}: {
|
|
children: ReactNode;
|
|
delay?: number;
|
|
className?: string;
|
|
}) {
|
|
const ref = useRef<HTMLDivElement | null>(null);
|
|
const [inView, setInView] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const node = ref.current;
|
|
if (!node) {
|
|
return;
|
|
}
|
|
if (typeof IntersectionObserver === "undefined") {
|
|
setInView(true);
|
|
return;
|
|
}
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
for (const entry of entries) {
|
|
if (entry.isIntersecting) {
|
|
setInView(true);
|
|
observer.disconnect();
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
{ threshold: 0.2, rootMargin: "0px 0px -10% 0px" },
|
|
);
|
|
|
|
observer.observe(node);
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={`reveal ${className}`}
|
|
data-in-view={inView}
|
|
style={delay ? ({ "--reveal-delay": `${delay}ms` } as CSSProperties) : undefined}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|