open-productive-stack/drupal/nextjs/components/scroll-reveal-section.tsx
rnsrk f8b8f53d54 Add Drupal headless stack with Next.js frontend
- Add Next.js frontend service (nextjs) with Dockerfile and source
- Update docker-compose.yml: image names, Drupal 11.3.3, nextjs service
- Add docker-compose.override.yml.disabled for dev hot-reload
- Add install-headless-modules.sh for OAuth/JSON:API module setup
- Add README.md with full setup and configuration guide
- Update nginx/Dockerfile and nginx.conf.template for cms. subdomain
- Update drupal/Dockerfile PHP-FPM build args
- Gitignore **/.vscode/ to prevent IDE workspace files from being tracked
2026-03-30 11:14:17 +02:00

56 lines
1.4 KiB
TypeScript

"use client"
import { useEffect, useRef, useState, type ReactNode } from "react"
interface ScrollRevealSectionProps {
children: ReactNode
/** When true, section starts visible (no opacity-20 flash). Use for above-the-fold hero. */
initialVisible?: boolean
/** Delay in ms before the reveal animation starts after the section enters view. */
revealDelayMs?: number
}
export function ScrollRevealSection({
children,
initialVisible = false,
revealDelayMs = 0,
}: ScrollRevealSectionProps) {
const ref = useRef<HTMLDivElement>(null)
const [isVisible, setIsVisible] = useState(initialVisible)
useEffect(() => {
const el = ref.current
if (!el) return
let timeoutId: ReturnType<typeof setTimeout> | null = null
const observer = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) return
if (revealDelayMs <= 0) {
setIsVisible(true)
return
}
timeoutId = setTimeout(() => setIsVisible(true), revealDelayMs)
},
{ threshold: 0.1, rootMargin: "0px 0px -80px 0px" }
)
observer.observe(el)
return () => {
if (timeoutId) clearTimeout(timeoutId)
observer.disconnect()
}
}, [revealDelayMs])
return (
<div
ref={ref}
className={`transition-all duration-700 ease-out ${
isVisible ? "translate-y-0 opacity-100" : "translate-y-12 opacity-20"
}`}
>
{children}
</div>
)
}