/* ==========================================================================
   COMPONENTS: Tags, Buttons, Placeholder-Boxen, Project-Card
   Werden auf Startseite (Slider) UND Projektseite wiederverwendet.
   ========================================================================== */

/* --- Tag-Liste ---------------------------------------------------------- */
.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tag-gap);
}

.tag {
  display: inline-flex;
  align-items: center;
  /* line-height:1 statt geerbtem --lh-body (1.55, s. body in base.css):
     ohne dies wirkt die Zeilenbox und damit die ganze Pille bei
     identischem Padding sichtbar höher – vormals nur an den Hero-Tags
     (.hero__email/-elevator-item/-connect-label) manuell gesetzt, jetzt
     hier zentral für ALLE .tag, damit Hero- und Section-Tags einheitlich
     kompakt aussehen. */
  line-height: 1;
  padding-inline: 0.9em;
  /* Asymmetrisch statt "padding: 0.35em 0.9em" — gleicht die optische
     Schieflage von --font-pixel-grotesk aus, s. --font-pixel-grotesk-nudge
     (tokens.css). */
  padding-block: calc(0.35em - var(--font-pixel-grotesk-nudge)) calc(0.35em + var(--font-pixel-grotesk-nudge));
  background: var(--color-heading-badge-background);
  color: var(--color-badge-font);
  font-family: var(--font-pixel-grotesk);
  font-size: var(--fs-tag);
  font-weight: var(--fw-regular);
  text-transform: uppercase;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

/* Dunkle Variante — für Tags auf dunklem Untergrund (s. .hero__connect-label
   in hero-section.php). */
.tag--dark {
  background: var(--color-badge-background-dark);
  color: var(--color-badge-font-dark);
}

/* --- Scramble-Effekt (assets/js/scramble.js) ----------------------------
   Für die Dauer der Animation ersetzt scramble.js den Textknoten durch diese
   Struktur und baut sie danach wieder ab:

     .scramble > .scramble__inner > .scramble__real   (echter Text)
                                  > .scramble__noise  (Rausch-Overlay)

   Grund ist die Positionsstabilität: Die Rausch-Zeichen (CHARS in
   scramble.js) sind nie exakt so breit wie die Zielbuchstaben, die
   Inhaltsbreite schwankt also von Frame zu Frame. Stünde das Rauschen im
   normalen Fluss, würde diese Schwankung in jeder mittig ausrichtenden Box
   (.btn: justify-content:center) den kompletten Text bei jedem Frame hin-
   und herwandern lassen — sichtbar als Zittern beim Hover.
   Deshalb bestimmt hier ausschließlich der echte Text das Layout und das
   Rauschen liegt absolut positioniert darüber, ohne am Layout teilzunehmen:
   Breite, Zentrierung und damit die Position des ersten Buchstabens stehen
   über die gesamte Animation fest. */
.scramble__inner {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}

/* color:transparent statt visibility:hidden/opacity: der echte Text bleibt
   so unsichtbar, aber als Textknoten im Accessibility-Baum — der Link/Button
   behält seinen zugänglichen Namen, während vorne das Rauschen läuft. */
.scramble__real {
  color: transparent;
}

.scramble__noise {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  white-space: nowrap;
}

/* .dud markiert die kurzzeitigen Rausch-Zeichen während der Scramble-
   Animation. opacity statt fester Farbe: Dud-Spans erben color vom
   Elternelement, dadurch funktioniert die Abdunkelung automatisch für
   .tag, .tag--dark, .btn und alle Theme-Varianten ohne eigene Farb-Tokens. */
.scramble .dud {
  opacity: 0.45;
}

/* .pending markiert die Zeichen, die die Links-nach-rechts-Welle noch nicht
   erreicht hat. visibility statt display:none/leerem Textknoten: Das Zeichen
   bleibt unsichtbar, belegt aber weiterhin exakt seine spätere Breite — ohne
   das würden die noch nicht aufgedeckten Positionen im Overlay zusammenfallen
   und die bereits sichtbaren Zeichen bei jedem Frame neu verschieben. */
.scramble .pending {
  visibility: hidden;
}

/* --- Zeichenabstand-Utility ------------------------------------------------
   Lose gekoppelt statt fest in .tag/.btn verankert: --ls-btn-tag (tokens.css)
   ist der eine gemeinsame Wert, diese Klasse trägt ihn ins Markup. Dadurch
   bekommen auch Elemente außerhalb von .tag/.btn (aktuell .lang-toggle,
   s. header.css/site-header.php; .project-fan-tooltip, s. project-fan.js)
   denselben Zeichenabstand, ohne dass die Klasse an die Button-/Tag-Optik
   gebunden ist. */
.tracking-wide {
  letter-spacing: var(--ls-btn-tag);
}

/* --- Umbruch-Utility: nur zwischen Wörtern -------------------------------
   Kontert body's hyphens:auto/overflow-wrap:break-word (base.css), die bei
   langen/schmalen Boxen sonst einzelne Wörter mitten im Wort trennen
   (Silbentrennung bzw. harter Bruch). Für Elemente, bei denen Umbrüche
   ausschließlich an Wortgrenzen erlaubt sein sollen (aktuell
   .project-fan__lead, s. project-fan-section.php). */
.no-word-break {
  hyphens: none;
  overflow-wrap: normal;
  word-break: normal;
}

/* --- Abstand-Utility: fester Freiraum unter Überschriften/Intro-Text ------
   Wiederverwendbarer Hook für den Abstand zwischen einer Überschrift (oder
   einem Intro-Absatz wie .project-fan__lead) und dem darauffolgenden
   Content. Konkrete Größe per --heading-gap auf dem jeweiligen Element
   überschreibbar; ohne eigene Deklaration greift --space-lg (tokens.css)
   als Fallback. Halbiert sich automatisch am bp-mobile-Breakpoint (640px)
   – auf schmalen Viewports wirkt derselbe absolute Abstand sonst
   unverhältnismäßig groß im Vergleich zum übrigen (ebenfalls kleineren)
   Content. */
.heading-gap {
  margin-bottom: var(--heading-gap, var(--space-lg));
}

/* bp: --bp-mobile (640px) */
@media (max-width: 640px) {
  .heading-gap {
    margin-bottom: calc(var(--heading-gap, var(--space-lg)) / 2);
  }
}

/* --- Page-Heading ----------------------------------------------------------
   Schriftfamilie + Zeilenhöhe für große, editoriale H1-Überschriften
   (--font-pixel-serif) — aktuell von .project-title (project.css) genutzt.
   line-height 1.08 entspricht dem Wert der Grotesk-H1 auf der Startseite
   (.hero__headline/.feed-title, home.css), damit beide H1 optisch gleich
   kompakt wirken, obwohl sie unterschiedliche Schriftfamilien nutzen.
   Schriftgröße bleibt bewusst außerhalb dieser Klasse: jede Verwendung
   skaliert ihre H1 eigenständig (s. font-size in der jeweiligen BEM-Klasse). */
.page-heading {
  font-family: var(--font-pixel-serif);
  line-height: 1.08;
}

/* --- Fließtext-Utility ----------------------------------------------------
   Standard-Klasse für ALLE Fließtext-Bereiche: setzt --font-text (tokens.css)
   auf den Container, <p> darin bekommt dieselbe Schrift bereits global über
   base.css. Gegenstück zu Headings/Buttons/Tags, die mit
   --font-pixel-grotesk dagegenhalten. */
.text {
  font-family: var(--font-text);
}

/* --- Intro-Heading ----------------------------------------------------------
   Gemeinsame Formatierung für die großen Zwischenüberschriften im
   Intro-Bereich einer Section (.services__heading, services.css;
   .project-fan__lead, project-fan.css) — Schriftfamilie, -größe, -stil,
   Zeilenhöhe und Trennungsverhalten identisch, damit beide Überschriften
   vom gleichen Stil erben. --font-intro-heading statt --font-pixel-grotesk
   (Default für h1–h6, base.css): diese Zwischenüberschriften haben ihre
   eigene Schrift, unabhängig von Fließtext (--font-text) und UI-Chrome.
   Schriftfamilie/-größe (font-family/font-size) bewusst NUR hier gesetzt,
   nicht mehr in den BEM-Klassen (.services__heading/.project-fan__lead) —
   beide Klassen dürfen deshalb selbst kein font-family/font-size mehr
   deklarieren, sonst gewinnt bei gleicher Spezifität (beides einfache
   Klassen) wieder die zufällige Cascade-Reihenfolge statt zentral hier.
   --fs-intro-heading (tokens.css) statt direkt --fs-h1: EIN Wert, an dem
   sich die Größe aller .intro-heading-Elemente zentral einstellen lässt.
   Layout (Breite, Ausrichtung, Farbe, Grid-Platzierung) bleibt weiterhin
   section-spezifisch in der jeweiligen BEM-Klasse. */
.intro-heading {
  font-family: var(--font-intro-heading);
  font-size: var(--fs-intro-heading);
  font-style: normal;
  line-height: 0.95;
  hyphens: none;
  overflow-wrap: normal;
}

/* Die .intro-heading-Felder sind Writer-Felder und geben ihren Text in <p>
   aus. Der globale "p"-Selektor (base.css) setzt --font-text DIREKT auf das
   <p> – eine direkte Deklaration gewinnt immer gegen einen geerbten Wert,
   unabhängig von Spezifität. Ohne diesen Reset liefe der Text daher in der
   Fließtext- statt der Überschriften-Schrift. */
.intro-heading p {
  font-family: inherit;
  /* Gleicher Grund wie font-family oben: der globale "p"-Selektor
     (base.css) setzt jetzt eine feste Fließtext-Größe (--fs-lead) direkt
     auf <p> – das schlägt die von .intro-heading geerbte Headline-Größe,
     unabhängig von Spezifität. */
  font-size: inherit;
}

/* Redakteur:innen können in den .intro-heading-Feldern (Writer-Felder,
   marks: [bold] – s. servicesIntroHeading in services.yml, lead in
   work.yml) einzelne Wörter markieren/fett setzen; die erzeugten <strong>
   bekommen hier statt Fettschrift die Pixel-Serif-Schrift, als optische
   Hervorhebung innerhalb der sonst per --font-intro-heading laufenden
   Überschrift.
   font-weight zurückgesetzt statt geerbtem Browser-"bolder": --font-pixel-
   serif liegt nur in einem Schnitt vor (PPMondwest-Regular, 400, s.
   tokens.css) – ohne Reset würde der Browser synthetisch fett rechnen
   (Faux Bold), was bei einer Pixel-Schrift sichtbar das Raster zerstört.
   font-size per --intro-heading-highlight-scale (tokens.css) etwas größer
   als die umgebende Schrift: gleicht aus, dass die dünnen Pixel-Serif-
   Striche neben der fetten --font-intro-heading-Grundschrift sonst
   optisch leichter/kleiner wirken statt als Hervorhebung aufzufallen. */
.intro-heading strong {
  font-family: var(--font-pixel-serif);
  font-weight: var(--fw-regular);
  font-size: calc(1em * var(--intro-heading-highlight-scale));
}

/* --- Section-Introduction ---------------------------------------------------
   Gemeinsame Schriftgröße (--fs-lead) für Einleitungstexte innerhalb einer
   Section — .project-fan__subtext (project-fan.css), .services__intro-text
   (services.css), .testimonial__text (testimonials.css) und
   .project-description (project.css). Die Schriftfamilie kommt aus der
   zusätzlich im Markup gesetzten .text-Klasse, Farbe aus der jeweiligen
   BEM-Klasse: die Sections haben unterschiedliche Hintergrundfarben und
   brauchen daher unterschiedliche Schriftfarben. */
.section-introduction {
  font-size: var(--fs-lead);
}

/* --- Section-Heading -------------------------------------------------------
   Gemeinsame Basis für die H2-Bereichsüberschriften der Onepager-Sektionen
   (Latest, Testimonials, Project-Fan — waren bisher drei identische Kopien
   derselben Deklarationen in slider.css/testimonials.css/project-fan.css).
   Zusätzlich zur jeweiligen BEM-Klasse (z.B. .testimonials__heading) im
   Markup gesetzt: die BEM-Klasse bleibt als Hook für section-spezifische
   Overrides erhalten, die eigentliche Optik lebt an dieser einen Stelle.
   Schriftgröße bewusst per calc() von --fs-h1 abgeleitet (1/2.25 = zweimal
   hintereinander /1.5) statt den Token selbst zu ändern, da --fs-h1 auch
   für andere Elemente gilt.
   Ein eigenes Badge pro Wort: die PHP-Snippets zerlegen den Heading-Text in
   .section-heading__word-Spans (je ein Wort), .section-heading reiht sie
   per Flex-Row (mit Umbruch) aneinander — dadurch bekommt jedes Wort seine
   eigene Badge-Box statt einer einzigen Box um den gesamten Text.
   Hintergrund-Badge identisch zu .services__row-title-text (services.css). */
.section-heading {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  font-family: var(--font-pixel-grotesk);
  /* --fs-section-heading (tokens.css): max() gegen --fs-h2, der reine
     calc()-Wert fällt auf schmalen Viewports sonst bis auf ~1.11rem (die
     Hälfte von --fs-h1s eigenem clamp()-Minimum nochmal halbiert) —
     .section-heading ist inhaltlich eine H2 (s. Kommentar oben) und soll
     deshalb nie kleiner werden als die sitewide H2-Größe. Gleicher Token wie
     .services__row-title-text (services.css) — beide sollen immer exakt
     gleich groß sein. */
  font-size: var(--fs-section-heading);
  line-height: var(--lh-tight);
  margin-bottom: var(--space-lg);
}

/* bp: --bp-mobile (640px) — halbiert den Abstand unter der Überschrift
   genauso wie .heading-gap (s. o.): EIN Rhythmus für den Freiraum unter
   allen Bereichsüberschriften, auf Mobile wie auf Desktop. Muss NACH der
   Basis-Regel stehen (gleiche Spezifität — davor würde die Basis-
   Deklaration margin-bottom auf Mobile wieder überschreiben). */
@media (max-width: 640px) {
  .section-heading {
    margin-bottom: calc(var(--space-lg) / 2);
  }
}

.section-heading__word {
  /* Gleiche Schriftgröße wie das Logo in der Header-Pille (--logo-font-size,
     tokens.css, dort geteilt mit .site-header__logo in header.css) statt
     dem bisherigen responsiven --fs-section-heading vom Elternelement —
     die Wort-Badges sollen überall exakt so groß wie das Logo wirken. */
  font-size: var(--logo-font-size);
  /* padding-inline in em statt zuvor fixem 0.3rem, padding-block-Ratio wie
     .tag (0.35em, s.o.) statt des alten fixen 1rem: beides skaliert jetzt
     mit --logo-font-size mit, nötig geworden, weil die Badges durch die
     Logo-Größe insgesamt kleiner geworden sind als vorher über das
     responsive --fs-section-heading. */
  padding-inline: 0.3em;
  /* Asymmetrisch statt "padding: 0.35em 0.3em" — s. --font-pixel-grotesk-
     nudge (tokens.css). */
  padding-block: calc(0.35em - var(--font-pixel-grotesk-nudge)) calc(0.35em + var(--font-pixel-grotesk-nudge));
  background: var(--color-heading-badge-background);
  color: var(--color-font-dark);
  border-radius: var(--radius-sm);
}

/* --- Section-Heading auf dunklem Untergrund -------------------------------
   Modifier für .section-heading (zusätzlich zur Basis-Klasse im Markup
   gesetzt), wenn die umgebende Section einen dunklen/dunkel abgedunkelten
   Hintergrund hat (Footer-Bild, Work/Project-Fan). Zentral statt
   sectiongebunden, da mehrere Sections dasselbe dunkle Glas-Design teilen —
   war zuvor eine Kopie in footer.css (.site-footer__label .section-heading__word).
   Gleiches Glas-Design wie .hero__email/.hero__elevator-item (hero.css),
   aber mit der DUNKLEN Mix-Farbe aus .tag--dark (--color-badge-background-dark)
   statt dem hellen Tag-Grau. Heller Text (--color-badge-font-dark) bleibt
   entsprechend auf dem dunklen Glas lesbar. Blur/Saturate/Schatten kommen
   aus den site-weiten --glass-*-Tokens (tokens.css), inkl.
   Dark-Theme-Schatten. */
.section-heading--dark .section-heading__word {
  color: var(--color-badge-font-dark);
  background: color-mix(in srgb, var(--color-badge-background-dark) 75%, transparent);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  box-shadow: var(--glass-shadow);
}

/* --- Farbwechsel-Hover (.color-swap) --------------------------------------
   Modular: einfach auf JEDES Element setzbar, das per Mauszeiger-X-Position
   durch 10 Panel-Farben blenden soll (aktuell nur am Logo verwendet,
   s. site-header.php/nav.js) – Klasse "color-swap" + zwei
   ".color-swap__layer"-Kinder + "data-hover-colors" (JSON-Array, aus den
   Panel-Feldern "logoHoverColor1..10", Design > Header) + optional ein
   ".color-swap__label" um den sichtbaren Inhalt (bleibt über den
   Farb-Ebenen). nav.js übernimmt den Rest: pro Mausbewegung Segment
   ermitteln, bisher aktive Farbe rechts rausschieben, neue immer von links
   reinfahren (auch beim allerersten Hover) – overflow:hidden hier schneidet
   das exakt auf die Elementfläche zu, nie eine Lücke/ein Überstand.
   BEWUSST OHNE mix-blend-mode: ein einziges Element mit einem Blend-Modus
   ungleich "normal" reicht in Firefox aus, um das backdrop-filter/blur der
   Glas-Buttons (.site-header .btn) irgendwo auf der Seite zu brechen – die
   per rgba() (s. COLOR_SWAP_TINT_ALPHA in nav.js) bereits ausreichend
   deckende Farbe kommt ohne dieses Risiko aus. */
.color-swap {
  position: relative;
  overflow: hidden;
}

.color-swap__layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  transform: translateX(-100%);
  transition: opacity var(--duration-fast) var(--ease-standard),
    transform var(--duration-base) var(--ease-standard);
  pointer-events: none;
}

