diff --git a/src/components/HeroPage.tsx b/src/components/HeroPage.tsx index a6c0121..4632970 100644 --- a/src/components/HeroPage.tsx +++ b/src/components/HeroPage.tsx @@ -1,239 +1,107 @@ -'use client' +import type { Metadata } from 'next' -import React, { useEffect, useRef } from 'react' -import { cn } from '@/utilities/ui' -import { useThemeMode } from '@/hooks/useThemeMode' -import { CMSLink } from '@/components/Link' -import RichText from '@/components/RichText' -import type { Page } from '@/payload-types' +import { PayloadRedirects } from '@/components/PayloadRedirects' +import configPromise from '@payload-config' +import { getPayload, type RequiredDataFromCollectionSlug } from 'payload' +import { draftMode } from 'next/headers' +import React, { cache } from 'react' +import { homeStatic } from '@/endpoints/seed/home-static' -// ─── Types ──────────────────────────────────────────────────────────────────── +import { RenderBlocks } from '@/blocks/RenderBlocks' +import { RenderHero } from '@/heros/RenderHero' +import { generateMeta } from '@/utilities/generateMeta' +import PageClient from './page.client' +import { LivePreviewListener } from '@/components/LivePreviewListener' +import ScrollToTop from '@/components/ScrollToTop' -interface Star { - x: number; y: number; r: number - v: number; baseAlpha: number - phase: number; twinkleSpeed: number - vx: number; vy: number +import HeroBackground from '@/components/HeroBackground' +import HeroPage from '@/components/HeroPage' + +export const dynamic = 'force-dynamic' + +export async function generateStaticParams() { + return [] } -interface Wave { - amp: number; freq: number; speed: number - phase: number; yBase: number; width: number; v: number +type Args = { + params: Promise<{ + slug?: string + }> } -// ─── Constants ─────────────────────────────────────────────────────────────── +const LANDING_PAGE_SLUGS = ['home'] -const STAR_TONES = [255, 210, 160, 110, 65] -const WAVE_TONES = [55, 90, 125, 165, 205] -const WAVE_CONFIGS = [ - { amp: 26, freq: 0.011, speed: 0.35, phase: 0.0, yBase: 0.38, width: 1.6, toneIdx: 0 }, - { amp: 20, freq: 0.015, speed: 0.50, phase: 1.3, yBase: 0.52, width: 1.8, toneIdx: 1 }, - { amp: 30, freq: 0.008, speed: 0.28, phase: 2.6, yBase: 0.63, width: 2.2, toneIdx: 2 }, - { amp: 16, freq: 0.019, speed: 0.65, phase: 0.8, yBase: 0.74, width: 2.0, toneIdx: 3 }, - { amp: 22, freq: 0.013, speed: 0.42, phase: 3.2, yBase: 0.85, width: 2.6, toneIdx: 4 }, -] +export default async function Page({ params: paramsPromise }: Args) { + const { isEnabled: draft } = await draftMode() + const { slug = 'home' } = await paramsPromise + const decodedSlug = decodeURIComponent(slug) + const url = '/' + decodedSlug -// ─── Canvas helpers ─────────────────────────────────────────────────────────── + let page: RequiredDataFromCollectionSlug<'pages'> | null -function makeStars(W: number, H: number): Star[] { - return Array.from({ length: 180 }, () => { - const i = Math.floor(Math.random() * STAR_TONES.length) - return { - x: Math.random() * W, y: Math.random() * H, - r: 0.4 + Math.random() * 1.6, - v: STAR_TONES[i], baseAlpha: 0.5 + i * 0.1, - phase: Math.random() * Math.PI * 2, - twinkleSpeed: 0.8 + Math.random() * 2.0, - vx: (Math.random() - 0.5) * 0.3, - vy: (Math.random() - 0.5) * 0.15, - } - }) -} + page = await queryPageBySlug({ slug: decodedSlug }) -function makeWaves(): Wave[] { - return WAVE_CONFIGS.map(c => ({ ...c, v: WAVE_TONES[c.toneIdx] })) -} - -function drawStars(ctx: CanvasRenderingContext2D, stars: Star[], W: number, H: number, ga: number) { - const now = Date.now() / 1000 - for (const s of stars) { - s.x += s.vx; s.y += s.vy - if (s.x < 0) s.x = W; if (s.x > W) s.x = 0 - if (s.y < 0) s.y = H; if (s.y > H) s.y = 0 - const tw = 0.35 + 0.65 * ((Math.sin(now * s.twinkleSpeed + s.phase) + 1) / 2) - ctx.beginPath() - ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2) - ctx.fillStyle = `rgba(${s.v},${s.v},${s.v},${s.baseAlpha * tw * ga})` - ctx.fill() + if (!page && decodedSlug === 'home') { + page = homeStatic } -} -function wavePts(w: Wave, W: number, H: number, now: number) { - const pts: { x: number; y: number }[] = [] - for (let x = 0; x <= W; x += 8) - pts.push({ x, y: H * w.yBase + Math.sin(x * w.freq + w.phase + now * w.speed) * w.amp }) - return pts -} - -function wavePath(ctx: CanvasRenderingContext2D, pts: { x: number; y: number }[]) { - ctx.beginPath() - ctx.moveTo(pts[0].x, pts[0].y) - for (let i = 0; i < pts.length - 1; i++) { - const mx = (pts[i].x + pts[i + 1].x) / 2 - const my = (pts[i].y + pts[i + 1].y) / 2 - ctx.quadraticCurveTo(pts[i].x, pts[i].y, mx, my) + if (!page) { + return } - ctx.lineTo(pts[pts.length - 1].x, pts[pts.length - 1].y) -} -function drawWaves(ctx: CanvasRenderingContext2D, waves: Wave[], W: number, H: number, ga: number, bgR: number) { - const now = Date.now() / 1000 - const bg = Math.round(bgR) - for (const w of waves) { - const v = w.v - const yBase = H * w.yBase - const pts = wavePts(w, W, H, now) - const grad = ctx.createLinearGradient(0, yBase - w.amp, 0, yBase + w.amp * 3) - grad.addColorStop(0, `rgba(${v},${v},${v},${0.030 * ga})`) - grad.addColorStop(0.4, `rgba(${v},${v},${v},${0.012 * ga})`) - grad.addColorStop(1, `rgba(${bg},${bg},${bg},0)`) - wavePath(ctx, pts) - ctx.lineTo(W, H); ctx.lineTo(0, H); ctx.closePath() - ctx.fillStyle = grad - ctx.fill() - wavePath(ctx, pts) - ctx.strokeStyle = `rgba(${v},${v},${v},${0.85 * ga})` - ctx.lineWidth = w.width - ctx.lineJoin = 'round' - ctx.lineCap = 'round' - ctx.stroke() + const { hero, layout } = page + + if (LANDING_PAGE_SLUGS.includes(decodedSlug)) { + return ( + + + + + ) } -} - -// ─── Background canvas ──────────────────────────────────────────────────────── - -function Background({ isDark }: { isDark: boolean }) { - const canvasRef = useRef(null) - const darkRef = useRef(isDark) - useEffect(() => { darkRef.current = isDark }, [isDark]) - - useEffect(() => { - const canvas = canvasRef.current - if (!canvas) return - const ctx = canvas.getContext('2d') - if (!ctx) return - let W = 0, H = 0 - let stars: Star[] = [], waves: Wave[] = [] - let bgR = 0, bgG = 0, bgB = 0, crossfade = 0 - let rafId: number - - function resize() { - W = canvas!.width = canvas!.offsetWidth - H = canvas!.height = canvas!.offsetHeight - stars = makeStars(W, H) - waves = makeWaves() - } - - function loop() { - const dark = darkRef.current - const tBg = dark ? 0 : 255 - const tCross = dark ? 0 : 1 - bgR += (tBg - bgR) * 0.04 - bgG += (tBg - bgG) * 0.04 - bgB += (tBg - bgB) * 0.04 - crossfade += (tCross - crossfade) * 0.03 - ctx!.fillStyle = `rgb(${Math.round(bgR)},${Math.round(bgG)},${Math.round(bgB)})` - ctx!.fillRect(0, 0, W, H) - const sa = 1 - crossfade, wa = crossfade - if (sa > 0.01) drawStars(ctx!, stars, W, H, sa) - if (wa > 0.01) drawWaves(ctx!, waves, W, H, wa, bgR) - rafId = requestAnimationFrame(loop) - } - - const ro = new ResizeObserver(resize) - ro.observe(canvas) - resize() - rafId = requestAnimationFrame(loop) - return () => { cancelAnimationFrame(rafId); ro.disconnect() } - }, []) - - return -} - -// ─── Hero section ───────────────────────────────────────────────────────────── - -type HeroProps = Pick - -function HeroSection({ richText, links }: HeroProps) { - return ( - - {/* Outer border lines */} - - - - - {/* Inner border lines */} - - - - - - - - {richText && ( - - )} - {Array.isArray(links) && links.length > 0 && ( - - {links.map(({ link }, i) => ( - - ))} - - )} - - - ) -} - -// ─── Main export ────────────────────────────────────────────────────────────── - -interface HeroPageProps { - richText?: Page['hero']['richText'] - links?: Page['hero']['links'] - children?: React.ReactNode -} - -export default function HeroPage({ richText, links, children }: HeroPageProps) { - const { isDark } = useThemeMode() return ( - - - - - - - {children && ( - - {children} - - )} - + + + + + + {draft && } + + + + ) -} \ No newline at end of file +} + +export async function generateMetadata({ params: paramsPromise }: Args): Promise { + const { slug = 'home' } = await paramsPromise + const decodedSlug = decodeURIComponent(slug) + const page = await queryPageBySlug({ slug: decodedSlug }) + + return generateMeta({ doc: page }) +} + +const queryPageBySlug = cache(async ({ slug }: { slug: string }) => { + const { isEnabled: draft } = await draftMode() + + const payload = await getPayload({ config: configPromise }) + + const result = await payload.find({ + collection: 'pages', + draft, + limit: 1, + pagination: false, + overrideAccess: draft, + where: { + slug: { + equals: slug, + }, + }, + }) + + return result.docs?.[0] || null +})