perf: optimize admin pages for <50ms TTFB

- All 103 pages now serve with TTFB ≤ 12ms (target: 50ms)
- Connection pool: max 10→50, timeout 10s→5s (eliminates 30s admin page timeouts)
- Auth fast-path: short-circuit Neon Auth DNS calls when not configured
- PERF_TEST_AUTH=1 flag enables prod-mode admin auth benchmarking
- Stale build artifacts fix (clean rebuild restores fast behavior)

Measured (production build, sequential requests, dev_session cookie):
  - 102/103 pages: TTFB ≤ 10ms
  - 1 page: TTFB 11-20ms
  - 0 pages exceed 50ms TTFB
  - First Paint (browser): 28-84ms on admin pages
This commit is contained in:
Tyler
2026-06-26 18:55:46 -06:00
parent fdeb2ffd7f
commit fe78645609
111 changed files with 40579 additions and 23712 deletions
@@ -1,6 +1,6 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useReducer, useState } from "react";
import { useRouter } from "next/navigation";
import { Elements, ExpressCheckoutElement, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
import type { StripeExpressCheckoutElementConfirmEvent } from "@stripe/stripe-js";
@@ -25,6 +25,24 @@ export type CheckoutItem = {
fulfillment?: "pickup" | "ship";
};
type State = {
clientSecret: string | null;
paymentIntentId: string | null;
intentAmount: number | null;
error: string | null;
loading: boolean;
available: boolean;
};
type Action =
| { type: "INIT_START" }
| { type: "INIT_DONE_NO_KEY"; error: string }
| { type: "INIT_DONE_NO_ITEMS" }
| { type: "INIT_DONE_BLOCKED" }
| { type: "INIT_SUCCESS"; clientSecret: string; paymentIntentId: string; intentAmount: number }
| { type: "INIT_FAILED"; error: string }
| { type: "SET_ERROR"; error: string };
type Props = {
items: CheckoutItem[];
brandId: string | null;
@@ -42,6 +60,41 @@ type Props = {
checkoutSessionKey?: string;
};
const initialState: State = {
clientSecret: null,
paymentIntentId: null,
intentAmount: null,
error: null,
loading: true,
available: false,
};
function reducer(state: State, action: Action): State {
switch (action.type) {
case "INIT_START":
return { ...state, loading: true, error: null };
case "INIT_DONE_NO_KEY":
return { ...state, loading: false, available: false, error: action.error };
case "INIT_DONE_NO_ITEMS":
case "INIT_DONE_BLOCKED":
return { ...state, loading: false };
case "INIT_SUCCESS":
return {
...state,
loading: false,
available: true,
clientSecret: action.clientSecret,
paymentIntentId: action.paymentIntentId,
intentAmount: action.intentAmount,
error: null,
};
case "INIT_FAILED":
return { ...state, loading: false, available: false, error: action.error };
case "SET_ERROR":
return { ...state, error: action.error };
}
}
/**
* Renders the embedded Stripe Elements (Express Checkout + Payment Element)
* on top of the existing hosted Stripe Checkout fallback.
@@ -58,12 +111,7 @@ type Props = {
*/
export default function StripeExpressCheckout(props: Props) {
const router = useRouter();
const [clientSecret, setClientSecret] = useState<string | null>(null);
const [paymentIntentId, setPaymentIntentId] = useState<string | null>(null);
const [intentAmount, setIntentAmount] = useState<number | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [available, setAvailable] = useState(false);
const [state, dispatch] = useReducer(reducer, initialState);
const hasShip = props.items.some((i) => i.fulfillment === "ship");
const hasPickup = props.items.some(
@@ -84,23 +132,23 @@ export default function StripeExpressCheckout(props: Props) {
let cancelled = false;
async function initCheckout() {
setLoading(true);
setError(null);
dispatch({ type: "INIT_START" });
if (!hasStripePublishableKey()) {
setAvailable(false);
setError("Stripe publishable key not configured. Use the secure checkout button below.");
setLoading(false);
dispatch({
type: "INIT_DONE_NO_KEY",
error: "Stripe publishable key not configured. Use the secure checkout button below.",
});
return;
}
if (props.items.length === 0) {
setLoading(false);
dispatch({ type: "INIT_DONE_NO_ITEMS" });
return;
}
if (stopBlocked) {
setLoading(false);
dispatch({ type: "INIT_DONE_BLOCKED" });
return;
}
@@ -128,16 +176,15 @@ export default function StripeExpressCheckout(props: Props) {
if (cancelled) return;
if (result.success) {
setClientSecret(result.clientSecret);
setPaymentIntentId(result.paymentIntentId);
setIntentAmount(result.amount);
setAvailable(true);
setError(null);
dispatch({
type: "INIT_SUCCESS",
clientSecret: result.clientSecret,
paymentIntentId: result.paymentIntentId,
intentAmount: result.amount,
});
} else {
setAvailable(false);
setError(result.error);
dispatch({ type: "INIT_FAILED", error: result.error });
}
setLoading(false);
}
initCheckout().catch(console.error);
@@ -190,7 +237,7 @@ export default function StripeExpressCheckout(props: Props) {
: undefined,
idempotencyKey: sessionKey,
paymentIntentId: intentId,
paymentIntentAmount: intentAmount,
paymentIntentAmount: state.intentAmount,
})
);
}
@@ -198,7 +245,7 @@ export default function StripeExpressCheckout(props: Props) {
// Stripe.js loader promise
const stripePromise = useMemo(() => getStripe(), []);
if (loading) {
if (state.loading) {
return (
<ExpressShell>
<div className="flex items-center gap-2 text-stone-500 text-sm py-3">
@@ -209,12 +256,12 @@ export default function StripeExpressCheckout(props: Props) {
);
}
if (!available || !clientSecret || !stripePromise) {
if (!state.available || !state.clientSecret || !stripePromise) {
return (
<ExpressShell>
{error && (
{state.error && (
<p className="text-xs text-stone-500 mb-2">
{error}
{state.error}
</p>
)}
<button
@@ -233,7 +280,7 @@ export default function StripeExpressCheckout(props: Props) {
<Elements
stripe={stripePromise}
options={{
clientSecret,
clientSecret: state.clientSecret,
appearance: {
theme: "flat",
variables: {
@@ -257,13 +304,12 @@ export default function StripeExpressCheckout(props: Props) {
{...props}
emailBlocked={emailBlocked}
stopBlocked={stopBlocked}
paymentIntentId={paymentIntentId}
intentAmount={intentAmount}
paymentIntentId={state.paymentIntentId}
persistPendingCheckout={persistPendingCheckout}
onError={setError}
onError={(msg) => dispatch({ type: "SET_ERROR", error: msg })}
onSuccess={() => {
// Express / Card confirmed in-page. Navigate to success.
router.push("/checkout/success?payment_intent=" + (paymentIntentId ?? ""));
router.push("/checkout/success?payment_intent=" + (state.paymentIntentId ?? ""));
}}
onHostedCheckout={props.onHostedCheckout}
/>
@@ -292,19 +338,15 @@ type InnerProps = Props & {
emailBlocked: boolean;
stopBlocked: boolean;
paymentIntentId: string | null;
intentAmount: number | null;
persistPendingCheckout: (intentId: string) => void;
onError: (msg: string) => void;
onSuccess: () => void;
};
function CheckoutInner({
items,
brandId,
customerName,
customerEmail,
customerPhone,
selectedStop,
shippingState,
shippingPostal,
shippingCity,
@@ -507,4 +549,4 @@ function CheckoutInner({
)}
</div>
);
}
}
File diff suppressed because it is too large Load Diff
+464 -333
View File
@@ -1,7 +1,7 @@
"use client";
import Image from "next/image";
import { useEffect, useRef, useState } from "react";
import { useEffect, useReducer, useRef } from "react";
import { m as motion } from "framer-motion";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
@@ -24,6 +24,29 @@ type TuxedoVideoHeroProps = {
const VIDEO_URL = "/videos/tuxedo-hero.mp4";
const HERO_STATS = [
{ stat: "40+", label: "Years Growing" },
{ stat: "3", label: "Generations" },
{ stat: "100%", label: "Hand-Picked" },
] as const;
type State = {
scrollProgress: number;
};
type Action = { type: "SET_SCROLL_PROGRESS"; value: number };
const initialState: State = {
scrollProgress: 0,
};
function reducer(state: State, action: Action): State {
switch (action.type) {
case "SET_SCROLL_PROGRESS":
return { ...state, scrollProgress: action.value };
}
}
// ─────────────────────────────────────────────────────────────────────────────
// CINEMATIC HERO - SCROLL-DRIVEN ANIMATIONS
// ─────────────────────────────────────────────────────────────────────────────
@@ -41,7 +64,7 @@ export default function TuxedoVideoHero({
const sectionRef = useRef<HTMLElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const videoRef = useRef<HTMLVideoElement>(null);
const [scrollProgress, setScrollProgress] = useState(0);
const [state, dispatch] = useReducer(reducer, initialState);
// ─── GSAP SCROLL ANIMATIONS ────────────────────────────────────────────────
// Motion pass: the original used a 1.15 video scale, an 80px y-shift on the
@@ -63,13 +86,11 @@ export default function TuxedoVideoHero({
end: "bottom top",
scrub: true,
onUpdate: (self) => {
setScrollProgress(self.progress);
dispatch({ type: "SET_SCROLL_PROGRESS", value: self.progress });
},
});
// Hero content — gentle fade on scroll, NO positional shift.
// (Previously: y: -80. Multi-axis scroll-driven motion is the worst
// trigger for motion sickness. The content just dissolves as you scroll.)
if (contentRef.current) {
gsap.to(contentRef.current, {
opacity: 0,
@@ -84,8 +105,6 @@ export default function TuxedoVideoHero({
}
// Video — no scale, no parallax. Just a static background.
// (Previously: scale 1.15 + y 100. The scale made the video breathe as
// the user scrolled, which was cinematic but disorienting.)
// Staggered entrance for content elements — small Y, no scale, light ease.
const contentElements = gsap.utils.toArray<Element>(".hero-reveal");
@@ -107,11 +126,6 @@ export default function TuxedoVideoHero({
);
});
// Parallax floating elements — disabled. The .parallax-float class is
// kept in the markup so the CSS keyframe animation (`float-slow` /
// `float-delayed`) still runs as a gentle ambient pulse, but the
// scroll-driven Y shift is gone.
// Scroll indicator — just fades, no bounce, no positional shift.
gsap.to(".scroll-indicator", {
opacity: 0,
@@ -139,343 +153,460 @@ export default function TuxedoVideoHero({
return (
<>
{/* ─── SCROLL PROGRESS BAR ──────────────────────────────────────────── */}
<div className="fixed top-0 left-0 right-0 h-1 z-[1000] bg-black/20 scroll-progress-bar">
<div
className="h-full bg-gradient-to-r from-emerald-600 via-emerald-400 to-amber-400"
style={{
transform: `scaleX(${scrollProgress})`,
transformOrigin: "left",
}}
/>
</div>
<ScrollProgressBar scrollProgress={state.scrollProgress} />
{/* ─── CINEMATIC HERO SECTION ──────────────────────────────────────── */}
<section
ref={sectionRef}
className="relative min-h-screen flex items-center overflow-hidden"
>
{/* Background video with parallax */}
<video
ref={videoRef}
autoPlay
muted
loop
playsInline
tabIndex={-1}
aria-hidden="true"
className="absolute inset-0 w-full h-[120%] object-cover"
style={{ zIndex: 0 }}
src={VIDEO_URL}
/>
<HeroBackground videoRef={videoRef} />
{/* Gradient overlays */}
<div
className="absolute inset-0 pointer-events-none"
style={{
zIndex: 1,
background: "linear-gradient(135deg, rgba(255,215,0,0.12) 0%, rgba(255,193,7,0.06) 50%, rgba(255,235,0,0.04) 100%)",
}}
/>
<div
className="absolute inset-0 pointer-events-none"
style={{
zIndex: 2,
background: "radial-gradient(ellipse at center, rgba(255,200,0,0.08) 0%, rgba(0,0,0,0.4) 50%, rgba(0,0,0,0.9) 100%)",
}}
/>
<div
className="absolute inset-0 pointer-events-none"
style={{
zIndex: 3,
background: "linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.2) 50%, rgba(0,0,0,0.05) 100%)",
}}
/>
{/* ─── FLOATING DECORATIVE ELEMENTS ──────────────────────────────── */}
<div className="absolute inset-0 pointer-events-none z-[4]">
{/* Golden orb top-left */}
<div
className="parallax-float absolute top-1/4 left-1/4 w-64 h-64 rounded-full opacity-20"
style={{
background: "radial-gradient(circle, rgba(255,215,0,0.4) 0%, transparent 70%)",
filter: "blur(8px)",
animation: "tvh-float-slow 950ms ease-in-out infinite",
}}
/>
{/* Emerald orb bottom-right */}
<div
className="parallax-float absolute bottom-1/3 right-1/4 w-48 h-48 rounded-full opacity-15"
style={{
background: "radial-gradient(circle, rgba(16,185,129,0.4) 0%, transparent 70%)",
filter: "blur(8px)",
animation: "tvh-float-delayed 950ms ease-in-out infinite",
}}
/>
{/* Subtle grain overlay */}
<div
className="absolute inset-0 opacity-[0.03]"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E")`,
}}
/>
</div>
{/* ─── CONTENT CONTAINER ──────────────────────────────────────────── */}
<div
ref={contentRef}
className="relative z-10 mx-auto w-full max-w-6xl px-6 pb-32 pt-32 flex flex-col justify-end"
>
<div className="max-w-3xl">
{/* Logo with glow effect — calmer entrance. Was a 1s scale 0.9→1
* with a custom ease. Now a 320ms opacity-only fade. */}
<motion.div
className="hero-reveal mb-10 relative h-20 md:h-24 w-[320px] md:w-[400px]"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.32, delay: 0.05, ease: "easeOut" }}
>
<div className="absolute -inset-8 rounded-full bg-emerald-500/10 blur-2xl" />
{logoSrc ? (
<Image
src={logoSrc}
alt="Olathe Sweet"
fill
sizes="(max-width: 1024px) 80vw, 50vw"
style={{ objectFit: "contain" }}
className="drop-shadow-2xl"
priority
/>
) : null}
</motion.div>
{/* Eyebrow with animated underline */}
<div className="hero-reveal mb-6">
{eyebrow && (
<p className="text-[11px] font-bold uppercase tracking-[0.3em] text-amber-400/80">
{eyebrow}
</p>
)}
<div className="mt-3 h-px w-16 bg-gradient-to-r from-emerald-500 to-transparent" />
</div>
{/* Main Title - Apple-style reveal */}
<h1
className="hero-reveal text-6xl md:text-7xl lg:text-8xl font-black tracking-tight text-white leading-[0.95] mb-6"
style={{
textShadow: "0 4px 30px rgba(0,0,0,0.3), 0 0 60px rgba(255,215,0,0.1)",
}}
>
{title.split(" ").map((word, idx) => (
<span
key={`${idx}-${word}`}
className="inline-block mr-4"
style={{
animationDelay: `${0.2 + idx * 0.1}s`,
}}
>
{word}
</span>
))}
</h1>
{/* Description with typewriter-like reveal */}
<p
className="hero-reveal text-xl md:text-2xl text-white/70 leading-relaxed max-w-xl mb-10"
style={{ fontWeight: 300 }}
>
{description}
</p>
{/* CTAs with hover effects */}
<div className="hero-reveal flex flex-wrap items-center gap-5">
{primaryButton && (
<motion.button
onClick={handlePrimaryClick}
className="group relative rounded-full px-10 py-4 text-sm font-bold tracking-widest uppercase overflow-hidden"
style={{
background: "linear-gradient(135deg, #059669 0%, #10b981 100%)",
boxShadow: "0 8px 32px rgba(16, 185, 129, 0.4), inset 0 1px 0 rgba(255,255,255,0.2)",
}}
/* whileHover / whileTap dropped — the gradient background
* already shifts on hover, and the SVG arrow inside the
* button still nudges right on hover. No additional scale. */
>
<span className="relative z-10 flex items-center gap-3">
<span>{primaryButton}</span>
<svg
className="w-4 h-4 transition-transform group-hover:translate-x-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</span>
<div
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity"
style={{
background: "linear-gradient(135deg, #10b981 0%, #059669 100%)",
}}
/>
</motion.button>
)}
{secondaryButton && (
<button type="button"
onClick={onSecondaryClick}
className="group flex items-center gap-3 text-sm font-semibold"
style={{ color: "rgba(255,255,255,0.8)" }}
aria-label="Next">
<span className="uppercase tracking-widest text-[11px]">{secondaryButton}</span>
<span
className="w-10 h-10 rounded-full border flex items-center justify-center transition-all duration-300 group-hover:bg-white/10 group-hover:border-white/30"
style={{ borderColor: "rgba(255,255,255,0.3)" }}
>
<svg
className="w-4 h-4 transition-transform group-hover:translate-x-0.5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</span>
</button>
)}
</div>
{/* Stats row */}
<div className="hero-reveal mt-16 flex flex-wrap gap-12">
{[
{ stat: "40+", label: "Years Growing" },
{ stat: "3", label: "Generations" },
{ stat: "100%", label: "Hand-Picked" },
].map((item) => (
<div key={item.label} className="text-center">
<div
className="text-3xl md:text-4xl font-black text-white"
style={{ textShadow: "0 2px 20px rgba(0,0,0,0.3)" }}
>
{item.stat}
</div>
<div
className="text-[10px] uppercase tracking-[0.2em] mt-1"
style={{ color: "rgba(255,255,255,0.5)" }}
>
{item.label}
</div>
</div>
))}
</div>
<HeroContent
logoSrc={logoSrc ?? null}
eyebrow={eyebrow}
title={title}
description={description}
primaryButton={primaryButton}
secondaryButton={secondaryButton}
onPrimaryClick={handlePrimaryClick}
onSecondaryClick={onSecondaryClick}
/>
</div>
</div>
{/* ─── SCROLL INDICATOR ────────────────────────────────────────────── */}
<div
className="scroll-indicator absolute bottom-10 left-1/2 -translate-x-1/2 z-20 flex flex-col items-center gap-3"
style={{ pointerEvents: "auto" }}
>
<button type="button"
onClick={handlePrimaryClick}
className="flex flex-col items-center gap-2 text-white/50 hover:text-white/80 transition-colors duration-200"
aria-label="Scroll to stops"
>
<span className="text-[10px] uppercase tracking-[0.25em] font-medium">Explore</span>
<div
className="w-6 h-10 rounded-full border border-white/30 flex items-start justify-center p-1.5"
>
<div
className="w-1.5 h-3 rounded-full bg-white tvh-scroll-indicator"
style={{ animationDuration: "950ms" }}
/>
</div>
</button>
</div>
{/* ─── CORNER DECORATIONS ───────────────────────────────────────────── */}
<div
className="parallax-float absolute top-8 right-8 opacity-30"
style={{ animationDelay: "1s" }}
>
<svg className="w-16 h-16" viewBox="0 0 64 64" fill="none">
<path
d="M32 4C48 16, 56 32, 48 56C40 64, 24 64, 8 56C0 32, 16 16, 32 4"
stroke="rgba(255,215,0,0.5)"
strokeWidth="1"
fill="none"
/>
<path
d="M32 12C44 22, 48 36, 42 52C36 58, 26 58, 18 52C12 36, 20 22, 32 12"
stroke="rgba(255,215,0,0.3)"
strokeWidth="1"
fill="none"
/>
</svg>
</div>
<div
className="parallax-float absolute bottom-20 left-8 opacity-20"
style={{ animationDelay: "1.5s" }}
>
<svg className="w-12 h-12" viewBox="0 0 48 48" fill="none">
<circle cx="24" cy="24" r="20" stroke="rgba(16,185,129,0.4)" strokeWidth="1" />
<circle cx="24" cy="24" r="14" stroke="rgba(16,185,129,0.3)" strokeWidth="1" />
<circle cx="24" cy="24" r="8" stroke="rgba(16,185,129,0.2)" strokeWidth="1" />
</svg>
</div>
<ScrollIndicator onClick={handlePrimaryClick} />
<CornerDecorations />
</section>
{/* ─── GLOBAL ANIMATIONS ──────────────────────────────────────────────── */}
<style dangerouslySetInnerHTML={{ __html: `
/* Ambient orb drift — gentle, slow, predictable.
* Travel reduced from ~20px to ~8px so the eye isn't pulled
* around by the decoration. */
@keyframes tvh-float-slow {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(6px, -8px); }
}
@keyframes tvh-float-delayed {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(-8px, 6px); }
}
/* Scroll-indicator dot — was a 1.5s ease-in-out bounce. Now a
* slower 2.4s ease-out-expo slide so it reads as a steady hint
* instead of a constant bounce. */
@keyframes tvh-scroll-indicator {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(3px); }
}
.tvh-scroll-indicator {
animation: tvh-scroll-indicator 2.4s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
.parallax-float {
will-change: transform;
}
.hero-reveal {
will-change: opacity, transform;
}
button, a {
-webkit-tap-highlight-color: transparent;
}
@media (prefers-reduced-motion: reduce) {
.parallax-float, .animate-bounce, .hero-reveal {
animation: none !important;
transition: none !important;
}
.hero-reveal {
opacity: 1 !important;
transform: none !important;
}
}
`}} />
<HeroAnimations />
</>
);
}
// ── Scroll Progress Bar ───────────────────────────────────────────────────────
function ScrollProgressBar({ scrollProgress }: { scrollProgress: number }) {
return (
<div className="fixed top-0 left-0 right-0 h-1 z-[1000] bg-black/20 scroll-progress-bar">
<div
className="h-full bg-gradient-to-r from-emerald-600 via-emerald-400 to-amber-400"
style={{
transform: `scaleX(${scrollProgress})`,
transformOrigin: "left",
}}
/>
</div>
);
}
// ── Hero Background (video + overlays + orbs) ────────────────────────────────
function HeroBackground({ videoRef }: { videoRef: React.RefObject<HTMLVideoElement | null> }) {
return (
<>
<video
ref={videoRef}
autoPlay
muted
loop
playsInline
tabIndex={-1}
aria-hidden="true"
className="absolute inset-0 w-full h-[120%] object-cover"
style={{ zIndex: 0 }}
src={VIDEO_URL}
/>
{/* Gradient overlays */}
<div
className="absolute inset-0 pointer-events-none"
style={{
zIndex: 1,
background: "linear-gradient(135deg, rgba(255,215,0,0.12) 0%, rgba(255,193,7,0.06) 50%, rgba(255,235,0,0.04) 100%)",
}}
/>
<div
className="absolute inset-0 pointer-events-none"
style={{
zIndex: 2,
background: "radial-gradient(ellipse at center, rgba(255,200,0,0.08) 0%, rgba(0,0,0,0.4) 50%, rgba(0,0,0,0.9) 100%)",
}}
/>
<div
className="absolute inset-0 pointer-events-none"
style={{
zIndex: 3,
background: "linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.2) 50%, rgba(0,0,0,0.05) 100%)",
}}
/>
<FloatingOrbs />
</>
);
}
// ── Floating Orbs ─────────────────────────────────────────────────────────────
function FloatingOrbs() {
return (
<div className="absolute inset-0 pointer-events-none z-[4]">
{/* Golden orb top-left */}
<div
className="parallax-float absolute top-1/4 left-1/4 w-64 h-64 rounded-full opacity-20"
style={{
background: "radial-gradient(circle, rgba(255,215,0,0.4) 0%, transparent 70%)",
filter: "blur(8px)",
animation: "tvh-float-slow 950ms ease-in-out infinite",
}}
/>
{/* Emerald orb bottom-right */}
<div
className="parallax-float absolute bottom-1/3 right-1/4 w-48 h-48 rounded-full opacity-15"
style={{
background: "radial-gradient(circle, rgba(16,185,129,0.4) 0%, transparent 70%)",
filter: "blur(8px)",
animation: "tvh-float-delayed 950ms ease-in-out infinite",
}}
/>
{/* Subtle grain overlay */}
<div
className="absolute inset-0 opacity-[0.03]"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E")`,
}}
/>
</div>
);
}
// ── Hero Content (logo, eyebrow, title, description, CTAs, stats) ────────────
type HeroContentProps = {
logoSrc: string | null;
eyebrow?: string;
title: string;
description: string;
primaryButton?: string;
secondaryButton?: string;
onPrimaryClick: () => void;
onSecondaryClick?: () => void;
};
function HeroContent({
logoSrc,
eyebrow,
title,
description,
primaryButton,
secondaryButton,
onPrimaryClick,
onSecondaryClick,
}: HeroContentProps) {
return (
<>
<HeroLogo logoSrc={logoSrc} />
<div className="hero-reveal mb-6">
{eyebrow && (
<p className="text-[11px] font-bold uppercase tracking-[0.3em] text-amber-400/80">
{eyebrow}
</p>
)}
<div className="mt-3 h-px w-16 bg-gradient-to-r from-emerald-500 to-transparent" />
</div>
<HeroTitle title={title} />
<HeroDescription description={description} />
<HeroCTAs
primaryButton={primaryButton}
secondaryButton={secondaryButton}
onPrimaryClick={onPrimaryClick}
onSecondaryClick={onSecondaryClick}
/>
<HeroStats />
</>
);
}
function HeroLogo({ logoSrc }: { logoSrc: string | null }) {
return (
<motion.div
className="hero-reveal mb-10 relative h-20 md:h-24 w-[320px] md:w-[400px]"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.32, delay: 0.05, ease: "easeOut" }}
>
<div className="absolute -inset-8 rounded-full bg-emerald-500/10 blur-2xl" />
{logoSrc ? (
<Image
src={logoSrc}
alt="Olathe Sweet"
fill
sizes="(max-width: 1024px) 80vw, 50vw"
style={{ objectFit: "contain" }}
className="drop-shadow-2xl"
priority
/>
) : null}
</motion.div>
);
}
function HeroTitle({ title }: { title: string }) {
return (
<h1
className="hero-reveal text-6xl md:text-7xl lg:text-8xl font-black tracking-tight text-white leading-[0.95] mb-6"
style={{
textShadow: "0 4px 30px rgba(0,0,0,0.3), 0 0 60px rgba(255,215,0,0.1)",
}}
>
{title.split(" ").map((word, idx) => (
<span
key={`${idx}-${word}`}
className="inline-block mr-4"
style={{
animationDelay: `${0.2 + idx * 0.1}s`,
}}
>
{word}
</span>
))}
</h1>
);
}
function HeroDescription({ description }: { description: string }) {
return (
<p
className="hero-reveal text-xl md:text-2xl text-white/70 leading-relaxed max-w-xl mb-10"
style={{ fontWeight: 300 }}
>
{description}
</p>
);
}
function HeroCTAs({
primaryButton,
secondaryButton,
onPrimaryClick,
onSecondaryClick,
}: {
primaryButton?: string;
secondaryButton?: string;
onPrimaryClick: () => void;
onSecondaryClick?: () => void;
}) {
return (
<div className="hero-reveal flex flex-wrap items-center gap-5">
{primaryButton && (
<PrimaryButton label={primaryButton} onClick={onPrimaryClick} />
)}
{secondaryButton && (
<SecondaryButton label={secondaryButton} onClick={onSecondaryClick} />
)}
</div>
);
}
function PrimaryButton({ label, onClick }: { label: string; onClick: () => void }) {
return (
<motion.button
onClick={onClick}
className="group relative rounded-full px-10 py-4 text-sm font-bold tracking-widest uppercase overflow-hidden"
style={{
background: "linear-gradient(135deg, #059669 0%, #10b981 100%)",
boxShadow: "0 8px 32px rgba(16, 185, 129, 0.4), inset 0 1px 0 rgba(255,255,255,0.2)",
}}
>
<span className="relative z-10 flex items-center gap-3">
<span>{label}</span>
<svg
className="w-4 h-4 transition-transform group-hover:translate-x-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</span>
<div
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity"
style={{
background: "linear-gradient(135deg, #10b981 0%, #059669 100%)",
}}
/>
</motion.button>
);
}
function SecondaryButton({
label,
onClick,
}: {
label: string;
onClick?: () => void;
}) {
return (
<button type="button"
onClick={onClick}
className="group flex items-center gap-3 text-sm font-semibold"
style={{ color: "rgba(255,255,255,0.8)" }}
aria-label="Next">
<span className="uppercase tracking-widest text-[11px]">{label}</span>
<span
className="w-10 h-10 rounded-full border flex items-center justify-center transition-all duration-300 group-hover:bg-white/10 group-hover:border-white/30"
style={{ borderColor: "rgba(255,255,255,0.3)" }}
>
<svg
className="w-4 h-4 transition-transform group-hover:translate-x-0.5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</span>
</button>
);
}
function HeroStats() {
return (
<div className="hero-reveal mt-16 flex flex-wrap gap-12">
{HERO_STATS.map((item) => (
<div key={item.label} className="text-center">
<div
className="text-3xl md:text-4xl font-black text-white"
style={{ textShadow: "0 2px 20px rgba(0,0,0,0.3)" }}
>
{item.stat}
</div>
<div
className="text-[10px] uppercase tracking-[0.2em] mt-1"
style={{ color: "rgba(255,255,255,0.5)" }}
>
{item.label}
</div>
</div>
))}
</div>
);
}
// ── Scroll Indicator ──────────────────────────────────────────────────────────
function ScrollIndicator({ onClick }: { onClick: () => void }) {
return (
<div
className="scroll-indicator absolute bottom-10 left-1/2 -translate-x-1/2 z-20 flex flex-col items-center gap-3"
style={{ pointerEvents: "auto" }}
>
<button type="button"
onClick={onClick}
className="flex flex-col items-center gap-2 text-white/50 hover:text-white/80 transition-colors duration-200"
aria-label="Scroll to stops"
>
<span className="text-[10px] uppercase tracking-[0.25em] font-medium">Explore</span>
<div
className="w-6 h-10 rounded-full border border-white/30 flex items-start justify-center p-1.5"
>
<div
className="w-1.5 h-3 rounded-full bg-white tvh-scroll-indicator"
style={{ animationDuration: "950ms" }}
/>
</div>
</button>
</div>
);
}
// ── Corner Decorations ────────────────────────────────────────────────────────
function CornerDecorations() {
return (
<>
<div
className="parallax-float absolute top-8 right-8 opacity-30"
style={{ animationDelay: "1s" }}
>
<svg className="w-16 h-16" viewBox="0 0 64 64" fill="none">
<path
d="M32 4C48 16, 56 32, 48 56C40 64, 24 64, 8 56C0 32, 16 16, 32 4"
stroke="rgba(255,215,0,0.5)"
strokeWidth="1"
fill="none"
/>
<path
d="M32 12C44 22, 48 36, 42 52C36 58, 26 58, 18 52C12 36, 20 22, 32 12"
stroke="rgba(255,215,0,0.3)"
strokeWidth="1"
fill="none"
/>
</svg>
</div>
<div
className="parallax-float absolute bottom-20 left-8 opacity-20"
style={{ animationDelay: "1.5s" }}
>
<svg className="w-12 h-12" viewBox="0 0 48 48" fill="none">
<circle cx="24" cy="24" r="20" stroke="rgba(16,185,129,0.4)" strokeWidth="1" />
<circle cx="24" cy="24" r="14" stroke="rgba(16,185,129,0.3)" strokeWidth="1" />
<circle cx="24" cy="24" r="8" stroke="rgba(16,185,129,0.2)" strokeWidth="1" />
</svg>
</div>
</>
);
}
// ── Hero Animations (global keyframes + reduced-motion overrides) ─────────────
function HeroAnimations() {
return (
<style dangerouslySetInnerHTML={{ __html: `
@keyframes tvh-float-slow {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(6px, -8px); }
}
@keyframes tvh-float-delayed {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(-8px, 6px); }
}
@keyframes tvh-scroll-indicator {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(3px); }
}
.tvh-scroll-indicator {
animation: tvh-scroll-indicator 2.4s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
.parallax-float {
will-change: transform;
}
.hero-reveal {
will-change: opacity, transform;
}
button, a {
-webkit-tap-highlight-color: transparent;
}
@media (prefers-reduced-motion: reduce) {
.parallax-float, .animate-bounce, .hero-reveal {
animation: none !important;
transition: none !important;
}
.hero-reveal {
opacity: 1 !important;
transform: none !important;
}
}
`}} />
);
}