Files
bar/README.md
T
valknar b3b9fb7ac6 Initial commit — Bar Pivoine cocktail recipe site
Hugo Extended site with 426 cocktail recipes from the open cocktail dataset.
Dark amber/gold editorial aesthetic, Tailwind CSS v4, Alpine.js client-side
search and filtering, HTMX page transitions, Docker + nginx production build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 11:53:45 +02:00

311 lines
11 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.
# Bar Pivoine
A moody, editorial cocktail recipe site. 426 recipes from the open cocktail dataset, served with AI-generated photography, client-side search, and a dark amber aesthetic.
Live at **[bar.pivoine.art](https://bar.pivoine.art)**
---
## Stack
| Layer | Technology |
|---|---|
| Site generator | Hugo Extended v0.154.3 |
| CSS | Tailwind CSS v4 via `@tailwindcss/postcss` |
| Interactivity | Alpine.js v3.14.8 (CDN) + HTMX v2.0.4 (CDN) |
| Page transitions | HTMX `hx-boost` + View Transitions API |
| Analytics | Umami (self-hosted at `umami.pivoine.art`) |
| Images | Hugo image processor (WebP srcset) + Replicate FLUX.2 pro |
| Package manager | pnpm |
| Container | Docker multi-stage → nginx:alpine |
---
## Project Structure
```
bar/
├── hugo.toml # Site config, taxonomies, params
├── package.json # pnpm scripts, Tailwind v4, Prettier
├── postcss.config.js # @tailwindcss/postcss + autoprefixer
├── Dockerfile # Multi-stage: node+hugo builder → nginx
├── nginx.conf # Gzip, cache headers, clean URLs, security headers
├── assets/
│ ├── css/main.css # Tailwind v4 @theme tokens + @layer components
│ └── js/main.js # Alpine cocktailSearch(), HTMX progress bar
├── content/
│ ├── _index.md # Homepage front matter
│ ├── recipes/
│ │ ├── _index.md # Archive front matter
│ │ └── {slug}/
│ │ ├── index.md # Cocktail page (426 bundles)
│ │ └── cocktail.webp # AI-generated image (optional)
├── layouts/
│ ├── _default/
│ │ ├── baseof.html # Base shell: HTMX, Alpine, Umami, progress bar
│ │ ├── list.html # Generic list fallback
│ │ ├── single.html # Generic single fallback
│ │ ├── terms.html # Taxonomy overview (e.g. /categories/)
│ │ └── term.html # Single taxonomy term (e.g. /categories/cocktail/)
│ ├── partials/
│ │ ├── head.html # SEO meta, OG, Twitter card, JSON-LD, fonts, CSS
│ │ ├── schema.html # JSON-LD: WebSite (home) + Recipe (detail pages)
│ │ ├── nav.html # Sticky header with cellar button
│ │ ├── footer.html # Taxonomy columns + brand + attribution
│ │ ├── mark.html # PeonyMark wreath SVG partial
│ │ ├── icon.html # SVG icon partial (search, arrows, chevron, x)
│ │ ├── img.html # Hugo WebP srcset generator
│ │ ├── cocktail-card.html # Reusable grid card
│ │ ├── pagination.html # Hugo paginator (prev/next + numbered pages)
│ │ └── schema.html # Structured data
│ ├── index.html # Homepage: hero, explore index, selected pours
│ ├── recipes/
│ │ ├── list.html # Archive: Alpine search + filter + pagination
│ │ └── single.html # Recipe detail: image, ingredients, method, related
│ └── 404.html
├── static/
│ ├── favicon.svg # PeonyMark wreath on dark rounded square
│ ├── images/
│ │ └── og-default.jpg # Default OG share image (1200×630)
│ └── site.webmanifest
└── scripts/
├── generate-content.mjs # CSV → Hugo content bundles
└── generate-images.mjs # Replicate FLUX.2 pro → cocktail.webp per recipe
```
---
## Getting Started
### Prerequisites
- [Hugo Extended](https://gohugo.io/installation/) v0.147+
- [Node.js](https://nodejs.org/) v22+
- [pnpm](https://pnpm.io/) v9+
### Development
```bash
pnpm install
pnpm dev # hugo server --buildDrafts --disableFastRender
```
The dev server starts at `http://localhost:1313` with live reload. Tailwind CSS is processed via Hugo Pipes on every change.
### Production build
```bash
pnpm build # NODE_ENV=production hugo --minify
```
Output goes to `public/`. CSS is fingerprinted and integrity-hashed.
---
## Content Generation
Cocktail content is generated from the [open cocktail dataset](https://www.kaggle.com/datasets/aadyasingh55/cocktails) (`prototype/uploads/final_cocktails.csv`).
### Generate recipe pages
```bash
pnpm generate:content
```
Reads the CSV and creates `content/recipes/{slug}/index.md` for every cocktail. Already-existing files are skipped (idempotent). Each page bundle includes:
- `title`, `date`, `description` (auto-generated)
- `alcoholic`, `categories`, `glasses`, `ingredients`, `ingredientMeasures`
- `drinkThumbnail` (original dataset URL, used as fallback)
- Body: preparation instructions
### Generate AI images
```bash
REPLICATE_API_TOKEN=r8_... pnpm generate:images
```
Calls Replicate's `black-forest-labs/flux-2-pro` model for each cocktail that doesn't yet have a `cocktail.webp`, using the dataset's reference photo as the image prompt input. Output is saved as a Hugo page resource alongside the content file so Hugo's image processor can generate WebP srcsets.
**Options:**
| Flag | Description |
|---|---|
| `--limit N` | Process only the first N cocktails |
| `--slug NAME` | Process a single cocktail by slug |
| `--concurrency N` | Parallel API requests (default: 3) |
| `--dry-run` | Log what would run without calling the API |
```bash
# Generate images for 10 cocktails to test
REPLICATE_API_TOKEN=r8_... node scripts/generate-images.mjs --limit 10
# Regenerate a single recipe
REPLICATE_API_TOKEN=r8_... node scripts/generate-images.mjs --slug margarita
```
---
## Design System
### Color tokens (`@theme` in `assets/css/main.css`)
| Token | Value | Usage |
|---|---|---|
| `--color-bg` | `#14100c` | Page background |
| `--color-bg-deep` | `#0d0a07` | Nav, footer |
| `--color-surface` | `#1c1611` | Cards, inputs |
| `--color-surface-2` | `#241c15` | Hover surfaces |
| `--color-warm` | `#e9d2b4` | Border opacity base (`border-warm/10`, `border-warm/18`) |
| `--color-ink` | `#efe6da` | Primary text |
| `--color-ink-soft` | `#c9bbab` | Secondary text |
| `--color-ink-mute` | `#8d8073` | Muted text |
| `--color-ink-faint` | `#5f574d` | Faint labels |
| `--color-gold` | `#cf9648` | Primary accent |
| `--color-gold-2` | `#e3ad5e` | Hover accent |
| `--color-gold-deep` | `#9c6a2c` | Deep accent |
### Typography
| Token | Font stack |
|---|---|
| `--font-serif` | Cormorant Garamond, Georgia, serif |
| `--font-sans` | Hanken Grotesk, system-ui, sans-serif |
| `--font-mono` | JetBrains Mono, ui-monospace, monospace |
All three are loaded from Google Fonts in `layouts/partials/head.html` using `display=swap`.
### Tailwind v4 conventions
- Border opacity via modifier: `border-warm/10``rgba(233,210,180,0.10)`
- Custom arbitrary values inline where one-off: `text-[clamp(44px,6.4vw,80px)]`
- `@layer components` only for styles requiring descendant selectors or pseudo-elements (`.card-wrap:hover .card-img`, `.frame-overlay::after`, `.arch-input::placeholder`)
- Everything else is Tailwind utility classes directly in HTML
---
## Taxonomies
Hugo taxonomies are defined in `hugo.toml`:
```toml
[taxonomies]
category = "categories"
glass = "glasses"
ingredient = "ingredients"
```
The `alcoholic` field (Alcoholic / Non alcoholic / Optional alcohol) is **not** a Hugo taxonomy — it is a plain front matter param. The archive page filters by it client-side via Alpine, linking to `/recipes/?alcoholic={slug}`.
---
## Client-side Search (`assets/js/main.js`)
The `/recipes/` archive embeds the full cocktail index as `window.__COCKTAILS__` (built at Hugo build time via `{{ $data | jsonify | safeJS }}`), then hands it to an Alpine.js component:
### `cocktailSearch()` component
| Property | Description |
|---|---|
| `q` | Free-text search query |
| `active` | Object of active filter slugs: `{ alcoholic, category, glass, ingredient }` |
| `page` | Current pagination page (1-indexed) |
| `perPage` | Cards per page (24) |
| `openFilter` | Which dropdown is open (`null` or key string) |
| `all` | Full cocktail array from `window.__COCKTAILS__` |
| `tax` | Computed facets `{ alcoholic[], category[], glass[], ingredient[] }` |
### `filtered` getter
Chains four slug-exact filters + a free-text substring match across name, category, glass, and all ingredient names.
### `buildTax(list)`
Computes taxonomy facets from the cocktail array — groups by value, counts occurrences, sorts by count descending. Produces the dropdown option lists.
### `taxSlug(s)`
Slugifies taxonomy values identically to Hugo's URL generation:
```js
String(s).toLowerCase()
.replace(/&/g, "and")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
```
### URL state
`pushState()` encodes active filters and current page into the URL query string (`?q=...&category=...&page=...`) so the search state is bookmarkable and shareable.
---
## Page Transitions
HTMX `hx-boost` intercepts all internal link clicks and replaces `#main-content` via XHR. The View Transitions API is wired in `assets/css/main.css` with `@keyframes page-in/page-out` for a crossfade. A CSS progress bar (`#progress-bar`) animates on `htmx:beforeRequest` / `htmx:afterSwap`.
---
## Docker
### Build and run
```bash
docker build -t bar-pivoine .
docker run -p 8080:80 bar-pivoine
```
The image is two stages:
1. **Builder** (`node:22-alpine`) — installs Hugo Extended binary, runs `pnpm install` and `hugo --minify`
2. **Production** (`nginx:alpine`) — copies `public/` into nginx, applies `nginx.conf`
### nginx features
- Gzip compression for HTML, CSS, JS, SVG, JSON
- `Cache-Control: no-store` for HTML; `immutable, max-age=31536000` for fingerprinted assets
- Security headers: `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`
- Clean URLs via `try_files $uri $uri/ $uri.html`
- Custom 404 page
---
## SEO
- Canonical URLs on every page
- `<meta name="description">` from page description or site default
- Open Graph (`og:title`, `og:description`, `og:image`, `og:type`)
- Twitter Card (`summary_large_image`)
- JSON-LD `WebSite` schema on the homepage
- JSON-LD `Recipe` schema on every cocktail detail page (name, ingredients, instructions, image, category)
- `robots.txt` generated by Hugo (`enableRobotsTXT = true`)
- XML sitemap at `/sitemap.xml`
---
## Code Formatting
```bash
pnpm format # Prettier write
pnpm format:check # Prettier check (CI)
```
Prettier is configured with `prettier-plugin-go-template` (Hugo HTML templates) and `prettier-plugin-toml`.
---
## Data Attribution
- Recipes: [Open Cocktail Dataset](https://www.kaggle.com/datasets/aadyasingh55/cocktails) (Kaggle / aadyasingh55)
- Imagery: AI-generated via [FLUX.2 pro](https://replicate.com/black-forest-labs/flux-2-pro) (Black Forest Labs / Replicate)
---
## License
Content is derived from the open cocktail dataset. Site code is © 2026 Bar Pivoine / pivoine.art.