.color-swap:hover .color-swap__layer {
  opacity: 1;
}

.color-swap__label {
  position: relative;
  /* Über den Farb-Ebenen (z-index:auto), s.o. */
  z-index: 1;
}

/* Gilt nur während des Hovers – im Ruhezustand behält das jeweilige Element
   seine eigene Textfarbe (z.B. .btn: var(--color-font-dark)). Ausnahme: das
   Logo erzwingt zusätzlich IMMER Weiß (s. .site-header__logo-text), damit
   Hover/Ruhezustand dort optisch identisch bleiben. */
.color-swap:hover .color-swap__label {
  color: var(--color-white);
}

/* --- Button (Nav-Items / Icon-Buttons) ----------------------------------
   Radius geteilt mit allen nicht-full-bleed Medienelementen (--radius-sm),
   siehe tokens.css. Hover/aria-current (s. .site-header__nav-list
   .btn[aria-current="page"] in header.css) bekommen stattdessen den
   stärker abgerundeten --radius-md, als gemeinsam definierter "aktiv"-Look. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3xs);
  padding-inline: 1.2em;
  /* Asymmetrisch statt "padding: 0.6em 1.2em" — s. --font-pixel-grotesk-
     nudge (tokens.css). */
  padding-block: calc(0.6em - var(--font-pixel-grotesk-nudge)) calc(0.6em + var(--font-pixel-grotesk-nudge));
  background: var(--color-button-background);
  color: var(--color-font-dark);
  font-family: var(--font-pixel-grotesk);
  font-size: var(--fs-nav);
  border-radius: var(--radius-sm);
  /* border-radius bewusst 4x so langsam wie transform/opacity: die
     Abrundung soll beim Hover sichtbar "nachziehen" statt im gleichen
     Tempo mitzulaufen. */
  transition: transform var(--duration-fast) var(--ease-standard),
    opacity var(--duration-fast) var(--ease-standard),
    border-radius calc(var(--duration-fast) * 4) var(--ease-standard);
}

