feat: reposition desfoto as an independent brand with four Arbeitsbereiche
@@ -90,15 +90,21 @@ if [ "$live" = 1 ] && [ "$release_sha" != "unknown" ] && git -C "$root" rev-pars
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
printf '\n== routes\n'
|
printf '\n== routes\n'
|
||||||
for route in / /shootings/ /businessfotografie/ /portrait-und-model/ /familien-und-paare/ \
|
for route in / /fotografie/ /businessfotografie/ /portrait-und-model/ /familie/ \
|
||||||
/minishootings/ /video/ /social-media/ /projekte/ /ueber/ /kontakt/ /impressum/ /datenschutz/; do
|
/minishootings/ /video/ /musik-und-buehne/ /social-media/ /projekte/ /ueber/ /kontakt/ /impressum/ /datenschutz/; do
|
||||||
check_status "$base$route" 200
|
check_status "$base$route" 200
|
||||||
done
|
done
|
||||||
|
|
||||||
# The retired photo URL must keep working: it is the umbrella page now.
|
# The retired photo URL must keep working: it is the umbrella page now.
|
||||||
check_redirect "$base/fotografie" "$base/shootings/"
|
check_redirect "$base/shootings" "$base/fotografie/"
|
||||||
check_redirect "$base/fotografie/" "$base/shootings/"
|
check_redirect "$base/shootings/" "$base/fotografie/"
|
||||||
check_redirect "$base/fotografie.html" "$base/shootings/"
|
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'
|
printf '\n== canonical host and redirects\n'
|
||||||
if [ "$live" = 1 ]; then
|
if [ "$live" = 1 ]; then
|
||||||
@@ -117,13 +123,19 @@ esac
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
printf '\n== content\n'
|
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/" 'href="/video/"'
|
||||||
check_contains "$base/" "Mobiles Studio"
|
check_contains "$base/" "Mobiles Studio"
|
||||||
check_contains "$base/" "Studio Neumünster"
|
check_contains "$base/" "Studio Neumünster"
|
||||||
check_not_contains "$base/" 'href="/fotografie/'
|
check_contains "$base/" "desfoto ist ein Angebot von Denny Schulz, Neumünster."
|
||||||
check_contains "$base/shootings/" "Autarke Blitzanlage"
|
check_not_contains "$base/" 'href="/shootings'
|
||||||
check_contains "$base/shootings/" "Minishootings"
|
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/" "§ 5 DDG"
|
||||||
check_contains "$base/impressum/" "DE462149560"
|
check_contains "$base/impressum/" "DE462149560"
|
||||||
check_contains "$base/datenschutz/" "keine Zugriffsprotokolle"
|
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/video/" "NWWFTf7l8g0"
|
||||||
check_contains "$base/" "https://www.dennyschulz.de/"
|
check_contains "$base/" "https://www.dennyschulz.de/"
|
||||||
check_contains "$base/" "https://dennyapp.de/"
|
check_contains "$base/" "https://dennyapp.de/"
|
||||||
check_contains "$base/sitemap.xml" "<loc>https://desfoto.de/shootings/</loc>"
|
check_contains "$base/sitemap.xml" "<loc>https://desfoto.de/fotografie/</loc>"
|
||||||
check_not_contains "$base/sitemap.xml" "<loc>https://desfoto.de/fotografie/</loc>"
|
check_not_contains "$base/sitemap.xml" "<loc>https://desfoto.de/shootings/</loc>"
|
||||||
|
|
||||||
printf '\n== error handling and metadata\n'
|
printf '\n== error handling and metadata\n'
|
||||||
check_status "$base/diese-seite-gibt-es-nicht/" 404
|
check_status "$base/diese-seite-gibt-es-nicht/" 404
|
||||||
@@ -143,20 +155,31 @@ check_status "$base/robots.txt" 200
|
|||||||
printf '\n== assets\n'
|
printf '\n== assets\n'
|
||||||
check_status "$base/assets/img/logo-160.png" 200
|
check_status "$base/assets/img/logo-160.png" 200
|
||||||
check_status "$base/assets/img/favicon-32.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/site.css" 200
|
||||||
check_status "$base/assets/fonts/fraunces-latin.woff2" 200
|
check_status "$base/assets/fonts/fraunces-latin.woff2" 200
|
||||||
# The portfolio expansion ships the operator's own public work...
|
# 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/music-dreadlocks-1200.webp" 200
|
||||||
check_status "$base/assets/img/business-hotel-window-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
|
check_status "$base/assets/img/editorial-leap-1200.webp" 200
|
||||||
# ...and the wedding pictures showing the guests stay removed.
|
# ...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-feier-1200.webp" 404
|
||||||
check_status "$base/assets/img/group-outdoor-1200.webp" 404
|
check_status "$base/assets/img/group-outdoor-1200.webp" 404
|
||||||
check_status "$base/assets/img/group-posiert-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/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'
|
printf '\n== privacy promises and headers\n'
|
||||||
check_header "$base/" "Strict-Transport-Security"
|
check_header "$base/" "Strict-Transport-Security"
|
||||||
|
|||||||
31
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").
|
request and is the only permitted reference (see "Editing rules").
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
- `/` — hero, two pillars (**Shootings** = photo, **Videoprojekte** = video), portfolio
|
- `/` — hero, the four **Arbeitsbereiche** (Fotografie, Video, Musik & Bühne, Projekte),
|
||||||
gallery, music video, mobiles Studio, process, facts.
|
the bookable services ("Was du bei mir buchen kannst"), the portfolio gallery
|
||||||
- `/shootings/` — the umbrella for all photographic work: Businessfotografie,
|
("Was ich außerdem mache"), music video, mobiles Studio, process, facts.
|
||||||
Portrait & Model, Familien & Paare, Minishootings, plus the mobiles Studio.
|
- `/fotografie/` — the umbrella for all photographic work: Businessfotografie,
|
||||||
`/fotografie` and `/fotografie/` are 301-redirected here by `nginx.conf`.
|
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/`.
|
- `/video/`, `/social-media/`, `/projekte/` (music + free work + pro bono), `/ueber/`.
|
||||||
|
|
||||||
## Commands
|
## 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.
|
the content stays hidden forever. `.reveal--mask` therefore clips its children.
|
||||||
- Never nest `<a>` inside `<a>` (or any other invalid nesting). The parser closes the
|
- 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
|
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
|
two homepage pillars the redesign replaced. Cards with their own links use the
|
||||||
(`.pillar__go::after`); `tests/test_site.py` guards this for every page.
|
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
|
- Two-space indentation, `from __future__ import annotations`, type hints on public
|
||||||
functions, no new third-party Python packages (Pillow is the only dependency).
|
functions, no new third-party Python packages (Pillow is the only dependency).
|
||||||
|
|
||||||
|
|||||||
19
nginx.conf
@@ -64,13 +64,22 @@ server {
|
|||||||
location = /impressum.html { return 301 /impressum/; }
|
location = /impressum.html { return 301 /impressum/; }
|
||||||
location = /datenschutz.html { return 301 /datenschutz/; }
|
location = /datenschutz.html { return 301 /datenschutz/; }
|
||||||
location = /kontakt.html { return 301 /kontakt/; }
|
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 is the umbrella term for the photographic work; the old
|
||||||
# /fotografie/ URL stays reachable and points to the new page.
|
# /shootings/ URL stays reachable and points to the new page.
|
||||||
# (/fotografie/index.html is already canonicalised to /fotografie/ above.)
|
# (/fotografie/index.html is already canonicalised to /fotografie/ above.)
|
||||||
location = /fotografie { return 301 /shootings/; }
|
location = /shootings { return 301 /fotografie/; }
|
||||||
location = /fotografie/ { return 301 /shootings/; }
|
location = /shootings/ { return 301 /fotografie/; }
|
||||||
location = /fotografie.html { return 301 /shootings/; }
|
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/ {
|
location ^~ /.well-known/ {
|
||||||
allow all;
|
allow all;
|
||||||
|
|||||||
@@ -48,15 +48,16 @@ ACCENT = (192, 67, 28)
|
|||||||
# OpenGraph image per route, built from the page's own key visual.
|
# OpenGraph image per route, built from the page's own key visual.
|
||||||
ROUTE_OG = {
|
ROUTE_OG = {
|
||||||
"/": ("music-vocal-blue", "og-desfoto.jpg", "Bilder und Filme, die nicht beliebig aussehen."),
|
"/": ("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"),
|
"/businessfotografie/": ("business-portrait-studio", "og-businessfotografie.jpg", "Businessfotografie"),
|
||||||
"/portrait-und-model/": ("editorial-colour", "og-portrait.jpg", "Portrait & Model"),
|
"/portrait-und-model/": ("editorial-colour", "og-portrait.jpg", "Portrait & Model"),
|
||||||
"/familien-und-paare/": ("people-joy", "og-familien.jpg", "Familien & Paare"),
|
"/familie/": ("free-portrait-light", "og-familie.jpg", "Familienfotografie"),
|
||||||
"/minishootings/": ("portrait-natural", "og-minishootings.jpg", "Minishootings"),
|
"/minishootings/": ("free-modern-portrait", "og-minishootings.jpg", "Minishootings"),
|
||||||
"/video/": ("music-guitar-silhouette", "og-video.jpg", "Videoprojekte"),
|
"/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"),
|
"/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"),
|
"/kontakt/": ("event-venue", "og-kontakt.jpg", "Kontakt"),
|
||||||
"/impressum/": ("gear-cases", "og-impressum.jpg", "Impressum"),
|
"/impressum/": ("gear-cases", "og-impressum.jpg", "Impressum"),
|
||||||
"/datenschutz/": ("event-venue", "og-datenschutz.jpg", "Datenschutz"),
|
"/datenschutz/": ("event-venue", "og-datenschutz.jpg", "Datenschutz"),
|
||||||
@@ -563,6 +564,24 @@ def link_check(routes: list[tuple[str, str]]) -> list[str]:
|
|||||||
return problems
|
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:
|
def normalize_permissions() -> None:
|
||||||
"""Make the build output readable for the serving nginx worker.
|
"""Make the build output readable for the serving nginx worker.
|
||||||
|
|
||||||
@@ -587,6 +606,7 @@ def main() -> int:
|
|||||||
build_brand_assets(table)
|
build_brand_assets(table)
|
||||||
print("== pages ==")
|
print("== pages ==")
|
||||||
routes = build_html(table)
|
routes = build_html(table)
|
||||||
|
prune_stale_pages(routes)
|
||||||
build_meta_files(routes)
|
build_meta_files(routes)
|
||||||
build_script()
|
build_script()
|
||||||
print("== checks ==")
|
print("== checks ==")
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<button class="nav__toggle" type="button" aria-expanded="false" aria-controls="site-nav" data-nav-toggle>
|
<button class="nav__toggle" type="button" aria-expanded="false" aria-controls="site-nav" data-nav-toggle>
|
||||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> Menü
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> Menü
|
||||||
</button>
|
</button>
|
||||||
<nav class="nav" id="site-nav" aria-label="Hauptnavigation"><div class="nav__item"><a class="nav__link" href="/shootings/">Shootings<svg class="nav__caret" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6l5 5 5-5"/></svg></a><div class="nav__sub"><a href="/businessfotografie/">Businessfotografie</a><a href="/portrait-und-model/">Portrait & Model</a><a href="/familien-und-paare/">Familien & Paare</a><a href="/minishootings/">Minishootings</a></div></div><a class="nav__link" href="/video/">Video</a><a class="nav__link" href="/projekte/">Projekte</a><a class="nav__link" href="/ueber/">Über</a><a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a></nav>
|
<nav class="nav" id="site-nav" aria-label="Hauptnavigation"><div class="nav__item"><a class="nav__link" href="/fotografie/">Fotografie<svg class="nav__caret" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6l5 5 5-5"/></svg></a><div class="nav__sub"><a href="/businessfotografie/">Businessfotografie</a><a href="/portrait-und-model/">Portrait & Model</a><a href="/familie/">Familie</a><a href="/minishootings/">Minishootings</a></div></div><a class="nav__link" href="/video/">Video</a><a class="nav__link" href="/musik-und-buehne/">Musik & Bühne</a><a class="nav__link" href="/projekte/">Projekte</a><a class="nav__link" href="/ueber/">Über</a><a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a></nav>
|
||||||
</div>
|
</div>
|
||||||
<span class="progress" aria-hidden="true"><span class="progress__bar" data-progress></span></span>
|
<span class="progress" aria-hidden="true"><span class="progress__bar" data-progress></span></span>
|
||||||
</header>
|
</header>
|
||||||
@@ -69,23 +69,29 @@
|
|||||||
<span class="brand__mark"><img src="/assets/img/logo-160.png" width="160" height="160" alt="" decoding="async"></span>
|
<span class="brand__mark"><img src="/assets/img/logo-160.png" width="160" height="160" alt="" decoding="async"></span>
|
||||||
<span class="brand__text"><span>desfoto</span><small>Fotografie & Video</small></span>
|
<span class="brand__text"><span>desfoto</span><small>Fotografie & Video</small></span>
|
||||||
</span>
|
</span>
|
||||||
<p class="footer__claim">Fotografie und Video aus Neumünster. Ein Angebot von Denny Schulz Fotografie.</p>
|
<p class="footer__claim">desfoto ist ein Angebot von Denny Schulz, Neumünster.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Shootings</h2>
|
<h2>Fotografie</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/shootings/">Überblick Shootings</a>
|
<a href="/fotografie/">Überblick Fotografie</a>
|
||||||
<a href="/businessfotografie/">Businessfotografie</a>
|
<a href="/businessfotografie/">Businessfotografie</a>
|
||||||
<a href="/portrait-und-model/">Portrait & Model</a>
|
<a href="/portrait-und-model/">Portrait & Model</a>
|
||||||
<a href="/familien-und-paare/">Familien & Paare</a>
|
<a href="/familie/">Familie</a>
|
||||||
<a href="/minishootings/">Minishootings</a>
|
<a href="/minishootings/">Minishootings</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Video & mehr</h2>
|
<h2>Video & Bühne</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/video/">Videoprojekte</a>
|
<a href="/video/">Videoprojekte</a>
|
||||||
|
<a href="/musik-und-buehne/">Musik & Bühne</a>
|
||||||
<a href="/social-media/">Social Media</a>
|
<a href="/social-media/">Social Media</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Mehr</h2>
|
||||||
|
<div class="footer__links">
|
||||||
<a href="/projekte/">Projekte</a>
|
<a href="/projekte/">Projekte</a>
|
||||||
<a href="/ueber/">Über desfoto</a>
|
<a href="/ueber/">Über desfoto</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,7 +107,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Netzwerk</h2>
|
<h2>Netzwerk</h2>
|
||||||
<div class="footer__links"><a class="footer__ext" href="https://www.dennyschulz.de/" target="_blank" rel="noopener">dennyschulz.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Hochzeiten, Portraits und Business — die fotografische Arbeit unter eigenem Namen.</small><a class="footer__ext" href="https://dennyapp.de/" target="_blank" rel="noopener">dennyapp.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Websites und Web-Apps aus Neumünster — Design, Entwicklung und eigene Produkte.</small></div>
|
<div class="footer__links"><a class="footer__ext" href="https://www.dennyschulz.de/" target="_blank" rel="noopener">dennyschulz.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Hochzeiten und Paare — die Arbeit unter eigenem Namen, mit eigenem Schwerpunkt.</small><a class="footer__ext" href="https://dennyapp.de/" target="_blank" rel="noopener">dennyapp.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Websites und Web-Apps aus Neumünster — Design, Entwicklung und eigene Produkte.</small></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer__bottom">
|
<div class="footer__bottom">
|
||||||
@@ -110,7 +116,7 @@
|
|||||||
<a href="/datenschutz/">Datenschutz</a>
|
<a href="/datenschutz/">Datenschutz</a>
|
||||||
<a href="/kontakt/">Kontakt</a>
|
<a href="/kontakt/">Kontakt</a>
|
||||||
</nav>
|
</nav>
|
||||||
<span>© Denny Schulz · Denny Schulz Fotografie · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
<span>© Denny Schulz · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 466 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 22 KiB |
BIN
site/assets/img/og-familie.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 89 KiB |
BIN
site/assets/img/og-fotografie.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 87 KiB |
BIN
site/assets/img/og-musik-und-buehne.jpg
Normal file
|
After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 35 KiB |
@@ -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--2{grid-template-columns:repeat(auto-fit,minmax(19rem,1fr))}
|
||||||
.grid--3{grid-template-columns:repeat(auto-fit,minmax(16.5rem,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))}
|
.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{display:flex;flex-direction:column;gap:1rem;height:100%}
|
||||||
.card__title{display:flex;align-items:baseline;gap:.6rem}
|
.card__title{display:flex;align-items:baseline;gap:.6rem}
|
||||||
.card__num{font-size:.72rem;font-weight:600;letter-spacing:.16em;color:var(--accent)}
|
.card__num{font-size:.72rem;font-weight:600;letter-spacing:.16em;color:var(--accent)}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
<button class="nav__toggle" type="button" aria-expanded="false" aria-controls="site-nav" data-nav-toggle>
|
<button class="nav__toggle" type="button" aria-expanded="false" aria-controls="site-nav" data-nav-toggle>
|
||||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> Menü
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> Menü
|
||||||
</button>
|
</button>
|
||||||
<nav class="nav" id="site-nav" aria-label="Hauptnavigation"><div class="nav__item"><a class="nav__link" href="/shootings/">Shootings<svg class="nav__caret" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6l5 5 5-5"/></svg></a><div class="nav__sub"><a href="/businessfotografie/">Businessfotografie</a><a href="/portrait-und-model/">Portrait & Model</a><a href="/familien-und-paare/">Familien & Paare</a><a href="/minishootings/">Minishootings</a></div></div><a class="nav__link" href="/video/">Video</a><a class="nav__link" href="/projekte/">Projekte</a><a class="nav__link" href="/ueber/">Über</a><a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a></nav>
|
<nav class="nav" id="site-nav" aria-label="Hauptnavigation"><div class="nav__item"><a class="nav__link" href="/fotografie/">Fotografie<svg class="nav__caret" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6l5 5 5-5"/></svg></a><div class="nav__sub"><a href="/businessfotografie/">Businessfotografie</a><a href="/portrait-und-model/">Portrait & Model</a><a href="/familie/">Familie</a><a href="/minishootings/">Minishootings</a></div></div><a class="nav__link" href="/video/">Video</a><a class="nav__link" href="/musik-und-buehne/">Musik & Bühne</a><a class="nav__link" href="/projekte/">Projekte</a><a class="nav__link" href="/ueber/">Über</a><a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a></nav>
|
||||||
</div>
|
</div>
|
||||||
<span class="progress" aria-hidden="true"><span class="progress__bar" data-progress></span></span>
|
<span class="progress" aria-hidden="true"><span class="progress__bar" data-progress></span></span>
|
||||||
</header>
|
</header>
|
||||||
@@ -56,7 +56,6 @@
|
|||||||
<span class="kicker">Rechtliches</span>
|
<span class="kicker">Rechtliches</span>
|
||||||
<h1 class="balance">Datenschutzerklärung</h1>
|
<h1 class="balance">Datenschutzerklärung</h1>
|
||||||
<p class="page-head__lead">Informationen zur Verarbeitung personenbezogener Daten · Stand September 2026</p>
|
<p class="page-head__lead">Informationen zur Verarbeitung personenbezogener Daten · Stand September 2026</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section><section class="section"><div class="shell"><div class="prose">
|
</section><section class="section"><div class="shell"><div class="prose">
|
||||||
<p>Diese Datenschutzerklärung informiert dich darüber, welche personenbezogenen Daten beim Besuch
|
<p>Diese Datenschutzerklärung informiert dich darüber, welche personenbezogenen Daten beim Besuch
|
||||||
@@ -185,23 +184,29 @@
|
|||||||
<span class="brand__mark"><img src="/assets/img/logo-160.png" width="160" height="160" alt="" decoding="async"></span>
|
<span class="brand__mark"><img src="/assets/img/logo-160.png" width="160" height="160" alt="" decoding="async"></span>
|
||||||
<span class="brand__text"><span>desfoto</span><small>Fotografie & Video</small></span>
|
<span class="brand__text"><span>desfoto</span><small>Fotografie & Video</small></span>
|
||||||
</span>
|
</span>
|
||||||
<p class="footer__claim">Fotografie und Video aus Neumünster. Ein Angebot von Denny Schulz Fotografie.</p>
|
<p class="footer__claim">desfoto ist ein Angebot von Denny Schulz, Neumünster.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Shootings</h2>
|
<h2>Fotografie</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/shootings/">Überblick Shootings</a>
|
<a href="/fotografie/">Überblick Fotografie</a>
|
||||||
<a href="/businessfotografie/">Businessfotografie</a>
|
<a href="/businessfotografie/">Businessfotografie</a>
|
||||||
<a href="/portrait-und-model/">Portrait & Model</a>
|
<a href="/portrait-und-model/">Portrait & Model</a>
|
||||||
<a href="/familien-und-paare/">Familien & Paare</a>
|
<a href="/familie/">Familie</a>
|
||||||
<a href="/minishootings/">Minishootings</a>
|
<a href="/minishootings/">Minishootings</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Video & mehr</h2>
|
<h2>Video & Bühne</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/video/">Videoprojekte</a>
|
<a href="/video/">Videoprojekte</a>
|
||||||
|
<a href="/musik-und-buehne/">Musik & Bühne</a>
|
||||||
<a href="/social-media/">Social Media</a>
|
<a href="/social-media/">Social Media</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Mehr</h2>
|
||||||
|
<div class="footer__links">
|
||||||
<a href="/projekte/">Projekte</a>
|
<a href="/projekte/">Projekte</a>
|
||||||
<a href="/ueber/">Über desfoto</a>
|
<a href="/ueber/">Über desfoto</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -217,7 +222,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Netzwerk</h2>
|
<h2>Netzwerk</h2>
|
||||||
<div class="footer__links"><a class="footer__ext" href="https://www.dennyschulz.de/" target="_blank" rel="noopener">dennyschulz.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Hochzeiten, Portraits und Business — die fotografische Arbeit unter eigenem Namen.</small><a class="footer__ext" href="https://dennyapp.de/" target="_blank" rel="noopener">dennyapp.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Websites und Web-Apps aus Neumünster — Design, Entwicklung und eigene Produkte.</small></div>
|
<div class="footer__links"><a class="footer__ext" href="https://www.dennyschulz.de/" target="_blank" rel="noopener">dennyschulz.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Hochzeiten und Paare — die Arbeit unter eigenem Namen, mit eigenem Schwerpunkt.</small><a class="footer__ext" href="https://dennyapp.de/" target="_blank" rel="noopener">dennyapp.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Websites und Web-Apps aus Neumünster — Design, Entwicklung und eigene Produkte.</small></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer__bottom">
|
<div class="footer__bottom">
|
||||||
@@ -226,7 +231,7 @@
|
|||||||
<a href="/datenschutz/">Datenschutz</a>
|
<a href="/datenschutz/">Datenschutz</a>
|
||||||
<a href="/kontakt/">Kontakt</a>
|
<a href="/kontakt/">Kontakt</a>
|
||||||
</nav>
|
</nav>
|
||||||
<span>© Denny Schulz · Denny Schulz Fotografie · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
<span>© Denny Schulz · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
138
site/familie/index.html
Normal file
138
site/fotografie/index.html
Normal file
@@ -43,7 +43,7 @@
|
|||||||
<button class="nav__toggle" type="button" aria-expanded="false" aria-controls="site-nav" data-nav-toggle>
|
<button class="nav__toggle" type="button" aria-expanded="false" aria-controls="site-nav" data-nav-toggle>
|
||||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> Menü
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> Menü
|
||||||
</button>
|
</button>
|
||||||
<nav class="nav" id="site-nav" aria-label="Hauptnavigation"><div class="nav__item"><a class="nav__link" href="/shootings/">Shootings<svg class="nav__caret" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6l5 5 5-5"/></svg></a><div class="nav__sub"><a href="/businessfotografie/">Businessfotografie</a><a href="/portrait-und-model/">Portrait & Model</a><a href="/familien-und-paare/">Familien & Paare</a><a href="/minishootings/">Minishootings</a></div></div><a class="nav__link" href="/video/">Video</a><a class="nav__link" href="/projekte/">Projekte</a><a class="nav__link" href="/ueber/">Über</a><a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a></nav>
|
<nav class="nav" id="site-nav" aria-label="Hauptnavigation"><div class="nav__item"><a class="nav__link" href="/fotografie/">Fotografie<svg class="nav__caret" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6l5 5 5-5"/></svg></a><div class="nav__sub"><a href="/businessfotografie/">Businessfotografie</a><a href="/portrait-und-model/">Portrait & Model</a><a href="/familie/">Familie</a><a href="/minishootings/">Minishootings</a></div></div><a class="nav__link" href="/video/">Video</a><a class="nav__link" href="/musik-und-buehne/">Musik & Bühne</a><a class="nav__link" href="/projekte/">Projekte</a><a class="nav__link" href="/ueber/">Über</a><a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a></nav>
|
||||||
</div>
|
</div>
|
||||||
<span class="progress" aria-hidden="true"><span class="progress__bar" data-progress></span></span>
|
<span class="progress" aria-hidden="true"><span class="progress__bar" data-progress></span></span>
|
||||||
</header>
|
</header>
|
||||||
@@ -56,7 +56,6 @@
|
|||||||
<span class="kicker">Rechtliches</span>
|
<span class="kicker">Rechtliches</span>
|
||||||
<h1 class="balance">Impressum</h1>
|
<h1 class="balance">Impressum</h1>
|
||||||
<p class="page-head__lead">Angaben gemäß § 5 DDG · Stand September 2026</p>
|
<p class="page-head__lead">Angaben gemäß § 5 DDG · Stand September 2026</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section><section class="section"><div class="shell"><div class="prose">
|
</section><section class="section"><div class="shell"><div class="prose">
|
||||||
<h2>Diensteanbieter</h2>
|
<h2>Diensteanbieter</h2>
|
||||||
@@ -110,23 +109,29 @@
|
|||||||
<span class="brand__mark"><img src="/assets/img/logo-160.png" width="160" height="160" alt="" decoding="async"></span>
|
<span class="brand__mark"><img src="/assets/img/logo-160.png" width="160" height="160" alt="" decoding="async"></span>
|
||||||
<span class="brand__text"><span>desfoto</span><small>Fotografie & Video</small></span>
|
<span class="brand__text"><span>desfoto</span><small>Fotografie & Video</small></span>
|
||||||
</span>
|
</span>
|
||||||
<p class="footer__claim">Fotografie und Video aus Neumünster. Ein Angebot von Denny Schulz Fotografie.</p>
|
<p class="footer__claim">desfoto ist ein Angebot von Denny Schulz, Neumünster.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Shootings</h2>
|
<h2>Fotografie</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/shootings/">Überblick Shootings</a>
|
<a href="/fotografie/">Überblick Fotografie</a>
|
||||||
<a href="/businessfotografie/">Businessfotografie</a>
|
<a href="/businessfotografie/">Businessfotografie</a>
|
||||||
<a href="/portrait-und-model/">Portrait & Model</a>
|
<a href="/portrait-und-model/">Portrait & Model</a>
|
||||||
<a href="/familien-und-paare/">Familien & Paare</a>
|
<a href="/familie/">Familie</a>
|
||||||
<a href="/minishootings/">Minishootings</a>
|
<a href="/minishootings/">Minishootings</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Video & mehr</h2>
|
<h2>Video & Bühne</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/video/">Videoprojekte</a>
|
<a href="/video/">Videoprojekte</a>
|
||||||
|
<a href="/musik-und-buehne/">Musik & Bühne</a>
|
||||||
<a href="/social-media/">Social Media</a>
|
<a href="/social-media/">Social Media</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Mehr</h2>
|
||||||
|
<div class="footer__links">
|
||||||
<a href="/projekte/">Projekte</a>
|
<a href="/projekte/">Projekte</a>
|
||||||
<a href="/ueber/">Über desfoto</a>
|
<a href="/ueber/">Über desfoto</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,7 +147,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Netzwerk</h2>
|
<h2>Netzwerk</h2>
|
||||||
<div class="footer__links"><a class="footer__ext" href="https://www.dennyschulz.de/" target="_blank" rel="noopener">dennyschulz.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Hochzeiten, Portraits und Business — die fotografische Arbeit unter eigenem Namen.</small><a class="footer__ext" href="https://dennyapp.de/" target="_blank" rel="noopener">dennyapp.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Websites und Web-Apps aus Neumünster — Design, Entwicklung und eigene Produkte.</small></div>
|
<div class="footer__links"><a class="footer__ext" href="https://www.dennyschulz.de/" target="_blank" rel="noopener">dennyschulz.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Hochzeiten und Paare — die Arbeit unter eigenem Namen, mit eigenem Schwerpunkt.</small><a class="footer__ext" href="https://dennyapp.de/" target="_blank" rel="noopener">dennyapp.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Websites und Web-Apps aus Neumünster — Design, Entwicklung und eigene Produkte.</small></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer__bottom">
|
<div class="footer__bottom">
|
||||||
@@ -151,7 +156,7 @@
|
|||||||
<a href="/datenschutz/">Datenschutz</a>
|
<a href="/datenschutz/">Datenschutz</a>
|
||||||
<a href="/kontakt/">Kontakt</a>
|
<a href="/kontakt/">Kontakt</a>
|
||||||
</nav>
|
</nav>
|
||||||
<span>© Denny Schulz · Denny Schulz Fotografie · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
<span>© Denny Schulz · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<button class="nav__toggle" type="button" aria-expanded="false" aria-controls="site-nav" data-nav-toggle>
|
<button class="nav__toggle" type="button" aria-expanded="false" aria-controls="site-nav" data-nav-toggle>
|
||||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> Menü
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> Menü
|
||||||
</button>
|
</button>
|
||||||
<nav class="nav" id="site-nav" aria-label="Hauptnavigation"><div class="nav__item"><a class="nav__link" href="/shootings/">Shootings<svg class="nav__caret" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6l5 5 5-5"/></svg></a><div class="nav__sub"><a href="/businessfotografie/">Businessfotografie</a><a href="/portrait-und-model/">Portrait & Model</a><a href="/familien-und-paare/">Familien & Paare</a><a href="/minishootings/">Minishootings</a></div></div><a class="nav__link" href="/video/">Video</a><a class="nav__link" href="/projekte/">Projekte</a><a class="nav__link" href="/ueber/">Über</a><a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a></nav>
|
<nav class="nav" id="site-nav" aria-label="Hauptnavigation"><div class="nav__item"><a class="nav__link" href="/fotografie/">Fotografie<svg class="nav__caret" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6l5 5 5-5"/></svg></a><div class="nav__sub"><a href="/businessfotografie/">Businessfotografie</a><a href="/portrait-und-model/">Portrait & Model</a><a href="/familie/">Familie</a><a href="/minishootings/">Minishootings</a></div></div><a class="nav__link" href="/video/">Video</a><a class="nav__link" href="/musik-und-buehne/">Musik & Bühne</a><a class="nav__link" href="/projekte/">Projekte</a><a class="nav__link" href="/ueber/">Über</a><a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a></nav>
|
||||||
</div>
|
</div>
|
||||||
<span class="progress" aria-hidden="true"><span class="progress__bar" data-progress></span></span>
|
<span class="progress" aria-hidden="true"><span class="progress__bar" data-progress></span></span>
|
||||||
</header>
|
</header>
|
||||||
@@ -57,7 +57,6 @@
|
|||||||
<span class="kicker">Kontakt</span>
|
<span class="kicker">Kontakt</span>
|
||||||
<h1 class="balance">Erzähl mir, was du vorhast</h1>
|
<h1 class="balance">Erzähl mir, was du vorhast</h1>
|
||||||
<p class="page-head__lead">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.</p>
|
<p class="page-head__lead">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.</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section><section class="section"><div class="shell"><div class="contact-grid"><div class="stack"><h2 class="balance">Direkter Draht</h2><p class="lead">Am schnellsten geht es per E-Mail oder Telefon. Beides landet direkt bei mir, nicht in einem Ticketsystem.</p><div class="dl"><div><dt>E-Mail</dt><dd><a href="mailto:info@dennyschulz.de">info@dennyschulz.de</a></dd></div><div><dt>Telefon</dt><dd><a href="tel:+491754083133">+49 175 4083133</a></dd></div><div><dt>Region</dt><dd>Neumünster · Schleswig-Holstein</dd></div><div><dt>Antwortzeit</dt><dd>Antwort in der Regel innerhalb von 24 Stunden</dd></div></div><div class="note">Termine vergebe ich nach Absprache — auch abends und am Wochenende, wenn es anders nicht passt.</div></div><div class="contact-card"><h2 class="balance" style="font-size:clamp(1.3rem,2vw,1.7rem);margin-bottom:1.2rem">Anfrage vorbereiten</h2><form class="form" data-contact-form novalidate action="mailto:info@dennyschulz.de" method="post" enctype="text/plain">
|
</section><section class="section"><div class="shell"><div class="contact-grid"><div class="stack"><h2 class="balance">Direkter Draht</h2><p class="lead">Am schnellsten geht es per E-Mail oder Telefon. Beides landet direkt bei mir, nicht in einem Ticketsystem.</p><div class="dl"><div><dt>E-Mail</dt><dd><a href="mailto:info@dennyschulz.de">info@dennyschulz.de</a></dd></div><div><dt>Telefon</dt><dd><a href="tel:+491754083133">+49 175 4083133</a></dd></div><div><dt>Region</dt><dd>Neumünster · Schleswig-Holstein</dd></div><div><dt>Antwortzeit</dt><dd>Antwort in der Regel innerhalb von 24 Stunden</dd></div></div><div class="note">Termine vergebe ich nach Absprache — auch abends und am Wochenende, wenn es anders nicht passt.</div></div><div class="contact-card"><h2 class="balance" style="font-size:clamp(1.3rem,2vw,1.7rem);margin-bottom:1.2rem">Anfrage vorbereiten</h2><form class="form" data-contact-form novalidate action="mailto:info@dennyschulz.de" method="post" enctype="text/plain">
|
||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
@@ -76,9 +75,10 @@
|
|||||||
<select id="cf-topic" name="topic">
|
<select id="cf-topic" name="topic">
|
||||||
<option>Businessfotografie</option>
|
<option>Businessfotografie</option>
|
||||||
<option>Portrait & Model</option>
|
<option>Portrait & Model</option>
|
||||||
<option>Familien & Paare</option>
|
<option>Familie</option>
|
||||||
<option>Minishooting</option>
|
<option>Minishooting</option>
|
||||||
<option>Videoprojekt</option>
|
<option>Video</option>
|
||||||
|
<option>Musik & Bühne</option>
|
||||||
<option>Social-Media-Inhalte</option>
|
<option>Social-Media-Inhalte</option>
|
||||||
<option>Pro-bono-Anfrage</option>
|
<option>Pro-bono-Anfrage</option>
|
||||||
<option>Etwas anderes</option>
|
<option>Etwas anderes</option>
|
||||||
@@ -127,23 +127,29 @@
|
|||||||
<span class="brand__mark"><img src="/assets/img/logo-160.png" width="160" height="160" alt="" decoding="async"></span>
|
<span class="brand__mark"><img src="/assets/img/logo-160.png" width="160" height="160" alt="" decoding="async"></span>
|
||||||
<span class="brand__text"><span>desfoto</span><small>Fotografie & Video</small></span>
|
<span class="brand__text"><span>desfoto</span><small>Fotografie & Video</small></span>
|
||||||
</span>
|
</span>
|
||||||
<p class="footer__claim">Fotografie und Video aus Neumünster. Ein Angebot von Denny Schulz Fotografie.</p>
|
<p class="footer__claim">desfoto ist ein Angebot von Denny Schulz, Neumünster.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Shootings</h2>
|
<h2>Fotografie</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/shootings/">Überblick Shootings</a>
|
<a href="/fotografie/">Überblick Fotografie</a>
|
||||||
<a href="/businessfotografie/">Businessfotografie</a>
|
<a href="/businessfotografie/">Businessfotografie</a>
|
||||||
<a href="/portrait-und-model/">Portrait & Model</a>
|
<a href="/portrait-und-model/">Portrait & Model</a>
|
||||||
<a href="/familien-und-paare/">Familien & Paare</a>
|
<a href="/familie/">Familie</a>
|
||||||
<a href="/minishootings/">Minishootings</a>
|
<a href="/minishootings/">Minishootings</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Video & mehr</h2>
|
<h2>Video & Bühne</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/video/">Videoprojekte</a>
|
<a href="/video/">Videoprojekte</a>
|
||||||
|
<a href="/musik-und-buehne/">Musik & Bühne</a>
|
||||||
<a href="/social-media/">Social Media</a>
|
<a href="/social-media/">Social Media</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Mehr</h2>
|
||||||
|
<div class="footer__links">
|
||||||
<a href="/projekte/">Projekte</a>
|
<a href="/projekte/">Projekte</a>
|
||||||
<a href="/ueber/">Über desfoto</a>
|
<a href="/ueber/">Über desfoto</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -159,7 +165,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Netzwerk</h2>
|
<h2>Netzwerk</h2>
|
||||||
<div class="footer__links"><a class="footer__ext" href="https://www.dennyschulz.de/" target="_blank" rel="noopener">dennyschulz.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Hochzeiten, Portraits und Business — die fotografische Arbeit unter eigenem Namen.</small><a class="footer__ext" href="https://dennyapp.de/" target="_blank" rel="noopener">dennyapp.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Websites und Web-Apps aus Neumünster — Design, Entwicklung und eigene Produkte.</small></div>
|
<div class="footer__links"><a class="footer__ext" href="https://www.dennyschulz.de/" target="_blank" rel="noopener">dennyschulz.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Hochzeiten und Paare — die Arbeit unter eigenem Namen, mit eigenem Schwerpunkt.</small><a class="footer__ext" href="https://dennyapp.de/" target="_blank" rel="noopener">dennyapp.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Websites und Web-Apps aus Neumünster — Design, Entwicklung und eigene Produkte.</small></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer__bottom">
|
<div class="footer__bottom">
|
||||||
@@ -168,7 +174,7 @@
|
|||||||
<a href="/datenschutz/">Datenschutz</a>
|
<a href="/datenschutz/">Datenschutz</a>
|
||||||
<a href="/kontakt/">Kontakt</a>
|
<a href="/kontakt/">Kontakt</a>
|
||||||
</nav>
|
</nav>
|
||||||
<span>© Denny Schulz · Denny Schulz Fotografie · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
<span>© Denny Schulz · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
160
site/musik-und-buehne/index.html
Normal file
@@ -44,7 +44,7 @@
|
|||||||
<button class="nav__toggle" type="button" aria-expanded="false" aria-controls="site-nav" data-nav-toggle>
|
<button class="nav__toggle" type="button" aria-expanded="false" aria-controls="site-nav" data-nav-toggle>
|
||||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> Menü
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg> Menü
|
||||||
</button>
|
</button>
|
||||||
<nav class="nav" id="site-nav" aria-label="Hauptnavigation"><div class="nav__item"><a class="nav__link" href="/shootings/" aria-current="page">Shootings<svg class="nav__caret" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6l5 5 5-5"/></svg></a><div class="nav__sub"><a href="/businessfotografie/">Businessfotografie</a><a href="/portrait-und-model/" aria-current="page">Portrait & Model</a><a href="/familien-und-paare/">Familien & Paare</a><a href="/minishootings/">Minishootings</a></div></div><a class="nav__link" href="/video/">Video</a><a class="nav__link" href="/projekte/">Projekte</a><a class="nav__link" href="/ueber/">Über</a><a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a></nav>
|
<nav class="nav" id="site-nav" aria-label="Hauptnavigation"><div class="nav__item"><a class="nav__link" href="/fotografie/" aria-current="page">Fotografie<svg class="nav__caret" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 6l5 5 5-5"/></svg></a><div class="nav__sub"><a href="/businessfotografie/">Businessfotografie</a><a href="/portrait-und-model/" aria-current="page">Portrait & Model</a><a href="/familie/">Familie</a><a href="/minishootings/">Minishootings</a></div></div><a class="nav__link" href="/video/">Video</a><a class="nav__link" href="/musik-und-buehne/">Musik & Bühne</a><a class="nav__link" href="/projekte/">Projekte</a><a class="nav__link" href="/ueber/">Über</a><a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a></nav>
|
||||||
</div>
|
</div>
|
||||||
<span class="progress" aria-hidden="true"><span class="progress__bar" data-progress></span></span>
|
<span class="progress" aria-hidden="true"><span class="progress__bar" data-progress></span></span>
|
||||||
</header>
|
</header>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<h3>Draußen, wo du bist</h3>
|
<h3>Draußen, wo du bist</h3>
|
||||||
<p class="card__text">Available light, Stadt, Wald, Wasser oder dein eigener Ort.
|
<p class="card__text">Available light, Stadt, Wald, Wasser oder dein eigener Ort.
|
||||||
Lebendiger, weniger kontrolliert — und oft genau richtig für Portraits, die sich echt anfühlen.</p>
|
Lebendiger, weniger kontrolliert — und oft genau richtig für Portraits, die sich echt anfühlen.</p>
|
||||||
</div></div></div></section><section class="section section--paper2"><div class="shell"><div class="section-head"><div class="section-head__text stack"><span class="kicker">Beispiele</span><h2 class="balance">Portraits und Editorial</h2></div></div><div class="grid grid--3"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-pose-1200.webp" srcset="/assets/img/editorial-pose-480.webp 480w, /assets/img/editorial-pose-768.webp 768w, /assets/img/editorial-pose-1200.webp 1200w, /assets/img/editorial-pose-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2000" decoding="async" loading="lazy" alt="Editorial-Portrait vor schwarzem Hintergrund" style="object-position:50% 28%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-back-1200.webp" srcset="/assets/img/editorial-back-480.webp 480w, /assets/img/editorial-back-768.webp 768w, /assets/img/editorial-back-1200.webp 1200w, /assets/img/editorial-back-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2398" decoding="async" loading="lazy" alt="Detailaufnahme eines Rückens mit Sporttop" style="object-position:50% 40%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-black-1200.webp" srcset="/assets/img/editorial-black-480.webp 480w, /assets/img/editorial-black-768.webp 768w, /assets/img/editorial-black-1200.webp 1200w, /assets/img/editorial-black-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2398" decoding="async" loading="lazy" alt="Model im Sporttop vor schwarzem Hintergrund" style="object-position:50% 28%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-earring-1200.webp" srcset="/assets/img/editorial-earring-480.webp 480w, /assets/img/editorial-earring-768.webp 768w, /assets/img/editorial-earring-1200.webp 1200w, /assets/img/editorial-earring-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2398" decoding="async" loading="lazy" alt="Portrait im Profil mit Ohrring" style="object-position:45% 28%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-studio-side-1200.webp" srcset="/assets/img/editorial-studio-side-480.webp 480w, /assets/img/editorial-studio-side-768.webp 768w, /assets/img/editorial-studio-side-1200.webp 1200w, /assets/img/editorial-studio-side-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2399" decoding="async" loading="lazy" alt="Marta im seitlichen Studiolicht vor dunklem Hintergrund" style="object-position:50% 30%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-balance-1200.webp" srcset="/assets/img/editorial-balance-480.webp 480w, /assets/img/editorial-balance-768.webp 768w, /assets/img/editorial-balance-1200.webp 1200w, /assets/img/editorial-balance-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2000" decoding="async" loading="lazy" alt="Marta balanciert sitzend mit angehobenem Bein" style="object-position:50% 40%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-leap-1200.webp" srcset="/assets/img/editorial-leap-480.webp 480w, /assets/img/editorial-leap-768.webp 768w, /assets/img/editorial-leap-1200.webp 1200w, /assets/img/editorial-leap-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2399" decoding="async" loading="lazy" alt="Marta springt mit angewinkelten Beinen" style="object-position:50% 35%"><img class="frame frame--4x5 frame--hover" src="/assets/img/portrait-natural-1200.webp" srcset="/assets/img/portrait-natural-480.webp 480w, /assets/img/portrait-natural-768.webp 768w, /assets/img/portrait-natural-1200.webp 1200w, /assets/img/portrait-natural-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="1067" decoding="async" loading="lazy" alt="Natürliches Portrait im Freien" style="object-position:50% 30%"><img class="frame frame--4x5 frame--hover" src="/assets/img/people-kiss-1200.webp" srcset="/assets/img/people-kiss-480.webp 480w, /assets/img/people-kiss-768.webp 768w, /assets/img/people-kiss-1200.webp 1200w, /assets/img/people-kiss-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="1068" decoding="async" loading="lazy" alt="Paar küsst sich vor hellem Hintergrund" style="object-position:50% 32%"></div></div></section><section class="section section--ink">
|
</div></div></div></section><section class="section section--paper2"><div class="shell"><div class="section-head"><div class="section-head__text stack"><span class="kicker">Beispiele</span><h2 class="balance">Portraits und Editorial</h2></div></div><div class="grid grid--3"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-pose-1200.webp" srcset="/assets/img/editorial-pose-480.webp 480w, /assets/img/editorial-pose-768.webp 768w, /assets/img/editorial-pose-1200.webp 1200w, /assets/img/editorial-pose-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2000" decoding="async" loading="lazy" alt="Editorial-Portrait vor schwarzem Hintergrund" style="object-position:50% 28%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-back-1200.webp" srcset="/assets/img/editorial-back-480.webp 480w, /assets/img/editorial-back-768.webp 768w, /assets/img/editorial-back-1200.webp 1200w, /assets/img/editorial-back-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2398" decoding="async" loading="lazy" alt="Detailaufnahme eines Rückens mit Sporttop" style="object-position:50% 40%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-black-1200.webp" srcset="/assets/img/editorial-black-480.webp 480w, /assets/img/editorial-black-768.webp 768w, /assets/img/editorial-black-1200.webp 1200w, /assets/img/editorial-black-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2398" decoding="async" loading="lazy" alt="Model im Sporttop vor schwarzem Hintergrund" style="object-position:50% 28%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-earring-1200.webp" srcset="/assets/img/editorial-earring-480.webp 480w, /assets/img/editorial-earring-768.webp 768w, /assets/img/editorial-earring-1200.webp 1200w, /assets/img/editorial-earring-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2398" decoding="async" loading="lazy" alt="Portrait im Profil mit Ohrring" style="object-position:45% 28%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-studio-side-1200.webp" srcset="/assets/img/editorial-studio-side-480.webp 480w, /assets/img/editorial-studio-side-768.webp 768w, /assets/img/editorial-studio-side-1200.webp 1200w, /assets/img/editorial-studio-side-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2399" decoding="async" loading="lazy" alt="Marta im seitlichen Studiolicht vor dunklem Hintergrund" style="object-position:50% 30%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-balance-1200.webp" srcset="/assets/img/editorial-balance-480.webp 480w, /assets/img/editorial-balance-768.webp 768w, /assets/img/editorial-balance-1200.webp 1200w, /assets/img/editorial-balance-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2000" decoding="async" loading="lazy" alt="Marta balanciert sitzend mit angehobenem Bein" style="object-position:50% 40%"><img class="frame frame--4x5 frame--hover" src="/assets/img/editorial-leap-1200.webp" srcset="/assets/img/editorial-leap-480.webp 480w, /assets/img/editorial-leap-768.webp 768w, /assets/img/editorial-leap-1200.webp 1200w, /assets/img/editorial-leap-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2399" decoding="async" loading="lazy" alt="Marta springt mit angewinkelten Beinen" style="object-position:50% 35%"><img class="frame frame--4x5 frame--hover" src="/assets/img/free-portrait-light-1200.webp" srcset="/assets/img/free-portrait-light-480.webp 480w, /assets/img/free-portrait-light-768.webp 768w, /assets/img/free-portrait-light-1200.webp 1200w, /assets/img/free-portrait-light-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2400" decoding="async" loading="lazy" alt="Frauenportrait im weichen Seitenlicht vor dunklem Hintergrund" style="object-position:50% 30%"><img class="frame frame--4x5 frame--hover" src="/assets/img/free-modern-portrait-1200.webp" srcset="/assets/img/free-modern-portrait-480.webp 480w, /assets/img/free-modern-portrait-768.webp 768w, /assets/img/free-modern-portrait-1200.webp 1200w, /assets/img/free-modern-portrait-1600.webp 1600w" sizes="(min-width: 62rem) 30vw, 92vw" width="1600" height="2000" decoding="async" loading="lazy" alt="Portrait einer Frau mit Sonnenbrille" style="object-position:50% 30%"></div></div></section><section class="section section--ink">
|
||||||
<div class="shell">
|
<div class="shell">
|
||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<div class="section-head__text stack">
|
<div class="section-head__text stack">
|
||||||
@@ -90,23 +90,29 @@
|
|||||||
<span class="brand__mark"><img src="/assets/img/logo-160.png" width="160" height="160" alt="" decoding="async"></span>
|
<span class="brand__mark"><img src="/assets/img/logo-160.png" width="160" height="160" alt="" decoding="async"></span>
|
||||||
<span class="brand__text"><span>desfoto</span><small>Fotografie & Video</small></span>
|
<span class="brand__text"><span>desfoto</span><small>Fotografie & Video</small></span>
|
||||||
</span>
|
</span>
|
||||||
<p class="footer__claim">Fotografie und Video aus Neumünster. Ein Angebot von Denny Schulz Fotografie.</p>
|
<p class="footer__claim">desfoto ist ein Angebot von Denny Schulz, Neumünster.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Shootings</h2>
|
<h2>Fotografie</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/shootings/">Überblick Shootings</a>
|
<a href="/fotografie/">Überblick Fotografie</a>
|
||||||
<a href="/businessfotografie/">Businessfotografie</a>
|
<a href="/businessfotografie/">Businessfotografie</a>
|
||||||
<a href="/portrait-und-model/">Portrait & Model</a>
|
<a href="/portrait-und-model/">Portrait & Model</a>
|
||||||
<a href="/familien-und-paare/">Familien & Paare</a>
|
<a href="/familie/">Familie</a>
|
||||||
<a href="/minishootings/">Minishootings</a>
|
<a href="/minishootings/">Minishootings</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Video & mehr</h2>
|
<h2>Video & Bühne</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/video/">Videoprojekte</a>
|
<a href="/video/">Videoprojekte</a>
|
||||||
|
<a href="/musik-und-buehne/">Musik & Bühne</a>
|
||||||
<a href="/social-media/">Social Media</a>
|
<a href="/social-media/">Social Media</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Mehr</h2>
|
||||||
|
<div class="footer__links">
|
||||||
<a href="/projekte/">Projekte</a>
|
<a href="/projekte/">Projekte</a>
|
||||||
<a href="/ueber/">Über desfoto</a>
|
<a href="/ueber/">Über desfoto</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +128,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Netzwerk</h2>
|
<h2>Netzwerk</h2>
|
||||||
<div class="footer__links"><a class="footer__ext" href="https://www.dennyschulz.de/" target="_blank" rel="noopener">dennyschulz.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Hochzeiten, Portraits und Business — die fotografische Arbeit unter eigenem Namen.</small><a class="footer__ext" href="https://dennyapp.de/" target="_blank" rel="noopener">dennyapp.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Websites und Web-Apps aus Neumünster — Design, Entwicklung und eigene Produkte.</small></div>
|
<div class="footer__links"><a class="footer__ext" href="https://www.dennyschulz.de/" target="_blank" rel="noopener">dennyschulz.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Hochzeiten und Paare — die Arbeit unter eigenem Namen, mit eigenem Schwerpunkt.</small><a class="footer__ext" href="https://dennyapp.de/" target="_blank" rel="noopener">dennyapp.de <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 5h5v5M19 5l-7 7M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/></svg></a><small>Websites und Web-Apps aus Neumünster — Design, Entwicklung und eigene Produkte.</small></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer__bottom">
|
<div class="footer__bottom">
|
||||||
@@ -131,7 +137,7 @@
|
|||||||
<a href="/datenschutz/">Datenschutz</a>
|
<a href="/datenschutz/">Datenschutz</a>
|
||||||
<a href="/kontakt/">Kontakt</a>
|
<a href="/kontakt/">Kontakt</a>
|
||||||
</nav>
|
</nav>
|
||||||
<span>© Denny Schulz · Denny Schulz Fotografie · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
<span>© Denny Schulz · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "desfoto",
|
"name": "desfoto",
|
||||||
"short_name": "desfoto",
|
"short_name": "desfoto",
|
||||||
"description": "desfoto ist die Foto- und Videoproduktion von Denny Schulz aus Neumünster: Shootings für Business, Portrait, Model und Familien, Minishootings, das mobile Studio mit autarker Blitzanlage sowie Videoprojekte und Social-Media-Inhalte.",
|
"description": "desfoto steht für Fotografie, Video und mediale Produktionen aus Neumünster: Fotografie für Menschen und Unternehmen, Video für Unternehmen, Musik und Events, Musik- und Bühnenarbeiten sowie eigene Projekte.",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#F6F2EA",
|
"background_color": "#F6F2EA",
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
<url><loc>https://desfoto.de/</loc><lastmod>2026-09-19</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url>
|
<url><loc>https://desfoto.de/</loc><lastmod>2026-09-19</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url>
|
||||||
<url><loc>https://desfoto.de/shootings/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
<url><loc>https://desfoto.de/fotografie/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
||||||
<url><loc>https://desfoto.de/businessfotografie/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
<url><loc>https://desfoto.de/businessfotografie/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
||||||
<url><loc>https://desfoto.de/portrait-und-model/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
<url><loc>https://desfoto.de/portrait-und-model/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
||||||
<url><loc>https://desfoto.de/familien-und-paare/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
<url><loc>https://desfoto.de/familie/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
||||||
<url><loc>https://desfoto.de/minishootings/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
<url><loc>https://desfoto.de/minishootings/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
||||||
<url><loc>https://desfoto.de/video/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
<url><loc>https://desfoto.de/video/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
||||||
|
<url><loc>https://desfoto.de/musik-und-buehne/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
||||||
<url><loc>https://desfoto.de/social-media/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
<url><loc>https://desfoto.de/social-media/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.9</priority></url>
|
||||||
<url><loc>https://desfoto.de/projekte/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
|
<url><loc>https://desfoto.de/projekte/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
|
||||||
<url><loc>https://desfoto.de/ueber/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
|
<url><loc>https://desfoto.de/ueber/</loc><lastmod>2026-09-19</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
|
||||||
|
|||||||
149
src/content.py
@@ -13,19 +13,22 @@ SITE = {
|
|||||||
"url": "https://desfoto.de",
|
"url": "https://desfoto.de",
|
||||||
"claim": "Fotografie & Video",
|
"claim": "Fotografie & Video",
|
||||||
"tagline": "Fotografie und Video aus Neumünster",
|
"tagline": "Fotografie und Video aus Neumünster",
|
||||||
|
# The only sentence that ties the brand back to its owner; it belongs in the
|
||||||
|
# footer and nowhere else (the legal entity itself lives in the Impressum).
|
||||||
|
"ownership": "desfoto ist ein Angebot von Denny Schulz, Neumünster.",
|
||||||
"locale": "de_DE",
|
"locale": "de_DE",
|
||||||
"language": "de",
|
"language": "de",
|
||||||
"theme_color": "#17140F",
|
"theme_color": "#17140F",
|
||||||
"default_description": (
|
"default_description": (
|
||||||
"desfoto ist die Foto- und Videoproduktion von Denny Schulz aus Neumünster: "
|
"desfoto steht für Fotografie, Video und mediale Produktionen aus Neumünster: "
|
||||||
"Shootings für Business, Portrait, Model und Familien, Minishootings, das mobile "
|
"Fotografie für Menschen und Unternehmen, Video für Unternehmen, Musik und Events, "
|
||||||
"Studio mit autarker Blitzanlage sowie Videoprojekte und Social-Media-Inhalte."
|
"Musik- und Bühnenarbeiten sowie eigene Projekte."
|
||||||
),
|
),
|
||||||
"keywords": (
|
"keywords": (
|
||||||
"Fotograf Neumünster, Videograf Neumünster, Shootings, Businessfotografie, "
|
"Fotograf Neumünster, Videograf Neumünster, Fotografie, Businessfotografie, "
|
||||||
"Businessportrait, Model-Shooting, Familienfotograf, Minishooting, mobiles Studio, "
|
"Businessportrait, Model-Shooting, Familienfotograf, Minishooting, mobiles Studio, "
|
||||||
"Blitzanlage, Musikvideo, Konzertfotografie, Social Media Inhalte, "
|
"Blitzanlage, Musikvideo, Konzertfotografie, Bandfotografie, Musik & Bühne, "
|
||||||
"Schleswig-Holstein"
|
"Social Media Inhalte, Schleswig-Holstein"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,61 +71,72 @@ VIDEO = {
|
|||||||
|
|
||||||
NAV = [
|
NAV = [
|
||||||
{
|
{
|
||||||
"label": "Shootings",
|
"label": "Fotografie",
|
||||||
"href": "/shootings/",
|
"href": "/fotografie/",
|
||||||
"children": [
|
"children": [
|
||||||
("Businessfotografie", "/businessfotografie/"),
|
("Businessfotografie", "/businessfotografie/"),
|
||||||
("Portrait & Model", "/portrait-und-model/"),
|
("Portrait & Model", "/portrait-und-model/"),
|
||||||
("Familien & Paare", "/familien-und-paare/"),
|
("Familie", "/familie/"),
|
||||||
("Minishootings", "/minishootings/"),
|
("Minishootings", "/minishootings/"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{"label": "Video", "href": "/video/"},
|
{"label": "Video", "href": "/video/"},
|
||||||
|
{"label": "Musik & Bühne", "href": "/musik-und-buehne/"},
|
||||||
{"label": "Projekte", "href": "/projekte/"},
|
{"label": "Projekte", "href": "/projekte/"},
|
||||||
{"label": "Über", "href": "/ueber/"},
|
{"label": "Über", "href": "/ueber/"},
|
||||||
]
|
]
|
||||||
|
|
||||||
# Die zwei Säulen der Arbeit: Foto (Shootings) und Video bleiben getrennt,
|
# Die vier Arbeitsbereiche der Marke. Die Startseite erklärt desfoto darüber —
|
||||||
# damit beide Bereiche für sich verständlich bleiben.
|
# erst der Bereich, dann die einzelnen buchbaren Leistungen darunter.
|
||||||
PILLARS = [
|
AREAS = [
|
||||||
{
|
{
|
||||||
"slug": "foto",
|
"slug": "fotografie",
|
||||||
"label": "Foto",
|
"title": "Fotografie",
|
||||||
"href": "/shootings/",
|
"scope": "Menschen, Unternehmen, Produkte, Veranstaltungen.",
|
||||||
"title": "Shootings",
|
|
||||||
"lead": "Der Überbegriff für alle fotografischen Arbeiten.",
|
|
||||||
"text": (
|
"text": (
|
||||||
"Businessfotografie, Portrait & Model, Familien & Paare und Minishootings — "
|
"Portrait, Business, Familie und Minishootings — im Studio in Neumünster "
|
||||||
"ein Shooting, ein Ablauf, ein Ansprechpartner. Du wählst nur, was du "
|
"oder mit dem mobilen Studio an deinem Ort."
|
||||||
"wirklich brauchst."
|
|
||||||
),
|
),
|
||||||
"points": [
|
"href": "/fotografie/",
|
||||||
("Businessfotografie", "/businessfotografie/"),
|
"image": "business-portrait-sit",
|
||||||
("Portrait & Model", "/portrait-und-model/"),
|
"fit": "50% 35%",
|
||||||
("Familien & Paare", "/familien-und-paare/"),
|
|
||||||
("Minishootings", "/minishootings/"),
|
|
||||||
],
|
|
||||||
"image": "business-portrait-studio",
|
|
||||||
"fit": "50% 30%",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "video",
|
"slug": "video",
|
||||||
"label": "Video",
|
"title": "Video",
|
||||||
"href": "/video/",
|
"scope": "Unternehmen, Musik, Events, Social Content.",
|
||||||
"title": "Videoprojekte",
|
|
||||||
"lead": "Bewegtbild von der Idee bis zum fertigen Schnitt.",
|
|
||||||
"text": (
|
"text": (
|
||||||
"Musikvideo, Imagefilm, Eventfilm und Formate für Social Media — "
|
"Musikvideo, Imagefilm, Recruiting und Eventnachbericht — von der Idee "
|
||||||
"gedreht, geschnitten und ausgeliefert in dem Format, in dem du es "
|
"über den Dreh bis zum fertigen Export."
|
||||||
"veröffentlichst."
|
|
||||||
),
|
),
|
||||||
"points": [
|
"href": "/video/",
|
||||||
("Videoprojekte", "/video/"),
|
"image": "movement-light",
|
||||||
("Social Media", "/social-media/"),
|
|
||||||
],
|
|
||||||
"image": "studio-lens",
|
|
||||||
"fit": "50% 45%",
|
"fit": "50% 45%",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"slug": "musik-und-buehne",
|
||||||
|
"title": "Musik & Bühne",
|
||||||
|
"scope": "Bands, Künstler, Konzerte, Musikvideo.",
|
||||||
|
"text": (
|
||||||
|
"Live, Bandportraits und Musikvideo — als Musiker kenne ich beide Seiten "
|
||||||
|
"der Bühne."
|
||||||
|
),
|
||||||
|
"href": "/musik-und-buehne/",
|
||||||
|
"image": "music-drums",
|
||||||
|
"fit": "50% 45%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "projekte",
|
||||||
|
"title": "Projekte",
|
||||||
|
"scope": "Freie Arbeiten, Kooperationen, besondere Produktionen.",
|
||||||
|
"text": (
|
||||||
|
"Landschaft, Tierwelt, Menschen und pro bono — die Arbeiten, die neben "
|
||||||
|
"den Aufträgen entstehen."
|
||||||
|
),
|
||||||
|
"href": "/projekte/",
|
||||||
|
"image": "free-mountain-view",
|
||||||
|
"fit": "50% 50%",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# Was in einem Shooting steckt — die gemeinsamen Eckpunkte aller Shootings.
|
# Was in einem Shooting steckt — die gemeinsamen Eckpunkte aller Shootings.
|
||||||
@@ -158,7 +172,7 @@ STUDIO = {
|
|||||||
{
|
{
|
||||||
"title": "Studio Neumünster",
|
"title": "Studio Neumünster",
|
||||||
"text": (
|
"text": (
|
||||||
"Ein eigenes Studio in Neumünster steht für Shootings bereit:"
|
"Ein eigenes Studio in Neumünster steht für Fototermine bereit:"
|
||||||
" kontrolliertes Licht, ruhiger Hintergrund und Platz für"
|
" kontrolliertes Licht, ruhiger Hintergrund und Platz für"
|
||||||
" Konzeptarbeit. Gut für Portraits, Businessbilder und alles, was"
|
" Konzeptarbeit. Gut für Portraits, Businessbilder und alles, was"
|
||||||
" eine wiederholbare Optik braucht — unabhängig vom Wetter."
|
" eine wiederholbare Optik braucht — unabhängig vom Wetter."
|
||||||
@@ -200,7 +214,7 @@ STUDIO = {
|
|||||||
"text": "Vom Büro in Neumünster bis zum Betrieb in Kiel, Lübeck oder Hamburg.",
|
"text": "Vom Büro in Neumünster bis zum Betrieb in Kiel, Lübeck oder Hamburg.",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"image": "studio-setup",
|
"image": "studio-lens",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Netzwerk: eigene Projekte des Betreibers (ausdrücklicher Wunsch des Betreibers).
|
# Netzwerk: eigene Projekte des Betreibers (ausdrücklicher Wunsch des Betreibers).
|
||||||
@@ -209,7 +223,7 @@ NETWORK = [
|
|||||||
"label": "dennyschulz.de",
|
"label": "dennyschulz.de",
|
||||||
"href": "https://www.dennyschulz.de/",
|
"href": "https://www.dennyschulz.de/",
|
||||||
"title": "dennyschulz.de",
|
"title": "dennyschulz.de",
|
||||||
"text": "Hochzeiten, Portraits und Business — die fotografische Arbeit unter eigenem Namen.",
|
"text": "Hochzeiten und Paare — die Arbeit unter eigenem Namen, mit eigenem Schwerpunkt.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "dennyapp.de",
|
"label": "dennyapp.de",
|
||||||
@@ -244,14 +258,14 @@ SERVICES = [
|
|||||||
"fit": "50% 30%",
|
"fit": "50% 30%",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "familien-und-paare",
|
"slug": "familie",
|
||||||
"href": "/familien-und-paare/",
|
"href": "/familie/",
|
||||||
"title": "Familien & Paare",
|
"title": "Familie",
|
||||||
"short": (
|
"short": (
|
||||||
"Familienshootings und Paarportraits, die nicht gestellt wirken. "
|
"Familienshootings, die nicht gestellt wirken. Zeit lassen, mitgehen, "
|
||||||
"Zeit lassen, mitgehen, im richtigen Moment auslösen."
|
"im richtigen Moment auslösen."
|
||||||
),
|
),
|
||||||
"image": "people-joy",
|
"image": "free-portrait-light",
|
||||||
"fit": "50% 30%",
|
"fit": "50% 30%",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -262,7 +276,7 @@ SERVICES = [
|
|||||||
"Die kurze Session für alle, die schnell ein gutes Bild brauchen: "
|
"Die kurze Session für alle, die schnell ein gutes Bild brauchen: "
|
||||||
"klarer Ablauf, kleine Auswahl, kurze Lieferzeit."
|
"klarer Ablauf, kleine Auswahl, kurze Lieferzeit."
|
||||||
),
|
),
|
||||||
"image": "portrait-natural",
|
"image": "free-modern-portrait",
|
||||||
"fit": "50% 30%",
|
"fit": "50% 30%",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -284,39 +298,39 @@ SERVICES = [
|
|||||||
"Foto- und Videocontent im Format deiner Kanäle — wiederkehrend, planbar "
|
"Foto- und Videocontent im Format deiner Kanäle — wiederkehrend, planbar "
|
||||||
"und über die Monate hinweg einheitlich."
|
"und über die Monate hinweg einheitlich."
|
||||||
),
|
),
|
||||||
"image": "event-motion",
|
"image": "event-speaker",
|
||||||
"fit": "50% 45%",
|
"fit": "50% 45%",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# Fotografie-Übersicht: Shootings als Überbegriff über den einzelnen Leistungen.
|
# Fotografie-Übersicht: die drei Themen, unter denen die einzelnen Angebote liegen.
|
||||||
PHOTO_OVERVIEW = [
|
PHOTO_OVERVIEW = [
|
||||||
{
|
{
|
||||||
"title": "Shootings",
|
"title": "Menschen",
|
||||||
"text": (
|
"text": (
|
||||||
"Ein Überbegriff, vier Wege: Businessfotografie für Teams und Marken, "
|
"Portrait, Bewerbungsbilder, Familie und Model — im Studio in Neumünster, "
|
||||||
"Portrait & Model für die persönliche Bildsprache, Familien & Paare für "
|
"an einem Ort deiner Wahl oder dort, wo dein Alltag stattfindet."
|
||||||
"die Menschen, die zu dir gehören, und Minishootings für alle, die schnell "
|
|
||||||
"ein gutes Bild brauchen."
|
|
||||||
),
|
),
|
||||||
"image": "editorial-pose",
|
"image": "editorial-pose",
|
||||||
|
"fit": "50% 30%",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Studio & mobil",
|
"title": "Unternehmen & Marken",
|
||||||
"text": (
|
"text": (
|
||||||
"Shootings im eigenen Studio in Neumünster oder mit dem mobilen Studio "
|
"Team- und Standortbilder, Produkte und Kampagnenmotive — abgestimmt auf "
|
||||||
"inklusive autarker Blitzanlage, Hintergründen und Lichtformern — "
|
"die Flächen, auf denen sie später stehen."
|
||||||
"aufgebaut an deinem Ort, statt umgekehrt."
|
|
||||||
),
|
),
|
||||||
"image": "business-portrait-sit",
|
"image": "business-portrait-sit",
|
||||||
|
"fit": "50% 30%",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Anlässe",
|
"title": "Veranstaltungen",
|
||||||
"text": (
|
"text": (
|
||||||
"Feiern, Events, Vereinsleben und Premieren. Dokumentarisch begleitet, "
|
"Feiern, Events, Vereinsleben und Premieren. Dokumentarisch begleitet, "
|
||||||
"ohne den Ablauf auszubremsen."
|
"ohne den Ablauf auszubremsen."
|
||||||
),
|
),
|
||||||
"image": "event-speaker",
|
"image": "event-speaker",
|
||||||
|
"fit": "50% 40%",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -329,10 +343,10 @@ WORK_GALLERY = [
|
|||||||
("free-dog-husky", "Ein Hund auf einem sonnigen Waldweg", "g-w2", "1x1", "50% 45%"),
|
("free-dog-husky", "Ein Hund auf einem sonnigen Waldweg", "g-w2", "1x1", "50% 45%"),
|
||||||
("editorial-colour", "Model-Shooting im Studio", "g-w3", "3x2", "50% 30%"),
|
("editorial-colour", "Model-Shooting im Studio", "g-w3", "3x2", "50% 30%"),
|
||||||
("music-dreadlocks", "Musiker mit Dreadlocks vor einem Mikrofon", "g-w3", "3x2", "50% 35%"),
|
("music-dreadlocks", "Musiker mit Dreadlocks vor einem Mikrofon", "g-w3", "3x2", "50% 35%"),
|
||||||
("couple-backlight-kiss", "Paar küsst sich im Gegenlicht", "g-w3", "4x5", "50% 45%"),
|
("free-woman-coast", "Eine Frau mit Hut steht an einer felsigen Küste", "g-w3", "4x5", "50% 40%"),
|
||||||
("free-fog-forest", "Nebel über einem Nadelwald", "g-w3", "3x2", "50% 45%"),
|
("free-fog-forest", "Nebel über einem Nadelwald", "g-w3", "3x2", "50% 45%"),
|
||||||
("free-cat", "Nahaufnahme einer getigerten Katze", "g-w2", "1x1", "50% 42%"),
|
("free-cat", "Nahaufnahme einer getigerten Katze", "g-w2", "1x1", "50% 42%"),
|
||||||
("couple-sun-silhouette", "Silhouetten eines Paares vor der Sonne", "g-w3", "3x2", "50% 50%"),
|
("portrait-marina-sunset", "Silhouette einer Frau vor orangefarbenem Abendhimmel", "g-w3", "3x2", "50% 50%"),
|
||||||
]
|
]
|
||||||
|
|
||||||
PROCESS = [
|
PROCESS = [
|
||||||
@@ -387,11 +401,12 @@ FACTS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
FOCUS_ITEMS = [
|
FOCUS_ITEMS = [
|
||||||
("Shootings", "/shootings/"),
|
("Fotografie", "/fotografie/"),
|
||||||
("Businessfotografie", "/businessfotografie/"),
|
("Businessfotografie", "/businessfotografie/"),
|
||||||
("Portrait & Model", "/portrait-und-model/"),
|
("Portrait & Model", "/portrait-und-model/"),
|
||||||
("Familien & Paare", "/familien-und-paare/"),
|
("Familie", "/familie/"),
|
||||||
("Minishootings", "/minishootings/"),
|
("Minishootings", "/minishootings/"),
|
||||||
("Videoprojekte", "/video/"),
|
("Video", "/video/"),
|
||||||
|
("Musik & Bühne", "/musik-und-buehne/"),
|
||||||
("Social Media", "/social-media/"),
|
("Social Media", "/social-media/"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,12 +9,6 @@
|
|||||||
"alt": "Kameraobjektiv im Studiolicht",
|
"alt": "Kameraobjektiv im Studiolicht",
|
||||||
"focus": "50% 50%"
|
"focus": "50% 50%"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"slug": "studio-setup",
|
|
||||||
"raw": "Studio/1777027120196_c501de64_dennyschulz_-05467.jpg",
|
|
||||||
"alt": "Studioequipment mit Logo von Denny Schulz Fotografie",
|
|
||||||
"focus": "50% 50%"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"slug": "gear-cases",
|
"slug": "gear-cases",
|
||||||
"raw": "1778949186414_b1648d20_dennyschulz_-05400-2.jpg",
|
"raw": "1778949186414_b1648d20_dennyschulz_-05400-2.jpg",
|
||||||
@@ -75,36 +69,6 @@
|
|||||||
"alt": "Fashion-Portrait in kräftigen Farben",
|
"alt": "Fashion-Portrait in kräftigen Farben",
|
||||||
"focus": "50% 30%"
|
"focus": "50% 30%"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"slug": "portrait-natural",
|
|
||||||
"raw": "1784293240857_deae4788_dennyschulz_-07255.jpg",
|
|
||||||
"alt": "Natürliches Portrait im Freien",
|
|
||||||
"focus": "50% 30%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "portrait-couple",
|
|
||||||
"raw": "1784293231735_a8ccbd3e_dennyschulz_-08620.jpg",
|
|
||||||
"alt": "Paarportrait im Grünen",
|
|
||||||
"focus": "50% 30%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "people-joy",
|
|
||||||
"raw": "1776343134779_e893c351_dennyschulz_-03745114107.jpg",
|
|
||||||
"alt": "Paar lacht im Abendlicht",
|
|
||||||
"focus": "50% 30%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "people-kiss",
|
|
||||||
"raw": "1776343151721_e3f65ef1_dennyschulz_-04483.jpg",
|
|
||||||
"alt": "Paar küsst sich vor hellem Hintergrund",
|
|
||||||
"focus": "50% 30%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "event-motion",
|
|
||||||
"raw": "1776670469051_dc2993ae_dennyschulz_-04663.jpg",
|
|
||||||
"alt": "Bewegung auf einer Veranstaltung",
|
|
||||||
"focus": "50% 45%"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"slug": "event-speaker",
|
"slug": "event-speaker",
|
||||||
"raw": "1784293225986_a37d21f4_dennyschulz_-07139.jpg",
|
"raw": "1784293225986_a37d21f4_dennyschulz_-07139.jpg",
|
||||||
@@ -117,12 +81,6 @@
|
|||||||
"alt": "Veranstaltungsort mit Gästen",
|
"alt": "Veranstaltungsort mit Gästen",
|
||||||
"focus": "50% 45%"
|
"focus": "50% 45%"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"slug": "event-dance",
|
|
||||||
"raw": "1784293226489_fa1b9420_dennyschulz_-09046.jpg",
|
|
||||||
"alt": "Tanzfläche bei einer Feier",
|
|
||||||
"focus": "50% 40%"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"slug": "movement-light",
|
"slug": "movement-light",
|
||||||
"raw": "1788850842323_a5ae410e_280826_shooting-69.jpg",
|
"raw": "1788850842323_a5ae410e_280826_shooting-69.jpg",
|
||||||
@@ -353,41 +311,6 @@
|
|||||||
"alt": "Marta springt mit angewinkelten Beinen",
|
"alt": "Marta springt mit angewinkelten Beinen",
|
||||||
"focus": "50% 35%"
|
"focus": "50% 35%"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"slug": "couple-park",
|
|
||||||
"source": "legacy",
|
|
||||||
"raw": "dennyschulz-5142.jpg",
|
|
||||||
"alt": "Sara und Carlos stehen Arm in Arm im Grünen",
|
|
||||||
"focus": "50% 30%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "couple-backlight-kiss",
|
|
||||||
"source": "legacy",
|
|
||||||
"raw": "dennyschulz-5191.jpg",
|
|
||||||
"alt": "Sara und Carlos küssen sich im Gegenlicht",
|
|
||||||
"focus": "50% 45%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "couple-forest-walk",
|
|
||||||
"source": "legacy",
|
|
||||||
"raw": "dennyschulz-4415.jpg",
|
|
||||||
"alt": "Sara und Carlos gehen Hand in Hand durch den Wald",
|
|
||||||
"focus": "50% 35%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "couple-sun-silhouette",
|
|
||||||
"source": "legacy",
|
|
||||||
"raw": "dennyschulz-2076.jpg",
|
|
||||||
"alt": "Die Silhouetten des Paars rahmen die tief stehende Sonne",
|
|
||||||
"focus": "50% 50%"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "couple-laugh-walk",
|
|
||||||
"source": "legacy",
|
|
||||||
"raw": "dennyschulz-0368.jpg",
|
|
||||||
"alt": "Tamara und Tobi lachen auf einem Waldweg",
|
|
||||||
"focus": "50% 35%"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"slug": "portrait-marina-snow",
|
"slug": "portrait-marina-snow",
|
||||||
"source": "legacy",
|
"source": "legacy",
|
||||||
|
|||||||
@@ -199,23 +199,29 @@ def footer_html() -> str:
|
|||||||
{BRAND_MARK}
|
{BRAND_MARK}
|
||||||
<span class="brand__text"><span>desfoto</span><small>{e(SITE['claim'])}</small></span>
|
<span class="brand__text"><span>desfoto</span><small>{e(SITE['claim'])}</small></span>
|
||||||
</span>
|
</span>
|
||||||
<p class="footer__claim">{e(SITE['tagline'])}. Ein Angebot von {e(LEGAL['business'])}.</p>
|
<p class="footer__claim">{e(SITE['ownership'])}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Shootings</h2>
|
<h2>Fotografie</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/shootings/">Überblick Shootings</a>
|
<a href="/fotografie/">Überblick Fotografie</a>
|
||||||
<a href="/businessfotografie/">Businessfotografie</a>
|
<a href="/businessfotografie/">Businessfotografie</a>
|
||||||
<a href="/portrait-und-model/">Portrait & Model</a>
|
<a href="/portrait-und-model/">Portrait & Model</a>
|
||||||
<a href="/familien-und-paare/">Familien & Paare</a>
|
<a href="/familie/">Familie</a>
|
||||||
<a href="/minishootings/">Minishootings</a>
|
<a href="/minishootings/">Minishootings</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Video & mehr</h2>
|
<h2>Video & Bühne</h2>
|
||||||
<div class="footer__links">
|
<div class="footer__links">
|
||||||
<a href="/video/">Videoprojekte</a>
|
<a href="/video/">Videoprojekte</a>
|
||||||
|
<a href="/musik-und-buehne/">Musik & Bühne</a>
|
||||||
<a href="/social-media/">Social Media</a>
|
<a href="/social-media/">Social Media</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Mehr</h2>
|
||||||
|
<div class="footer__links">
|
||||||
<a href="/projekte/">Projekte</a>
|
<a href="/projekte/">Projekte</a>
|
||||||
<a href="/ueber/">Über desfoto</a>
|
<a href="/ueber/">Über desfoto</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,7 +246,7 @@ def footer_html() -> str:
|
|||||||
<a href="/datenschutz/">Datenschutz</a>
|
<a href="/datenschutz/">Datenschutz</a>
|
||||||
<a href="/kontakt/">Kontakt</a>
|
<a href="/kontakt/">Kontakt</a>
|
||||||
</nav>
|
</nav>
|
||||||
<span>© {e(LEGAL['owner'])} · {e(LEGAL['business'])} · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
<span>© {e(LEGAL['owner'])} · Alle Rechte an den gezeigten Bildern und Filmen vorbehalten.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -296,17 +302,20 @@ def page_head(
|
|||||||
f'<div class="breadcrumb"><a href="/">Start</a><span>/</span><span>{e(breadcrumb[0])}</span></div>'
|
f'<div class="breadcrumb"><a href="/">Start</a><span>/</span><span>{e(breadcrumb[0])}</span></div>'
|
||||||
)
|
)
|
||||||
actions_html = f'<div class="page-head__actions">{actions}</div>' if actions else ""
|
actions_html = f'<div class="page-head__actions">{actions}</div>' if actions else ""
|
||||||
|
# Bedingte Zeilen tragen ihren Zeilenumbruch selbst. Sonst bleibt bei leerem
|
||||||
|
# Wert eine Zeile aus Einrückung stehen — trailing whitespace, das das
|
||||||
|
# Release-Gateway (`git diff --check`) ablehnt.
|
||||||
|
crumb_line = f" {crumb}\n" if crumb else ""
|
||||||
|
actions_line = f" {actions_html}\n" if actions_html else ""
|
||||||
return f"""<section class="page-head">
|
return f"""<section class="page-head">
|
||||||
<div class="page-head__bg">
|
<div class="page-head__bg">
|
||||||
{picture(image, "100vw", css_class="", priority=True, loading="eager", alt="", fit=fit)}
|
{picture(image, "100vw", css_class="", priority=True, loading="eager", alt="", fit=fit)}
|
||||||
</div>
|
</div>
|
||||||
<div class="shell page-head__inner">
|
<div class="shell page-head__inner">
|
||||||
{crumb}
|
{crumb_line} <span class="kicker">{e(kicker)}</span>
|
||||||
<span class="kicker">{e(kicker)}</span>
|
|
||||||
<h1 class="balance">{e(title)}</h1>
|
<h1 class="balance">{e(title)}</h1>
|
||||||
<p class="page-head__lead">{lead}</p>
|
<p class="page-head__lead">{lead}</p>
|
||||||
{actions_html}
|
{actions_line} </div>
|
||||||
</div>
|
|
||||||
</section>"""
|
</section>"""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
378
src/pages.py
@@ -7,12 +7,12 @@ canonical paths are returned by the ROUTES table at the bottom of the module.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from content import (
|
from content import (
|
||||||
|
AREAS,
|
||||||
CONTACT,
|
CONTACT,
|
||||||
FACTS,
|
FACTS,
|
||||||
LEGAL,
|
LEGAL,
|
||||||
NETWORK,
|
NETWORK,
|
||||||
PHOTO_OVERVIEW,
|
PHOTO_OVERVIEW,
|
||||||
PILLARS,
|
|
||||||
PROCESS,
|
PROCESS,
|
||||||
SERVICES,
|
SERVICES,
|
||||||
SHOOTING_STEPS,
|
SHOOTING_STEPS,
|
||||||
@@ -81,6 +81,9 @@ def video_block(*, dark: bool = True, with_meta: bool = True) -> str:
|
|||||||
if with_meta
|
if with_meta
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
|
# Ohne Meta-Zeile darf die Einrückung nicht stehenbleiben: eine Zeile aus zwei
|
||||||
|
# Leerzeichen ist trailing whitespace und `git diff --check` lehnt sie ab.
|
||||||
|
meta_line = f" {meta}\n" if meta else ""
|
||||||
return f"""<div class="video-shell">
|
return f"""<div class="video-shell">
|
||||||
<button class="video-facade" type="button" data-video-id="{e(VIDEO['id'])}"
|
<button class="video-facade" type="button" data-video-id="{e(VIDEO['id'])}"
|
||||||
aria-label="Video starten: {e(VIDEO['title'])} (lädt YouTube nach Klick)">
|
aria-label="Video starten: {e(VIDEO['title'])} (lädt YouTube nach Klick)">
|
||||||
@@ -90,8 +93,7 @@ def video_block(*, dark: bool = True, with_meta: bool = True) -> str:
|
|||||||
<span class="video-facade__label">Video starten</span>
|
<span class="video-facade__label">Video starten</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{meta}
|
{meta_line}</div>
|
||||||
</div>
|
|
||||||
<p class="form__hint" style="margin-top:.8rem">Das Video wird erst nach deinem Klick von YouTube geladen (youtube-nocookie.com). Mehr dazu in der <a href="/datenschutz/">Datenschutzerklärung</a>.</p>"""
|
<p class="form__hint" style="margin-top:.8rem">Das Video wird erst nach deinem Klick von YouTube geladen (youtube-nocookie.com). Mehr dazu in der <a href="/datenschutz/">Datenschutzerklärung</a>.</p>"""
|
||||||
|
|
||||||
|
|
||||||
@@ -146,39 +148,27 @@ def caption_gallery(
|
|||||||
return f'<div class="gallery" data-stagger="{stagger}">{figures}</div>'
|
return f'<div class="gallery" data-stagger="{stagger}">{figures}</div>'
|
||||||
|
|
||||||
|
|
||||||
def pillar_cards() -> str:
|
def area_cards() -> str:
|
||||||
"""The two big entry cards: Shootings (photo) and Videoprojekte (video).
|
"""The four Arbeitsbereiche of the brand.
|
||||||
|
|
||||||
The body carries its own links, so the card must not be one great ``<a>``:
|
Fotografie, Video, Musik & Bühne and Projekte replace the old flat service
|
||||||
|
list: the area comes first, the bookable services sit underneath it. Each
|
||||||
|
card carries its own link, so the card itself must not be one great ``<a>``:
|
||||||
nested anchors are invalid HTML and the parser would break the grid apart.
|
nested anchors are invalid HTML and the parser would break the grid apart.
|
||||||
``.pillar__go`` stretches over the card and keeps the whole surface clickable.
|
|
||||||
"""
|
"""
|
||||||
cards = []
|
cards = []
|
||||||
for pillar in PILLARS:
|
for area in AREAS:
|
||||||
points = "".join(
|
|
||||||
f'<a href="{e(href)}">{e(label)}</a>' for label, href in pillar["points"]
|
|
||||||
)
|
|
||||||
cards.append(
|
cards.append(
|
||||||
'<article class="pillar spot reveal">'
|
'<article class="card reveal">'
|
||||||
'<span class="pillar__media">'
|
f'<div>{picture(area["image"], "(min-width: 72rem) 22vw, (min-width: 40rem) 44vw, 92vw", css_class="frame frame--16x10", fit=area.get("fit"))}</div>'
|
||||||
+ picture(
|
f'<h3 class="card__title">{e(area["title"])}</h3>'
|
||||||
pillar["image"],
|
f'<p class="eyebrow">{e(area["scope"])}</p>'
|
||||||
"(min-width: 62rem) 46vw, 92vw",
|
f'<p class="card__text">{e(area["text"])}</p>'
|
||||||
css_class="",
|
f'<div class="card__foot"><a class="link-arrow" href="{e(area["href"])}">'
|
||||||
fit=pillar.get("fit"),
|
f'{e(area["title"])} ansehen {ICONS["arrow"]}</a></div>'
|
||||||
alt="",
|
"</article>"
|
||||||
)
|
)
|
||||||
+ "</span>"
|
return '<div class="grid grid--areas" data-stagger="0.08">' + "".join(cards) + "</div>"
|
||||||
'<div class="pillar__body">'
|
|
||||||
f'<span class="pillar__label">{e(pillar["label"])}</span>'
|
|
||||||
f'<h3 class="pillar__title">{e(pillar["title"])}</h3>'
|
|
||||||
f'<p class="pillar__text">{e(pillar["lead"])} {e(pillar["text"])}</p>'
|
|
||||||
f'<div class="pillar__points">{points}</div>'
|
|
||||||
f'<a class="pillar__go" href="{e(pillar["href"])}">'
|
|
||||||
f'{e(pillar["title"])} ansehen {ICONS["arrow"]}</a>'
|
|
||||||
"</div></article>"
|
|
||||||
)
|
|
||||||
return '<div class="pillars" data-stagger="0.12">' + "".join(cards) + "</div>"
|
|
||||||
|
|
||||||
|
|
||||||
def marquee(items: list[str]) -> str:
|
def marquee(items: list[str]) -> str:
|
||||||
@@ -211,7 +201,6 @@ def studio_section(*, variant: str = "paper2", tight: bool = False) -> str:
|
|||||||
"(min-width: 62rem) 46vw, 92vw",
|
"(min-width: 62rem) 46vw, 92vw",
|
||||||
css_class="frame frame--4x5",
|
css_class="frame frame--4x5",
|
||||||
fit="50% 45%",
|
fit="50% 45%",
|
||||||
alt="Studioequipment mit Logo von Denny Schulz Fotografie",
|
|
||||||
)
|
)
|
||||||
+ '<span class="studio__badge"><strong>Autarke Blitzanlage</strong>'
|
+ '<span class="studio__badge"><strong>Autarke Blitzanlage</strong>'
|
||||||
"<span>Hintergründe, Blitze, Lichtformer</span></span>"
|
"<span>Hintergründe, Blitze, Lichtformer</span></span>"
|
||||||
@@ -250,9 +239,10 @@ def contact_form() -> str:
|
|||||||
<select id="cf-topic" name="topic">
|
<select id="cf-topic" name="topic">
|
||||||
<option>Businessfotografie</option>
|
<option>Businessfotografie</option>
|
||||||
<option>Portrait & Model</option>
|
<option>Portrait & Model</option>
|
||||||
<option>Familien & Paare</option>
|
<option>Familie</option>
|
||||||
<option>Minishooting</option>
|
<option>Minishooting</option>
|
||||||
<option>Videoprojekt</option>
|
<option>Video</option>
|
||||||
|
<option>Musik & Bühne</option>
|
||||||
<option>Social-Media-Inhalte</option>
|
<option>Social-Media-Inhalte</option>
|
||||||
<option>Pro-bono-Anfrage</option>
|
<option>Pro-bono-Anfrage</option>
|
||||||
<option>Etwas anderes</option>
|
<option>Etwas anderes</option>
|
||||||
@@ -331,19 +321,19 @@ def home() -> str:
|
|||||||
<span class="kicker hero__kicker">{e(SITE['tagline'])}</span>
|
<span class="kicker hero__kicker">{e(SITE['tagline'])}</span>
|
||||||
<h1 class="hero__title">{words('Bilder von Menschen, Marken und Musik.')}</h1>
|
<h1 class="hero__title">{words('Bilder von Menschen, Marken und Musik.')}</h1>
|
||||||
<p class="hero__sub">
|
<p class="hero__sub">
|
||||||
desfoto ist die fotografische und filmische Arbeit von Denny Schulz aus Neumünster:
|
desfoto ist Fotografie, Video und mediale Produktion aus Neumünster:
|
||||||
Shootings für Business, Portrait und Familie, dazu Konzerte, eigene Projekte,
|
Bilder für Menschen und Unternehmen, Bewegtbild für Marken, Musik und Events,
|
||||||
Video und Inhalte für Social Media.
|
Bühnenarbeiten und eigene Projekte.
|
||||||
</p>
|
</p>
|
||||||
<div class="btn-row hero__actions">
|
<div class="btn-row hero__actions">
|
||||||
<a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a>
|
<a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a>
|
||||||
<a class="btn btn--outline-light" href="/projekte/">Arbeiten ansehen</a>
|
<a class="btn btn--outline-light" href="/projekte/">Arbeiten ansehen</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero__facts">
|
<div class="hero__facts">
|
||||||
<div class="hero__fact"><strong>Shootings</strong>Business, Portrait, Familie und Minishootings unter einem Dach.</div>
|
|
||||||
<div class="hero__fact"><strong>Studio & vor Ort</strong>Eigenes Studio in Neumünster oder mobiles Studio inklusive autarker Blitzanlage bei dir.</div>
|
<div class="hero__fact"><strong>Studio & vor Ort</strong>Eigenes Studio in Neumünster oder mobiles Studio inklusive autarker Blitzanlage bei dir.</div>
|
||||||
<div class="hero__fact"><strong>Foto & Video</strong>Beides aus einer Hand, damit Optik und Ton zusammenpassen.</div>
|
<div class="hero__fact"><strong>Foto & Video</strong>Beides aus einer Hand, damit Optik und Ton zusammenpassen.</div>
|
||||||
<div class="hero__fact"><strong>Musik & Bühne</strong>Konzerte, Bands und Musikvideos aus eigener Erfahrung.</div>
|
<div class="hero__fact"><strong>Musik & Bühne</strong>Konzerte, Bands und Musikvideos aus eigener Erfahrung.</div>
|
||||||
|
<div class="hero__fact"><strong>Klarer Ablauf</strong>Umfang, Termin und Preis sind vor der Buchung geklärt.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="scroll-cue" aria-hidden="true">Scroll<i></i></span>
|
<span class="scroll-cue" aria-hidden="true">Scroll<i></i></span>
|
||||||
@@ -361,22 +351,29 @@ def home() -> str:
|
|||||||
</div>""",
|
</div>""",
|
||||||
)
|
)
|
||||||
|
|
||||||
pillars = section(
|
areas = section(
|
||||||
kicker="Zwei Bereiche",
|
kicker="Arbeitsbereiche",
|
||||||
title="Shootings und Videoprojekte",
|
title="Fotografie, Video, Musik & Bühne und Projekte",
|
||||||
lead="Foto und Video bleiben getrennt, damit beide Bereiche für sich verständlich sind — und kommen trotzdem von derselben Person.",
|
lead="Vier Bereiche, in denen ich arbeite. Was du davon buchen kannst, steht direkt darunter — die freien Arbeiten laufen daneben weiter.",
|
||||||
content=pillar_cards(),
|
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(
|
works = section(
|
||||||
variant="paper2",
|
|
||||||
kicker="Portfolio",
|
kicker="Portfolio",
|
||||||
title="Musik, Menschen und freie Arbeiten",
|
title="Was ich außerdem mache",
|
||||||
lead="Konzerte und Bands, Portraits und Business, dazu eigene Strecken aus Landschaft und Tierwelt. Das ist die Bandbreite meiner Arbeit.",
|
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()
|
content=work_gallery()
|
||||||
+ '<div class="btn-row" style="margin-top:2rem">'
|
+ '<div class="btn-row" style="margin-top:2rem">'
|
||||||
'<a class="btn btn--ink" href="/projekte/">Alle Projekte</a>'
|
'<a class="btn btn--ink" href="/projekte/">Alle Projekte</a>'
|
||||||
'<a class="btn btn--ghost" href="/shootings/">Shootings ansehen</a></div>',
|
'<a class="btn btn--ghost" href="/musik-und-buehne/">Musik & Bühne</a></div>',
|
||||||
)
|
)
|
||||||
|
|
||||||
video_sec = f"""<section class="section section--ink">
|
video_sec = f"""<section class="section section--ink">
|
||||||
@@ -422,7 +419,6 @@ def home() -> str:
|
|||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "ProfessionalService",
|
"@type": "ProfessionalService",
|
||||||
"name": SITE["brand"],
|
"name": SITE["brand"],
|
||||||
"alternateName": LEGAL["business"],
|
|
||||||
"description": SITE["default_description"],
|
"description": SITE["default_description"],
|
||||||
"url": SITE["url"] + "/",
|
"url": SITE["url"] + "/",
|
||||||
"image": SITE["url"] + "/assets/img/og-desfoto.jpg",
|
"image": SITE["url"] + "/assets/img/og-desfoto.jpg",
|
||||||
@@ -442,19 +438,23 @@ def home() -> str:
|
|||||||
],
|
],
|
||||||
body=hero
|
body=hero
|
||||||
+ statement
|
+ statement
|
||||||
+ pillars
|
+ areas
|
||||||
+ marquee(
|
+ marquee(
|
||||||
[
|
[
|
||||||
|
"Fotografie",
|
||||||
|
"Video",
|
||||||
"Musik & Bühne",
|
"Musik & Bühne",
|
||||||
|
"Projekte",
|
||||||
"Businessfotografie",
|
"Businessfotografie",
|
||||||
"Portrait & Model",
|
"Portrait & Model",
|
||||||
"Familien & Paare",
|
"Familie",
|
||||||
"Minishootings",
|
"Minishootings",
|
||||||
"Mobiles Studio",
|
"Mobiles Studio",
|
||||||
"Videoprojekte",
|
"Musikvideo",
|
||||||
"Social Media",
|
"Social Media",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
+ bookable
|
||||||
+ works
|
+ works
|
||||||
+ video_sec
|
+ video_sec
|
||||||
+ studio
|
+ studio
|
||||||
@@ -464,15 +464,15 @@ def home() -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def shootings() -> str:
|
def fotografie() -> str:
|
||||||
path = "/shootings/"
|
path = "/fotografie/"
|
||||||
overview = "".join(
|
overview = "".join(
|
||||||
'<article class="card reveal"><div>'
|
'<article class="card reveal"><div>'
|
||||||
+ picture(
|
+ picture(
|
||||||
item["image"],
|
item["image"],
|
||||||
"(min-width: 62rem) 30vw, 92vw",
|
"(min-width: 62rem) 30vw, 92vw",
|
||||||
css_class="frame frame--4x5",
|
css_class="frame frame--4x5",
|
||||||
fit="50% 30%",
|
fit=item.get("fit"),
|
||||||
alt=item["title"],
|
alt=item["title"],
|
||||||
)
|
)
|
||||||
+ f'</div><h3>{e(item["title"])}</h3><p class="card__text">{e(item["text"])}</p></article>'
|
+ f'</div><h3>{e(item["title"])}</h3><p class="card__text">{e(item["text"])}</p></article>'
|
||||||
@@ -480,35 +480,35 @@ def shootings() -> str:
|
|||||||
)
|
)
|
||||||
body = (
|
body = (
|
||||||
page_head(
|
page_head(
|
||||||
kicker="Shootings",
|
kicker="Fotografie",
|
||||||
title="Shootings — von Business bis Familie",
|
title="Fotografie — Menschen, Unternehmen, Produkte, Veranstaltungen",
|
||||||
lead="Alles, was ich fotografisch mache, läuft unter dem Begriff Shooting: vier Schwerpunkte, ein Ablauf, ein Ansprechpartner.",
|
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",
|
image="editorial-earring",
|
||||||
breadcrumb=("Shootings", path),
|
breadcrumb=("Fotografie", path),
|
||||||
fit="45% 30%",
|
fit="45% 30%",
|
||||||
actions='<a class="btn btn--solid" href="/kontakt/">Shooting anfragen</a>'
|
actions='<a class="btn btn--solid" href="/kontakt/">Fototermin anfragen</a>'
|
||||||
'<a class="btn btn--outline-light" href="/projekte/">Arbeiten ansehen</a>',
|
'<a class="btn btn--outline-light" href="/projekte/">Arbeiten ansehen</a>',
|
||||||
)
|
)
|
||||||
+ section(
|
+ section(
|
||||||
kicker="Überblick",
|
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.",
|
lead="Du musst dich nicht für eine Schublade entscheiden — erzähl einfach, worum es geht.",
|
||||||
content=f'<div class="grid grid--3" data-stagger="0.09">{overview}</div>',
|
content=f'<div class="grid grid--3" data-stagger="0.09">{overview}</div>',
|
||||||
)
|
)
|
||||||
+ section(
|
+ section(
|
||||||
variant="paper2",
|
variant="paper2",
|
||||||
kicker="Schwerpunkte",
|
kicker="Schwerpunkte",
|
||||||
title="Die vier Shootings im Detail",
|
title="Die vier buchbaren Schwerpunkte",
|
||||||
content=service_cards(
|
content=service_cards(
|
||||||
["businessfotografie", "portrait-und-model", "familien-und-paare", "minishootings"]
|
["businessfotografie", "portrait-und-model", "familie", "minishootings"]
|
||||||
),
|
),
|
||||||
tight=True,
|
tight=True,
|
||||||
)
|
)
|
||||||
+ studio_section(variant="")
|
+ studio_section(variant="")
|
||||||
+ section(
|
+ section(
|
||||||
kicker="Ablauf",
|
kicker="Ablauf",
|
||||||
title="So läuft ein Shooting",
|
title="So läuft ein Termin",
|
||||||
lead="Bei jedem Shooting derselbe Weg — vom ersten Satz bis zur fertigen Datei.",
|
lead="Bei jedem Auftrag derselbe Weg — vom ersten Satz bis zur fertigen Datei.",
|
||||||
content='<div class="steps">'
|
content='<div class="steps">'
|
||||||
+ "".join(
|
+ "".join(
|
||||||
f'<div class="step reveal"><h3>{e(step["title"])}</h3><p>{e(step["text"])}</p></div>'
|
f'<div class="step reveal"><h3>{e(step["title"])}</h3><p>{e(step["text"])}</p></div>'
|
||||||
@@ -519,16 +519,16 @@ def shootings() -> str:
|
|||||||
+ section(
|
+ section(
|
||||||
variant="paper2",
|
variant="paper2",
|
||||||
kicker="Beispiele",
|
kicker="Beispiele",
|
||||||
title="Aus Shootings und Aufträgen",
|
title="Aus Aufträgen und eigener Arbeit",
|
||||||
content=media_grid(
|
content=media_grid(
|
||||||
[
|
[
|
||||||
("business-portrait-dark", "Portrait eines Mannes vor dunklem Hintergrund", "4x5", "50% 28%"),
|
("business-portrait-dark", "Portrait eines Mannes vor dunklem Hintergrund", "4x5", "50% 28%"),
|
||||||
("editorial-colour", "Fashion-Portrait in kräftigen Farben", "4x5", "50% 30%"),
|
("editorial-balance", "Editorial-Pose mit ausbalancierter Haltung", "4x5", "50% 40%"),
|
||||||
("portrait-couple", "Paarportrait im Grünen", "4x5", "50% 30%"),
|
("portrait-marina-sunset", "Silhouette einer Frau vor orangefarbenem Abendhimmel", "4x5", "50% 40%"),
|
||||||
("business-portrait-stool", "Sitzendes Portrait im weichen Studiolicht", "3x2", "50% 35%"),
|
("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%"),
|
("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%"),
|
("portrait-marina-snow", "Marina mit roten Zöpfen auf einem verschneiten Waldweg", "3x2", "50% 30%"),
|
||||||
("couple-backlight-kiss", "Sara und Carlos küssen sich im Gegenlicht", "1x1", "50% 45%"),
|
("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%"),
|
("editorial-leap", "Marta springt mit angewinkelten Beinen", "1x1", "50% 35%"),
|
||||||
("free-dog-husky", "Ein Hund steht auf einem sonnigen Waldweg", "1x1", "50% 45%"),
|
("free-dog-husky", "Ein Hund steht auf einem sonnigen Waldweg", "1x1", "50% 45%"),
|
||||||
]
|
]
|
||||||
@@ -558,22 +558,22 @@ def shootings() -> str:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
+ cta_band(
|
+ 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.",
|
text="Schreib kurz, worum es geht und wann es stattfinden soll. Du bekommst Ablauf, Umfang und Preis.",
|
||||||
secondary=("Minishootings ansehen", "/minishootings/"),
|
secondary=("Minishootings ansehen", "/minishootings/"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return page(
|
return page(
|
||||||
title="Shootings in Neumünster · desfoto",
|
title="Fotografie in Neumünster · desfoto",
|
||||||
description="Shootings aus Neumünster: Businessfotografie, Portrait & Model, Familien & Paare und Minishootings — mit mobilem Studio inklusive autarker Blitzanlage.",
|
description="Fotografie aus Neumünster: Businessfotografie, Portrait & Model, Familie und Minishootings — mit mobilem Studio inklusive autarker Blitzanlage.",
|
||||||
path=path,
|
path=path,
|
||||||
json_ld=[
|
json_ld=[
|
||||||
service_ld(
|
service_ld(
|
||||||
"Shootings",
|
"Fotografie",
|
||||||
path,
|
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,
|
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-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%"),
|
("business-hotel-tea", "Drei Frauen mit Teetassen und Handtuchturbanen", "4x5", "50% 45%"),
|
||||||
("gear-cases", "Kamerakoffer und Objektive vor einem Studiorack", "3x2", "50% 50%"),
|
("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-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%"),
|
("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-studio-side", "50% 30%"),
|
||||||
("editorial-balance", "50% 40%"),
|
("editorial-balance", "50% 40%"),
|
||||||
("editorial-leap", "50% 35%"),
|
("editorial-leap", "50% 35%"),
|
||||||
("portrait-natural", "50% 30%"),
|
("free-portrait-light", "50% 30%"),
|
||||||
("people-kiss", "50% 32%"),
|
("free-modern-portrait", "50% 30%"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
+ "</div>",
|
+ "</div>",
|
||||||
@@ -789,21 +789,21 @@ def portrait_und_model() -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def familien_und_paare() -> str:
|
def familie() -> str:
|
||||||
path = "/familien-und-paare/"
|
path = "/familie/"
|
||||||
body = (
|
body = (
|
||||||
page_head(
|
page_head(
|
||||||
kicker="Familien & Paare",
|
kicker="Familie",
|
||||||
title="Wachsendes festhalten, ohne Pose",
|
title="Wachsendes festhalten, ohne Pose",
|
||||||
lead="Familienshootings und Paarportraits, die nicht gestellt wirken. Zeit lassen, mitgehen und im richtigen Moment auslösen.",
|
lead="Familienshootings, die nicht gestellt wirken. Zeit lassen, mitgehen und im richtigen Moment auslösen.",
|
||||||
image="people-joy",
|
image="free-portrait-light",
|
||||||
breadcrumb=("Familien & Paare", path),
|
breadcrumb=("Familie", path),
|
||||||
fit="50% 30%",
|
fit="50% 28%",
|
||||||
actions='<a class="btn btn--solid" href="/kontakt/">Termin anfragen</a>',
|
actions='<a class="btn btn--solid" href="/kontakt/">Termin anfragen</a>',
|
||||||
)
|
)
|
||||||
+ section(
|
+ section(
|
||||||
kicker="Anlässe",
|
kicker="Anlässe",
|
||||||
title="Für welche Momente mich Familien und Paare buchen",
|
title="Für welche Momente mich Familien buchen",
|
||||||
content='<div class="grid grid--3">'
|
content='<div class="grid grid--3">'
|
||||||
+ "".join(
|
+ "".join(
|
||||||
f'<article class="card reveal"><h3>{e(t)}</h3><p class="card__text">{e(d)}</p></article>'
|
f'<article class="card reveal"><h3>{e(t)}</h3><p class="card__text">{e(d)}</p></article>'
|
||||||
@@ -812,18 +812,6 @@ def familien_und_paare() -> str:
|
|||||||
"Familienshooting",
|
"Familienshooting",
|
||||||
"Alle zusammen, ohne dass es nach Fototermin aussieht. Auch mit mehreren Generationen.",
|
"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",
|
"Generationenbilder",
|
||||||
"Großeltern, Eltern, Enkel: Bilder, die in Jahren mehr wert sind als heute.",
|
"Großeltern, Eltern, Enkel: Bilder, die in Jahren mehr wert sind als heute.",
|
||||||
@@ -832,6 +820,18 @@ def familien_und_paare() -> str:
|
|||||||
"Kinderportraits",
|
"Kinderportraits",
|
||||||
"Kurz, spielerisch und geduldig — lieber zehn ehrliche Bilder als eine Stunde Stillhalten.",
|
"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.",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
+ "</div>",
|
+ "</div>",
|
||||||
@@ -845,20 +845,16 @@ def familien_und_paare() -> str:
|
|||||||
)
|
)
|
||||||
+ section(
|
+ section(
|
||||||
kicker="Beispiele",
|
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='<div class="grid grid--3">'
|
content='<div class="grid grid--3">'
|
||||||
+ "".join(
|
+ "".join(
|
||||||
picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit)
|
picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit)
|
||||||
for slug, fit in [
|
for slug, fit in [
|
||||||
("portrait-natural", "50% 30%"),
|
("free-lake-person", "50% 45%"),
|
||||||
("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%"),
|
|
||||||
("portrait-marina-snow", "50% 30%"),
|
("portrait-marina-snow", "50% 30%"),
|
||||||
|
("free-modern-portrait", "50% 30%"),
|
||||||
|
("free-woman-coast", "50% 40%"),
|
||||||
("portrait-marina-sunset", "50% 45%"),
|
("portrait-marina-sunset", "50% 45%"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -871,12 +867,16 @@ def familien_und_paare() -> str:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
return page(
|
return page(
|
||||||
title="Familien- & Paarfotografie in Neumünster · desfoto",
|
title="Familienfotografie 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.",
|
description="Familienshootings aus Neumünster: natürliche Bilder ohne Pose, im Studio, in der Natur oder bei euch zuhause.",
|
||||||
path=path,
|
path=path,
|
||||||
json_ld=[
|
json_ld=[
|
||||||
service_ld("Familien- und Paarfotografie", path, "Familienshootings und Paarportraits ohne gestellte Posen."),
|
service_ld(
|
||||||
breadcrumb_ld("Familien & Paare", path),
|
"Familienfotografie",
|
||||||
|
path,
|
||||||
|
"Familienshootings ohne gestellte Posen, im Studio, in der Natur oder zuhause.",
|
||||||
|
),
|
||||||
|
breadcrumb_ld("Familie", path),
|
||||||
],
|
],
|
||||||
body=body,
|
body=body,
|
||||||
)
|
)
|
||||||
@@ -889,7 +889,7 @@ def minishootings() -> str:
|
|||||||
kicker="Minishootings",
|
kicker="Minishootings",
|
||||||
title="Kurze Session, sofort brauchbares Bild",
|
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.",
|
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),
|
breadcrumb=("Minishootings", path),
|
||||||
fit="50% 30%",
|
fit="50% 30%",
|
||||||
actions='<a class="btn btn--solid" href="/kontakt/">Minishooting buchen</a>',
|
actions='<a class="btn btn--solid" href="/kontakt/">Minishooting buchen</a>',
|
||||||
@@ -902,11 +902,11 @@ def minishootings() -> str:
|
|||||||
f'<article class="card reveal"><h3>{e(t)}</h3><p class="card__text">{e(d)}</p></article>'
|
f'<article class="card reveal"><h3>{e(t)}</h3><p class="card__text">{e(d)}</p></article>'
|
||||||
for t, d in [
|
for t, d in [
|
||||||
("Profilbild", "Für LinkedIn, Bewerbung, Vereinsseite oder die Website."),
|
("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."),
|
("Social Media", "Frisches Material für deine Kanäle, passend zum Format."),
|
||||||
("Verein & Mannschaft", "Schnell alle Gesichter für Mannschaftsfotos und Anmeldungen."),
|
("Verein & Mannschaft", "Schnell alle Gesichter für Mannschaftsfotos und Anmeldungen."),
|
||||||
("Businesskurzportrait", "Du brauchst nur ein Bild für die neue Stelle oder Präsentation."),
|
("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."),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
+ "</div>",
|
+ "</div>",
|
||||||
@@ -958,11 +958,11 @@ def minishootings() -> str:
|
|||||||
+ "".join(
|
+ "".join(
|
||||||
picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit)
|
picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit)
|
||||||
for slug, fit in [
|
for slug, fit in [
|
||||||
("portrait-natural", "50% 28%"),
|
("free-woman-coast", "50% 30%"),
|
||||||
("business-portrait-studio", "50% 28%"),
|
("business-portrait-studio", "50% 28%"),
|
||||||
("editorial-black", "50% 28%"),
|
("editorial-black", "50% 28%"),
|
||||||
("editorial-studio-side", "50% 30%"),
|
("editorial-studio-side", "50% 30%"),
|
||||||
("free-modern-portrait", "50% 30%"),
|
("free-portrait-light", "50% 30%"),
|
||||||
("portrait-marina-snow", "50% 30%"),
|
("portrait-marina-snow", "50% 30%"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -1006,6 +1006,7 @@ def video() -> str:
|
|||||||
<h2 class="balance">{e(VIDEO['title'])}</h2>
|
<h2 class="balance">{e(VIDEO['title'])}</h2>
|
||||||
<p class="lead measure" style="color:rgba(246,242,234,.82)">{e(VIDEO['description'])}</p>
|
<p class="lead measure" style="color:rgba(246,242,234,.82)">{e(VIDEO['description'])}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<a class="link-arrow" style="color:#F6F2EA;border-color:rgba(246,242,234,.4)" href="/musik-und-buehne/">Musik & Bühne {ICONS['arrow']}</a>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:clamp(1.6rem,3vw,2.6rem)">{video_block()}</div>
|
<div style="margin-top:clamp(1.6rem,3vw,2.6rem)">{video_block()}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1102,7 +1103,7 @@ def video() -> str:
|
|||||||
content=caption_gallery(
|
content=caption_gallery(
|
||||||
[
|
[
|
||||||
("studio-lens", "Brennweite und Licht im Studio", "g-w3", "3x2", "50% 50%"),
|
("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%"),
|
("movement-light", "Lange Belichtung", "g-w2", "1x1", "50% 50%"),
|
||||||
("event-speaker", "Statement vor der Kamera", "g-w2", "1x1", "50% 30%"),
|
("event-speaker", "Statement vor der Kamera", "g-w2", "1x1", "50% 30%"),
|
||||||
("event-venue", "Location vor dem Dreh", "g-w2", "1x1", "50% 45%"),
|
("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='<a class="btn btn--solid" href="/kontakt/">Musikprojekt anfragen</a>'
|
||||||
|
'<a class="btn btn--outline-light" href="/projekte/">Arbeiten ansehen</a>',
|
||||||
|
)
|
||||||
|
+ section(
|
||||||
|
kicker="Für Bands und Veranstalter",
|
||||||
|
title="Was ich für Musik und Bühne mache",
|
||||||
|
content='<div class="grid grid--3" data-stagger="0.08">'
|
||||||
|
+ "".join(
|
||||||
|
f'<article class="card reveal"><h3>{e(t)}</h3><p class="card__text">{e(d)}</p></article>'
|
||||||
|
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.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
+ "</div>",
|
||||||
|
)
|
||||||
|
+ f"""<section class="section section--ink">
|
||||||
|
<div class="shell">
|
||||||
|
<div class="section-head">
|
||||||
|
<div class="section-head__text stack">
|
||||||
|
<span class="kicker">Musikvideo</span>
|
||||||
|
<h2 class="balance">{e(VIDEO['title'])}</h2>
|
||||||
|
<p class="lead measure" style="color:rgba(246,242,234,.82)">{e(VIDEO['description'])}</p>
|
||||||
|
</div>
|
||||||
|
<a class="link-arrow" style="color:#F6F2EA;border-color:rgba(246,242,234,.4)" href="/video/">Mehr zu Video {ICONS['arrow']}</a>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:clamp(1.6rem,3vw,2.6rem)">{video_block(with_meta=False)}</div>
|
||||||
|
</div>
|
||||||
|
</section>"""
|
||||||
|
+ 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='<div class="grid grid--4" data-stagger="0.08">'
|
||||||
|
+ "".join(
|
||||||
|
f'<article class="card reveal"><h3>{e(t)}</h3><p class="card__text">{e(d)}</p></article>'
|
||||||
|
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."),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
+ "</div>",
|
||||||
|
)
|
||||||
|
+ 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:
|
def social_media() -> str:
|
||||||
path = "/social-media/"
|
path = "/social-media/"
|
||||||
body = (
|
body = (
|
||||||
@@ -1136,7 +1247,7 @@ def social_media() -> str:
|
|||||||
kicker="Social Media",
|
kicker="Social Media",
|
||||||
title="Content, der regelmäßig kommt",
|
title="Content, der regelmäßig kommt",
|
||||||
lead="Foto- und Videomaterial im Format deiner Kanäle — wiederkehrend, planbar und über die Monate hinweg einheitlich.",
|
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),
|
breadcrumb=("Social Media", path),
|
||||||
fit="50% 42%",
|
fit="50% 42%",
|
||||||
actions='<a class="btn btn--solid" href="/kontakt/">Content anfragen</a>',
|
actions='<a class="btn btn--solid" href="/kontakt/">Content anfragen</a>',
|
||||||
@@ -1214,9 +1325,9 @@ def social_media() -> str:
|
|||||||
+ "".join(
|
+ "".join(
|
||||||
picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit)
|
picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit)
|
||||||
for slug, fit in [
|
for slug, fit in [
|
||||||
("event-motion", "50% 45%"),
|
("event-venue", "50% 45%"),
|
||||||
("event-dance", "50% 36%"),
|
("free-modern-portrait", "50% 40%"),
|
||||||
("event-speaker", "50% 30%"),
|
("music-dreadlocks", "50% 35%"),
|
||||||
("business-hotel-tea", "50% 45%"),
|
("business-hotel-tea", "50% 45%"),
|
||||||
("business-hotel-laugh", "50% 45%"),
|
("business-hotel-laugh", "50% 45%"),
|
||||||
("music-drummer", "50% 45%"),
|
("music-drummer", "50% 45%"),
|
||||||
@@ -1274,7 +1385,7 @@ def projekte() -> str:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
+ '<div class="btn-row" style="margin-top:2rem">'
|
+ '<div class="btn-row" style="margin-top:2rem">'
|
||||||
'<a class="link-arrow" href="/video/">Zum Musikvideo '
|
'<a class="link-arrow" href="/musik-und-buehne/">Musik & Bühne '
|
||||||
+ ICONS["arrow"]
|
+ ICONS["arrow"]
|
||||||
+ "</a></div>",
|
+ "</a></div>",
|
||||||
)
|
)
|
||||||
@@ -1343,7 +1454,7 @@ def projekte() -> str:
|
|||||||
+ "".join(
|
+ "".join(
|
||||||
f'<article class="card reveal"><h3>{e(t)}</h3><p class="card__text">{e(d)}</p><div class="card__foot"><a class="link-arrow" href="{h}">Ansehen {ICONS["arrow"]}</a></div></article>'
|
f'<article class="card reveal"><h3>{e(t)}</h3><p class="card__text">{e(d)}</p><div class="card__foot"><a class="link-arrow" href="{h}">Ansehen {ICONS["arrow"]}</a></div></article>'
|
||||||
for t, d, h in [
|
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/"),
|
("Businessfotografie", "Portraits, Teams und Produkte für Unternehmen.", "/businessfotografie/"),
|
||||||
("Videoprojekte", "Musikvideo, Imagefilm und Eventnachbericht.", "/video/"),
|
("Videoprojekte", "Musikvideo, Imagefilm und Eventnachbericht.", "/video/"),
|
||||||
]
|
]
|
||||||
@@ -1367,8 +1478,8 @@ def ueber() -> str:
|
|||||||
page_head(
|
page_head(
|
||||||
kicker="Über",
|
kicker="Über",
|
||||||
title="Fotograf und Videograf aus Neumünster",
|
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.",
|
lead="Hinter desfoto stehe ich, Denny Schulz. Fotograf, Videograf und Musiker aus Neumünster.",
|
||||||
image="studio-setup",
|
image="gear-cases",
|
||||||
breadcrumb=("Über", path),
|
breadcrumb=("Über", path),
|
||||||
fit="50% 45%",
|
fit="50% 45%",
|
||||||
actions='<a class="btn btn--solid" href="/kontakt/">Schreib mir</a>',
|
actions='<a class="btn btn--solid" href="/kontakt/">Schreib mir</a>',
|
||||||
@@ -1398,7 +1509,7 @@ def ueber() -> str:
|
|||||||
for t, d in [
|
for t, d in [
|
||||||
(
|
(
|
||||||
"Menschen fotografieren",
|
"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",
|
"Musik & Bühne begleiten",
|
||||||
@@ -1443,9 +1554,9 @@ def ueber() -> str:
|
|||||||
content=media_grid(
|
content=media_grid(
|
||||||
[
|
[
|
||||||
("business-portrait-studio", "Mann im weißen Hemd vor dunklem Hintergrund, Softbox im Bild", "4x5", "50% 28%"),
|
("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%"),
|
("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%"),
|
("free-fox", "Nahaufnahme eines Rotfuchses", "1x1", "50% 45%"),
|
||||||
],
|
],
|
||||||
columns="grid--2",
|
columns="grid--2",
|
||||||
@@ -1455,7 +1566,7 @@ def ueber() -> str:
|
|||||||
variant="paper2",
|
variant="paper2",
|
||||||
kicker="Netzwerk",
|
kicker="Netzwerk",
|
||||||
title="Wo ich sonst zu finden bin",
|
title="Wo ich sonst zu finden bin",
|
||||||
lead="Zwei eigene Projekte, die zu desfoto gehören.",
|
lead="Zwei eigene Projekte des Betreibers.",
|
||||||
content='<div class="grid grid--2">'
|
content='<div class="grid grid--2">'
|
||||||
+ "".join(
|
+ "".join(
|
||||||
'<article class="card reveal">'
|
'<article class="card reveal">'
|
||||||
@@ -1484,7 +1595,7 @@ def ueber() -> str:
|
|||||||
)
|
)
|
||||||
return page(
|
return page(
|
||||||
title="Über desfoto · Denny Schulz, Neumünster",
|
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,
|
path=path,
|
||||||
json_ld=[
|
json_ld=[
|
||||||
breadcrumb_ld("Über", path),
|
breadcrumb_ld("Über", path),
|
||||||
@@ -1494,7 +1605,7 @@ def ueber() -> str:
|
|||||||
"name": LEGAL["owner"],
|
"name": LEGAL["owner"],
|
||||||
"jobTitle": "Fotograf und Videograf",
|
"jobTitle": "Fotograf und Videograf",
|
||||||
"url": SITE["url"] + path,
|
"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],
|
"sameAs": [item["href"] for item in NETWORK],
|
||||||
"address": {
|
"address": {
|
||||||
"@type": "PostalAddress",
|
"@type": "PostalAddress",
|
||||||
@@ -1576,7 +1687,7 @@ def kontakt() -> str:
|
|||||||
title="Lieber direkt telefonieren?",
|
title="Lieber direkt telefonieren?",
|
||||||
text="Kein Problem. Ruf einfach an — falls ich gerade im Termin bin, melde ich mich zurück.",
|
text="Kein Problem. Ruf einfach an — falls ich gerade im Termin bin, melde ich mich zurück.",
|
||||||
primary=("Anrufen", "tel:+491754083133"),
|
primary=("Anrufen", "tel:+491754083133"),
|
||||||
secondary=("E-Mail schreiben", "mailto:info@dennyschulz.de"),
|
secondary=("E-Mail schreiben", f"mailto:{CONTACT['email']}"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return page(
|
return page(
|
||||||
@@ -1831,12 +1942,13 @@ def not_found() -> str:
|
|||||||
|
|
||||||
ROUTES = [
|
ROUTES = [
|
||||||
("/", "index.html", home, None, "1.0"),
|
("/", "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"),
|
("/businessfotografie/", "businessfotografie/index.html", businessfotografie, None, "0.9"),
|
||||||
("/portrait-und-model/", "portrait-und-model/index.html", portrait_und_model, 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"),
|
("/minishootings/", "minishootings/index.html", minishootings, None, "0.9"),
|
||||||
("/video/", "video/index.html", video, 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"),
|
("/social-media/", "social-media/index.html", social_media, None, "0.9"),
|
||||||
("/projekte/", "projekte/index.html", projekte, None, "0.8"),
|
("/projekte/", "projekte/index.html", projekte, None, "0.8"),
|
||||||
("/ueber/", "ueber/index.html", ueber, None, "0.8"),
|
("/ueber/", "ueber/index.html", ueber, None, "0.8"),
|
||||||
|
|||||||
@@ -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--2{grid-template-columns:repeat(auto-fit,minmax(19rem,1fr))}
|
||||||
.grid--3{grid-template-columns:repeat(auto-fit,minmax(16.5rem,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))}
|
.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{display:flex;flex-direction:column;gap:1rem;height:100%}
|
||||||
.card__title{display:flex;align-items:baseline;gap:.6rem}
|
.card__title{display:flex;align-items:baseline;gap:.6rem}
|
||||||
.card__num{font-size:.72rem;font-weight:600;letter-spacing:.16em;color:var(--accent)}
|
.card__num{font-size:.72rem;font-weight:600;letter-spacing:.16em;color:var(--accent)}
|
||||||
|
|||||||
@@ -262,10 +262,11 @@ class Content(unittest.TestCase):
|
|||||||
def test_all_requested_services_are_reachable(self):
|
def test_all_requested_services_are_reachable(self):
|
||||||
home = read(SITE / "index.html")
|
home = read(SITE / "index.html")
|
||||||
for href in (
|
for href in (
|
||||||
"/shootings/",
|
"/fotografie/",
|
||||||
"/businessfotografie/",
|
"/businessfotografie/",
|
||||||
"/familien-und-paare/",
|
"/familie/",
|
||||||
"/minishootings/",
|
"/minishootings/",
|
||||||
|
"/musik-und-buehne/",
|
||||||
"/projekte/",
|
"/projekte/",
|
||||||
"/video/",
|
"/video/",
|
||||||
"/social-media/",
|
"/social-media/",
|
||||||
@@ -315,31 +316,110 @@ class Content(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class Redesign(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:
|
for file in HTML_FILES:
|
||||||
text = read(file)
|
text = read(file)
|
||||||
with self.subTest(file=file.name):
|
with self.subTest(file=file.name):
|
||||||
self.assertIn('href="/shootings/"', text)
|
self.assertIn('href="/fotografie/"', text)
|
||||||
self.assertNotIn('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):
|
def test_all_four_photo_schwerpunkte_are_grouped_under_the_umbrella(self):
|
||||||
shootings = read(SITE / "shootings" / "index.html")
|
fotografie = read(SITE / "fotografie" / "index.html")
|
||||||
for href in (
|
for href in (
|
||||||
"/businessfotografie/",
|
"/businessfotografie/",
|
||||||
"/portrait-und-model/",
|
"/portrait-und-model/",
|
||||||
"/familien-und-paare/",
|
"/familie/",
|
||||||
"/minishootings/",
|
"/minishootings/",
|
||||||
):
|
):
|
||||||
with self.subTest(href=href):
|
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")
|
home = read(SITE / "index.html")
|
||||||
self.assertEqual(home.count('<article class="pillar spot reveal">'), 2)
|
start = home.index("Arbeitsbereiche")
|
||||||
self.assertIn('<a class="pillar__go" href="/shootings/">Shootings ansehen', home)
|
end = home.index("</section>", start)
|
||||||
self.assertIn('<a class="pillar__go" href="/video/">Videoprojekte ansehen', home)
|
block = home[start:end]
|
||||||
|
self.assertEqual(4, block.count('<article class="card reveal">'))
|
||||||
|
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("<footer"):]
|
||||||
|
self.assertNotIn("Denny Schulz Fotografie", footer)
|
||||||
|
|
||||||
|
def test_legal_entity_only_survives_in_legal_pages_and_structured_data(self):
|
||||||
|
"""„Denny Schulz Fotografie" ist die Rechtsperson, nicht die Marke.
|
||||||
|
|
||||||
|
Sichtbar darf sie nur auf Impressum und Datenschutz auftauchen; auf allen
|
||||||
|
anderen Seiten ist ausschließlich das unsichtbare ``legalName`` im
|
||||||
|
JSON-LD erlaubt.
|
||||||
|
"""
|
||||||
|
for file in HTML_FILES:
|
||||||
|
if file.parent.name in ("impressum", "datenschutz"):
|
||||||
|
continue
|
||||||
|
remainder = read(file).replace('"legalName":"Denny Schulz Fotografie"', "")
|
||||||
|
with self.subTest(file=str(file.relative_to(SITE))):
|
||||||
|
self.assertNotIn("Denny Schulz Fotografie", remainder)
|
||||||
|
|
||||||
|
def test_brand_is_written_lowercase_only(self):
|
||||||
|
"""Der Betreiber schreibt die Marke klein: ``desfoto``, nie ``DESFOTO``."""
|
||||||
|
for file in HTML_FILES:
|
||||||
|
with self.subTest(file=str(file.relative_to(SITE))):
|
||||||
|
self.assertNotIn("DESFOTO", read(file))
|
||||||
|
|
||||||
|
def test_built_pages_carry_no_trailing_whitespace(self):
|
||||||
|
"""Zeilen aus Leerzeichen lehnt das Release-Gateway (``git diff --check``) ab.
|
||||||
|
|
||||||
|
Eine bedingte Zeile in einer f-String-Vorlage erzeugt genau das: leer
|
||||||
|
eingesetzt bleibt die Einrückung stehen. Der Test hält die Ausgabe sauber,
|
||||||
|
damit die Freigabe nicht erst am Gateway scheitert.
|
||||||
|
"""
|
||||||
|
offenders = [
|
||||||
|
f"{file.relative_to(SITE)}:{number}"
|
||||||
|
for file in HTML_FILES
|
||||||
|
for number, line in enumerate(read(file).splitlines(), start=1)
|
||||||
|
if line != line.rstrip()
|
||||||
|
]
|
||||||
|
self.assertEqual([], offenders)
|
||||||
|
|
||||||
def test_links_are_never_nested(self):
|
def test_links_are_never_nested(self):
|
||||||
"""Nested <a> is invalid and the parser splits the surrounding layout."""
|
"""Nested <a> is invalid and the parser splits the surrounding layout."""
|
||||||
@@ -370,30 +450,54 @@ class Redesign(unittest.TestCase):
|
|||||||
self.assertEqual([], guard.problems)
|
self.assertEqual([], guard.problems)
|
||||||
self.assertEqual(0, guard.depth, "unbalanced <a>")
|
self.assertEqual(0, guard.depth, "unbalanced <a>")
|
||||||
|
|
||||||
def test_pillar_grid_has_exactly_two_children(self):
|
def test_every_area_card_links_to_its_area(self):
|
||||||
"""Regression: the two pillars must stay two grid children."""
|
"""Each Arbeitsbereich card keeps its own link (no nested anchors)."""
|
||||||
home = read(SITE / "index.html")
|
home = read(SITE / "index.html")
|
||||||
start = home.index('<div class="pillars"')
|
start = home.index("Arbeitsbereiche")
|
||||||
end = home.index("</section>", start)
|
end = home.index("</section>", start)
|
||||||
self.assertEqual(2, home[start:end].count('<article class="pillar spot reveal">'))
|
block = home[start:end]
|
||||||
|
self.assertEqual(4, block.count('class="link-arrow"'))
|
||||||
|
self.assertNotIn("<a class=\"card", block)
|
||||||
|
|
||||||
def test_nginx_redirects_the_retired_fotografie_url(self):
|
def test_nginx_redirects_the_renamed_photo_urls(self):
|
||||||
conf = read(ROOT / "nginx.conf")
|
conf = read(ROOT / "nginx.conf")
|
||||||
for line in (
|
for line in (
|
||||||
"location = /fotografie { return 301 /shootings/; }",
|
"location = /shootings { return 301 /fotografie/; }",
|
||||||
"location = /fotografie/ { return 301 /shootings/; }",
|
"location = /shootings/ { return 301 /fotografie/; }",
|
||||||
"location = /fotografie.html { return 301 /shootings/; }",
|
"location = /shootings.html { return 301 /fotografie/; }",
|
||||||
|
"location = /fotografie.html { return 301 /fotografie/; }",
|
||||||
|
"location = /familien-und-paare { return 301 /familie/; }",
|
||||||
|
"location = /familien-und-paare/ { return 301 /familie/; }",
|
||||||
|
"location = /familien-und-paare.html { return 301 /familie/; }",
|
||||||
):
|
):
|
||||||
with self.subTest(line=line):
|
with self.subTest(line=line):
|
||||||
self.assertIn(line, conf)
|
self.assertIn(line, conf)
|
||||||
|
# The old umbrella must not win over the new one.
|
||||||
|
self.assertNotIn("return 301 /shootings/;", conf)
|
||||||
# /fotografie/index.html is canonicalised to /fotografie/ by the
|
# /fotografie/index.html is canonicalised to /fotografie/ by the
|
||||||
# server-level index rule and reaches /shootings/ on the second hop.
|
# server-level index rule instead of bouncing back to itself.
|
||||||
self.assertIn(r"if ($request_uri ~ ^/(.*/)?index\.html(\?.*)?$)", conf)
|
self.assertIn(r"if ($request_uri ~ ^/(.*/)?index\.html(\?.*)?$)", conf)
|
||||||
|
|
||||||
def test_retired_fotografie_build_output_is_gone(self):
|
def test_retired_routes_and_brand_bleed_assets_are_gone(self):
|
||||||
self.assertFalse((SITE / "fotografie").exists())
|
self.assertFalse((SITE / "shootings").exists())
|
||||||
self.assertFalse((SITE / "assets" / "img" / "og-fotografie.jpg").exists())
|
self.assertFalse((SITE / "familien-und-paare").exists())
|
||||||
|
self.assertFalse((SITE / "assets" / "img" / "og-shootings.jpg").exists())
|
||||||
|
self.assertFalse((SITE / "assets" / "img" / "og-familien.jpg").exists())
|
||||||
|
manifest = json.loads(read(SRC / "images.json"))
|
||||||
|
slugs = {entry["slug"] for entry in manifest["images"]}
|
||||||
for slug in (
|
for slug in (
|
||||||
|
# Studiopanorama with a visible "Denny Schulz Fotograf" business card.
|
||||||
|
"studio-setup",
|
||||||
|
# Romantic couple imagery: advertised on dennyschulz.de only.
|
||||||
|
"people-joy",
|
||||||
|
"people-kiss",
|
||||||
|
"portrait-couple",
|
||||||
|
"couple-park",
|
||||||
|
"couple-backlight-kiss",
|
||||||
|
"couple-forest-walk",
|
||||||
|
"couple-sun-silhouette",
|
||||||
|
"couple-laugh-walk",
|
||||||
|
# Retired from an earlier build.
|
||||||
"people-forest",
|
"people-forest",
|
||||||
"people-veil",
|
"people-veil",
|
||||||
"people-walk",
|
"people-walk",
|
||||||
@@ -405,8 +509,12 @@ class Redesign(unittest.TestCase):
|
|||||||
"travel-norway-pano",
|
"travel-norway-pano",
|
||||||
"travel-norway-valley",
|
"travel-norway-valley",
|
||||||
):
|
):
|
||||||
|
with self.subTest(slug=slug):
|
||||||
|
self.assertNotIn(slug, slugs)
|
||||||
for image in (SITE / "assets" / "img").glob(f"{slug}-*.webp"):
|
for image in (SITE / "assets" / "img").glob(f"{slug}-*.webp"):
|
||||||
self.fail(f"stale image built: {image.name}")
|
self.fail(f"stale image built: {image.name}")
|
||||||
|
for file in HTML_FILES:
|
||||||
|
self.assertNotIn(slug, read(file))
|
||||||
|
|
||||||
def test_wedding_crowd_photos_are_not_shipped(self):
|
def test_wedding_crowd_photos_are_not_shipped(self):
|
||||||
"""The operator asked to drop the wedding photos showing the guests."""
|
"""The operator asked to drop the wedding photos showing the guests."""
|
||||||
@@ -419,10 +527,29 @@ class Redesign(unittest.TestCase):
|
|||||||
for file in HTML_FILES:
|
for file in HTML_FILES:
|
||||||
self.assertNotIn(slug, read(file))
|
self.assertNotIn(slug, read(file))
|
||||||
|
|
||||||
def test_sitemap_lists_the_new_route_only(self):
|
def test_couple_photography_assets_are_gone_entirely(self):
|
||||||
|
"""Paare gehören zu dennyschulz.de — auch als Bild dürfen sie nicht mitlaufen.
|
||||||
|
|
||||||
|
``portrait-natural``, ``event-motion`` und ``event-dance`` zeigen Brautpaare
|
||||||
|
und sind deshalb aus dem Bildbestand entfernt, nicht nur von den Seiten.
|
||||||
|
"""
|
||||||
|
manifest = json.loads(read(SRC / "images.json"))
|
||||||
|
slugs = {entry["slug"] for entry in manifest["images"]}
|
||||||
|
for slug in ("portrait-natural", "event-motion", "event-dance"):
|
||||||
|
with self.subTest(slug=slug):
|
||||||
|
self.assertNotIn(slug, slugs)
|
||||||
|
self.assertEqual([], list((SITE / "assets" / "img").glob(f"{slug}-*.webp")))
|
||||||
|
for file in HTML_FILES:
|
||||||
|
self.assertNotIn(slug, read(file))
|
||||||
|
|
||||||
|
def test_sitemap_lists_the_new_routes_only(self):
|
||||||
sitemap = read(SITE / "sitemap.xml")
|
sitemap = read(SITE / "sitemap.xml")
|
||||||
self.assertIn("<loc>https://desfoto.de/shootings/</loc>", sitemap)
|
for route in ("fotografie", "familie", "musik-und-buehne"):
|
||||||
self.assertNotIn("<loc>https://desfoto.de/fotografie/</loc>", sitemap)
|
with self.subTest(route=route):
|
||||||
|
self.assertIn(f"<loc>https://desfoto.de/{route}/</loc>", sitemap)
|
||||||
|
for retired in ("shootings", "familien-und-paare"):
|
||||||
|
with self.subTest(route=retired):
|
||||||
|
self.assertNotIn(f"<loc>https://desfoto.de/{retired}/</loc>", sitemap)
|
||||||
|
|
||||||
def test_operator_logo_is_the_brand_mark(self):
|
def test_operator_logo_is_the_brand_mark(self):
|
||||||
self.assertTrue((SITE / "assets" / "img" / "logo-160.png").is_file())
|
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):
|
def test_mobile_studio_is_described_where_photo_work_is_sold(self):
|
||||||
needles = ("Mobiles Studio", "Autarke Blitzanlage", "autarker Blitzanlage")
|
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)
|
text = read(SITE / rel)
|
||||||
for needle in needles:
|
for needle in needles:
|
||||||
with self.subTest(file=rel, needle=needle):
|
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."""
|
"""Projects can also be photographed in the operator's own studio on site."""
|
||||||
for rel in (
|
for rel in (
|
||||||
"index.html",
|
"index.html",
|
||||||
"shootings/index.html",
|
"fotografie/index.html",
|
||||||
"businessfotografie/index.html",
|
"businessfotografie/index.html",
|
||||||
"ueber/index.html",
|
"ueber/index.html",
|
||||||
):
|
):
|
||||||
@@ -489,13 +616,20 @@ class Redesign(unittest.TestCase):
|
|||||||
"free-sparrow",
|
"free-sparrow",
|
||||||
"free-dogs-forest",
|
"free-dogs-forest",
|
||||||
),
|
),
|
||||||
"familien-und-paare": (
|
"familie": (
|
||||||
"couple-park",
|
"free-lake-person",
|
||||||
"couple-backlight-kiss",
|
"free-portrait-light",
|
||||||
"couple-forest-walk",
|
|
||||||
"couple-sun-silhouette",
|
|
||||||
"couple-laugh-walk",
|
|
||||||
"portrait-marina-snow",
|
"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"),
|
"portrait-und-model": ("editorial-studio-side", "editorial-balance", "editorial-leap"),
|
||||||
"businessfotografie": (
|
"businessfotografie": (
|
||||||
@@ -522,6 +656,16 @@ class Redesign(unittest.TestCase):
|
|||||||
with self.subTest(page="video", slug=slug):
|
with self.subTest(page="video", slug=slug):
|
||||||
self.assertIn(f"/assets/img/{slug}-1200.webp", video)
|
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"<h3>{step}</h3>", page)
|
||||||
|
self.assertIn("NWWFTf7l8g0", page)
|
||||||
|
|
||||||
def test_invented_japan_content_is_absent(self):
|
def test_invented_japan_content_is_absent(self):
|
||||||
for file in HTML_FILES:
|
for file in HTML_FILES:
|
||||||
text = read(file).lower()
|
text = read(file).lower()
|
||||||
@@ -601,11 +745,17 @@ class Release(unittest.TestCase):
|
|||||||
self.assertEqual(2, deploy.count("up -d --force-recreate --remove-orphans"))
|
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)
|
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")
|
verify = read(ROOT / ".ocauto" / "verify")
|
||||||
self.assertIn('check_redirect "$base/fotografie/" "$base/shootings/"', verify)
|
for check in (
|
||||||
self.assertIn("""check_not_contains "$base/" 'href="/fotografie/'""", verify)
|
'check_redirect "$base/shootings/" "$base/fotografie/"',
|
||||||
self.assertNotIn('/fotografie/ /businessfotografie/', verify)
|
'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)
|
self.assertNotIn("hero-studio-1200.webp", verify)
|
||||||
|
|
||||||
def test_deploy_hooks_are_executable(self):
|
def test_deploy_hooks_are_executable(self):
|
||||||
|
|||||||