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