feat: home page hero enter button

This commit is contained in:
Yehoshua Sandler 2025-04-30 14:32:21 -05:00
parent b245e6d8c1
commit 0e9891b5a2
4 changed files with 220 additions and 60 deletions

View File

@ -0,0 +1,112 @@
'use client';
import React, { useState, useEffect, useRef } from 'react';
import {
motion,
useMotionValue,
useSpring,
type SpringOptions,
} from 'motion/react';
const SPRING_CONFIG = { stiffness: 26.7, damping: 4.1, mass: 0.2 };
export type MagneticProps = {
children: React.ReactNode;
intensity?: number;
range?: number;
actionArea?: 'self' | 'parent' | 'global';
springOptions?: SpringOptions;
};
export function Magnetic({
children,
intensity = 0.6,
range = 100,
actionArea = 'self',
springOptions = SPRING_CONFIG,
}: MagneticProps) {
const [isHovered, setIsHovered] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const springX = useSpring(x, springOptions);
const springY = useSpring(y, springOptions);
useEffect(() => {
const calculateDistance = (e: MouseEvent) => {
if (ref.current) {
const rect = ref.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const distanceX = e.clientX - centerX;
const distanceY = e.clientY - centerY;
const absoluteDistance = Math.sqrt(distanceX ** 2 + distanceY ** 2);
if (isHovered && absoluteDistance <= range) {
const scale = 1 - absoluteDistance / range;
x.set(distanceX * intensity * scale);
y.set(distanceY * intensity * scale);
} else {
x.set(0);
y.set(0);
}
}
};
document.addEventListener('mousemove', calculateDistance);
return () => {
document.removeEventListener('mousemove', calculateDistance);
};
}, [ref, isHovered, intensity, range]);
useEffect(() => {
if (actionArea === 'parent' && ref.current?.parentElement) {
const parent = ref.current.parentElement;
const handleParentEnter = () => setIsHovered(true);
const handleParentLeave = () => setIsHovered(false);
parent.addEventListener('mouseenter', handleParentEnter);
parent.addEventListener('mouseleave', handleParentLeave);
return () => {
parent.removeEventListener('mouseenter', handleParentEnter);
parent.removeEventListener('mouseleave', handleParentLeave);
};
} else if (actionArea === 'global') {
setIsHovered(true);
}
}, [actionArea]);
const handleMouseEnter = () => {
if (actionArea === 'self') {
setIsHovered(true);
}
};
const handleMouseLeave = () => {
if (actionArea === 'self') {
setIsHovered(false);
x.set(0);
y.set(0);
}
};
return (
<motion.div
ref={ref}
onMouseEnter={actionArea === 'self' ? handleMouseEnter : undefined}
onMouseLeave={actionArea === 'self' ? handleMouseLeave : undefined}
style={{
x: springX,
y: springY,
}}
>
{children}
</motion.div>
);
}

