docs: add comprehensive README
Covers stack, getting started, project structure, content model (posts + authors), image processing pipeline, design system (tokens, typography, components), templates, HTMX/Alpine interactivity, SEO, Docker/nginx/Traefik deployment, and content authoring workflow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
# PIVOINE
|
||||
|
||||
> *Where the machine dreams.*
|
||||
|
||||
An editorial magazine for AI-generated art and generative aesthetics — built on Hugo, styled with Tailwind v4, animated with Alpine.js and HTMX.
|
||||
|
||||
---
|
||||
|
||||
## Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|-------|-----------|
|
||||
| Static site generator | Hugo (extended) |
|
||||
| CSS | Tailwind CSS v4 via PostCSS |
|
||||
| Reactivity | Alpine.js v3 |
|
||||
| Navigation | HTMX v2 (boosted links + View Transitions) |
|
||||
| Fonts | Bebas Neue · Barlow · Share Tech Mono (Google Fonts) |
|
||||
| Analytics | Umami (cookie-free) |
|
||||
| Server | nginx (Docker) → Traefik → Coolify |
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
**Prerequisites:** Node.js 22+, pnpm, Hugo extended
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm dev # Hugo dev server with drafts, http://localhost:1313
|
||||
pnpm build # Production build → public/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
pivoine.art/
|
||||
├── assets/
|
||||
│ ├── css/main.css # Design tokens + all component styles
|
||||
│ └── js/main.js # HTMX progress bar, lazy-load videos
|
||||
├── content/
|
||||
│ ├── _index.md # Homepage front matter
|
||||
│ ├── about/index.md
|
||||
│ ├── imprint/index.md
|
||||
│ ├── authors/
|
||||
│ │ └── {slug}/
|
||||
│ │ ├── index.md # Author profile
|
||||
│ │ └── avatar.png # Page resource (auto-processed to WebP)
|
||||
│ └── posts/
|
||||
│ └── {slug}/
|
||||
│ ├── index.md # Post front matter
|
||||
│ ├── banner.png # Hero image (or 01.png as fallback)
|
||||
│ ├── 01.png # Gallery image
|
||||
│ ├── 01.mp4 # Optional companion video
|
||||
│ └── 02.png …
|
||||
├── layouts/
|
||||
│ ├── _default/ # baseof, list, single, terms
|
||||
│ ├── partials/ # head, nav, footer, cards, img, schema …
|
||||
│ ├── posts/single.html # Article template
|
||||
│ ├── authors/ # Author list + profile
|
||||
│ ├── categories/ tags/ # Taxonomy pages
|
||||
│ ├── index.html # Homepage
|
||||
│ └── 404.html
|
||||
├── static/
|
||||
│ ├── images/og-default.jpg
|
||||
│ └── favicon.* / site.webmanifest
|
||||
├── Dockerfile
|
||||
├── nginx.conf
|
||||
├── hugo.toml
|
||||
└── postcss.config.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Content Model
|
||||
|
||||
### Posts
|
||||
|
||||
```yaml
|
||||
title: "Hyperloop"
|
||||
description: "Short editorial summary shown in cards and meta tags."
|
||||
date: 2025-08-17
|
||||
author: "valknar" # must match content/authors/{slug}/
|
||||
featured: true # appears in homepage hero / secondary strip
|
||||
|
||||
categories: [Surreal]
|
||||
tags: [futuristic, vibrant, urban]
|
||||
|
||||
# Optional — only needed when not using page-bundle images
|
||||
banner:
|
||||
type: image # or "video"
|
||||
src: "/images/posts/…"
|
||||
alt: "Alt text"
|
||||
|
||||
background: # full-bleed blurred backdrop on article page
|
||||
type: image
|
||||
src: "/images/posts/…"
|
||||
alt: "Alt text"
|
||||
```
|
||||
|
||||
**Page-bundle images (preferred):**
|
||||
|
||||
Drop numbered images and optional companion videos alongside `index.md`:
|
||||
|
||||
```
|
||||
posts/hyperloop/
|
||||
├── index.md
|
||||
├── banner.png ← hero (detected automatically)
|
||||
├── 01.png ← gallery item 1
|
||||
├── 01.mp4 ← plays instead of 01.png if present
|
||||
├── 02.png
|
||||
└── 03.png …
|
||||
```
|
||||
|
||||
Hugo automatically resizes all bundle images to WebP at multiple widths — no manual optimisation needed.
|
||||
|
||||
### Authors
|
||||
|
||||
```yaml
|
||||
title: "Valknar"
|
||||
name: "Valknar"
|
||||
bio: "Short biography shown in bylines and author profile."
|
||||
social:
|
||||
instagram: "handle"
|
||||
x: "handle"
|
||||
```
|
||||
|
||||
Place `avatar.png` (or `.jpg`) in the same directory — it is converted to 96 × 96 px WebP automatically.
|
||||
|
||||
---
|
||||
|
||||
## Image Processing
|
||||
|
||||
All page-resource images are handled by `layouts/partials/img.html`, which converts to WebP and emits responsive `srcset`:
|
||||
|
||||
| Context | Widths generated | `sizes` hint |
|
||||
|---------|-----------------|-------------|
|
||||
| Post banner | 1200w · 1800w | `100vw` |
|
||||
| Gallery image | 800w · 1200w | `33vw` on desktop |
|
||||
| Featured card | 800w · 1200w | `50vw` on desktop |
|
||||
| Grid card thumbnail | 600w · 1000w | `25vw` on desktop |
|
||||
| Author avatar | 96px | — |
|
||||
|
||||
**Before / after example (hyperloop/banner.png):**
|
||||
|
||||
| Original PNG | WebP 1800w | WebP 600w |
|
||||
|-------------|-----------|----------|
|
||||
| 7.9 MB | 496 KB | 56 KB |
|
||||
|
||||
Processed images are cached in `resources/_gen/` (gitignored).
|
||||
|
||||
---
|
||||
|
||||
## Design System
|
||||
|
||||
### Colour Palette
|
||||
|
||||
```css
|
||||
/* Base — deep navy-black */
|
||||
--color-void: #050510
|
||||
--color-ink: #09091A
|
||||
--color-concrete: #0F0F22
|
||||
--color-zinc: #1C1C38
|
||||
|
||||
/* Neutrals */
|
||||
--color-fog: #6868A0
|
||||
--color-chalk: #B8B8E0
|
||||
--color-paper: #EEEEFF
|
||||
|
||||
/* Accent triad */
|
||||
--color-heat: #FF1A8C /* hot pink — primary */
|
||||
--color-pulse: #9B00FF /* electric purple — mid */
|
||||
--color-frost: #00C8FF /* ice blue — cool */
|
||||
```
|
||||
|
||||
### Typography
|
||||
|
||||
| Role | Font | Usage |
|
||||
|------|------|-------|
|
||||
| Display | Bebas Neue | Headlines, oversized decorative text |
|
||||
| Body | Barlow 300–600 | Editorial prose, UI copy |
|
||||
| Label | Share Tech Mono | Metadata, badges, specs |
|
||||
|
||||
### Component Classes
|
||||
|
||||
```
|
||||
.btn CTA button — clip-path skew, shimmer sweep on hover
|
||||
.btn-outline Transparent variant
|
||||
.btn-frost Ice blue variant
|
||||
|
||||
.badge Parallelogram sticker (multiple colour variants)
|
||||
.badge-outline Bordered, transparent
|
||||
|
||||
.card-comic Card with 1.5px border + lift + glow on hover
|
||||
.card-media Media container — zoom on hover
|
||||
|
||||
.gradient-line 1 px heat → pulse → frost divider
|
||||
.graffiti-tag Oversized hollow decorative text (text-stroke)
|
||||
.speed-lines Isometric triangle grid background
|
||||
.halftone Diamond crosshatch background
|
||||
.spray-bg Radial dot pattern
|
||||
|
||||
.text-gradient Static pink → purple → ice gradient text
|
||||
.text-gradient-animated Looping gradient (5 s)
|
||||
.label Monospace spec-sheet micro-text
|
||||
.prose-editorial Long-form article body styles
|
||||
.gutter-x Responsive horizontal padding (clamp 1 – 3 rem)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Templates
|
||||
|
||||
| Template | Route | Description |
|
||||
|----------|-------|-------------|
|
||||
| `index.html` | `/` | Hero (featured post) · secondary cards (3) · latest grid (8) · about strip |
|
||||
| `posts/single.html` | `/posts/{slug}/` | Article header · author byline · banner · prose · gallery · tags · related |
|
||||
| `_default/list.html` | `/posts/` | Paginated post grid (12/page) |
|
||||
| `authors/single.html` | `/authors/{slug}/` | Author profile · all posts by author |
|
||||
| `authors/list.html` | `/authors/` | Author grid |
|
||||
| `categories/single.html` | `/categories/{slug}/` | Posts by category |
|
||||
| `_default/terms.html` | `/categories/` `/tags/` | Badge cloud of all terms + counts |
|
||||
| `404.html` | `*` | Styled error page |
|
||||
|
||||
---
|
||||
|
||||
## Navigation & Interactivity
|
||||
|
||||
**HTMX** — `hx-boost` on `<body>` intercepts all internal links, swaps only `#main-content`, and pushes URL history. Combined with the CSS View Transitions API for smooth page animations.
|
||||
|
||||
**Alpine.js** — `$store.nav.path` drives active link highlighting. `$store.nav.open` toggles the mobile fullscreen overlay (teleported to `<body>` to escape header stacking context).
|
||||
|
||||
**Mobile menu** — animated hamburger → ✕, staggered link slide-in with `mob-link-in` keyframe.
|
||||
|
||||
---
|
||||
|
||||
## SEO & Meta
|
||||
|
||||
- Open Graph + Twitter Card on every page
|
||||
- JSON-LD structured data (`WebSite` on home, `BlogPosting` on articles)
|
||||
- `robots.txt` generated by Hugo (`enableRobotsTXT = true`)
|
||||
- `X-Robots-Tag: index, follow` set in nginx
|
||||
- Canonical URLs, `article:modified_time` from git history
|
||||
- Sitemap at `/sitemap.xml`
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Docker
|
||||
|
||||
```dockerfile
|
||||
# Stage 1 — build
|
||||
FROM node:22-alpine AS builder
|
||||
# installs Hugo + pnpm, runs pnpm build
|
||||
|
||||
# Stage 2 — serve
|
||||
FROM nginx:alpine
|
||||
COPY --from=builder /app/public /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
```
|
||||
|
||||
```bash
|
||||
docker build -t pivoine-art .
|
||||
docker run -p 8080:80 pivoine-art
|
||||
```
|
||||
|
||||
### nginx
|
||||
|
||||
- Gzip enabled for text, CSS, JS, SVG, JSON
|
||||
- Static assets: `Cache-Control: public, immutable` · 1 year
|
||||
- HTML: `Cache-Control: no-store, no-cache` (always fresh)
|
||||
- Clean URLs via `try_files $uri $uri/ $uri.html`
|
||||
|
||||
### Traefik / Coolify
|
||||
|
||||
The container sits behind Traefik (managed by Coolify). A global `security-headers` middleware in `stacks/traefik/dynamic/security.yaml` applies HSTS, CSP, and other headers to all services. A separate `no-index` middleware in the same file can be applied to internal services that should not be indexed.
|
||||
|
||||
---
|
||||
|
||||
## Adding Content
|
||||
|
||||
### New post
|
||||
|
||||
```bash
|
||||
# Create a leaf bundle
|
||||
mkdir -p content/posts/my-new-drop
|
||||
touch content/posts/my-new-drop/index.md
|
||||
|
||||
# Drop images alongside index.md
|
||||
cp ~/renders/banner.png content/posts/my-new-drop/
|
||||
cp ~/renders/01.png content/posts/my-new-drop/
|
||||
```
|
||||
|
||||
Fill in `index.md` front matter, run `pnpm dev`, and the post appears immediately. Hugo handles WebP conversion, srcset generation, and gallery detection automatically.
|
||||
|
||||
### New author
|
||||
|
||||
```bash
|
||||
mkdir -p content/authors/my-handle
|
||||
touch content/authors/my-handle/index.md
|
||||
cp ~/avatar.png content/authors/my-handle/
|
||||
```
|
||||
|
||||
Reference the author slug in any post's `author:` field.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings in `hugo.toml`:
|
||||
|
||||
```toml
|
||||
[params]
|
||||
description = "An editorial magazine for AI-generated art …"
|
||||
tagline = "Where the machine dreams."
|
||||
author = "Pivoine Editorial"
|
||||
logoText = "PIVOINE"
|
||||
twitterHandle = "pivoineArt"
|
||||
ogImage = "/images/og-default.jpg"
|
||||
copyrightYear = "2026"
|
||||
umamiSrc = "https://umami.pivoine.art/script.js"
|
||||
umamiId = "…"
|
||||
|
||||
[params.social]
|
||||
instagram = "https://instagram.com/…"
|
||||
x = "https://x.com/…"
|
||||
```
|
||||
|
||||
Menus are defined as `[[menus.main]]` and `[[menus.footer]]` arrays in the same file.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
© 2026 Pivoine Editorial. All rights reserved.
|
||||
Reference in New Issue
Block a user