Files
com.gregkilmer.www/.zcode/plans/plan-sess_cfbaea37-9611-497f-804c-b438e5e659b3.md
T
Greg Kilmer f4a49fe9bc Store base
2026-08-06 12:13:48 -04:00

92 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Store (store.gregkilmer.com) — MTG Proxy Cards & 3D-Printed Deck Boxes / Counters
## Summary
Add a self-contained **store** section to this repo that renders a commerce-style storefront for your proxy-card printing and 3D-printing services, with a gated email-submission order form. It lives at `/store` (and is reachable via `store.gregkilmer.com`), reuses the existing mystic night-sky aesthetic, and shares **no nav links** with the main site. A single JSON file you edit controls order-open/closed, per-category slot counts, and turnaround messaging.
**Key constraint honored:** no payment is taken; the order form is **hard-locked** until you flip a flag in the JSON.
---
## Architecture decision (defaulting, since the question went unanswered)
- **Same app, `/store` route set, with a store-only nav and zero cross-links from the main site.** Store pages are reachable only by direct URL or the `store.gregkilmer.com` subdomain (the subdomain routing is a reverse-proxy concern, not code). This reuses BaseLayout, the theme system, Icon, Reveal, SectionHeader, the contact/email pipeline, and Turnstile — no new dependencies.
- **Order lock = hard gate.** When `ordersOpen` is false the order page shows a clear "orders currently closed" notice, links to your email, and **renders no form at all**. When true, the full order form appears.
- **Capacity JSON drives:** overall store open/closed flag, open-slot counts for cards and for 3D-print jobs (shown as "X of Y open"), an estimated turnaround string, and a last-updated timestamp.
---
## Files to create
### 1. Store data + availability (the "simple JSON file you edit")
- **`src/data/store-availability.json`** — the single file you edit to control the storefront. Schema:
```json
{
"ordersOpen": false, // master gate — false = form hidden
"lastUpdated": "2026-08-04",
"turnaround": "Estimated turnaround: ~2 weeks once a list is confirmed.",
"maxNote": "I take a limited number of orders at a time so each gets full attention.",
"slots": {
"cards": { "open": 0, "total": 5 },
"deckBoxes": { "open": 0, "total": 4 }
}
}
```
- **`src/data/store-products.ts`** — typed product/pricing data (cards, deck boxes, finishes, sizing), authored in TS for type-safety and kept here (not JSON) so it's version-controlled with the components that render it. Pricing tables and product specs read from here.
### 2. Store-only layout + nav
- **`src/layouts/StoreLayout.astro`** — wraps `BaseLayout` with `surface="interest"` (warm candlelit tone, clearly distinct from the dark professional wing but still on-brand). Passes a `noindex` option (store can be noindexed until you're ready to promote). Renders a **store-specific top nav** (`/store`, `/store/cards`, `/store/deck-boxes`, `/store/order`) with a store wordmark and a small "store.gregkilmer.com" label, and a store footer variant that points to your email — **no links back to the main site**, per your request.
- **`src/components/store/StoreNav.astro`** — the store nav bar (mirrors `NavBar.astro`'s scroll-blur treatment and StarBurst mark, but store items only).
### 3. Reusable store UI components
- **`src/components/store/ProductCard.astro`** — image + spec list (size, thickness, finish, white back, etc.) for a product.
- **`src/components/store/PricingTable.astro`** — generic pricing-by-quantity table (takes rows of `{ quantity, price, note? }`).
- **`src/components/store/SlotMeter.astro`** — visual "X of Y slots open" meter with open/closed pill.
- **`src/components/store/RequirementList.astro`** — the rules/requirements list with mdi check/alert icons.
### 4. Pages
- **`src/pages/store/index.astro`** — Storefront home: hero (uses existing `/images/proxies.jpg` / `/images/deckboxes.jpg`), intro, **clear disclaimer block** (not affiliated with WotC/any game company; these are clearly-marked proxies, not counterfeits; will not print offensive content or custom art), capacity/availability summary from the JSON, links to the two product lines + order page.
- **`src/pages/store/cards.astro`** — Proxy MTG cards product line: example images, spec details (size = standard MTG 63×88mm, cardstock thickness/finish options, white back, etc.), pricing table for sets (e.g. 1–9 / 10–39 / 40–99 / 100+ tiers), double-sided-counts-as-two rule callout.
- **`src/pages/store/deck-boxes.astro`** — 3D-printed deck box & counter product line: example images (`/images/deckboxes.jpg`), sizing/finish/material details, pricing table for prints.
- **`src/pages/store/order.astro`** — Order page. Reads `store-availability.json`; when `ordersOpen === false`, renders the **closed notice + email link, no form**. When open, renders the order form island and shows the slot meters + requirements inline.
### 5. Order form + API
- **`src/components/store/OrderForm.tsx`** — React island modeled on the existing `ContactForm.tsx` (same idle/submitting/success/error states, honeypot, optional Turnstile, aria-live, same input styling). Fields: name, email, order type (cards / deck-box print / both), card list / deck-box spec (textarea), quantity/finish selectors, notes, honeypot, Turnstile token. **Posts to `/api/order`.**
- **`src/pages/api/order.ts`** — new POST endpoint cloned from `/api/contact.ts`'s proven pipeline: Zod validation, per-IP rate limit, Turnstile verification (reusing `@lib/env`), SMTP-or-webhook delivery to your inbox with a subject like `New store order — ${type}`. **Server-side hard gate:** even if a client POSTs while `ordersOpen` is false, the endpoint reads the JSON and returns 403 "Orders are currently closed." This makes the lock enforced both in UI and on the server.
### 6. Env additions
- Append to **`.env.example`** (and document in the new docs file): an optional `ORDER_TO` inbox (defaults to `CONTACT_TO`), and reuse existing `TURNSTILE_*`, `SMTP_*`, and rate-limit vars — **no new dependencies**. Order emails reuse the same `deliver()` pattern.
### 7. Docs
- **`docs/STORE.md`** — how to edit `store-availability.json` to open/close orders and update slots/turnaround, how the hard gate works (UI + API), how to point `store.gregkilmer.com` at the same server via your reverse proxy, and the "no cross-links" rule.
---
## Files modified
- **`src/lib/env.ts`** — add `orderTo()` helper (falls back to `contactTo()`).
- **`.env.example`** — add `ORDER_TO=` with a comment.
- **`src/middleware.ts`** — no change needed; CSP already allows `'self'` form-action, Turnstile origins, and same-origin connect. (I'll add a comment noting `/api/order` reuses the existing policy.)
- No change to the main site nav (`site.ts`), Footer, or any main-site page — **zero cross-links**, as requested.
---
## How each of your requirements is met
| Requirement | Where |
|---|---|
| Order form submits an email | `OrderForm.tsx` → `/api/order` → SMTP/webhook (cloned from working `/api/contact`) |
| Example images + product details | `cards.astro` / `deck_boxes.astro` + `ProductCard.astro` (size, thickness, finishes, white back, etc.) |
| Pricing tables for card sets & deck-box prints | `PricingTable.astro` fed by `store-products.ts` |
| Hosted at store.gregkilmer.com, no clear link between sites | `/store` route set, store-only nav, no main-site links; subdomain is a reverse-proxy mapping to the same server |
| No payment taken | Form collects a *request* only; no payment fields, no checkout |
| Order form locked until you choose to accept | Hard gate: `ordersOpen` in JSON hides the form (UI) **and** 403s the API (server) |
| Number of card + 3D-print slots, updatable from a simple JSON | `slots.cards` / `slots.deckBoxes` in `store-availability.json`, rendered by `SlotMeter.astro` |
| Clear "I can only take so many at a time" | `maxNote` + slot meters prominently on storefront and order page |
| Stated requirements (card list final, double-sided = 2 slots, payment is final, not affiliated with WotC, no counterfeits — clearly proxies, no offensive/custom art, etc.) | `RequirementList.astro` on storefront + repeated on the order page; order form requires an explicit acknowledgment checkbox |
---
## Defaults I'll use (flag any you want changed)
- **Disclaimer copy** drafted to match your wording (not affiliated with WotC / any game company; clearly-marked proxies, never counterfeits; no offensive or unauthorized custom art; card list is final once received; double-sided = two cards/two slots; all sales final). You can tweak exact phrasing after.
- **Pricing & specs** are placeholder structure with clearly-marked `[Greg to fill]` values (you'll swap in your real tier prices, cardstock GSM, finish names, deck-box dimensions). I won't invent real prices.
- **Slot counts** default to `{ cards: 0/5, deckBoxes: 0/4 }` and `ordersOpen: false`, so the site launches visibly "closed."
- **`noindex`** on by default for store pages until you say otherwise.
I will not commit or push anything. Ready to implement on approval.