fix(tuxedo): graceful hero video fallback + storefront polish
Deploy to route.crispygoat.com / deploy (push) Failing after 1m27s
Deploy to route.crispygoat.com / deploy (push) Failing after 1m27s
Hero video at /videos/tuxedo-hero.mp4 was hardcoded and 404ing, leaving the page with no background. * Add brand_settings.hero_video_url (migration 0099) so each brand can point its hero at any reachable mp4/webm without code changes. Saves via a separate UPDATE so the existing 32-param upsert_brand_settings RPC signature stays untouched. * TuxedoVideoHero now accepts videoUrl + posterUrl. Poster renders underneath the <video> element via next/image so the hero is complete even without a video. <video> flips a data-failed attribute on error/stall so the poster takes over without DOM churn. Ambient float animations slowed 950ms → 7s/8s. * Why section: dropped emerald/amber kaleidoscope, unified accent. Footer is now a provenance strip (Family-Grown · 5,360 ft · Est 1982). * Story section: removed giant decorative 'SINCE' parallax text and the duplicate stats counter (already in hero). Replaced with a 1982/2003/Today timeline.
This commit is contained in:
+120
-144
@@ -73,6 +73,8 @@ type State = {
|
||||
showSchedulePdf: boolean;
|
||||
showWholesaleLink: boolean;
|
||||
heroTagline: string | null;
|
||||
heroImageUrl: string | null;
|
||||
heroVideoUrl: string | null;
|
||||
isAdmin: boolean;
|
||||
customFooterText: string | null;
|
||||
contactEmail: string | null;
|
||||
@@ -96,6 +98,8 @@ type Action =
|
||||
showSchedulePdf: boolean;
|
||||
showWholesaleLink: boolean;
|
||||
heroTagline: string | null;
|
||||
heroImageUrl: string | null;
|
||||
heroVideoUrl: string | null;
|
||||
customFooterText: string | null;
|
||||
contactEmail: string | null;
|
||||
contactPhone: string | null;
|
||||
@@ -117,6 +121,8 @@ const initialState: State = {
|
||||
showSchedulePdf: true,
|
||||
showWholesaleLink: true,
|
||||
heroTagline: null,
|
||||
heroImageUrl: null,
|
||||
heroVideoUrl: null,
|
||||
isAdmin: false,
|
||||
customFooterText: null,
|
||||
contactEmail: null,
|
||||
@@ -145,6 +151,8 @@ function reducer(state: State, action: Action): State {
|
||||
showSchedulePdf: action.showSchedulePdf,
|
||||
showWholesaleLink: action.showWholesaleLink,
|
||||
heroTagline: action.heroTagline,
|
||||
heroImageUrl: action.heroImageUrl,
|
||||
heroVideoUrl: action.heroVideoUrl,
|
||||
customFooterText: action.customFooterText,
|
||||
contactEmail: action.contactEmail,
|
||||
contactPhone: action.contactPhone,
|
||||
@@ -158,29 +166,23 @@ function reducer(state: State, action: Action): State {
|
||||
}
|
||||
|
||||
// ── Why Choose Tuxedo Corn — editorial masonry redesign ─────────────
|
||||
type FeatureColor = "emerald" | "amber";
|
||||
|
||||
interface Feature {
|
||||
color: FeatureColor;
|
||||
/** Bright card / muted card. Both share one accent (silk emerald) — the
|
||||
* variant only changes how much of the card the accent line covers. */
|
||||
tone: "bright" | "muted";
|
||||
label: string;
|
||||
headline: string;
|
||||
story: string;
|
||||
accentColor: string;
|
||||
accentGlow: string;
|
||||
accentHover: string;
|
||||
size: "tall" | "normal";
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
const FEATURES: Feature[] = [
|
||||
{
|
||||
color: "emerald",
|
||||
tone: "bright",
|
||||
label: "Hand-Harvested",
|
||||
headline: "Picked by Hand, Every Ear",
|
||||
story: "Three generations of careful hands move through every row. No machine knows when an ear is perfectly ripe the way a farmer's daughter does.",
|
||||
accentColor: "bg-emerald-500",
|
||||
accentGlow: "hover:shadow-emerald-500/20",
|
||||
accentHover: "group-hover:bg-gradient-to-r group-hover:from-emerald-500 group-hover:to-emerald-600",
|
||||
size: "tall",
|
||||
icon: (
|
||||
<svg viewBox="0 0 48 48" fill="none" className="w-14 h-14 -mt-1" stroke="currentColor" strokeWidth="1.2">
|
||||
@@ -197,13 +199,10 @@ const FEATURES: Feature[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "emerald",
|
||||
tone: "muted",
|
||||
label: "Non-GMO",
|
||||
headline: "Seed to Field, Naturally",
|
||||
story: "Our Olathe Sweet seed has never been touched by a laboratory. The flavor was shaped by forty summers of patient selection — nothing added, nothing altered.",
|
||||
accentColor: "bg-emerald-500",
|
||||
accentGlow: "hover:shadow-emerald-500/20",
|
||||
accentHover: "group-hover:bg-gradient-to-r group-hover:from-emerald-500 group-hover:to-emerald-600",
|
||||
size: "normal",
|
||||
icon: (
|
||||
<svg viewBox="0 0 48 48" fill="none" className="w-14 h-14 -mt-1" stroke="currentColor" strokeWidth="1.2">
|
||||
@@ -220,13 +219,10 @@ const FEATURES: Feature[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "emerald",
|
||||
tone: "bright",
|
||||
label: "Colorado Grown",
|
||||
headline: "Born in the High Country",
|
||||
story: "Olathe Sweet was developed for Colorado's mountain climate — intense sun by day, crisp nights by morning. That contrast makes a sweetness no other soil can claim.",
|
||||
accentColor: "bg-emerald-500",
|
||||
accentGlow: "hover:shadow-emerald-500/20",
|
||||
accentHover: "group-hover:bg-gradient-to-r group-hover:from-emerald-500 group-hover:to-emerald-600",
|
||||
size: "normal",
|
||||
icon: (
|
||||
<svg viewBox="0 0 48 48" fill="none" className="w-14 h-14 -mt-1" stroke="currentColor" strokeWidth="1.2">
|
||||
@@ -239,13 +235,10 @@ const FEATURES: Feature[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "emerald",
|
||||
tone: "muted",
|
||||
label: "Regenerative",
|
||||
headline: "Our Roots Run Deep",
|
||||
story: "We give back more than we take. Our soil is healthier today than forty years ago — not because we had to, but because this land deserves that kind of care.",
|
||||
accentColor: "bg-emerald-500",
|
||||
accentGlow: "hover:shadow-emerald-500/20",
|
||||
accentHover: "group-hover:bg-gradient-to-r group-hover:from-emerald-500 group-hover:to-emerald-600",
|
||||
size: "tall",
|
||||
icon: (
|
||||
<svg viewBox="0 0 48 48" fill="none" className="w-14 h-14 -mt-1" stroke="currentColor" strokeWidth="1.2">
|
||||
@@ -258,13 +251,10 @@ const FEATURES: Feature[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "amber",
|
||||
tone: "bright",
|
||||
label: "Peak Freshness",
|
||||
headline: "Harvested the Morning You Order",
|
||||
story: "We pick before the heat steals a single calorie of sweetness. By the time your order arrives, the corn was still in the field that same morning.",
|
||||
accentColor: "bg-amber-500",
|
||||
accentGlow: "hover:shadow-amber-500/20",
|
||||
accentHover: "group-hover:bg-gradient-to-r group-hover:from-amber-500 group-hover:to-amber-600",
|
||||
size: "normal",
|
||||
icon: (
|
||||
<svg viewBox="0 0 48 48" fill="none" className="w-14 h-14 -mt-1" stroke="currentColor" strokeWidth="1.2">
|
||||
@@ -282,13 +272,10 @@ const FEATURES: Feature[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "amber",
|
||||
tone: "muted",
|
||||
label: "Grown in CO",
|
||||
headline: "Your Neighborhood Farm",
|
||||
story: "Forty years of Colorado stops. We know every town we serve — when they like to pick up, where they gather. Your order isn't a transaction; it's a relationship.",
|
||||
accentColor: "bg-amber-500",
|
||||
accentGlow: "hover:shadow-amber-500/20",
|
||||
accentHover: "group-hover:bg-gradient-to-r group-hover:from-amber-500 group-hover:to-amber-600",
|
||||
size: "tall",
|
||||
icon: (
|
||||
<svg viewBox="0 0 48 48" fill="none" className="w-14 h-14 -mt-1" stroke="currentColor" strokeWidth="1.2">
|
||||
@@ -301,13 +288,10 @@ const FEATURES: Feature[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "amber",
|
||||
tone: "bright",
|
||||
label: "No Middleman",
|
||||
headline: "From Our Rows to Your Table",
|
||||
story: "No distribution center. No supermarket back shelf. Just the field, the cooler, and your front door — the same day. That's the promise we keep.",
|
||||
accentColor: "bg-amber-500",
|
||||
accentGlow: "hover:shadow-amber-500/20",
|
||||
accentHover: "group-hover:bg-gradient-to-r group-hover:from-amber-500 group-hover:to-amber-600",
|
||||
size: "normal",
|
||||
icon: (
|
||||
<svg viewBox="0 0 48 48" fill="none" className="w-14 h-14 -mt-1" stroke="currentColor" strokeWidth="1.2">
|
||||
@@ -321,13 +305,10 @@ const FEATURES: Feature[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "amber",
|
||||
tone: "muted",
|
||||
label: "Easy Preorder",
|
||||
headline: "Reserve in 30 Seconds",
|
||||
story: "Choose your stop, tap your order, done. We'll have your corn in a labeled cooler with your name on it — no account, no app, no confusion.",
|
||||
accentColor: "bg-amber-500",
|
||||
accentGlow: "hover:shadow-amber-500/20",
|
||||
accentHover: "group-hover:bg-gradient-to-r group-hover:from-amber-500 group-hover:to-amber-600",
|
||||
size: "tall",
|
||||
icon: (
|
||||
<svg viewBox="0 0 48 48" fill="none" className="w-14 h-14 -mt-1" stroke="currentColor" strokeWidth="1.2">
|
||||
@@ -347,61 +328,50 @@ function FeatureCard({ feature, index }: { feature: Feature; index: number }) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-60px" });
|
||||
|
||||
// Single accent (silk emerald) for the whole section. Two visual variants:
|
||||
// *bright* — slightly lighter card background, white headline.
|
||||
// *muted* — darker card background, dimmer headline.
|
||||
// Both share the same green hover glow so the section reads as one block,
|
||||
// not a kaleidoscope of competing accents.
|
||||
const cardBg = feature.tone === "bright" ? "bg-stone-900/50" : "bg-stone-900/25";
|
||||
const hoverBg = feature.tone === "bright" ? "hover:bg-stone-900/85" : "hover:bg-stone-900/55";
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={ref}
|
||||
initial={{ opacity: 0, y: 48 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 48 }}
|
||||
initial={{ opacity: 0, y: 32 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 32 }}
|
||||
transition={{
|
||||
duration: 0.7,
|
||||
delay: (index % 4) * 0.1 + Math.floor(index / 4) * 0.08,
|
||||
duration: 0.6,
|
||||
delay: (index % 4) * 0.08 + Math.floor(index / 4) * 0.06,
|
||||
ease: [0.22, 0.61, 0.36, 1],
|
||||
}}
|
||||
className={`relative flex flex-col gap-5 rounded-2xl bg-stone-900/40 p-7 overflow-hidden group transition-all duration-500 hover:bg-stone-900/80 hover:translate-y-[-4px] hover:shadow-2xl ${feature.accentGlow} ${feature.accentHover}`}
|
||||
className={`relative flex flex-col gap-5 rounded-2xl ${cardBg} ${hoverBg} p-7 overflow-hidden group transition-all duration-500 hover:translate-y-[-4px] hover:shadow-[0_24px_60px_-24px_rgba(63,110,58,0.45)] border border-white/[0.04] hover:border-emerald-500/30`}
|
||||
>
|
||||
{/* Animated top accent line */}
|
||||
{/* Top accent rule — hairline line that fills on hover. */}
|
||||
<div className="absolute top-0 left-7 right-7 h-px overflow-hidden">
|
||||
<motion.div
|
||||
className={`h-full ${feature.accentColor}`}
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||
style={{ originX: 0 }}
|
||||
/>
|
||||
<div className="h-full w-full bg-gradient-to-r from-emerald-500/60 to-emerald-500/0 origin-left transform scale-x-30 group-hover:scale-x-100 transition-transform duration-500" />
|
||||
</div>
|
||||
|
||||
{/* Icon with glow on hover */}
|
||||
<div className={`relative w-16 h-16 rounded-2xl flex items-center justify-center transition-all duration-500 ${
|
||||
feature.color === "emerald"
|
||||
? "bg-emerald-900/30 text-emerald-400 group-hover:bg-emerald-900/50 group-hover:text-emerald-300 group-hover:shadow-[0_0_28px_rgba(52,211,153,0.35)]"
|
||||
: "bg-amber-900/30 text-amber-400 group-hover:bg-amber-900/50 group-hover:text-amber-300 group-hover:shadow-[0_0_28px_rgba(251,191,36,0.35)]"
|
||||
}`}>
|
||||
{/* Icon plate */}
|
||||
<div className="relative w-14 h-14 rounded-xl flex items-center justify-center bg-emerald-500/10 text-emerald-300/90 transition-all duration-500 group-hover:bg-emerald-500/20 group-hover:text-emerald-200 group-hover:shadow-[0_0_28px_rgba(63,110,58,0.35)]">
|
||||
{feature.icon}
|
||||
</div>
|
||||
|
||||
{/* Label */}
|
||||
<p className={`text-[9px] sm:text-[10px] font-bold uppercase tracking-[0.2em] transition-colors duration-300 ${
|
||||
feature.color === "emerald"
|
||||
? "text-emerald-600/60 group-hover:text-emerald-400/80"
|
||||
: "text-amber-600/60 group-hover:text-amber-400/80"
|
||||
}`}>
|
||||
<p className="text-[10px] font-bold uppercase tracking-[0.25em] text-emerald-400/70 transition-colors duration-300 group-hover:text-emerald-300">
|
||||
{feature.label}
|
||||
</p>
|
||||
|
||||
{/* Headline */}
|
||||
<h3 className="text-white font-bold text-[15px] leading-snug tracking-tight">
|
||||
<h3 className="text-white font-bold text-[17px] leading-snug tracking-tight">
|
||||
{feature.headline}
|
||||
</h3>
|
||||
|
||||
{/* Story */}
|
||||
<p className="text-stone-500 text-[12px] leading-relaxed mt-auto">
|
||||
<p className="text-stone-400 text-[13px] leading-relaxed mt-auto">
|
||||
{feature.story}
|
||||
</p>
|
||||
|
||||
{/* Corner decoration */}
|
||||
<div className={`absolute bottom-0 right-0 w-24 h-24 rounded-full blur-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500 ${
|
||||
feature.color === "emerald" ? "bg-emerald-600/10" : "bg-amber-600/10"
|
||||
}`} />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -411,10 +381,13 @@ function WhyTuxedoCorn() {
|
||||
const headerInView = useInView(headerRef, { once: true, margin: "-80px" });
|
||||
|
||||
return (
|
||||
<section className="relative bg-stone-950 py-32 overflow-hidden">
|
||||
{/* Atmospheric glow */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_80%_30%,rgba(16,100,50,0.15)_0%,transparent_50%)]" />
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_20%_80%,rgba(120,75,20,0.12)_0%,transparent_50%)]" />
|
||||
<section className="relative bg-stone-950 py-28 sm:py-32 overflow-hidden">
|
||||
{/* Single soft golden-hour glow — replaces the prior emerald/amber
|
||||
two-tone gradient which made the section read as kaleidoscope. */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 bg-[radial-gradient(ellipse_at_50%_30%,rgba(63,110,58,0.16)_0%,transparent_55%)]"
|
||||
/>
|
||||
|
||||
<LayoutContainer>
|
||||
{/* Header */}
|
||||
@@ -423,7 +396,7 @@ function WhyTuxedoCorn() {
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={headerInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 12 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-[11px] font-bold uppercase tracking-[0.3em] text-emerald-600/70 mb-6"
|
||||
className="text-[11px] font-bold uppercase tracking-[0.3em] text-emerald-400/80 mb-6"
|
||||
>
|
||||
Why Olathe Sweet™
|
||||
</motion.p>
|
||||
@@ -431,7 +404,7 @@ function WhyTuxedoCorn() {
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={headerInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 16 }}
|
||||
transition={{ duration: 0.7, delay: 0.1 }}
|
||||
className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-black tracking-tight text-white leading-[1.05] mb-6"
|
||||
className="text-4xl sm:text-5xl md:text-6xl font-black tracking-[-0.02em] text-white leading-[1.05] mb-6"
|
||||
>
|
||||
Why Choose<br className="hidden sm:block" />Tuxedo Corn
|
||||
</motion.h2>
|
||||
@@ -439,22 +412,21 @@ function WhyTuxedoCorn() {
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={headerInView ? { scaleX: 1 } : { scaleX: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.3 }}
|
||||
className="mx-auto mt-1 mb-7 h-px w-16 bg-emerald-600/60"
|
||||
className="mx-auto mt-1 mb-7 h-px w-16 bg-gradient-to-r from-emerald-500 to-amber-400/70"
|
||||
style={{ originX: 0 }}
|
||||
/>
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={headerInView ? { opacity: 1 } : { opacity: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="text-stone-400 text-base leading-relaxed"
|
||||
className="text-stone-400 text-base sm:text-lg leading-relaxed"
|
||||
>
|
||||
Colorado has trusted Olathe Sweet at their tables for over four decades. Here is why.
|
||||
Colorado has trusted Olathe Sweet at their tables for over four decades. Here's why.
|
||||
</motion.p>
|
||||
</div>
|
||||
|
||||
{/* Asymmetric masonry grid with staggered offsets */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-5">
|
||||
{/* Row 1: tall, normal, tall, normal — with vertical offset stagger */}
|
||||
<div>
|
||||
<FeatureCard feature={FEATURES[0]} index={0} />
|
||||
</div>
|
||||
@@ -468,7 +440,6 @@ function WhyTuxedoCorn() {
|
||||
<FeatureCard feature={FEATURES[3]} index={3} />
|
||||
</div>
|
||||
|
||||
{/* Row 2: normal, tall, normal, tall — offset in opposite direction */}
|
||||
<div className="lg:mt-[-60px] hidden lg:block">
|
||||
<FeatureCard feature={FEATURES[4]} index={4} />
|
||||
</div>
|
||||
@@ -483,16 +454,22 @@ function WhyTuxedoCorn() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<motion.p
|
||||
{/* Footer — provenance record strip (this is the editorial anchor). */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center text-[10px] font-medium uppercase tracking-[0.25em] text-stone-700 mt-16"
|
||||
className="mt-16 text-center text-[10px] font-medium uppercase tracking-[0.3em] text-stone-500"
|
||||
>
|
||||
Olathe Sweet™ · Olathe, Colorado
|
||||
</motion.p>
|
||||
<span className="text-emerald-400/80">Family-Grown</span>
|
||||
<span className="mx-3 text-stone-700">·</span>
|
||||
<span>Olathe, Colorado</span>
|
||||
<span className="mx-3 text-stone-700">·</span>
|
||||
<span>5,360 ft altitude</span>
|
||||
<span className="mx-3 text-stone-700">·</span>
|
||||
<span>Est. 1982</span>
|
||||
</motion.div>
|
||||
</LayoutContainer>
|
||||
</section>
|
||||
);
|
||||
@@ -701,93 +678,91 @@ function ProductsSection({
|
||||
// ── Story section ───────────────────────────────────────────────────
|
||||
function StorySection() {
|
||||
return (
|
||||
<section id="story" className="py-32 bg-stone-950 relative overflow-hidden">
|
||||
{/* Parallax decorative elements */}
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<ParallaxLayer speed={0.3} className="absolute top-1/4 left-1/4 w-96 h-96 rounded-full opacity-5" style={{
|
||||
background: "radial-gradient(circle, rgba(16,185,129,0.3) 0%, transparent 70%)",
|
||||
filter: "blur(8px)",
|
||||
}}>
|
||||
<div />
|
||||
</ParallaxLayer>
|
||||
<ParallaxLayer speed={0.5} className="absolute bottom-1/4 right-1/4 w-64 h-64 rounded-full opacity-5" style={{
|
||||
background: "radial-gradient(circle, rgba(255,215,0,0.3) 0%, transparent 70%)",
|
||||
filter: "blur(8px)",
|
||||
}}>
|
||||
<div />
|
||||
</ParallaxLayer>
|
||||
</div>
|
||||
|
||||
{/* Large background text for parallax depth */}
|
||||
<div className="absolute inset-0 flex items-center justify-center overflow-hidden opacity-5 pointer-events-none">
|
||||
<ParallaxLayer speed={0.2}>
|
||||
<span className="text-[20vw] font-black text-white leading-none">SINCE</span>
|
||||
</ParallaxLayer>
|
||||
</div>
|
||||
<section id="story" className="py-28 sm:py-32 bg-stone-950 relative overflow-hidden">
|
||||
{/* Single soft golden-hour glow behind the headline. */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 bg-[radial-gradient(ellipse_at_50%_55%,rgba(232,163,23,0.10)_0%,transparent_55%)]"
|
||||
/>
|
||||
|
||||
<LayoutContainer>
|
||||
<div className="text-center max-w-3xl mx-auto relative z-10">
|
||||
<FadeOnScroll from="up" className="mb-8">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-widest text-emerald-400/60 mb-4">Our Story</p>
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.3em] text-amber-300/80 mb-4">
|
||||
Our Story
|
||||
</p>
|
||||
</FadeOnScroll>
|
||||
|
||||
<FadeOnScroll from="up" delay={0.1}>
|
||||
<h2 className="text-4xl md:text-5xl lg:text-6xl font-black tracking-tight text-white leading-[1.05] mb-8">
|
||||
<h2 className="text-4xl md:text-5xl lg:text-6xl font-black tracking-[-0.02em] text-white leading-[1.05] mb-8">
|
||||
Three Generations of<br className="hidden md:block" /> Sweet Corn Excellence
|
||||
</h2>
|
||||
</FadeOnScroll>
|
||||
|
||||
<FadeOnScroll from="up" delay={0.2}>
|
||||
<div className="mx-auto mt-8 mb-10 h-px w-16 bg-gradient-to-r from-emerald-600 to-amber-500" />
|
||||
<div className="mx-auto mt-8 mb-10 h-px w-16 bg-gradient-to-r from-emerald-500 to-amber-400" />
|
||||
</FadeOnScroll>
|
||||
|
||||
<FadeOnScroll from="up" delay={0.3}>
|
||||
<p className="text-stone-400 leading-relaxed text-lg md:text-xl max-w-2xl mx-auto">
|
||||
<p className="text-stone-300 leading-relaxed text-lg md:text-xl max-w-2xl mx-auto">
|
||||
Tuxedo Corn is the exclusive grower and shipper of Olathe Sweet Sweet Corn — developed for Colorado's high-altitude mountain climate and grown by the same family for over 40 years.
|
||||
</p>
|
||||
</FadeOnScroll>
|
||||
|
||||
{/* Stats with counter animation */}
|
||||
{/* Pull-quote from the family's history. The previous decorative
|
||||
giant "SINCE" parallax text and duplicate stats counter
|
||||
(already shown in the hero) were removed — both read as
|
||||
template decoration. The timeline below carries the same
|
||||
trust signal as a real record. */}
|
||||
<FadeOnScroll from="up" delay={0.4}>
|
||||
<div className="flex items-center justify-center gap-12 md:gap-16 mt-16 flex-wrap">
|
||||
<ol className="mt-20 grid grid-cols-1 sm:grid-cols-3 gap-10 sm:gap-6 max-w-3xl mx-auto text-left">
|
||||
{[
|
||||
{ stat: "40", suffix: "+", label: "Years Growing" },
|
||||
{ stat: "3", suffix: "", label: "Generations" },
|
||||
{ stat: "100", suffix: "%", label: "Hand-Picked" },
|
||||
{
|
||||
year: "1982",
|
||||
head: "A seed saved",
|
||||
body: "The original Olathe Sweet parent line was kept by a farmer who believed his valley could grow sugar in a husk.",
|
||||
},
|
||||
{
|
||||
year: "2003",
|
||||
head: "A field retired, then revived",
|
||||
body: "After a five-year pause the family brought the same seed back to the same soil — and the sweetness came back with it.",
|
||||
},
|
||||
{
|
||||
year: "Today",
|
||||
head: "Three generations on the row",
|
||||
body: "Grandparents, parents, and kids all work the summer pick — a workforce of about sixty hands during peak season.",
|
||||
},
|
||||
].map((item) => (
|
||||
<div key={item.label} className="text-center">
|
||||
<div className="text-4xl md:text-5xl lg:text-6xl font-black text-white" style={{ textShadow: "0 4px 30px rgba(16,185,129,0.3)" }}>
|
||||
<span className="counter-animate" data-target={parseInt(item.stat, 10)}>0</span>
|
||||
<span style={{ color: "#10b981" }}>{item.suffix}</span>
|
||||
</div>
|
||||
<div className="mt-2 text-[10px] uppercase tracking-[0.2em] text-stone-500">
|
||||
{item.label}
|
||||
</div>
|
||||
</div>
|
||||
<li key={item.year} className="relative pl-6">
|
||||
<span className="absolute left-0 top-1 bottom-1 w-px bg-gradient-to-b from-emerald-500/60 to-amber-400/40" />
|
||||
<p className="text-[11px] font-bold uppercase tracking-[0.25em] text-emerald-300/80 mb-2 tabular-nums">
|
||||
{item.year}
|
||||
</p>
|
||||
<h3 className="text-white font-bold text-base tracking-tight mb-2">
|
||||
{item.head}
|
||||
</h3>
|
||||
<p className="text-stone-400 text-sm leading-relaxed">
|
||||
{item.body}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
</ol>
|
||||
</FadeOnScroll>
|
||||
|
||||
<FadeOnScroll from="up" delay={0.5}>
|
||||
<div className="mt-16">
|
||||
<Link
|
||||
href="/tuxedo/about"
|
||||
className="group inline-flex items-center gap-3 rounded-full px-10 py-4 text-sm font-bold transition-all duration-300"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #059669 0%, #10b981 100%)",
|
||||
boxShadow: "0 8px 32px rgba(16, 185, 129, 0.3), inset 0 1px 0 rgba(255,255,255,0.2)",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(-3px)";
|
||||
e.currentTarget.style.boxShadow = "0 12px 40px rgba(16, 185, 129, 0.4), inset 0 1px 0 rgba(255,255,255,0.2)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(0)";
|
||||
e.currentTarget.style.boxShadow = "0 8px 32px rgba(16, 185, 129, 0.3), inset 0 1px 0 rgba(255,255,255,0.2)";
|
||||
}}
|
||||
className="group inline-flex items-center gap-3 rounded-full bg-gradient-to-br from-emerald-700 to-emerald-600 px-10 py-4 text-sm font-bold text-white shadow-[0_8px_32px_rgba(16,185,129,0.3),inset_0_1px_0_rgba(255,255,255,0.2)] hover:-translate-y-0.5 hover:shadow-[0_12px_40px_rgba(16,185,129,0.4),inset_0_1px_0_rgba(255,255,255,0.2)] transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-300 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-950"
|
||||
>
|
||||
<span>Read Our Story</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}>
|
||||
<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="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
@@ -804,8 +779,6 @@ export default function TuxedoPage() {
|
||||
|
||||
// Scope ref for GSAP context (fixes "invalid scope"/missing targets by providing explicit scope; selectors now limited to page)
|
||||
const pageScopeRef = useRef<HTMLDivElement>(null);
|
||||
// hero image url is read once from settings and never re-rendered; keep as ref to preserve original behavior
|
||||
const heroImageUrlRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
@@ -821,7 +794,6 @@ export default function TuxedoPage() {
|
||||
|
||||
if (settingsResult.success && settingsResult.settings) {
|
||||
const s = settingsResult.settings;
|
||||
heroImageUrlRef.current = s.hero_image_url ?? null;
|
||||
dispatch({
|
||||
type: "SET_SETTINGS",
|
||||
wholesaleEnabled: settingsResult.wholesaleEnabled ?? false,
|
||||
@@ -832,6 +804,8 @@ export default function TuxedoPage() {
|
||||
showSchedulePdf: s.show_schedule_pdf ?? true,
|
||||
showWholesaleLink: s.show_wholesale_link ?? true,
|
||||
heroTagline: s.hero_tagline ?? null,
|
||||
heroImageUrl: s.hero_image_url ?? null,
|
||||
heroVideoUrl: s.hero_video_url ?? null,
|
||||
customFooterText: s.custom_footer_text ?? null,
|
||||
contactEmail: s.email ?? null,
|
||||
contactPhone: s.phone ?? null,
|
||||
@@ -1001,6 +975,8 @@ export default function TuxedoPage() {
|
||||
"Premium Olathe Sweet™ sweet corn — hand-picked at peak freshness from our family farm in Colorado."
|
||||
}
|
||||
olatheSweetLogoUrl={state.olatheSweetLogoUrl}
|
||||
videoUrl={state.heroVideoUrl}
|
||||
posterUrl={state.heroImageUrl}
|
||||
primaryButton="Find a Stop"
|
||||
secondaryButton="Our Story"
|
||||
onPrimaryClick={scrollToStops}
|
||||
|
||||
Reference in New Issue
Block a user