diff --git a/.ocauto/verify b/.ocauto/verify index 7812a70..f422792 100755 --- a/.ocauto/verify +++ b/.ocauto/verify @@ -90,15 +90,21 @@ if [ "$live" = 1 ] && [ "$release_sha" != "unknown" ] && git -C "$root" rev-pars fi printf '\n== routes\n' -for route in / /shootings/ /businessfotografie/ /portrait-und-model/ /familien-und-paare/ \ - /minishootings/ /video/ /social-media/ /projekte/ /ueber/ /kontakt/ /impressum/ /datenschutz/; do +for route in / /fotografie/ /businessfotografie/ /portrait-und-model/ /familie/ \ + /minishootings/ /video/ /musik-und-buehne/ /social-media/ /projekte/ /ueber/ /kontakt/ /impressum/ /datenschutz/; do check_status "$base$route" 200 done # The retired photo URL must keep working: it is the umbrella page now. -check_redirect "$base/fotografie" "$base/shootings/" -check_redirect "$base/fotografie/" "$base/shootings/" -check_redirect "$base/fotografie.html" "$base/shootings/" +check_redirect "$base/shootings" "$base/fotografie/" +check_redirect "$base/shootings/" "$base/fotografie/" +check_redirect "$base/shootings.html" "$base/fotografie/" +check_redirect "$base/fotografie.html" "$base/fotografie/" + +# Familien & Paare was renamed: the remaining family work stays with desfoto. +check_redirect "$base/familien-und-paare" "$base/familie/" +check_redirect "$base/familien-und-paare/" "$base/familie/" +check_redirect "$base/familien-und-paare.html" "$base/familie/" printf '\n== canonical host and redirects\n' if [ "$live" = 1 ]; then @@ -117,13 +123,19 @@ esac fi printf '\n== content\n' -check_contains "$base/" 'href="/shootings/"' +check_contains "$base/" 'href="/fotografie/"' +check_contains "$base/" 'href="/musik-und-buehne/"' check_contains "$base/" 'href="/video/"' check_contains "$base/" "Mobiles Studio" check_contains "$base/" "Studio Neumünster" -check_not_contains "$base/" 'href="/fotografie/' -check_contains "$base/shootings/" "Autarke Blitzanlage" -check_contains "$base/shootings/" "Minishootings" +check_contains "$base/" "desfoto ist ein Angebot von Denny Schulz, Neumünster." +check_not_contains "$base/" 'href="/shootings' +check_not_contains "$base/" "Paarshooting" +check_contains "$base/fotografie/" "Autarke Blitzanlage" +check_contains "$base/fotografie/" "Minishootings" +check_contains "$base/familie/" "Familienshooting" +check_contains "$base/musik-und-buehne/" "Thjódrörir" +check_contains "$base/musik-und-buehne/" "Sagenbringer" check_contains "$base/impressum/" "§ 5 DDG" check_contains "$base/impressum/" "DE462149560" check_contains "$base/datenschutz/" "keine Zugriffsprotokolle" @@ -131,8 +143,8 @@ check_contains "$base/datenschutz/" "youtube-nocookie.com" check_contains "$base/video/" "NWWFTf7l8g0" check_contains "$base/" "https://www.dennyschulz.de/" check_contains "$base/" "https://dennyapp.de/" -check_contains "$base/sitemap.xml" "https://desfoto.de/shootings/" -check_not_contains "$base/sitemap.xml" "https://desfoto.de/fotografie/" +check_contains "$base/sitemap.xml" "https://desfoto.de/fotografie/" +check_not_contains "$base/sitemap.xml" "https://desfoto.de/shootings/" printf '\n== error handling and metadata\n' check_status "$base/diese-seite-gibt-es-nicht/" 404 @@ -143,20 +155,31 @@ check_status "$base/robots.txt" 200 printf '\n== assets\n' check_status "$base/assets/img/logo-160.png" 200 check_status "$base/assets/img/favicon-32.png" 200 -check_status "$base/assets/img/og-shootings.jpg" 200 +check_status "$base/assets/img/og-fotografie.jpg" 200 check_status "$base/assets/site.css" 200 check_status "$base/assets/fonts/fraunces-latin.woff2" 200 # The portfolio expansion ships the operator's own public work... check_status "$base/assets/img/music-dreadlocks-1200.webp" 200 check_status "$base/assets/img/business-hotel-window-1200.webp" 200 -check_status "$base/assets/img/couple-park-1200.webp" 200 +check_status "$base/assets/img/free-portrait-light-1200.webp" 200 check_status "$base/assets/img/editorial-leap-1200.webp" 200 # ...and the wedding pictures showing the guests stay removed. check_status "$base/assets/img/group-feier-1200.webp" 404 check_status "$base/assets/img/group-outdoor-1200.webp" 404 check_status "$base/assets/img/group-posiert-1200.webp" 404 +# Couple imagery and the studio shot with the old business card ship nowhere. +check_status "$base/assets/img/couple-park-1200.webp" 404 +check_status "$base/assets/img/portrait-couple-1200.webp" 404 +check_status "$base/assets/img/studio-setup-1200.webp" 404 +# Brautpaar-Bilder sind komplett aus dem Bestand: Fotografie- und Eventbilder +# mit Paaren gehören zu dennyschulz.de, nicht zu desfoto. +check_status "$base/assets/img/portrait-natural-1200.webp" 404 +check_status "$base/assets/img/event-motion-1200.webp" 404 +check_status "$base/assets/img/event-dance-1200.webp" 404 +check_contains "$base/minishootings/" "free-modern-portrait-1200.webp" +check_contains "$base/social-media/" "event-speaker-1200.webp" check_contains "$base/projekte/" "music-dreadlocks-1200.webp" -check_contains "$base/familien-und-paare/" "couple-park-1200.webp" +check_contains "$base/familie/" "free-portrait-light-1200.webp" printf '\n== privacy promises and headers\n' check_header "$base/" "Strict-Transport-Security" diff --git a/AGENTS.md b/AGENTS.md index 1e2a8c0..abe5726 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,11 +12,17 @@ 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, two pillars (**Shootings** = photo, **Videoprojekte** = video), portfolio - gallery, music video, mobiles Studio, process, facts. -- `/shootings/` — the umbrella for all photographic work: Businessfotografie, - Portrait & Model, Familien & Paare, Minishootings, plus the mobiles Studio. - `/fotografie` and `/fotografie/` are 301-redirected here by `nginx.conf`. +- `/` — 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 @@ -64,8 +70,19 @@ request and is the only permitted reference (see "Editing rules"). the content stays hidden forever. `.reveal--mask` therefore clips its children. - Never nest `` inside `` (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. Cards with their own links use the stretched-link pattern - (`.pillar__go::after`); `tests/test_site.py` guards this for every page. + 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). diff --git a/nginx.conf b/nginx.conf index 9145e57..0e5d471 100644 --- a/nginx.conf +++ b/nginx.conf @@ -64,13 +64,22 @@ server { location = /impressum.html { return 301 /impressum/; } location = /datenschutz.html { return 301 /datenschutz/; } location = /kontakt.html { return 301 /kontakt/; } + # /fotografie.html used to redirect to /shootings/; it resolves to its own + # canonical page now instead of turning into a 404. + location = /fotografie.html { return 301 /fotografie/; } - # Shootings is the umbrella term for the photographic work; the old - # /fotografie/ URL stays reachable and points to the new page. + # Fotografie is the umbrella term for the photographic work; the old + # /shootings/ URL stays reachable and points to the new page. # (/fotografie/index.html is already canonicalised to /fotografie/ above.) - location = /fotografie { return 301 /shootings/; } - location = /fotografie/ { return 301 /shootings/; } - location = /fotografie.html { return 301 /shootings/; } + location = /shootings { return 301 /fotografie/; } + location = /shootings/ { return 301 /fotografie/; } + location = /shootings.html { return 301 /fotografie/; } + + # Familie keeps the photographic range that stays with desfoto; the old + # combined Familien-und-Paare URL points here. + location = /familien-und-paare { return 301 /familie/; } + location = /familien-und-paare/ { return 301 /familie/; } + location = /familien-und-paare.html { return 301 /familie/; } location ^~ /.well-known/ { allow all; diff --git a/scripts/build-site.py b/scripts/build-site.py index 0ba47b5..ad79c5d 100755 --- a/scripts/build-site.py +++ b/scripts/build-site.py @@ -48,15 +48,16 @@ ACCENT = (192, 67, 28) # OpenGraph image per route, built from the page's own key visual. ROUTE_OG = { "/": ("music-vocal-blue", "og-desfoto.jpg", "Bilder und Filme, die nicht beliebig aussehen."), - "/shootings/": ("business-portrait-studio", "og-shootings.jpg", "Shootings aus Neumünster"), + "/fotografie/": ("business-portrait-studio", "og-fotografie.jpg", "Fotografie aus Neumünster"), "/businessfotografie/": ("business-portrait-studio", "og-businessfotografie.jpg", "Businessfotografie"), "/portrait-und-model/": ("editorial-colour", "og-portrait.jpg", "Portrait & Model"), - "/familien-und-paare/": ("people-joy", "og-familien.jpg", "Familien & Paare"), - "/minishootings/": ("portrait-natural", "og-minishootings.jpg", "Minishootings"), + "/familie/": ("free-portrait-light", "og-familie.jpg", "Familienfotografie"), + "/minishootings/": ("free-modern-portrait", "og-minishootings.jpg", "Minishootings"), "/video/": ("music-guitar-silhouette", "og-video.jpg", "Videoprojekte"), - "/social-media/": ("event-motion", "og-social-media.jpg", "Social-Media-Inhalte"), + "/musik-und-buehne/": ("music-vocal-blue", "og-musik-und-buehne.jpg", "Musik & Bühne"), + "/social-media/": ("event-speaker", "og-social-media.jpg", "Social-Media-Inhalte"), "/projekte/": ("free-mountain-view", "og-projekte.jpg", "Eigene Projekte"), - "/ueber/": ("studio-setup", "og-ueber.jpg", "Über desfoto"), + "/ueber/": ("gear-cases", "og-ueber.jpg", "Über desfoto"), "/kontakt/": ("event-venue", "og-kontakt.jpg", "Kontakt"), "/impressum/": ("gear-cases", "og-impressum.jpg", "Impressum"), "/datenschutz/": ("event-venue", "og-datenschutz.jpg", "Datenschutz"), @@ -563,6 +564,24 @@ def link_check(routes: list[tuple[str, str]]) -> list[str]: return problems +def prune_stale_pages(routes: list[tuple]) -> None: + """Remove built pages that no longer belong to the route table. + + The build writes into the existing tree, so a renamed route would otherwise + leave its old HTML (and its retired image srcsets) behind and the link check + would keep failing on files nobody serves any more. + """ + expected = {SITE / route[1] for route in routes} | {SITE / "404.html"} + for html in sorted(SITE.rglob("*.html")): + if html in expected: + continue + print(f" [pages] removed stale {html.relative_to(SITE)}") + html.unlink() + parent = html.parent + if parent != SITE and not any(parent.iterdir()): + parent.rmdir() + + def normalize_permissions() -> None: """Make the build output readable for the serving nginx worker. @@ -587,6 +606,7 @@ def main() -> int: build_brand_assets(table) print("== pages ==") routes = build_html(table) + prune_stale_pages(routes) build_meta_files(routes) build_script() print("== checks ==") diff --git a/site/404.html b/site/404.html index e3e2519..e4b68b1 100644 --- a/site/404.html +++ b/site/404.html @@ -42,7 +42,7 @@ - + @@ -69,23 +69,29 @@ desfotoFotografie & Video - +
-

Shootings

+

Fotografie

-

Video & mehr

+

Video & Bühne

+
+
+

Mehr

+ @@ -101,7 +107,7 @@

Netzwerk