.btn:hover {
  transform: translateY(-2px);
  border-radius: var(--radius-md);
}

.btn-icon {
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}

.btn-icon svg {
  width: 1rem;
  height: 1rem;
}

/* --- Solid-Pill-Button (dunkler Hintergrund, heller Text) ----------------
   Gemeinsame Optik für "echte" CTA-Buttons mit optionalem Scramble-Hover
   (Klasse "scramble" im Markup, s. scramble.js) — ursprünglich nur für
   "Project Overview" im Next-Project-Bereich (project.css), jetzt geteilte
   Basis für jeden weiteren gleich aussehenden Button (z.B. "Live Website"
   auf der Projektseite). Form (Padding/Radius) 1:1 vom "Mehr laden"-Button
   im Project-Fan-Slider (.project-fan__load-more, project-fan.css)
   übernommen — Farbgebung dagegen schwarz/deckend mit hellem Text: gleiche
   Rezeptur wie der aktive Nav-Button (.site-header__nav-list
   .btn[aria-current="page"], header.css) – --color-nav-button-active-background
   statt Blur/Transparenz (unnötig unter deckendem Hintergrund). Kein
   Farbwechsel beim Hover: die Klasse "scramble" übernimmt das
   Hover-Feedback stattdessen über den Text-Scramble-Effekt. */
