/* Öffentliche Galerie — vollständiges Stylesheet.
 *
 * Ersetzt das frühere Tailwind vom CDN. Zwei Gründe: Auf einer Kundendomain
 * ist ein Fremd-CDN ein Datenschutz- und ein Ladezeit-Thema, und das CDN-Build
 * von Tailwind übersetzt Klassen erst im Browser — also nach dem ersten
 * Seitenaufbau, sichtbar als Umspringen.
 *
 * ALLE Farben, Schriften und Radien kommen aus den CSS-Custom-Properties, die
 * der Server aus dem Markenprofil in den <head> schreibt. In dieser Datei darf
 * keine Markenfarbe fest stehen — sonst ließe sie sich vom Händler nicht ändern.
 *
 * Ausgenommen sind bewusst die Farben ÜBER Bildern (Hero-Text, Lightbox,
 * Kopier-Buttons): Sie liegen auf unbekanntem Bildmaterial und müssen dort
 * lesbar bleiben, unabhängig vom Theme.
 */

*, *::before, *::after { box-sizing: border-box; }

body {
	margin: 0;
	font-family: var(--brand-font-body);
	background: var(--brand-bg);
	color: var(--brand-text);
	-webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
	font-family: var(--brand-font-head);
	letter-spacing: var(--brand-tracking);
	margin: 0;
}

img, video { display: block; max-width: 100%; }
button { font: inherit; border: 0; background: none; }

.g-hidden { display: none !important; }
/* Abstand oben und unten um die Blockfolge. Seitlich NICHT — randlose Blöcke
   (fullbleed, hero) müssen bis an den Rand laufen können; die Innenabstände
   setzt jeder Block über `.blk-inner` selbst. */
.g-blocks { padding: clamp(2.5rem, 6vw, 4.5rem) 0; }

/* --- Kopfbereich ------------------------------------------------------- */

/* Alle Werte kommen aus dem Markenprofil (theme_css_variables). Die
   Rückfallwerte hier greifen nur, wenn ein Mandant noch kein Profil hat —
   sie bilden das frühere, fest verdrahtete Aussehen nach. */
