125 lines
8.3 KiB
Markdown
125 lines
8.3 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, analytics or tracking. The standalone `/gtin/` tool is the only
|
|
`localStorage`/Cache API exception: it keeps its last 20 generated entries in
|
|
the browser and caches only its own offline app shell; it sends no tool data
|
|
to a server. Its `/gtin/impressum/` and `/gtin/datenschutz/` pages are
|
|
standalone, unlisted legal pages. The tool stays unlisted: no main-site
|
|
navigation or page links, no sitemap entry, and `noindex,nofollow`; only the
|
|
tool links to its own legal pages, which never link back to the main site;
|
|
- `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;
|
|
- the GTIN service worker is scoped to `/gtin/` and may not intercept other site
|
|
routes or delete caches owned by another application;
|
|
- 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.
|