This commit is contained in:
Cayden Schwechten 2026-10-31 20:44:29 -05:00
commit c19b910c36
80 changed files with 16134 additions and 0 deletions

View file

@ -0,0 +1,149 @@
"use client";
import { Check, Plus, Repeat2 } from "lucide-react";
import { useState } from "react";
export interface OverlapDish {
id: string;
title: string;
cuisine: string;
standaloneCheckout: number;
}
export interface OverlapComboSnapshot {
checkoutTotal: number;
usedTotal: number;
carryoverValue: number;
sharedNames: string[];
packageCount: number;
}
interface OverlapExplorerProps {
dishes: OverlapDish[];
/** Keyed by sorted comma-joined dish IDs; precomputed on the server so zero catalog data ships to the landing bundle. */
combos: Record<string, OverlapComboSnapshot>;
}
const fmt = (n: number) => `$${n.toFixed(2)}`;
export function OverlapExplorer({ dishes, combos }: OverlapExplorerProps) {
const [selected, setSelected] = useState<string[]>(() => dishes.slice(0, 2).map((d) => d.id));
function toggleDish(id: string) {
setSelected((prev) => {
if (prev.includes(id)) {
return prev.length > 1 ? prev.filter((x) => x !== id) : prev; // keep at least 1 selected
}
return [...prev, id];
});
}
const comboKey = [...selected].sort().join(",");
const current = combos[comboKey];
const naiveSum = dishes.filter((d) => selected.includes(d.id)).reduce((acc, d) => acc + d.standaloneCheckout, 0);
const saved = Math.max(0, naiveSum - (current?.checkoutTotal ?? 0));
return (
<div className="grid gap-6 rounded-xl border border-line-strong bg-surface p-5 sm:p-7 lg:grid-cols-[1.15fr_1fr]">
<div>
<p className="font-mono text-xs tracking-wider text-brand-300 uppercase">Interactive checkout math · 2 people</p>
<h3 className="mt-1 font-serif text-2xl font-semibold tracking-tight text-balance">
Toggle dishes to watch shared packages drop the bill
</h3>
<p className="mt-2 text-sm leading-relaxed text-ink-muted">
Grocery stores sell whole bags of onions, rice, and spices—not single tablespoons. Selecting meals that reuse open
packages slashes what each extra dinner adds at the register.
</p>
<ul className="mt-5 space-y-2" aria-label="Sample dishes">
{dishes.map((dish) => {
const active = selected.includes(dish.id);
return (
<li key={dish.id}>
<button
type="button"
aria-pressed={active}
onClick={() => toggleDish(dish.id)}
className={`flex min-h-12 w-full items-center justify-between gap-3 rounded-lg border px-3.5 py-2.5 text-left transition-colors ${
active
? "border-selected-line bg-selected text-ink"
: "border-line bg-canvas text-ink-muted hover:border-line-strong hover:text-ink"
}`}
>
<span className="flex items-center gap-3 min-w-0">
<span
className={`grid size-5 shrink-0 place-items-center rounded border ${
active ? "border-brand-600 bg-brand-600 text-on-accent" : "border-line-strong bg-surface"
}`}
>
{active ? <Check className="size-3.5" strokeWidth={2.5} aria-hidden /> : <Plus className="size-3 text-ink-faint" aria-hidden />}
</span>
<span className="min-w-0">
<span className="block truncate text-sm font-semibold text-ink">{dish.title}</span>
<span className="block text-xs text-ink-faint">{dish.cuisine}</span>
</span>
</span>
<span className="shrink-0 font-mono text-xs text-ink-muted tabular-nums">
{fmt(dish.standaloneCheckout)} alone
</span>
</button>
</li>
);
})}
</ul>
</div>
{/* Simulated paper receipt */}
<div className="paper-grain flex flex-col justify-between rounded-lg border border-line bg-canvas p-5 font-mono text-sm">
<div>
<div className="flex items-center justify-between border-b border-line pb-3 text-xs text-ink-faint uppercase">
<span>Checkout Receipt</span>
<span>{selected.length} {selected.length === 1 ? "meal" : "meals"}</span>
</div>
<dl className="mt-4 space-y-2.5 text-xs sm:text-sm">
<div className="flex justify-between text-ink-muted">
<dt>Buying each meal separately</dt>
<dd className="line-through tabular-nums">{fmt(naiveSum)}</dd>
</div>
{saved > 0.01 && (
<div className="flex justify-between font-semibold text-status-ok">
<dt>Shared-package savings</dt>
<dd className="tabular-nums">−{fmt(saved)}</dd>
</div>
)}
<div className="receipt-rule flex items-baseline justify-between pt-3 text-base font-bold text-ink">
<dt className="font-sans text-sm font-semibold">Combined register total</dt>
<dd className="text-lg tabular-nums">{fmt(current?.checkoutTotal ?? 0)}</dd>
</div>
</dl>
<dl className="mt-4 grid grid-cols-2 gap-3 rounded-md border border-line bg-surface p-3 font-sans text-xs">
<div>
<dt className="text-ink-faint">Food eaten</dt>
<dd className="mt-0.5 font-mono text-sm font-semibold text-ink tabular-nums">{fmt(current?.usedTotal ?? 0)}</dd>
</div>
<div>
<dt className="text-ink-faint">Kept in pantry</dt>
<dd className="mt-0.5 font-mono text-sm font-semibold text-ink tabular-nums">{fmt(current?.carryoverValue ?? 0)}</dd>
</div>
</dl>
</div>
<div className="mt-4 border-t border-line pt-3 font-sans text-xs text-ink-muted">
{current && current.sharedNames.length > 0 ? (
<p className="flex items-start gap-1.5">
<Repeat2 className="mt-0.5 size-3.5 shrink-0 text-status-ok" strokeWidth={2} aria-hidden />
<span>
<strong className="font-semibold text-ink">Reused across meals:</strong> {current.sharedNames.slice(0, 5).join(", ")}
{current.sharedNames.length > 5 ? ` +${current.sharedNames.length - 5} more` : ""}
</span>
</p>
) : (
<p>Select a second dish to see which store packages overlap in your cart.</p>
)}
</div>
</div>
</div>
);
}