feat(landing): honest high-end redesign — kill fake social proof, brand gradient identity, final CTA, mobile fixes

- removed fabricated fork counts/'verified' badges, 'our customers ship
  today' copy, pseudo client logo marks and stale v0.1 badge (zero-user
  product must not fake traction)
- new proof-by-specificity band: verifiable links to /docs/oauth, /status,
  /security instead of testimonial cosplay
- identity: indigo-to-cyan brand gradient, indigo-tinted elevated surfaces,
  mono section kickers, terminal-chrome hero rotator, gradient h-11 CTA
- how-it-works as connected pipeline; new final CTA band (page no longer
  ends on FAQ); footer upgraded to 4-column product/resources/legal
- lib/pricing.ts single tier source for pricing page + landing teaser;
  annual-billing FAQ claim softened to truth (no annual checkout exists)
- hero video preload=metadata + IntersectionObserver play (2.6MB off the
  critical path); 44px touch targets; mobile menu: Guides link, CTA,
  scroll-lock, escape/outside-tap close

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXUwmPVRTD8AKQtio6gCN5
This commit is contained in:
Marco Sadjadi
2026-07-08 23:00:25 +02:00
parent cba45402ce
commit 17056d0b30
8 changed files with 627 additions and 432 deletions

View File

@@ -47,6 +47,9 @@ function formatTime(seconds: number): string {
export function HeroVideo() {
const videoRef = useRef<HTMLVideoElement>(null);
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
// Set when the user explicitly pauses — the IntersectionObserver must not
// restart a video the user chose to stop.
const userPausedRef = useRef(false);
const [muted, setMuted] = useState(true);
const [playing, setPlaying] = useState(false);
const [playFailed, setPlayFailed] = useState(false);
@@ -69,10 +72,26 @@ export function HeroVideo() {
v.addEventListener('timeupdate', onTime);
v.addEventListener('loadedmetadata', onMeta);
v.addEventListener('durationchange', onMeta);
// Best-effort autoplay attempt — silently fail on browsers that
// block it; the overlay is the user's escape hatch.
v.play().catch(() => undefined);
// Play only while scrolled into view: with preload="metadata" the 2.6 MB
// mp4 stays off the critical path until the section is actually visible,
// and playback pauses again off-screen (saves battery + decode). The
// .play() attempt is still best-effort — browsers that block autoplay
// leave the overlay as the user's escape hatch. `userPaused` tracks an
// explicit pause so the observer doesn't fight the user's choice.
const io = new IntersectionObserver(
([entry]) => {
if (!entry) return;
if (entry.isIntersecting) {
if (!userPausedRef.current && v.paused) v.play().catch(() => undefined);
} else if (!v.paused) {
v.pause();
}
},
{ threshold: 0.35 },
);
io.observe(v);
return () => {
io.disconnect();
v.removeEventListener('play', onPlay);
v.removeEventListener('pause', onPause);
v.removeEventListener('timeupdate', onTime);
@@ -82,9 +101,12 @@ export function HeroVideo() {
}, []);
// Clear any pending hide timer on unmount.
useEffect(() => () => {
if (hideTimer.current) clearTimeout(hideTimer.current);
}, []);
useEffect(
() => () => {
if (hideTimer.current) clearTimeout(hideTimer.current);
},
[],
);
const scheduleHide = useCallback(() => {
if (hideTimer.current) clearTimeout(hideTimer.current);
@@ -106,6 +128,7 @@ export function HeroVideo() {
if (!v) return;
if (v.paused) {
setPlayFailed(false);
userPausedRef.current = false;
try {
// .load() resets the media element's resource selection — required
// when an earlier autoplay attempt was blocked before the source
@@ -116,6 +139,7 @@ export function HeroVideo() {
setPlayFailed(true);
}
} else {
userPausedRef.current = true;
v.pause();
}
}, []);
@@ -165,11 +189,10 @@ export function HeroVideo() {
>
<video
ref={videoRef}
autoPlay
muted
loop
playsInline
preload="auto"
preload="metadata"
poster="/videos/hero-poster.jpg"
onClick={onVideoClick}
className="size-full cursor-pointer object-cover"