feat: reposition desfoto as an independent brand with four Arbeitsbereiche

This commit is contained in:
desfoto automation
2026-09-19 19:14:42 +02:00
parent 96247da074
commit 68613788b8
86 changed files with 1287 additions and 756 deletions

View File

@@ -13,19 +13,22 @@ SITE = {
"url": "https://desfoto.de",
"claim": "Fotografie & Video",
"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",
"language": "de",
"theme_color": "#17140F",
"default_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."
"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."
),
"keywords": (
"Fotograf Neumünster, Videograf Neumünster, Shootings, Businessfotografie, "
"Fotograf Neumünster, Videograf Neumünster, Fotografie, Businessfotografie, "
"Businessportrait, Model-Shooting, Familienfotograf, Minishooting, mobiles Studio, "
"Blitzanlage, Musikvideo, Konzertfotografie, Social Media Inhalte, "
"Schleswig-Holstein"
"Blitzanlage, Musikvideo, Konzertfotografie, Bandfotografie, Musik & Bühne, "
"Social Media Inhalte, Schleswig-Holstein"
),
}
@@ -68,61 +71,72 @@ VIDEO = {
NAV = [
{
"label": "Shootings",
"href": "/shootings/",
"label": "Fotografie",
"href": "/fotografie/",
"children": [
("Businessfotografie", "/businessfotografie/"),
("Portrait & Model", "/portrait-und-model/"),
("Familien & Paare", "/familien-und-paare/"),
("Familie", "/familie/"),
("Minishootings", "/minishootings/"),
],
},
{"label": "Video", "href": "/video/"},
{"label": "Musik & Bühne", "href": "/musik-und-buehne/"},
{"label": "Projekte", "href": "/projekte/"},
{"label": "Über", "href": "/ueber/"},
]
# Die zwei Säulen der Arbeit: Foto (Shootings) und Video bleiben getrennt,
# damit beide Bereiche für sich verständlich bleiben.
PILLARS = [
# Die vier Arbeitsbereiche der Marke. Die Startseite erklärt desfoto darüber —
# erst der Bereich, dann die einzelnen buchbaren Leistungen darunter.
AREAS = [
{
"slug": "foto",
"label": "Foto",
"href": "/shootings/",
"title": "Shootings",
"lead": "Der Überbegriff für alle fotografischen Arbeiten.",
"slug": "fotografie",
"title": "Fotografie",
"scope": "Menschen, Unternehmen, Produkte, Veranstaltungen.",
"text": (
"Businessfotografie, Portrait & Model, Familien & Paare und Minishootings — "
"ein Shooting, ein Ablauf, ein Ansprechpartner. Du wählst nur, was du "
"wirklich brauchst."
"Portrait, Business, Familie und Minishootings — im Studio in Neumünster "
"oder mit dem mobilen Studio an deinem Ort."
),
"points": [
("Businessfotografie", "/businessfotografie/"),
("Portrait & Model", "/portrait-und-model/"),
("Familien & Paare", "/familien-und-paare/"),
("Minishootings", "/minishootings/"),
],
"image": "business-portrait-studio",
"fit": "50% 30%",
"href": "/fotografie/",
"image": "business-portrait-sit",
"fit": "50% 35%",
},
{
"slug": "video",
"label": "Video",
"href": "/video/",
"title": "Videoprojekte",
"lead": "Bewegtbild von der Idee bis zum fertigen Schnitt.",
"title": "Video",
"scope": "Unternehmen, Musik, Events, Social Content.",
"text": (
"Musikvideo, Imagefilm, Eventfilm und Formate für Social Media — "
"gedreht, geschnitten und ausgeliefert in dem Format, in dem du es "
"veröffentlichst."
"Musikvideo, Imagefilm, Recruiting und Eventnachbericht — von der Idee "
"über den Dreh bis zum fertigen Export."
),
"points": [
("Videoprojekte", "/video/"),
("Social Media", "/social-media/"),
],
"image": "studio-lens",
"href": "/video/",
"image": "movement-light",
"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.
@@ -158,7 +172,7 @@ STUDIO = {
{
"title": "Studio Neumünster",
"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"
" Konzeptarbeit. Gut für Portraits, Businessbilder und alles, was"
" 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.",
},
],
"image": "studio-setup",
"image": "studio-lens",
}
# Netzwerk: eigene Projekte des Betreibers (ausdrücklicher Wunsch des Betreibers).
@@ -209,7 +223,7 @@ NETWORK = [
"label": "dennyschulz.de",
"href": "https://www.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",
@@ -244,14 +258,14 @@ SERVICES = [
"fit": "50% 30%",
},
{
"slug": "familien-und-paare",
"href": "/familien-und-paare/",
"title": "Familien & Paare",
"slug": "familie",
"href": "/familie/",
"title": "Familie",
"short": (
"Familienshootings und Paarportraits, die nicht gestellt wirken. "
"Zeit lassen, mitgehen, im richtigen Moment auslösen."
"Familienshootings, die nicht gestellt wirken. Zeit lassen, mitgehen, "
"im richtigen Moment auslösen."
),
"image": "people-joy",
"image": "free-portrait-light",
"fit": "50% 30%",
},
{
@@ -262,7 +276,7 @@ SERVICES = [
"Die kurze Session für alle, die schnell ein gutes Bild brauchen: "
"klarer Ablauf, kleine Auswahl, kurze Lieferzeit."
),
"image": "portrait-natural",
"image": "free-modern-portrait",
"fit": "50% 30%",
},
{
@@ -284,39 +298,39 @@ SERVICES = [
"Foto- und Videocontent im Format deiner Kanäle — wiederkehrend, planbar "
"und über die Monate hinweg einheitlich."
),
"image": "event-motion",
"image": "event-speaker",
"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 = [
{
"title": "Shootings",
"title": "Menschen",
"text": (
"Ein Überbegriff, vier Wege: Businessfotografie für Teams und Marken, "
"Portrait & Model für die persönliche Bildsprache, Familien & Paare für "
"die Menschen, die zu dir gehören, und Minishootings für alle, die schnell "
"ein gutes Bild brauchen."
"Portrait, Bewerbungsbilder, Familie und Model — im Studio in Neumünster, "
"an einem Ort deiner Wahl oder dort, wo dein Alltag stattfindet."
),
"image": "editorial-pose",
"fit": "50% 30%",
},
{
"title": "Studio & mobil",
"title": "Unternehmen & Marken",
"text": (
"Shootings im eigenen Studio in Neumünster oder mit dem mobilen Studio "
"inklusive autarker Blitzanlage, Hintergründen und Lichtformern — "
"aufgebaut an deinem Ort, statt umgekehrt."
"Team- und Standortbilder, Produkte und Kampagnenmotive — abgestimmt auf "
"die Flächen, auf denen sie später stehen."
),
"image": "business-portrait-sit",
"fit": "50% 30%",
},
{
"title": "Anlässe",
"title": "Veranstaltungen",
"text": (
"Feiern, Events, Vereinsleben und Premieren. Dokumentarisch begleitet, "
"ohne den Ablauf auszubremsen."
),
"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%"),
("editorial-colour", "Model-Shooting im Studio", "g-w3", "3x2", "50% 30%"),
("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-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 = [
@@ -387,11 +401,12 @@ FACTS = [
]
FOCUS_ITEMS = [
("Shootings", "/shootings/"),
("Fotografie", "/fotografie/"),
("Businessfotografie", "/businessfotografie/"),
("Portrait & Model", "/portrait-und-model/"),
("Familien & Paare", "/familien-und-paare/"),
("Familie", "/familie/"),
("Minishootings", "/minishootings/"),
("Videoprojekte", "/video/"),
("Video", "/video/"),
("Musik & Bühne", "/musik-und-buehne/"),
("Social Media", "/social-media/"),
]

View File

@@ -9,12 +9,6 @@
"alt": "Kameraobjektiv im Studiolicht",
"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",
"raw": "1778949186414_b1648d20_dennyschulz_-05400-2.jpg",
@@ -75,36 +69,6 @@
"alt": "Fashion-Portrait in kräftigen Farben",
"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",
"raw": "1784293225986_a37d21f4_dennyschulz_-07139.jpg",
@@ -117,12 +81,6 @@
"alt": "Veranstaltungsort mit Gästen",
"focus": "50% 45%"
},
{
"slug": "event-dance",
"raw": "1784293226489_fa1b9420_dennyschulz_-09046.jpg",
"alt": "Tanzfläche bei einer Feier",
"focus": "50% 40%"
},
{
"slug": "movement-light",
"raw": "1788850842323_a5ae410e_280826_shooting-69.jpg",
@@ -353,41 +311,6 @@
"alt": "Marta springt mit angewinkelten Beinen",
"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",
"source": "legacy",

View File

@@ -199,23 +199,29 @@ def footer_html() -> str:
{BRAND_MARK}
<span class="brand__text"><span>desfoto</span><small>{e(SITE['claim'])}</small></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>
<h2>Shootings</h2>
<h2>Fotografie</h2>
<div class="footer__links">
<a href="/shootings/">Überblick Shootings</a>
<a href="/fotografie/">Überblick Fotografie</a>
<a href="/businessfotografie/">Businessfotografie</a>
<a href="/portrait-und-model/">Portrait &amp; Model</a>
<a href="/familien-und-paare/">Familien &amp; Paare</a>
<a href="/familie/">Familie</a>
<a href="/minishootings/">Minishootings</a>
</div>
</div>
<div>
<h2>Video &amp; mehr</h2>
<h2>Video &amp; Bühne</h2>
<div class="footer__links">
<a href="/video/">Videoprojekte</a>
<a href="/musik-und-buehne/">Musik &amp; Bühne</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="/ueber/">Über desfoto</a>
</div>
@@ -240,7 +246,7 @@ def footer_html() -> str:
<a href="/datenschutz/">Datenschutz</a>
<a href="/kontakt/">Kontakt</a>
</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>
</footer>
@@ -296,17 +302,20 @@ def page_head(
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 ""
# 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">
<div class="page-head__bg">
{picture(image, "100vw", css_class="", priority=True, loading="eager", alt="", fit=fit)}
</div>
<div class="shell page-head__inner">
{crumb}
<span class="kicker">{e(kicker)}</span>
{crumb_line} <span class="kicker">{e(kicker)}</span>
<h1 class="balance">{e(title)}</h1>
<p class="page-head__lead">{lead}</p>
{actions_html}
</div>
{actions_line} </div>
</section>"""

View File

@@ -7,12 +7,12 @@ canonical paths are returned by the ROUTES table at the bottom of the module.
from __future__ import annotations
from content import (
AREAS,
CONTACT,
FACTS,
LEGAL,
NETWORK,
PHOTO_OVERVIEW,
PILLARS,
PROCESS,
SERVICES,
SHOOTING_STEPS,
@@ -81,6 +81,9 @@ def video_block(*, dark: bool = True, with_meta: bool = True) -> str:
if with_meta
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">
<button class="video-facade" type="button" data-video-id="{e(VIDEO['id'])}"
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>
</button>
{meta}
</div>
{meta_line}</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>"""
@@ -146,39 +148,27 @@ def caption_gallery(
return f'<div class="gallery" data-stagger="{stagger}">{figures}</div>'
def pillar_cards() -> str:
"""The two big entry cards: Shootings (photo) and Videoprojekte (video).
def area_cards() -> str:
"""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.
``.pillar__go`` stretches over the card and keeps the whole surface clickable.
"""
cards = []
for pillar in PILLARS:
points = "".join(
f'<a href="{e(href)}">{e(label)}</a>' for label, href in pillar["points"]
)
for area in AREAS:
cards.append(
'<article class="pillar spot reveal">'
'<span class="pillar__media">'
+ picture(
pillar["image"],
"(min-width: 62rem) 46vw, 92vw",
css_class="",
fit=pillar.get("fit"),
alt="",
)
+ "</span>"
'<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>"
'<article class="card reveal">'
f'<div>{picture(area["image"], "(min-width: 72rem) 22vw, (min-width: 40rem) 44vw, 92vw", css_class="frame frame--16x10", fit=area.get("fit"))}</div>'
f'<h3 class="card__title">{e(area["title"])}</h3>'
f'<p class="eyebrow">{e(area["scope"])}</p>'
f'<p class="card__text">{e(area["text"])}</p>'
f'<div class="card__foot"><a class="link-arrow" href="{e(area["href"])}">'
f'{e(area["title"])} ansehen {ICONS["arrow"]}</a></div>'
"</article>"
)
return '<div class="pillars" data-stagger="0.12">' + "".join(cards) + "</div>"
return '<div class="grid grid--areas" data-stagger="0.08">' + "".join(cards) + "</div>"
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",
css_class="frame frame--4x5",
fit="50% 45%",
alt="Studioequipment mit Logo von Denny Schulz Fotografie",
)
+ '<span class="studio__badge"><strong>Autarke Blitzanlage</strong>'
"<span>Hintergründe, Blitze, Lichtformer</span></span>"
@@ -250,9 +239,10 @@ def contact_form() -> str:
<select id="cf-topic" name="topic">
<option>Businessfotografie</option>
<option>Portrait &amp; Model</option>
<option>Familien &amp; Paare</option>
<option>Familie</option>
<option>Minishooting</option>
<option>Videoprojekt</option>
<option>Video</option>
<option>Musik &amp; Bühne</option>
<option>Social-Media-Inhalte</option>
<option>Pro-bono-Anfrage</option>
<option>Etwas anderes</option>
@@ -331,19 +321,19 @@ def home() -> str:
<span class="kicker hero__kicker">{e(SITE['tagline'])}</span>
<h1 class="hero__title">{words('Bilder von Menschen, Marken und Musik.')}</h1>
<p class="hero__sub">
desfoto ist die fotografische und filmische Arbeit von Denny Schulz aus Neumünster:
Shootings für Business, Portrait und Familie, dazu Konzerte, eigene Projekte,
Video und Inhalte für Social Media.
desfoto ist Fotografie, Video und mediale Produktion aus Neumünster:
Bilder für Menschen und Unternehmen, Bewegtbild für Marken, Musik und Events,
Bühnenarbeiten und eigene Projekte.
</p>
<div class="btn-row hero__actions">
<a class="btn btn--solid" href="/kontakt/">Projekt anfragen</a>
<a class="btn btn--outline-light" href="/projekte/">Arbeiten ansehen</a>
</div>
<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 &amp; vor Ort</strong>Eigenes Studio in Neumünster oder mobiles Studio inklusive autarker Blitzanlage bei dir.</div>
<div class="hero__fact"><strong>Foto &amp; Video</strong>Beides aus einer Hand, damit Optik und Ton zusammenpassen.</div>
<div class="hero__fact"><strong>Musik &amp; 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>
<span class="scroll-cue" aria-hidden="true">Scroll<i></i></span>
@@ -361,22 +351,29 @@ def home() -> str:
</div>""",
)
pillars = section(
kicker="Zwei Bereiche",
title="Shootings und Videoprojekte",
lead="Foto und Video bleiben getrennt, damit beide Bereiche für sich verständlich sind — und kommen trotzdem von derselben Person.",
content=pillar_cards(),
areas = section(
kicker="Arbeitsbereiche",
title="Fotografie, Video, Musik & Bühne und Projekte",
lead="Vier Bereiche, in denen ich arbeite. Was du davon buchen kannst, steht direkt darunter — die freien Arbeiten laufen daneben weiter.",
content=area_cards(),
)
bookable = section(
variant="paper2",
kicker="Buchen",
title="Was du bei mir buchen kannst",
lead="Auftragsarbeiten mit klarem Umfang: Businessfotografie, Portrait & Model, Familie, Minishootings, Video und Social-Media-Inhalte.",
content=service_cards(),
)
works = section(
variant="paper2",
kicker="Portfolio",
title="Musik, Menschen und freie Arbeiten",
lead="Konzerte und Bands, Portraits und Business, dazu eigene Strecken aus Landschaft und Tierwelt. Das ist die Bandbreite meiner Arbeit.",
title="Was ich außerdem mache",
lead="Freie Arbeiten, Musik & Bühne und pro bono — Projekte, die nicht jede Woche auf einer Rechnung stehen. Hier muss kein Bild ein Angebot verkaufen.",
content=work_gallery()
+ '<div class="btn-row" style="margin-top:2rem">'
'<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 &amp; Bühne</a></div>',
)
video_sec = f"""<section class="section section--ink">
@@ -422,7 +419,6 @@ def home() -> str:
"@context": "https://schema.org",
"@type": "ProfessionalService",
"name": SITE["brand"],
"alternateName": LEGAL["business"],
"description": SITE["default_description"],
"url": SITE["url"] + "/",
"image": SITE["url"] + "/assets/img/og-desfoto.jpg",
@@ -442,19 +438,23 @@ def home() -> str:
],
body=hero
+ statement
+ pillars
+ areas
+ marquee(
[
"Fotografie",
"Video",
"Musik & Bühne",
"Projekte",
"Businessfotografie",
"Portrait & Model",
"Familien & Paare",
"Familie",
"Minishootings",
"Mobiles Studio",
"Videoprojekte",
"Musikvideo",
"Social Media",
]
)
+ bookable
+ works
+ video_sec
+ studio
@@ -464,15 +464,15 @@ def home() -> str:
)
def shootings() -> str:
path = "/shootings/"
def fotografie() -> str:
path = "/fotografie/"
overview = "".join(
'<article class="card reveal"><div>'
+ picture(
item["image"],
"(min-width: 62rem) 30vw, 92vw",
css_class="frame frame--4x5",
fit="50% 30%",
fit=item.get("fit"),
alt=item["title"],
)
+ f'</div><h3>{e(item["title"])}</h3><p class="card__text">{e(item["text"])}</p></article>'
@@ -480,35 +480,35 @@ def shootings() -> str:
)
body = (
page_head(
kicker="Shootings",
title="Shootings — von Business bis Familie",
lead="Alles, was ich fotografisch mache, läuft unter dem Begriff Shooting: vier Schwerpunkte, ein Ablauf, ein Ansprechpartner.",
kicker="Fotografie",
title="Fotografie — Menschen, Unternehmen, Produkte, Veranstaltungen",
lead="Alles, was ich fotografisch mache: drei Themen, vier Schwerpunkte, ein Ablauf und ein Ansprechpartner — im Studio in Neumünster oder mit dem mobilen Studio bei dir.",
image="editorial-earring",
breadcrumb=("Shootings", path),
breadcrumb=("Fotografie", path),
fit="45% 30%",
actions='<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>',
)
+ section(
kicker="Überblick",
title="Ein Überbegriff, vier Wege",
title="Drei Themen, ein Blick",
lead="Du musst dich nicht für eine Schublade entscheiden — erzähl einfach, worum es geht.",
content=f'<div class="grid grid--3" data-stagger="0.09">{overview}</div>',
)
+ section(
variant="paper2",
kicker="Schwerpunkte",
title="Die vier Shootings im Detail",
title="Die vier buchbaren Schwerpunkte",
content=service_cards(
["businessfotografie", "portrait-und-model", "familien-und-paare", "minishootings"]
["businessfotografie", "portrait-und-model", "familie", "minishootings"]
),
tight=True,
)
+ studio_section(variant="")
+ section(
kicker="Ablauf",
title="So läuft ein Shooting",
lead="Bei jedem Shooting derselbe Weg — vom ersten Satz bis zur fertigen Datei.",
title="So läuft ein Termin",
lead="Bei jedem Auftrag derselbe Weg — vom ersten Satz bis zur fertigen Datei.",
content='<div class="steps">'
+ "".join(
f'<div class="step reveal"><h3>{e(step["title"])}</h3><p>{e(step["text"])}</p></div>'
@@ -519,16 +519,16 @@ def shootings() -> str:
+ section(
variant="paper2",
kicker="Beispiele",
title="Aus Shootings und Aufträgen",
title="Aus Aufträgen und eigener Arbeit",
content=media_grid(
[
("business-portrait-dark", "Portrait eines Mannes vor dunklem Hintergrund", "4x5", "50% 28%"),
("editorial-colour", "Fashion-Portrait in kräftigen Farben", "4x5", "50% 30%"),
("portrait-couple", "Paarportrait im Grünen", "4x5", "50% 30%"),
("editorial-balance", "Editorial-Pose mit ausbalancierter Haltung", "4x5", "50% 40%"),
("portrait-marina-sunset", "Silhouette einer Frau vor orangefarbenem Abendhimmel", "4x5", "50% 40%"),
("business-portrait-stool", "Sitzendes Portrait im weichen Studiolicht", "3x2", "50% 35%"),
("business-hotel-window", "Drei Frauen in Bademänteln und Handtuchturbanen am Fenster", "3x2", "50% 40%"),
("portrait-natural", "Natürliches Portrait im Freien", "3x2", "50% 32%"),
("couple-backlight-kiss", "Sara und Carlos küssen sich im Gegenlicht", "1x1", "50% 45%"),
("portrait-marina-snow", "Marina mit roten Zöpfen auf einem verschneiten Waldweg", "3x2", "50% 30%"),
("free-woman-coast", "Eine Frau mit Hut steht an einer felsigen Küste", "1x1", "50% 40%"),
("editorial-leap", "Marta springt mit angewinkelten Beinen", "1x1", "50% 35%"),
("free-dog-husky", "Ein Hund steht auf einem sonnigen Waldweg", "1x1", "50% 45%"),
]
@@ -558,22 +558,22 @@ def shootings() -> str:
),
)
+ cta_band(
title="Welches Shooting passt zu dir?",
title="Welcher Bereich passt zu dir?",
text="Schreib kurz, worum es geht und wann es stattfinden soll. Du bekommst Ablauf, Umfang und Preis.",
secondary=("Minishootings ansehen", "/minishootings/"),
)
)
return page(
title="Shootings in Neumünster · desfoto",
description="Shootings aus Neumünster: Businessfotografie, Portrait & Model, Familien & Paare und Minishootings — mit mobilem Studio inklusive autarker Blitzanlage.",
title="Fotografie in Neumünster · desfoto",
description="Fotografie aus Neumünster: Businessfotografie, Portrait & Model, Familie und Minishootings — mit mobilem Studio inklusive autarker Blitzanlage.",
path=path,
json_ld=[
service_ld(
"Shootings",
"Fotografie",
path,
"Fotografische Shootings für Business, Portrait & Model, Familien und Paare.",
"Fotografie für Menschen, Unternehmen, Produkte und Veranstaltungen.",
),
breadcrumb_ld("Shootings", path),
breadcrumb_ld("Fotografie", path),
],
body=body,
)
@@ -655,7 +655,7 @@ def businessfotografie() -> str:
("business-hotel-window", "Drei Frauen in Bademänteln und Handtuchturbanen am Fenster", "4x5", "50% 40%"),
("business-hotel-tea", "Drei Frauen mit Teetassen und Handtuchturbanen", "4x5", "50% 45%"),
("gear-cases", "Kamerakoffer und Objektive vor einem Studiorack", "3x2", "50% 50%"),
("studio-setup", "Studioequipment mit Logo von Denny Schulz Fotografie", "3x2", "50% 45%"),
("business-portrait-sit", "Sitzendes Portrait mit Kameratasche im Studio", "3x2", "50% 35%"),
("business-hotel-orange", "Frauen im Bademantel halten Orangenscheiben vor ihre Gesichter", "3x2", "50% 50%"),
("business-hotel-laugh", "Drei Frauen lachen mit Orangenscheiben in den Händen", "3x2", "50% 45%"),
],
@@ -765,8 +765,8 @@ def portrait_und_model() -> str:
("editorial-studio-side", "50% 30%"),
("editorial-balance", "50% 40%"),
("editorial-leap", "50% 35%"),
("portrait-natural", "50% 30%"),
("people-kiss", "50% 32%"),
("free-portrait-light", "50% 30%"),
("free-modern-portrait", "50% 30%"),
]
)
+ "</div>",
@@ -789,21 +789,21 @@ def portrait_und_model() -> str:
)
def familien_und_paare() -> str:
path = "/familien-und-paare/"
def familie() -> str:
path = "/familie/"
body = (
page_head(
kicker="Familien & Paare",
kicker="Familie",
title="Wachsendes festhalten, ohne Pose",
lead="Familienshootings und Paarportraits, die nicht gestellt wirken. Zeit lassen, mitgehen und im richtigen Moment auslösen.",
image="people-joy",
breadcrumb=("Familien & Paare", path),
fit="50% 30%",
lead="Familienshootings, die nicht gestellt wirken. Zeit lassen, mitgehen und im richtigen Moment auslösen.",
image="free-portrait-light",
breadcrumb=("Familie", path),
fit="50% 28%",
actions='<a class="btn btn--solid" href="/kontakt/">Termin anfragen</a>',
)
+ section(
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">'
+ "".join(
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",
"Alle zusammen, ohne dass es nach Fototermin aussieht. Auch mit mehreren Generationen.",
),
(
"Paarshooting",
"Zwei Menschen, die sich nah sind. Vor der Hochzeit, zum Jahrestag oder einfach so.",
),
(
"Verlobung & Ja-Wort",
"Kleine Zeremonien, Standesamt, freie Trauung — begleitend oder als Portraitstrecke.",
),
(
"Jubiläum & Familientreffen",
"Wenn die ganze Verwandtschaft zusammenkommt, lohnt sich ein gemeinsames Bild.",
),
(
"Generationenbilder",
"Großeltern, Eltern, Enkel: Bilder, die in Jahren mehr wert sind als heute.",
@@ -832,6 +820,18 @@ def familien_und_paare() -> str:
"Kinderportraits",
"Kurz, spielerisch und geduldig — lieber zehn ehrliche Bilder als eine Stunde Stillhalten.",
),
(
"Geschwister & Patchwork",
"Nicht jede Familie sieht gleich aus. Gezeigt wird, wer zu euch gehört.",
),
(
"Jubiläum & Familientreffen",
"Wenn die ganze Verwandtschaft zusammenkommt, lohnt sich ein gemeinsames Bild.",
),
(
"Bei euch oder im Studio",
"Zuhause, im Park oder im Studio in Neumünster: dort, wo ihr euch wohlfühlt.",
),
]
)
+ "</div>",
@@ -845,20 +845,16 @@ def familien_und_paare() -> str:
)
+ section(
kicker="Beispiele",
title="Paare und Menschen",
title="Menschen aus meiner Arbeit",
lead="Familienshootings laufen ruhig und ohne Regieanweisungen ab. Genau so entstehen die Portraits, die ich hier zeige.",
content='<div class="grid grid--3">'
+ "".join(
picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit)
for slug, fit in [
("portrait-natural", "50% 30%"),
("people-kiss", "50% 32%"),
("portrait-couple", "50% 30%"),
("couple-park", "50% 30%"),
("couple-backlight-kiss", "50% 45%"),
("couple-forest-walk", "50% 35%"),
("couple-laugh-walk", "50% 35%"),
("couple-sun-silhouette", "50% 50%"),
("free-lake-person", "50% 45%"),
("portrait-marina-snow", "50% 30%"),
("free-modern-portrait", "50% 30%"),
("free-woman-coast", "50% 40%"),
("portrait-marina-sunset", "50% 45%"),
]
)
@@ -871,12 +867,16 @@ def familien_und_paare() -> str:
)
)
return page(
title="Familien- & Paarfotografie in Neumünster · desfoto",
description="Familien- und Paarshootings aus Neumünster: natürliche Bilder ohne Pose, im Studio, in der Natur oder bei euch zuhause.",
title="Familienfotografie in Neumünster · desfoto",
description="Familienshootings aus Neumünster: natürliche Bilder ohne Pose, im Studio, in der Natur oder bei euch zuhause.",
path=path,
json_ld=[
service_ld("Familien- und Paarfotografie", path, "Familienshootings und Paarportraits ohne gestellte Posen."),
breadcrumb_ld("Familien & Paare", path),
service_ld(
"Familienfotografie",
path,
"Familienshootings ohne gestellte Posen, im Studio, in der Natur oder zuhause.",
),
breadcrumb_ld("Familie", path),
],
body=body,
)
@@ -889,7 +889,7 @@ def minishootings() -> str:
kicker="Minishootings",
title="Kurze Session, sofort brauchbares Bild",
lead="Der Termin für alle, die schnell ein gutes Bild brauchen: klarer Ablauf, kleine Auswahl, kurze Lieferzeit. Meist in rund 20 bis 30 Minuten erledigt.",
image="portrait-natural",
image="free-modern-portrait",
breadcrumb=("Minishootings", path),
fit="50% 30%",
actions='<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>'
for t, d in [
("Profilbild", "Für LinkedIn, Bewerbung, Vereinsseite oder die Website."),
("Paar & Familie", "Ein, zwei schöne Bilder zusammen — ohne großen Termin."),
("Familie", "Eltern und Kinder, ohne großen Termin — ein, zwei schöne Bilder zusammen."),
("Social Media", "Frisches Material für deine Kanäle, passend zum Format."),
("Verein & Mannschaft", "Schnell alle Gesichter für Mannschaftsfotos und Anmeldungen."),
("Businesskurzportrait", "Du brauchst nur ein Bild für die neue Stelle oder Präsentation."),
("Geschenkidee", "Gutschein für ein gemeinsames Shooting — auf Anfrage."),
("Geschenkidee", "Gutschein für ein Shooting — auf Anfrage."),
]
)
+ "</div>",
@@ -958,11 +958,11 @@ def minishootings() -> str:
+ "".join(
picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit)
for slug, fit in [
("portrait-natural", "50% 28%"),
("free-woman-coast", "50% 30%"),
("business-portrait-studio", "50% 28%"),
("editorial-black", "50% 28%"),
("editorial-studio-side", "50% 30%"),
("free-modern-portrait", "50% 30%"),
("free-portrait-light", "50% 30%"),
("portrait-marina-snow", "50% 30%"),
]
)
@@ -1006,6 +1006,7 @@ def video() -> str:
<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="/musik-und-buehne/">Musik &amp; Bühne {ICONS['arrow']}</a>
</div>
<div style="margin-top:clamp(1.6rem,3vw,2.6rem)">{video_block()}</div>
</div>
@@ -1102,7 +1103,7 @@ def video() -> str:
content=caption_gallery(
[
("studio-lens", "Brennweite und Licht im Studio", "g-w3", "3x2", "50% 50%"),
("event-motion", "Bewegung im Bild", "g-w3", "3x2", "50% 45%"),
("band-extinct-group", "Band vor der Kamera", "g-w3", "3x2", "50% 40%"),
("movement-light", "Lange Belichtung", "g-w2", "1x1", "50% 50%"),
("event-speaker", "Statement vor der Kamera", "g-w2", "1x1", "50% 30%"),
("event-venue", "Location vor dem Dreh", "g-w2", "1x1", "50% 45%"),
@@ -1129,6 +1130,116 @@ def video() -> str:
)
def musik_und_buehne() -> str:
path = "/musik-und-buehne/"
body = (
page_head(
kicker="Musik & Bühne",
title="Bands, Künstler und Konzerte",
lead="Live, Bandportraits und Musikvideo — als Musiker kenne ich beide Seiten der Bühne.",
image="music-vocal-blue",
breadcrumb=("Musik & Bühne", path),
fit="50% 30%",
actions='<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:
path = "/social-media/"
body = (
@@ -1136,7 +1247,7 @@ def social_media() -> str:
kicker="Social Media",
title="Content, der regelmäßig kommt",
lead="Foto- und Videomaterial im Format deiner Kanäle — wiederkehrend, planbar und über die Monate hinweg einheitlich.",
image="event-motion",
image="event-speaker",
breadcrumb=("Social Media", path),
fit="50% 42%",
actions='<a class="btn btn--solid" href="/kontakt/">Content anfragen</a>',
@@ -1214,9 +1325,9 @@ def social_media() -> str:
+ "".join(
picture(slug, "(min-width: 62rem) 30vw, 92vw", css_class="frame frame--4x5", fit=fit)
for slug, fit in [
("event-motion", "50% 45%"),
("event-dance", "50% 36%"),
("event-speaker", "50% 30%"),
("event-venue", "50% 45%"),
("free-modern-portrait", "50% 40%"),
("music-dreadlocks", "50% 35%"),
("business-hotel-tea", "50% 45%"),
("business-hotel-laugh", "50% 45%"),
("music-drummer", "50% 45%"),
@@ -1274,7 +1385,7 @@ def projekte() -> str:
]
)
+ '<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 &amp; Bühne '
+ ICONS["arrow"]
+ "</a></div>",
)
@@ -1343,7 +1454,7 @@ def projekte() -> str:
+ "".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>'
for t, d, h in [
("Shootings", "Business, Portrait, Familie und Minishootings.", "/shootings/"),
("Fotografie", "Menschen, Unternehmen, Produkte und Veranstaltungen.", "/fotografie/"),
("Businessfotografie", "Portraits, Teams und Produkte für Unternehmen.", "/businessfotografie/"),
("Videoprojekte", "Musikvideo, Imagefilm und Eventnachbericht.", "/video/"),
]
@@ -1367,8 +1478,8 @@ def ueber() -> str:
page_head(
kicker="Über",
title="Fotograf und Videograf aus Neumünster",
lead="Ich bin Denny Schulz. Ich fotografiere Menschen, Marken und Musik — und produziere seit einigen Jahren genauso gern Bewegtbild.",
image="studio-setup",
lead="Hinter desfoto stehe ich, Denny Schulz. Fotograf, Videograf und Musiker aus Neumünster.",
image="gear-cases",
breadcrumb=("Über", path),
fit="50% 45%",
actions='<a class="btn btn--solid" href="/kontakt/">Schreib mir</a>',
@@ -1398,7 +1509,7 @@ def ueber() -> str:
for t, d in [
(
"Menschen fotografieren",
"Portrait, Business, Model, Familie und Paar. Die Arbeit an Gesichtern, Licht und Ausdruck ist mein Kern.",
"Portrait, Business, Model und Familie. Die Arbeit an Gesichtern, Licht und Ausdruck ist mein Kern.",
),
(
"Musik & Bühne begleiten",
@@ -1443,9 +1554,9 @@ def ueber() -> str:
content=media_grid(
[
("business-portrait-studio", "Mann im weißen Hemd vor dunklem Hintergrund, Softbox im Bild", "4x5", "50% 28%"),
("studio-setup", "Studioequipment mit Logo von Denny Schulz Fotografie", "4x5", "50% 45%"),
("studio-lens", "Kameraobjektiv im Studiolicht", "4x5", "50% 45%"),
("business-hotel-window", "Drei Frauen in Bademänteln und Handtuchturbanen am Fenster", "4x5", "50% 40%"),
("couple-park", "Sara und Carlos stehen Arm in Arm im Grünen", "4x5", "50% 30%"),
("portrait-marina-snow", "Marina mit roten Zöpfen auf einem verschneiten Waldweg", "4x5", "50% 30%"),
("free-fox", "Nahaufnahme eines Rotfuchses", "1x1", "50% 45%"),
],
columns="grid--2",
@@ -1455,7 +1566,7 @@ def ueber() -> str:
variant="paper2",
kicker="Netzwerk",
title="Wo ich sonst zu finden bin",
lead="Zwei eigene Projekte, die zu desfoto gehören.",
lead="Zwei eigene Projekte des Betreibers.",
content='<div class="grid grid--2">'
+ "".join(
'<article class="card reveal">'
@@ -1484,7 +1595,7 @@ def ueber() -> str:
)
return page(
title="Über desfoto · Denny Schulz, Neumünster",
description="desfoto ist die Foto- und Videoproduktion von Denny Schulz aus Neumünster. Arbeitsweise, mobiles Studio, Netzwerk und Kontaktmöglichkeiten.",
description="desfoto steht für Fotografie, Video und mediale Produktionen von Denny Schulz aus Neumünster: Arbeitsweise, mobiles Studio, Musik & Bühne, Netzwerk und Kontakt.",
path=path,
json_ld=[
breadcrumb_ld("Über", path),
@@ -1494,7 +1605,7 @@ def ueber() -> str:
"name": LEGAL["owner"],
"jobTitle": "Fotograf und Videograf",
"url": SITE["url"] + path,
"worksFor": {"@type": "Organization", "name": LEGAL["business"], "url": SITE["url"]},
"worksFor": {"@type": "Organization", "name": SITE["brand"], "url": SITE["url"] + "/"},
"sameAs": [item["href"] for item in NETWORK],
"address": {
"@type": "PostalAddress",
@@ -1576,7 +1687,7 @@ def kontakt() -> str:
title="Lieber direkt telefonieren?",
text="Kein Problem. Ruf einfach an — falls ich gerade im Termin bin, melde ich mich zurück.",
primary=("Anrufen", "tel:+491754083133"),
secondary=("E-Mail schreiben", "mailto:info@dennyschulz.de"),
secondary=("E-Mail schreiben", f"mailto:{CONTACT['email']}"),
)
)
return page(
@@ -1831,12 +1942,13 @@ def not_found() -> str:
ROUTES = [
("/", "index.html", home, None, "1.0"),
("/shootings/", "shootings/index.html", shootings, None, "0.9"),
("/fotografie/", "fotografie/index.html", fotografie, None, "0.9"),
("/businessfotografie/", "businessfotografie/index.html", businessfotografie, None, "0.9"),
("/portrait-und-model/", "portrait-und-model/index.html", portrait_und_model, None, "0.9"),
("/familien-und-paare/", "familien-und-paare/index.html", familien_und_paare, None, "0.9"),
("/familie/", "familie/index.html", familie, None, "0.9"),
("/minishootings/", "minishootings/index.html", minishootings, None, "0.9"),
("/video/", "video/index.html", video, None, "0.9"),
("/musik-und-buehne/", "musik-und-buehne/index.html", musik_und_buehne, None, "0.9"),
("/social-media/", "social-media/index.html", social_media, None, "0.9"),
("/projekte/", "projekte/index.html", projekte, None, "0.8"),
("/ueber/", "ueber/index.html", ueber, None, "0.8"),

View File

@@ -204,6 +204,11 @@ img.frame{width:100%;height:auto;object-fit:cover;transition:transform .8s var(-
.grid--2{grid-template-columns:repeat(auto-fit,minmax(19rem,1fr))}
.grid--3{grid-template-columns:repeat(auto-fit,minmax(16.5rem,1fr))}
.grid--4{grid-template-columns:repeat(auto-fit,minmax(13.5rem,1fr))}
/* Vier Arbeitsbereiche: vier Spalten auf großen, zwei auf mittleren und eine
auf kleinen Schirmen. auto-fit würde bei mittlerer Breite 3+1 Karten setzen. */
.grid--areas{grid-template-columns:1fr}
@media (min-width:40rem){.grid--areas{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:72rem){.grid--areas{grid-template-columns:repeat(4,minmax(0,1fr))}}
.card{display:flex;flex-direction:column;gap:1rem;height:100%}
.card__title{display:flex;align-items:baseline;gap:.6rem}
.card__num{font-size:.72rem;font-weight:600;letter-spacing:.16em;color:var(--accent)}