.btn-solid {
  display: inline-block;
  padding-inline: calc(1rem / 1.5);
  /* Asymmetrisch statt eines einzelnen Werts — s. --font-pixel-grotesk-nudge
     (tokens.css): schiebt die Zeichentinte des Pixel-Grotesk-Fonts sichtbar
     nach unten in die Box, exakt dasselbe Rezept wie beim padding-block von
     .btn oben. */
  padding-block: calc(1.29em / 1.5 - var(--font-pixel-grotesk-nudge)) calc(1.29em / 1.5 + var(--font-pixel-grotesk-nudge));
  border-radius: var(--radius-sm);
  /* --color-font-dark statt --color-nav-button-active-background: beide sind
     zufällig derselbe Hex-Wert (tokens.css), aber semantisch soll der Button
     bewusst dieselbe Farbe wie der Fließtext der Seite tragen statt einer
     unabhängig gepflegten Nav-Button-Variable, die künftig abweichen könnte. */
  background: var(--color-font-dark);
  box-shadow: var(--glass-shadow);
  font-family: var(--font-pixel-grotesk);
  font-size: var(--fs-nav-btn);
  text-transform: uppercase;
  letter-spacing: var(--ls-btn-tag);
  color: var(--color-font-bright);
  white-space: nowrap;
  /* Gleiche Mechanik wie .btn/.btn:hover oben: Radius läuft beim Hover
     sichtbar "nach", 4x langsamer als eine reine transform/opacity-
     Transition. */
  transition: border-radius calc(var(--duration-fast) * 4) var(--ease-standard);
}

