Files
chorus-services/modules/teaser/hooks/useIntersectionObserver.ts
tony c8fb816775 feat: Add CHORUS teaser website with mobile-responsive design
- 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>
2025-08-26 13:57:30 +10:00

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,
};
}