- Created complete Next.js 15 teaser website with CHORUS brand styling - Implemented mobile-responsive 3D logo (128px mobile, 512px desktop) - Added proper Exo font loading via Next.js Google Fonts for iOS/Chrome compatibility - Built comprehensive early access form with GDPR compliance and rate limiting - Integrated PostgreSQL database with complete schema for lead capture - Added scroll indicators that auto-hide when scrolling begins - Optimized mobile modal forms with proper scrolling and submit button access - Deployed via Docker Swarm with Traefik SSL termination at chorus.services - Includes database migrations, consent tracking, and email notifications 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
49 lines
1.2 KiB
TypeScript
49 lines
1.2 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
|
|
interface IntersectionObserverOptions {
|
|
threshold?: number;
|
|
rootMargin?: string;
|
|
triggerOnce?: boolean;
|
|
}
|
|
|
|
export function useIntersectionObserver(
|
|
options: IntersectionObserverOptions = {}
|
|
) {
|
|
const { threshold = 0.1, rootMargin = '0px 0px -100px 0px', triggerOnce = true } = options;
|
|
const [isIntersecting, setIsIntersecting] = useState(false);
|
|
const [hasIntersected, setHasIntersected] = useState(false);
|
|
const elementRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const element = elementRef.current;
|
|
if (!element) return;
|
|
|
|
const observer = new IntersectionObserver(
|
|
([entry]) => {
|
|
const isVisible = entry.isIntersecting;
|
|
setIsIntersecting(isVisible);
|
|
|
|
if (isVisible && !hasIntersected) {
|
|
setHasIntersected(true);
|
|
}
|
|
},
|
|
{
|
|
threshold,
|
|
rootMargin,
|
|
}
|
|
);
|
|
|
|
observer.observe(element);
|
|
|
|
return () => {
|
|
observer.unobserve(element);
|
|
};
|
|
}, [threshold, rootMargin, hasIntersected]);
|
|
|
|
return {
|
|
elementRef,
|
|
isVisible: triggerOnce ? hasIntersected : isIntersecting,
|
|
};
|
|
} |