- +
diff --git a/site/assets/img/couple-backlight-kiss-1200.webp b/site/assets/img/couple-backlight-kiss-1200.webp deleted file mode 100644 index 61d751a..0000000 Binary files a/site/assets/img/couple-backlight-kiss-1200.webp and /dev/null differ diff --git a/site/assets/img/couple-backlight-kiss-1600.webp b/site/assets/img/couple-backlight-kiss-1600.webp deleted file mode 100644 index 11cc459..0000000 Binary files a/site/assets/img/couple-backlight-kiss-1600.webp and /dev/null differ diff --git a/site/assets/img/couple-backlight-kiss-480.webp b/site/assets/img/couple-backlight-kiss-480.webp deleted file mode 100644 index 73d67e9..0000000 Binary files a/site/assets/img/couple-backlight-kiss-480.webp and /dev/null differ diff --git a/site/assets/img/couple-backlight-kiss-768.webp b/site/assets/img/couple-backlight-kiss-768.webp deleted file mode 100644 index ea2fd6f..0000000 Binary files a/site/assets/img/couple-backlight-kiss-768.webp and /dev/null differ diff --git a/site/assets/img/couple-forest-walk-1200.webp b/site/assets/img/couple-forest-walk-1200.webp deleted file mode 100644 index ed44479..0000000 Binary files a/site/assets/img/couple-forest-walk-1200.webp and /dev/null differ diff --git a/site/assets/img/couple-forest-walk-1600.webp b/site/assets/img/couple-forest-walk-1600.webp deleted file mode 100644 index 8b49409..0000000 Binary files a/site/assets/img/couple-forest-walk-1600.webp and /dev/null differ diff --git a/site/assets/img/couple-forest-walk-480.webp b/site/assets/img/couple-forest-walk-480.webp deleted file mode 100644 index e6fb4de..0000000 Binary files a/site/assets/img/couple-forest-walk-480.webp and /dev/null differ diff --git a/site/assets/img/couple-forest-walk-768.webp b/site/assets/img/couple-forest-walk-768.webp deleted file mode 100644 index 3f9f6bd..0000000 Binary files a/site/assets/img/couple-forest-walk-768.webp and /dev/null differ diff --git a/site/assets/img/couple-laugh-walk-1200.webp b/site/assets/img/couple-laugh-walk-1200.webp deleted file mode 100644 index db35a0e..0000000 Binary files a/site/assets/img/couple-laugh-walk-1200.webp and /dev/null differ diff --git a/site/assets/img/couple-laugh-walk-1600.webp b/site/assets/img/couple-laugh-walk-1600.webp deleted file mode 100644 index f2044cf..0000000 Binary files a/site/assets/img/couple-laugh-walk-1600.webp and /dev/null differ diff --git a/site/assets/img/couple-laugh-walk-480.webp b/site/assets/img/couple-laugh-walk-480.webp deleted file mode 100644 index 9a16a36..0000000 Binary files a/site/assets/img/couple-laugh-walk-480.webp and /dev/null differ diff --git a/site/assets/img/couple-laugh-walk-768.webp b/site/assets/img/couple-laugh-walk-768.webp deleted file mode 100644 index f10b226..0000000 Binary files a/site/assets/img/couple-laugh-walk-768.webp and /dev/null differ diff --git a/site/assets/img/couple-park-1200.webp b/site/assets/img/couple-park-1200.webp deleted file mode 100644 index cdb35fb..0000000 Binary files a/site/assets/img/couple-park-1200.webp and /dev/null differ diff --git a/site/assets/img/couple-park-1600.webp b/site/assets/img/couple-park-1600.webp deleted file mode 100644 index d3ddf58..0000000 Binary files a/site/assets/img/couple-park-1600.webp and /dev/null differ diff --git a/site/assets/img/couple-park-480.webp b/site/assets/img/couple-park-480.webp deleted file mode 100644 index 50053e5..0000000 Binary files a/site/assets/img/couple-park-480.webp and /dev/null differ diff --git a/site/assets/img/couple-park-768.webp b/site/assets/img/couple-park-768.webp deleted file mode 100644 index debdb44..0000000 Binary files a/site/assets/img/couple-park-768.webp and /dev/null differ diff --git a/site/assets/img/couple-sun-silhouette-1200.webp b/site/assets/img/couple-sun-silhouette-1200.webp deleted file mode 100644 index ea73ebe..0000000 Binary files a/site/assets/img/couple-sun-silhouette-1200.webp and /dev/null differ diff --git a/site/assets/img/couple-sun-silhouette-1600.webp b/site/assets/img/couple-sun-silhouette-1600.webp deleted file mode 100644 index 93370d8..0000000 Binary files a/site/assets/img/couple-sun-silhouette-1600.webp and /dev/null differ diff --git a/site/assets/img/couple-sun-silhouette-480.webp b/site/assets/img/couple-sun-silhouette-480.webp deleted file mode 100644 index e574594..0000000 Binary files a/site/assets/img/couple-sun-silhouette-480.webp and /dev/null differ diff --git a/site/assets/img/couple-sun-silhouette-768.webp b/site/assets/img/couple-sun-silhouette-768.webp deleted file mode 100644 index 7965723..0000000 Binary files a/site/assets/img/couple-sun-silhouette-768.webp and /dev/null differ diff --git a/site/assets/img/event-dance-1200.webp b/site/assets/img/event-dance-1200.webp deleted file mode 100644 index c798a2c..0000000 Binary files a/site/assets/img/event-dance-1200.webp and /dev/null differ diff --git a/site/assets/img/event-dance-1600.webp b/site/assets/img/event-dance-1600.webp deleted file mode 100644 index 5d2be36..0000000 Binary files a/site/assets/img/event-dance-1600.webp and /dev/null differ diff --git a/site/assets/img/event-dance-480.webp b/site/assets/img/event-dance-480.webp deleted file mode 100644 index b8c7b2e..0000000 Binary files a/site/assets/img/event-dance-480.webp and /dev/null differ diff --git a/site/assets/img/event-dance-768.webp b/site/assets/img/event-dance-768.webp deleted file mode 100644 index 9289c36..0000000 Binary files a/site/assets/img/event-dance-768.webp and /dev/null differ diff --git a/site/assets/img/event-motion-1200.webp b/site/assets/img/event-motion-1200.webp deleted file mode 100644 index 88468b3..0000000 Binary files a/site/assets/img/event-motion-1200.webp and /dev/null differ diff --git a/site/assets/img/event-motion-1600.webp b/site/assets/img/event-motion-1600.webp deleted file mode 100644 index c0e1174..0000000 Binary files a/site/assets/img/event-motion-1600.webp and /dev/null differ diff --git a/site/assets/img/event-motion-480.webp b/site/assets/img/event-motion-480.webp deleted file mode 100644 index 92dd508..0000000 Binary files a/site/assets/img/event-motion-480.webp and /dev/null differ diff --git a/site/assets/img/event-motion-768.webp b/site/assets/img/event-motion-768.webp deleted file mode 100644 index 0a779ad..0000000 Binary files a/site/assets/img/event-motion-768.webp and /dev/null differ diff --git a/site/assets/img/og-familie.jpg b/site/assets/img/og-familie.jpg new file mode 100644 index 0000000..bb72d26 Binary files /dev/null and b/site/assets/img/og-familie.jpg differ diff --git a/site/assets/img/og-familien.jpg b/site/assets/img/og-familien.jpg deleted file mode 100644 index f00fce7..0000000 Binary files a/site/assets/img/og-familien.jpg and /dev/null differ diff --git a/site/assets/img/og-fotografie.jpg b/site/assets/img/og-fotografie.jpg new file mode 100644 index 0000000..914f2ca Binary files /dev/null and b/site/assets/img/og-fotografie.jpg differ diff --git a/site/assets/img/og-minishootings.jpg b/site/assets/img/og-minishootings.jpg index e387a16..b70503d 100644 Binary files a/site/assets/img/og-minishootings.jpg and b/site/assets/img/og-minishootings.jpg differ diff --git a/site/assets/img/og-musik-und-buehne.jpg b/site/assets/img/og-musik-und-buehne.jpg new file mode 100644 index 0000000..7db7986 Binary files /dev/null and b/site/assets/img/og-musik-und-buehne.jpg differ diff --git a/site/assets/img/og-shootings.jpg b/site/assets/img/og-shootings.jpg deleted file mode 100644 index 0e2f282..0000000 Binary files a/site/assets/img/og-shootings.jpg and /dev/null differ diff --git a/site/assets/img/og-social-media.jpg b/site/assets/img/og-social-media.jpg index 7bae691..4136f43 100644 Binary files a/site/assets/img/og-social-media.jpg and b/site/assets/img/og-social-media.jpg differ diff --git a/site/assets/img/og-ueber.jpg b/site/assets/img/og-ueber.jpg index 540b0e6..f54376e 100644 Binary files a/site/assets/img/og-ueber.jpg and b/site/assets/img/og-ueber.jpg differ diff --git a/site/assets/img/people-joy-1200.webp b/site/assets/img/people-joy-1200.webp deleted file mode 100644 index e4622d1..0000000 Binary files a/site/assets/img/people-joy-1200.webp and /dev/null differ diff --git a/site/assets/img/people-joy-1600.webp b/site/assets/img/people-joy-1600.webp deleted file mode 100644 index a16f8f4..0000000 Binary files a/site/assets/img/people-joy-1600.webp and /dev/null differ diff --git a/site/assets/img/people-joy-480.webp b/site/assets/img/people-joy-480.webp deleted file mode 100644 index e25cde5..0000000 Binary files a/site/assets/img/people-joy-480.webp and /dev/null differ diff --git a/site/assets/img/people-joy-768.webp b/site/assets/img/people-joy-768.webp deleted file mode 100644 index c117688..0000000 Binary files a/site/assets/img/people-joy-768.webp and /dev/null differ diff --git a/site/assets/img/people-kiss-1200.webp b/site/assets/img/people-kiss-1200.webp deleted file mode 100644 index 8a18455..0000000 Binary files a/site/assets/img/people-kiss-1200.webp and /dev/null differ diff --git a/site/assets/img/people-kiss-1600.webp b/site/assets/img/people-kiss-1600.webp deleted file mode 100644 index 2a10fb1..0000000 Binary files a/site/assets/img/people-kiss-1600.webp and /dev/null differ diff --git a/site/assets/img/people-kiss-480.webp b/site/assets/img/people-kiss-480.webp deleted file mode 100644 index 8e8aaba..0000000 Binary files a/site/assets/img/people-kiss-480.webp and /dev/null differ diff --git a/site/assets/img/people-kiss-768.webp b/site/assets/img/people-kiss-768.webp deleted file mode 100644 index 09ad2db..0000000 Binary files a/site/assets/img/people-kiss-768.webp and /dev/null differ diff --git a/site/assets/img/portrait-couple-1200.webp b/site/assets/img/portrait-couple-1200.webp deleted file mode 100644 index 576cff1..0000000 Binary files a/site/assets/img/portrait-couple-1200.webp and /dev/null differ diff --git a/site/assets/img/portrait-couple-1600.webp b/site/assets/img/portrait-couple-1600.webp deleted file mode 100644 index 754da45..0000000 Binary files a/site/assets/img/portrait-couple-1600.webp and /dev/null differ diff --git a/site/assets/img/portrait-couple-480.webp b/site/assets/img/portrait-couple-480.webp deleted file mode 100644 index 477f3cb..0000000 Binary files a/site/assets/img/portrait-couple-480.webp and /dev/null differ diff --git a/site/assets/img/portrait-couple-768.webp b/site/assets/img/portrait-couple-768.webp deleted file mode 100644 index 0c46dde..0000000 Binary files a/site/assets/img/portrait-couple-768.webp and /dev/null differ diff --git a/site/assets/img/portrait-natural-1200.webp b/site/assets/img/portrait-natural-1200.webp deleted file mode 100644 index 5b8fd02..0000000 Binary files a/site/assets/img/portrait-natural-1200.webp and /dev/null differ diff --git a/site/assets/img/portrait-natural-1600.webp b/site/assets/img/portrait-natural-1600.webp deleted file mode 100644 index aec3350..0000000 Binary files a/site/assets/img/portrait-natural-1600.webp and /dev/null differ diff --git a/site/assets/img/portrait-natural-480.webp b/site/assets/img/portrait-natural-480.webp deleted file mode 100644 index 326d690..0000000 Binary files a/site/assets/img/portrait-natural-480.webp and /dev/null differ diff --git a/site/assets/img/portrait-natural-768.webp b/site/assets/img/portrait-natural-768.webp deleted file mode 100644 index cc4477f..0000000 Binary files a/site/assets/img/portrait-natural-768.webp and /dev/null differ diff --git a/site/assets/img/studio-setup-1200.webp b/site/assets/img/studio-setup-1200.webp deleted file mode 100644 index 0e24a8f..0000000 Binary files a/site/assets/img/studio-setup-1200.webp and /dev/null differ diff --git a/site/assets/img/studio-setup-1600.webp b/site/assets/img/studio-setup-1600.webp deleted file mode 100644 index 44e1422..0000000 Binary files a/site/assets/img/studio-setup-1600.webp and /dev/null differ diff --git a/site/assets/img/studio-setup-480.webp b/site/assets/img/studio-setup-480.webp deleted file mode 100644 index a8c4a09..0000000 Binary files a/site/assets/img/studio-setup-480.webp and /dev/null differ diff --git a/site/assets/img/studio-setup-768.webp b/site/assets/img/studio-setup-768.webp deleted file mode 100644 index fe711bf..0000000 Binary files a/site/assets/img/studio-setup-768.webp and /dev/null differ diff --git a/site/assets/site.css b/site/assets/site.css index 3287160..8224246 100644 --- a/site/assets/site.css +++ b/site/assets/site.css @@ -195,6 +195,11 @@ img.frame{width:100%;height:auto;object-fit:cover;transition:transform .8s var(- .grid--2{grid-template-columns:repeat(auto-fit,minmax(19rem,1fr))} .grid--3{grid-template-columns:repeat(auto-fit,minmax(16.5rem,1fr))} .grid--4{grid-template-columns:repeat(auto-fit,minmax(13.5rem,1fr))} +/* Vier Arbeitsbereiche: vier Spalten auf großen, zwei auf mittleren und eine + auf kleinen Schirmen. auto-fit würde bei mittlerer Breite 3+1 Karten setzen. */ +.grid--areas{grid-template-columns:1fr} +@media (min-width:40rem){.grid--areas{grid-template-columns:repeat(2,minmax(0,1fr))}} +@media (min-width:72rem){.grid--areas{grid-template-columns:repeat(4,minmax(0,1fr))}} .card{display:flex;flex-direction:column;gap:1rem;height:100%} .card__title{display:flex;align-items:baseline;gap:.6rem} .card__num{font-size:.72rem;font-weight:600;letter-spacing:.16em;color:var(--accent)} diff --git a/site/businessfotografie/index.html b/site/businessfotografie/index.html index 6fbe2b3..1df0342 100644 --- a/site/businessfotografie/index.html +++ b/site/businessfotografie/index.html @@ -44,7 +44,7 @@ - + @@ -59,7 +59,7 @@

Portraits, Teams, Standorte und Produkte — abgestimmt auf die Flächen, auf denen sie später stehen: Website, LinkedIn, Kampagne, Stellenanzeige.

Termin anfragen
-
Typische Aufträge

Wofür Unternehmen mich buchen

Portraits & Profilbilder

Einzelpersonen vor ruhigem Hintergrund oder im eigenen Arbeitsumfeld. Mehrere Varianten, damit Website und Profil nicht dasselbe Bild zeigen.

Teams & Standort

Menschen bei der Arbeit, Räume, Details. Zeigt, wie es bei euch wirklich aussieht — ohne Stockfoto-Gefühl.

Produkt & Detail

Sauber ausgeleuchtete Produkte für Shop, Katalog und Anzeigen, auch mit Szenenbildern.

Recruiting & Arbeitgebermarke

Bildpakete für Stellenanzeigen und Karriereseiten, die Bewerberinnen und Bewerber vorher sehen lassen, was sie erwartet.

Presse & Kampagne

Bildstrecken für Pressemitteilungen, Anzeigen und Social-Media-Kampagnen, inklusive der passenden Formate.

Events & Messen

Dokumentarische Begleitung von Firmenfeiern, Tagungen und Messeauftritten.

Studio

Im Studio in Neumünster oder bei dir vor Ort

Projekte können im eigenen Studio in Neumünster fotografiert werden — oder ich baue das mobile Studio inklusive autarker Blitzanlage dort auf, wo deine Leute sind.

Studioequipment mit Logo von Denny Schulz FotografieAutarke BlitzanlageHintergründe, Blitze, Lichtformer

Studio Neumünster

Ein eigenes Studio in Neumünster steht für Shootings bereit: kontrolliertes Licht, ruhiger Hintergrund und Platz für Konzeptarbeit. Gut für Portraits, Businessbilder und alles, was eine wiederholbare Optik braucht — unabhängig vom Wetter.

Mobiles Studio

Nicht jedes Team kann für ein Shooting den Betrieb verlassen. Deshalb baue ich das Studio dort auf, wo deine Leute sowieso sind: im Besprechungsraum, in der Werkstatt, im Vereinsheim oder draußen vor der Halle.

Dabei ist die komplette Ausrüstung dabei — Hintergründe, Blitze, Lichtformer und Ersatztechnik. Die Blitzanlage ist autark, ich bin vor Ort also sofort einsatzbereit und brauche nichts aus deinem Haushalt.

Komplette Ausrüstung

Kamera, Objektive, Hintergründe, Blitze und Lichtformer kommen mit.

Autarke Blitzanlage

Das Licht ist im Gepäck und in wenigen Minuten aufgebaut.

Dein Ort, deine Zeit

Geschossen wird zwischen zwei Terminen, in der Mittagspause oder nach Feierabend.

Überall in SH & Hamburg

Vom Büro in Neumünster bis zum Betrieb in Kiel, Lübeck oder Hamburg.

Zusammenarbeit

Was ich von euch brauche

Ein kurzer Call oder eine E-Mail mit drei Angaben reicht für ein verbindliches Angebot.

  • Wofür die Bilder gebraucht werden (Website, Print, Social, Presse)
  • Wie viele Personen oder Produkte fotografiert werden
  • Ort und Wunschzeitraum — bei euch, im Studio oder draußen
  • Ob einheitliche Hintergründe oder zusätzliche Retusche gebraucht werden
Nutzungsrechte: Wir halten vorab fest, für welche Kanäle und wie lange die Bilder genutzt werden dürfen. Das schützt euch und mich — und ihr bekommt genau die Rechte, die ihr braucht.

Nach dem Shooting bekommt ihr eine Auswahlgalerie, in der ihr kommentieren und freigeben könnt. Erst danach geht es in die finale Bearbeitung.

Beispiele

Aus der Business- und Studiopraxis

Portrait eines Mannes vor dunklem Hintergrund
Sitzendes Portrait im weichen Studiolicht
Drei Frauen in Bademänteln und Handtuchturbanen am Fenster
Drei Frauen mit Teetassen und Handtuchturbanen
Kamerakoffer und Objektive vor einem Studiorack
Studioequipment mit Logo von Denny Schulz Fotografie
Frauen im Bademantel halten Orangenscheiben vor ihre Gesichter
Drei Frauen lachen mit Orangenscheiben in den Händen
+
Typische Aufträge

Wofür Unternehmen mich buchen

Portraits & Profilbilder

Einzelpersonen vor ruhigem Hintergrund oder im eigenen Arbeitsumfeld. Mehrere Varianten, damit Website und Profil nicht dasselbe Bild zeigen.

Teams & Standort

Menschen bei der Arbeit, Räume, Details. Zeigt, wie es bei euch wirklich aussieht — ohne Stockfoto-Gefühl.

Produkt & Detail

Sauber ausgeleuchtete Produkte für Shop, Katalog und Anzeigen, auch mit Szenenbildern.

Recruiting & Arbeitgebermarke

Bildpakete für Stellenanzeigen und Karriereseiten, die Bewerberinnen und Bewerber vorher sehen lassen, was sie erwartet.

Presse & Kampagne

Bildstrecken für Pressemitteilungen, Anzeigen und Social-Media-Kampagnen, inklusive der passenden Formate.

Events & Messen

Dokumentarische Begleitung von Firmenfeiern, Tagungen und Messeauftritten.

Studio

Im Studio in Neumünster oder bei dir vor Ort

Projekte können im eigenen Studio in Neumünster fotografiert werden — oder ich baue das mobile Studio inklusive autarker Blitzanlage dort auf, wo deine Leute sind.

Kameraobjektiv im StudiolichtAutarke BlitzanlageHintergründe, Blitze, Lichtformer

Studio Neumünster

Ein eigenes Studio in Neumünster steht für Fototermine bereit: kontrolliertes Licht, ruhiger Hintergrund und Platz für Konzeptarbeit. Gut für Portraits, Businessbilder und alles, was eine wiederholbare Optik braucht — unabhängig vom Wetter.

Mobiles Studio

Nicht jedes Team kann für ein Shooting den Betrieb verlassen. Deshalb baue ich das Studio dort auf, wo deine Leute sowieso sind: im Besprechungsraum, in der Werkstatt, im Vereinsheim oder draußen vor der Halle.

Dabei ist die komplette Ausrüstung dabei — Hintergründe, Blitze, Lichtformer und Ersatztechnik. Die Blitzanlage ist autark, ich bin vor Ort also sofort einsatzbereit und brauche nichts aus deinem Haushalt.

Komplette Ausrüstung

Kamera, Objektive, Hintergründe, Blitze und Lichtformer kommen mit.

Autarke Blitzanlage

Das Licht ist im Gepäck und in wenigen Minuten aufgebaut.

Dein Ort, deine Zeit

Geschossen wird zwischen zwei Terminen, in der Mittagspause oder nach Feierabend.

Überall in SH & Hamburg

Vom Büro in Neumünster bis zum Betrieb in Kiel, Lübeck oder Hamburg.

Zusammenarbeit

Was ich von euch brauche

Ein kurzer Call oder eine E-Mail mit drei Angaben reicht für ein verbindliches Angebot.

  • Wofür die Bilder gebraucht werden (Website, Print, Social, Presse)
  • Wie viele Personen oder Produkte fotografiert werden
  • Ort und Wunschzeitraum — bei euch, im Studio oder draußen
  • Ob einheitliche Hintergründe oder zusätzliche Retusche gebraucht werden
Nutzungsrechte: Wir halten vorab fest, für welche Kanäle und wie lange die Bilder genutzt werden dürfen. Das schützt euch und mich — und ihr bekommt genau die Rechte, die ihr braucht.

Nach dem Shooting bekommt ihr eine Auswahlgalerie, in der ihr kommentieren und freigeben könnt. Erst danach geht es in die finale Bearbeitung.

Beispiele

Aus der Business- und Studiopraxis

Portrait eines Mannes vor dunklem Hintergrund
Sitzendes Portrait im weichen Studiolicht
Drei Frauen in Bademänteln und Handtuchturbanen am Fenster
Drei Frauen mit Teetassen und Handtuchturbanen
Kamerakoffer und Objektive vor einem Studiorack
Sitzendes Portrait mit Kameratasche im Studio
Frauen im Bademantel halten Orangenscheiben vor ihre Gesichter
Drei Frauen lachen mit Orangenscheiben in den Händen
@@ -82,23 +82,29 @@ desfotoFotografie & Video - +
-

Shootings

+

Fotografie

-

Video & mehr

+

Video & Bühne

+
+
+

Mehr

+ @@ -114,7 +120,7 @@

Netzwerk

- +
diff --git a/site/datenschutz/index.html b/site/datenschutz/index.html index c463b4a..11fd353 100644 --- a/site/datenschutz/index.html +++ b/site/datenschutz/index.html @@ -43,7 +43,7 @@ - + @@ -56,7 +56,6 @@ Rechtliches

Datenschutzerklärung

Informationen zur Verarbeitung personenbezogener Daten · Stand September 2026

-

Diese Datenschutzerklärung informiert dich darüber, welche personenbezogenen Daten beim Besuch @@ -185,23 +184,29 @@ desfotoFotografie & Video -

+
-

Shootings

+

Fotografie

-

Video & mehr

+

Video & Bühne

+
+
+

Mehr

+ @@ -217,7 +222,7 @@

Netzwerk

- +
diff --git a/site/familie/index.html b/site/familie/index.html new file mode 100644 index 0000000..8f4e17b --- /dev/null +++ b/site/familie/index.html @@ -0,0 +1,138 @@ + + + + + +Familienfotografie in Neumünster · desfoto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ Frauenportrait im weichen Seitenlicht vor dunklem Hintergrund +
+
+ + Familie +

Wachsendes festhalten, ohne Pose

+

Familienshootings, die nicht gestellt wirken. Zeit lassen, mitgehen und im richtigen Moment auslösen.

+ +
+
Anlässe

Für welche Momente mich Familien buchen

Familienshooting

Alle zusammen, ohne dass es nach Fototermin aussieht. Auch mit mehreren Generationen.

Generationenbilder

Großeltern, Eltern, Enkel: Bilder, die in Jahren mehr wert sind als heute.

Kinderportraits

Kurz, spielerisch und geduldig — lieber zehn ehrliche Bilder als eine Stunde Stillhalten.

Geschwister & Patchwork

Nicht jede Familie sieht gleich aus. Gezeigt wird, wer zu euch gehört.

Jubiläum & Familientreffen

Wenn die ganze Verwandtschaft zusammenkommt, lohnt sich ein gemeinsames Bild.

Bei euch oder im Studio

Zuhause, im Park oder im Studio in Neumünster: dort, wo ihr euch wohlfühlt.

Ablauf

Entspannt statt steif

Wir treffen uns dort, wo ihr euch wohlfühlt — im Studio, im Park oder bei euch zuhause. Ich sage, wann es losgeht, und danach einfach laufen lassen.

Anfrage

Du schreibst kurz, worum es geht. Ich melde mich mit einer ehrlichen Einschätzung, einem Ablauf und einem Preis.

Planung

Wir klären Ort, Licht, Zeit und Bildidee. Bei größeren Produktionen gibt es vorab ein Moodboard oder ein Treatment.

Dreh

Am Termin übernehme ich Technik, Licht und Regie — auf Wunsch mit dem mobilen Studio inklusive autarker Blitzanlage an deinem Ort. Du musst nichts mitbringen außer Zeit und dich.

Lieferung

Du bekommst eine Auswahlgalerie, bearbeitete Dateien in Web- und Druckgröße und bei Video das passende Exportformat für deinen Kanal.

Beispiele

Menschen aus meiner Arbeit

Familienshootings laufen ruhig und ohne Regieanweisungen ab. Genau so entstehen die Portraits, die ich hier zeige.

Eine Person am Bergsee spiegelt sich im WasserMarina mit roten Zöpfen auf einem verschneiten WaldwegPortrait einer Frau mit SonnenbrilleEine Frau mit Hut steht an einer felsigen KüsteMarinas Silhouette vor orangefarbenem Abendhimmel
+
+
+
+ Kontakt +

Termin für dein Familienshooting?

+

Schreib mir, wer dabei ist, wo es stattfinden soll und ob du eher ruhige oder lebendige Bilder möchtest.

+
+ +
+
+
+ + + + diff --git a/site/familien-und-paare/index.html b/site/familien-und-paare/index.html deleted file mode 100644 index 1f63562..0000000 --- a/site/familien-und-paare/index.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - -Familien- & Paarfotografie in Neumünster · desfoto - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
-
- Paar lacht im Abendlicht -
-
- - Familien & Paare -

Wachsendes festhalten, ohne Pose

-

Familienshootings und Paarportraits, die nicht gestellt wirken. Zeit lassen, mitgehen und im richtigen Moment auslösen.

- -
-
Anlässe

Für welche Momente mich Familien und Paare buchen

Familienshooting

Alle zusammen, ohne dass es nach Fototermin aussieht. Auch mit mehreren Generationen.

Paarshooting

Zwei Menschen, die sich nah sind. Vor der Hochzeit, zum Jahrestag oder einfach so.

Verlobung & Ja-Wort

Kleine Zeremonien, Standesamt, freie Trauung — begleitend oder als Portraitstrecke.

Jubiläum & Familientreffen

Wenn die ganze Verwandtschaft zusammenkommt, lohnt sich ein gemeinsames Bild.

Generationenbilder

Großeltern, Eltern, Enkel: Bilder, die in Jahren mehr wert sind als heute.

Kinderportraits

Kurz, spielerisch und geduldig — lieber zehn ehrliche Bilder als eine Stunde Stillhalten.

Ablauf

Entspannt statt steif

Wir treffen uns dort, wo ihr euch wohlfühlt — im Studio, im Park oder bei euch zuhause. Ich sage, wann es losgeht, und danach einfach laufen lassen.

Anfrage

Du schreibst kurz, worum es geht. Ich melde mich mit einer ehrlichen Einschätzung, einem Ablauf und einem Preis.

Planung

Wir klären Ort, Licht, Zeit und Bildidee. Bei größeren Produktionen gibt es vorab ein Moodboard oder ein Treatment.

Dreh

Am Termin übernehme ich Technik, Licht und Regie — auf Wunsch mit dem mobilen Studio inklusive autarker Blitzanlage an deinem Ort. Du musst nichts mitbringen außer Zeit und dich.

Lieferung

Du bekommst eine Auswahlgalerie, bearbeitete Dateien in Web- und Druckgröße und bei Video das passende Exportformat für deinen Kanal.

Beispiele

Paare und Menschen

Natürliches Portrait im FreienPaar küsst sich vor hellem HintergrundPaarportrait im GrünenSara und Carlos stehen Arm in Arm im GrünenSara und Carlos küssen sich im GegenlichtSara und Carlos gehen Hand in Hand durch den WaldTamara und Tobi lachen auf einem WaldwegDie Silhouetten des Paars rahmen die tief stehende SonneMarina mit roten Zöpfen auf einem verschneiten WaldwegMarinas Silhouette vor orangefarbenem Abendhimmel
-
-
-
- Kontakt -

Termin für dein Familienshooting?

-

Schreib mir, wer dabei ist, wo es stattfinden soll und ob du eher ruhige oder lebendige Bilder möchtest.

-
- -
-
-
- - - - diff --git a/site/fotografie/index.html b/site/fotografie/index.html new file mode 100644 index 0000000..69ef394 --- /dev/null +++ b/site/fotografie/index.html @@ -0,0 +1,138 @@ + + + + + +Fotografie in Neumünster · desfoto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ Portrait im Profil mit Ohrring +
+
+ + Fotografie +

Fotografie — Menschen, Unternehmen, Produkte, Veranstaltungen

+

Alles, was ich fotografisch mache: drei Themen, vier Schwerpunkte, ein Ablauf und ein Ansprechpartner — im Studio in Neumünster oder mit dem mobilen Studio bei dir.

+ +
+
Überblick

Drei Themen, ein Blick

Du musst dich nicht für eine Schublade entscheiden — erzähl einfach, worum es geht.

Menschen

Menschen

Portrait, Bewerbungsbilder, Familie und Model — im Studio in Neumünster, an einem Ort deiner Wahl oder dort, wo dein Alltag stattfindet.

Unternehmen & Marken

Unternehmen & Marken

Team- und Standortbilder, Produkte und Kampagnenmotive — abgestimmt auf die Flächen, auf denen sie später stehen.

Veranstaltungen

Veranstaltungen

Feiern, Events, Vereinsleben und Premieren. Dokumentarisch begleitet, ohne den Ablauf auszubremsen.

Schwerpunkte

Die vier buchbaren Schwerpunkte

Mann im weißen Hemd vor dunklem Hintergrund, Softbox im Bild

01Businessfotografie

Portraits, Teams und Produkte für Website, LinkedIn, Kampagne und Presse — sauber ausgeleuchtet und sofort einsetzbar.

Fashion-Portrait in kräftigen Farben

02Portrait & Model

Portraits, Sedcards, Editorial und Fashion. Im Studio in Neumünster oder an einem Ort, der zu dir und deiner Geschichte passt.

Frauenportrait im weichen Seitenlicht vor dunklem Hintergrund

03Familie

Familienshootings, die nicht gestellt wirken. Zeit lassen, mitgehen, im richtigen Moment auslösen.

Portrait einer Frau mit Sonnenbrille

04Minishootings

Die kurze Session für alle, die schnell ein gutes Bild brauchen: klarer Ablauf, kleine Auswahl, kurze Lieferzeit.

Studio

Im Studio in Neumünster oder bei dir vor Ort

Projekte können im eigenen Studio in Neumünster fotografiert werden — oder ich baue das mobile Studio inklusive autarker Blitzanlage dort auf, wo deine Leute sind.

Kameraobjektiv im StudiolichtAutarke BlitzanlageHintergründe, Blitze, Lichtformer

Studio Neumünster

Ein eigenes Studio in Neumünster steht für Fototermine bereit: kontrolliertes Licht, ruhiger Hintergrund und Platz für Konzeptarbeit. Gut für Portraits, Businessbilder und alles, was eine wiederholbare Optik braucht — unabhängig vom Wetter.

Mobiles Studio

Nicht jedes Team kann für ein Shooting den Betrieb verlassen. Deshalb baue ich das Studio dort auf, wo deine Leute sowieso sind: im Besprechungsraum, in der Werkstatt, im Vereinsheim oder draußen vor der Halle.

Dabei ist die komplette Ausrüstung dabei — Hintergründe, Blitze, Lichtformer und Ersatztechnik. Die Blitzanlage ist autark, ich bin vor Ort also sofort einsatzbereit und brauche nichts aus deinem Haushalt.

Komplette Ausrüstung

Kamera, Objektive, Hintergründe, Blitze und Lichtformer kommen mit.

Autarke Blitzanlage

Das Licht ist im Gepäck und in wenigen Minuten aufgebaut.

Dein Ort, deine Zeit

Geschossen wird zwischen zwei Terminen, in der Mittagspause oder nach Feierabend.

Überall in SH & Hamburg

Vom Büro in Neumünster bis zum Betrieb in Kiel, Lübeck oder Hamburg.

Ablauf

So läuft ein Termin

Bei jedem Auftrag derselbe Weg — vom ersten Satz bis zur fertigen Datei.

Vorab klären

Wir sprechen kurz über Anlass, Ort und gewünschte Bildsprache. Daraus ergibt sich der Umfang.

Licht planen

Ob Tageslicht, mobiles Studio oder Bühne: Das Licht entscheidet, wie nah das Ergebnis an deiner Vorstellung liegt.

Fotografieren

Am Termin führe ich durch den Ablauf, gebe Haltung und Tempo vor und löse im richtigen Moment aus.

Auswählen

Du bekommst eine geordnete Auswahl und die bearbeiteten Bilddateien in den Größen, die du brauchst.

Beispiele

Aus Aufträgen und eigener Arbeit

Portrait eines Mannes vor dunklem Hintergrund
Editorial-Pose mit ausbalancierter Haltung
Silhouette einer Frau vor orangefarbenem Abendhimmel
Sitzendes Portrait im weichen Studiolicht
Drei Frauen in Bademänteln und Handtuchturbanen am Fenster
Marina mit roten Zöpfen auf einem verschneiten Waldweg
Eine Frau mit Hut steht an einer felsigen Küste
Marta springt mit angewinkelten Beinen
Ein Hund steht auf einem sonnigen Waldweg
Technik & Ausgabe

Was am Ende bei dir landet

Bilddaten in der Auflösung, die du wirklich brauchst — nicht ein Ordner voller RAW-Dateien.

  • Bearbeitete JPEG-Dateien in Web- und Druckgröße
  • Auswahlgalerie zum Sichten und Teilen
  • Retusche auf Wunsch, besprochen im Vorfeld
  • Nutzungsrechte schriftlich festgehalten
  • Tageslicht, Studiolicht und mobile Blitzanlage
  • Im Studio in Neumünster oder mit dem mobilen Studio bei dir
  • Reportagestil bei Veranstaltungen
  • Lieferung per Downloadlink
+
+
+
+ Kontakt +

Welcher Bereich passt zu dir?

+

Schreib kurz, worum es geht und wann es stattfinden soll. Du bekommst Ablauf, Umfang und Preis.

+
+ +
+
+
+ + + + diff --git a/site/impressum/index.html b/site/impressum/index.html index 849a0ed..c46ce35 100644 --- a/site/impressum/index.html +++ b/site/impressum/index.html @@ -43,7 +43,7 @@ - + @@ -56,7 +56,6 @@ Rechtliches

Impressum

Angaben gemäß § 5 DDG · Stand September 2026

-

Diensteanbieter

@@ -110,23 +109,29 @@ desfotoFotografie & Video - +
-

Shootings

+

Fotografie

-

Video & mehr

+

Video & Bühne

+
+
+

Mehr

+ @@ -142,7 +147,7 @@

Netzwerk

- +
diff --git a/site/index.html b/site/index.html index 12295c6..00a789a 100644 --- a/site/index.html +++ b/site/index.html @@ -4,7 +4,7 @@ desfoto · Fotografie & Video aus Neumünster - + @@ -13,14 +13,14 @@ - + - + @@ -30,7 +30,7 @@ - + @@ -43,7 +43,7 @@ - + @@ -55,19 +55,19 @@ Fotografie und Video aus Neumünster

Bilder von Menschen, Marken und Musik.

- desfoto ist die fotografische und filmische Arbeit von Denny Schulz aus Neumünster: - Shootings für Business, Portrait und Familie, dazu Konzerte, eigene Projekte, - Video und Inhalte für Social Media. + desfoto ist Fotografie, Video und mediale Produktion aus Neumünster: + Bilder für Menschen und Unternehmen, Bewegtbild für Marken, Musik und Events, + Bühnenarbeiten und eigene Projekte.

Projekt anfragen Arbeiten ansehen
-
ShootingsBusiness, Portrait, Familie und Minishootings unter einem Dach.
Studio & vor OrtEigenes Studio in Neumünster oder mobiles Studio inklusive autarker Blitzanlage bei dir.
Foto & VideoBeides aus einer Hand, damit Optik und Ton zusammenpassen.
Musik & BühneKonzerte, Bands und Musikvideos aus eigener Erfahrung.
+
Klarer AblaufUmfang, Termin und Preis sind vor der Buchung geklärt.
@@ -78,7 +78,7 @@ im Feed, im Bewerbungsportfolio, im Plakat. Kein Katalog von der Stange — wir klären kurz, was du brauchst, und ich liefere genau das.

-
Zwei Bereiche

Shootings und Videoprojekte

Foto und Video bleiben getrennt, damit beide Bereiche für sich verständlich sind — und kommen trotzdem von derselben Person.

Kameraobjektiv im Studiolicht
Video

Videoprojekte

Bewegtbild von der Idee bis zum fertigen Schnitt. Musikvideo, Imagefilm, Eventfilm und Formate für Social Media — gedreht, geschnitten und ausgeliefert in dem Format, in dem du es veröffentlichst.

Videoprojekte ansehen
Portfolio

Musik, Menschen und freie Arbeiten

Konzerte und Bands, Portraits und Business, dazu eigene Strecken aus Landschaft und Tierwelt. Das ist die Bandbreite meiner Arbeit.

+
Arbeitsbereiche

Fotografie, Video, Musik & Bühne und Projekte

Vier Bereiche, in denen ich arbeite. Was du davon buchen kannst, steht direkt darunter — die freien Arbeiten laufen daneben weiter.

Sitzendes Portrait mit Kameratasche im Studio

Fotografie

Menschen, Unternehmen, Produkte, Veranstaltungen.

Portrait, Business, Familie und Minishootings — im Studio in Neumünster oder mit dem mobilen Studio an deinem Ort.

Lichtspur aus einer langen Belichtung

Video

Unternehmen, Musik, Events, Social Content.

Musikvideo, Imagefilm, Recruiting und Eventnachbericht — von der Idee über den Dreh bis zum fertigen Export.

Schlagzeuger am Drumset in Schwarzweiß

Musik & Bühne

Bands, Künstler, Konzerte, Musikvideo.

Live, Bandportraits und Musikvideo — als Musiker kenne ich beide Seiten der Bühne.

Eine Person sitzt über einer wolkenverhangenen Berglandschaft

Projekte

Freie Arbeiten, Kooperationen, besondere Produktionen.

Landschaft, Tierwelt, Menschen und pro bono — die Arbeiten, die neben den Aufträgen entstehen.

Buchen

Was du bei mir buchen kannst

Auftragsarbeiten mit klarem Umfang: Businessfotografie, Portrait & Model, Familie, Minishootings, Video und Social-Media-Inhalte.

Mann im weißen Hemd vor dunklem Hintergrund, Softbox im Bild

01Businessfotografie

Portraits, Teams und Produkte für Website, LinkedIn, Kampagne und Presse — sauber ausgeleuchtet und sofort einsetzbar.

Fashion-Portrait in kräftigen Farben

02Portrait & Model

Portraits, Sedcards, Editorial und Fashion. Im Studio in Neumünster oder an einem Ort, der zu dir und deiner Geschichte passt.

Frauenportrait im weichen Seitenlicht vor dunklem Hintergrund

03Familie

Familienshootings, die nicht gestellt wirken. Zeit lassen, mitgehen, im richtigen Moment auslösen.

Portrait einer Frau mit Sonnenbrille

04Minishootings

Die kurze Session für alle, die schnell ein gutes Bild brauchen: klarer Ablauf, kleine Auswahl, kurze Lieferzeit.

Kameraobjektiv im Studiolicht

05Videoprojekte

Musikvideo, Imagefilm, Recruiting und Eventnachbericht — von der Idee über den Dreh bis zum fertigen Schnitt.

Redner auf einer Veranstaltung

06Social-Media-Inhalte

Foto- und Videocontent im Format deiner Kanäle — wiederkehrend, planbar und über die Monate hinweg einheitlich.

Portfolio

Was ich außerdem mache

Freie Arbeiten, Musik & Bühne und pro bono — Projekte, die nicht jede Woche auf einer Rechnung stehen. Hier muss kein Bild ein Angebot verkaufen.

@@ -100,11 +100,10 @@ Video starten -

Das Video wird erst nach deinem Klick von YouTube geladen (youtube-nocookie.com). Mehr dazu in der Datenschutzerklärung.

-
Studio

Im Studio in Neumünster oder bei dir vor Ort

Projekte können im eigenen Studio in Neumünster fotografiert werden — oder ich baue das mobile Studio inklusive autarker Blitzanlage dort auf, wo deine Leute sind.

Studioequipment mit Logo von Denny Schulz FotografieAutarke BlitzanlageHintergründe, Blitze, Lichtformer

Studio Neumünster

Ein eigenes Studio in Neumünster steht für Shootings bereit: kontrolliertes Licht, ruhiger Hintergrund und Platz für Konzeptarbeit. Gut für Portraits, Businessbilder und alles, was eine wiederholbare Optik braucht — unabhängig vom Wetter.

Mobiles Studio

Nicht jedes Team kann für ein Shooting den Betrieb verlassen. Deshalb baue ich das Studio dort auf, wo deine Leute sowieso sind: im Besprechungsraum, in der Werkstatt, im Vereinsheim oder draußen vor der Halle.

Dabei ist die komplette Ausrüstung dabei — Hintergründe, Blitze, Lichtformer und Ersatztechnik. Die Blitzanlage ist autark, ich bin vor Ort also sofort einsatzbereit und brauche nichts aus deinem Haushalt.

Komplette Ausrüstung

Kamera, Objektive, Hintergründe, Blitze und Lichtformer kommen mit.

Autarke Blitzanlage

Das Licht ist im Gepäck und in wenigen Minuten aufgebaut.

Dein Ort, deine Zeit

Geschossen wird zwischen zwei Terminen, in der Mittagspause oder nach Feierabend.

Überall in SH & Hamburg

Vom Büro in Neumünster bis zum Betrieb in Kiel, Lübeck oder Hamburg.

Ablauf

Vier Schritte, kein Rätselraten

Ob Minishooting oder Kampagne: Der Weg ist immer derselbe und du weißt jederzeit, was als Nächstes passiert.

Anfrage

Du schreibst kurz, worum es geht. Ich melde mich mit einer ehrlichen Einschätzung, einem Ablauf und einem Preis.

Planung

Wir klären Ort, Licht, Zeit und Bildidee. Bei größeren Produktionen gibt es vorab ein Moodboard oder ein Treatment.

Dreh

Am Termin übernehme ich Technik, Licht und Regie — auf Wunsch mit dem mobilen Studio inklusive autarker Blitzanlage an deinem Ort. Du musst nichts mitbringen außer Zeit und dich.

Lieferung

Du bekommst eine Auswahlgalerie, bearbeitete Dateien in Web- und Druckgröße und bei Video das passende Exportformat für deinen Kanal.

Warum desfoto

Was du erwarten kannst

Studio & vor Ort

Eigenes Studio in Neumünster oder mobiles Studio mit autarker Blitzanlage, Hintergründen und Lichtformern.

Aus Neumünster

Zuhause zwischen Kiel und Hamburg — unterwegs in Schleswig-Holstein und darüber hinaus.

Foto und Video

Ein Ansprechpartner für Bild und Bewegtbild. Das spart Abstimmung und hält die Optik zusammen.

Klarer Ablauf

Umfang, Termin und Preis klären wir vor der Buchung. Keine Überraschungen auf der Rechnung.

+
Studio

Im Studio in Neumünster oder bei dir vor Ort

Projekte können im eigenen Studio in Neumünster fotografiert werden — oder ich baue das mobile Studio inklusive autarker Blitzanlage dort auf, wo deine Leute sind.

Kameraobjektiv im StudiolichtAutarke BlitzanlageHintergründe, Blitze, Lichtformer

Studio Neumünster

Ein eigenes Studio in Neumünster steht für Fototermine bereit: kontrolliertes Licht, ruhiger Hintergrund und Platz für Konzeptarbeit. Gut für Portraits, Businessbilder und alles, was eine wiederholbare Optik braucht — unabhängig vom Wetter.

Mobiles Studio

Nicht jedes Team kann für ein Shooting den Betrieb verlassen. Deshalb baue ich das Studio dort auf, wo deine Leute sowieso sind: im Besprechungsraum, in der Werkstatt, im Vereinsheim oder draußen vor der Halle.

Dabei ist die komplette Ausrüstung dabei — Hintergründe, Blitze, Lichtformer und Ersatztechnik. Die Blitzanlage ist autark, ich bin vor Ort also sofort einsatzbereit und brauche nichts aus deinem Haushalt.

Komplette Ausrüstung

Kamera, Objektive, Hintergründe, Blitze und Lichtformer kommen mit.

Autarke Blitzanlage

Das Licht ist im Gepäck und in wenigen Minuten aufgebaut.

Dein Ort, deine Zeit

Geschossen wird zwischen zwei Terminen, in der Mittagspause oder nach Feierabend.

Überall in SH & Hamburg

Vom Büro in Neumünster bis zum Betrieb in Kiel, Lübeck oder Hamburg.

Ablauf

Vier Schritte, kein Rätselraten

Ob Minishooting oder Kampagne: Der Weg ist immer derselbe und du weißt jederzeit, was als Nächstes passiert.

Anfrage

Du schreibst kurz, worum es geht. Ich melde mich mit einer ehrlichen Einschätzung, einem Ablauf und einem Preis.

Planung

Wir klären Ort, Licht, Zeit und Bildidee. Bei größeren Produktionen gibt es vorab ein Moodboard oder ein Treatment.

Dreh

Am Termin übernehme ich Technik, Licht und Regie — auf Wunsch mit dem mobilen Studio inklusive autarker Blitzanlage an deinem Ort. Du musst nichts mitbringen außer Zeit und dich.

Lieferung

Du bekommst eine Auswahlgalerie, bearbeitete Dateien in Web- und Druckgröße und bei Video das passende Exportformat für deinen Kanal.

Warum desfoto

Was du erwarten kannst

Studio & vor Ort

Eigenes Studio in Neumünster oder mobiles Studio mit autarker Blitzanlage, Hintergründen und Lichtformern.

Aus Neumünster

Zuhause zwischen Kiel und Hamburg — unterwegs in Schleswig-Holstein und darüber hinaus.

Foto und Video

Ein Ansprechpartner für Bild und Bewegtbild. Das spart Abstimmung und hält die Optik zusammen.

Klarer Ablauf

Umfang, Termin und Preis klären wir vor der Buchung. Keine Überraschungen auf der Rechnung.

@@ -127,23 +126,29 @@ desfotoFotografie & Video - +
-

Shootings

+

Fotografie

-

Video & mehr

+

Video & Bühne

+
+
+

Mehr

+ @@ -159,7 +164,7 @@

Netzwerk

- +
diff --git a/site/kontakt/index.html b/site/kontakt/index.html index 150ff10..4e42ccd 100644 --- a/site/kontakt/index.html +++ b/site/kontakt/index.html @@ -44,7 +44,7 @@ - + @@ -57,7 +57,6 @@ Kontakt

Erzähl mir, was du vorhast

Kurze Nachricht genügt: worum geht es, wann soll es stattfinden und was hast du mit den Bildern oder dem Video vor. Du bekommst eine ehrliche Einschätzung und einen Preis.

-

Direkter Draht

Am schnellsten geht es per E-Mail oder Telefon. Beides landet direkt bei mir, nicht in einem Ticketsystem.

Region
Neumünster · Schleswig-Holstein
Antwortzeit
Antwort in der Regel innerhalb von 24 Stunden
Termine vergebe ich nach Absprache — auch abends und am Wochenende, wenn es anders nicht passt.

Anfrage vorbereiten

@@ -76,9 +75,10 @@ - + - + + @@ -331,19 +321,19 @@ def home() -> str: {e(SITE['tagline'])}

{words('Bilder von Menschen, Marken und Musik.')}

- desfoto ist die fotografische und filmische Arbeit von Denny Schulz aus Neumünster: - Shootings für Business, Portrait und Familie, dazu Konzerte, eigene Projekte, - Video und Inhalte für Social Media. + desfoto ist Fotografie, Video und mediale Produktion aus Neumünster: + Bilder für Menschen und Unternehmen, Bewegtbild für Marken, Musik und Events, + Bühnenarbeiten und eigene Projekte.

-
ShootingsBusiness, Portrait, Familie und Minishootings unter einem Dach.
Studio & vor OrtEigenes Studio in Neumünster oder mobiles Studio inklusive autarker Blitzanlage bei dir.
Foto & VideoBeides aus einer Hand, damit Optik und Ton zusammenpassen.
Musik & BühneKonzerte, Bands und Musikvideos aus eigener Erfahrung.
+
Klarer AblaufUmfang, Termin und Preis sind vor der Buchung geklärt.
@@ -361,22 +351,29 @@ def home() -> str:
""", ) - pillars = section( - kicker="Zwei Bereiche", - title="Shootings und Videoprojekte", - lead="Foto und Video bleiben getrennt, damit beide Bereiche für sich verständlich sind — und kommen trotzdem von derselben Person.", - content=pillar_cards(), + areas = section( + kicker="Arbeitsbereiche", + title="Fotografie, Video, Musik & Bühne und Projekte", + lead="Vier Bereiche, in denen ich arbeite. Was du davon buchen kannst, steht direkt darunter — die freien Arbeiten laufen daneben weiter.", + content=area_cards(), + ) + + bookable = section( + variant="paper2", + kicker="Buchen", + title="Was du bei mir buchen kannst", + lead="Auftragsarbeiten mit klarem Umfang: Businessfotografie, Portrait & Model, Familie, Minishootings, Video und Social-Media-Inhalte.", + content=service_cards(), ) works = section( - variant="paper2", kicker="Portfolio", - title="Musik, Menschen und freie Arbeiten", - lead="Konzerte und Bands, Portraits und Business, dazu eigene Strecken aus Landschaft und Tierwelt. Das ist die Bandbreite meiner Arbeit.", + title="Was ich außerdem mache", + lead="Freie Arbeiten, Musik & Bühne und pro bono — Projekte, die nicht jede Woche auf einer Rechnung stehen. Hier muss kein Bild ein Angebot verkaufen.", content=work_gallery() + '', + 'Musik & Bühne
', ) video_sec = f"""
@@ -422,7 +419,6 @@ def home() -> str: "@context": "https://schema.org", "@type": "ProfessionalService", "name": SITE["brand"], - "alternateName": LEGAL["business"], "description": SITE["default_description"], "url": SITE["url"] + "/", "image": SITE["url"] + "/assets/img/og-desfoto.jpg", @@ -442,19 +438,23 @@ def home() -> str: ], body=hero + statement - + pillars + + areas + marquee( [ + "Fotografie", + "Video", "Musik & Bühne", + "Projekte", "Businessfotografie", "Portrait & Model", - "Familien & Paare", + "Familie", "Minishootings", "Mobiles Studio", - "Videoprojekte", + "Musikvideo", "Social Media", ] ) + + bookable + works + video_sec + studio @@ -464,15 +464,15 @@ def home() -> str: ) -def shootings() -> str: - path = "/shootings/" +def fotografie() -> str: + path = "/fotografie/" overview = "".join( '
' + picture( item["image"], "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", - fit="50% 30%", + fit=item.get("fit"), alt=item["title"], ) + f'

{e(item["title"])}

{e(item["text"])}

' @@ -480,35 +480,35 @@ def shootings() -> str: ) body = ( page_head( - kicker="Shootings", - title="Shootings — von Business bis Familie", - lead="Alles, was ich fotografisch mache, läuft unter dem Begriff Shooting: vier Schwerpunkte, ein Ablauf, ein Ansprechpartner.", + kicker="Fotografie", + title="Fotografie — Menschen, Unternehmen, Produkte, Veranstaltungen", + lead="Alles, was ich fotografisch mache: drei Themen, vier Schwerpunkte, ein Ablauf und ein Ansprechpartner — im Studio in Neumünster oder mit dem mobilen Studio bei dir.", image="editorial-earring", - breadcrumb=("Shootings", path), + breadcrumb=("Fotografie", path), fit="45% 30%", - actions='Shooting anfragen' + actions='Fototermin anfragen' 'Arbeiten ansehen', ) + section( kicker="Überblick", - title="Ein Überbegriff, vier Wege", + title="Drei Themen, ein Blick", lead="Du musst dich nicht für eine Schublade entscheiden — erzähl einfach, worum es geht.", content=f'
{overview}
', ) + section( variant="paper2", kicker="Schwerpunkte", - title="Die vier Shootings im Detail", + title="Die vier buchbaren Schwerpunkte", content=service_cards( - ["businessfotografie", "portrait-und-model", "familien-und-paare", "minishootings"] + ["businessfotografie", "portrait-und-model", "familie", "minishootings"] ), tight=True, ) + studio_section(variant="") + section( kicker="Ablauf", - title="So läuft ein Shooting", - lead="Bei jedem Shooting derselbe Weg — vom ersten Satz bis zur fertigen Datei.", + title="So läuft ein Termin", + lead="Bei jedem Auftrag derselbe Weg — vom ersten Satz bis zur fertigen Datei.", content='
' + "".join( f'

{e(step["title"])}

{e(step["text"])}

' @@ -519,16 +519,16 @@ def shootings() -> str: + section( variant="paper2", kicker="Beispiele", - title="Aus Shootings und Aufträgen", + title="Aus Aufträgen und eigener Arbeit", content=media_grid( [ ("business-portrait-dark", "Portrait eines Mannes vor dunklem Hintergrund", "4x5", "50% 28%"), - ("editorial-colour", "Fashion-Portrait in kräftigen Farben", "4x5", "50% 30%"), - ("portrait-couple", "Paarportrait im Grünen", "4x5", "50% 30%"), + ("editorial-balance", "Editorial-Pose mit ausbalancierter Haltung", "4x5", "50% 40%"), + ("portrait-marina-sunset", "Silhouette einer Frau vor orangefarbenem Abendhimmel", "4x5", "50% 40%"), ("business-portrait-stool", "Sitzendes Portrait im weichen Studiolicht", "3x2", "50% 35%"), ("business-hotel-window", "Drei Frauen in Bademänteln und Handtuchturbanen am Fenster", "3x2", "50% 40%"), - ("portrait-natural", "Natürliches Portrait im Freien", "3x2", "50% 32%"), - ("couple-backlight-kiss", "Sara und Carlos küssen sich im Gegenlicht", "1x1", "50% 45%"), + ("portrait-marina-snow", "Marina mit roten Zöpfen auf einem verschneiten Waldweg", "3x2", "50% 30%"), + ("free-woman-coast", "Eine Frau mit Hut steht an einer felsigen Küste", "1x1", "50% 40%"), ("editorial-leap", "Marta springt mit angewinkelten Beinen", "1x1", "50% 35%"), ("free-dog-husky", "Ein Hund steht auf einem sonnigen Waldweg", "1x1", "50% 45%"), ] @@ -558,22 +558,22 @@ def shootings() -> str: ), ) + cta_band( - title="Welches Shooting passt zu dir?", + title="Welcher Bereich passt zu dir?", text="Schreib kurz, worum es geht und wann es stattfinden soll. Du bekommst Ablauf, Umfang und Preis.", secondary=("Minishootings ansehen", "/minishootings/"), ) ) return page( - title="Shootings in Neumünster · desfoto", - description="Shootings aus Neumünster: Businessfotografie, Portrait & Model, Familien & Paare und Minishootings — mit mobilem Studio inklusive autarker Blitzanlage.", + title="Fotografie in Neumünster · desfoto", + description="Fotografie aus Neumünster: Businessfotografie, Portrait & Model, Familie und Minishootings — mit mobilem Studio inklusive autarker Blitzanlage.", path=path, json_ld=[ service_ld( - "Shootings", + "Fotografie", path, - "Fotografische Shootings für Business, Portrait & Model, Familien und Paare.", + "Fotografie für Menschen, Unternehmen, Produkte und Veranstaltungen.", ), - breadcrumb_ld("Shootings", path), + breadcrumb_ld("Fotografie", path), ], body=body, ) @@ -655,7 +655,7 @@ def businessfotografie() -> str: ("business-hotel-window", "Drei Frauen in Bademänteln und Handtuchturbanen am Fenster", "4x5", "50% 40%"), ("business-hotel-tea", "Drei Frauen mit Teetassen und Handtuchturbanen", "4x5", "50% 45%"), ("gear-cases", "Kamerakoffer und Objektive vor einem Studiorack", "3x2", "50% 50%"), - ("studio-setup", "Studioequipment mit Logo von Denny Schulz Fotografie", "3x2", "50% 45%"), + ("business-portrait-sit", "Sitzendes Portrait mit Kameratasche im Studio", "3x2", "50% 35%"), ("business-hotel-orange", "Frauen im Bademantel halten Orangenscheiben vor ihre Gesichter", "3x2", "50% 50%"), ("business-hotel-laugh", "Drei Frauen lachen mit Orangenscheiben in den Händen", "3x2", "50% 45%"), ], @@ -765,8 +765,8 @@ def portrait_und_model() -> str: ("editorial-studio-side", "50% 30%"), ("editorial-balance", "50% 40%"), ("editorial-leap", "50% 35%"), - ("portrait-natural", "50% 30%"), - ("people-kiss", "50% 32%"), + ("free-portrait-light", "50% 30%"), + ("free-modern-portrait", "50% 30%"), ] ) + "
", @@ -789,21 +789,21 @@ def portrait_und_model() -> str: ) -def familien_und_paare() -> str: - path = "/familien-und-paare/" +def familie() -> str: + path = "/familie/" body = ( page_head( - kicker="Familien & Paare", + kicker="Familie", title="Wachsendes festhalten, ohne Pose", - lead="Familienshootings und Paarportraits, die nicht gestellt wirken. Zeit lassen, mitgehen und im richtigen Moment auslösen.", - image="people-joy", - breadcrumb=("Familien & Paare", path), - fit="50% 30%", + lead="Familienshootings, die nicht gestellt wirken. Zeit lassen, mitgehen und im richtigen Moment auslösen.", + image="free-portrait-light", + breadcrumb=("Familie", path), + fit="50% 28%", actions='Termin anfragen', ) + section( kicker="Anlässe", - title="Für welche Momente mich Familien und Paare buchen", + title="Für welche Momente mich Familien buchen", content='
' + "".join( f'

{e(t)}

{e(d)}

' @@ -812,18 +812,6 @@ def familien_und_paare() -> str: "Familienshooting", "Alle zusammen, ohne dass es nach Fototermin aussieht. Auch mit mehreren Generationen.", ), - ( - "Paarshooting", - "Zwei Menschen, die sich nah sind. Vor der Hochzeit, zum Jahrestag oder einfach so.", - ), - ( - "Verlobung & Ja-Wort", - "Kleine Zeremonien, Standesamt, freie Trauung — begleitend oder als Portraitstrecke.", - ), - ( - "Jubiläum & Familientreffen", - "Wenn die ganze Verwandtschaft zusammenkommt, lohnt sich ein gemeinsames Bild.", - ), ( "Generationenbilder", "Großeltern, Eltern, Enkel: Bilder, die in Jahren mehr wert sind als heute.", @@ -832,6 +820,18 @@ def familien_und_paare() -> str: "Kinderportraits", "Kurz, spielerisch und geduldig — lieber zehn ehrliche Bilder als eine Stunde Stillhalten.", ), + ( + "Geschwister & Patchwork", + "Nicht jede Familie sieht gleich aus. Gezeigt wird, wer zu euch gehört.", + ), + ( + "Jubiläum & Familientreffen", + "Wenn die ganze Verwandtschaft zusammenkommt, lohnt sich ein gemeinsames Bild.", + ), + ( + "Bei euch oder im Studio", + "Zuhause, im Park oder im Studio in Neumünster: dort, wo ihr euch wohlfühlt.", + ), ] ) + "
", @@ -845,20 +845,16 @@ def familien_und_paare() -> str: ) + section( kicker="Beispiele", - title="Paare und Menschen", + title="Menschen aus meiner Arbeit", + lead="Familienshootings laufen ruhig und ohne Regieanweisungen ab. Genau so entstehen die Portraits, die ich hier zeige.", content='
' + "".join( picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit) for slug, fit in [ - ("portrait-natural", "50% 30%"), - ("people-kiss", "50% 32%"), - ("portrait-couple", "50% 30%"), - ("couple-park", "50% 30%"), - ("couple-backlight-kiss", "50% 45%"), - ("couple-forest-walk", "50% 35%"), - ("couple-laugh-walk", "50% 35%"), - ("couple-sun-silhouette", "50% 50%"), + ("free-lake-person", "50% 45%"), ("portrait-marina-snow", "50% 30%"), + ("free-modern-portrait", "50% 30%"), + ("free-woman-coast", "50% 40%"), ("portrait-marina-sunset", "50% 45%"), ] ) @@ -871,12 +867,16 @@ def familien_und_paare() -> str: ) ) return page( - title="Familien- & Paarfotografie in Neumünster · desfoto", - description="Familien- und Paarshootings aus Neumünster: natürliche Bilder ohne Pose, im Studio, in der Natur oder bei euch zuhause.", + title="Familienfotografie in Neumünster · desfoto", + description="Familienshootings aus Neumünster: natürliche Bilder ohne Pose, im Studio, in der Natur oder bei euch zuhause.", path=path, json_ld=[ - service_ld("Familien- und Paarfotografie", path, "Familienshootings und Paarportraits ohne gestellte Posen."), - breadcrumb_ld("Familien & Paare", path), + service_ld( + "Familienfotografie", + path, + "Familienshootings ohne gestellte Posen, im Studio, in der Natur oder zuhause.", + ), + breadcrumb_ld("Familie", path), ], body=body, ) @@ -889,7 +889,7 @@ def minishootings() -> str: kicker="Minishootings", title="Kurze Session, sofort brauchbares Bild", lead="Der Termin für alle, die schnell ein gutes Bild brauchen: klarer Ablauf, kleine Auswahl, kurze Lieferzeit. Meist in rund 20 bis 30 Minuten erledigt.", - image="portrait-natural", + image="free-modern-portrait", breadcrumb=("Minishootings", path), fit="50% 30%", actions='Minishooting buchen', @@ -902,11 +902,11 @@ def minishootings() -> str: f'

{e(t)}

{e(d)}

' for t, d in [ ("Profilbild", "Für LinkedIn, Bewerbung, Vereinsseite oder die Website."), - ("Paar & Familie", "Ein, zwei schöne Bilder zusammen — ohne großen Termin."), + ("Familie", "Eltern und Kinder, ohne großen Termin — ein, zwei schöne Bilder zusammen."), ("Social Media", "Frisches Material für deine Kanäle, passend zum Format."), ("Verein & Mannschaft", "Schnell alle Gesichter für Mannschaftsfotos und Anmeldungen."), ("Businesskurzportrait", "Du brauchst nur ein Bild für die neue Stelle oder Präsentation."), - ("Geschenkidee", "Gutschein für ein gemeinsames Shooting — auf Anfrage."), + ("Geschenkidee", "Gutschein für ein Shooting — auf Anfrage."), ] ) + "
", @@ -958,11 +958,11 @@ def minishootings() -> str: + "".join( picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit) for slug, fit in [ - ("portrait-natural", "50% 28%"), + ("free-woman-coast", "50% 30%"), ("business-portrait-studio", "50% 28%"), ("editorial-black", "50% 28%"), ("editorial-studio-side", "50% 30%"), - ("free-modern-portrait", "50% 30%"), + ("free-portrait-light", "50% 30%"), ("portrait-marina-snow", "50% 30%"), ] ) @@ -1006,6 +1006,7 @@ def video() -> str:

{e(VIDEO['title'])}

{e(VIDEO['description'])}

+ Musik & Bühne {ICONS['arrow']}
{video_block()}
@@ -1102,7 +1103,7 @@ def video() -> str: content=caption_gallery( [ ("studio-lens", "Brennweite und Licht im Studio", "g-w3", "3x2", "50% 50%"), - ("event-motion", "Bewegung im Bild", "g-w3", "3x2", "50% 45%"), + ("band-extinct-group", "Band vor der Kamera", "g-w3", "3x2", "50% 40%"), ("movement-light", "Lange Belichtung", "g-w2", "1x1", "50% 50%"), ("event-speaker", "Statement vor der Kamera", "g-w2", "1x1", "50% 30%"), ("event-venue", "Location vor dem Dreh", "g-w2", "1x1", "50% 45%"), @@ -1129,6 +1130,116 @@ def video() -> str: ) +def musik_und_buehne() -> str: + path = "/musik-und-buehne/" + body = ( + page_head( + kicker="Musik & Bühne", + title="Bands, Künstler und Konzerte", + lead="Live, Bandportraits und Musikvideo — als Musiker kenne ich beide Seiten der Bühne.", + image="music-vocal-blue", + breadcrumb=("Musik & Bühne", path), + fit="50% 30%", + actions='Musikprojekt anfragen' + 'Arbeiten ansehen', + ) + + section( + kicker="Für Bands und Veranstalter", + title="Was ich für Musik und Bühne mache", + content='
' + + "".join( + f'

{e(t)}

{e(d)}

' + for t, d in [ + ( + "Live & Konzert", + "Bühnenbilder aus Clubs, Festivals und Vereinsheimen — mitten im Geschehen statt am Rand, auch bei wenig Licht.", + ), + ( + "Band- & Künstlerportraits", + "Pressebilder, Bandportraits und Artwork-Motive, die zur Musik passen.", + ), + ( + "Musikvideo", + "Konzept, Dreh, Schnitt und Farbkorrektur — für Songs, die ein Bild brauchen.", + ), + ( + "Promotion & Content", + "Standbilder und Kurzclips für Ankündigungen, Kanäle und Presse.", + ), + ] + ) + + "
", + ) + + f"""
+
+
+
+ Musikvideo +

{e(VIDEO['title'])}

+

{e(VIDEO['description'])}

+
+ Mehr zu Video {ICONS['arrow']} +
+
{video_block(with_meta=False)}
+
+
""" + + section( + variant="paper2", + kicker="Referenz", + title="Musikvideo Thjódrörir: Konzept, Dreh, Schnitt, Farbkorrektur", + lead="Der komplette Weg vom Song zum fertigen Video — ohne zweite Crew und ohne Zwischenabstimmungen zwischen Dienstleistern.", + content='
' + + "".join( + f'

{e(t)}

{e(d)}

' + for t, d in [ + ("Konzept", "Idee, Bildsprache und Drehorte, abgestimmt auf den Song."), + ("Dreh", "Kamera, Licht und Regie am Drehtag — aus einer Hand."), + ("Schnitt", "Schnitt und Aufbau des Videos in der Länge, die der Song vorgibt."), + ("Farbkorrektur", "Farbkorrektur und Ausgabe in den Formaten, die YouTube und die Kanäle brauchen."), + ] + ) + + "
", + ) + + section( + kicker="Aus der Arbeit", + title="Konzerte, Bands, Backstage", + lead="Für Extinct und Sagenbringer entstehen Bilder und Videoarbeiten — live, auf der Probe und für Ankündigungen.", + content=media_grid( + [ + ("music-guitar-silhouette", "Gitarrist als Silhouette im Bühnennebel", "4x5", "50% 42%"), + ("band-extinct-guitar", "Zwei Bandmitglieder stehen Rücken an Rücken mit Gitarre", "4x5", "50% 35%"), + ("music-neck", "Hand am Gitarrenhals im dunklen Bühnenlicht", "4x5", "50% 50%"), + ("music-mic", "Bärtiger Sänger am Mikrofon mit erhobener Hand", "3x2", "50% 35%"), + ("music-banner", "Gitarrist vor einem beleuchteten Bandbanner", "3x2", "50% 40%"), + ("band-sagenbringer", "Musiker in Fellkleidung im Wald, von unten fotografiert", "3x2", "50% 30%"), + ("music-drums", "Schlagzeuger am Drumset in Schwarzweiß", "1x1", "50% 45%"), + ("band-extinct-group", "Vier Bandmitglieder hintereinander in Schwarzweiß", "1x1", "50% 40%"), + ("music-horns", "Zwei Musiker trinken aus Trinkhörnern", "1x1", "50% 35%"), + ] + ), + ) + + cta_band( + title="Konzert, Band oder Musikvideo?", + text="Schreib mir, was ansteht: Termin, Ort und was du mit den Bildern oder dem Film vorhast.", + secondary=("Videoprojekte ansehen", "/video/"), + ) + ) + return page( + title="Musik & Bühne · Konzerte, Bands, Musikvideos · desfoto", + description="Musik- und Bühnenfotografie aus Neumünster: Konzerte, Bandportraits und Musikvideos — plus Video für Künstler und Veranstalter.", + path=path, + json_ld=[ + service_ld( + "Musik- und Bühnenfotografie", + path, + "Konzertfotografie, Bandportraits und Musikvideos für Bands, Künstler und Veranstalter.", + ), + breadcrumb_ld("Musik & Bühne", path), + ], + body=body, + ) + + def social_media() -> str: path = "/social-media/" body = ( @@ -1136,7 +1247,7 @@ def social_media() -> str: kicker="Social Media", title="Content, der regelmäßig kommt", lead="Foto- und Videomaterial im Format deiner Kanäle — wiederkehrend, planbar und über die Monate hinweg einheitlich.", - image="event-motion", + image="event-speaker", breadcrumb=("Social Media", path), fit="50% 42%", actions='Content anfragen', @@ -1214,9 +1325,9 @@ def social_media() -> str: + "".join( picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit) for slug, fit in [ - ("event-motion", "50% 45%"), - ("event-dance", "50% 36%"), - ("event-speaker", "50% 30%"), + ("event-venue", "50% 45%"), + ("free-modern-portrait", "50% 40%"), + ("music-dreadlocks", "50% 35%"), ("business-hotel-tea", "50% 45%"), ("business-hotel-laugh", "50% 45%"), ("music-drummer", "50% 45%"), @@ -1274,7 +1385,7 @@ def projekte() -> str: ] ) + '
' - 'Zum Musikvideo ' + 'Musik & Bühne ' + ICONS["arrow"] + "
", ) @@ -1343,7 +1454,7 @@ def projekte() -> str: + "".join( f'

{e(t)}

{e(d)}

' for t, d, h in [ - ("Shootings", "Business, Portrait, Familie und Minishootings.", "/shootings/"), + ("Fotografie", "Menschen, Unternehmen, Produkte und Veranstaltungen.", "/fotografie/"), ("Businessfotografie", "Portraits, Teams und Produkte für Unternehmen.", "/businessfotografie/"), ("Videoprojekte", "Musikvideo, Imagefilm und Eventnachbericht.", "/video/"), ] @@ -1367,8 +1478,8 @@ def ueber() -> str: page_head( kicker="Über", title="Fotograf und Videograf aus Neumünster", - lead="Ich bin Denny Schulz. Ich fotografiere Menschen, Marken und Musik — und produziere seit einigen Jahren genauso gern Bewegtbild.", - image="studio-setup", + lead="Hinter desfoto stehe ich, Denny Schulz. Fotograf, Videograf und Musiker aus Neumünster.", + image="gear-cases", breadcrumb=("Über", path), fit="50% 45%", actions='Schreib mir', @@ -1398,7 +1509,7 @@ def ueber() -> str: for t, d in [ ( "Menschen fotografieren", - "Portrait, Business, Model, Familie und Paar. Die Arbeit an Gesichtern, Licht und Ausdruck ist mein Kern.", + "Portrait, Business, Model und Familie. Die Arbeit an Gesichtern, Licht und Ausdruck ist mein Kern.", ), ( "Musik & Bühne begleiten", @@ -1443,9 +1554,9 @@ def ueber() -> str: content=media_grid( [ ("business-portrait-studio", "Mann im weißen Hemd vor dunklem Hintergrund, Softbox im Bild", "4x5", "50% 28%"), - ("studio-setup", "Studioequipment mit Logo von Denny Schulz Fotografie", "4x5", "50% 45%"), + ("studio-lens", "Kameraobjektiv im Studiolicht", "4x5", "50% 45%"), ("business-hotel-window", "Drei Frauen in Bademänteln und Handtuchturbanen am Fenster", "4x5", "50% 40%"), - ("couple-park", "Sara und Carlos stehen Arm in Arm im Grünen", "4x5", "50% 30%"), + ("portrait-marina-snow", "Marina mit roten Zöpfen auf einem verschneiten Waldweg", "4x5", "50% 30%"), ("free-fox", "Nahaufnahme eines Rotfuchses", "1x1", "50% 45%"), ], columns="grid--2", @@ -1455,7 +1566,7 @@ def ueber() -> str: variant="paper2", kicker="Netzwerk", title="Wo ich sonst zu finden bin", - lead="Zwei eigene Projekte, die zu desfoto gehören.", + lead="Zwei eigene Projekte des Betreibers.", content='
' + "".join( '
' @@ -1484,7 +1595,7 @@ def ueber() -> str: ) return page( title="Über desfoto · Denny Schulz, Neumünster", - description="desfoto ist die Foto- und Videoproduktion von Denny Schulz aus Neumünster. Arbeitsweise, mobiles Studio, Netzwerk und Kontaktmöglichkeiten.", + description="desfoto steht für Fotografie, Video und mediale Produktionen von Denny Schulz aus Neumünster: Arbeitsweise, mobiles Studio, Musik & Bühne, Netzwerk und Kontakt.", path=path, json_ld=[ breadcrumb_ld("Über", path), @@ -1494,7 +1605,7 @@ def ueber() -> str: "name": LEGAL["owner"], "jobTitle": "Fotograf und Videograf", "url": SITE["url"] + path, - "worksFor": {"@type": "Organization", "name": LEGAL["business"], "url": SITE["url"]}, + "worksFor": {"@type": "Organization", "name": SITE["brand"], "url": SITE["url"] + "/"}, "sameAs": [item["href"] for item in NETWORK], "address": { "@type": "PostalAddress", @@ -1576,7 +1687,7 @@ def kontakt() -> str: title="Lieber direkt telefonieren?", text="Kein Problem. Ruf einfach an — falls ich gerade im Termin bin, melde ich mich zurück.", primary=("Anrufen", "tel:+491754083133"), - secondary=("E-Mail schreiben", "mailto:info@dennyschulz.de"), + secondary=("E-Mail schreiben", f"mailto:{CONTACT['email']}"), ) ) return page( @@ -1831,12 +1942,13 @@ def not_found() -> str: ROUTES = [ ("/", "index.html", home, None, "1.0"), - ("/shootings/", "shootings/index.html", shootings, None, "0.9"), + ("/fotografie/", "fotografie/index.html", fotografie, None, "0.9"), ("/businessfotografie/", "businessfotografie/index.html", businessfotografie, None, "0.9"), ("/portrait-und-model/", "portrait-und-model/index.html", portrait_und_model, None, "0.9"), - ("/familien-und-paare/", "familien-und-paare/index.html", familien_und_paare, None, "0.9"), + ("/familie/", "familie/index.html", familie, None, "0.9"), ("/minishootings/", "minishootings/index.html", minishootings, None, "0.9"), ("/video/", "video/index.html", video, None, "0.9"), + ("/musik-und-buehne/", "musik-und-buehne/index.html", musik_und_buehne, None, "0.9"), ("/social-media/", "social-media/index.html", social_media, None, "0.9"), ("/projekte/", "projekte/index.html", projekte, None, "0.8"), ("/ueber/", "ueber/index.html", ueber, None, "0.8"), diff --git a/src/theme.py b/src/theme.py index 622592c..da9348e 100644 --- a/src/theme.py +++ b/src/theme.py @@ -204,6 +204,11 @@ img.frame{width:100%;height:auto;object-fit:cover;transition:transform .8s var(- .grid--2{grid-template-columns:repeat(auto-fit,minmax(19rem,1fr))} .grid--3{grid-template-columns:repeat(auto-fit,minmax(16.5rem,1fr))} .grid--4{grid-template-columns:repeat(auto-fit,minmax(13.5rem,1fr))} +/* Vier Arbeitsbereiche: vier Spalten auf großen, zwei auf mittleren und eine + auf kleinen Schirmen. auto-fit würde bei mittlerer Breite 3+1 Karten setzen. */ +.grid--areas{grid-template-columns:1fr} +@media (min-width:40rem){.grid--areas{grid-template-columns:repeat(2,minmax(0,1fr))}} +@media (min-width:72rem){.grid--areas{grid-template-columns:repeat(4,minmax(0,1fr))}} .card{display:flex;flex-direction:column;gap:1rem;height:100%} .card__title{display:flex;align-items:baseline;gap:.6rem} .card__num{font-size:.72rem;font-weight:600;letter-spacing:.16em;color:var(--accent)} diff --git a/tests/test_site.py b/tests/test_site.py index 671af0c..71d1baf 100644 --- a/tests/test_site.py +++ b/tests/test_site.py @@ -262,10 +262,11 @@ class Content(unittest.TestCase): def test_all_requested_services_are_reachable(self): home = read(SITE / "index.html") for href in ( - "/shootings/", + "/fotografie/", "/businessfotografie/", - "/familien-und-paare/", + "/familie/", "/minishootings/", + "/musik-und-buehne/", "/projekte/", "/video/", "/social-media/", @@ -315,31 +316,110 @@ class Content(unittest.TestCase): class Redesign(unittest.TestCase): - """The 2026 redesign: Shootings umbrella, real portfolio, logo, motion layer.""" + """The 2026 redesign: Arbeitsbereiche, real portfolio, logo, motion layer.""" - def test_nav_uses_shootings_as_the_photo_umbrella(self): + def test_nav_uses_fotografie_as_the_photo_umbrella(self): for file in HTML_FILES: text = read(file) with self.subTest(file=file.name): - self.assertIn('href="/shootings/"', text) - self.assertNotIn('href="/fotografie/', text) + self.assertIn('href="/fotografie/"', text) + self.assertIn('href="/familie/"', text) + self.assertNotIn('href="/shootings', text) + self.assertNotIn('href="/familien-und-paare', text) - def test_all_four_shootings_are_grouped_under_the_umbrella(self): - shootings = read(SITE / "shootings" / "index.html") + def test_all_four_photo_schwerpunkte_are_grouped_under_the_umbrella(self): + fotografie = read(SITE / "fotografie" / "index.html") for href in ( "/businessfotografie/", "/portrait-und-model/", - "/familien-und-paare/", + "/familie/", "/minishootings/", ): with self.subTest(href=href): - self.assertIn(f'href="{href}"', shootings) + self.assertIn(f'href="{href}"', fotografie) - def test_photo_and_video_are_separate_but_both_on_the_homepage(self): + def test_homepage_explains_the_four_arbeitsbereiche(self): home = read(SITE / "index.html") - self.assertEqual(home.count('
", start) + block = home[start:end] + self.assertEqual(4, block.count('
')) + for href in ("/fotografie/", "/video/", "/musik-und-buehne/", "/projekte/"): + with self.subTest(href=href): + self.assertIn(f'href="{href}"', block) + for scope in ( + "Menschen, Unternehmen, Produkte, Veranstaltungen.", + "Unternehmen, Musik, Events, Social Content.", + "Bands, Künstler, Konzerte, Musikvideo.", + "Freie Arbeiten, Kooperationen, besondere Produktionen.", + ): + with self.subTest(scope=scope): + self.assertIn(scope, block) + + def test_homepage_separates_bookable_work_from_own_projects(self): + home = read(SITE / "index.html") + self.assertIn("Was du bei mir buchen kannst", home) + self.assertIn("Was ich außerdem mache", home) + self.assertLess( + home.index("Was du bei mir buchen kannst"), + home.index("Was ich außerdem mache"), + ) + + def test_couples_are_not_advertised_on_desfoto(self): + """Paare/Engagement stay with dennyschulz.de; desfoto keeps single portraits.""" + for file in HTML_FILES: + text = read(file) + with self.subTest(file=file.name): + for needle in ("Paarshooting", "Paarportrait", "Verlobung", "Ja-Wort", "Brautpaar"): + self.assertNotIn(needle, text) + # Der einzige erlaubte Paar-Bezug ist der Hinweis auf den eigenen + # Schwerpunkt auf dennyschulz.de im Footer. + remainder = visible_text(text).replace("Hochzeiten und Paare", "") + self.assertNotIn("Paar", remainder) + + def test_footer_carries_the_ownership_line_without_the_legal_entity(self): + """The brand is tied to its owner in one unobtrusive footer sentence.""" + for file in HTML_FILES: + text = read(file) + with self.subTest(file=file.name): + self.assertIn("desfoto ist ein Angebot von Denny Schulz, Neumünster.", text) + footer = text[text.index(" is invalid and the parser splits the surrounding layout.""" @@ -370,30 +450,54 @@ class Redesign(unittest.TestCase): self.assertEqual([], guard.problems) self.assertEqual(0, guard.depth, "unbalanced ") - def test_pillar_grid_has_exactly_two_children(self): - """Regression: the two pillars must stay two grid children.""" + def test_every_area_card_links_to_its_area(self): + """Each Arbeitsbereich card keeps its own link (no nested anchors).""" home = read(SITE / "index.html") - start = home.index('
", start) - self.assertEqual(2, home[start:end].count('
')) + block = home[start:end] + self.assertEqual(4, block.count('class="link-arrow"')) + self.assertNotIn("https://desfoto.de/shootings/", sitemap) - self.assertNotIn("https://desfoto.de/fotografie/", sitemap) + for route in ("fotografie", "familie", "musik-und-buehne"): + with self.subTest(route=route): + self.assertIn(f"https://desfoto.de/{route}/", sitemap) + for retired in ("shootings", "familien-und-paare"): + with self.subTest(route=retired): + self.assertNotIn(f"https://desfoto.de/{retired}/", sitemap) def test_operator_logo_is_the_brand_mark(self): self.assertTrue((SITE / "assets" / "img" / "logo-160.png").is_file()) @@ -459,7 +586,7 @@ class Redesign(unittest.TestCase): def test_mobile_studio_is_described_where_photo_work_is_sold(self): needles = ("Mobiles Studio", "Autarke Blitzanlage", "autarker Blitzanlage") - for rel in ("index.html", "shootings/index.html", "businessfotografie/index.html"): + for rel in ("index.html", "fotografie/index.html", "businessfotografie/index.html"): text = read(SITE / rel) for needle in needles: with self.subTest(file=rel, needle=needle): @@ -469,7 +596,7 @@ class Redesign(unittest.TestCase): """Projects can also be photographed in the operator's own studio on site.""" for rel in ( "index.html", - "shootings/index.html", + "fotografie/index.html", "businessfotografie/index.html", "ueber/index.html", ): @@ -489,13 +616,20 @@ class Redesign(unittest.TestCase): "free-sparrow", "free-dogs-forest", ), - "familien-und-paare": ( - "couple-park", - "couple-backlight-kiss", - "couple-forest-walk", - "couple-sun-silhouette", - "couple-laugh-walk", + "familie": ( + "free-lake-person", + "free-portrait-light", "portrait-marina-snow", + "free-modern-portrait", + "free-woman-coast", + "portrait-marina-sunset", + ), + "musik-und-buehne": ( + "music-guitar-silhouette", + "band-extinct-guitar", + "music-neck", + "music-banner", + "music-drums", ), "portrait-und-model": ("editorial-studio-side", "editorial-balance", "editorial-leap"), "businessfotografie": ( @@ -522,6 +656,16 @@ class Redesign(unittest.TestCase): with self.subTest(page="video", slug=slug): self.assertIn(f"/assets/img/{slug}-1200.webp", video) + def test_musik_und_buehne_page_shows_the_real_cases(self): + page = read(SITE / "musik-und-buehne" / "index.html") + for needle in ("Thjódrörir", "Extinct", "Sagenbringer"): + with self.subTest(needle=needle): + self.assertIn(needle, page) + for step in ("Konzept", "Dreh", "Schnitt", "Farbkorrektur"): + with self.subTest(step=step): + self.assertIn(f"

{step}

", page) + self.assertIn("NWWFTf7l8g0", page) + def test_invented_japan_content_is_absent(self): for file in HTML_FILES: text = read(file).lower() @@ -601,11 +745,17 @@ class Release(unittest.TestCase): self.assertEqual(2, deploy.count("up -d --force-recreate --remove-orphans")) self.assertIn("docker exec desfoto-web-1 wget -q --spider http://127.0.0.1/", deploy) - def test_verify_hook_covers_the_renamed_route(self): + def test_verify_hook_covers_the_renamed_routes(self): verify = read(ROOT / ".ocauto" / "verify") - self.assertIn('check_redirect "$base/fotografie/" "$base/shootings/"', verify) - self.assertIn("""check_not_contains "$base/" 'href="/fotografie/'""", verify) - self.assertNotIn('/fotografie/ /businessfotografie/', verify) + for check in ( + 'check_redirect "$base/shootings/" "$base/fotografie/"', + 'check_redirect "$base/familien-und-paare/" "$base/familie/"', + 'check_contains "$base/fotografie/"', + 'check_contains "$base/musik-und-buehne/"', + ): + with self.subTest(check=check): + self.assertIn(check, verify) + self.assertNotIn("/shootings/ /businessfotografie/", verify) self.assertNotIn("hero-studio-1200.webp", verify) def test_deploy_hooks_are_executable(self):