7.7 KiB
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.htmlare 301-redirected here bynginx.conf, and the old/fotografie.htmlis canonicalised to/fotografie/./familie/— family photography (no couples: Paare and Engagement stay ondennyschulz.de)./familien-und-paare,/familien-und-paare/and/familien-und-paare.htmlare 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 intoassets-src/(idempotent;assets-src/is not committed).python3 scripts/build-site.py— rebuild all ofsite/. Printsbuild okand 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, runsnginx -tanddocker compose config, thenpython3 -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.pyandsrc/pages.py; never hand-editsite/.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:LEGALmirrors 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 Germanalt."source": "legacy"marks images fetched from the operator's own site (dennyschulz.de/legacy/<file>) — they are written intoassets-src/images/and never hotlinked at runtime.alttexts must come from the operator's own captions. - The operator's original logo lives in
assets-src/brand/logo.png; the build deriveslogo-160.png, the favicons, the apple-touch icon andfavicon.svgfrom it. Never redraw or guess a replacement mark. - The footer's "Netzwerk" column links to
https://www.dennyschulz.de/andhttps://dennyapp.de/because the operator asked for it; these are the only external links allowed and they must keeptarget="_blank" rel="noopener". Nothing else may referencedennyschulz.de(no assets, no fetches, no rewriting). - Animations are progressive enhancement: they live in CSS plus the guarded
site.jsblock, every page carries the<noscript>reveal fallback, and theprefers-reduced-motionblock must stay the last rule insrc/theme.pyso it wins over the motion definitions. - Never hide the element you observe: Chromium folds a target's own
clip-pathinto theIntersectionObservergeometry, so a closed mask never reports as visible and the content stays hidden forever..reveal--masktherefore 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.pyguards this for every page. - Brand separation:
desfotois the brand,Denny Schulz Fotografieis only the legal entity. The legal name may appear in the Impressum, the Datenschutz page and the JSON-LDlegalName— nowhere else. The footer carries exactly one ownership sentence (SITE["ownership"]). Couples, engagements and weddings are advertised ondennyschulz.deonly; desfoto shows single-person portraits, family and business work, and no couple imagery may be added back tosrc/images.json. /fotografie/is the umbrella term for the photographic work ("Shooting" is not a section label any more). Renaming a route means updatingpages.ROUTES,nginx.conf(301 from the old URL) andscripts/build-site.py:ROUTE_OG; the build prunes the retired page fromsite/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 offinnginx.conf— never enable request logging;error_log /dev/null crit;innginx.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 amailto: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.