.g-header {
	display: flex;
	align-items: center;
	min-height: var(--g-header-h, 72px);
	padding: 0 1.5rem;
	background: var(--g-header-bg, #FFFFFF);
	border-bottom: 1px solid var(--g-header-line, transparent);
}
/* Der innere Kasten trägt die Ausrichtung, damit derselbe Aufbau als <a>
   und als <div> funktioniert — verlinkt ist die ganze Zeile, nicht nur das
   Logo, sonst wäre das Ziel auf dem Handy kaum zu treffen. */
.g-header-inner {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	width: 100%;
	text-decoration: none;
	color: inherit;
	min-width: 0;
}
.g-header--center .g-header-inner { justify-content: center; }
.g-header--right  .g-header-inner { justify-content: flex-end; }

.g-header img {
	height: var(--g-header-logo-h, 40px);
	width: auto;
	max-width: 100%;
	object-fit: contain;   /* breite Wortmarken dürfen nicht verzerren */
}
.g-header-name {
	font-family: var(--brand-font-head);
	font-size: 1.35rem;
	font-weight: 600;
	color: var(--g-header-fg, var(--brand-primary));
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* Mitscrollen ist eine Option, kein Standard: Ein hoher, farbiger Balken,
   der dauerhaft im Bild bleibt, kostet auf dem Handy spürbar Fläche. */
.g-header.is-sticky {
	position: sticky;
	top: 0;
	z-index: 30;   /* über der schwebenden Kontaktleiste (20), unter der Lightbox (50) */
}

/* Auf schmalen Geräten schrumpfen beide Höhen mit. Ohne das frisst ein
   Balken von 140 px mit 100-px-Logo das halbe Sichtfeld. */
@media (max-width: 640px) {
	.g-header {
		min-height: calc(var(--g-header-h, 72px) * 0.75);
		padding: 0 1rem;
	}
	.g-header img { height: calc(var(--g-header-logo-h, 40px) * 0.75); }
	.g-header-name { font-size: 1.1rem; }
}

/* --- Hero -------------------------------------------------------------- */

.hero {
	position: relative;
	width: 100%; height: 45vh;
	min-height: 280px; max-height: 560px;
	overflow: hidden;
	background: color-mix(in srgb, var(--brand-text) 88%, black);
}
.hero img, .hero video { width: 100%; height: 100%; object-fit: cover; object-position: center; }
.hero-overlay {
	position: absolute; inset: 0;
	background: linear-gradient(to top, rgba(0,0,0,0.78) 0%, rgba(0,0,0,0.22) 45%, rgba(0,0,0,0.03) 100%);
}
.hero-text { position: absolute; left: 0; right: 0; bottom: 0; padding: clamp(1.5rem, 5vw, 3.5rem); }
.hero-fallback {
	width: 100%;
	padding: clamp(2rem, 6vw, 4rem) clamp(1.5rem, 5vw, 3.5rem);
	background: var(--brand-primary);
}
/* Über dem Bild bewusst weiß — unabhängig vom Theme lesbar. */
.hero-title { font-size: var(--brand-h1); line-height: 1.05; font-weight: 700; color: #fff; }
.hero-desc {
	margin: 0.85rem 0 0; max-width: 42rem;
	font-size: clamp(0.9rem, 1.6vw, 1.125rem);
	color: rgba(255,255,255,0.8);
}

/* ==========================================================================
   Blockkatalog
   ==========================================================================
   Jeder Blocktyp hat eine ausdrücklich definierte Mobilvariante. Mode-Traffic
   ist überwiegend mobil; ein Block, der nur am Desktop funktioniert, ist für
   die Mehrheit der Besucher kaputt. Deshalb durchgehend mobile-first: die
   Grundregeln gelten klein, Media-Queries erweitern nach oben.
   ========================================================================== */

/* Sanftes Einblenden beim Eintritt in den Viewport. `no-reveal` setzt das JS,
   wenn IntersectionObserver fehlt — dann ist sofort alles sichtbar statt
   dauerhaft unsichtbar. */
.blk { opacity: 0; transform: translateY(14px); transition: opacity .7s ease, transform .7s ease; }
.blk.is-visible, .no-reveal .blk { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
	.blk { opacity: 1; transform: none; transition: none; }
}

.blk + .blk { margin-top: clamp(3rem, 7vw, 6rem); }
/* Randlose Blöcke stoßen direkt aneinander — der Weißraum säße sonst innerhalb
   der Vollflächigkeit und bräche sie auf. */
.blk-fullbleed + .blk-fullbleed, .blk-hero + .blk-fullbleed { margin-top: 0; }

.blk-inner { max-width: 80rem; margin: 0 auto; padding: 0 1.25rem; }
@media (min-width: 640px) { .blk-inner { padding: 0 2rem; } }

.blk-head { margin-bottom: 1.5rem; }
.blk-title { font-size: var(--brand-h2); font-weight: 600; color: var(--brand-primary); margin-bottom: .3rem; }
.blk-sub { margin: 0; max-width: 48rem; font-size: .95rem; line-height: 1.6; color: var(--brand-muted); }

/* --- hero ---------------------------------------------------------------- */

.blk-hero { position: relative; width: 100%; overflow: hidden; background: color-mix(in srgb, var(--brand-text) 88%, black); }
.blk-hero[data-height="medium"] { height: 55vh; min-height: 320px; }
.blk-hero[data-height="large"]  { height: 75vh; min-height: 420px; }
.blk-hero[data-height="full"]   { height: 100svh; }
.blk-hero img, .blk-hero video { width: 100%; height: 100%; object-fit: cover; }

/* Ken Burns: eine einzige transform-Animation, langsam genug, um nicht
   aufzufallen — sie soll das Bild beleben, nicht bewegen. */
@keyframes ken-burns { from { transform: scale(1); } to { transform: scale(1.08); } }
.blk-hero[data-kenburns="1"] img { animation: ken-burns 22s ease-out forwards; }
@media (prefers-reduced-motion: reduce) { .blk-hero img { animation: none !important; } }

.blk-hero-shade { position: absolute; inset: 0; }
.blk-hero-text {
	position: absolute; inset: 0; display: flex; padding: clamp(1.5rem, 5vw, 4rem);
	color: #fff; pointer-events: none;
}
.blk-hero-text > div { max-width: 44rem; pointer-events: auto; }
.blk-hero[data-pos="bottom-left"]   .blk-hero-text { align-items: flex-end; justify-content: flex-start; }
.blk-hero[data-pos="bottom-center"] .blk-hero-text { align-items: flex-end; justify-content: center; text-align: center; }
.blk-hero[data-pos="center"]        .blk-hero-text { align-items: center; justify-content: center; text-align: center; }

.blk-hero h2 { font-size: var(--brand-h1); line-height: 1.05; font-weight: 700; color: #fff; }
.blk-hero p { margin: .8rem 0 0; font-size: clamp(.95rem, 1.6vw, 1.15rem); color: rgba(255,255,255,.85); }

.blk-cta {
	display: inline-block; margin-top: 1.4rem; padding: .7rem 1.5rem;
	border-radius: min(var(--brand-radius), 2rem);
	font-size: .9rem; font-weight: 600; text-decoration: none;
	background: #fff; color: var(--brand-primary);
	transition: opacity .15s;
}
.blk-cta:hover { opacity: .85; }

/* --- text ---------------------------------------------------------------- */

.blk-text-body { line-height: 1.75; }
.blk-text[data-width="narrow"] .blk-text-body { max-width: 38rem; }
.blk-text[data-width="wide"]   .blk-text-body { max-width: 58rem; }
.blk-text[data-width="full"]   .blk-text-body { max-width: none; }
.blk-text[data-align="center"] .blk-text-body { margin-left: auto; margin-right: auto; text-align: center; }

.blk-text[data-style="body"]      .blk-text-body { font-size: 1rem; color: var(--brand-text); }
.blk-text[data-style="lead"]      .blk-text-body { font-size: 1.2rem; color: var(--brand-text); }
/* `statement` ist der editoriale Ausreißer: große Schrift in der
   Überschriftenfamilie, bewusst ohne Fließtext-Charakter. */
.blk-text[data-style="statement"] .blk-text-body {
	font-family: var(--brand-font-head);
	font-size: clamp(1.6rem, 4vw, 2.6rem);
	line-height: 1.25; font-weight: 600;
	letter-spacing: var(--brand-tracking);
	color: var(--brand-primary);
}
.blk-text-body p { margin: 0 0 1em; }
.blk-text-body p:last-child { margin-bottom: 0; }

/* Rich Text. Der Fließtext kommt seit 2026-08-10 aus TinyMCE und kann Listen,
   Zwischenüberschriften, Zitate und Links enthalten. Welche Tags überhaupt
   ankommen können, entscheidet die serverseitige Bereinigung
   (backend/app/core/html_sanitize.py) — hier steht genau diese Auswahl, jedes
   Tag einmal. Ohne diese Regeln erbt der Text den Browser-Reset der Seite:
   Listen hätten keine Aufzählungszeichen und Überschriften keine Auszeichnung,
   die Formatierung im Editor bliebe also folgenlos.

   Die Abstände sind relativ (`em`), damit sie in `lead` und `statement`
   mitwachsen statt in großer Schrift zusammenzukleben. */
.blk-text-body strong, .blk-text-body b { font-weight: 600; }
.blk-text-body em, .blk-text-body i { font-style: italic; }
.blk-text-body u { text-decoration: underline; }
.blk-text-body s { text-decoration: line-through; }

.blk-text-body ul, .blk-text-body ol { margin: 0 0 1em; padding-left: 1.4em; }
.blk-text-body ul { list-style: disc; }
.blk-text-body ol { list-style: decimal; }
.blk-text-body li { margin: .3em 0; }

.blk-text-body h2, .blk-text-body h3, .blk-text-body h4 {
	font-family: var(--brand-font-head);
	font-weight: 600; line-height: 1.25;
	letter-spacing: var(--brand-tracking);
	color: var(--brand-primary);
	margin: 1.6em 0 .5em;
}
.blk-text-body h2 { font-size: 1.5em; }
.blk-text-body h3 { font-size: 1.25em; }
.blk-text-body h4 { font-size: 1.1em; }

.blk-text-body blockquote {
	margin: 1.25em 0; padding-left: 1em;
	border-left: 2px solid color-mix(in srgb, var(--brand-primary) 45%, transparent);
	font-style: italic; color: var(--brand-muted);
}
.blk-text-body hr {
	border: 0; margin: 1.75em 0;
	border-top: 1px solid color-mix(in srgb, var(--brand-text) 12%, transparent);
}

/* Farbe geerbt, nicht Blau: Ein Link im Fließtext soll sich in das Markenbild
   einfügen — erkennbar bleibt er an der Unterstreichung. */
.blk-text-body a { color: inherit; text-decoration: underline; text-underline-offset: .15em; }
.blk-text-body a:hover { color: var(--brand-primary); }

/* Der erste und letzte Absatz dürfen keinen Außenabstand mitbringen, sonst
   verrutscht der Block gegenüber allem, was daneben oder darunter steht —
   beim `split` neben dem Bild fällt genau das auf. */
.blk-text-body > :first-child { margin-top: 0; }
.blk-text-body > :last-child { margin-bottom: 0; }

/* Zweispaltiger Satz. Erst ab Tablet-Breite — auf dem Telefon wären zwei
   Spalten je ~15 Zeichen breit und damit unlesbar. `column-fill: balance`
   sorgt für gleich hohe Spalten statt einer vollen und einer fast leeren. */
@media (min-width: 768px) {
	.blk-text[data-cols="2"] .blk-text-body {
		column-count: 2;
		column-gap: 3rem;
		column-fill: balance;
	}
	/* Absätze nicht über den Spaltenumbruch zerreißen, wenn es sich vermeiden
	   lässt — ein einzelner Rest oben in Spalte 2 sieht nach Fehler aus. */
	.blk-text[data-cols="2"] .blk-text-body p { break-inside: avoid-column; }
	/* Zweispaltig ist bei "narrow" sinnlos eng — dort auf die breite Bahn gehen. */
	.blk-text[data-cols="2"][data-width="narrow"] .blk-text-body { max-width: 52rem; }
}
/* Eine große Aussage in zwei Spalten zu setzen ergibt keinen Sinn. */
.blk-text[data-style="statement"] .blk-text-body { column-count: 1 !important; }

/* --- grid ---------------------------------------------------------------- */

.blk-grid .g-grid { display: grid; grid-template-columns: repeat(2, 1fr); }
.blk-grid[data-gap="none"]   .g-grid { gap: 0; }
.blk-grid[data-gap="small"]  .g-grid { gap: .35rem; }
.blk-grid[data-gap="normal"] .g-grid { gap: .75rem; }
.blk-grid[data-gap="large"]  .g-grid { gap: 1.5rem; }
/* Mobil bewusst immer zwei Spalten, unabhängig von der Einstellung: Bei fünf
   Spalten auf 360px wäre jedes Bild 60px breit. */
@media (min-width: 640px) {
	.blk-grid[data-cols="2"] .g-grid { grid-template-columns: repeat(2, 1fr); }
	.blk-grid[data-cols="3"] .g-grid { grid-template-columns: repeat(3, 1fr); }
	.blk-grid[data-cols="4"] .g-grid { grid-template-columns: repeat(3, 1fr); }
	.blk-grid[data-cols="5"] .g-grid { grid-template-columns: repeat(3, 1fr); }
	.blk-grid[data-gap="normal"] .g-grid { gap: 1rem; }
}
@media (min-width: 1024px) {
	.blk-grid[data-cols="4"] .g-grid { grid-template-columns: repeat(4, 1fr); }
	.blk-grid[data-cols="5"] .g-grid { grid-template-columns: repeat(5, 1fr); }
}

.g-caption { margin: .5rem 0 0; font-size: .8rem; line-height: 1.4; color: var(--brand-muted); }

/* --- fullbleed ----------------------------------------------------------- */

.blk-fullbleed { position: relative; width: 100%; overflow: hidden; }
.blk-fullbleed[data-height="medium"] { height: 50vh; min-height: 280px; }
.blk-fullbleed[data-height="large"]  { height: 72vh; min-height: 380px; }
.blk-fullbleed[data-height="full"]   { height: 100svh; }
.blk-fullbleed img, .blk-fullbleed video { width: 100%; height: 100%; object-fit: cover; }
.blk-fullbleed-shade { position: absolute; inset: 0; }
.blk-fullbleed-text {
	position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
	text-align: center; padding: 2rem; color: #fff; pointer-events: none;
}
.blk-fullbleed-text h2 { font-size: var(--brand-h1); line-height: 1.1; font-weight: 700; color: #fff; }
.blk-fullbleed-text p { margin: .7rem 0 0; color: rgba(255,255,255,.85); max-width: 40rem; }

/* Leichter Parallax-Eindruck nur dort, wo der Browser ihn billig hinbekommt.
   Auf Touch-Geräten erzeugt das Verfahren Ruckeln, deshalb per Hover-Fähigkeit
   eingegrenzt statt per Bildschirmbreite. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
	.blk-fullbleed[data-parallax="1"] img { transform: scale(1.12); will-change: transform; }
}

/* --- slider -------------------------------------------------------------- */

.blk-slider-viewport { position: relative; }

.blk-slider-track {
	display: flex; gap: .75rem;
	overflow-x: auto; scroll-snap-type: x mandatory;
	scrollbar-width: none; -webkit-overflow-scrolling: touch;
	padding-bottom: .25rem;
}

/* Blätterpfeile. Bewusst nur dort, wo gezeigt wird: Auf Touchgeräten wird
   gewischt, dort verdecken Pfeile nur Bildfläche. Sie hängen NICHT an der
   Punkt-Einstellung — wer die Punkte ausblendet, muss trotzdem blättern
   können. */
.blk-arrow {
	position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
	display: none; align-items: center; justify-content: center;
	width: 2.75rem; height: 2.75rem; border-radius: 9999px;
	background: color-mix(in srgb, var(--brand-surface) 88%, transparent);
	color: var(--brand-primary); cursor: pointer;
	box-shadow: 0 2px 12px rgba(0,0,0,.14);
	transition: background .15s, opacity .15s;
}
.blk-arrow:hover { background: var(--brand-surface); }
.blk-arrow--prev { left: .25rem; }
.blk-arrow--next { right: .25rem; }
@media (hover: hover) and (min-width: 640px) {
	.blk-arrow { display: flex; opacity: 0; }
	.blk-slider-viewport:hover .blk-arrow { opacity: 1; }
	/* Tastaturbedienung: Der Fokus muss den Pfeil sichtbar machen, auch ohne
	   dass die Maus über dem Slider steht. */
	.blk-arrow:focus-visible { opacity: 1; outline: 2px solid var(--brand-accent); outline-offset: 2px; }
}
.blk-slider-track::-webkit-scrollbar { display: none; }
.blk-slider-track > * { scroll-snap-align: start; flex: 0 0 auto; }

/* Drei Ausprägungen. `peek` zeigt den Anfang des nächsten Bildes und ist der
   stärkste Hinweis darauf, dass sich hier etwas wischen lässt — ohne Pfeile. */
.blk-slider[data-format="peek"] .blk-slider-track > * { width: 78%; }
.blk-slider[data-format="contained"] .blk-slider-track > * { width: 100%; }
.blk-slider[data-format="fullbleed"] .blk-slider-track > * { width: 92%; }
@media (min-width: 640px) {
	.blk-slider[data-format="peek"] .blk-slider-track > * { width: 42%; }
	.blk-slider[data-format="contained"] .blk-slider-track > * { width: 48%; }
	.blk-slider[data-format="fullbleed"] .blk-slider-track > * { width: 62%; }
}
@media (min-width: 1024px) {
	.blk-slider[data-format="peek"] .blk-slider-track > * { width: 28%; }
	.blk-slider[data-format="contained"] .blk-slider-track > * { width: 32%; }
	.blk-slider[data-format="fullbleed"] .blk-slider-track > * { width: 46%; }
}
/* Fullbleed-Slider laufen bis an den Rand und bekommen nur seitlich Luft. */
.blk-slider[data-format="fullbleed"] .blk-slider-track { padding-left: 1.25rem; padding-right: 1.25rem; }
@media (min-width: 640px) { .blk-slider[data-format="fullbleed"] .blk-slider-track { padding-left: 2rem; padding-right: 2rem; } }

.blk-dots { display: flex; justify-content: center; gap: .4rem; margin-top: 1rem; }
.blk-dots button {
	width: .5rem; height: .5rem; border-radius: 9999px; cursor: pointer;
	background: color-mix(in srgb, var(--brand-text) 25%, transparent);
	transition: background .2s, width .2s;
}
.blk-dots button.is-active { width: 1.4rem; background: var(--brand-accent); }

/* --- split --------------------------------------------------------------- */

.blk-split-inner { display: grid; gap: 1.75rem; align-items: center; }
@media (min-width: 768px) {
	.blk-split[data-ratio="50/50"] .blk-split-inner { grid-template-columns: 1fr 1fr; gap: 3rem; }
	.blk-split[data-ratio="60/40"] .blk-split-inner { grid-template-columns: 3fr 2fr; gap: 3rem; }
	.blk-split[data-ratio="40/60"] .blk-split-inner { grid-template-columns: 2fr 3fr; gap: 3rem; }
	/* Bild rechts: Reihenfolge nur optisch drehen. Im Markup steht das Bild
	   zuerst, damit es auf Mobil oben erscheint — dort ist die gestapelte
	   Reihenfolge Bild-dann-Text die erwartete. */
	.blk-split[data-side="right"] .blk-split-media { order: 2; }
	.blk-split[data-sticky="1"] .blk-split-text { position: sticky; top: 3rem; }
}
.blk-split-media img, .blk-split-media video {
	width: 100%; border-radius: min(var(--brand-radius), 1.25rem);
}
/* Ist ein Bildformat gesetzt, bekommt das Bild einen festen Rahmen — dann muss
   `object-fit` mit. Ohne das gilt der Vorgabewert `fill` und das Motiv wird
   verzerrt statt beschnitten. Bei `auto` (Vorgabe) bleibt alles wie bisher:
   kein aspect-ratio, kein object-fit, das Bild behaelt sein Originalformat.
   Die KI-Plakette ist davon nicht betroffen, sie setzt beides zurueck. */
.blk-split-media[data-ratio]:not([data-ratio="auto"]) img,
.blk-split-media[data-ratio]:not([data-ratio="auto"]) video {
	height: auto; object-fit: cover;
}
.blk-split-text h2 { font-size: var(--brand-h2); font-weight: 600; color: var(--brand-primary); margin-bottom: .5rem; }
.blk-split-text .blk-text-body { line-height: 1.75; color: var(--brand-text); }

/* Textbreite. Die Werte sind kleiner als beim Textblock (38/58rem), weil hier
   nicht die Seitenbahn den Rahmen setzt, sondern die halbe Spalte daneben —
   „schmal" heisst an dieser Stelle etwas anderes. `full` ist die Vorgabe und
   der bisherige Zustand: dann begrenzt allein die Spalte.

   Wirksam wird die Einstellung vor allem bei „Text groesser" (40/60) und auf
   breiten Bildschirmen; ist die Spalte ohnehin schmaler als der eingestellte
   Wert, aendert sie nichts. Das ist gewollt — eine Obergrenze, kein Sollwert. */
.blk-split[data-width="narrow"] .blk-split-text .blk-text-body { max-width: 32rem; }
.blk-split[data-width="wide"]   .blk-split-text .blk-text-body { max-width: 44rem; }
.blk-split[data-width="full"]   .blk-split-text .blk-text-body { max-width: none; }

/* --- outfit --------------------------------------------------------------- */
/* Ein Look gross, die Produkte daneben. Die Aufteilung ist bewusst NICHT frei
   einstellbar: Das grosse Bild muss deutlich dominieren, sonst zerfaellt der
   Block in zwei gleichrangige Bildgruppen und die Aussage ("das ist ein Outfit,
   das sind seine Teile") geht verloren. */

.blk-outfit-inner { display: grid; gap: clamp(1.5rem, 3vw, 2.75rem); }

@media (min-width: 900px) {
	.blk-outfit-inner { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); align-items: center; }
	/* Solange nichts daneben steht (noch keine Produkte, Text ober- oder
	   unterhalb), nimmt der Look die ganze Breite — mittig und in der Breite
	   gedeckelt, damit ein Hochformat nicht bildschirmhoch wird. */
	.blk-outfit[data-solo="1"] .blk-outfit-inner { grid-template-columns: minmax(0, 1fr); }
	.blk-outfit[data-solo="1"] .blk-outfit-main { max-width: 34rem; margin: 0 auto; }
	/* Bild rechts: nur optisch drehen. Im Markup steht der Look zuerst, damit
	   die gestapelte Reihenfolge auf dem Telefon Look-dann-Produkte bleibt. */
	.blk-outfit[data-side="right"] .blk-outfit-main { order: 2; }
}

/* Der Look bekommt etwas Tiefe — er traegt den Block. */
.blk-outfit-main > * { box-shadow: 0 18px 40px -24px rgba(0,0,0,.55); border-radius: min(var(--brand-radius), 1.25rem); }

.blk-outfit-side {
	display: flex; flex-direction: column;
	gap: clamp(1.25rem, 2.5vw, 2rem);
	justify-content: center;
}

/* Punkte auf dem Look-Bild lassen sich abschalten, wenn die Produktkacheln
   daneben ohnehin alles verlinken — zwei Wege zum selben Ziel wirken unruhig. */
.blk-outfit[data-dots="0"] .blk-outfit-main .hs-layer { display: none; }

.of-eyebrow {
	display: block; margin-bottom: .5rem;
	font-size: .72rem; font-weight: 600;
	letter-spacing: .14em; text-transform: uppercase;
	color: var(--brand-accent);
}
.of-title { font-size: var(--brand-h2); font-weight: 600; color: var(--brand-primary); }
.of-text .blk-text-body { margin-top: .75rem; font-size: .95rem; line-height: 1.7; color: var(--brand-muted); }

/* Text ueber dem Block: Abschnittskopf wie bei Raster und Slider — also
   linksbuendig und in der Fluchtlinie des Bildes, nicht mittig. */
.blk-outfit-head { max-width: 44rem; margin-bottom: clamp(1.5rem, 3.5vw, 2.25rem); }

/* Text unter dem Block: mittig und schmal — dort ist er Bildunterschrift zum
   ganzen Look, nicht Spaltentext. */
.blk-outfit-foot { max-width: 44rem; margin: clamp(1.75rem, 4vw, 2.75rem) auto 0; text-align: center; }
.blk-outfit-foot .of-text .blk-text-body { margin-left: auto; margin-right: auto; }

/* Textbreite. Die 44rem oben sind zugleich die Stufe „Breit" und die Vorgabe —
   damit sehen bestehende Bloecke unveraendert aus, auch die, die den Schluessel
   noch gar nicht in ihrer config tragen.

   Steht der Text NEBEN dem Look, begrenzt ihn ohnehin seine Spalte; dort wirkt
   nur „Schmal". Eine Einstellung, die je nach Textposition unterschiedlich viel
   tut, ist der Preis dafuer, dass es eine einzige bleibt statt zweier. */
.blk-outfit[data-width="narrow"] .blk-outfit-head,
.blk-outfit[data-width="narrow"] .blk-outfit-foot { max-width: 32rem; }
.blk-outfit[data-width="full"] .blk-outfit-head,
.blk-outfit[data-width="full"] .blk-outfit-foot { max-width: none; }
.blk-outfit[data-width="narrow"] .of-text .blk-text-body { max-width: 32rem; }

/* Steht der Text ausserhalb, tragen die Produkte die halbe Spalte allein. Sie
   dann oben auszurichten laesst sie am Look-Bild "haengen" statt in der Mitte
   zu schweben — das wirkt gesetzt statt zufaellig. */
@media (min-width: 900px) {
	.blk-outfit:not([data-text="side"]) .blk-outfit-inner { align-items: start; }
}

/* Produktkacheln. Drei Stueck stehen in EINER Reihe statt als 2+1 — eine
   angebrochene zweite Reihe sieht nach fehlendem Bild aus. */
.of-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(.65rem, 1.4vw, 1.1rem); }
.of-tiles[data-n="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.of-tile {
	display: block; margin: 0;
	color: inherit; text-decoration: none;
	transition: transform .25s ease;
}
.of-tile figcaption { display: block; }

/* Der Zeiger muss sagen, was passiert. `.img-card` bringt `zoom-in` mit — auf
   einer verlinkten Kachel waere die Lupe eine Luege, dort fuehrt der Klick in
   den Shop. Beide Faelle deshalb ausdruecklich setzen: `.of-tile .of-media`
   ist spezifischer als `.img-card` und gewinnt unabhaengig von der
   Reihenfolge im Stylesheet (`cursor: inherit` tat das nicht — gleiche
   Spezifitaet, und `.img-card` steht weiter unten). */
a.of-tile, a.of-tile .of-media { cursor: pointer; }
.of-tile.is-static, .of-tile.is-static .of-media { cursor: zoom-in; }

.of-media {
	display: block;
	/* Die Kachel darf nicht schrumpfen, wenn der Name darunter zweizeilig wird. */
	margin-bottom: .55rem;
	box-shadow: 0 6px 16px -12px rgba(0,0,0,.5);
	transition: box-shadow .25s ease;
}
/* Packshots auf hellem Grund vertragen keinen Beschnitt — "Einpassen" legt das
   ganze Produkt mit etwas Luft auf die Flaeche. */
.blk-outfit[data-fit="contain"] .of-media { background: color-mix(in srgb, var(--brand-text) 4%, var(--brand-surface)); }
.blk-outfit[data-fit="contain"] .of-media img,
.blk-outfit[data-fit="contain"] .of-media video { object-fit: contain; padding: 8%; }

.of-name {
	display: block;
	font-size: .85rem; font-weight: 600; line-height: 1.35;
	color: var(--brand-text);
	transition: color .2s ease;
}

.of-cta {
	display: inline-flex; align-items: center; gap: .2rem;
	margin-top: .15rem;
	font-size: .7rem; font-weight: 600;
	letter-spacing: .08em; text-transform: uppercase;
	color: var(--brand-accent);
	opacity: .85; transition: opacity .2s ease;
}
.of-pfeil { font-size: 1.05em; line-height: 1; transition: transform .2s ease; }

@media (hover: hover) {
	a.of-tile:hover { transform: translateY(-3px); }
	a.of-tile:hover .of-media { box-shadow: 0 14px 26px -16px rgba(0,0,0,.55); }
	a.of-tile:hover .of-name { color: var(--brand-primary); }
	a.of-tile:hover .of-cta { opacity: 1; }
	a.of-tile:hover .of-pfeil { transform: translateX(3px); }
}
a.of-tile:focus-visible { outline: 2px solid var(--brand-accent); outline-offset: 4px; border-radius: .25rem; }

@media (prefers-reduced-motion: reduce) {
	.of-tile, .of-media, .of-pfeil { transition: none; }
	a.of-tile:hover { transform: none; }
}

/* --- gemeinsame Bildkachel ------------------------------------------------ */

.img-card {
	position: relative; cursor: zoom-in; overflow: hidden;
	border-radius: min(var(--brand-radius), 1.25rem);
	background: color-mix(in srgb, var(--brand-text) 10%, var(--brand-surface));
}
.img-card img, .img-card video {
	width: 100%; height: 100%; object-fit: cover;
	transition: transform .35s ease, filter .4s ease;
}
/* Bildformat. Dieselbe Auswahl gilt fuer die Rasterkachel (`.img-card`, also
   Raster, Slider und Outfit) und fuer das Bild im Block "Bild + Text"
   (`.blk-split-media`). Die Werte muessen zu BILDFORMATE in
   backend/app/schemas/gallery.py und zu FORMAT_OPTIONEN in galleries.js
   passen — fehlt einer hier, bleibt die Einstellung im Editor folgenlos.

   `.blk-split-media` ist bewusst mit aufgezaehlt und nicht ueber ein blankes
   [data-ratio] erschlagen: `.blk-split` selbst traegt dasselbe Attribut mit
   ganz anderer Bedeutung (die Spaltenaufteilung 50/50 …). */
.img-card[data-ratio="3/4"]  img, .img-card[data-ratio="3/4"]  video,
.blk-split-media[data-ratio="3/4"]  img, .blk-split-media[data-ratio="3/4"]  video { aspect-ratio: 3/4; }
.img-card[data-ratio="1/1"]  img, .img-card[data-ratio="1/1"]  video,
.blk-split-media[data-ratio="1/1"]  img, .blk-split-media[data-ratio="1/1"]  video { aspect-ratio: 1/1; }
.img-card[data-ratio="4/3"]  img, .img-card[data-ratio="4/3"]  video,
.blk-split-media[data-ratio="4/3"]  img, .blk-split-media[data-ratio="4/3"]  video { aspect-ratio: 4/3; }
.img-card[data-ratio="2/3"]  img, .img-card[data-ratio="2/3"]  video,
.blk-split-media[data-ratio="2/3"]  img, .blk-split-media[data-ratio="2/3"]  video { aspect-ratio: 2/3; }
.img-card[data-ratio="4/5"]  img, .img-card[data-ratio="4/5"]  video,
.blk-split-media[data-ratio="4/5"]  img, .blk-split-media[data-ratio="4/5"]  video { aspect-ratio: 4/5; }
.img-card[data-ratio="16/9"] img, .img-card[data-ratio="16/9"] video,
.blk-split-media[data-ratio="16/9"] img, .blk-split-media[data-ratio="16/9"] video { aspect-ratio: 16/9; }
/* `auto` = Originalformat behalten (versetzter Lookbook-Charakter). Beim
   Split-Block ist das die Vorgabe und der Zustand von vor der Formatauswahl. */
.img-card[data-ratio="auto"] img, .img-card[data-ratio="auto"] video { aspect-ratio: auto; height: auto; }

/* Beschnitt. Greift nur, wenn ein Format gesetzt ist — ohne festen Rahmen gibt
   es nichts zu beschneiden. Die KI-Plakette ist selbst ein <img> in der Kachel
   und waere sonst mitbetroffen; sie setzt `object-fit`/`aspect-ratio` weiter
   unten mit !important zurueck. */
.img-card[data-fit="contain"] img, .img-card[data-fit="contain"] video,
.blk-split-media[data-fit="contain"] img, .blk-split-media[data-fit="contain"] video {
	object-fit: contain;
}
.img-card:hover img, .img-card:hover video { transform: scale(1.04); }

/* Blur-Up: Das 400px-Thumb steht unscharf als Platzhalter, bis das größere
   Bild geladen ist. Kostet nichts — das Thumb wird ohnehin geladen. */
.img-card img.is-loading { filter: blur(12px); transform: scale(1.03); }

.video-badge {
	position: absolute; top: .375rem; left: .375rem;
	padding: .25rem .375rem; border-radius: .375rem;
	background: rgba(0,0,0,.55); color: #fff; pointer-events: none;
	display: flex; align-items: center;
}

/* KI-Kennzeichen (Art. 50) — HTML-Plakette ueber dem unbeschrifteten Bild.
   Sie sitzt am Container, nicht am Motiv, und wird deshalb nie mit
   zugeschnitten, egal wie die Kachel das Bild beschneidet.

   WICHTIG: Die Plakette ist selbst ein <img> INNERHALB der Kachel, also greifen
   sonst die Regeln fuer das Kachelbild (`.img-card img`, `.blk-fullbleed img`,
   `.hero img` ...) mit `width/height:100%; object-fit:cover; aspect-ratio` auf
   sie durch — dann wird sie zu einem vollhohen, beschnittenen Streifen. Deshalb
   hier alles hart mit `!important` zuruecksetzen. `width` kommt inline aus den
   Mandanten-Einstellungen (% der Kachelbreite) und braucht kein !important, weil
   Inline-Styles die Klassenregeln ohnehin schlagen. */
:root { --ai-margin: 3%; }
.ai-badge {
	position: absolute !important;
	z-index: 4;
	height: auto !important;
	/* Zweites Netz zur Deckelung in `aiBadgeHtml` (dort steht sie inline als
	   `width: min(x%, AI_MAX)`). Beide Werte gehoeren zusammen — wird einer
	   geaendert, muss der andere mit. Frueher `none`, um das globale
	   `img { max-width: 100% }` abzuwehren; ein fester Wert tut das genauso. */
	max-width: 28px !important;
	aspect-ratio: auto !important;
	object-fit: contain !important;
	border-radius: 0 !important;
	background: none !important;
	box-shadow: none !important;
	transform: none !important;
	transition: none !important;
	pointer-events: none;   /* Klick geht zur Lightbox durch */
	filter: none !important; /* nicht vom Blur-Up der Kachel erfasst */
}
/* Der Split-Media-Container muss Bezugssystem sein, sonst haengt die Plakette
   an der aeusseren Box statt am Bild. */
.blk-split-media { position: relative; }

.g-empty { text-align: center; padding: 5rem 0; color: var(--brand-muted); }

/* --- Fußbereich --------------------------------------------------------- */

.g-footer {
	border-top: 1px solid color-mix(in srgb, var(--brand-text) 10%, transparent);
	background: var(--brand-surface);
	padding: 2.5rem 1.5rem;
	font-size: 0.875rem;
	color: var(--brand-muted);
}
.g-footer-inner { max-width: 80rem; margin: 0 auto; display: grid; gap: 1.5rem; }
@media (min-width: 768px) { .g-footer-inner { grid-template-columns: 1fr auto; align-items: start; } }
.g-footer-brand { font-family: var(--brand-font-head); font-size: 1.1rem; font-weight: 600; color: var(--brand-primary); margin-bottom: 0.5rem; }
.g-footer p { margin: 0.2rem 0; }
.g-footer a { color: var(--brand-accent); text-decoration: none; }
.g-footer a:hover { text-decoration: underline; }
.g-footer-legal { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 0.75rem; }
.g-credit { margin-top: 2rem; text-align: center; font-size: 0.8rem; opacity: 0.55; }

/* --- Lade- und Fehlerzustand -------------------------------------------- */

.g-center { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 0 1.5rem; text-align: center; }
.g-state-title { font-family: var(--brand-font-head); font-size: 1.35rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--brand-primary); }
.g-state-text { font-size: 0.9rem; color: var(--brand-muted); }

/* --- Kopier-Buttons ------------------------------------------------------ */
/* Liegen auf Bildmaterial → feste Kontrastfarben, kein Theme. */

/* Links unten: rechts unten sitzt das KI-Kennzeichen. */
.copy-btn {
	position: absolute; bottom: 0.5rem; left: 0.5rem; z-index: 3;
	width: 2rem; height: 2rem; border-radius: 0.5rem;
	display: flex; align-items: center; justify-content: center;
	background: rgba(0,0,0,0.5); color: #fff; cursor: pointer;
	opacity: 0; transition: opacity 0.15s, background 0.15s;
}
.img-card:hover .copy-btn { opacity: 1; }
.copy-btn:hover { background: rgba(0,0,0,0.78); }
.copy-btn .check-ic { display: none; }
.copy-btn.copied .copy-ic { display: none; }
.copy-btn.copied .check-ic { display: block; color: #4ade80; }
@media (hover: none) { .copy-btn { opacity: 0.85; } }

.lb-copy {
	position: absolute; bottom: 1.25rem; right: 1.5rem; z-index: 2;
	display: flex; align-items: center; gap: 0.45rem; padding: 0.5rem 0.9rem;
	border-radius: 9999px; background: rgba(0,0,0,0.45); color: rgba(255,255,255,0.9);
	cursor: pointer; font-size: 0.85rem; font-weight: 500; transition: background 0.15s, color 0.15s;
}
.lb-copy:hover { background: rgba(0,0,0,0.7); color: #fff; }
.lb-copy .check-ic { display: none; }
.lb-copy.copied .copy-ic { display: none; }
.lb-copy.copied .check-ic { display: block; color: #4ade80; }

/* --- Lightbox ------------------------------------------------------------ */
/* Vollflächig über den Bildern → bewusst immer dunkel. */

.lb { position: fixed; inset: 0; z-index: 50; background: rgba(7,8,13,0.94); display: none; align-items: center; justify-content: center; }
.lb.open { display: flex; }
#lightbox-media { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; }
/* Bild + Fallback-Plakette als Einheit — die Plakette sitzt am Bild, nicht am
   Vollbild-Rand. inline-flex schrumpft auf die Bildgroesse. */
.lb-wrap { position: relative; display: inline-flex; }
/* KI-Plakette in der Lightbox: nur als Rueckfall sichtbar, solange die
   eingebrannte Fassung noch entsteht. Rechts unten, klein. */
#lightbox-media .ai-badge {
	/* Alle drei mit !important — `.ai-badge` deckelt oben selbst per
	   !important, das schlaegt sonst jede Spezifitaet. `width` hier ebenso,
	   weil die Plakette in der Lightbox nur als Rueckfall erscheint und dort
	   ihre eigene Groesse braucht, nicht die inline gesetzte. */
	width: 7% !important; max-width: 32px !important; min-width: 20px !important;
	right: 3%; bottom: 3%; left: auto; top: auto;
}
.lb img, .lb video { max-width: 94vw; max-height: 92svh; object-fit: contain; border-radius: 0.5rem; background: #000; transform-origin: center center; }
.lb img { will-change: transform; }

/* Blur-Up in der Vergroesserung: `.lb-unter` ist das Rasterbild (400 px), das
   ohnehin schon im Cache liegt. Es fuellt die Flaeche sofort; `.lb-img` legt
   sich deckungsgleich darueber und blendet sich ein, sobald es da ist.

   Die Groesse kommt vom Seitenverhaeltnis (--ar, aus media_analysis), NICHT von
   den Bildern: Die Unterlage ist nur 400 px breit und wuerde die Vergroesserung
   sonst auf 400 px festnageln. */
.lb-wrap.hat-unterlage {
	aspect-ratio: var(--ar);
	width: min(94vw, calc(92svh * var(--ar)));
}
/* Bewusst nur die beiden Bildebenen, nicht `img` allgemein: Die KI-Plakette
   ist ebenfalls ein <img> in dieser Huelle und wuerde sonst auf volle Hoehe
   gezogen. */
.lb-wrap.hat-unterlage .lb-unter,
.lb-wrap.hat-unterlage .lb-img {
	position: absolute; inset: 0; width: 100%; height: 100%;
	max-width: none; max-height: none;
}
.lb-unter { filter: blur(14px); }
.lb-wrap.hat-unterlage .lb-img { opacity: 0; transition: opacity .18s ease-out; }
.lb-wrap.hat-unterlage .lb-img.bereit { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
	.lb-wrap.hat-unterlage .lb-img { transition: none; }
}

.lb-zoom { position: absolute; top: 1.25rem; left: 1.5rem; z-index: 2; display: flex; align-items: center; gap: 0.4rem; }
.lb-zoom button {
	width: 2.25rem; height: 2.25rem; border-radius: 0.5rem; background: rgba(0,0,0,0.45);
	color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center;
	font-size: 1.15rem; line-height: 1; transition: background 0.15s;
}
.lb-zoom button:hover { background: rgba(0,0,0,0.7); }
.lb-zoom .lb-zoom-level {
	min-width: 3.4rem; text-align: center; color: rgba(255,255,255,0.9);
	font-size: 0.82rem; background: rgba(0,0,0,0.45); border-radius: 9999px;
	padding: 0.4rem 0.5rem; user-select: none;
}
/* Ohne Maus wird nicht geklickt, sondern aufgezogen. Die Leiste waere dort
   ein zweiter Weg fuer dieselbe Sache -- und sie verdeckt oben links dauerhaft
   einen Teil des Bildes.

   Erkennung ueber `hover: none`, nicht per User-Agent: Hybridgeraete (Surface,
   iPad mit Maus) behalten die Leiste, weil dort das Aufziehen fehlt.

   `!important` ist noetig, weil `showLbMedia()` das `display` inline setzt
   (`flex` beim Bild, `none` beim Video) -- eine Inline-Angabe schlaegt sonst
   jede Regel aus dem Stylesheet. */
@media (hover: none) {
	.lb-zoom { display: none !important; }
}

.lb-close { position: absolute; top: 1.25rem; right: 1.5rem; color: rgba(255,255,255,0.7); cursor: pointer; z-index: 2; }
.lb-close:hover { color: #fff; }
.lb-nav {
	position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
	display: flex; align-items: center; justify-content: center;
	width: 3rem; height: 3rem; border-radius: 9999px;
	background: rgba(0,0,0,0.35); color: rgba(255,255,255,0.8); cursor: pointer;
	transition: background 0.15s, color 0.15s;
}
.lb-nav:hover { background: rgba(0,0,0,0.6); color: #fff; }
.lb-prev { left: 1rem; }
.lb-next { right: 1rem; }
@media (max-width: 640px) { .lb-prev { left: 0.35rem; } .lb-next { right: 0.35rem; } }

/* --- Icons --------------------------------------------------------------- */

.ic-xs { width: 0.875rem; height: 0.875rem; }
.ic-sm { width: 1rem; height: 1rem; }
.ic-md { width: 1.75rem; height: 1.75rem; }
.ic-lg { width: 2rem; height: 2rem; }

/* --- Shoppable: Produktpunkte -------------------------------------------- */
/* Liegen auf Bildmaterial → feste Kontrastfarben, kein Theme.               */

.hs-layer {
	position: absolute; inset: 0; z-index: 4;
	pointer-events: none;   /* nur die Punkte fangen Klicks, nicht die Fläche */
}

.hs-dot {
	position: absolute; pointer-events: auto;
	width: 1.125rem; height: 1.125rem; border-radius: 9999px;
	transform: translate(-50%, -50%);
	background: rgba(255,255,255,0.95);
	box-shadow: 0 1px 6px rgba(0,0,0,0.45);
	display: flex; align-items: center; justify-content: center;
	text-decoration: none; cursor: pointer;
}

/* Der pulsierende Ring ist ein eigenes Element: So bleibt der Punkt selbst
   ruhig und exakt an seiner Koordinate, während nur der Ring animiert. */
.hs-ring {
	position: absolute; inset: 0; border-radius: 9999px;
	border: 2px solid rgba(255,255,255,0.9);
	animation: hs-puls 2.2s ease-out infinite;
}

@keyframes hs-puls {
	0%   { transform: scale(1);   opacity: 0.9; }
	70%  { transform: scale(2.1); opacity: 0; }
	100% { transform: scale(2.1); opacity: 0; }
}

.hs-label {
	position: absolute; bottom: calc(100% + 0.5rem); left: 50%;
	transform: translateX(-50%);
	/* Umbrechen statt abschneiden: Infopunkte tragen ganze Saetze, keine
	   Produktnamen. `max-content` haelt kurze Texte einzeilig, die Breite
	   deckelt lange. */
	width: max-content; max-width: min(var(--hs-label-max, 18rem), 70vw);
	white-space: normal; overflow-wrap: anywhere; hyphens: auto;
	background: var(--hs-label-bg, rgba(0,0,0,0.82));
	color: var(--hs-label-color, #fff);
	font-size: var(--hs-label-size, 0.75rem);
	font-weight: var(--hs-label-weight, 500);
	line-height: 1.35; text-align: left;
	padding: 0.45rem 0.65rem;
	border-radius: var(--hs-label-radius, 0.375rem);
	opacity: 0; transition: opacity 0.15s;
	pointer-events: none;
}

.hs-dot[data-unten="1"] .hs-label {
	bottom: auto; top: calc(100% + 0.5rem);
}

/* Am linken/rechten Bildrand wuerde eine mittig zentrierte Beschriftung aus
   der Kachel laufen (die ist `overflow:hidden`) — dort also buendig ansetzen
   statt zentrieren. */
.hs-dot[data-seite="links"]  .hs-label { left: 0; right: auto; transform: none; }
.hs-dot[data-seite="rechts"] .hs-label { left: auto; right: 0; transform: none; }

/* Die Kachel zoomt beim Hover (transform auf img/video). Die Punkte liegen
   daneben im Layer und wuerden nicht mitwandern — also den Zoom aussetzen,
   solange Punkte auf dem Bild sind, statt sie verrutschen zu lassen. */
.img-card:has(.hs-layer):hover img,
.img-card:has(.hs-layer):hover video { transform: none; }

/* Infopunkt ohne Ziel: gleiche Form, aber kein Klickversprechen. */
.hs-dot.is-info { cursor: default; }

.hs-dot:hover, .hs-dot:focus-visible { background: #fff; }
.hs-dot:hover .hs-label, .hs-dot:focus-visible .hs-label { opacity: 1; }
.hs-dot:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* --- Infoleiste auf Touch-Geräten ---------------------------------------- */
/* Ohne Hover gibt es kein "darüberfahren". Früher standen deshalb ALLE
   Beschriftungen dauerhaft im Bild — bei mehreren Punkten unruhig und
   überlappend. Stattdessen: Tippen öffnet eine Leiste am unteren Bildrand
   (siehe `hsTippen` in gallery_public.html). Die Sprechblase entfällt dort
   komplett; das erspart ihr zugleich die Randkollisionen. */
@media (hover: none) {
	.hs-label { display: none; }
}

/* Am FENSTERrand, nicht in der Kachel: Eine Rasterkachel ist zu schmal fuer
   Text und Schaltflaeche nebeneinander, und ihr `overflow:hidden` schnitt
   beides an. z-index 40 = ueber der schwebenden Kontaktleiste (20), unter der
   Lightbox (50) — die verdeckt das Bild ohnehin und schliesst das Blatt. */
.hs-sheet {
	position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
	display: flex; align-items: center; gap: 0.75rem;
	padding: 0.85rem 1rem;
	/* Unterer Rand: Auf iPhones liegt darunter der Home-Balken. */
	padding-bottom: calc(0.85rem + env(safe-area-inset-bottom, 0px));
	background: rgba(12,12,14,0.94);
	color: #fff;
	font-size: 0.9375rem; line-height: 1.35;
	box-shadow: 0 -4px 20px rgba(0,0,0,0.35);
	/* Von unten einfahren statt aufblitzen. */
	animation: hs-sheet-auf 0.18s ease-out;
}

.hs-sheet[hidden] { display: none; }

@keyframes hs-sheet-auf {
	from { transform: translateY(100%); }
	to   { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
	.hs-sheet { animation: none; }
}

/* Der Text darf wachsen, die Schaltflaeche nicht schrumpfen: Bei langen
   Artikelnamen soll "Zum Produkt" vollstaendig lesbar bleiben. */
.hs-sheet-text {
	flex: 1 1 auto; min-width: 0;
	overflow-wrap: anywhere;
	display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
	overflow: hidden;
}

.hs-sheet-btn {
	flex: 0 0 auto;
	background: #fff; color: #111;
	font-weight: 600; text-decoration: none; white-space: nowrap;
	padding: 0.5rem 1rem; border-radius: 9999px;
	/* Fingergerecht: unter ~44px wird das Ziel unangenehm klein. */
	min-height: 2.75rem; display: inline-flex; align-items: center;
}

/* Ohne eigene Schaltflaeche ist das ganze Blatt der Link. */
.hs-sheet-link {
	flex: 1 1 auto; min-width: 0;
	display: flex; align-items: center; gap: 0.5rem;
	color: inherit; text-decoration: none;
}

.hs-sheet-pfeil { flex: 0 0 auto; font-size: 1.25rem; opacity: 0.75; }

.hs-sheet-zu {
	flex: 0 0 auto;
	width: 2.25rem; height: 2.25rem; border-radius: 9999px;
	background: rgba(255,255,255,0.12); border: 0; color: #fff;
	font-size: 1.35rem; line-height: 1; cursor: pointer;
	display: flex; align-items: center; justify-content: center;
}

/* Sehr schmale Geraete: Lieber umbrechen als den Text auf zwei Woerter
   zusammenquetschen — Text in die erste Zeile, Schaltflaeche und Kreuz in die
   zweite. */
@media (max-width: 22rem) {
	.hs-sheet { flex-wrap: wrap; }
	.hs-sheet-text { flex-basis: 100%; order: -1; }
}

/* Der angetippte Punkt bleibt sichtbar hervorgehoben, solange seine Leiste
   offen ist — sonst weiß man bei mehreren Punkten nicht, welcher gemeint war. */
.hs-dot.is-offen { background: #fff; }
.hs-dot.is-offen .hs-ring { animation: none; opacity: 1; }

/* Die übrigen Punkte treten zurück, solange eine Leiste offen ist — ihre
   pulsierenden Ringe würden sonst vom Text ablenken. */
.hs-layer.hs-offen .hs-dot:not(.is-offen) { opacity: 0.55; }

@media (prefers-reduced-motion: reduce) {
	.hs-ring { animation: none; opacity: 0.9; }
}

/* --- Kontakt & Terminanfrage (Baustein D) -------------------------------- */

.blk-contact { padding: clamp(2.5rem, 6vw, 5rem) 0; }
.blk-contact[data-width="narrow"] .blk-inner { max-width: 46rem; }
.blk-contact[data-width="wide"]   .blk-inner { max-width: 68rem; }

/* Einleitung über dem Formular — der Ort für „Wir melden uns innerhalb eines
   Werktages" oder die Öffnungszeiten. Steht über der ganzen Breite und nicht
   in der Formularspalte: Sie gilt auch für die Kontaktkarte daneben. */
.blk-contact-intro {
	margin-bottom: clamp(1.25rem, 3vw, 2rem);
	font-size: .98rem; line-height: 1.7; color: var(--brand-text);
}

.blk-contact-grid { display: grid; gap: 2rem; align-items: start; }
@media (min-width: 820px) {
	.blk-contact-grid:has(.blk-contact-card) { grid-template-columns: 1.6fr 1fr; }
}

.blk-form { display: grid; gap: 1rem; }
@media (min-width: 560px) {
	.blk-form { grid-template-columns: 1fr 1fr; }
	.blk-field-wide, .blk-consent, .blk-form-msg, .blk-form .blk-cta { grid-column: 1 / -1; }
}

.blk-field { display: flex; flex-direction: column; gap: 0.35rem; }
.blk-field > span {
	font-size: 0.8rem; font-weight: 500;
	color: color-mix(in srgb, var(--brand-text) 65%, transparent);
}
.blk-field input, .blk-field select, .blk-field textarea {
	width: 100%; padding: 0.65rem 0.8rem;
	font: inherit; font-size: 0.95rem; color: var(--brand-text);
	background: var(--brand-surface);
	border: 1px solid color-mix(in srgb, var(--brand-text) 22%, transparent);
	border-radius: min(var(--brand-radius), 0.5rem);
}
.blk-field textarea { resize: vertical; min-height: 6rem; }
.blk-field input:focus, .blk-field select:focus, .blk-field textarea:focus {
	outline: 2px solid var(--brand-accent); outline-offset: 1px;
	border-color: transparent;
}

/* Honigtopf: aus dem Blickfeld, aber NICHT display:none — manche Bots
   überspringen unsichtbare Felder gezielt. */
.blk-hp {
	position: absolute; left: -9999px; width: 1px; height: 1px;
	overflow: hidden;
}

.blk-consent {
	display: flex; gap: 0.6rem; align-items: flex-start;
	font-size: 0.85rem; line-height: 1.45;
	color: color-mix(in srgb, var(--brand-text) 75%, transparent);
}
.blk-consent input { margin-top: 0.2rem; flex-shrink: 0; }
.blk-consent a { color: var(--brand-accent); }

.blk-form .blk-cta { justify-self: start; cursor: pointer; border: none; }
.blk-form .blk-cta:disabled { opacity: 0.6; cursor: default; }

.blk-form-msg { margin: 0; font-size: 0.85rem; min-height: 1.2em; }
.blk-form-msg.is-error { color: #DC2626; }

.blk-form-done {
	padding: 1.25rem 1.5rem; margin: 0;
	background: color-mix(in srgb, var(--brand-accent) 12%, var(--brand-surface));
	border-radius: min(var(--brand-radius), 0.75rem);
	font-size: 1rem;
}

.blk-contact-card {
	padding: 1.25rem 1.5rem;
	background: color-mix(in srgb, var(--brand-text) 5%, var(--brand-surface));
	border-radius: min(var(--brand-radius), 0.75rem);
	font-size: 0.9rem; line-height: 1.6;
}
.blk-contact-card p { margin: 0 0 0.75rem; }
.blk-contact-card p:last-child { margin-bottom: 0; }
.blk-contact-card a { color: var(--brand-accent); text-decoration: none; }

/* --- Kontaktleiste (ohne Formular) --------------------------------------- */

.blk-cta-band { padding: clamp(2rem, 5vw, 3.5rem) 0; text-align: center; }
.blk-cta-row {
	display: flex; flex-wrap: wrap; gap: 0.75rem;
	justify-content: center; margin-top: 1.25rem;
}
.blk-cta-btn {
	padding: 0.7rem 1.4rem; border-radius: 9999px;
	background: var(--brand-primary); color: var(--brand-surface);
	text-decoration: none; font-size: 0.95rem; font-weight: 500;
	transition: opacity 0.15s;
}
.blk-cta-btn:hover { opacity: 0.85; }

/* Schwebend: bleibt am unteren Rand stehen, damit der Rückkanal auf langen
   Seiten immer erreichbar ist. */
.blk-cta-band[data-style="floating"] {
	position: sticky; bottom: 0; z-index: 20; padding: 0.75rem 0;
	background: color-mix(in srgb, var(--brand-surface) 92%, transparent);
	backdrop-filter: blur(8px);
	border-top: 1px solid color-mix(in srgb, var(--brand-text) 12%, transparent);
}
.blk-cta-band[data-style="floating"] .blk-cta-row { margin-top: 0; }
.blk-cta-band[data-style="floating"] .blk-title,
.blk-cta-band[data-style="floating"] .blk-sub { display: none; }

/* --- Einbettung (Stufe 3) ------------------------------------------------ */
/* Eingebettet zieht der Parent den iframe auf die Inhaltshoehe. Viewport-
   Hoehen (vh/svh) beziehen sich dann auf genau diese Hoehe und schaukeln sich
   endlos auf (Hoehe -> vh -> mehr Hoehe). Im Embed-Modus deshalb BREITENbasiert
   ueber aspect-ratio: die iframe-Breite ist stabil, also gibt es keine
   Rueckkopplung. Optik bleibt ein Banner, nur die Hoehe kommt aus dem Format.

   Die Breite allein reicht aber nicht: 16/10 einer 1920 px breiten Gastseite
   sind 1200 px — hoeher als der Bildschirm des Besuchers. Ausserhalb des
   iframes deckelt das Fenster die Bloecke von selbst (72vh kann nie hoeher als
   das Fenster sein); im iframe faellt genau dieser Deckel weg. Die Gastseite
   meldet ihre Fensterhoehe deshalb per postMessage herein (embed.js -> `env`),
   die Galerie legt sie als `--gast-hoehe` an, und die Bloecke deckeln damit.
   Der Wert stammt NICHT aus der iframe-Hoehe, also gibt es keine Rueckkopplung.

   `--gast-hoehe` traegt hier einen Ersatzwert, damit eine noch zwischen-
   gespeicherte alte embed.js (Cache 1 h) keine ungedeckelten Bloecke liefert.
   Die Faktoren entsprechen den Hoehen der frei veroeffentlichten Seite, der
   Block ist eingebettet also hoechstens so hoch wie dort. */
.is-embed { --gast-hoehe: 800px; }
.is-embed .hero { height: auto; aspect-ratio: 16 / 7; }
.is-embed .blk-hero[data-height],
.is-embed .blk-fullbleed[data-height] { height: auto; min-height: 0; }
.is-embed .blk-hero[data-height="medium"] { aspect-ratio: 16 / 9; max-height: calc(var(--gast-hoehe) * 0.55); }
.is-embed .blk-hero[data-height="large"]  { aspect-ratio: 16 / 10; max-height: calc(var(--gast-hoehe) * 0.75); }
.is-embed .blk-hero[data-height="full"]   { aspect-ratio: 16 / 10; max-height: var(--gast-hoehe); }
.is-embed .blk-fullbleed[data-height="medium"] { aspect-ratio: 16 / 9; max-height: calc(var(--gast-hoehe) * 0.50); }
.is-embed .blk-fullbleed[data-height="large"]  { aspect-ratio: 16 / 10; max-height: calc(var(--gast-hoehe) * 0.72); }
.is-embed .blk-fullbleed[data-height="full"]   { aspect-ratio: 16 / 10; max-height: var(--gast-hoehe); }
/* Das Bild fuellt den Block, statt ihn aufzuspannen. Frei veroeffentlicht hat der
   Block eine gesetzte Hoehe (72vh), gegen die `height: 100%` am Bild sauber
   aufloest. Eingebettet kommt die Hoehe aus `aspect-ratio` und wird von
   `max-height` beschnitten — dann ist nicht verlaesslich, wogegen ein Prozent-
   wert rechnet, und das Bild faellt im Zweifel auf seine natuerliche Hoehe
   zurueck (Hochformat: bildschirmhoch statt zugeschnitten). Absolut gesetzt
   haengt es an der Box, wie die auch immer zu ihrer Hoehe gekommen ist.
   `:not(.ai-badge)` ist Pflicht: Die KI-Plakette ist ebenfalls ein direktes
   <img> in dieser Huelle und wuerde sonst zum vollflaechigen Streifen. */
.is-embed .blk-hero > :is(img, video):not(.ai-badge),
.is-embed .blk-fullbleed > :is(img, video):not(.ai-badge) {
	position: absolute; inset: 0; width: 100%; height: 100%;
}
/* Ladezustand nicht an die (anfangs winzige) iframe-Hoehe koppeln. */
.is-embed .g-center { min-height: 50vw; }

/* Kopf und Fuss in der Einbettung ausblenden, wenn das Markenprofil das so
   will. Mit `?embed=1` entsteht das Markup schon serverseitig nicht — diese
   Regeln greifen fuer alle uebrigen Rahmen: ein von Hand gebautes iframe und
   die eigene Kundendomain am Wurzelpfad, die den Parameter gar nicht tragen
   kann. Ohne sie stand dort der Fussbereich mitsamt "erstellt mit LumaDrop",
   obwohl der Schalter aus war.

   Die Attribute sitzen am <html>, die Klasse ebenfalls — deshalb beides ohne
   Nachfahrenzeichen am selben Element. */
.is-embed[data-embed-header="0"] .g-header { display: none; }
.is-embed[data-embed-footer="0"] .g-footer { display: none; }