.btn-solid:hover,
.btn-solid:focus-visible {
  /* 1:1 .btn:hover oben — Ruhezustand ist derselbe kleine Radius wie .btn,
     der Hover-Zustand übernimmt konsequent auch dessen Gegenstück. */
  border-radius: var(--radius-md);
}

/* --- Slider-Controls (Prev/Next) -----------------------------------------
   Geteilte Position + Optik für alle Slider-Sektionen (aktuell
   blocks/latest-slider.php UND testimonials-section.php): EINE Regel statt
   Duplikaten pro Section, damit ein künftiger Design-Wechsel (Farbe, Form,
   Hover) nur hier angefasst werden muss. Erwartet einen Elternrahmen mit
   position:relative in Viewport-Größe (z.B. .latest-slider__viewport /
   .testimonials__viewport). */
.slider-controls {
  position: absolute;
  inset: 0;
  top: 40%;
  display: flex;
  justify-content: space-between;
  pointer-events: none;
}

.slider-controls .btn {
  pointer-events: auto;
  border-radius: 0;
  background: var(--color-background-highlight);
}

/* Slider-Pfeile bleiben am Viewport-Rand fixiert, kein Hover-Lift. */
.slider-controls .btn:hover {
  transform: none;
}

/* Beim Hovern runden nur die INNEREN Ecken ab (Richtung Slider-Mitte) –
   die Ecken zum echten Viewport-Rand hin bleiben eckig, damit der Button
   optisch bündig mit der Kante bleibt. Deshalb pro Button einzeln (statt
   der generischen .btn:hover-Abrundung an allen 4 Ecken) und explizit über
   [data-slider-prev/next], da .slider-controls .btn oben border-radius:0
   ohne :hover-Bedingung setzt (gleiche Spezifität + spätere Stelle würde
   sonst gegen eine generische .btn:hover-Abrundung gewinnen). */
