/*
 * Iso-Grid-Übersichtsseite — Abschnitt 5 des Übergabeprotokolls.
 * Farbsystem: gedämpfte Basis (Marineblau/Petrol/Waldgrün/Anthrazit),
 * Vollfarbe (Magenta/Apricot/Neon-Türkis) nur als Hover-/Fokus-Akzent.
 * Die Farb-Tokens selbst stehen seit der Aufräumrunde 2026-08-18 auf
 * :root in style.css (dort auch die Begründung) statt hier lokal.
 */

.portfolio-grid-page {
	/* Abstands-Skala — für den Header konsequent verwendet, statt frei
	   gewählter rem-Werte pro Regel. */
	--space-2xs: 0.25rem;
	--space-xs: 0.5rem;
	--space-sm: 0.75rem;
	--space-md: 1rem;
	--space-lg: 1.5rem;
	--space-xl: 2rem;

	--tile-w: 220px;
	--tile-h: 110px;

	/* 200deg statt 160deg (Una-Wunsch 2026-08-11: Lichtquelle oben rechts
	   statt oben links, siehe .grid-tile::before/::after weiter unten für
	   dieselbe Umkehr an den Kacheln — damit passt der Schattenwurf jetzt
	   zum Panel (das schon länger nach links wirft, .project-panel weiter
	   unten) und den Totems.
	   Una-Fund 2026-08-19: Farbreihenfolge war dabei nie mit umgedreht
	   worden — die hellere --color-forest lag weiterhin auf der von der
	   Lichtquelle abgewandten Seite (unten links), die dunklere
	   --color-navy dagegen genau dort, wo laut Kachel-/Totem-Beleuchtung
	   eigentlich das Licht herkommt (oben rechts). Farben getauscht statt
	   den Winkel erneut zu ändern — 200deg (Richtung "nach unten links")
	   bleibt exakt der Winkel, der zur übrigen Beleuchtung passt, nur
	   Start-/Endfarbe sind jetzt vertauscht. */
	background: linear-gradient( 200deg, var( --color-forest ), var( --color-navy ) 70% );
	color: var( --color-text );
	/* 100vh auf echten mobilen Browsern (Una-Fund 2026-08-08: Hintergrund
	   deckt den Viewport nicht vollständig ab) wird dort anhand des GRÖSSTEN
	   möglichen Viewports berechnet (Adressleiste ausgeblendet) — sobald die
	   Adressleiste sichtbar ist, bleibt unten ein Streifen ohne Hintergrund.
	   100dvh (dynamic viewport height) verfolgt stattdessen die tatsächlich
	   sichtbare Höhe live mit. Als zweite Deklaration statt @supports: in
	   Browsern ohne dvh-Unterstützung wird die Zeile einfach ignoriert, die
	   100vh-Zeile darüber bleibt als Fallback aktiv. */
	min-height: 100vh;
	min-height: 100dvh;
	font-family: 'Fira Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

/* --- Sticky Header (Abschnitt 5) --- */

.portfolio-header {
	position: sticky;
	top: 0;
	z-index: 20;
	display: flex;
	flex-direction: column;
	gap: var( --space-sm );
	padding: var( --space-md ) var( --space-lg );
	/* Transparent statt hinterlegter Leiste (Una-Wunsch 2026-08-10, für
	   den minimalistischeren Look zusammen mit dem bis nach oben
	   gezogenen Panel weiter unten) — vorher: background: rgba( 10, 22,
	   34, 0.85 ); backdrop-filter: blur( 8px ); */
	/* Testweise entfernt (Una-Feedback 2026-08-07) — war: border-bottom:
	   1px solid rgba( 233, 243, 241, 0.1 ); */
}

/* Eigenständiges Schatten-Element statt box-shadow direkt auf dem Header
   (Una-Wunsch 2026-08-08): Header + Panel sollen zusammen wie ein Rahmen
   wirken, in dem das Kachelgrid abgesenkt liegt — Panel wirft dafür schon
   länger einen Schatten nach links (siehe .project-panel weiter unten),
   jetzt wirft der Header spiegelbildlich einen nach unten. Der Schatten
   soll aber HINTER dem Panel liegen, wenn dieses offen ist (sonst läge er
   sichtbar über der Panel-Kante und würde den "Rahmen"-Eindruck stören).
   Ein box-shadow direkt auf .portfolio-header würde zwingend dessen
   eigenen, hohen z-index (20, bleibt bewusst über dem abgedunkelten Grid
   bedienbar) erben und läge damit IMMER über dem Panel (16) — als eigenes
   Element mit niedrigerem z-index lässt sich das trennen. Bleibt wie der
   Header selbst an --header-bottom angeheftet. */
.portfolio-header__shadow {
	position: sticky;
	top: var( --header-bottom, 0px );
	height: 0;
	z-index: 10;
	/* Deutlich kräftiger als der erste Versuch (Alpha 0.5): gegen den
	   ohnehin sehr dunklen Seitenhintergrund (--color-navy) direkt unter
	   dem Header war der Schatten dort praktisch unsichtbar — per
	   Vorher/Nachher-Screenshot-Vergleich bestätigt, nicht nur anhand des
	   berechneten Stils (Una-Feedback 2026-08-08: "nach wie vor kein
	   Schatten", zu Recht). 0.8 ist am selben dunklen Hintergrund
	   nachweislich sichtbar. */
	box-shadow: 0 20px 40px rgba( 0, 0, 0, 0.8 );
	pointer-events: none;
}

/* Logo (optional) + Suchfeld links, About rechtsbündig — eine Zeile.
   Ohne Logo entfällt .portfolio-header__logo einfach (nicht im Markup),
   das Suchfeld rutscht dadurch automatisch an den linken Rand. */
.portfolio-header__top {
	display: flex;
	align-items: center;
	gap: var( --space-lg );
}

.portfolio-header__logo img {
	display: block;
	height: 2.5rem;
	width: auto;
}

.portfolio-header__about {
	color: var( --color-text );
	text-decoration: none;
	font-weight: 600;
	white-space: nowrap;
	margin-left: auto;
	padding-right: var( --space-2xs );
}

/* Una-Fund 2026-08-18/19 (echtes iPhone, Safari UND Firefox — identisch,
   weil beide auf iOS denselben WebKit-Unterbau nutzen): der globale
   :focus:not(:focus-visible)-Reset (style.css) reichte nicht — WebKits
   eigene :focus-visible-Heuristik stuft einen Touch-Tap dort selbst
   fälschlich als "focus-visible" ein, ":not(:focus-visible)" greift für
   diesen Fall also gar nicht erst. (hover:hover) and (pointer:fine)
   umgeht das Problem komplett, weil es NICHT von dieser Heuristik
   abhängt, sondern von der Geräte-Fähigkeit selbst — auf einem reinen
   Touchscreen (kein Maus-/Trackpad-Zeiger) immer falsch, unabhängig
   davon, wie der jeweilige Browser :focus-visible einzeln interpretiert.
   Gilt hier zusammen mit :hover, weil ein echtes Hover auf Touch ohnehin
   nicht existiert (bestenfalls ein "hängengebliebener" Tap-Zustand). */
@media ( hover: hover ) and ( pointer: fine ) {
	.portfolio-header__about:hover,
	.portfolio-header__about:focus-visible {
		color: var( --color-accent-turquoise );
	}
}

/* Suchfeld + aktive Tag-Chips nebeneinander in einer gemeinsamen Gruppe
   (Una-Feedback 2026-08-07: Tags sollen neben der Suche stehen, nicht
   darunter — wickeln nur um, wenn der Platz wirklich nicht reicht). */
.portfolio-header__search-group {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var( --space-sm );
	flex: 1;
	min-width: 0;
	max-width: 40rem;
}

.portfolio-search {
	position: relative;
	flex: 0 1 18rem;
	min-width: 10rem;
}

/* Pillenform zurück (Una-Feedback 2026-08-07, zweite Runde: "war als Pill
   schöner") — aber ohne helle Outline und wie in den Header "eingestanzt":
   kein Rahmen, stattdessen derselbe zweilagige Inset-Schatten wie bei den
   versenkten Medien in case-study.css (enger scharfer + weicher breiter
   Layer), damit es sich als vertiefter Ausschnitt im Header liest statt
   als aufgesetztes Element. */
.portfolio-search__input {
	width: 100%;
	padding: var( --space-xs ) var( --space-sm );
	border-radius: 999px;
	border: none;
	background: rgba( 0, 0, 0, 0.3 );
	/* Erste Schicht deutlich abgeschwächt/weicher (Una-Fund 2026-08-10:
	   "unerwünschter schwarzer Rand") — bei 0.9 Deckkraft und nur 0.4rem
	   Weichzeichnung wirkte der innere Schatten trotz border:none wie ein
	   harter schwarzer Rahmen entlang der Pillenform. */
	box-shadow:
		inset 0 0 0.5rem rgba( 0, 0, 0, 0.45 ),
		inset 0 0 1.5rem rgba( 0, 0, 0, 0.5 );
	color: var( --color-text );
	font-family: 'Fira Sans', sans-serif;
	font-size: 0.95rem;
}

.portfolio-search__input::placeholder {
	font-family: 'Fira Sans', sans-serif;
	color: var( --color-text-dim );
}

/* Siehe ausführliche Begründung bei .portfolio-header__about weiter
   oben. */
@media ( hover: hover ) and ( pointer: fine ) {
	.portfolio-search__input:focus-visible {
		outline: 2px solid var( --color-accent-turquoise );
		outline-offset: 2px;
	}
}

.portfolio-search__suggestions {
	position: absolute;
	top: calc( 100% + var( --space-2xs ) );
	left: 0;
	right: 0;
	margin: 0;
	padding: var( --space-2xs );
	list-style: none;
	background: var( --color-anthracite );
	border: 1px solid rgba( 233, 243, 241, 0.15 );
	border-radius: 0.4rem;
	box-shadow: 0 10px 30px rgba( 0, 0, 0, 0.4 );
	max-height: 16rem;
	overflow-y: auto;
}

.portfolio-search__suggestions[hidden] {
	display: none;
}

.portfolio-search__suggestion {
	padding: var( --space-xs ) var( --space-sm );
	border-radius: 0.4rem;
	cursor: pointer;
	font-size: 0.9rem;
}

.portfolio-search__suggestion:hover,
.portfolio-search__suggestion.is-active {
	background: rgba( 47, 242, 208, 0.15 );
	color: var( --color-accent-turquoise );
}

/* --- Aktive Tag-Chips: fließen neben dem Suchfeld, wickeln nur bei
   Platzmangel innerhalb der Such-Gruppe um --- */

.active-tag-filters {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var( --space-xs );
	flex: 1 1 auto;
}

/* min-width nur, wenn tatsächlich Chips drin sind (Bestandsaufnahme
   2026-08-08 — Una-Fund: "Logo, Suchfeld und About nicht auf einer
   Mittellinie"). Ursache: das leere Element reservierte per min-width
   auch ganz ohne aktive Tags 8rem Platz — in Kombination mit
   flex-wrap:wrap auf dem übergeordneten .portfolio-header__search-group
   sprang das leere Element dadurch in eine zweite Zeile, blähte die
   Header-Reihe künstlich auf zwei Zeilen Höhe auf und verschob dadurch
   die vertikale Zentrierung von Logo/Suchfeld/About gegeneinander. Ohne
   Chips ist das Element jetzt 0 breit, bricht nicht mehr um. */
.active-tag-filters:not( :empty ) {
	min-width: 8rem;
}

.tag-chip {
	display: inline-flex;
	align-items: center;
	gap: var( --space-2xs );
	padding: var( --space-2xs ) var( --space-sm );
	border-radius: 999px;
	font-size: 0.85rem;
	font-weight: 600;
	cursor: pointer;
	border: 1px solid transparent;
	background: rgba( 255, 255, 255, 0.08 );
	color: var( --color-text );
}

/* Siehe ausführliche Begründung bei .portfolio-header__about weiter
   oben. */
@media ( hover: hover ) and ( pointer: fine ) {
	.tag-chip:focus-visible {
		outline: 2px solid var( --color-accent-turquoise );
		outline-offset: 2px;
	}
}

/* Grün/Rot ersetzt durch die bestehenden Akzentfarben (Una-Entscheidung
   2026-08-07) — kein drittes Farbpaar außerhalb der in Abschnitt 5
   festgelegten Palette (Magenta/Apricot/Türkis). */
.tag-chip[data-state="green"] {
	background: rgba( 47, 242, 208, 0.18 );
	border-color: var( --color-accent-turquoise );
	color: var( --color-accent-turquoise );
}

.tag-chip[data-state="red"] {
	background: rgba( 255, 63, 180, 0.18 );
	border-color: var( --color-accent-magenta );
	color: var( --color-accent-magenta );
}

.tag-chip__icon {
	font-size: 0.9em;
}

/* --- Grid --- */

.portfolio-grid {
	position: relative;
	padding: 6rem 2rem 8rem;
	min-height: 60vh;
	/* grid-panel.js verschiebt das ganze Grid per translateX, damit die
	   aktive Kachel im durch das offene Panel verkleinerten sichtbaren
	   Bereich mittig bleibt statt halb dahinter zu verschwinden
	   (Una-Feedback 2026-08-07). Deutlich verlangsamt und mit sanft
	   auslaufender Kurve statt linear-ähnlichem "ease" (Una-Feedback
	   2026-08-08: die Bewegung soll "nachziehen" dürfen und möglichst
	   wenig Aufmerksamkeit auf sich ziehen) — cubic-bezier hier ist eine
	   "ease-out-quint"-Kurve: schneller Start, langes, ruhiges Ausklingen
	   statt eines gleichmäßigen Tempos. */
	transition: transform 0.9s cubic-bezier( 0.22, 1, 0.36, 1 );
}

.grid-tile {
	position: absolute;
	display: block;
	width: var( --tile-w );
	height: var( --tile-h );
	left: calc( 50% + ( var( --col ) - var( --row ) ) * ( var( --tile-w ) / 2 ) );
	top: calc( ( var( --col ) + var( --row ) ) * ( var( --tile-h ) / 2 ) );
	transform: translateX( -50% );
	text-decoration: none;
	/* Die rechteckige Box selbst bleibt pointer-events:none — Abschnitt 7s
	   Alpha-Hit-Testing gilt weiterhin fürs Totem (::before/::after unten
	   sind explizit wieder auf auto gesetzt). Das verhindert, dass eine
	   "leere" Ecke der eigenen Box einen Klick auf ein überlappendes
	   Nachbar-Totem blockiert; die sichtbare Diamant-Plattform selbst darf
	   trotzdem klickbar sein (Una-Feedback 2026-08-07), weil sie durch die
	   Fugen nicht mehr mit Nachbar-Plattformen überlappt. */
	pointer-events: none;
}

.grid-tile.is-filtered-out {
	display: none;
}

/* --- Mobile: Grid scroll-/pannbar statt abgeschnitten (Una-Wunsch
   2026-08-10, "vernünftig responsiv") ---
   .grid-tile positioniert sich per calc(50% + …) relativ zur MITTE
   seines Containers — bei genug Kacheln oder genug Abstand zur
   Grid-Mitte rutschen einzelne davon auf schmalen Bildschirmen über den
   linken/rechten Rand hinaus. Browser scrollen zuverlässig nur zu
   POSITIV überlaufendem Inhalt, nie zu negativ positioniertem — mit nur
   2 Test-Kacheln bereits live reproduziert: eine saß teils außerhalb
   des Viewports, ganz ohne Möglichkeit, sie zu erreichen.

   .portfolio-grid-viewport (neuer Wrapper, siehe grid-shell.php) ist auf
   Desktop unsichtbar/wirkungslos (kein overflow gesetzt) — dort bleibt
   alles exakt wie zuvor, inklusive der bestehenden, ausschließlich
   Desktop-relevanten recenterGrid()-Logik in grid-panel.js. Erst ab hier
   wird er zum tatsächlichen Scroll-Container, und .portfolio-grid
   bekommt eine aus den tatsächlich vorhandenen Projekten berechnete
   feste Größe (--grid-min/max-delta/sum als Inline-Custom-Properties aus
   grid-shell.php) statt 100%-Breite — sonst gäbe es innerhalb des
   Viewports nichts, wohin man scrollen könnte. Die Kachel-Position
   selbst wird dabei vom kleinsten vorkommenden Wert aus gerechnet statt
   von der Container-Mitte — dadurch landet keine Kachel mehr im
   Negativen. grid-panel.js zentriert den Scroll-Ausschnitt beim Laden
   zusätzlich auf die aktive (oder erste) Kachel. */
@media ( max-width: 768px ) {
	.portfolio-grid-viewport {
		overflow: auto;

		/* Una-Fund 2026-08-18 ("Kacheln werden an einer geraden Kante
		   abgeschnitten"): .portfolio-grid-viewport ist hier ein echter,
		   eigener Scroll-Container mit fixer Höhe (s. u.) — sein oberer
		   Rand fällt exakt mit --header-bottom zusammen, es gibt also gar
		   keine Überlappung mit dem Header selbst (anders als beim Panel,
		   das bewusst bis top:0 REICHT und den Header-Bereich mit einer
		   blickdichten Fläche abdeckt). Kacheln, die beim Scrollen diese
		   obere Kante erreichen, werden schlicht vom ganz normalen
		   overflow:auto hart abgeschnitten — sieht bei einer isometrischen
		   Kachel (keine rechteckige Form) besonders unsauber aus, mitten
		   durchs Motiv statt an einer Kante.
		   mask-image statt eines zusätzlichen Fade-Elements (wie beim
		   Panel): hier gibt es keine position:fixed-Nachfahren, die durch
		   eine mask-Eigenschaft einen neuen containing block bekommen
		   könnten (das Video-Problem vom 2026-08-10 betraf nur den
		   Panel-Scroll-Container) — mask-image ist hier also gefahrlos.
		   Lässt Kacheln in den letzten 3rem vor der oberen Kante weich
		   auf Transparent auslaufen statt hart aufzuhören; -webkit-Präfix
		   nötig für mobile Safari, das primäre Zielgerät hierfür. */
		mask-image: linear-gradient( to bottom, transparent 0, black 3rem, black calc( 100% - 3rem ), transparent 100% );
		-webkit-mask-image: linear-gradient( to bottom, transparent 0, black 3rem, black calc( 100% - 3rem ), transparent 100% );

		/* Una-Fund 2026-08-11: "Die Zentrierung der aktiven Kachel
		   funktioniert nach wie vor nicht." Live am tatsächlichen
		   Testprojekt nachgemessen: viewport.scrollHeight ==
		   viewport.clientHeight IMMER, unabhängig von der Kachelanzahl
		   oder -position — .portfolio-grid-viewport hatte hier nie eine
		   eigene Höhenbegrenzung, wuchs also als ganz normaler Block im
		   Fluss immer exakt auf die Höhe seines Inhalts (.portfolio-grid)
		   mit. overflow:auto kann unter dieser Bedingung strukturell NIE
		   vertikal greifen — es gibt nie einen Moment, in dem der Inhalt
		   die eigene Box überragt. (Horizontal funktionierte es dagegen
		   von Anfang an zufällig richtig: Block-Boxen bekommen ihre
		   Breite standardmäßig vom Elternelement vorgegeben, .portfolio-
		   grids eigene, oft größere Breite konnte diese also echt
		   überschreiten — bei der Höhe gilt diese Vorgabe nicht, Block-
		   Boxen sind standardmäßig höhen-auto, nicht höhen-begrenzt.)
		   centerGridViewport()s Rechnung in grid-panel.js war die ganze
		   Zeit über korrekt, hatte aber schlicht nie einen positiven
		   Scroll-Bereich, in den hinein sie zentrieren konnte — jeder
		   berechnete Zielwert lag automatisch bei/unter 0 und wurde vom
		   Browser dorthin geklemmt.
		   Fix: eine echte Höhenbegrenzung auf den sichtbaren Bereich
		   unterhalb des Sticky-Headers, exakt nach demselben Muster wie
		   das mobile Bottom-Sheet-Panel und der obere Fade-Verlauf
		   weiter unten (--header-bottom, live von grid-panel.js
		   nachgemessen). Erst dadurch kann .portfolio-grid tatsächlich
		   über die eigene Box hinausragen und overflow:auto bekommt in
		   beiden Richtungen etwas zu tun. */
		height: calc( 100vh - var( --header-bottom, 0px ) );
		height: calc( 100dvh - var( --header-bottom, 0px ) );
	}

	.portfolio-grid {
		/* Una-Fund 2026-08-18 ("Aktive Kachel horizontal linksbündig statt
		   zentriert"): ein fester 2rem-Rand reicht als Scroll-Puffer nur
		   zufällig aus, je nachdem wo die aktive Kachel im Grid liegt — bei
		   wenigen Testprojekten war der reale Spielraum (19px) deutlich zu
		   knapp, um JEDE Kachel bis zur echten Bildschirmmitte scrollen zu
		   können; grid-panel.js klemmte die Zielposition dadurch korrekt,
		   aber sichtbar unzentriert, auf das verfügbare Maximum.
		   Una-Wunsch: "dann gib ihm doch mehr Spielraum" — statt eines
		   festen Werts jetzt ein an die tatsächliche Viewport-Breite
		   gekoppelter Rand: mit 50vw (minus der halben Kachelbreite, die ja
		   bereits zur Kachel selbst gehört) auf jeder Seite lässt sich auch
		   die jeweils äußerste Kachel exakt bis zur Bildschirmmitte
		   scrollen (max()-Untergrenze bei 2rem, damit auf sehr breiten
		   "mobilen" Ansichten — z. B. Tablet-Querformat, noch innerhalb
		   dieses Breakpoints — kein unnötig riesiger Leerraum entsteht,
		   falls 50vw kleiner als 2rem plus halbe Kachelbreite würde). Die
		   Breiten-Formel bekommt denselben Puffer statt der bisherigen
		   festen 4rem (2 × --grid-h-pad statt 2 × 2rem).
		   Una-Fund 2026-08-19 ("Die oberste Kachel kann nie vertikal
		   zentriert werden, weil ihr Abstand nach oben fehlt"): exakt
		   dieselbe strukturelle Lücke wie beim horizontalen Fix oben, nur
		   an der oberen statt der linken Kante — das obere padding:2rem
		   reichte als Scroll-Puffer für Reihe 0 nur zufällig, und wird
		   von .grid-tile ohnehin nicht automatisch respektiert (s. u.).
		   --grid-v-pad nach demselben Prinzip: bezogen auf die tatsächlich
		   sichtbare Höhe des Grid-Viewports (100dvh minus Header, s. o.),
		   nicht auf 50vh — das deckt automatisch auch den (großzügigeren)
		   Fall bei offenem Panel mit ab, weil dort weniger Puffer nötig
		   ist als im Grundzustand (der volle Viewport ohne Panel ist der
		   Fall mit dem größten nötigen Abstand). Unten bewusst weiterhin
		   feste 2rem statt eines symmetrischen --grid-v-pad: der
		   bestehende body.has-open-panel-margin-bottom-Mechanismus weiter
		   unten deckt den unteren Rand bereits ab (margin wirkt anders als
		   padding — siehe Kommentar bei .grid-tile weiter unten — auch auf
		   absolut positionierte Kinder korrekt). */
		--grid-h-pad: max( 2rem, calc( 50vw - ( var( --tile-w ) / 2 ) ) );
		--grid-v-pad: max( 2rem, calc( ( 100dvh - var( --header-bottom, 0px ) ) / 2 - ( var( --tile-h ) / 2 ) ) );
		width: calc( ( var( --grid-max-delta ) - var( --grid-min-delta ) ) * ( var( --tile-w ) / 2 ) + var( --tile-w ) + ( var( --grid-h-pad ) * 2 ) );
		height: calc( ( var( --grid-max-sum ) - var( --grid-min-sum ) ) * ( var( --tile-h ) / 2 ) + var( --tile-h ) + var( --grid-v-pad ) + 2rem );
		min-height: 0;
		padding: var( --grid-v-pad ) var( --grid-h-pad ) 2rem;
	}

	/* Una-Fund 2026-08-18 (Zweitmeinung einer anderen KI, von mir
	   nachgeprüft und bestätigt): Kacheln nah am unteren Grid-Rand waren
	   bei offenem Panel unerreichbar — .portfolio-grid-viewports eigener
	   Scroll-Bereich kennt die Höhe des (rein optisch darüber liegenden)
	   Bottom-Sheet-Panels nirgends, reichte für solche Kacheln also nicht
	   aus, um sie über den Panel-Rand hinweg in den sichtbaren oberen
	   Bereich zu scrollen. .has-open-panel kommt von grid-panel.js
	   (setBodyPanelOpenState()) — ein Geschwister-Selektor auf
	   .project-panel.is-open wäre hier eleganter gewesen, funktioniert
	   aber nicht: .project-panel steht im DOM NACH .portfolio-grid-
	   viewport (siehe template-parts/grid-shell.php), Geschwister-
	   Selektoren wirken nur nach vorne. 50vh + 2rem statt einer Referenz
	   auf die tatsächliche Panel-Höhe, weil die als fester Wert in der
	   Mobile-@media-Regel von .project-panel weiter unten ohnehin genauso
	   hartkodiert ist.
	   margin-bottom statt padding-bottom (live nachgewiesen, erster
	   Versuch): .portfolio-grid hat eine EXPLIZITE height (s. o.) unter
	   sitweitem box-sizing:border-box (style.css) — padding-bottom hätte
	   dort nur den Innenraum umverteilt, ohne die tatsächliche Außen-
	   höhe (und damit den Scroll-Bereich des Elternelements) überhaupt
	   zu vergrößern. margin liegt außerhalb der border-box-Berechnung
	   und schafft echten zusätzlichen Scroll-Puffer. */
	body.has-open-panel .portfolio-grid {
		margin-bottom: calc( 50vh + 2rem );
	}

	.grid-tile {
		/* Una-Fund 2026-08-18, RICHTIGSTELLUNG: der bisherige Kommentar
		   hier war schlicht falsch. "Absolut positionierte Kinder richten
		   sich an der Innenkante der Padding-Box aus" gilt für
		   PROZENTUALE offsets — bei einem FESTEN Wert wie hier (kein %)
		   ist die Containing Block zwar weiterhin die Padding-Box DES
		   ELTERNELEMENTS, aber "left:0" bedeutet dabei "flächenbündig mit
		   der Padding-Box-KANTE", und diese Kante liegt (ohne Border) an
		   exakt derselben physischen Stelle wie die Border-Box-Kante —
		   das Padding selbst liegt INNERHALB der Containing Block, schiebt
		   das Kind also nicht nach innen. Padding auf einem position:
		   relative-Elternteil wirkt NUR auf normal fließende Kinder, nie
		   auf dessen absolut positionierte Kinder. Live nachgewiesen (Una-
		   Fund "Kacheln linksbündig statt zentriert"): --grid-h-pad auf
		   .portfolio-grid selbst probeweise auf 500px gesetzt, Kachel-
		   Position blieb exakt unverändert — das schon lange in diesem
		   Kommentar behauptete Verhalten hat hier also nie tatsächlich so
		   funktioniert, sondern der zusätzliche Platz landete komplett
		   ungenutzt rechts/unten hinter der letzten Kachel statt links/oben
		   vor der ersten.
		   Fix: --grid-h-pad (s. o., von .portfolio-grid geerbt — Custom
		   Properties vererben sich automatisch) jetzt EXPLIZIT im
		   left-Wert der Kachel selbst mit addiert, statt sich auf einen
		   nie wirksamen automatischen Versatz zu verlassen.
		   Una-Fund 2026-08-19: dieselbe Lücke bestätigte sich wie
		   vorhergesagt auch vertikal ("oberste Kachel nie zentrierbar,
		   Abstand nach oben fehlt") — --grid-v-pad (s. o.) jetzt ebenso
		   explizit im top-Wert mit addiert. */
		left: calc( var( --grid-h-pad, 2rem ) + ( var( --col ) - var( --row ) - var( --grid-min-delta ) ) * ( var( --tile-w ) / 2 ) );
		top: calc( var( --grid-v-pad, 2rem ) + ( var( --col ) + var( --row ) - var( --grid-min-sum ) ) * ( var( --tile-h ) / 2 ) );
		transform: none;
	}
}

/* Diamant-„Plattform" mit Schatten — die erhabene Kachel-Optik aus Abschnitt 5.
   Nach innen eingerückt (inset statt 0) statt die volle Zellfläche zu
   füllen — dadurch bleibt zwischen benachbarten Kacheln eine sichtbare
   Fuge (wie Mörtel zwischen Ziegeln, nur isometrisch), durch die der
   dunkle Seitenhintergrund durchscheint (Una-Feedback 2026-08-07). Der
   zweilagige Schatten (weich+eng) verstärkt den erhabenen Eindruck
   gegenüber dieser Fuge. */
.grid-tile::before {
	content: '';
	position: absolute;
	inset: 8px;
	/* 215deg statt 145deg (Una-Wunsch 2026-08-11: Lichtquelle oben rechts
	   statt oben links, damit der Schattenwurf zum Panel — das nach links
	   wirft — und den Totems passt). Seitenwände (.grid-tile::after) und
	   der Seitenhintergrund (.portfolio-grid-page) sind entsprechend
	   mitgekehrt. */
	background: linear-gradient( 215deg, var( --color-petrol ), var( --color-navy ) );
	clip-path: polygon( 50% 0, 100% 50%, 50% 100%, 0 50% );
	box-shadow:
		0 14px 24px rgba( 0, 0, 0, 0.5 ),
		0 3px 6px rgba( 0, 0, 0, 0.35 );
	/* Auto statt none: die Plattform selbst soll klickbar sein, nicht nur
	   das Totem darauf (Una-Feedback 2026-08-07). */
	pointer-events: auto;
	transition: box-shadow 0.2s ease, background 0.2s ease;
}

/* Hover/Fokus/aktives Panel: nicht nur das Totem wacht auf (s. u.), auch
   die Kachel-Plattform selbst bekommt einen wärmeren, akzentuierten
   Verlauf statt der gedämpften Ruhefarbe (Una-Feedback 2026-08-07). */
/* .is-active-panel bewusst NICHT im (hover:hover)/(pointer:fine)-Block
   weiter unten — das ist ein echter Anwendungszustand (von grid-panel.js
   gesetzt), keine Zeigereingabe, muss also auf jedem Gerät unverändert
   gelten. Nur :hover/:focus-visible sind hier die touch-spezifisch
   fehleranfälligen Teile (siehe .portfolio-header__about weiter oben für
   die ausführliche Begründung). */
.grid-tile.is-active-panel::before {
	background: linear-gradient( 215deg, var( --color-accent-turquoise ), var( --color-petrol ) 75% );
}

@media ( hover: hover ) and ( pointer: fine ) {
	.grid-tile:hover::before,
	.grid-tile:focus-visible::before {
		background: linear-gradient( 215deg, var( --color-accent-turquoise ), var( --color-petrol ) 75% );
	}
}

/* Kachel, deren Case Study gerade im Slide-in-Panel offen ist (Phase 3b) */
.grid-tile.is-active-panel::before {
	box-shadow:
		0 0 0 2px var( --color-accent-turquoise ),
		0 14px 24px rgba( 0, 0, 0, 0.5 );
}

/* Seitenwände der Plattform (Fake-3D, Una-Feedback 2026-08-07): eine
   sechseckige Fläche direkt unter den beiden unteren Diamant-Kanten,
   zweigeteilt eingefärbt — rechte Wand heller/petrol, linke Wand dunkler
   (Una-Wunsch 2026-08-11: Lichtquelle oben rechts statt oben links, siehe
   .grid-tile::before oben — 270deg statt 90deg tauscht dafür einfach,
   welche Seite die hellere der beiden Farben bekommt). Werte sind auf
   --tile-w:220px/--tile-h:110px und den 8px-Inset von ::before
   abgestimmt, nicht proportional/responsiv berechnet — das Grid nutzt
   aktuell ohnehin nur diese eine feste Kachelgröße. */
.grid-tile::after {
	content: '';
	position: absolute;
	left: 8px;
	top: calc( var( --tile-h ) / 2 );
	width: calc( var( --tile-w ) - 16px );
	height: calc( var( --tile-h ) / 2 - 8px + 10px );
	clip-path: polygon( 0% 0%, 50% 82.5%, 100% 0%, 100% 17.5%, 50% 100%, 0% 17.5% );
	background: linear-gradient(
		270deg,
		#0a2426 0%,
		#0a2426 50%,
		#050d14 50%,
		#050d14 100%
	);
	/* Auto statt none — Teil der klickbaren Kachel-Fläche, siehe ::before. */
	pointer-events: auto;
}

.totem {
	position: absolute;
	left: 50%;
	/* An die eingerückte Plattform + Seitenwände angepasst (Una-Feedback
	   2026-08-07: Totems ragten vorher unten über die Kachel hinaus).
	   18% → 25% (Una-Wunsch 2026-08-19): Totem-Fuß soll auf der
	   vertikalen Mitte zwischen Kachel-Fuß und Kachel-Mitte stehen, statt
	   nah am unteren Rand zu wirken — reine Geschmacks-/Bildkorrektur,
	   die Überlappungs-Korrektur von 2026-08-07 bleibt davon unberührt
	   (25% ist immer noch innerhalb der eingerückten Plattform). */
	bottom: 25%;
	transform: translateX( -50% );
	width: 70%;
	height: auto;
	max-height: 92%;
	filter: grayscale( 1 ) sepia( 1 ) hue-rotate( 150deg ) saturate( 2 ) brightness( 0.62 );
	transition: filter 0.35s ease;
	pointer-events: none;
}

.totem path,
.totem polygon,
.totem circle,
.totem ellipse,
.totem rect,
.totem polyline {
	pointer-events: auto;
}

.grid-tile.is-active-panel .totem {
	filter: none;
}

@media ( hover: hover ) and ( pointer: fine ) {
	.grid-tile:hover .totem,
	.grid-tile:focus-visible .totem {
		filter: none;
	}
}

.totem--placeholder {
	pointer-events: auto;
	border-radius: 50%;
	background: rgba( 233, 243, 241, 0.15 );
	border: 1px dashed rgba( 233, 243, 241, 0.4 );
}

.grid-tile__label {
	/* Für Screenreader/Tastaturnutzer, visuell nicht sichtbar */
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect( 0 0 0 0 );
	white-space: nowrap;
}

/* --- Slide-in-Panel (Phase 3b, Abschnitt 6) ---
   Off-Canvas, kein Modal: das Grid bleibt daneben sicht- und bedienbar.
   Öffnen/Schließen (inkl. [hidden]-Timing für die Transition) übernimmt
   assets/js/grid-panel.js, das auch --header-bottom live misst (per
   getBoundingClientRect, nicht offsetHeight — berücksichtigt damit auch
   eine eventuelle WP-Admin-Bar, die den Header nach unten schiebt).

   Z-Index-Reihenfolge (Una-Feedback 2026-08-07): Sticky Header (20) liegt
   über allem hier — Panel/Close-Button/Scrim bleiben bewusst darunter,
   das Panel startet daher erst bei --header-bottom statt bei top:0,
   damit sein Inhalt nicht dahinter verschwindet. */

.project-panel__scrim {
	position: fixed;
	inset: 0;
	z-index: 15;
	/* Deutlich abgeschwächt (0.6 → 0.18, Una-Feedback 2026-08-07): die
	   Kacheln bleiben bei offenem Panel genauso anklickbar/relevant wie
	   der Panel-Inhalt (direktes Umschalten zwischen Projekten), eine
	   kräftige Abdunkelung widersprach dem optisch. Ganz ohne Abdunkelung
	   wirkte das Panel im Test aber zu wenig wie der aktuelle Fokus —
	   dieser Wert ist ein bewusster Mittelweg, keine feste Setzung. */
	background: rgba( 6, 12, 18, 0.18 );
	opacity: 0;
	transition: opacity 0.3s ease;
	/* Rein visuelle Abdunkelung — Klicks müssen bis zum Grid durchgereicht
	   werden, damit Kacheln darunter weiterhin einzeln anklickbar bleiben
	   (direktes Umschalten zwischen Projekten, s. grid-panel.js). Schließen
	   bei Klick auf leere Grid-Fläche übernimmt daher der Grid-eigene
	   Klick-Handler, nicht das Scrim. */
	pointer-events: none;
}

.project-panel__scrim.is-open {
	opacity: 1;
}

.project-panel {
	/* min-height:0 überschreibt das min-height:100vh, das über die
	   mitgenutzte .case-study-page-Klasse (für die Farb-Variablen) sonst
	   auch hier greift — eigentlich für die echte Einzelseite gedacht,
	   zwang aber das Mobile-Panel auf volle Bildschirmhöhe und
	   übersteuerte damit dessen height:50vh (Una-Feedback 2026-08-07). */
	min-height: 0;
	position: fixed;
	/* Bis ganz nach oben gezogen statt erst ab --header-bottom (Una-Wunsch
	   2026-08-10, zusammen mit dem jetzt transparenten Header): der
	   Header bleibt trotzdem zuverlässig sichtbar, weil sein z-index (20)
	   über dem des Panels (16) liegt — er "schwimmt" jetzt einfach über
	   dem oberen Streifen des Panel-Hintergrunds, statt dass das Panel
	   erst darunter beginnt. Damit der eigentliche Inhalt UND der
	   Schließen-Button dabei optisch an derselben Stelle stehen bleiben
	   wie vorher, bekommt .project-panel__scroll unten einen
	   entsprechenden padding-top und .project-panel__close einen
	   angepassten top-Wert — nur die Panel-BOX selbst reicht jetzt weiter
	   nach oben, nicht ihr wahrnehmbarer Inhalt. */
	top: 0;
	/* right statt transform:translateX() für die Öffnen/Schließen-
	   Animation (Una-Wunsch 2026-08-10: die Parallax-Bilder sollen im
	   Panel genauso funktionieren wie auf der Einzelseite). Grund: laut
	   Spezifikation (von allen gängigen Browsern so umgesetzt) bezieht
	   sich background-attachment:fixed nicht mehr auf den Viewport,
	   sobald IRGENDEIN Vorfahre-Element ein transform ≠ none hat — und
	   .project-panel selbst hatte durchgehend eins gesetzt, auch im
	   Ruhezustand ("translateX(0)" zählt weiterhin als transform ≠
	   none). Dadurch waren die Parallax-Bilder im nachgeladenen
	   Panel-Inhalt nie wirklich am Sichtfenster fixiert, obwohl exakt
	   derselbe Inhalt auf der Einzelseite (kein transformierter
	   Vorfahre) korrekt funktionierte. right ist eine layout-auslösende
	   Eigenschaft (etwas teurer als eine reine Compositor-Transform),
	   für ein einzelnes, selten animiertes Element aber unproblematisch.
	   -1 mal die eigene Breite verschiebt das Panel exakt einmal
	   komplett nach rechts aus dem Sichtbereich, wie zuvor
	   translateX(100%). */
	right: calc( -1 * min( 48%, 44rem ) );
	bottom: 0;
	z-index: 16;
	width: min( 48%, 44rem );
	background: var( --color-navy );
	/* Testweise entfernt (Una-Feedback 2026-08-07) — war: border-left:
	   1px solid rgba( 233, 243, 241, 0.12 ); */
	box-shadow: -20px 0 50px rgba( 0, 0, 0, 0.5 );
	transition: right 0.35s ease;
}

.project-panel.is-open {
	right: 0;
}

/* Kachelwechsel bei bereits offenem Panel: das Panel selbst bewegt sich
   nicht mehr (kein Raus-/Reinschieben aus dem Viewport mehr, Una-Feedback
   2026-08-07, dritte Runde — wirkte wie ein "Hüpfen" statt einem sanften
   Wechsel) — nur der Inhalt blendet kurz aus und wieder ein, während das
   Panel unverändert an Ort und Stelle stehen bleibt. */
.project-panel__scroll {
	height: 100%;
	overflow-y: auto;
	/* Gleicht das neue top:0 der Panel-Box aus (s. o.) — schiebt den
	   eigentlichen Inhalt exakt um die Header-Höhe wieder nach unten, an
	   dieselbe Stelle, an der er vorher stand. Der jetzt sichtbare
	   Streifen darüber zeigt einfach den normalen Panel-Hintergrund, den
	   der (transparente) Header überlagert. */
	padding-top: var( --header-bottom, 0px );
	/* Una-Fund 2026-08-18 (Mobile): "Ein etwa 1px breiter Streifen
	   Schrift blitzt über dem Verlauf durch." Vermutete Ursache: das
	   native "Rubber-Band"-Überscrollen mobiler Browser (v. a. iOS
	   Safari) verschiebt den scrollenden Inhalt beim Erreichen des
	   Rands kurz über seine eigentliche Grenze hinaus — der Fade-
	   Verlauf (.project-panel__fade--top/--bottom, position:absolute,
	   bewegt sich NICHT mit) deckt diesen kurzzeitig verschobenen
	   Bereich dann nicht mehr ab, ein Streifen Text schimmert durch.
	   overscroll-behavior-y:none unterdrückt genau dieses lokale
	   Überscroll-Verhalten (nicht nur die Weiterleitung an die
	   dahinterliegende Seite, dafür würde "contain" reichen) — ohne
	   Zugriff auf ein echtes iOS-Gerät nicht hundertprozentig
	   nachstellbar, aber die naheliegendste Erklärung für ein Mobile-
	   spezifisches, nur "blitzendes" (also kurzzeitiges) Artefakt genau
	   an der Fade-Kante. */
	overscroll-behavior-y: none;
}

/* Weicher Verlauf statt harter Kante oben UND unten, hinter dem der
   Panel-Inhalt beim Scrollen verblasst — aber jeweils nur, solange dort
   tatsächlich noch etwas abgeschnitten wird (grid-panel.js togglet
   "is-scrolled"/"has-more-below" per Scroll-Listener, abhängig von der
   tatsächlichen Scroll-Position). Ein permanenter Verlauf hätte sonst
   auch Inhalte direkt am Seitenanfang bzw. -ende verdeckt, obwohl dort gar
   nichts "abgeschnitten" ist (Una-Feedback 2026-08-07, dritte Runde — der
   obere Verlauf überlagerte Überschriften gleich zu Beginn; derselbe
   Grund gilt spiegelbildlich unten).

   FRÜHER als mask-image auf .project-panel__scroll selbst umgesetzt —
   Una-Fund 2026-08-10 ("Video verschwindet bei einigen Scrollpositionen")
   führte auf einen deutlich subtileren Bug zurück: jede Eigenschaft aus
   der mask-/filter-/transform-/perspective-/contain-Familie lässt ein
   Element zum "Containing Block" für seine position:fixed-Nachfahren
   werden, sobald sie einen Wert ungleich none hat (derselbe Mechanismus,
   der uns bei .project-panel selbst schon einmal mit dessen transform
   begegnet ist — siehe Kommentar dort). .project-panel__scroll ist ein
   Vorfahre des vom Video-Skript auf position:fixed umgeschalteten
   <video> — die dortige mask-image-Regel riss dessen Verankerung am
   echten Viewport ab, sobald "is-scrolled"/"has-more-below" griff (in
   der Praxis: fast immer, sobald überhaupt gescrollt wurde).

   Jetzt stattdessen zwei blickdichte, mit einem Verlauf zum Panel-
   Hintergrund auslaufende Flächen — als GESCHWISTER von
   .project-panel__scroll (nicht als dessen Vorfahre), male also densel-
   ben optischen Effekt, ohne je ein Containing Block für das Video zu
   werden. Der obere Verlauf ist zusätzlich bis --header-bottom komplett
   blickdicht (Una-Wunsch 2026-08-10: "about im Header immer über dem
   opaken Bereich") statt erst nach und nach über die volle Fade-Höhe
   auszublenden. */
.project-panel__fade {
	position: absolute;
	left: 0;
	right: 0;
	/* Una-Fund 2026-08-18: "Das Video scrollt bei Desktop über den
	   Verlauf statt darunter." Grund: kein eigener z-index hier bedeutet
	   z-index:auto — das aktive Video (.case-study__fixed-media--active)
	   hat dagegen ein EXPLIZITES z-index:17 (case-study.css). Positionierte
	   Elemente mit explizitem, positivem z-index malen laut CSS-Stacking-
	   Regeln grundsätzlich ÜBER allen z-index:auto-Elementen derselben
	   Stapelebene, unabhängig von der DOM-Reihenfolge — der Verlauf lag
	   dadurch immer unter dem Video, nie darüber. 19 statt auto: über dem
	   Video (17) und dem Play/Pause-Knopf (18, case-study.css), weiterhin
	   unter dem Sticky-Header (20). */
	z-index: 19;
	pointer-events: none;
	opacity: 0;
	transition: opacity 0.2s ease;
	/* Una-Fund 2026-08-19 (echtes iPhone, weiterhin sichtbar trotz
	   rgba-statt-transparent-Fix): Versuch gegen eine mögliche zweite
	   Ursache für denselben Saum — WebKit kann ein separat compositiertes
	   Element (dieser Verlauf) und den darunterliegenden, sich bewegenden
	   Scroll-Inhalt bei bestimmten Sub-Pixel-Scroll-Offsets geringfügig
	   unterschiedlich rastern, sichtbar als hauchdünne Kante genau an der
	   Berührungsstelle. translateZ(0) zwingt diesen Verlauf auf eine
	   eigene, GPU-beschleunigte Ebene mit eigenem, konsistentem
	   Rundungsverhalten, unabhängig vom Scroll-Inhalt darunter — ein
	   reiner Compositing-Hinweis, kein layoutwirksames Transform (0px in
	   jede Richtung), deshalb gefahrlos: KEIN Vorfahre der per JS auf
	   position:fixed geschalteten Medien (die sitzen in
	   .project-panel__scroll, einem Geschwister-Element, nicht in einem
	   Nachfahren dieses Verlaufs) — anders als bei .project-panel__scroll
	   selbst löst ein transform hier also nicht das schon einmal gefundene
	   Containing-Block-Problem aus. */
	transform: translateZ( 0 );
}

.project-panel__fade--top {
	top: 0;
	height: calc( var( --header-bottom, 0px ) + 2rem );
	/* Una-Fund 2026-08-18 (Mobile, 1px-Text-Streifen): overscroll-behavior-
	   y:none auf .project-panel__scroll (s. o.) sollte das lokale Rubber-
	   Band-Überscrollen bereits unterdrücken — ohne echtes iOS-Gerät zum
	   Nachtesten aber nicht hundertprozentig verlässlich zuzusichern.
	   Zusätzliche, günstige Absicherung nach Unas eigenem Vorschlag: der
	   blickdichte Teil des Verlaufs reicht jetzt 4px über die eigentliche
	   Content-Kante (--header-bottom) hinaus, statt exakt an ihr zu enden
	   — ein kleiner "Puffer", der ein evtl. trotzdem durchscheinendes,
	   wenige Pixel breites Text-Stück weiterhin überdeckt, unabhängig
	   davon, ob overscroll-behavior allein ausreicht.
	   Una-Fund 2026-08-19 (echtes iPhone): "Der transparente Verlauf hat an
	   der transparenten Seite unten eine etwa 1px dicke opake Linie."
	   Ursache vermutlich das CSS-Schlüsselwort "transparent" selbst — per
	   Spezifikation "rgba(0,0,0,0)", also TRANSPARENTES SCHWARZ, nicht
	   "kein Farbton". Ein Gradient von einer opaken Farbe zu "transparent"
	   interpoliert dadurch technisch über Zwischenwerte, die zunehmend
	   Richtung Schwarz tendieren (nur eben gleichzeitig an Deckkraft
	   verlieren) — auf manchen Compositing-Pfaden (u. a. WebKit auf iOS,
	   wie hier beobachtet) sichtbar als hauchdünner, dunkler Saum genau an
	   der äußersten, eigentlich völlig durchsichtigen Kante. Fix: derselbe
	   Farbton wie der opake Teil, nur mit Alpha 0, statt des generischen
	   "transparent" — die Interpolation bleibt dadurch durchgehend
	   dieselbe Farbe (nur die Deckkraft ändert sich), kein Schwarz-Anteil
	   mehr, der als Saum sichtbar werden könnte. rgba(10,22,34,…)
	   entspricht --color-navy (#0a1622); rgba() statt var()-Interpolation,
	   weil CSS Custom Properties innerhalb einer rgba()-Funktion nicht als
	   einzelne r/g/b-Komponenten zerlegt werden können. */
	background: linear-gradient(
		to bottom,
		var( --color-navy ) 0,
		var( --color-navy ) calc( var( --header-bottom, 0px ) + 4px ),
		rgba( 10, 22, 34, 0 ) 100%
	);
}

.project-panel__fade--bottom {
	bottom: 0;
	height: 4rem;
	/* Derselbe 4px-Puffer wie beim oberen Verlauf, aus demselben Grund;
	   derselbe rgba-statt-transparent-Fix wie oben gegen den 1px-Saum. */
	background: linear-gradient(
		to top,
		var( --color-navy ) 0,
		var( --color-navy ) calc( 1rem + 4px ),
		rgba( 10, 22, 34, 0 ) 100%
	);
}

.project-panel__scroll.is-scrolled ~ .project-panel__fade--top {
	opacity: 1;
}

.project-panel__scroll.has-more-below ~ .project-panel__fade--bottom {
	opacity: 1;
}

.project-panel__body {
	min-height: 100%;
	transition: opacity 0.18s ease;
}

.project-panel__body.is-switching-content {
	opacity: 0;
}

/* Case-Study-Inhalt sitzt in der schmaleren Panel-Spalte statt der vollen
   Seitenbreite — eigener, etwas knapperer Außenabstand. max-width:none
   überschreibt die 42rem-Textspalten-Grenze aus case-study.css: die
   Panel-Breite (min(48%, 44rem)) ist selbst schon die sinnvolle Grenze
   hier, eine zusätzliche eigene Grenze von .case-study sorgte sonst
   dafür, dass .alignfull-Bilder nur bis zum (ggf. schmaleren) Rand
   dieser Textspalte statt bis zum tatsächlichen Panel-Rand reichten
   (Una-Feedback 2026-08-07). */
.project-panel .case-study {
	max-width: none;
	padding: 2.5rem 1.5rem 4rem;
}

/* Schließen-Button: jetzt Kind von .project-panel (siehe Markup), damit er
   die Panel-eigene Öffnen/Schließen-Transform mitmacht statt abrupt
   ein-/auszublenden (Una-Feedback 2026-08-07, dritte Runde). Bleibt
   optisch außerhalb der Panel-Box, weil .project-panel kein
   overflow:hidden hat — position:absolute mit negativem left rutscht
   also sichtbar über das Grid links neben das Panel, bewegt sich aber
   als Kind exakt mit dessen transform mit. Bewusst ohne Kreis/Rahmen/
   Hintergrund, nur das Zeichen selbst. */
.project-panel__close {
	position: absolute;
	/* Gleicht das neue top:0 der Panel-Box aus (siehe .project-panel) —
	   bleibt dadurch optisch auf derselben Höhe wie vorher, obwohl die
	   Box selbst jetzt höher reicht. Der feste -3rem-Wert für die
	   Mobile-Ansicht weiter unten (Bottom-Sheet, kein --header-bottom-
	   Bezug) bleibt davon unberührt. */
	top: calc( var( --header-bottom, 0px ) + var( --space-md ) );
	left: -3rem;
	border: none;
	background: none;
	padding: var( --space-2xs );
	color: var( --color-text );
	font-size: 2rem;
	line-height: 1;
	cursor: pointer;
}

/* Siehe ausführliche Begründung bei .portfolio-header__about weiter
   oben. */
@media ( hover: hover ) and ( pointer: fine ) {
	.project-panel__close:hover,
	.project-panel__close:focus-visible {
		color: var( --color-accent-turquoise );
	}
}

.project-panel__loading,
.project-panel__error {
	padding: 5rem 1.5rem;
	color: var( --color-text-dim );
	font-family: 'Fira Sans', sans-serif;
}

.project-panel__error a {
	color: var( --color-accent-turquoise );
}

/* Mobile: von unten statt von rechts, Abschnitt 6 "horizontale Teilung" —
   deckt bewusst nur die halbe Höhe ab statt (fast) den ganzen Viewport
   (Una-Feedback 2026-08-07: 85vh wirkte praktisch wie Vollbild). Kein
   --header-bottom-Versatz für das Panel selbst nötig, die Kachel-Fläche
   bleibt oben ohnehin frei sichtbar — der (als Kind positionierte)
   Close-Button rutscht hier per negativem top über die Panel-Oberkante
   statt per negativem left daneben. */
@media ( max-width: 768px ) {
	.project-panel {
		top: auto;
		right: 0;
		left: 0;
		height: 50vh;
		width: auto;
		border-left: none;
		/* Testweise entfernt (Una-Feedback 2026-08-07) — war: border-top:
		   1px solid rgba( 233, 243, 241, 0.12 ); */
		border-radius: 1rem 1rem 0 0;
		box-shadow: 0 -20px 50px rgba( 0, 0, 0, 0.5 );
		/* bottom statt transform:translateY() (Una-Fund 2026-08-11: "Video
		   fehlt auf Mobile komplett", "Bild verhält sich nicht wie auf
		   Desktop") — genau derselbe Grund, aus dem .project-panel auf
		   Desktop schon länger "right" statt eines transforms animiert
		   (siehe Kommentar dort): jedes Vorfahre-Element mit einem
		   transform ≠ none wird zum Containing Block für seine
		   position:fixed-Nachfahren UND bricht background-attachment:fixed
		   für seine Bild-Nachfahren — betraf hier bislang nur die
		   Desktop-Variante des Panels, die Mobile-Variante hatte ihr
		   eigenes translateY(100%)/translateY(0) nie mitbekommen. Dadurch
		   positionierte sich das per JS auf position:fixed umgeschaltete
		   Video relativ zum PANEL statt zum echten Viewport — landete
		   dadurch faktisch außerhalb des sichtbaren Bereichs (live
		   bestätigt: elementFromPoint() an der erwarteten Bildschirm-
		   position fand nicht das Video, sondern die dahinterliegende,
		   jetzt leere Fenster-Box). -50vh statt -100% (bezogen auf die
		   eigene Panel-Höhe von 50vh) verschiebt das Panel exakt einmal
		   komplett nach unten aus dem Sichtbereich, wie zuvor
		   translateY(100%). */
		bottom: -50vh;
		transition: bottom 0.35s ease;
	}

	.project-panel.is-open {
		bottom: 0;
	}

	/* Der padding-top-Ausgleich (siehe .project-panel__scroll weiter
	   oben) gilt nur für die Desktop-top:0-Änderung — das Mobile-Panel
	   nutzte --header-bottom nie, würde durch das Padding hier nur
	   unnötig Platz im ohnehin knappen 50vh-Bottom-Sheet verschenken. */
	.project-panel__scroll {
		padding-top: 0;
	}

	.project-panel__close {
		top: -3rem;
		left: auto;
		right: var( --space-md );
	}

	/* Una-Fund 2026-08-11: der obere Fade-Verlauf war hier viel zu hoch —
	   seine Höhe hing an --header-bottom (auf Desktop richtig, weil das
	   Panel bis dorthin reicht), auf Mobile ist das Panel aber ein
	   Bottom-Sheet, das die Header-Höhe gar nicht betrifft. Bei nur 50vh
	   Panel-Höhe insgesamt nahm der eigentlich nur zum Schutz von "about"
	   gedachte, komplett blickdichte Bereich (~104px) einen unverhältnis-
	   mäßig großen Teil davon ein. Auf ein knappes Drittel der bisherigen
	   Höhe reduziert, keine --header-bottom-Abhängigkeit mehr. */
	.project-panel__fade--top {
		/* Una-Fund 2026-08-19 (Mobile-Blitzer, ganz oben im Panel): anders
		   als auf Desktop (Panel reicht bis top:0 des Bildschirms, darüber
		   ist nichts) hat das Panel hier als Bottom-Sheet sichtbaren Raum
		   ÜBER sich (das Grid dahinter) — UND .project-panel selbst schneidet
		   nichts an seiner eigenen Kante ab (bewusst so, damit der
		   Schließen-Knopf leicht außerhalb der Panel-Box schweben kann,
		   siehe dort). Ein negativer top-Wert lässt die Folie deshalb
		   tatsächlich sichtbar über die Panel-Oberkante hinausragen, statt
		   abgeschnitten zu werden — deckt genau die Stelle ab, an der beim
		   elastischen iOS-Überscrollen (Una-Fund) der Inhalt kurz über den
		   eigentlichen Rand von .project-panel__scroll hinausrutscht (der
		   praktisch exakt mit der Panel-Oberkante zusammenfällt). 8px
		   Überhang als erster Test-Wert — bei Bedarf nachjustierbar. Höhe
		   und Verlauf-Stopps entsprechend um denselben Betrag verschoben,
		   damit die UNTERE Kante des Verlaufs (wo der Inhalt normal sichtbar
		   wird) exakt an derselben Stelle bleibt wie vorher — nur die unten
		   angehängte 8px ragen zusätzlich oben heraus. */
		top: -8px;
		height: calc( 2rem + 8px );
		/* Derselbe 4px-Sicherheitspuffer wie in der Desktop-Regel weiter
		   oben (0.5rem + 4px statt exakt 0.5rem) UND derselbe
		   rgba-statt-transparent-Fix gegen den 1px-Saum (Una-Fund
		   2026-08-19, echtes iPhone) — ausführliche Begründung dort. Plus
		   8px Verschiebung des opaken Endpunkts, s. o. */
		background: linear-gradient(
			to bottom,
			var( --color-navy ) 0,
			var( --color-navy ) calc( 0.5rem + 4px + 8px ),
			rgba( 10, 22, 34, 0 ) 100%
		);
	}
}

/* --- Cursor-Glow (Abschnitt 6, Una-Nachforderung 2026-08-19) ---
   Element wird nur per JS erzeugt (assets/js/cursor-glow.js), und auch
   nur auf Geräten mit echtem Zeiger ohne reduzierte-Bewegung-Präferenz —
   ohne JS oder bei ausgeschlossenen Geräten existiert .cursor-glow gar
   nicht erst im DOM, diese Regel hier ist reine Optik für den Fall, dass
   es existiert. pointer-events:none, damit der Schein nie Klicks auf
   Kacheln/Buttons darunter blockieren kann. Bewusst KEIN z-index (siehe
   Kommentar in cursor-glow.js) — die Einfügung als erstes Body-Kind
   reicht, um ihn hinter allen regulären Grid-Inhalten zu halten. */
.cursor-glow {
	position: fixed;
	inset: 0;
	pointer-events: none;
	/* 47, 242, 208 = --color-accent-turquoise (style.css) als literale
	   RGB-Kanäle — rgba() kann eine Custom Property nicht in ihre
	   einzelnen Kanäle zerlegen, daher hier hartkodiert statt var(). */
	background: radial-gradient(
		520px circle at var( --cursor-x, 50% ) var( --cursor-y, 50% ),
		rgba( 47, 242, 208, 0.05 ),
		transparent 70%
	);
}

/* Bestandsaufnahme 2026-08-08 (Barrierefreiheit): keine der Animationen im
   Theme (Grid-Verschiebung, Panel-Slide, Content-Crossfade, Totem-
   Einfärbung) berücksichtigte bislang prefers-reduced-motion. Für
   Besucher:innen, die reduzierte Bewegung eingestellt haben, werden die
   Übergänge hier praktisch auf null verkürzt statt komplett entfernt
   (transition-duration statt transition: none) — grid-panel.js hört beim
   Schließen des Panels auf das transitionend-Event, das bei "none" gar
   nicht mehr feuern würde und sich stattdessen auf den 400ms-Fallback-
   Timeout verlassen müsste. */
@media ( prefers-reduced-motion: reduce ) {
	.portfolio-grid,
	.grid-tile::before,
	.totem,
	.project-panel,
	.project-panel__scrim,
	.project-panel__body,
	.project-panel__fade {
		transition-duration: 0.01ms !important;
	}
}
