planmymeal/app/caydens-corner/page.tsx
Cayden Schwechten df5dd3a70e Move UI to BoardUI with slate + salmon theme; add PDF plan export
- Adopt BoardUI (Pro init): theme tokens, Inter, .dark mode, Button,
  SegmentedControl, Meter, Skeleton and ThemeToggle; remap the app's
  color aliases onto BoardUI tokens and swap lucide for Remix icons
- Retint BoardUI's neutral ramp to cool slate with a salmon accent;
  drop the dotted background; raise muted text contrast
- Simplify the landing page and remove the overlap explorer
- Planner: explicit "I have this" toggle on grocery lines, pantry panel
  above the menu with the staples list spelled out, sticky aisle
  header fix, and a "Download PDF" plan export (jsPDF, lazy-loaded)
- Recipes: replace the family cabbage soup with Cade's Cabbage Soup
  (adds Bone-In Ham Steak to the catalog); remove Ham, Bean & Cabbage Soup
- Remove unused BoardUI components, dependencies and exports

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:36:33 -06:00

108 lines
5.5 KiB
TypeScript

import { RiArrowRightLine as ArrowRight, RiHeart3Line as BookHeart } from "@remixicon/react";
import type { Metadata } from "next";
import Link from "next/link";
import { ViewTransition } from "react";
import { ApplianceIcon } from "@/app/components/Icons";
import { SiteHeader } from "@/app/components/SiteHeader";
import { APPLIANCES } from "@/app/lib/plan";
import { RECIPES } from "@/app/lib/recipes";
import type { Recipe } from "@/app/types";
export const metadata: Metadata = {
title: "Cayden's Corner",
description: "Homemade family recipes: tried, tested and loved.",
};
const FAMILY = RECIPES.filter((r) => r.styles.includes("caydens_corner"));
const SECTIONS: { title: string; recipes: Recipe[] }[] = [
{ title: "Appetizers & Side Dishes", recipes: FAMILY.filter((r) => r.kind === "side") },
{ title: "Main Dishes", recipes: FAMILY.filter((r) => r.kind === "main" || r.kind === "breakfast") },
{ title: "Desserts & Bakes", recipes: FAMILY.filter((r) => r.kind === "dessert") },
].map((s) => ({ ...s, recipes: [...s.recipes].sort((a, b) => a.title.localeCompare(b.title)) }));
export default function CaydensCornerPage() {
return (
<div className="min-h-dvh">
<SiteHeader active="caydens-corner" />
<main className="mx-auto max-w-7xl px-4 py-10 sm:px-6">
<section className="rounded-2xl border border-brand-border bg-brand-950 px-6 py-10 sm:px-10">
<p className="inline-flex items-center gap-2 font-mono text-xs font-semibold tracking-widest text-brand-300 uppercase">
<BookHeart className="size-4" strokeWidth={2} aria-hidden />
Cayden&apos;s Corner · Heirloom Cookbook
</p>
<h1 className="mt-2 font-serif text-3xl font-semibold tracking-tight text-balance sm:text-5xl">
Family Recipes
</h1>
<p className="mt-3 max-w-2xl text-base leading-relaxed text-ink-muted sm:text-lg">
{FAMILY.length} homemade family favorites—passed down, tested at the dinner table, and loved. Comfort classics,
holiday staples, and plenty of scratch-baked desserts.
</p>
<div className="mt-6 flex flex-wrap items-center gap-4 border-t border-brand-border/40 pt-5 text-sm text-ink-muted">
<p>
Written originally for a table of four with leftovers; the planner scales every ingredient automatically to your household.
</p>
<Link
href="/plan"
className="trim-cap ml-auto inline-flex min-h-10 items-center gap-1.5 rounded-lg border border-line-strong bg-surface px-4 py-2 text-sm font-semibold text-ink transition-colors hover:bg-raised"
>
Plan a family-recipe week
<ArrowRight className="size-4" aria-hidden />
</Link>
</div>
</section>
{SECTIONS.map((section) => (
<section key={section.title} className="mt-12" aria-labelledby={`section-${section.title}`}>
<div className="flex items-baseline justify-between border-b border-line pb-2.5">
<h2 id={`section-${section.title}`} className="font-serif text-2xl font-semibold tracking-tight">
{section.title}
</h2>
<span className="font-mono text-xs text-ink-faint">{section.recipes.length} recipes</span>
</div>
<ul className="mt-5 grid gap-3.5 sm:grid-cols-2 lg:grid-cols-3">
{section.recipes.map((r) => {
const applianceNames =
r.requiredAppliances.map((a) => APPLIANCES.find((x) => x.id === a)?.label).join(", ") || "No cooking";
return (
<li key={r.id}>
<Link
href={`/recipes/${r.id}`}
className="group flex h-full flex-col rounded-3xl border border-border-button-default bg-surface p-4 transition-colors hover:border-line-strong hover:bg-raised/60"
>
<ViewTransition name={`recipe-title-${r.id}`}>
<span className="font-serif text-lg leading-snug font-semibold text-ink group-hover:text-brand-300">
{r.title}
</span>
</ViewTransition>
<span className="mt-1.5 line-clamp-2 text-sm leading-relaxed text-ink-muted">{r.description}</span>
<span className="mt-auto flex items-center justify-between border-t border-line pt-3 mt-4 text-xs text-ink-faint">
<span className="font-mono tabular-nums">
{r.prepTime >= 120 ? `${Math.round(r.prepTime / 60)} hr` : `${r.prepTime} min`} · serves {r.servings}
</span>
<span className="inline-flex items-center gap-1.5" title={applianceNames}>
{r.requiredAppliances.length > 0 ? (
<>
{r.requiredAppliances.map((a) => (
<ApplianceIcon key={a} id={a} className="size-3.5 text-ink-muted" />
))}
<span className="sr-only">Requires {applianceNames}</span>
</>
) : (
<span>No cooking</span>
)}
</span>
</span>
</Link>
</li>
);
})}
</ul>
</section>
))}
</main>
</div>
);
}