.slider-controls [data-slider-prev]:hover {
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

.slider-controls [data-slider-next]:hover {
  border-radius: var(--radius-md) 0 0 var(--radius-md);
}

/* --- Platzhalter-Box (grau, für Bilder/Videos ohne Asset) ---------------
   --radius-sm als Default: Ausnahmen (full-bleed Hero/Footer-Bild) setzen
   border-radius explizit auf 0 zurück, siehe hero.css / footer.css. */
.placeholder-box {
  width: 100%;
  aspect-ratio: var(--placeholder-ratio, 4 / 3);
  background: var(--color-heading-badge-background);
  object-fit: cover;
  border-radius: var(--radius-sm);
}

/* --- Project-Card (Slider-Karte + "Next Project") ----------------------- */
.project-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  color: inherit;
}

.project-card__media {
  width: 100%;
  /* Bezugsrahmen für die absolut positionierten Tags unten rechts (.tag-list,
     s. project-fan.css) — dieselbe Regel wie .project-fan__body dort. */
  position: relative;
  /* Format identisch zur aufgeklappten Project-Fan-Karte (3:2), s.
     --project-media-ratio (tokens.css). */
  aspect-ratio: var(--project-media-ratio);
  overflow: hidden;
  border-radius: var(--radius-sm);
  /* Gleiches Schwarz wie der aktive Nav-Button (.btn[aria-current="page"],
     header.css) statt des hellen Badge-Grau — Bilder mit abweichendem
     Seitenverhältnis (object-fit:contain unten) liegen dadurch auf dunklem
     statt hellem Untergrund. */
  background: var(--color-nav-button-active-background);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Nur Block-Padding explizit — der Rand links/rechts ergibt sich
     automatisch aus object-fit:contain, sobald das Bildformat schmaler als
     --project-media-ratio ausfällt. */
  padding-block: calc(var(--space-sm) * 4);
}

