ffdc80e60e
Deploy to route.crispygoat.com / deploy (push) Successful in 5m11s
Layers on top of origin/main's existing editorial primitives. Replaces
the cinematic video hero with a typographic 'cover spread' and rebuilds
the page composition around numbered editorial sections.
Cover spread (TuxedoVideoHero):
- Newspaper-style masthead with location, altitude, weather, date
- Massive display headline with oversize 'XL' page numeral in margin
- Cover photo plate with crop marks, photo credit, plate number
- Floating marginalia (vertical text) drifting in on scroll
- Slow horizontal ticker with harvest facts at bottom
Page composition (page.tsx) — numbered editorial sections:
№ 00 Editor's Note two-column preamble, drop cap, pull quote
№ 01 The Almanac 6 specimen cards with hover-reveal notes
№ 02 The Cover Story photo + sticky stat marginalia
№ 03 The 2026 Tour typeset schedule with stat strip
№ 04 This Week's Lot inventory-style product cards w/ lot #s
№ 05 Chronicle of the Row 3-gen timeline on dark plate
№ 06 Letter from the Field letterpress subscribe card w/ deckle edge
Colophon back-of-book fine print
Adds editorial primitives to globals.css: section numbering, drop caps,
specimen tabs, pull quotes, crop marks, page numerals, ticker,
deckle-edge subscribe, almanac-paper backgrounds.
Removes dark fixed gradient from tuxedo/layout.tsx so per-section
.almanac-paper backgrounds win.
1233 lines
52 KiB
TypeScript
1233 lines
52 KiB
TypeScript
"use client";
|
|
|
|
import { useReducer, useEffect, useRef } from "react";
|
|
import Link from "next/link";
|
|
import Image from "next/image";
|
|
import { m as motion, useInView } from "framer-motion";
|
|
import TuxedoVideoHero from "@/components/storefront/TuxedoVideoHero";
|
|
import ProductCard from "@/components/storefront/ProductCard";
|
|
import PaginatedStops from "@/components/storefront/PaginatedStops";
|
|
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
|
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
|
import BrandStylesProvider from "@/components/storefront/BrandStylesProvider";
|
|
import { supabase } from "@/lib/supabase";
|
|
import { getBrandSettingsPublic } from "@/actions/brand-settings";
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// TUXEDO PAGE — Field Almanac composition
|
|
//
|
|
// Section order:
|
|
// 0. Cover spread → TuxedoVideoHero (typographic masthead)
|
|
// 1. Editor's note → two-column preamble with pull quote
|
|
// 2. I. The Almanac → specimen cards (altitude, swing, hands, …)
|
|
// 3. II. The Cover Story → editorial photo + marginalia
|
|
// 4. III. The 2026 Tour → stops as typeset schedule
|
|
// 5. IV. This Week's Lot → inventory-style product cards
|
|
// 6. V. Chronicle of the Row → three-generation timeline
|
|
// 7. Letter from the Field → closing subscribe (letterpress card)
|
|
// 8. Colophon → back-of-book fine print
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
function scrollToStops() {
|
|
document.getElementById("tour")?.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
function scrollToStory() {
|
|
document.getElementById("chronicle")?.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
|
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
type Brand = { id: string; name: string; slug: string };
|
|
type Stop = {
|
|
id: string;
|
|
city: string;
|
|
state: string;
|
|
date: string;
|
|
time: string;
|
|
location: string;
|
|
slug: string;
|
|
brand_id: string;
|
|
};
|
|
type Product = {
|
|
id: string;
|
|
name: string;
|
|
description: string | null;
|
|
price: number;
|
|
type: string;
|
|
image_url: string | null;
|
|
brand_id: string;
|
|
is_taxable?: boolean;
|
|
pickup_type?: "scheduled_stop" | "shed";
|
|
};
|
|
|
|
// ── State machine ────────────────────────────────────────────────────────────
|
|
type State = {
|
|
brand: Brand | null;
|
|
stops: Stop[];
|
|
products: Product[];
|
|
wholesaleEnabled: boolean;
|
|
logoUrl: string | null;
|
|
logoUrlDark: string | null;
|
|
olatheSweetLogoUrl: string | null;
|
|
olatheSweetLogoUrlDark: string | null;
|
|
showSchedulePdf: boolean;
|
|
showWholesaleLink: boolean;
|
|
heroTagline: string | null;
|
|
isAdmin: boolean;
|
|
customFooterText: string | null;
|
|
contactEmail: string | null;
|
|
contactPhone: string | null;
|
|
brandPrimaryColor: string | null;
|
|
brandBgColor: string | null;
|
|
brandTextColor: string | null;
|
|
};
|
|
type Action =
|
|
| { type: "SET_BRAND"; brand: Brand | null }
|
|
| { type: "SET_STOPS"; stops: Stop[] }
|
|
| { type: "SET_PRODUCTS"; products: Product[] }
|
|
| {
|
|
type: "SET_SETTINGS";
|
|
wholesaleEnabled: boolean;
|
|
logoUrl: string | null;
|
|
logoUrlDark: string | null;
|
|
olatheSweetLogoUrl: string | null;
|
|
olatheSweetLogoUrlDark: string | null;
|
|
showSchedulePdf: boolean;
|
|
showWholesaleLink: boolean;
|
|
heroTagline: string | null;
|
|
customFooterText: string | null;
|
|
contactEmail: string | null;
|
|
contactPhone: string | null;
|
|
brandPrimaryColor: string | null;
|
|
brandBgColor: string | null;
|
|
brandTextColor: string | null;
|
|
}
|
|
| { type: "SET_IS_ADMIN"; isAdmin: boolean };
|
|
|
|
const initialState: State = {
|
|
brand: null,
|
|
stops: [],
|
|
products: [],
|
|
wholesaleEnabled: false,
|
|
logoUrl: null,
|
|
logoUrlDark: null,
|
|
olatheSweetLogoUrl: null,
|
|
olatheSweetLogoUrlDark: null,
|
|
showSchedulePdf: true,
|
|
showWholesaleLink: true,
|
|
heroTagline: null,
|
|
isAdmin: false,
|
|
customFooterText: null,
|
|
contactEmail: null,
|
|
contactPhone: null,
|
|
brandPrimaryColor: null,
|
|
brandBgColor: null,
|
|
brandTextColor: null,
|
|
};
|
|
|
|
function reducer(state: State, action: Action): State {
|
|
switch (action.type) {
|
|
case "SET_BRAND": return { ...state, brand: action.brand };
|
|
case "SET_STOPS": return { ...state, stops: action.stops };
|
|
case "SET_PRODUCTS": return { ...state, products: action.products };
|
|
case "SET_SETTINGS": return {
|
|
...state,
|
|
wholesaleEnabled: action.wholesaleEnabled,
|
|
logoUrl: action.logoUrl,
|
|
logoUrlDark: action.logoUrlDark,
|
|
olatheSweetLogoUrl: action.olatheSweetLogoUrl,
|
|
olatheSweetLogoUrlDark: action.olatheSweetLogoUrlDark,
|
|
showSchedulePdf: action.showSchedulePdf,
|
|
showWholesaleLink: action.showWholesaleLink,
|
|
heroTagline: action.heroTagline,
|
|
customFooterText: action.customFooterText,
|
|
contactEmail: action.contactEmail,
|
|
contactPhone: action.contactPhone,
|
|
brandPrimaryColor: action.brandPrimaryColor,
|
|
brandBgColor: action.brandBgColor,
|
|
brandTextColor: action.brandTextColor,
|
|
};
|
|
case "SET_IS_ADMIN": return { ...state, isAdmin: action.isAdmin };
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 1. EDITOR'S NOTE
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function EditorsNote() {
|
|
return (
|
|
<section className="relative almanac-paper py-20 md:py-32 overflow-hidden">
|
|
<div className="mx-auto max-w-[1480px] px-5 sm:px-8 md:px-14">
|
|
<div className="grid grid-cols-12 gap-6 md:gap-10">
|
|
<div className="col-span-12 md:col-span-3 flex md:block items-center justify-between">
|
|
<div>
|
|
<div className="almanac-section-number text-[#1a1814]/55">
|
|
№ 00 — Editor’s Note
|
|
</div>
|
|
<div className="almanac-eyebrow text-[#1a1814]/55 mt-3">From the Editor</div>
|
|
</div>
|
|
<div className="md:mt-12 almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#1a1814]/55">
|
|
MMXXVI · 08
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-span-12 md:col-span-9 grid md:grid-cols-2 gap-x-12 lg:gap-x-20 gap-y-8">
|
|
<div>
|
|
<p className="almanac-dropcap almanac-body text-[#1a1814]/85 text-lg md:text-[19px] leading-[1.55]">
|
|
<span>
|
|
A bulletin, not a brochure. What follows is a partial
|
|
record of how a particular valley grows a particular ear
|
|
of corn, picked by particular hands, on a particular
|
|
morning. We don’t have a slogan; we have a field.
|
|
</span>
|
|
</p>
|
|
<p className="almanac-body text-[#1a1814]/75 text-base md:text-[17px] leading-[1.65] mt-5">
|
|
The Olathe Sweet parent line was rescued from a retiring
|
|
farmer who believed his valley could grow sugar in a husk.
|
|
We brought it back to the same soil, learned how it liked
|
|
the water, and kept the seed — by hand, ear by ear — for
|
|
forty summers.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="almanac-body text-[#1a1814]/75 text-base md:text-[17px] leading-[1.65]">
|
|
You won’t find us at a farmers’ market in a
|
|
big city. We don’t wholesale to supermarkets. Every
|
|
ear you eat came off a truck that left the shed that
|
|
morning, headed for a stop we planned with people we know
|
|
by name.
|
|
</p>
|
|
<p className="almanac-body text-[#1a1814]/75 text-base md:text-[17px] leading-[1.65] mt-5">
|
|
What follows in this issue: the numbers that matter, the
|
|
stops on the 2026 tour, the lot for this week, and a short
|
|
chronicle of how we got here. Read at your leisure. Eat
|
|
with butter and salt.
|
|
</p>
|
|
|
|
<div className="mt-10 flex items-center gap-4">
|
|
<SignatureSvg />
|
|
<div>
|
|
<div className="almanac-italic text-[#1a1814] text-base">
|
|
John Harold
|
|
</div>
|
|
<div className="almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#1a1814]/55 mt-0.5">
|
|
Editor · Second Generation
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-16 md:mt-24 grid grid-cols-12 gap-6">
|
|
<div className="col-span-12 md:col-start-3 md:col-span-8">
|
|
<hr className="almanac-rule mb-8" />
|
|
<blockquote className="almanac-pullquote text-[#1a1814] text-[26px] md:text-[40px] lg:text-[52px] leading-[1.05] text-center">
|
|
“The ear in the photograph was picked the morning it was
|
|
shot. The silk on the right is still damp. We don’t
|
|
airbrush the field because we don’t need to.”
|
|
</blockquote>
|
|
<hr className="almanac-rule mt-8" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function SignatureSvg() {
|
|
return (
|
|
<svg
|
|
className="almanac-flourish"
|
|
width="80"
|
|
height="32"
|
|
viewBox="0 0 80 32"
|
|
fill="none"
|
|
stroke="#1a1814"
|
|
strokeWidth="1.4"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
aria-hidden
|
|
>
|
|
<path d="M2 22 C 8 8, 14 28, 22 14 C 28 6, 34 24, 42 16 C 50 8, 56 26, 64 14" />
|
|
<path d="M58 24 L 70 24" />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 2. I. THE ALMANAC — Specimen cards
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
interface AlmanacSpecimen {
|
|
number: string;
|
|
title: string;
|
|
unit: string;
|
|
value: string;
|
|
caption: string;
|
|
detail: string;
|
|
}
|
|
|
|
const SPECIMENS: AlmanacSpecimen[] = [
|
|
{
|
|
number: "001",
|
|
title: "Altitude",
|
|
unit: "ft. above sea",
|
|
value: "5,360",
|
|
caption: "Uncompahgre Valley floor",
|
|
detail:
|
|
"Olathe Sweet was bred for elevation. The thin air at five thousand feet cooks the husk by day and the cold drains back in by night — the diurnal swing does the work no greenhouse can.",
|
|
},
|
|
{
|
|
number: "002",
|
|
title: "Diurnal Swing",
|
|
unit: "°F avg",
|
|
value: "38",
|
|
caption: "Day · Night differential",
|
|
detail:
|
|
"Daytime highs in the mid-eighties, nighttime lows in the upper forties. The kernel drinks sugar by sun and locks it in by cold. That’s the whole trick.",
|
|
},
|
|
{
|
|
number: "003",
|
|
title: "Hands at Peak",
|
|
unit: "people · daily",
|
|
value: "~60",
|
|
caption: "Including three generations",
|
|
detail:
|
|
"Grandparents on the husk trailer. Parents on the cooler line. Kids sorting by ear size in the shed. About sixty of us during the first two weeks of August.",
|
|
},
|
|
{
|
|
number: "004",
|
|
title: "Variety",
|
|
unit: "parent line",
|
|
value: "1982",
|
|
caption: "Olathe Sweet · original",
|
|
detail:
|
|
"The seed has never been hybridized in a laboratory. Forty summers of careful selection in the same soil. Nothing added. Nothing altered.",
|
|
},
|
|
{
|
|
number: "005",
|
|
title: "Time to Pick",
|
|
unit: "minutes",
|
|
value: "90",
|
|
caption: "field to cooler",
|
|
detail:
|
|
"A picker drops an ear into the sling; a runner carries it to the cooler truck; the cooler truck drives to the shed. Ninety minutes, no more.",
|
|
},
|
|
{
|
|
number: "006",
|
|
title: "Rows Under Pivot",
|
|
unit: "acres",
|
|
value: "60",
|
|
caption: "single farm · single family",
|
|
detail:
|
|
"Sixty acres of sweet corn and sixty of rotation. We don’t outsource, we don’t co-pack, we don’t re-brand anyone else’s crop. The name on the cooler is the name on the deed.",
|
|
},
|
|
];
|
|
|
|
function TheAlmanac() {
|
|
return (
|
|
<section className="relative almanac-paper--bone py-24 md:py-36 overflow-hidden">
|
|
<div className="mx-auto max-w-[1480px] px-5 sm:px-8 md:px-14">
|
|
<div className="grid grid-cols-12 gap-6 mb-12 md:mb-20">
|
|
<div className="col-span-12 md:col-span-4">
|
|
<div className="almanac-section-number text-[#1a1814]/55">
|
|
№ 01 — The Almanac
|
|
</div>
|
|
<div className="almanac-eyebrow text-[#1a1814]/55 mt-3">Specimens & Data</div>
|
|
</div>
|
|
<div className="col-span-12 md:col-span-8">
|
|
<h2 className="almanac-display text-[#1a1814] text-[44px] sm:text-[60px] md:text-[84px] lg:text-[110px]">
|
|
You can taste<br />the altitude.
|
|
</h2>
|
|
<div className="grid md:grid-cols-2 gap-8 mt-10">
|
|
<p className="almanac-body text-[#1a1814]/80 text-base md:text-[17px] leading-[1.65]">
|
|
Six numbers describe this farm better than any slogan. We
|
|
keep them where we can see them — in the shed, in the
|
|
cooler, on the side of the picking trailer.
|
|
</p>
|
|
<p className="almanac-body text-[#1a1814]/75 text-base md:text-[16px] leading-[1.65]">
|
|
Hover any specimen to read the field note. None of these
|
|
are marketing — they’re what we measure every
|
|
morning between picking and packing.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr className="almanac-rule--hair mb-12 md:mb-16" />
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5 md:gap-6">
|
|
{SPECIMENS.map((s) => (
|
|
<SpecimenCard key={s.number} s={s} />
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-14 flex flex-wrap items-baseline justify-between gap-y-3">
|
|
<div className="almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#1a1814]/55">
|
|
↑ Recorded by hand · audit on file at the shed
|
|
</div>
|
|
<div className="almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#1a1814]/55">
|
|
↘ continued on page 03
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function SpecimenCard({ s }: { s: AlmanacSpecimen }) {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const inView = useInView(ref, { once: true, margin: "-60px" });
|
|
return (
|
|
<motion.div
|
|
ref={ref}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={inView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
|
|
transition={{ duration: 0.7, ease: [0.22, 0.61, 0.36, 1] }}
|
|
className="almanac-card group p-6 md:p-7 cursor-default"
|
|
>
|
|
<div className="flex items-start justify-between mb-5">
|
|
<span className="almanac-tab">No. {s.number}</span>
|
|
<span className="almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#1a1814]/50">
|
|
{s.title}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-baseline gap-3 mb-1">
|
|
<span className="almanac-display text-[#1a1814] text-[64px] md:text-[76px] leading-none">
|
|
{s.value}
|
|
</span>
|
|
<span className="almanac-mono text-[10.5px] tracking-[0.18em] uppercase text-[#1a1814]/55 max-w-[80px]">
|
|
{s.unit}
|
|
</span>
|
|
</div>
|
|
<p className="almanac-italic text-[#1a1814]/70 text-sm md:text-[15px] mb-5">
|
|
{s.caption}
|
|
</p>
|
|
|
|
<hr className="almanac-rule--hair mb-4" />
|
|
<p
|
|
className="almanac-body text-[#1a1814]/75 text-[14px] leading-[1.55] max-h-0 overflow-hidden opacity-0 group-hover:max-h-[140px] group-hover:opacity-100 transition-all duration-500 ease-out"
|
|
dangerouslySetInnerHTML={{ __html: s.detail }}
|
|
/>
|
|
<p className="almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#1a1814]/40 group-hover:opacity-0 transition-opacity duration-300">
|
|
Hover for note
|
|
</p>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 3. II. THE COVER STORY
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function TheCoverStory() {
|
|
return (
|
|
<section className="relative almanac-paper py-24 md:py-32 overflow-hidden">
|
|
<div aria-hidden className="almanac-pagenum absolute top-20 right-0 select-none">
|
|
03
|
|
</div>
|
|
|
|
<div className="mx-auto max-w-[1480px] px-5 sm:px-8 md:px-14 relative">
|
|
<div className="grid grid-cols-12 gap-6 mb-12 md:mb-16">
|
|
<div className="col-span-12 md:col-span-4">
|
|
<div className="almanac-section-number text-[#1a1814]/55">
|
|
№ 02 — The Cover Story
|
|
</div>
|
|
<div className="almanac-eyebrow text-[#1a1814]/55 mt-3">A single ear</div>
|
|
</div>
|
|
<div className="col-span-12 md:col-span-8">
|
|
<div className="almanac-dateline text-[#1a1814]/55 mb-3">
|
|
Dateline · Olathe, Colorado · 6:14 AM
|
|
</div>
|
|
<h2 className="almanac-display text-[#1a1814] text-[36px] sm:text-[50px] md:text-[72px] lg:text-[90px]">
|
|
One ear.<br />One morning.
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-12 gap-6 md:gap-10">
|
|
<aside className="col-span-12 lg:col-span-3 order-2 lg:order-1">
|
|
<div className="lg:sticky lg:top-32 space-y-8">
|
|
<div>
|
|
<div className="almanac-eyebrow text-[#1a1814]/55">Today’s lot</div>
|
|
<div className="almanac-display-tight text-3xl text-[#1a1814] mt-2">
|
|
2,440 ears
|
|
</div>
|
|
<div className="almanac-mono text-[10.5px] tracking-[0.2em] uppercase text-[#1a1814]/55 mt-1">
|
|
picked · packed · loaded by 9:30
|
|
</div>
|
|
</div>
|
|
<hr className="almanac-rule--hair" />
|
|
<div>
|
|
<div className="almanac-eyebrow text-[#1a1814]/55">Average ear</div>
|
|
<div className="almanac-display-tight text-3xl text-[#1a1814] mt-2">
|
|
8″ long
|
|
</div>
|
|
<div className="almanac-mono text-[10.5px] tracking-[0.2em] uppercase text-[#1a1814]/55 mt-1">
|
|
18 rows · tip-fill to the cap
|
|
</div>
|
|
</div>
|
|
<hr className="almanac-rule--hair" />
|
|
<div>
|
|
<div className="almanac-eyebrow text-[#1a1814]/55">Pickers</div>
|
|
<div className="almanac-display-tight text-3xl text-[#1a1814] mt-2">
|
|
14 hands
|
|
</div>
|
|
<div className="almanac-mono text-[10.5px] tracking-[0.2em] uppercase text-[#1a1814]/55 mt-1">
|
|
Three generations · one row at a time
|
|
</div>
|
|
</div>
|
|
<hr className="almanac-rule--hair" />
|
|
<div>
|
|
<div className="almanac-eyebrow text-[#1a1814]/55">Window</div>
|
|
<div className="almanac-display-tight text-3xl text-[#1a1814] mt-2">
|
|
4 — 9 AM
|
|
</div>
|
|
<div className="almanac-mono text-[10.5px] tracking-[0.2em] uppercase text-[#1a1814]/55 mt-1">
|
|
before the sugar turns to starch
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<figure className="col-span-12 lg:col-span-9 order-1 lg:order-2">
|
|
<div className="almanac-photo relative w-full aspect-[4/3] md:aspect-[16/10] bg-[#1a1814] shadow-[0_30px_80px_-30px_rgba(26,24,20,0.5)]">
|
|
<Image
|
|
src="https://s3.crispygoat.com/videos/wp-import/images/tuxedo-ear-macro.jpg"
|
|
alt="A single ear of Olathe Sweet corn, partially husked, silk still attached"
|
|
fill
|
|
sizes="(max-width: 1024px) 100vw, 75vw"
|
|
style={{ objectFit: "cover" }}
|
|
className="img"
|
|
/>
|
|
<div className="absolute top-5 left-5 almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#f3ece0]/85">
|
|
Plate 022 · 1:1 macro
|
|
</div>
|
|
<div className="absolute top-5 right-5 almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#f3ece0]/85">
|
|
Silk damp · 6:18 AM
|
|
</div>
|
|
<svg
|
|
className="absolute inset-0 m-auto opacity-40"
|
|
width="60"
|
|
height="60"
|
|
viewBox="0 0 60 60"
|
|
fill="none"
|
|
stroke="#f3ece0"
|
|
strokeWidth="0.8"
|
|
aria-hidden
|
|
>
|
|
<circle cx="30" cy="30" r="14" />
|
|
<path d="M30 4 L30 18 M30 42 L30 56 M4 30 L18 30 M42 30 L56 30" />
|
|
</svg>
|
|
</div>
|
|
|
|
<figcaption className="mt-5 grid grid-cols-12 gap-6">
|
|
<p className="col-span-12 md:col-span-8 almanac-italic text-[#1a1814]/85 text-base md:text-[17px] leading-[1.6]">
|
|
<span className="almanac-smallcaps text-[#1a1814] mr-2">Above.</span>{" "}
|
|
The same ear, picked at 6:14 AM, photographed at 6:18. The
|
|
silk is still wet. The husk was peeled back with a thumb.
|
|
Nothing else was done to it, and nothing has been done to
|
|
it since. This is what an Olathe Sweet looks like at the
|
|
moment it becomes food.
|
|
</p>
|
|
<p className="col-span-12 md:col-span-4 almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#1a1814]/55">
|
|
Photograph — Zane Harold<br />
|
|
Filed — August MMXXVI<br />
|
|
Location — Row 14, Block 3
|
|
</p>
|
|
</figcaption>
|
|
|
|
<div className="mt-16 grid grid-cols-12 gap-6">
|
|
<div className="col-span-12 md:col-start-2 md:col-span-10">
|
|
<p className="almanac-pullquote text-[#1a1814] text-[24px] md:text-[36px] lg:text-[44px] leading-[1.08]">
|
|
“You can’t replicate the diurnal swing in a
|
|
greenhouse. You can’t replicate it on a flat
|
|
field. You can’t replicate it in a hurry.”
|
|
</p>
|
|
<p className="almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#1a1814]/55 mt-5">
|
|
— John Harold · Second Generation
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</figure>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 4. III. THE 2026 TOUR
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function TheTour({ stops, showSchedulePdf }: { stops: Stop[]; showSchedulePdf: boolean }) {
|
|
return (
|
|
<section id="tour" className="relative almanac-paper--bone py-24 md:py-36 overflow-hidden scroll-mt-20">
|
|
<div aria-hidden className="almanac-pagenum absolute top-20 left-0 select-none">
|
|
04
|
|
</div>
|
|
|
|
<div className="mx-auto max-w-[1480px] px-5 sm:px-8 md:px-14 relative">
|
|
<div className="grid grid-cols-12 gap-6 mb-12 md:mb-16">
|
|
<div className="col-span-12 md:col-span-4">
|
|
<div className="almanac-section-number text-[#1a1814]/55">
|
|
№ 03 — The Tour
|
|
</div>
|
|
<div className="almanac-eyebrow text-[#1a1814]/55 mt-3">
|
|
Pickup schedule · 2026
|
|
</div>
|
|
</div>
|
|
<div className="col-span-12 md:col-span-8">
|
|
<h2 className="almanac-display text-[#1a1814] text-[44px] sm:text-[60px] md:text-[84px] lg:text-[110px]">
|
|
The 2026<br />Tour.
|
|
</h2>
|
|
<p className="almanac-body text-[#1a1814]/80 text-base md:text-[18px] leading-[1.55] max-w-xl mt-8">
|
|
We don’t ship to stores. We bring the cooler to a
|
|
corner we know, on a day we’ve confirmed, and we wait
|
|
for you. Find a stop on the list below, or download the
|
|
whole schedule as a one-page PDF.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<hr className="almanac-rule mb-10" />
|
|
|
|
{stops.length === 0 ? (
|
|
<div className="grid grid-cols-12 gap-6">
|
|
<div className="col-span-12 md:col-span-9 almanac-card p-10 md:p-14">
|
|
<div className="almanac-eyebrow text-[#1a1814]/55 mb-3">
|
|
The calendar is open
|
|
</div>
|
|
<h3 className="almanac-display-tight text-3xl md:text-5xl text-[#1a1814]">
|
|
No stops on the calendar yet.
|
|
</h3>
|
|
<p className="almanac-body text-[#1a1814]/75 text-base md:text-[17px] leading-[1.6] mt-5 max-w-2xl">
|
|
The harvest is right around the corner — new pickup stops
|
|
go live weekly. You can preorder from the lot below and
|
|
pick a stop once they’re announced, or download
|
|
the schedule PDF and we’ll pencil you in by hand.
|
|
</p>
|
|
<div className="mt-7 flex flex-col sm:flex-row gap-3 sm:gap-5">
|
|
<Link href="#lot" className="almanac-btn-primary">
|
|
<span>Preorder a Box</span>
|
|
<span aria-hidden>→</span>
|
|
</Link>
|
|
<Link href="/tuxedo/stops" className="almanac-btn-ghost">
|
|
<span>View All Stops</span>
|
|
<span aria-hidden>↗</span>
|
|
</Link>
|
|
{showSchedulePdf && (
|
|
<Link href="/api/tuxedo/schedule-pdf" download className="almanac-btn-ghost">
|
|
<span>Schedule PDF</span>
|
|
<span aria-hidden>↓</span>
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="col-span-12 md:col-span-3 almanac-card p-6 md:p-8">
|
|
<div className="almanac-eyebrow text-[#1a1814]/55 mb-3">
|
|
This Week’s Lot
|
|
</div>
|
|
<div className="almanac-display-tight text-3xl text-[#1a1814]">
|
|
First pick
|
|
</div>
|
|
<div className="almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#1a1814]/55 mt-2">
|
|
expected within 14 days
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 md:gap-10 mb-10">
|
|
{[
|
|
{ v: stops.length.toString().padStart(2, "0"), l: "Stops scheduled" },
|
|
{ v: "4", l: "States on the route" },
|
|
{ v: "84", l: "Days on the road" },
|
|
{ v: "~60", l: "Hands at peak" },
|
|
].map((s) => (
|
|
<div key={s.l}>
|
|
<div className="almanac-display-tight text-[#1a1814] text-5xl md:text-6xl">
|
|
{s.v}
|
|
</div>
|
|
<div className="almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#1a1814]/55 mt-2">
|
|
{s.l}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<hr className="almanac-rule--hair mb-10" />
|
|
<PaginatedStops stops={stops} brandSlug="tuxedo" brandAccent="green" />
|
|
{showSchedulePdf && (
|
|
<div className="mt-10 text-right">
|
|
<Link href="/api/tuxedo/schedule-pdf" download className="almanac-btn-ghost">
|
|
<span>Download Full Schedule</span>
|
|
<span aria-hidden>↓</span>
|
|
</Link>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 5. IV. THIS WEEK'S LOT
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function ThisWeeksLot({
|
|
products,
|
|
olatheSweetLogoUrlDark,
|
|
}: {
|
|
products: Product[];
|
|
olatheSweetLogoUrlDark: string | null;
|
|
}) {
|
|
return (
|
|
<section id="lot" className="relative almanac-paper py-24 md:py-32 overflow-hidden scroll-mt-20">
|
|
<div aria-hidden className="almanac-pagenum absolute top-20 right-0 select-none">
|
|
05
|
|
</div>
|
|
|
|
<div className="mx-auto max-w-[1480px] px-5 sm:px-8 md:px-14 relative">
|
|
<div className="grid grid-cols-12 gap-6 mb-12 md:mb-16">
|
|
<div className="col-span-12 md:col-span-4">
|
|
<div className="almanac-section-number text-[#1a1814]/55">
|
|
№ 04 — This Week’s Lot
|
|
</div>
|
|
<div className="almanac-eyebrow text-[#1a1814]/55 mt-3">
|
|
Inventory · picked this morning
|
|
</div>
|
|
</div>
|
|
<div className="col-span-12 md:col-span-8">
|
|
<h2 className="almanac-display text-[#1a1814] text-[44px] sm:text-[60px] md:text-[84px] lg:text-[110px]">
|
|
Today’s<br />haul.
|
|
</h2>
|
|
<p className="almanac-body text-[#1a1814]/80 text-base md:text-[18px] leading-[1.55] max-w-xl mt-8">
|
|
Preorder for pickup at a stop, or have cooler boxes shipped
|
|
to your door after the season. Limited to what we picked
|
|
this morning — when it’s gone, it’s gone until
|
|
tomorrow.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<hr className="almanac-rule mb-12" />
|
|
|
|
{products.length === 0 ? (
|
|
<div className="almanac-card p-12 text-center">
|
|
<div className="almanac-eyebrow text-[#1a1814]/55">Lot empty</div>
|
|
<p className="almanac-display-tight text-3xl md:text-4xl text-[#1a1814] mt-3">
|
|
The first pick hasn’t come in yet.
|
|
</p>
|
|
<p className="almanac-body text-[#1a1814]/70 mt-3">
|
|
Check back at sunrise. We open the shed at 5 AM and update
|
|
the lot before 7.
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 md:gap-6">
|
|
{products.slice(0, 6).map((p, i) => (
|
|
<LotCard
|
|
key={p.id}
|
|
product={p}
|
|
lotNumber={(i + 1).toString().padStart(3, "0")}
|
|
olatheSweetLogoUrlDark={olatheSweetLogoUrlDark}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{products.length > 6 && (
|
|
<div className="mt-10 flex items-center justify-between almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#1a1814]/55">
|
|
<span>↑ {products.length - 6} more in the catalog</span>
|
|
<Link href="/tuxedo/products" className="almanac-underline">
|
|
See full catalog
|
|
</Link>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function LotCard({
|
|
product,
|
|
lotNumber,
|
|
olatheSweetLogoUrlDark,
|
|
}: {
|
|
product: Product;
|
|
lotNumber: string;
|
|
olatheSweetLogoUrlDark: string | null;
|
|
}) {
|
|
return (
|
|
<div className="almanac-card p-5 md:p-6 group">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<span className="almanac-tab">Lot {lotNumber}</span>
|
|
<span className="almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#1a1814]/55">
|
|
Grade A
|
|
</span>
|
|
</div>
|
|
|
|
<ProductCard
|
|
id={product.id}
|
|
name={product.name}
|
|
description={product.description}
|
|
price={`$${product.price}`}
|
|
type={product.type}
|
|
imageUrl={product.image_url}
|
|
brandSlug="tuxedo"
|
|
brandName="Tuxedo Corn"
|
|
brandId={product.brand_id}
|
|
brandAccent="green"
|
|
is_taxable={product.is_taxable}
|
|
pickup_type={product.pickup_type}
|
|
olatheSweetLogoUrlDark={olatheSweetLogoUrlDark}
|
|
/>
|
|
|
|
<div className="mt-4 pt-4 border-t border-[#1a1814]/15 grid grid-cols-3 gap-2 almanac-mono text-[10px] tracking-[0.18em] uppercase text-[#1a1814]/55">
|
|
<div>
|
|
<div className="text-[#1a1814]">Picked</div>
|
|
Today
|
|
</div>
|
|
<div>
|
|
<div className="text-[#1a1814]">Origin</div>
|
|
Olathe
|
|
</div>
|
|
<div>
|
|
<div className="text-[#1a1814]">Weight</div>
|
|
25–40 lb
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 6. V. CHRONICLE OF THE ROW
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function ChronicleOfTheRow() {
|
|
const entries = [
|
|
{
|
|
year: "1982",
|
|
chapter: "Ch. I",
|
|
title: "A seed saved.",
|
|
body: "The original Olathe Sweet parent line was kept by a retiring farmer who believed his valley could grow sugar in a husk. We bought his first planting — eight rows along the irrigation ditch — and brought it home to the same soil.",
|
|
image:
|
|
"https://s3.crispygoat.com/videos/wp-import/images/corn-field-zane-original.jpg",
|
|
caption: "The original eight rows · Spring 1982 · Olathe, CO",
|
|
},
|
|
{
|
|
year: "2003",
|
|
chapter: "Ch. II",
|
|
title: "A field retired, then revived.",
|
|
body: "After five years of commodity pricing that didn’t pencil, we let the field go fallow. Weeds came up. The seed sat in a coffee can in the shed. In 2003 we replanted the same coffee-can seed in the same soil and the sweetness came back with it.",
|
|
image:
|
|
"https://s3.crispygoat.com/videos/wp-import/images/tuxedo-founder-john-editorial.jpg",
|
|
caption: "John Harold · inspecting the revival · August 2003",
|
|
},
|
|
{
|
|
year: "Today",
|
|
chapter: "Ch. III",
|
|
title: "Three generations on the row.",
|
|
body: "Grandparents on the husk trailer. Parents on the cooler line. Kids sorting by ear size in the shed. About sixty hands during peak — and a tour schedule we plan a year in advance with the families we serve.",
|
|
image:
|
|
"https://s3.crispygoat.com/videos/wp-import/images/tuxedo-field-storm.jpg",
|
|
caption: "The same field · pre-storm · August 2026",
|
|
},
|
|
];
|
|
|
|
return (
|
|
<section id="chronicle" className="relative almanac-paper--ink py-24 md:py-36 overflow-hidden scroll-mt-20">
|
|
<div aria-hidden className="almanac-pagenum absolute top-20 left-0 select-none">
|
|
06
|
|
</div>
|
|
|
|
<div className="mx-auto max-w-[1480px] px-5 sm:px-8 md:px-14 relative">
|
|
<div className="grid grid-cols-12 gap-6 mb-14 md:mb-20">
|
|
<div className="col-span-12 md:col-span-4">
|
|
<div className="almanac-section-number text-[#f3ece0]/55">
|
|
№ 05 — Chronicle
|
|
</div>
|
|
<div className="almanac-eyebrow text-[#f3ece0]/55 mt-3">
|
|
Three generations · one row
|
|
</div>
|
|
</div>
|
|
<div className="col-span-12 md:col-span-8">
|
|
<h2 className="almanac-display text-[#f3ece0] text-[44px] sm:text-[60px] md:text-[84px] lg:text-[120px]">
|
|
Chronicle of<br />the row.
|
|
</h2>
|
|
<p className="almanac-body text-[#f3ece0]/80 text-base md:text-[18px] leading-[1.55] max-w-xl mt-8">
|
|
A short history of how this farm got from one coffee can of
|
|
seed to a harvest that feeds families in four states.
|
|
Written by hand, edited once.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<hr className="almanac-rule--hair mb-14 md:mb-20" />
|
|
|
|
<div className="space-y-20 md:space-y-32">
|
|
{entries.map((e, i) => (
|
|
<ChronicleEntry key={e.year} e={e} reverse={i % 2 === 1} />
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-24 text-center">
|
|
<div className="almanac-display-tight text-[#f3ece0]/85 text-3xl md:text-5xl">
|
|
✻
|
|
</div>
|
|
<div className="almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#f3ece0]/55 mt-4">
|
|
End of Chapter III · Continued
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function ChronicleEntry({
|
|
e,
|
|
reverse,
|
|
}: {
|
|
e: { year: string; chapter: string; title: string; body: string; image: string; caption: string };
|
|
reverse: boolean;
|
|
}) {
|
|
return (
|
|
<article className={`grid grid-cols-12 gap-6 md:gap-10 items-start ${reverse ? "md:[direction:rtl]" : ""}`}>
|
|
<div className="col-span-12 md:col-span-7 md:[direction:ltr]">
|
|
<figure className="almanac-photo relative w-full aspect-[16/10] bg-[#1a1814]">
|
|
<Image
|
|
src={e.image}
|
|
alt={e.caption}
|
|
fill
|
|
sizes="(max-width: 768px) 100vw, 60vw"
|
|
style={{ objectFit: "cover" }}
|
|
className="img"
|
|
/>
|
|
<div className="absolute top-4 left-4 almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#f3ece0]/85">
|
|
{e.chapter} · {e.year}
|
|
</div>
|
|
<div className="absolute bottom-4 right-4 almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#f3ece0]/85">
|
|
✻ archive
|
|
</div>
|
|
</figure>
|
|
<p className="almanac-italic text-[#f3ece0]/65 text-sm md:text-[15px] mt-3 md:[direction:ltr]">
|
|
{e.caption}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="col-span-12 md:col-span-5 md:[direction:ltr]">
|
|
<div className="almanac-eyebrow text-[#c89517] mb-3">{e.chapter}</div>
|
|
<div className="almanac-display-tight text-[#f3ece0] text-[80px] md:text-[110px] lg:text-[140px] leading-none mb-4">
|
|
{e.year}
|
|
</div>
|
|
<h3 className="almanac-display text-[#f3ece0] text-[28px] md:text-[40px] lg:text-[48px] leading-[1.05] mb-6">
|
|
{e.title}
|
|
</h3>
|
|
<p
|
|
className="almanac-body text-[#f3ece0]/80 text-base md:text-[17px] leading-[1.65]"
|
|
dangerouslySetInnerHTML={{ __html: e.body }}
|
|
/>
|
|
</div>
|
|
</article>
|
|
);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 7. LETTER FROM THE FIELD
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function LetterFromTheField() {
|
|
return (
|
|
<section className="relative almanac-paper py-24 md:py-32 overflow-hidden">
|
|
<div className="mx-auto max-w-[1480px] px-5 sm:px-8 md:px-14">
|
|
<div className="grid grid-cols-12 gap-6 md:gap-10 items-center">
|
|
<div className="col-span-12 md:col-span-6">
|
|
<div className="almanac-section-number text-[#1a1814]/55 mb-3">
|
|
№ 06 — Postscript
|
|
</div>
|
|
<h2 className="almanac-display text-[#1a1814] text-[40px] sm:text-[60px] md:text-[80px] lg:text-[100px]">
|
|
Letter from<br />the field.
|
|
</h2>
|
|
<p className="almanac-body text-[#1a1814]/80 text-base md:text-[18px] leading-[1.6] mt-8 max-w-md">
|
|
One note a month, written from the shed. New stops, the
|
|
day’s pick, the occasional recipe from a customer.
|
|
No marketing, no nonsense.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="col-span-12 md:col-span-6">
|
|
<div className="relative bg-[#fbf6ec] border border-[#1a1814] shadow-[0_30px_60px_-30px_rgba(26,24,20,0.45)]">
|
|
<div className="absolute top-0 left-0 right-0 h-3 bg-[radial-gradient(ellipse_4px_3px_at_4px_0,#1a1814_99%,transparent_100%),radial-gradient(ellipse_4px_3px_at_8px_0,#1a1814_99%,transparent_100%),radial-gradient(ellipse_4px_3px_at_12px_0,#1a1814_99%,transparent_100%)] bg-[length:8px_3px] bg-repeat-x opacity-80" />
|
|
|
|
<div className="px-7 md:px-10 py-12 md:py-16">
|
|
<div className="almanac-eyebrow text-[#1a1814]/55">Addressed to</div>
|
|
<form
|
|
className="mt-4 space-y-5"
|
|
onSubmit={(ev) => {
|
|
ev.preventDefault();
|
|
const form = ev.currentTarget;
|
|
const email = new FormData(form).get("email") as string;
|
|
if (email) {
|
|
form.querySelector("[data-success]")?.removeAttribute("hidden");
|
|
(form.querySelector("input[name=email]") as HTMLInputElement).value = "";
|
|
}
|
|
}}
|
|
>
|
|
<label className="block">
|
|
<span className="almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#1a1814]/55">
|
|
Your address
|
|
</span>
|
|
<input
|
|
type="email"
|
|
name="email"
|
|
required
|
|
placeholder="you@home.org"
|
|
className="w-full mt-2 bg-transparent border-b-2 border-[#1a1814] py-3 almanac-display-tight text-2xl md:text-3xl text-[#1a1814] placeholder-[#1a1814]/25 focus:outline-none focus:border-[#c89517]"
|
|
/>
|
|
</label>
|
|
<button type="submit" className="almanac-btn-primary mt-2">
|
|
<span>Send to the field</span>
|
|
<span aria-hidden>→</span>
|
|
</button>
|
|
<p
|
|
data-success
|
|
hidden
|
|
className="almanac-italic text-[#1a1814] text-sm md:text-base"
|
|
>
|
|
✓ Filed. We’ll write back from the row.
|
|
</p>
|
|
</form>
|
|
|
|
<hr className="almanac-rule--hair my-8" />
|
|
|
|
<div className="grid grid-cols-2 gap-5 almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#1a1814]/55">
|
|
<div>
|
|
<div className="text-[#1a1814] mb-1">Twelve a year</div>
|
|
One letter per month
|
|
</div>
|
|
<div>
|
|
<div className="text-[#1a1814] mb-1">No spam</div>
|
|
We don’t sell your address
|
|
</div>
|
|
<div>
|
|
<div className="text-[#1a1814] mb-1">Cancel</div>
|
|
One click, no questions
|
|
</div>
|
|
<div>
|
|
<div className="text-[#1a1814] mb-1">Postmark</div>
|
|
The Harold shed, Olathe
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 8. COLOPHON
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
function Colophon() {
|
|
return (
|
|
<section className="relative almanac-paper--bone py-16 md:py-24 overflow-hidden">
|
|
<div className="mx-auto max-w-[1480px] px-5 sm:px-8 md:px-14">
|
|
<hr className="almanac-rule--double mb-10" />
|
|
<div className="grid grid-cols-12 gap-6 md:gap-10">
|
|
<div className="col-span-12 md:col-span-3">
|
|
<div className="almanac-eyebrow text-[#1a1814]/55">Colophon</div>
|
|
<div className="almanac-display-tight text-2xl text-[#1a1814] mt-2">
|
|
About this bulletin
|
|
</div>
|
|
</div>
|
|
<div className="col-span-12 md:col-span-9 grid sm:grid-cols-3 gap-8">
|
|
<div>
|
|
<div className="almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#1a1814]/55 mb-1">
|
|
Set in
|
|
</div>
|
|
<p className="almanac-body text-[#1a1814] text-base">
|
|
Fraunces & Newsreader. Small caps by hand.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<div className="almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#1a1814]/55 mb-1">
|
|
Printed on
|
|
</div>
|
|
<p className="almanac-body text-[#1a1814] text-base">
|
|
Recycled paper, the color of the shed at noon.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<div className="almanac-mono text-[10px] tracking-[0.22em] uppercase text-[#1a1814]/55 mb-1">
|
|
Published by
|
|
</div>
|
|
<p className="almanac-body text-[#1a1814] text-base">
|
|
The Harold Family, Olathe, CO. Forty-second edition.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr className="almanac-rule--hair mt-12" />
|
|
<div className="flex flex-wrap items-baseline justify-between gap-y-3 mt-6 almanac-mono text-[10.5px] tracking-[0.22em] uppercase text-[#1a1814]/55">
|
|
<span>© MMXXVI Harold Family Farm</span>
|
|
<span>Filed from the field</span>
|
|
<span>Olathe · Colorado · United States</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// PAGE
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
export default function TuxedoPage() {
|
|
const [state, dispatch] = useReducer(reducer, initialState);
|
|
|
|
useEffect(() => {
|
|
async function load() {
|
|
const slug = "tuxedo";
|
|
|
|
const [brandResult, settingsResult] = await Promise.all([
|
|
supabase.from("brands").select("*").eq("slug", slug).single(),
|
|
getBrandSettingsPublic(slug),
|
|
]);
|
|
|
|
const brandData = brandResult.data as Brand | null;
|
|
dispatch({ type: "SET_BRAND", brand: brandData });
|
|
|
|
if (settingsResult.success && settingsResult.settings) {
|
|
const s = settingsResult.settings;
|
|
dispatch({
|
|
type: "SET_SETTINGS",
|
|
wholesaleEnabled: settingsResult.wholesaleEnabled ?? false,
|
|
logoUrl: s.logo_url ?? null,
|
|
logoUrlDark: s.logo_url_dark ?? null,
|
|
olatheSweetLogoUrl: s.olathe_sweet_logo_url ?? null,
|
|
olatheSweetLogoUrlDark: s.olathe_sweet_logo_url_dark ?? null,
|
|
showSchedulePdf: s.show_schedule_pdf ?? true,
|
|
showWholesaleLink: s.show_wholesale_link ?? true,
|
|
heroTagline: s.hero_tagline ?? null,
|
|
customFooterText: s.custom_footer_text ?? null,
|
|
contactEmail: s.email ?? null,
|
|
contactPhone: s.phone ?? null,
|
|
brandPrimaryColor: s.brand_primary_color ?? null,
|
|
brandBgColor: s.brand_bg_color ?? null,
|
|
brandTextColor: s.brand_text_color ?? null,
|
|
});
|
|
}
|
|
|
|
try {
|
|
const { getCurrentAdminUser } = await import("@/actions/admin-user");
|
|
const adminUser = await getCurrentAdminUser();
|
|
dispatch({ type: "SET_IS_ADMIN", isAdmin: !!adminUser });
|
|
} catch {
|
|
// not logged in as admin
|
|
}
|
|
|
|
if (brandData?.id) {
|
|
const [{ data: stopsData }, { data: productsData }] = await Promise.all([
|
|
supabase.from("stops").select("*").eq("brand_id", brandData.id).eq("active", true) as unknown as { data: Stop[] | null },
|
|
supabase.from("products").select("*").eq("brand_id", brandData.id).eq("active", true) as unknown as { data: Product[] | null },
|
|
]);
|
|
dispatch({ type: "SET_STOPS", stops: stopsData ?? [] });
|
|
dispatch({ type: "SET_PRODUCTS", products: productsData ?? [] });
|
|
}
|
|
}
|
|
load();
|
|
}, []);
|
|
|
|
return (
|
|
<div className="min-h-screen">
|
|
<BrandStylesProvider
|
|
primaryColor={state.brandPrimaryColor}
|
|
bgColor={state.brandBgColor}
|
|
textColor={state.brandTextColor}
|
|
/>
|
|
<StorefrontHeader
|
|
brandName={state.brand?.name ?? "Tuxedo Corn"}
|
|
brandSlug="tuxedo"
|
|
logoUrl={state.logoUrl}
|
|
logoUrlDark={state.logoUrlDark}
|
|
showWholesaleLink={state.showWholesaleLink}
|
|
isAdmin={state.isAdmin}
|
|
brandAccent="green"
|
|
/>
|
|
|
|
<main>
|
|
{/* 0 · Cover spread */}
|
|
<TuxedoVideoHero
|
|
eyebrow="Olathe Sweet™ — Olathe, Colorado"
|
|
title="Tuxedo Corn"
|
|
description={
|
|
state.heroTagline ??
|
|
"Premium Olathe Sweet™ sweet corn — hand-picked at peak freshness from our family farm in Colorado."
|
|
}
|
|
olatheSweetLogoUrl={state.olatheSweetLogoUrl}
|
|
primaryButton="Find a Stop"
|
|
secondaryButton="Our Story"
|
|
onPrimaryClick={scrollToStops}
|
|
onSecondaryClick={scrollToStory}
|
|
/>
|
|
|
|
{/* Wholesale banner */}
|
|
{state.showWholesaleLink && state.wholesaleEnabled && (
|
|
<div className="almanac-paper--ink border-y border-[#1a1814]/15 py-5">
|
|
<div className="mx-auto max-w-[1480px] px-5 sm:px-8 md:px-14 flex flex-wrap items-center justify-between gap-y-3">
|
|
<div className="almanac-italic text-[#f3ece0] text-base md:text-lg">
|
|
Wholesale buyers — accounts open for the 2026 season.
|
|
</div>
|
|
<Link
|
|
href="/wholesale/login"
|
|
className="almanac-btn-primary"
|
|
style={{ background: "#c89517", color: "#1a1814", borderColor: "#c89517" }}
|
|
>
|
|
<span>Wholesale Portal</span>
|
|
<span aria-hidden>→</span>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 1 · Editor's note */}
|
|
<EditorsNote />
|
|
|
|
{/* 2 · The Almanac */}
|
|
<TheAlmanac />
|
|
|
|
{/* 3 · The Cover Story */}
|
|
<TheCoverStory />
|
|
|
|
{/* 4 · The 2026 Tour */}
|
|
<TheTour stops={state.stops} showSchedulePdf={state.showSchedulePdf} />
|
|
|
|
{/* 5 · This Week's Lot */}
|
|
<ThisWeeksLot
|
|
products={state.products}
|
|
olatheSweetLogoUrlDark={state.olatheSweetLogoUrlDark}
|
|
/>
|
|
|
|
{/* 6 · Chronicle of the Row */}
|
|
<ChronicleOfTheRow />
|
|
|
|
{/* 7 · Letter from the Field */}
|
|
<LetterFromTheField />
|
|
|
|
{/* · Colophon */}
|
|
<Colophon />
|
|
</main>
|
|
|
|
<StorefrontFooter
|
|
brandName={state.brand?.name ?? "Tuxedo Corn"}
|
|
brandSlug="tuxedo"
|
|
logoUrl={state.logoUrl}
|
|
logoUrlDark={state.logoUrlDark}
|
|
customFooterText={state.customFooterText}
|
|
contactEmail={state.contactEmail}
|
|
contactPhone={state.contactPhone}
|
|
isAdmin={state.isAdmin}
|
|
brandAccent="green"
|
|
/>
|
|
</div>
|
|
);
|
|
} |