Retargets the build and the playbook off rootful Docker on monoco, following the conventions already in place on emily's other services: Quadlet units in ~/.config/containers/systemd, per-service data in ~/podman-data, and a shared Caddy that every service registers itself with — a network drop-in plus a marked block in its Caddyfile. Nothing is published on the host; Caddy reaches the containers by name over mozimo_net. - Makefile: podman build + push instead of docker buildx, PLATFORMS=linux/arm64 (both hosts are aarch64). podman has no built-in QEMU the way buildx's builder image does, so a foreign-platform failure now points at qemu-user-static. - deploy.sh: pass COMMIT= to make, which the Makefile actually reads; it was passing TAG=, so the tag silently came from `git rev-parse` instead. - ansible: hosts emily, become false, XDG_RUNTIME_DIR for rootless podman and systemctl --user. Docker modules become Quadlet templates with user-service handlers, and the network role now ships mozimo.network plus the Caddy drop-in rather than calling docker_network. - UserNS=keep-id maps the container's uid 1000 (node) and root (vince) onto the host user, so PocketBase and Vince write host-owned files instead of subuid-owned ones; the play asserts the pinned uid still matches the host. - ansible/README.md: the unit layout, the service-registration convention, and the monoco -> emily migration (PocketBase 23 MB + Vince 119 MB, DNS cutover, rollback). Verified by a check-mode rehearsal against emily (13 ok / 8 changed / 0 failed) and a disposable Quadlet unit on that host proving the uid mapping, the EnvironmentFile and the isolated network. The real build and cutover have not been run.
96 lines
7.2 KiB
Markdown
96 lines
7.2 KiB
Markdown
# Mozimo — The Art of Chocolate, Made in India
|
|
|
|
A full luxury-editorial redesign of [mozimo.in](https://mozimo.in), implementing the
|
|
proposed "Enter the world of Mozimo" flow: ivory, deep cacao, champagne gold,
|
|
restrained puce — editorial, European, slightly mysterious.
|
|
|
|
Built with **TanStack Start (React 19 + Vite)**, **Tailwind CSS 4**, and zero animation
|
|
dependencies (hand-rolled CSS + IntersectionObserver).
|
|
|
|
## Design language
|
|
|
|
- **Palette** — ivory `#faf6ee`, deep cacao `#140a06`, champagne gold `#c2a06e`, brand puce `#703133`
|
|
- **Type** — Fraunces (display serif), Renner* (brand geometric sans), Samantha Signature (script accents)
|
|
- **Motifs** — arched image masks, paper-grain texture, champagne hairlines, marquee tickers, cursor-trailing index previews, staggered line reveals
|
|
|
|
## Site map
|
|
|
|
| Route | Contents |
|
|
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| `/` | Announcement bar → hero ("Chocolate, perfected.") → brand statement → Shop by Experience → The Mozimo Edit → Signatures → Made by Hand (film) → Ingredient Story → The Art of Giving → Seasonal Edit → Experiences → Journal → Press → Instagram |
|
|
| `/chocolate` | Collection index (Bars → Gifting) with 3-image hover carousels + The Pantry |
|
|
| `/collections/[handle]` | A Shopify collection as a first-party page (also powers `/gifting` CTAs) |
|
|
| `/products/[handle]` | Product page — gallery, price, description, buy (Shopify checkout) |
|
|
| `/gifting` | Who is it for (price-wise tiles) → hampers → Seasonal Edit |
|
|
| `/experiences` | Chef's Table, Tastings, Private Events, Collaborations, Pop-ups, Mozimo at Home |
|
|
| `/bespoke` | Made for Your Moment — corporate, weddings, collaborations, events, custom packaging + bulk/bespoke enquiry form |
|
|
| `/contact` | Contact form + atelier details |
|
|
| `/our-world` | Story · Chocolate · Craft · Ingredients · People (+ `/about` redirects here) |
|
|
| `/journal` (+3 essays) | The Mozimo Journal — The Art of Tempering; Why Dark Chocolate Tastes Different; The Story Behind Our Signature Box |
|
|
|
|
## Headless commerce & CMS
|
|
|
|
The site renders commerce itself; Shopify runs headless behind it.
|
|
|
|
- **Shopify Storefront API** (`src/lib/shopify.ts`) — products, prices,
|
|
collections. Server-side, cached ~60s; auth via a static
|
|
`SHOPIFY_STOREFRONT_ACCESS_TOKEN` or, with `SHOPIFY_CLIENT_ID` +
|
|
`SHOPIFY_CLIENT_SECRET`, the client auto-mints a storefront token
|
|
(client-credentials → Admin REST) and self-heals if it is ever revoked.
|
|
Checkout stays on Shopify's hosted page until Phase 2 (in-container cart,
|
|
`unauthenticated_write_checkouts` scope already granted).
|
|
- **PocketBase** (`src/lib/cms.ts`, running in the same container on :8090,
|
|
published as `cms.mozimo.in`) — editorial layer: gifting tabs, /chocolate
|
|
rows + carousel images, gifting page copy. Reads cached ~30s.
|
|
- **Fallbacks** — if either backend is down the site serves the bundled
|
|
defaults from `src/data/catalog.ts`; it never blanks.
|
|
|
|
Content editing (no deploys): PocketBase admin at `https://cms.mozimo.in/_/` —
|
|
edit `gifting_tabs` (tiles, blurbs, product handles per tier, ordering),
|
|
`collection_rows` (row order, names, the 3 carousel images per collection),
|
|
`gifting_page` (hero copy). Changes go live within the cache TTL.
|
|
|
|
First-run seeding: `scripts/pb-seed.mjs` (runs automatically at container
|
|
start; also `npm run seed`) creates the collections and inserts the shipped
|
|
content into an empty database. Idempotent — it never overwrites edits.
|
|
|
|
## Develop
|
|
|
|
```bash
|
|
npm run dev # vite dev server (port 3000), loads .env when present
|
|
npm run pb # one-time download + run PocketBase locally (:8090)
|
|
npm run seed # create PB schema + seed default content
|
|
npm run build # production build to dist/
|
|
npm start # serve the production build (node server.mjs)
|
|
npm run preview # serve the production build via vite preview
|
|
npm run lint # eslint (currently broken: leftover Next.js config)
|
|
```
|
|
|
|
Copy `.env.example` to `.env` and fill in the Shopify Storefront token and
|
|
PocketBase superuser credentials (`.env*` is gitignored).
|
|
|
|
## Containers
|
|
|
|
The image is built with **podman** (`podman build` reads the same `Dockerfile`):
|
|
|
|
```bash
|
|
podman build -t mozimo-web .
|
|
podman run -p 3000:3000 -p 8090:8090 -v mozimo_pb_data:/app/pb_data mozimo-web
|
|
```
|
|
|
|
Production is **rootless podman + Quadlet** on the host `emily`: systemd user
|
|
services generated from `.container` units, fronted by that host's shared
|
|
Caddy. `./deploy.sh` builds the pushed commit with `make build-production` and
|
|
runs `ansible/playbook.yml`. See [`ansible/README.md`](ansible/README.md) for
|
|
the unit layout and the monoco → emily migration.
|
|
|
|
Three-stage build (`deps → builder → runner`) on pinned `node:26-trixie-slim`:
|
|
dependencies install with `npm ci` and BuildKit cache mounts, the final image
|
|
runs as the unprivileged `node` user, ships production dependencies, the build
|
|
output, PocketBase v0.40.2 (multi-arch) and `entrypoint.sh` which starts both
|
|
processes, ensures the PocketBase superuser matches `PB_SUPERUSER_EMAIL` /
|
|
`PB_SUPERUSER_PASSWORD` on every boot, and seeds the CMS schema + defaults.
|
|
Volumes: `/app/pb_data` holds the CMS database — back it up. Ports: `3000`
|
|
web, `8090` PocketBase (both reached by Caddy over the podman network, not
|
|
published on the host; see [`ansible/`](ansible/)).
|