/* bp: --bp-tablet (1024px) / --bp-mobile (640px) — die Karte selbst wird auf
   schmaleren Viewports schmaler und dadurch (fixes --project-media-ratio)
   auch niedriger; das feste padding-block von oben frisst auf Mobile sonst
   fast die gesamte Boxhöhe auf und das Bild wirkt winzig. Gleiches
   Halbierungs-Muster wie .heading-gap/.section-heading (s.o.). */
@media (max-width: 1024px) {
  .project-card__media {
    padding-block: calc(var(--space-sm) * 2);
  }
}

/* Auf dem Smartphone entfällt das Einpassen (object-fit:contain) vor dem
   dunklen Hintergrund komplett – dafür kein padding-block mehr nötig. */
@media (max-width: 640px) {
  .project-card__media {
    padding-block: 0;
  }
}

.project-card__media img,
.project-card__media video {
  width: 100%;
  height: 100%;
  /* contain statt cover: Bild wird nicht mehr beschnitten/gestreckt,
     sondern unverzerrt mittig im Rahmen platziert (s. o.). */
  object-fit: contain;
  /* "ease" statt var(--ease-standard): sanfterer, langsam anlaufender
     Verlauf für den feinen Zoom (1.03/1.05, s. u.) — passender als der
     site-weite, schärfere Standard-Bezier für diese kleine Bewegung
     (gleiches Muster wie die Tag-Einblendung im Fan-Slider, s.
     project-fan.css). */
  transition: transform var(--duration-base) ease;
}

