Files
Claude a2f723ca2d Start the rebuild: note schema, vault kit and data migration
Phase 1 of the functional design plan.

- Move the old site into legacy/, unchanged, for reference. It still
  builds from there until cutover.
- Add packages/schema: the one definition of all 20 note types, with
  field titles and descriptions, note and vault checks, and JSON Schema
  export so later editing tools and the MCP server share it.
- Add vault-kit: Obsidian templates generated from the schema, and the
  Site guide for 24.01_PersonalWebsite.
- Add tools/migrate: turns the legacy JSON, collections, store data and
  page text into 545 Site folder notes and 84 images, and checks the
  result with the schema.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
2026-10-11 18:40:55 +00:00

6.1 KiB

Store (store.gregkilmer.com)

A small storefront for Greg's proxy MTG card and 3D-printed deck box / counter services. It lives at /store inside the same app, reuses the main site's aesthetic, and is reachable directly or via the store.gregkilmer.com subdomain. It takes no payment — the order form only emails a request — and the form is hard-locked until you choose to accept orders.

Pages

Route What it is
/store Storefront: intro, products, live slots, policies
/store/cards Proxy MTG cards specs + pricing
/store/deck-boxes 3D-printed deck boxes & counters specs + pricing
/store/order Order request form (gated) or "closed" notice

The store uses its own nav (StoreNav.astro) and has no links back to the main site. The two sites are intentionally not connected from the UI.

Opening and closing orders

Everything customer-facing about availability is driven by one file:

src/data/store-availability.json
{
  "ordersOpen": false,
  "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...",
  "slots": {
    "cards":     { "open": 0, "total": 5 },
    "deckBoxes": { "open": 0, "total": 4 }
  }
}
  • ordersOpen is the master switch.
    • false → the order page shows a "closed" notice and renders no form.
    • true → the order form appears.
  • slots.cards / slots.deckBoxes drive the "X of Y open" meters on the storefront and order page. Set open to 0 to show a category as Closed.
  • turnaround, maxNote, and lastUpdated are shown verbatim.

Edit the file, commit, and redeploy. That's it.

The lock is enforced in two places

  1. UI: /store/order reads the file at render and hides the form when ordersOpen is false.
  2. Server: /api/order reads the file again and returns 403 if ordersOpen is false — before doing any validation or delivery. So even a direct POST can't bypass the lock.

Editing products, specs, and pricing

Those live in version-controlled TypeScript (not the availability JSON):

src/data/store-products.ts

Each product has an icon, title, summary, example image, a specs list (size, thickness, finishes, white back, etc.), and pricing tiers. Search for [Greg to fill] to find the placeholder prices and concrete specs to replace.

Available print colors (deck boxes)

The deck-box product has a colors array — this drives the Available print colors swatch grid on /store/deck-boxes and the color dropdown in the order form. Each entry is { name, hex, inStock }:

colors: [
  { name: 'Midnight Black', hex: '#1b1730', inStock: true },
  { name: 'Nebula Teal',    hex: '#4f8c84', inStock: false }, // shows "Out of stock"
],
  • Add a color → append a new { name, hex, inStock } entry.
  • Mark a color sold out → set inStock: false. It shows an "Out of stock" label on the grid and drops out of the order-form dropdown automatically.
  • Remove a color → delete the entry.

Only in-stock colors are offered in the order form; the chosen color is included in the order email. Colors are optional — products with no colors array (e.g. proxy cards) simply don't show the grid or the dropdown.

The shared requirements / policies list is also in this file (requirements) and is rendered on the storefront, the order page, and required as a checkbox on the order form.

Order delivery (email)

Order requests are emailed using the same SMTP/webhook pipeline as the contact form (see docs/DEPLOYMENT.md). The only addition is the inbox:

ORDER_TO=orders@gregkilmer.com

If ORDER_TO is blank, it falls back to CONTACT_TO. Configure SMTP_* vars as usual; if neither SMTP nor CONTACT_WEBHOOK_URL is set, /api/order returns 503 (delivery not configured) so it's visibly a config gap.

Rate limiting reuses CONTACT_RATE_LIMIT / CONTACT_RATE_WINDOW_MIN unless you set ORDER_RATE_LIMIT / ORDER_RATE_WINDOW_MIN to give orders a separate budget.

Bot protection reuses the Cloudflare Turnstile keys (TURNSTILE_SITE_KEY / TURNSTILE_SECRET_KEY). Leave both blank to skip Turnstile (e.g. local dev).

Pointing store.gregkilmer.com at the store

The store is part of the same app — no separate server. The subdomain is purely a reverse-proxy concern (Cosmos / Traefik / Nginx Proxy Manager):

  1. Create a DNS record for store.gregkilmer.com.
  2. In your reverse proxy, point store.gregkilmer.com at the same container on port 4321, with TLS.
  3. (Optional) Add a redirect or rewrite so store.gregkilmer.com/ maps to /store. The store pages are reachable at /store* on the main domain too, so a redirect isn't strictly required — visitors who know the path can reach it either way.

No code change is needed to enable the subdomain.

Search visibility

Store pages are noindex by default (set in StoreLayout.astro). When you're ready for the store to be indexed, change noindex to false there (or override it per page via the noindex prop).

Files added / changed

  • src/data/store-availability.json — the file you edit
  • src/data/store-availability.ts — typed reader (used by pages + API)
  • src/data/store-products.ts — products, specs, pricing, requirements
  • src/components/store/ — StoreNav, ProductCard, PricingTable, SlotMeter, RequirementList, OrderForm.tsx
  • src/layouts/StoreLayout.astro — store wrapper (surface, store nav, noindex)
  • src/pages/store/ — index, cards, deck-boxes, order
  • src/pages/api/order.ts — order endpoint with hard gate
  • src/layouts/BaseLayout.astro — added optional storeNav prop (renders StoreNav instead of the main NavBar on store pages)
  • src/components/nav/MobileMenu.tsx — added store icon paths
  • src/lib/env.ts — added orderTo() helper
  • .env.example — added ORDER_TO