Add guest/account auth flows, order tracking, and order history UI

- Wire up Better Auth (email/password + Google/Apple SSO) with a
  sign-in/sign-up UI, header account menu, and a basket that follows
  the signed-in-or-guest state.
- Give the order status page a vertical timeline per preparation step
  (with a distinct customer-facing name separate from the machine tag,
  a done/in-progress/queued/pending look, and a sonar-ping highlight on
  the active step), a formatted ETA (relative under 3h, absolute clock
  time beyond that), and Buy again / Get help / Cancel actions.
- Expand Pizza's recipe into 6 steps across a shared prep station and
  the existing oven/assembly stations, with matching schema, seed, and
  domain changes plus updated/added test coverage.
- Add an Orders list (account orders via session, guest orders via a
  locally remembered access-token list) with a shared getOrdersForCustomer
  domain summary, and remember a guest's name/contact after their first
  order so checkout doesn't ask again.
- Improve page transitions with the View Transitions API: a shared
  element morph between an order card and its detail page, a stable
  (non-jumping) header across navigations, and reduced-motion handling.
- Add a Ghost Kitchen wordmark (Space Grotesk, faded "Ghost", ghost
  icon standing in for the "o") and shrink the basket icon to match
  the rest of the header controls.
- Add a run-ghost-kitchen agent skill documenting local setup, the
  dev-server driver, and the gotchas hit along the way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 11:58:24 +02:00
co-authored by Claude Sonnet 5
parent 5059f40d4c
commit 79bd64ce8c
54 changed files with 3514 additions and 301 deletions
+77
View File
@@ -21,3 +21,80 @@ body {
button {
font: inherit;
}
// Route navigation transitions, powered by the router's defaultViewTransition
// (see router.tsx). No-ops in browsers without View Transitions support.
@media (prefers-reduced-motion: no-preference) {
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.22s;
animation-timing-function: ease;
}
::view-transition-old(root) {
animation-name: view-transition-fade-out;
}
::view-transition-new(root) {
animation-name: view-transition-fade-in;
}
// Any element that carries a matching `view-transition-name` on both the
// outgoing and incoming page (e.g. an Order card morphing into its detail
// header, see OrdersListView / OrderStatusView) gets its own transition
// group — this smooths and slows the browser's default morph/resize
// animation for those to match the root crossfade's pace instead of the
// UA default's snappier one.
::view-transition-group(*) {
animation-duration: 0.32s;
animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}
// The top nav bar (see __root.tsx) carries its own `view-transition-name`
// so it's excluded from the root snapshot above — otherwise the root
// fade's translateY drags the persistent header along with it, making it
// visibly jump on every navigation. It doesn't change between routes, so
// it should just stay put: no morph, no crossfade.
::view-transition-group(site-header) {
animation: none;
}
::view-transition-old(site-header),
::view-transition-new(site-header) {
animation: none;
}
}
// document.startViewTransition() itself doesn't check this preference —
// without an explicit override every browser falls back to its own default
// crossfade/morph animations, unanimated only for the rules above (which
// only apply under no-preference). Kill all of it outright here instead.
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
@keyframes view-transition-fade-in {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes view-transition-fade-out {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-6px);
}
}