/* Muss NACH der Basis-Regel oben stehen (gleiche Spezifität: Klasse +
   Element) – davor platziert würde die Basis-Regel object-fit auf Mobile
   sonst wieder auf "contain" zurückkippen (genau der Bug, der hier zu
   sichtbaren Rändern führte). object-fit:cover statt contain: das Bild/
   Video füllt den Elternrahmen (fixes --project-media-ratio, s.o.) immer
   vollständig aus – unabhängig vom eigenen Seitenverhältnis, es wird
   stattdessen bei Bedarf beschnitten statt Ränder freizulassen. */
@media (max-width: 640px) {
  .project-card__media img,
  .project-card__media video {
    object-fit: cover;
  }
}

/* Hover-Effekt nur über transform/opacity (GPU-beschleunigt) */
.project-card:hover .project-card__media img,
.project-card:hover .project-card__media video {
  transform: scale(1.03);
}

/* Stärkerer Zoom, sobald der Cursor zusätzlich direkt über dem sichtbaren
   Bildinhalt liegt (nicht nur irgendwo auf der Karte, s. o.) — eigener,
   vom Cursor-Tooltip unabhängiger Zustand (project-fan.js toggelt beide
   separat), rechnerisch aber auf demselben über object-fit:contain
   berechneten Teilrechteck (s. getVisibleMediaRect() dort): reines CSS
   :hover würde hier wieder den toten Rand links/rechts (Letterbox bei
   schmalerem Bildformat als --project-media-ratio) mit einschließen, exakt
   das Problem, das für den Tooltip schon gelöst wurde.
   ".project-card:hover " vorangestellt (redundant, da .is-image-hovered
   ohnehin nur bei tatsächlichem Hover gesetzt wird) — rein um die
   Spezifität über die generische Hover-Regel oben zu heben (4 statt 3
   Klassen/Pseudoklassen + Element): ohne den Zusatz gewinnt sonst IMMER die
   1.03-Regel oben, unabhängig von der Dateireihenfolge, da Spezifität vor
   Quellreihenfolge entscheidet. */
.project-card:hover .project-card__media img.is-image-hovered,
.project-card:hover .project-card__media video.is-image-hovered {
  transform: scale(1.05);
}

/* --- Media-Item (Bild/GIF/Video/YouTube, siehe components/media-item.php) */
.media-item {
  width: 100%;
}

/* Lightbox-Link ums Bild (media-item.php, data-lightbox — s. media.js) —
   display:block statt des inline-Standards von <a>, sonst würde width:100%
   auf dem inneren .placeholder-box-img nicht die volle .media-item-Breite
   ausfüllen (inline-Boxen skalieren am Inhalt, nicht am Container). */
.media-item__link {
  display: block;
  width: 100%;
}

.media-item__youtube-facade {
  position: relative;
  width: 100%;
  border: 0;
  padding: 0;
  background-size: cover;
  background-position: center;
  display: flex;
  align-items: center;
  justify-content: center;
}

.media-item__play-icon {
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-background-contrast) 70%, transparent);
  color: var(--color-font-bright);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--duration-fast) var(--ease-standard);
}

.media-item__play-icon svg {
  width: 1.25rem;
  height: 1.25rem;
}

.media-item__youtube-facade:hover .media-item__play-icon {
  transform: scale(1.08);
}

.media-item__youtube-iframe {
  width: 100%;
  aspect-ratio: var(--placeholder-ratio, 16 / 9);
  border: 0;
}

.project-card__title {
  font-size: var(--fs-small);
  font-weight: var(--fw-regular);
  /* Verhindert, dass unterschiedlich lange Titel die Card-Höhen im
     Slider gegeneinander verschieben (max. 2 Zeilen, dann Ellipsis). */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(var(--fs-small) * var(--lh-body) * 2);
}