1
public/images/down.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -10,6 +10,10 @@ import { TextShimmer } from '@/components/ui/text-shimmer'
import { LoginForm } from '@/components/login-form' import { LoginForm } from '@/components/login-form'
import SearchBooks from '@/components/Search/SearchBooks' import SearchBooks from '@/components/Search/SearchBooks'
import Manage from '@/components/Manage/Manage' import Manage from '@/components/Manage/Manage'
import { Magnetic } from 'components/motion-primitives/magnetic'
import { Button } from '@/components/ui/button'
import Image from 'next/image'
import HomeHero from '@/components/HomeHero'
export default async function HomePage() { export default async function HomePage() {
const headers = await getHeaders() const headers = await getHeaders()
@ -89,68 +93,10 @@ export default async function HomePage() {
return ( return (
<div className="home"> <div className="home">
<div className="relative isolate overflow-hidden py-24 sm:py-32 rounded-md"> <HomeHero user={user} />
<img
alt=""
src="/api/media/file/geniza1.jpg"
className="absolute inset-0 -z-10 size-full object-cover"
/>
<div
aria-hidden="true"
className="hidden sm:absolute sm:-top-10 sm:right-1/2 sm:-z-10 sm:mr-10 sm:block sm:transform-gpu sm:blur-3xl"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="aspect-1097/845 w-[78.5625rem] bg-linear-to-tr from-accent-background to-background opacity-100"
/>
</div>
<div
aria-hidden="true"
className="absolute -top-52 left-1/2 -z-10 -translate-x-1/2 transform-gpu blur-3xl sm:top-[-28rem] sm:ml-16 sm:translate-x-0 sm:transform-gpu"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="aspect-1097/845 w-[78.5625rem] bg-linear-to-tr from-background bg-background"
/>
</div>
<div className="mx-auto max-w-7xl px-6 lg:px-8 ">
<div className="mx-auto max-w-2xl lg:mx-0">
<p className="text-base/7 font-semibold text-foreground">Temple Beth-El Beit Midrash</p>
<h2 className="mt-2 text-5xl font-semibold tracking-tight text-foreground sm:text-7xl text-shadow-lg">
<TextShimmer
duration={2.2}
className="[--base-color:var(--color-emerald-700)] [--base-gradient-color:var(--color-white)] dark:[--base-color:var(--color-emerald-600)] dark:[--base-gradient-color:var(--color-white)]"
>
Welcome&nbsp;
</TextShimmer>
<span className="text-shadow-lg text-shadow-background">
{user ? <small>{user.firstName}</small> : <small>In</small>}
</span>
</h2>
<div className="mt-8 text-lg font-normal text-pretty text-foreground text-shadow-lg text-shadow-background sm:text-xl/8">
<p className="indent-5 italic">
Never refuse to lend books to anyone who cannot afford to purchase them, but lend
books only to those who can be trusted to return them.
</p>
<small className="block text-right">- ibn Tibbon</small>
</div>
</div>
</div>
</div>
{user ? ( {user ? (
<Tabs defaultValue="feed" className="p-4"> <Tabs id="tabs" defaultValue="feed" className="p-4">
<TabsList className="grid w-full grid-cols-3"> <TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="feed">Your Feed</TabsTrigger> <TabsTrigger value="feed">Your Feed</TabsTrigger>
<TabsTrigger value="search">Search</TabsTrigger> <TabsTrigger value="search">Search</TabsTrigger>

101
src/components/HomeHero.tsx Normal file
View File

@ -0,0 +1,101 @@
'use client'
import { Magnetic } from 'components/motion-primitives/magnetic'
import { Button } from './ui/button'
import Image from 'next/image'
import { TextShimmer } from './ui/text-shimmer'
import { User } from '@/payload-types'
type Props = {
user: User | null
}
const HomeHero = (props: Props) => {
const { user } = props
return (
<div>
<div className="relative isolate overflow-hidden py-24 sm:py-32 rounded-md">
<img
alt=""
src="/api/media/file/geniza1.jpg"
className="absolute inset-0 -z-10 size-full object-cover"
/>
<div
aria-hidden="true"
className="hidden sm:absolute sm:-top-10 sm:right-1/2 sm:-z-10 sm:mr-10 sm:block sm:transform-gpu sm:blur-3xl"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="aspect-1097/845 w-[78.5625rem] bg-linear-to-tr from-accent-background to-background opacity-100"
/>
</div>
<div
aria-hidden="true"
className="absolute -top-52 left-1/2 -z-10 -translate-x-1/2 transform-gpu blur-3xl sm:top-[-28rem] sm:ml-16 sm:translate-x-0 sm:transform-gpu"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="aspect-1097/845 w-[78.5625rem] bg-linear-to-tr from-background bg-background"
/>
</div>
<div className="mx-auto max-w-7xl px-6 lg:px-8 ">
<div className="mx-auto max-w-2xl lg:mx-0">
<p className="text-base/7 font-semibold text-foreground">Temple Beth-El Beit Midrash</p>
<h2 className="mt-2 text-5xl font-semibold tracking-tight text-foreground sm:text-7xl text-shadow-lg">
<TextShimmer
duration={2.2}
className="[--base-color:var(--color-emerald-700)] [--base-gradient-color:var(--color-white)] dark:[--base-color:var(--color-emerald-600)] dark:[--base-gradient-color:var(--color-white)]"
>
Welcome&nbsp;
</TextShimmer>
<span className="text-shadow-lg text-shadow-background">
{user ? <small>{user.firstName}</small> : <small>In</small>}
</span>
</h2>
<div className="mt-8 text-lg font-normal text-pretty text-foreground text-shadow-lg text-shadow-background sm:text-xl/8">
<p className="indent-5 italic">
Never refuse to lend books to anyone who cannot afford to purchase them, but lend
books only to those who can be trusted to return them.
</p>
<small className="block text-right">- ibn Tibbon</small>
</div>
</div>
</div>
</div>
<div className="flex items-center justify-center h-32">
<Magnetic intensity={0.2} springOptions={{ bounce: 0.1 }} actionArea="global" range={200}>
<Button
type="button"
className="animate-pulse mx-auto block items-center rounded-lg border border-zinc-100/10 bg-transparent text-foreground px-8 py-2 text-sm transition-all duration-200 h-fit"
onClick={() => {
const tabs = document.getElementById('tabs')
tabs?.scrollIntoView({ behavior: 'smooth' })
}}
>
<Image
src="/images/down.svg"
alt="Enter site"
width={40}
height={40}
className="block mx-auto dark:invert"
/>
<span className="block">Enter</span>
</Button>
</Magnetic>
</div>
</div>
)
}
export default HomeHero