Files
desfoto/AGENTS.md

117 lines
7.7 KiB
Markdown

# desfoto.de — Agent Guidelines
## What this repository is
The complete source of **https://desfoto.de**, the photography and video brand of
Denny Schulz (Neumünster). It is a hand-written static site: no framework, no CMS,
no runtime dependencies. `src/` holds Python builders and the editorial content,
`scripts/build-site.py` renders everything into the committed `site/` directory,
and nginx serves `site/` from a container behind the shared VPS Traefik instance.
`dennyschulz.de` is a **separate** site: nothing here may modify it, proxy it or
hotlink its assets. Linking out to it from the footer is an explicit operator
request and is the only permitted reference (see "Editing rules").
## Structure
- `/` — hero, the four **Arbeitsbereiche** (Fotografie, Video, Musik & Bühne, Projekte),
the bookable services ("Was du bei mir buchen kannst"), the portfolio gallery
("Was ich außerdem mache"), music video, mobiles Studio, process, facts.
- `/fotografie/` — the umbrella for all photographic work: Businessfotografie,
Portrait & Model, Familie, Minishootings, plus the mobiles Studio.
`/shootings`, `/shootings/` and `/shootings.html` are 301-redirected here by
`nginx.conf`, and the old `/fotografie.html` is canonicalised to `/fotografie/`.
- `/familie/` — family photography (no couples: Paare and Engagement stay on
`dennyschulz.de`). `/familien-und-paare`, `/familien-und-paare/` and
`/familien-und-paare.html` are 301-redirected here.
- `/musik-und-buehne/` — concerts, band portraits and the Thjódrörir music video.
- `/video/`, `/social-media/`, `/projekte/` (music + free work + pro bono), `/ueber/`.
## Commands
- `python3 scripts/fetch-assets.py` — download pool images, Google Fonts subsets and
licences into `assets-src/` (idempotent; `assets-src/` is not committed).
- `python3 scripts/build-site.py` — rebuild all of `site/`. Prints `build ok` and a
link/alt check; exits non-zero on any broken internal reference.
- `.ocauto/qa` — authoritative deterministic QA: builds twice to prove the output is
reproducible, runs `nginx -t` and `docker compose config`, then
`python3 -m unittest discover -s tests`.
- Local runtime: `docker compose -f compose.yml up -d` → http://127.0.0.1:18430
- UI smoke: `/home/king/bin/oc-ui-smoke http://127.0.0.1:18430/`
- Release: `/home/king/bin/oc-release /home/king/projects/desfoto "<msg>" <files...>`
## Editing rules
- **Content lives in `src/content.py` and `src/pages.py`; never hand-edit `site/`.**
`site/` is generated output that happens to be committed for deployment.
- Never invent facts. Prices, testimonials, client names, awards and biography dates
are intentionally absent; do not add them without verified information. Legal data
in `src/content.py:LEGAL` mirrors the operator's own Impressum (`§ 5 DDG`).
The portfolio must reflect the operator's real work (musicians, live shows, free
and artistic series, business shoots) — never invent trips, locations or jobs
(an earlier build captioned an image as a "Japanreise"; that was invented and must
not return).
- Keep the design language: paper/ink/rust/teal tokens from `src/theme.py`, rounded
cards, hairline dividers, Fraunces for display and Manrope for text.
- Images come from the operator's own pool through `src/images.json`. Only add an
entry if the operator holds the rights; always give a meaningful German `alt`.
`"source": "legacy"` marks images fetched from the operator's own site
(`dennyschulz.de/legacy/<file>`) — they are written into `assets-src/images/` and
never hotlinked at runtime. `alt` texts must come from the operator's own captions.
- The operator's original logo lives in `assets-src/brand/logo.png`; the build derives
`logo-160.png`, the favicons, the apple-touch icon and `favicon.svg` from it. Never
redraw or guess a replacement mark.
- The footer's "Netzwerk" column links to `https://www.dennyschulz.de/` and
`https://dennyapp.de/` because the operator asked for it; these are the only
external links allowed and they must keep `target="_blank" rel="noopener"`.
Nothing else may reference `dennyschulz.de` (no assets, no fetches, no rewriting).
- Animations are progressive enhancement: they live in CSS plus the guarded
`site.js` block, every page carries the `<noscript>` reveal fallback, and the
`prefers-reduced-motion` block must stay the **last** rule in `src/theme.py` so it
wins over the motion definitions.
- Never hide the element you observe: Chromium folds a target's own `clip-path` into
the `IntersectionObserver` geometry, so a closed mask never reports as visible and
the content stays hidden forever. `.reveal--mask` therefore clips its children.
- Never nest `<a>` inside `<a>` (or any other invalid nesting). The parser closes the
outer anchor and the surrounding grid falls apart — that is what happened to the
two homepage pillars the redesign replaced. Cards with their own links use the
stretched-link pattern (`.pillar__go::after`, `.link-arrow`) or keep the link in
the card foot; `tests/test_site.py` guards this for every page.
- **Brand separation:** `desfoto` is the brand, `Denny Schulz Fotografie` is only the
legal entity. The legal name may appear in the Impressum, the Datenschutz page and
the JSON-LD `legalName` — nowhere else. The footer carries exactly one ownership
sentence (`SITE["ownership"]`). Couples, engagements and weddings are advertised on
`dennyschulz.de` only; desfoto shows single-person portraits, family and business
work, and no couple imagery may be added back to `src/images.json`.
- `/fotografie/` is the umbrella term for the photographic work ("Shooting" is not a
section label any more). Renaming a route means updating `pages.ROUTES`,
`nginx.conf` (301 from the old URL) and `scripts/build-site.py:ROUTE_OG`;
the build prunes the retired page from `site/` automatically.
- Two-space indentation, `from __future__ import annotations`, type hints on public
functions, no new third-party Python packages (Pillow is the only dependency).
## Privacy contract
The `/datenschutz/` page makes concrete promises. Any change that would break one of
them is a defect, not a style question:
- no cookies, no `localStorage`, no analytics, no tracking;
- `access_log off` in `nginx.conf` — never enable request logging;
- `error_log /dev/null crit;` in `nginx.conf` — never write an error-log file either;
- fonts, scripts, styles and images are served from this origin only;
- YouTube is embedded exclusively via the click-to-load facade
(`youtube-nocookie.com`) — no iframe before the click, no preconnect;
- the contact form never posts anywhere; it composes a local `mailto:` draft
(the non-JS fallback is a `mailto:` form action, which also stays on the device).
`tests/test_site.py` enforces these promises; extend it when you add a new flow.
## Deployment
`.ocauto/deploy <sha>` ships the committed build to `/home/denny/stacks/desfoto` on
`prod-main`, snapshots the previous release into `/home/denny/stacks/desfoto-releases/`
and removes the obsolete `desfoto.de → dennyschulz.de` labels from `/srv/stack`.
`.ocauto/verify <sha>` proves the live site matches the release. Details, including
rollback, are in `docs/deployment.md`. Traefik is shared infrastructure: route through
labels, never restart or recreate the proxy.
The deploy replaces `site/` (new inode) and rewrites `nginx.conf`, so it runs
`docker compose up -d --force-recreate`: a container that is merely "running" keeps
the deleted directory bind-mounted and then serves an empty document root — every
path 404, while Compose reports no change and the stale health status still says
`healthy`. The recreate is followed by an explicit in-container request for `/`, and
`tests/test_site.py` guards the flag. Never remove either check.