:root {
  /* Standard dark brand color. Also mapped onto Bootstrap's --bs-dark (+
     its rgb triple, used by Bootstrap's rgba()-based utilities) so any
     Bootstrap "dark" utility (bg-dark, text-dark, border-dark, ...)
     picks up the brand color instead of Bootstrap's default #212529. */
  --color-canvas: #1a042e;
  --bs-dark: var(--color-canvas);
  --bs-dark-rgb: 26, 4, 46;

  /* Brand pink (hero's .btn-pill outline and .section-divider) mapped
     onto Bootstrap's built-in --bs-pink (+ rgb triple) so it's one
     central value instead of the hex repeated at each usage site, and
     any Bootstrap "pink" utility (bg-pink, text-pink, border-pink, ...)
     picks up the brand color instead of Bootstrap's default #d63384. */
  --bs-pink: #ff00c4;
  --bs-pink-rgb: 255, 0, 196;

  /* Standard light text color (the other half of the palette --
     see the color rule below). */
  --color-text-light: #f5eaff;

  --font-base: "Inter", -apple-system, "Segoe UI", sans-serif;

  /* Scoped to .pattern-box only (see there) -- everywhere else keeps
     var(--font-base) above. */
  --font-heading: "Shippori Mincho", serif;
  --font-pattern-text: "Zen Kaku Gothic New", -apple-system, "Segoe UI", sans-serif;

  /* Grid basis (Adobe XD spec): 12 Spalten, 122px Spaltenbreite, 16px Gutter,
     140px Aussenrand, Gesamt-Artboard 1920px. */
  --grid-columns: 12;
  --grid-col-width: 122px;
  --grid-gutter: 16px;
  --grid-margin: 140px;
  --grid-artboard: 1920px;

  /* Standard box (text and/or image content). */
  --box-radius: 30px;
  --box-padding: 45px;
}

/* Color rule: headlines (h1-h6) are always either white or
   var(--color-canvas); all other text is always either
   var(--color-text-light) or var(--color-canvas) -- i.e. never any
   other color, and always picked for contrast against whatever
   background they sit on (white/light text on dark backgrounds, the
   canvas color on light backgrounds). The defaults below cover the
   dark-background case, which is the only one the site uses so far;
   a future light-background component should set color explicitly to
   var(--color-canvas) on its headlines and body text. */

* {
  box-sizing: border-box;
}

/* Browsers' scroll anchoring (on by default) tries to keep the same
   content visually in place under the viewport whenever something
   above it changes size after the initial paint -- a web font
   finishing its swap-in, an image getting its real dimensions, or
   js/hero-height.js setting .hero-section's min-height all happen
   slightly after first paint, and any resulting height change nudged
   the scroll position by a couple pixels on load, just enough to
   reveal a sliver of the next section at the bottom. Turned off
   entirely rather than chasing which specific late layout shift
   triggers it. */
html {
  overflow-anchor: none;
}

body {
  margin: 0;
  background-color: #fff;
  color: #fff;
  font-family: var(--font-base);
  font-size: 14px;
  line-height: 19px;
  font-weight: 400;
  overflow-anchor: none;
}

/* All pages using the hero/yellow/footer stack (home + legal pages): the
   rubber-band/overscroll bounce past the top or bottom edge paints the
   page's root background, not the actual layer underneath -- with body
   staying white, that bounce flashed white behind the purple hero/footer.
   Matching it to the canvas color makes the overscroll invisible instead
   of an unexplained white flash. */
body:has(.hero-section) {
  background-color: var(--color-canvas);
}

/* Wraps everything except the sticky footer, with its own opaque
   background so it visually covers the fixed footer underneath while
   scrolling -- see js/footer-reveal.js for the bottom-spacer height. */
.page-content {
  position: relative;
  background-color: #fff;
}

/* Liquid background: several blurred, monochromatic blobs drifting and
   blending into each other behind the page content. */
.liquid-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  background-color: var(--color-canvas);
}

/* The animated background is confined to the hero section (locked to
   the viewport height at initial load, see js/hero-height.js), not the
   whole fixed viewport -- everything below it is plain white. */
.hero-section {
  /* Normal document flow -- scrolls away to reveal .section-pattern
     (and, after that, the sticky footer) underneath once it has fully
     passed. z-index keeps it visually on top of .section-pattern, which
     is pinned at the same screen position while hero is visible (see
     .section-pattern below). */
  position: relative;
  z-index: 2;
  /* No overflow:hidden here on purpose -- it would silently clip this
     element's own box-shadow. The blobs are already clipped by
     .liquid-bg's own overflow:hidden, so this isn't needed for that. */
  /* position:relative alone (without a z-index) does NOT create a new
     stacking context, so the liquid-bg's z-index:-1 would otherwise escape
     past this element and end up behind .page-content's white background
     instead of just behind the header/hero text. Isolate traps it here. */
  isolation: isolate;
  /* Centers .hero-intro (the only in-flow child with real height --
     .liquid-bg is absolute, .site-header's own box collapses to zero
     since its .logo-link child is fixed) vertically within the space
     left over after the padding below. */
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* box-sizing: border-box (global) keeps this padding inside the
     JS-set min-height (viewport height at load), so the min-height
     guarantee still holds. */
  padding-top: 150px;
  padding-bottom: 150px;
  /* Cast downward onto whatever it reveals while scrolling. */
  box-shadow: 0 14px 35px rgba(0, 0, 0, 0.42);
}

.hero-section .liquid-bg {
  position: absolute;
}

/* Reserves the document space .section-pattern needs (one screen)
   regardless of whether that element itself is currently position:fixed
   (contributing zero flow height on its own) or released back into
   normal flow -- without this, the page's scrollable height would
   change at the exact moment it releases, jumping the scroll position. */
/* Visually hidden but still fully rendered -- NOT display:none. This
   holds the master <svg class="pattern-fish"> (mask/gradients/the
   #fish-body group), moved here out of layer 2 specifically because
   .fish-widget's <use href="#fish-body"> (see index.html) stopped
   resolving once its source lived inside a display:none subtree
   (layer 2 below). width/height:0 + overflow:hidden keeps it in the
   actual render tree (so <use> references stay live everywhere --
   layer 2's own .pattern-fish-wrap now also just references it via
   <use>) while taking up no visible space -- the standard technique
   for hidden SVG icon sprite-sheets. */
.svg-sprite-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* Layer 2 ("backlog") -- hidden from the page entirely (no scroll
   runway, no rendering) rather than deleted, so its markup/CSS/JS stay
   intact to reuse individual elements from later. display:none here
   removes .pattern-spacer's own layout box, so the scroll sequence now
   goes straight from layer 1 to layer 3 with no gap; js/hero-height.js's
   IntersectionObserver on this element still fires fine on a
   display:none target (just permanently non-intersecting), so layer 3
   ends up releasing right away, immediately after layer 1, instead of
   after this spacer's old scroll runway. */
.pattern-spacer {
  display: none;
  position: relative;
  min-height: 100vh;
}

/* Pinned to the viewport (like .site-footer-sticky) for as long as
   .hero-section is still visible, so it's held at screen position 0 --
   completely covered by hero, which has the higher z-index -- without
   any per-frame JS recalculation, which is what caused visible jitter
   in an earlier version of this that used a scroll listener updating a
   `transform` every frame (it lagged a frame or so behind the native,
   compositor-driven scroll of .hero-section). `position: sticky` was
   also tried, both plain and pulled into place with a negative margin,
   but never actually registered as "stuck" in real Chrome/Firefox
   testing -- it just scrolled in lockstep with the page the whole time.

   js/hero-height.js adds .is-released, in one single step, the instant
   an IntersectionObserver reports .hero-section has fully left the
   viewport -- switching it from fixed (viewport-relative) to absolute
   (relative to .pattern-spacer, which sits in normal flow right where
   hero ends). At that exact moment both give the same screen position
   (0), so the switch is seamless, and from then on it moves away
   normally as part of the page's normal scroll, revealing the sticky
   footer underneath in turn. */
.section-pattern {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1;
  min-height: 100vh;
  background-image: url("../img/japanese.svg");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  /* Center-center: the box inside sits in the middle of this layer on
     both axes, regardless of how tall its own content ends up being. */
  display: flex;
  align-items: center;
  justify-content: center;
  /* 3D context for .pattern-fish-wrap's translateZ parallax (see
     there). Only affects children that themselves have a 3D
     transform, so .pattern-box (which has none) is unaffected. */
  perspective: 1000px;
}

.section-pattern.is-released {
  position: absolute;
}

/* Same pin/release mechanic as .pattern-spacer/.section-pattern above,
   one layer further down -- js/hero-height.js releases this one once
   .pattern-spacer (not .hero-section) has fully left the viewport, so
   it's revealed in turn once the fish layer has scrolled away. Content,
   typography and the liquid-bg blob background are all reused from
   .hero-section/.hero-intro -- z-index 0 keeps it under .section-pattern
   (z-index 1) while both are still fixed at the same screen position,
   pre-release. */
.pattern-spacer-2 {
  position: relative;
  /* max(), not a plain 100vh -- on short viewports (small monitors,
     laptop windows) 100vh alone can end up shorter than the content
     actually needs, especially now that .pattern-fish-wrap's downward
     shift (see there) pushes further into this layer's own space.
     1200px is a floor, not a replacement -- taller viewports still get
     the full 100vh. Kept in sync with .section-pattern-2's own
     min-height below: this spacer reserves the scroll runway the fixed/
     released section actually needs, so a mismatch here would either
     leave a gap or make content jump once released. */
  min-height: max(100vh, 1200px);
}

.section-pattern-2 {
  /* display:none until js/hero-height.js adds .blobs-active -- pausing
     the blob animations (see the shared .blob rule) wasn't enough on
     its own: a paused animation with filter:blur() + will-change still
     gets promoted to its own persistent GPU compositing layer in
     Chrome, unlike the live site, which has none of these elements in
     its DOM at all. display:none pulls it out of layout/paint/compositing
     entirely instead, matching that. */
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 0;
  min-height: max(100vh, 1200px);
  /* flex-start, not center -- .hero-intro gets a fixed gap from the
     top instead (see there), which needs a fixed top-anchored flex item
     to actually mean "N px from the top" rather than "shifts the
     centered position by half that" the way it would under
     align-items:center. */
  align-items: flex-start;
  /* Traps .liquid-bg's z-index:-1 within this section instead of
     letting it escape past .section-pattern-2 and paint behind
     everything else -- same reasoning as .hero-section's isolation. */
  isolation: isolate;
}

.section-pattern-2.blobs-active {
  display: flex;
}

.section-pattern-2.is-released {
  position: absolute;
}

/* Confines the blob background to this section's own box (like
   .hero-section .liquid-bg) rather than the whole fixed viewport, and
   replaces the dark-purple canvas backdrop .liquid-bg uses by default
   (still fine for .hero-section) with neon yellow -- no dark color left
   anywhere in this layer, including the backdrop. */
.section-pattern-2 .liquid-bg {
  position: absolute;
  background-color: #faff00;
}

/* Sits between .liquid-bg (z-index:-1, always behind) and .hero-intro
   (z-index:auto, normal DOM-order stacking) in the markup -- no z-index
   of its own needed, since that's already enough to land it in front of
   the animated background but behind the text/pills. js/pill-select.js
   fades this in/out and swaps background-image on pill clicks.
   mix-blend-mode:darken is "Abdunkeln". background-size:cover (with the
   element itself covering the full section via inset:0) is what
   guarantees no letterboxing on any side -- the image always fills the
   box completely, cropping whatever doesn't fit instead of leaving a
   border. ease-in-out over 1.8s reads as a much more gradual fade than
   a short linear/ease one. */
.pill-bg-image {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  mix-blend-mode: darken;
  opacity: 0;
  transition: opacity 1.8s ease-in-out;
  pointer-events: none;
}

/* Lives inside #chat-overlay now (see below), triggered from the
   fish-widget's own input (js/fish-widget-submit.js) rather than
   reached by scrolling -- layer 2, which it used to sit inside, is
   backlogged/hidden (see .pattern-spacer). 5 grid columns wide (see the
   grid basis comment in :root); capped so it doesn't touch the
   viewport edges on narrow screens, same minimum gutter .container-grid
   uses. */
.pattern-box {
  width: calc(5 * var(--grid-col-width) + 4 * var(--grid-gutter));
  max-width: calc(100% - 32px);
  max-height: 90vh;
  overflow-y: auto;
  background-color: #dfe5d7;
  font-family: var(--font-pattern-text);
  /* 20% larger than the inherited body size (14px) -- this cascades to
     any text in here that doesn't set its own explicit font-size (chat
     messages, list items, etc.); anything that does (h2, .text-lead,
     .pattern-input, .pattern-chat-message--typing) gets its own
     dedicated +20% override further down instead, since a directly-set
     font-size always wins over an inherited one regardless of
     specificity. */
  font-size: 17px;
  /* No x/y offset -- an even, directionless glow rather than a cast
     shadow. */
  box-shadow: 0 0 40px rgba(0, 0, 0, 0.55);
  /* Column flex so .pattern-input can be pinned to the bottom of the
     box (margin-top:auto, see there) regardless of how tall the box
     is or how much content sits above it. */
  display: flex;
  flex-direction: column;
  /* js/pattern-chat.js sets an explicit pixel height (frozen at the
     current height, then doubled) when the chat activates -- this is
     what that height change animates against. */
  transition: height 0.3s ease;
}

/* Hidden by default; js/fish-widget-submit.js adds .is-active the
   moment the fish-widget's input is submitted. A plain centered
   fixed-position backdrop -- independent of layer 2's own scroll-pinned
   fixed/absolute machinery, which stays backlogged and untouched. */
.chat-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 300;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.6);
}

.chat-overlay.is-active {
  display: flex;
}

/* Same width formula as .pattern-box itself, explicitly repeated here
   rather than left to shrink-to-fit -- as a flex item of .chat-overlay,
   this wasn't reliably resolving to the exact same box width, so
   .chat-overlay-close's right:0 below wasn't quite flush with
   .pattern-box's actual right edge. position:relative (rather than
   putting the close icon directly inside .pattern-box) is what lets it
   sit above the box without .pattern-box's own overflow-y:auto (needed
   for long chats) clipping it. */
.chat-overlay-dialog {
  position: relative;
  width: calc(5 * var(--grid-col-width) + 4 * var(--grid-gutter));
  max-width: calc(100% - 32px);
}

.chat-overlay-close {
  position: absolute;
  bottom: 100%;
  right: 0;
  margin-bottom: 16px;
  width: 26px;
  height: auto;
  cursor: pointer;
  /* SVG has no fill set (defaults to black) -- brightness(0) flattens
     it to solid black regardless of source color, invert(1) then flips
     that to white (same technique as .fish-widget-close). */
  filter: brightness(0) invert(1);
}

/* .content-box (below, in source order) sets its own border-radius/
   padding at the same specificity (one class each) -- source order
   alone would let it win over a plain ".pattern-box { ... }" here,
   which is why that didn't stick before. The compound selector raises
   specificity so this override wins regardless of source order.
   padding: standard --box-padding (45px) + 5px on every side. */
.pattern-box.content-box {
  border-radius: 4px;
  padding: 50px;
}

/* Hidden once the chat activates (see js/pattern-chat.js), which adds
   .is-chat-active to .pattern-box. */
.pattern-box.is-chat-active .text-lead {
  display: none;
}

/* Replaces .text-lead in the same spot once activated -- hidden until
   then so it takes up no space and .pattern-input's margin-top lands
   against .text-lead as usual. Deliberately NOT display:flex, even
   though it's a flex ITEM of .pattern-box (flex:1 below) -- its own
   children (the messages) need normal block flow for
   .pattern-chat-message--user's float:left to actually apply; floats
   are ignored on direct children of a flex container. */
.pattern-chat {
  display: none;
  overflow-y: auto;
  /* Gap above .pattern-input-wrap -- messages scroll up through this
     space and disappear behind it the same way they already vanish
     behind the box's top edge (see overflow-y above), rather than
     butting flush against the input field. */
  margin-bottom: 20px;
  mask-repeat: no-repeat;
  -webkit-mask-repeat: no-repeat;
  /* Hide the scrollbar chrome (Firefox, then WebKit/Chromium) -- it
     stays scrollable via JS (js/pattern-chat.js scrolls it to the
     bottom on every new message), it just doesn't need a visible
     track for that, since messages are meant to look like they slide
     up and vanish behind the box's own background rather than being
     something the user manually scrolls. */
  scrollbar-width: none;
}

.pattern-chat::-webkit-scrollbar {
  display: none;
}

/* Fades content into a 30px band only on the edge(s) that actually
   have more messages scrolled past them -- js/pattern-chat.js toggles
   these classes on scroll/content change. A fade on the true first or
   last message would wrongly suggest there's more to see beyond it. */
.pattern-chat.has-more-top {
  mask-image: linear-gradient(to bottom, transparent 0, black 30px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 30px);
}

.pattern-chat.has-more-bottom {
  mask-image: linear-gradient(to bottom, black calc(100% - 30px), transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, black calc(100% - 30px), transparent 100%);
}

.pattern-chat.has-more-top.has-more-bottom {
  mask-image: linear-gradient(to bottom, transparent 0, black 30px, black calc(100% - 30px), transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 30px, black calc(100% - 30px), transparent 100%);
}

.pattern-box.is-chat-active .pattern-chat {
  display: block;
  /* Fills whatever space is left in .pattern-box's flex column between
     the headline and .pattern-input (pinned to the bottom via its own
     margin-top:auto) -- this is the fixed-size area new messages push
     up and out of the top of, rather than growing the box further.
     min-height:0 overrides flex's default min-height:auto, which would
     otherwise let this item grow to fit all its messages instead of
     respecting the space flex:1 gives it -- a common flexbox gotcha
     that would silently break the overflow/scroll behavior above. */
  flex: 1;
  min-height: 0;
}

.pattern-chat-message {
  margin: 0 0 16px;
}

/* Left edge to the midpoint of the box's own RIGHT half (100% - 75% =
   25%, i.e. starts at 0% and runs to 75%) -- mirrors
   .pattern-chat-message--user's max-width:75% on the other side.
   clear:both so it always starts on its own line below any preceding
   floated .pattern-chat-message--user, instead of potentially
   wrapping alongside it. */
.pattern-chat-message--ai {
  max-width: 75%;
  margin-right: auto;
  clear: both;
}

/* AI replies (and the static greeting) can contain multiple <p> tags
   -- the global p rule zeroes margin, so without this they'd sit flush
   against each other with no visual paragraph break. */
.pattern-chat-message--ai p {
  margin: 0 0 16px;
}

.pattern-chat-message--ai p:last-child {
  margin-bottom: 0;
}

.pattern-chat-message--ai ul:last-child {
  margin-bottom: 0;
}

/* line-height matches the global p rule (18px/28px) -- li has no font
   styling of its own otherwise and would fall back to the body rule's
   24px, reading visibly tighter than the surrounding Fließtext. */
.pattern-chat-message--ai li {
  line-height: 26px;
  margin-bottom: 5px;
}

.pattern-chat-message--ai li:last-child {
  margin-bottom: 0;
}

/* Browser default for strong is font-weight 700 -- lighter here so
   emphasis in AI replies stays legible/soft rather than heavy-bold. */
.pattern-chat-message--ai strong {
  font-weight: 500;
}

/* Waiting-loop proverbs (see js/pattern-chat.js): 7px larger than the
   inherited 18px body size, and its own display font. Color is
   animated in JS through the same palette the fish blobs use. */
.pattern-chat-message--typing {
  font-family: "Yuji Mai", var(--font-pattern-text);
  font-size: 24px;
}

/* Small, floated boxes rather than full-width bubbles: float:right and
   clear:right stack them vertically anchored to the right edge, but
   max-width:75% lets them run leftward as far as the midpoint of the
   box's own LEFT half (100% - 75% = 25%). Slightly darker than
   .pattern-box's own background (#dfe5d7 -> #cacfc4) is what
   distinguishes them from the plain-text AI responses.

   line-height overrides the inherited 28px (from the global p rule) --
   at font-size 18px that's extra leading on top of the padding, and
   browsers don't always split that leading evenly above vs. below the
   text, which reads as an uneven/larger top padding. Tightening it
   reduces that slack, though evidently not all the way to zero. */
.pattern-chat-message--user {
  float: right;
  clear: right;
  max-width: 75%;
  padding: 20px 23px;
  border-radius: 10px;
  line-height: 1.3;
  background-color: #cacfc4;
}

/* Horizontally centered on the same point as .pattern-box (10 grid
   columns wide, same width formula, see the grid basis comment in
   :root) -- but anchored vertically to .section-pattern's own bottom
   edge (bottom:-200px) instead of centered, so it hangs a fixed, fully
   predictable 200px past that edge regardless of the section's actual
   height. An earlier version centered the box (inset:0 + margin:auto on
   all sides) and then nudged it down via translateY -- centering ties
   the resting position to the section's height, which on short
   viewports (small monitors, the min-height:max(100vh,1200px) floor
   elsewhere in this file exists for exactly this reason) pulled the
   fish up into .pattern-box's headline instead of staying clear of it.
   Anchoring to the bottom edge directly removes that dependency
   entirely -- left:0/right:0 with the explicit width below still
   centers it horizontally via the same auto-margin mechanism, since
   that axis has no such problem.

   overflow:hidden clips the fish to this box's own bounds -- it's what
   makes the parallax below a *local* effect instead of one that bleeds
   across the whole page (unlike background-attachment:fixed, tried
   first here: it computes size and position against the viewport
   rather than the element, which looks fine full-bleed but renders as
   an arbitrary, broken-looking crop inside a small clipped box like
   this one).

   The parallax itself: pushed back in 3D space (translateZ) under
   .section-pattern's `perspective`, which makes it visually drift
   slower than .pattern-box (which has no 3D transform, and so scrolls
   at the normal 1x rate) as .section-pattern scrolls normally once
   released -- a light depth effect with no scroll-linked JS at all,
   fully compositor-driven. scale compensates for perspective's
   foreshortening so it still renders at its intended on-screen size:
   scale = (perspective + |translateZ|) / perspective = 1200/1000 = 1.2.
   Apparent scroll speed becomes perspective / (perspective +
   |translateZ|) = 1000/1200, i.e. roughly 83% of normal -- a light
   effect; push translateZ further negative (with scale adjusted to
   match) for a stronger one. */
.pattern-fish-wrap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -200px;
  margin: auto;
  width: calc(10 * var(--grid-col-width) + 9 * var(--grid-gutter));
  max-width: calc(100% - 32px);
  aspect-ratio: 1120.9 / 864;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  transform: translateZ(-200px) scale(1.2);
}

/* Reused here (moved out of the backlogged layer 2, not duplicated --
   see index.html) as the chat overlay's backdrop, centered behind
   .chat-overlay-dialog rather than anchored to a scrolling section's
   bottom edge. The scroll-parallax translateZ/scale/perspective trick
   above doesn't apply to a static, fixed-position overlay (nothing
   ever scrolls it), so this plainly centers on both axes instead; the
   compound selector (two classes) is needed purely for specificity, to
   win over the plain .pattern-fish-wrap rule regardless of source
   order. No explicit z-index needed to sit behind .chat-overlay-dialog
   -- both are position:absolute/relative with the default z-index:auto,
   so plain DOM order (this comes first, see index.html) already
   decides the stacking. overflow:visible overrides the base rule's own
   overflow:hidden (there to clip the scroll-parallax scale's overscan,
   irrelevant here) -- left as hidden, it also clipped .pattern-fish's
   own fish-swim-in-left entrance while still off to the left. */
.chat-overlay .pattern-fish-wrap {
  top: 50%;
  bottom: auto;
  overflow: visible;
  transform: translateY(-50%);
}

/* Fully native SVG now (mask, blur filter, gradients, and the .fish-
   blob circles all live inside the inlined <svg> in index.html) --
   the previous version used this file as an external CSS mask-image,
   which rendered as completely invisible in real-browser testing
   (mask-image's visibility/loading semantics for external SVG sources
   have enough cross-browser and possibly file://-related ambiguity
   that it wasn't worth chasing further). An in-document SVG <mask>
   sidesteps all of that -- same document, same coordinate space, no
   external resource involved. */
.pattern-fish {
  display: block;
  width: 100%;
  height: 100%;
}

/* Timed to start only once .fish-widget-fish (the small fish in the
   widget) has fully finished its own exit -- .is-closing's
   fish-exit-fall there runs 0.5s delay + 0.6s duration = done at 1.1s
   (see css/style.css further down), so 1.2s leaves a hair of buffer
   rather than a hard cut. A plain fixed delay, not JS-driven
   coordination, per explicit request -- both this and the widget's
   own hide are already triggered by the same event (the chat overlay
   opening), so their timelines start together regardless. Swims in
   from the left (negative translateX, mirroring .fish-widget-fish's
   own from-the-right fish-swim-in) with the same wave-like path/timing,
   then eases into the same fish-idle-sway loop the small fish uses --
   reusing that existing keyframe animation rather than a duplicate. */
.chat-overlay .pattern-fish {
  animation:
    fish-swim-in-left 3.5s 1.2s linear both,
    fish-idle-sway 7s ease-in-out 3s infinite;
}

/* Mirror of fish-swim-in (see further down) -- same wave-like
   deceleration shape, just entering from the opposite side (negative
   translateX instead of positive). */
@keyframes fish-swim-in-left {
  0%   { transform: translate(-260%, 0)    rotate(0deg); }
  10%  { transform: translate(-220%, -3%)  rotate(-2deg); }
  20%  { transform: translate(-180%, -7%)  rotate(-4deg); }
  30%  { transform: translate(-145%, -6%)  rotate(-3deg); }
  40%  { transform: translate(-113%, -2%)  rotate(0deg); }
  50%  { transform: translate(-86%, 3%)    rotate(2deg); }
  60%  { transform: translate(-61%, 6%)    rotate(3deg); }
  70%  { transform: translate(-41%, 4%)    rotate(2deg); }
  78%  { transform: translate(-26%, 1.5%)  rotate(1deg); }
  84%  { transform: translate(-16%, 0.5%)  rotate(0.5deg); }
  89%  { transform: translate(-8.4%, 0%)   rotate(0deg); }
  93%  { transform: translate(-3.7%, 0%)   rotate(0deg); }
  96%  { transform: translate(-1.3%, 0%)   rotate(0deg); }
  100% { transform: translate(0, 0)        rotate(0deg); }
}

/* Hidden (opacity:0) at rest -- js/pattern-chat.js adds .is-animating
   to #fish-body (not .pattern-fish, which only <use>s it -- a <use>'s
   cloned rendering can't be targeted by descendant selectors from
   outside) for the 4s AI response delay, fading these in, moves them
   around (via cx/cy attributes) during that window, then removes the
   class again, fading back to the plain resting-gold <rect>
   underneath. Each circle's own radial gradient (see the inline <defs>)
   already fades to transparent, and the shared <feGaussianBlur> filter
   on their wrapping <g> softens their edges further -- together that's
   what makes them blend into each other and into the resting color
   instead of looking like flatly colored discs. Palette (bronze, amber,
   champagne highlight, koi vermillion, optional jade) is kept in the
   same warm-metal saturation/brightness range so the cycle reads as
   liquid metal shifting rather than a color-swap effect. */
.fish-blob {
  opacity: 0;
  transition: opacity 0.6s ease;
}

#fish-body.is-animating .fish-blob {
  opacity: 0.95;
}

@media (prefers-reduced-motion: reduce) {
  .fish-blob {
    transition: none;
  }
}

/* Headline and focus text (a deliberate one-off exception to the
   site's usual headline/text color rule -- see :root -- for this
   specific light-background box). .pattern-chat-message is a <div>
   (not a <p>, see js/pattern-chat.js) so it's listed explicitly here.
   font-size/line-height are needed here too -- AI replies stream in as
   real <p> tags (see api/chat.php), and the global p rule (14px/22px)
   matches those directly, which wins over .pattern-box's own inherited
   17px regardless of that ancestor rule's specificity. */
.pattern-box p,
.pattern-box .pattern-chat-message {
  color: #021d25;
  font-size: 17px;
  line-height: 26px;
}

/* Scoped to this specific paragraph -- space below the focus text,
   separate from .pattern-input's own margin-top:auto that pins it to
   the bottom of the box. */
.pattern-box .text-lead {
  /* 20% larger than the shared global .text-lead (18px/26px) -- scoped
     here so layer 1's own Fokustext (which also matches that shared
     rule) is unaffected. */
  font-size: 22px;
  line-height: 31px;
  margin-bottom: 60px;
}

/* font-family repeated here (not just inherited from .pattern-box):
   the global h1-h6 rule sets font-family directly on this element too,
   and a directly-specified value always wins over an inherited one
   regardless of the ancestor rule's specificity -- so relying on
   inheritance from .pattern-box alone doesn't actually override it.
   Regular weight throughout, including the highlighted phrase below.
   margin-bottom overrides the global h2 default (20px) with this box's
   own spacing to the text below it. */
.pattern-box h2 {
  font-family: var(--font-pattern-text);
  font-weight: 400;
  color: #021d25;
  /* 20% larger than the global h2 (24px) -- scoped here so other h2s
     on the page are unaffected. */
  font-size: 29px;
  line-height: 41px;
  margin-bottom: 30px;
}

.pattern-box h2 .highlight {
  color: #021d25;
}

/* margin-top:auto (rather than a fixed gap) is what pins this to the
   bottom of .pattern-box's flex column, whatever the box's height --
   moved here from .pattern-input itself now that the input is wrapped
   (see .pattern-input-icon), since this wrapper is .pattern-box's
   actual flex-column child now. */
.pattern-input-wrap {
  position: relative;
  margin-top: auto;
}

.pattern-input {
  display: block;
  width: 100%;
  height: 61px;
  padding: 19px;
  /* Reserves room on the right for .pattern-input-icon (36.8px wide,
     inset 19px from the edge -- 55.8px total to its left edge) plus
     the gap between the icon and the text (14.4px): 55.8 + 14.4 =
     70.2px (all scaled up 20% together with everything else here, on
     top of the earlier 20% site-wide shrink). So typed text that
     overflows and scrolls left (the input's own native behavior once
     it's longer than the field) stops there instead of running under
     the icon. */
  padding-right: 70.2px;
  border: none;
  border-radius: 10px;
  background-color: #cad1c9;
  color: #021d25;
  font-family: var(--font-pattern-text);
  font-size: 19px;
  font-weight: 400;
}

/* Custom focus ring instead of the browser default (usually ~2px) --
   twice as wide. outline (not border) so it doesn't affect the
   input's own box size/layout. */
.pattern-input:focus-visible {
  outline: 4px solid #d4a017;
}

.pattern-input::placeholder {
  color: #4c646c;
  font-size: 19px;
  font-weight: 400;
  /* Firefox lowers placeholder opacity by default -- keep the exact
     color as specified. */
  opacity: 1;
}

/* Now a <button> (js/pattern-chat.js submits the message on click) --
   the reset properties strip the browser's default button chrome so
   it still just looks like a plain icon. */
.pattern-input-icon {
  position: absolute;
  top: 50%;
  right: 19px;
  transform: translateY(-50%);
  width: 36.8px;
  height: 36.8px;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
}

.pattern-input-icon svg {
  display: block;
  width: 100%;
  height: 100%;
  /* Matches .pattern-input's own text color (see there). */
  fill: #021d25;
}

.pattern-input-icon:hover svg,
.pattern-input-icon:active svg {
  fill: #d97a56;
}

/* Paused by default -- these are continuously-morphing, heavily
   blurred elements (expensive to keep compositing every frame), so
   they only actually run while their containing section (.hero-section
   or #section-pattern-2, see the shared .blobs-active rule below and
   js/hero-height.js) is somewhere in the viewport. Without this, both
   sections' blobs would composite every frame from page load onward
   regardless of which one is actually on screen -- confirmed to cause
   visible flicker on at least one Chrome/macOS/monitor combination. */
.blob {
  position: absolute;
  width: 58vmax;
  height: 58vmax;
  filter: blur(75px);
  opacity: 0.9;
  will-change: transform, border-radius;
  animation-play-state: paused;
}

/* Shared by .hero-section and #section-pattern-2 -- js/hero-height.js
   toggles this on whichever one currently has its own reserved scroll
   space intersecting the viewport. */
.blobs-active .blob {
  animation-play-state: running;
}

/* Shades are the base canvas color (#1a042e) scaled by at most +/-25%
   (same hue/saturation, only lightness changes within that range).
   Position (transform) is driven entirely by js/blob-random.js via chained
   CSS transitions -- keyframes below only morph the border-radius shape,
   so randomizing the target position never fights a running animation. */
.blob-1 {
  background: #21053a; /* +25% */
  top: -15%;
  left: -15%;
  animation: shape1 3.2s ease-in-out infinite;
}

.blob-2 {
  background: #140323; /* -25% */
  top: 15%;
  right: -20%;
  animation: shape2 3.8s ease-in-out infinite;
}

.blob-3 {
  background: #1e0535; /* +15% */
  bottom: -20%;
  left: 5%;
  animation: shape3 3s ease-in-out infinite;
}

.blob-4 {
  background: #160327; /* -15% */
  bottom: -15%;
  right: 0%;
  animation: shape4 3.5s ease-in-out infinite;
}

/* Very subtle pink accent blobs. A radial gradient fading to fully
   transparent keeps the hue clean (no blend-mode mixing with whatever sits
   underneath), with the alpha baked into the gradient stop so it stays
   easy to dose precisely. */
.blob-5 {
  background: radial-gradient(circle, rgba(255, 0, 196, 0.08) 0%, rgba(255, 0, 196, 0) 70%);
  opacity: 1;
  width: 46vmax;
  height: 46vmax;
  top: 30%;
  left: 35%;
  animation: shape2 7s ease-in-out infinite;
}

.blob-6 {
  background: radial-gradient(circle, rgba(255, 0, 196, 0.04) 0%, rgba(255, 0, 196, 0) 70%);
  opacity: 1;
  width: 26vmax;
  height: 26vmax;
  bottom: 8%;
  right: 12%;
  animation: shape4 6.5s ease-in-out infinite;
}

/* Layer 3's own palette/pace -- neon green-yellow (chartreuse direction,
   not pure green) + neon yellow, no dark shades anywhere including the
   backdrop (.section-pattern-2 .liquid-bg, near the section's own base
   rules, overrides the canvas color to neon yellow). Only animation-
   duration is overridden (not the whole `animation` shorthand) so
   shape/timing-function/iteration-count still come from the base
   .blob-N rules above -- just faster, for a livelier feel than
   .hero-section's blobs. Actual position (translate/scale) speed is
   handled separately in js/blob-random.js, which uses a faster range
   for blobs inside #section-pattern-2. */
#section-pattern-2 .blob-1 {
  background: #7fff00;
  animation-duration: 0.95s;
}

#section-pattern-2 .blob-2 {
  background: #faff00;
  animation-duration: 1.15s;
}

#section-pattern-2 .blob-3 {
  background: #a6ff00;
  animation-duration: 0.88s;
}

#section-pattern-2 .blob-4 {
  background: #d4ff00;
  animation-duration: 1.08s;
}


@keyframes shape1 {
  0%, 100% { border-radius: 42% 58% 63% 37% / 41% 44% 56% 59%; }
  33% { border-radius: 63% 37% 40% 60% / 55% 60% 40% 45%; }
  66% { border-radius: 37% 63% 55% 45% / 48% 40% 60% 52%; }
}

@keyframes shape2 {
  0%, 100% { border-radius: 55% 45% 48% 52% / 38% 55% 45% 62%; }
  33% { border-radius: 38% 62% 55% 45% / 60% 40% 55% 45%; }
  66% { border-radius: 60% 40% 35% 65% / 45% 58% 42% 55%; }
}

@keyframes shape3 {
  0%, 100% { border-radius: 48% 52% 60% 40% / 55% 45% 55% 45%; }
  33% { border-radius: 62% 38% 45% 55% / 42% 60% 38% 58%; }
  66% { border-radius: 40% 60% 52% 48% / 58% 42% 60% 40%; }
}

@keyframes shape4 {
  0%, 100% { border-radius: 50% 50% 42% 58% / 45% 55% 48% 52%; }
  33% { border-radius: 40% 60% 58% 42% / 62% 38% 55% 45%; }
  66% { border-radius: 58% 42% 38% 62% / 48% 52% 42% 58%; }
}

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

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--font-base);
}

h1 {
  font-size: 40px;
  line-height: 48px;
  font-weight: 600;
}

h2 {
  font-size: 24px;
  line-height: 29px;
  font-weight: 600;
  margin-bottom: 20px;
}

h3 {
  font-size: 18px;
  line-height: 22px;
  font-weight: 500;
}

/* h4/h5 weren't part of the original type spec (only h1-h3 + p were given).
   Provisional sizes so the Datenschutz sub-subheadings aren't unstyled. */
h4 {
  font-size: 14px;
  line-height: 22px;
  font-weight: 500;
}

h5 {
  font-size: 14px;
  line-height: 22px;
  font-weight: 500;
}

h6 {
  font-size: 14px;
  line-height: 22px;
  font-weight: 500;
}

p {
  margin: 0;
  font-size: 14px;
  line-height: 22px;
  font-weight: 400;
  color: var(--color-text-light);
}

/* Grid container (Adobe XD basis)
   12 * (122 + 16) = 1656 Inhaltsbreite; + 2 * 140 Rand = 1920 Gesamt-Artboard.
   Rand skaliert fluid mit vw, gedeckelt auf 140px ab 1920px Viewport-Breite. */
.container-grid {
  width: 100%;
  max-width: var(--grid-artboard);
  margin-inline: auto;
  padding-inline: clamp(16px, 7.292vw, var(--grid-margin));
}

.container-grid .row {
  --bs-gutter-x: var(--grid-gutter);
}

/* Hero intro */
/* width:100% matters on both layers now that .hero-section is also
   display:flex (see there) alongside layer 3's #section-pattern-2: a
   flex item with no explicit width shrinks to its own content size and
   sits at the flex container's start edge -- so .container-grid's
   margin-inline:auto centering inside it never had a full-width box to
   center within, leaving the whole block flush against the left edge
   instead of aligned with the grid. No margin-top here (unlike layer
   3's own override further down) -- .hero-section's
   justify-content:center handles vertical position on layer 1 now. */
.hero-intro {
  width: 100%;
}

/* Layer 3's own light neon-yellow/green backdrop needs dark text for
   contrast -- layer 1's .hero-intro keeps the default white/light
   headings and body-text color (from the h1-h6/p rules), only layer 3's
   instance is overridden here. */
#section-pattern-2 .hero-intro h2,
#section-pattern-2 .hero-intro .text-lead,
#section-pattern-2 .hero-intro .btn-pill {
  color: #000;
}

/* #section-pattern-2 is now align-items:flex-start (not center, see
   there), so this is a plain, fixed gap from the top -- no centering-
   related halving math to account for anymore. */
#section-pattern-2 .hero-intro {
  margin-top: 200px;
}

/* Pink underline on part of layer 3's headline -- keeps the text's own
   color (black, from the h2 rule above), only the underline itself
   picks up the brand pink accent. */
.underline-pink {
  text-decoration: underline;
  text-decoration-color: var(--bs-pink);
}

/* h1-visual-size h2 (layer 3 has no h1 of its own -- semantically it
   stays an h2, just styled at h1's size). h2 tag + class beats the
   plain ".hero-intro h2" rule's specificity so this wins regardless of
   source order. */
.hero-intro h2.h1-style {
  font-size: 40px;
  line-height: 48px;
}

/* Legal pages (Impressum, Datenschutz) */
.legal-page {
  margin-top: 150px;
}

.legal-content p {
  margin: 0 0 16px;
}

.legal-content h1 {
  margin-bottom: 32px;
}

.legal-content h2 {
  margin-bottom: 16px;
}

.legal-content h1 {
  text-decoration: underline;
  text-decoration-color: #e2ff00;
}

.legal-content h3 {
  margin-bottom: 8px;
}

.legal-content h4 {
  margin-bottom: 8px;
}

.legal-content h5 {
  margin-bottom: 8px;
}

.legal-content h6 {
  margin-bottom: 8px;
}

.legal-content ul {
  margin: 0 0 16px;
  padding-left: 20px;
}

.legal-content li {
  margin-bottom: 8px;
}

.legal-content a {
  color: inherit;
}

.legal-content p:last-child,
.legal-content ul:last-child {
  margin-bottom: 0;
}

/* Standard box for text and/or image content. */
.content-box {
  border-radius: var(--box-radius);
  padding: var(--box-padding);
}

/* 3 grid columns wide: 3 * 122px + 2 * 16px gutter = 398px */
.section-divider {
  width: 398px;
  max-width: 100%;
  height: 1px;
  margin: 90px 0 150px;
  border: 0;
  background-color: var(--bs-pink);
  opacity: 1;
}

.reveal-section {
  opacity: 0;
  transition: opacity 1.2s ease;
}

.reveal-section.is-visible {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .reveal-section {
    opacity: 1;
    transition: none;
  }
}

.text-lead {
  font-size: 18px;
  line-height: 26px;
  font-weight: 400;
}

/* Empty until js/pill-select.js fills it in on a pill click. Faded via
   opacity + .is-visible (toggled by that script) rather than the
   :empty/display:none this used before -- display can't be transitioned
   smoothly, and an empty paragraph already collapses to no visible
   height on its own regardless. */
#pill-text {
  /* 5 grid columns wide (see the grid basis comment in :root; same
     formula .pattern-box uses). */
  width: calc(5 * var(--grid-col-width) + 4 * var(--grid-gutter));
  max-width: 100%;
  /* position:absolute (relative to .pill-sublinks-row, see there) at a
     fixed column-4 offset, rather than margin-left as a flex sibling of
     .pill-sublinks -- as a normal flex child, margin-left is relative
     to wherever this would otherwise sit right after .pill-sublinks,
     so it compounded with .pill-sublinks' own width (which varies by
     topic: some link lists are wider than others), making this drift
     left/right depending on which topic was open. Absolute positioning
     anchors it to the row's own left/top edge unconditionally instead,
     also guaranteeing it starts at the same height as the links
     (top:0) regardless of .pill-sublinks' height. margin:0 is needed on
     top of that -- .hero-intro .text-lead's own margin-top:45px (a
     block-flow spacing rule, still matching via the shared .text-lead
     class) doesn't stop applying just because this is now absolutely
     positioned; margin still adds to the computed top offset instead of
     being replaced by it. */
  position: absolute;
  top: 0;
  left: calc(3 * var(--grid-col-width) + 3 * var(--grid-gutter));
  margin: 0;
  opacity: 0;
  transition: opacity 0.6s ease;
  /* Each sublink's data-text (see index.html) is two paragraphs
     separated by a blank line ("\n\n") -- this is what renders that as
     an actual paragraph break instead of collapsing it into one run of
     text, since js/pill-select.js sets it via textContent (a single
     plain-text string, no <p> tags). */
  white-space: pre-line;
}

#pill-text.is-visible {
  opacity: 1;
}

.hero-intro .text-lead {
  margin-top: 45px;
}

/* Sticky reveal footer: sits behind normal page content (position: fixed,
   first in DOM) and only becomes visible once the content above it has
   scrolled past -- see js/footer-reveal.js for the bottom-spacer height
   that makes that math work. */
.site-footer-sticky {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #1a042e;
  color: var(--color-text-light);
  padding-top: 200px;
  padding-bottom: 200px;
}

.footer-address {
  margin-top: 24px;
  font-size: 14px;
  line-height: 22px;
  color: var(--color-text-light);
}

.footer-phone {
  margin: 24px 0 0;
  font-size: 14px;
  line-height: 22px;
  color: var(--color-text-light);
}

.footer-whatsapp-label {
  margin: 24px 0 0;
  font-size: 14px;
  line-height: 22px;
  color: var(--color-text-light);
}

.footer-whatsapp-number {
  display: inline-block;
  margin-top: 4px;
  font-size: 14px;
  line-height: 22px;
  color: var(--color-text-light);
  text-decoration: underline;
}

.footer-whatsapp-number:hover,
.footer-whatsapp-number:focus-visible {
  color: #e2ff00;
}

.footer-heading {
  margin: 0 0 24px;
  font-size: 14px;
  line-height: 22px;
  font-weight: 600;
  color: var(--color-text-light);
}

.footer-link-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.footer-link-list li {
  margin-bottom: 12px;
}

.footer-link-list a {
  color: var(--color-text-light);
  text-decoration: underline;
}

.footer-link-list a:hover,
.footer-link-list a:focus-visible {
  color: #e2ff00;
}

/* Row of topic pills between the headline and the Fokustext -- wraps on
   narrow viewports rather than forcing 6 pills onto one line. Neutralizes
   .btn-pill's own margin-top (meant for "gap below the paragraph before
   a single button", not a bespoke pill row) since gap handles spacing
   here instead. */
.pill-group {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 100px;
}

/* --btn-outline drives both the border color and (via the base rule's
   animation) the pulsing box-shadow color -- overriding it to pink here
   would still leave the pulse running, so animation:none is also needed
   to actually remove that effect for these pills specifically. #a6007f
   is a darkened version of --bs-pink (#ff00c4, ~65% value in HSV).
   These are now <button> elements (js/pill-select.js makes them
   clickable), not <a>, so font-family/cursor need restating -- buttons
   don't inherit page typography or get pointer cursor by default the
   way links do. */
.pill-group .btn-pill {
  margin-top: 0;
  --btn-outline: #a6007f;
  animation: none;
  font-family: inherit;
  cursor: pointer;
}

/* Overrides the base rule's own :hover/:focus-visible (which sets
   --btn-outline to yellow) with the full-brightness brand pink instead
   -- higher specificity (.pill-group .btn-pill:hover vs plain
   .btn-pill:hover) wins regardless of source order. */
.pill-group .btn-pill:hover,
.pill-group .btn-pill:focus-visible {
  --btn-outline: var(--bs-pink);
}

/* Selected state (js/pill-select.js toggles .is-active on click) --
   otherwise identical to :hover/:focus-visible below (same bright-pink
   --btn-outline, same background/text, neither of which this rule
   touches) plus a neon glow outside the border in that same outline
   color. The #section-pattern-2 prefix is needed purely for
   specificity: .hero-intro .btn-pill's own color:#000 rule (which
   these pills also match) is ID-qualified, and an ID always outweighs
   any number of classes regardless of source order. */
#section-pattern-2 .pill-group .btn-pill.is-active {
  --btn-outline: var(--bs-pink);
  /* Dimmer/smaller than a full-strength glow (rgba, not the solid
     --btn-outline) -- and the transition above (added to .btn-pill's
     own list) is what fades it in smoothly rather than snapping on. */
  box-shadow: 0 0 10px 1px rgba(255, 0, 196, 0.45);
}

/* Holds .pill-sublinks and #pill-text side by side -- left-aligned,
   stacked links under the left button, text starting at column 4 (see
   both below), rather than the links wrapping full-width with the text
   underneath. align-items:flex-start so a short link list doesn't
   stretch to match #pill-text's own (often taller) height. */
.pill-sublinks-row {
  /* Containing block for #pill-text's own position:absolute (see
     there). */
  position: relative;
  display: flex;
  align-items: flex-start;
  margin-top: 50px;
}

/* 3 grid columns wide, max -- long link labels wrap onto a second line
   within that width instead of running arbitrarily wide (the flex
   column's items are inline-level <a>s, so they wrap normally once
   constrained like this). */
.pill-sublinks {
  max-width: calc(3 * var(--grid-col-width) + 2 * var(--grid-gutter));
}

/* Each .pill-sublink-group corresponds to one .btn-pill's data-topic --
   js/pill-select.js toggles .is-active on whichever one matches the
   currently selected pill (only ever one at a time), hiding the rest.
   Vertical stack, not a wrapping row: left-aligned under the left
   button rather than spreading across the full row. */
.pill-sublink-group {
  display: none;
  flex-direction: column;
  align-items: flex-start;
  gap: 15px;
}

.pill-sublink-group.is-active {
  display: flex;
}

/* Underlined, clickable second-level links -- color:#000 to match this
   layer's dark-on-neon text (see .hero-intro h2/.text-lead/.btn-pill
   above), pink underline for the same visual language as the headline's
   own .underline-pink span. js/pill-select.js toggles .is-active
   per group (single-select, like the pills themselves) to show that
   link's own text in #pill-text below. */
.pill-sublink {
  color: #000;
  /* At least .text-lead's own 18px (the Fokustext) -- both shrunk 20%. */
  font-size: 18px;
  font-weight: 500;
  text-decoration: underline;
  text-decoration-color: var(--bs-pink);
  cursor: pointer;
  /* Fades in fresh every time its group goes display:none -> flex
     (js/pill-select.js's .is-active toggle) -- the same "redisplay
     restarts the animation" behavior this codebase already relies on
     elsewhere. Each link's own animation-delay (below) is what staggers
     them one after another instead of all popping in at once. */
  opacity: 0;
  animation: pill-sublink-in 0.3s ease forwards;
}

@keyframes pill-sublink-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Staggered by ~70ms per link -- quick enough that even the longest
   group (7 links, "Systeme") finishes well under a second, but still
   reads as one link after another rather than a single flash. */
.pill-sublink:nth-child(1) { animation-delay: 0s; }
.pill-sublink:nth-child(2) { animation-delay: 0.07s; }
.pill-sublink:nth-child(3) { animation-delay: 0.14s; }
.pill-sublink:nth-child(4) { animation-delay: 0.21s; }
.pill-sublink:nth-child(5) { animation-delay: 0.28s; }
.pill-sublink:nth-child(6) { animation-delay: 0.35s; }
.pill-sublink:nth-child(7) { animation-delay: 0.42s; }

.pill-sublink:hover,
.pill-sublink:focus-visible,
.pill-sublink.is-active {
  color: var(--bs-pink);
}

.btn-pill {
  --btn-outline: var(--bs-pink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 53px;
  padding: 0 28px;
  margin-top: 50px;
  border: 3px solid var(--btn-outline);
  border-radius: 50px;
  font-size: 14px;
  line-height: 24px;
  font-weight: 600;
  color: #fff;
  text-decoration: none;
  background: transparent;
  transition: border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease, box-shadow 0.3s ease;
  animation: btn-pill-pulse 2.4s ease-in-out infinite;
}

.btn-pill:hover,
.btn-pill:focus-visible {
  --btn-outline: #e2ff00;
  background-color: transparent;
  color: #fff;
}

@keyframes btn-pill-pulse {
  0%, 100% {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--btn-outline) 45%, transparent);
  }
  50% {
    box-shadow: 0 0 0 6px color-mix(in srgb, var(--btn-outline) 0%, transparent);
  }
}

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

.logo-link {
  display: inline-block;
  line-height: 0;
}

/* Sticky logo, pinned above every scroll layer -- confirmed via an
   isolated test not to cause the GPU-compositing flicker earlier
   attempts hit (that was apparently tied to the layer-3 animation
   lifecycle bug / blob-mouse.js, both since fixed/removed). .site-header
   (not a bare .logo-link) scopes this to the header's instance only,
   leaving the footer's separate .logo-link untouched -- same class,
   different elements. z-index 10 clears the highest layer z-index in
   use (.hero-section's 2) with headroom. */
.site-header .logo-link {
  position: fixed;
  top: 50px;
  left: 50px;
  z-index: 10;
  transition: opacity 0.3s ease;
}

/* Sticky, bottom-right (flush, no offset), ahead of everything else on
   the page including the logo (z-index 10) -- 20 clears every other
   z-index in use with headroom. Sizing/positioning lives here on the
   wrapper (25% of viewport width) rather than on the image directly, so
   future sibling components can be added inside without redoing the
   layout -- they'll just stack/arrange within this same box. */
.fish-widget {
  position: fixed;
  bottom: 0;
  right: 0;
  z-index: 20;
  width: 25vw;
  pointer-events: none;
}

/* Sits above the widget's own imagery (bottom:100%). pointer-events:auto
   re-enables interaction just for this block -- .fish-widget itself is
   pointer-events:none so the decorative fish/placeholder never block
   clicks on whatever's behind them, but the input here needs to stay
   clickable/typeable. Fades in smoothly once the fish has arrived (see
   .fish-widget-fish's own 0.4s + 1.8s = 2.2s). */
/* Grid, not flex -- .pattern-input-wrap and .fish-widget-back-btn share
   the same grid-row (see below) so swapping between them on submit (see
   .fish-widget.is-submitted further down) can't change this container's
   own height. That matters because this whole block is positioned by
   its BOTTOM edge (bottom:100% + margin-bottom, anchored a fixed
   distance above .fish-widget, so it never grows down into the widget
   below it) -- with a flex column, hiding any child shrinks the
   container, and since it's bottom-anchored, a shorter container pulls
   everything still visible (including the button) down toward that
   fixed bottom point instead of leaving it where the input used to sit. */
.fish-widget-prompt {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  margin-bottom: 210px;
  display: grid;
  grid-template-rows: auto auto auto;
  gap: 10px;
  pointer-events: auto;
  opacity: 0;
  animation: fish-widget-prompt-in 0.8s ease 2.2s both;
}

/* align-self:start (rather than the grid default, stretch) so each
   sits at row 2's top edge sized to its own natural height, instead of
   stretching to match whichever of the two is taller -- needed since
   .fish-widget-back-btn reuses .btn-pill wholesale, including its own
   margin-top:50px (meant for spacing pill buttons apart in the hero
   section), which otherwise inflated row 2's reserved height by that
   much even while the button was invisible, and stretch would have
   spread that extra space into .pattern-input-wrap's own box too. See
   also the margin-top:0 override below that kills that 50px directly. */
.fish-widget-prompt .pattern-input-wrap,
.fish-widget-back-btn {
  grid-row: 2;
  grid-column: 1;
  align-self: start;
}

.fish-widget-caption {
  grid-row: 3;
}

@keyframes fish-widget-prompt-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Scoped to this widget only (not .pattern-input-wrap itself), so the
   main chatbox's own input is untouched. */
.fish-widget-prompt .pattern-input-wrap {
  width: 63%;
}

/* Dimmed until focused -- draws the eye less when idle, full opacity
   (and clarity) once actually being typed into. */
.fish-widget-prompt .pattern-input {
  background-color: #fff;
  opacity: 0.3;
  transition: opacity 0.3s ease;
  /* Restores .pattern-input's own pre-shrink height/padding/font-size --
     the site-wide 20% reduction skips .fish-widget entirely, but this
     input reuses the shared .pattern-input class. */
  height: 64px;
  padding: 20px;
  padding-right: 73.4px;
  font-size: 20px;
}

/* Same reasoning as .fish-widget-prompt .pattern-input above -- this
   reuses the shared .pattern-input-icon class, which the site-wide 20%
   shrink also touches. */
.fish-widget-prompt .pattern-input-icon {
  right: 20px;
  width: 38.4px;
  height: 38.4px;
}

.fish-widget-prompt .pattern-input:focus {
  opacity: 1;
}

.fish-widget-question {
  grid-row: 1;
  /* Matches .pattern-input-wrap's own 63% (see above) instead of a hard
     <br> in the markup, so it wraps naturally at that width whatever
     the actual text ends up being. */
  width: 63%;
  margin: 0 0 10px;
  color: #fff;
  font-size: 28px;
  font-weight: 300;
  line-height: 1.2;
  font-family: var(--font-base);
}


.fish-widget-caption {
  margin: 0;
  /* Pulls it closer to .pattern-input-wrap above, on top of/reducing
     .fish-widget-prompt's own 10px grid gap. */
  margin-top: -6px;
  color: #fff;
  font-size: 14px;
  text-align: left;
}

/* js/fish-widget-submit.js adds .is-submitted to .fish-widget itself
   once a message has actually been sent -- swaps the input for
   .fish-widget-back-btn below (rather than showing both at once) and
   hides .fish-widget-close (further down) too. Scoped to .fish-widget
   rather than just .fish-widget-prompt so that one class also reaches
   .fish-widget-close, which lives outside the prompt as .fish-widget's
   own separate child. visibility, not display -- all three keep
   reserving their grid-row's space either way (see .fish-widget-prompt
   above), but display:none would let that row collapse and shrink the
   grid overall, which is exactly the height change bottom-anchoring
   turns into unwanted downward drift. */
.fish-widget.is-submitted .fish-widget-question,
.fish-widget.is-submitted .pattern-input-wrap,
.fish-widget.is-submitted .fish-widget-caption {
  visibility: hidden;
}

/* Reuses .btn-pill's own look/pulse wholesale (same as "Jetzt
   kennenlernen") -- only overriding what was actually asked to differ:
   a border noticeably darker than --bs-pink (#a6007f, the same darker
   pink already used elsewhere for a pill's active/selected state), and
   horizontally centered rather than stretched to the full width of the
   grid column (a grid item stretches to fill its column by default,
   since .btn-pill itself has no explicit width of its own to resist
   that). The compound .fish-widget-back-btn.btn-pill selector (two
   classes) is needed purely for specificity, to reliably win over the
   plain .btn-pill rule regardless of source order. visibility (not
   display), for the same reason as .pattern-input-wrap/.fish-widget-
   caption above -- this and .pattern-input-wrap share one grid-row,
   and both need to keep reserving that row's height even while only
   one of them is actually shown, or the row (and so the whole
   bottom-anchored grid) would resize depending on which one's
   currently invisible. */
.fish-widget-back-btn.btn-pill {
  --btn-outline: #a6007f;
  /* Kills .btn-pill's own margin-top:50px (meant for spacing pill
     buttons apart in the hero section) -- left in place, it inflated
     row 2's reserved height by that much even while this button was
     invisible, which is what pushed .pattern-input-wrap down instead
     of the button sitting exactly where the input already was. */
  margin-top: 0;
  justify-self: center;
  visibility: hidden;
  /* No height/padding/font-size/line-height override here anymore --
     this button is meant to match the other buttons' current design,
     so it now just takes the shared .btn-pill's own (already 20%
     shrunk) sizing like everything else, instead of staying pinned to
     the pre-shrink size. */
}

.fish-widget.is-submitted .fish-widget-back-btn.btn-pill {
  visibility: visible;
}

/* height:auto keeps its own proportions (source image is 1638x239, a
   wide strip, not square/portrait) rather than distorting it into a
   fixed box. No overflow:hidden anywhere around this -- this asset's
   own edges aren't a soft fade, so clipping through it at any point
   other than its exact true border reveals a hard, ugly cut. Left
   unclipped, the idle zoom below is simply allowed to bleed a little
   beyond the widget's box at its peak, which reads as a normal,
   harmless part of a gentle zoom rather than a crop.
   transform-origin:bottom anchors the idle zoom to the image's bottom
   edge, so growing larger only ever adds coverage upward, never shifts
   the bottom edge (and horizontal growth stays centered/symmetric, so
   it only ever adds coverage there too, never uncovers a side). Slides
   up from off-screen below on page load, settling directly at rest (no
   bounce/overshoot), before the fish (see .fish-widget-fish's
   animation-delay below) swims in; once that settles, it eases into a
   slow, continuous side-to-side sway with a gentle zoom breathing in
   and out. */
.fish-placeholder {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: bottom;
  /* Two independent properties (transform/translate+scale) so the
     one-shot slide-up and the infinite idle drift can run concurrently
     without fighting over the same property -- two animations
     targeting the same property can't have their values combined, only
     the later-listed one would apply. linear on the slide-up, not an
     eased curve: its keyframe percentages/values already encode the
     deceleration by how far apart each waypoint is (wide gaps early,
     narrow gaps late); layering a nonlinear global timing-function on
     top of that double-warps time and reads as choppy, flipbook-like
     stills instead of continuous motion. The drift waits out the
     slide-up's 0.9s via animation-delay before starting (default
     fill-mode none means it simply has no effect on translate/scale
     until then, leaving the slide-up's transform untouched). */
  animation:
    fish-placeholder-slide-up 0.9s linear both,
    fish-placeholder-drift 8s ease-in-out 0.9s infinite;
}

/* Sits behind .fish-widget-fish (z-index -1) rather than behind the
   whole widget, hence the more negative -2. Shares .fish-placeholder's
   sizing/animations verbatim via that shared class -- only position
   (needs to be positioned to have a z-index at all, and to not add to
   .fish-widget's auto height alongside the real placeholder), the extra
   top:-10% shift (this layer sits 10% higher than .fish-placeholder so
   more of it -- being the taller of the two -- shows above it), and
   timing (1s later than .fish-placeholder, on both the one-shot
   slide-up and the infinite drift) differ. */
.fish-background {
  position: absolute;
  top: -35%;
  left: 0;
  z-index: -2;
  animation:
    fish-placeholder-slide-up 0.9s linear 0.2s both,
    /* A different duration than .fish-placeholder's 8s (not a simple
       ratio of it) rather than the same 8s at a fixed delay -- a fixed
       delay is just a constant phase lag behind the same 8s cycle, so
       the two layers' sway/zoom stayed in lockstep (same relative
       position to each other at every moment) instead of drifting
       independently. */
    fish-placeholder-drift 11s ease-in-out 1.1s infinite;
}

/* translate and scale move in lockstep on purpose: the sway is zero
   exactly when the zoom is at 1 (0%/50%/100%), so at rest the image
   sits perfectly flush -- no crop, no gap. The sideways drift only ever
   happens while zoomed in, and by a smaller amount (1.2%) than the
   zoom's own horizontal overhang at that point (half of the scale-1
   growth, e.g. 2.5% at scale 1.05, since transform-origin's horizontal
   component is still centered) -- so it always still fully covers the
   frame, with margin to spare, at every point in the cycle. */
@keyframes fish-placeholder-drift {
  0%   { translate: 0 0;     scale: 1;    }
  25%  { translate: 1.2% 0;  scale: 1.05; }
  50%  { translate: 0 0;     scale: 1;    }
  75%  { translate: -1.2% 0; scale: 1.05; }
  100% { translate: 0 0;     scale: 1;    }
}

/* Many close, ever-shrinking steps rather than a straight linear drop --
   smooth deceleration into a plain, un-overshot rest at 0. */
@keyframes fish-placeholder-slide-up {
  0%   { transform: translateY(120%); }
  15%  { transform: translateY(95%);  }
  30%  { transform: translateY(65%);  }
  45%  { transform: translateY(40%);  }
  58%  { transform: translateY(22%);  }
  70%  { transform: translateY(11%);  }
  80%  { transform: translateY(5%);   }
  88%  { transform: translateY(2%);   }
  94%  { transform: translateY(0.7%); }
  100% { transform: translateY(0);    }
}

/* Mirrored live reference to the layer-2 fish (see <use href="#fish-body">
   in index.html) -- not a duplicated copy of its mask/gradients/blur, so
   it also stays in sync with that fish's ongoing blob animation
   automatically. .fish-widget already establishes the containing block
   (position:fixed), so this positions directly against it: 30% of the
   widget's own width (not the viewport), horizontally centered
   (left:50% + translateX(-50%)), starting at the widget's vertical
   midpoint rather than centered on it (no vertical translate). */
.fish-widget-fish {
  position: absolute;
  bottom: 25%;
  right: 5%;
  width: 70%;
  height: auto;
  /* .fish-widget establishes the stacking context (position:fixed +
     z-index:20); a negative z-index here paints this behind the
     in-flow, non-positioned .fish-placeholder img above it. */
  z-index: -1;
  /* Starts off-screen right (see 0% keyframe below) and swims left into
     its resting position (translate(0,0) rotate(0), i.e. the
     right/bottom values above) with a wave-like up/down bob plus a
     little rotational sway along the way, settling flat at the end.
     Starts shortly before .fish-placeholder's own slide-up has
     finished. "both" applies the 0% transform during that delay (so it
     doesn't flash at rest first) and holds the final transform
     afterwards. linear, not an eased curve, for the same reason as
     .fish-placeholder-slide-up above: the keyframe values already carry
     the accelerate-then-decelerate shape via how much each 10% step
     changes (bigger jumps in the middle, shrinking ones at the tail).
     Adding a nonlinear timing-function on top of already-uneven
     keyframe deltas double-warps time -- it stalls near some waypoints
     and then crosses several others in one short burst, which is what
     read as choppy, flipbook-like stills instead of one continuous
     swim. Once it arrives (0.4s delay + 1.8s duration = 2.2s), it eases
     into a slow, very gentle rock/bob loop -- on the separate
     translate/rotate properties (not transform), so it can't fight
     with fish-swim-in's own transform-based animation, the same way
     .fish-placeholder's slide-up and idle drift coexist. */
  animation:
    fish-swim-in 1.8s 0.4s linear both,
    fish-idle-sway 7s ease-in-out 2.2s infinite;
}

/* Very small amplitudes (a hair of vertical bob, a hair of rotation) on
   purpose -- meant to read as barely-there, ambient life, not another
   swim. */
@keyframes fish-idle-sway {
  0%   { translate: 0 0;     rotate: 0deg;   }
  25%  { translate: 0 -1.2%; rotate: 1.5deg; }
  50%  { translate: 0 0;     rotate: 0deg;   }
  75%  { translate: 0 1.2%;  rotate: -1.5deg;}
  100% { translate: 0 0;     rotate: 0deg;   }
}

/* right:5% matches .fish-widget-fish's own right offset ("gleicher
   Abstand nach rechts"); bottom:85% sits a bit above the fish (which
   sits at bottom:25% plus its own rendered height). Fades in last, once
   .fish-widget-prompt's own entrance has finished (2.2s delay + 0.8s
   duration = 3s -- see .fish-widget-prompt above). animation-fill-mode
   is deliberately "backwards", not "both"/"forwards": that applies the
   0% keyframe (opacity:0) during the delay, same as elsewhere on this
   widget, but -- unlike elsewhere -- lets the animation's effect on
   opacity fully lapse once it ends, falling back to the plain opacity:
   0.5 below (identical value, so no visible jump) with CSS Animations
   no longer in play. That matters here specifically because :hover
   below needs to keep overriding opacity afterwards, and a lingering
   forwards-filled animation would otherwise permanently outrank that
   plain author-level rule in the cascade. */
.fish-widget-close {
  position: absolute;
  bottom: 550%;
  right: 5%;
  width: 4.8%;
  height: auto;
  pointer-events: auto;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 0.2s ease;
  animation: fish-widget-close-in 0.5s ease 3s backwards;
  /* SVG has no fill set (defaults to black) -- brightness(0) flattens
     it to solid black regardless of source color, invert(1) then flips
     that to white. */
  filter: brightness(0) invert(1);
}

@keyframes fish-widget-close-in {
  from { opacity: 0; }
  to   { opacity: 0.5; }
}

.fish-widget-close:hover {
  opacity: 1;
}

/* Once a message has been sent (see .fish-widget.is-submitted above),
   there's no longer a fish-widget "session" left to close this way --
   .fish-widget-back-btn covers returning to it instead. */
.fish-widget.is-submitted .fish-widget-close {
  display: none;
}

/* Closing sequence (js/fish-widget-close.js adds .is-closing to
   .fish-widget on click): each piece gets its own exit, all as fresh
   @keyframes (not transitions) so they don't have to fight the
   infinite drift/sway animations already running on these elements --
   a higher-specificity "animation" declaration here fully replaces
   that shorthand rather than trying to interrupt/blend with it.
   .fish-widget-prompt goes first, immediately; everything else below
   waits out a short delay (0.5s -- just past .fish-widget-prompt's own
   0.4s fade) before starting, so the text/input visibly clears first
   instead of everything vanishing at once. */
.fish-widget.is-closing .fish-widget-close {
  animation: fish-widget-close-out 0.2s ease 0.5s forwards;
  /* Overrides the base rule's own transition:opacity -- without this,
     losing hover the instant pointer-events goes to none below (the
     mouse is very likely still over the button right after the click
     that set .is-closing) fires that transition too, and since CSS
     Transitions outrank CSS Animations in the cascade, that snapped it
     straight to the un-hovered 0.5 immediately instead of waiting for
     the animation's own delay -- reading as the icon vanishing at once. */
  transition: none;
  pointer-events: none;
}

@keyframes fish-widget-close-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

.fish-widget.is-closing .fish-widget-prompt {
  animation: fish-widget-prompt-out 0.4s ease forwards;
  pointer-events: none;
}

@keyframes fish-widget-prompt-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Falls briskly, accelerating (cubic-bezier's steep end) rather than
   swaying -- distinct from the two pngs below, which drift down slowly
   instead. */
.fish-widget.is-closing .fish-widget-fish {
  animation: fish-exit-fall 0.6s cubic-bezier(0.55, 0, 1, 0.45) 0.5s forwards;
}

@keyframes fish-exit-fall {
  0%   { transform: translateY(0); opacity: 1; }
  100% { transform: translateY(180%); opacity: 0; }
}

/* Slow, swaying descent (a couple of rocks left/right on the way down)
   before fading out fully -- shared by both .fish-placeholder and
   .fish-background via that common class. */
.fish-widget.is-closing .fish-placeholder {
  animation: fish-exit-sway 1.8s ease-in 0.5s forwards;
}

@keyframes fish-exit-sway {
  0%   { transform: translateY(0)    rotate(0deg); opacity: 1;   }
  30%  { transform: translateY(20%)  rotate(-6deg); opacity: 1;  }
  60%  { transform: translateY(60%)  rotate(5deg);  opacity: 0.6;}
  100% { transform: translateY(140%) rotate(-3deg); opacity: 0;  }
}

/* Many close-together waypoints (instead of 5 widely-spaced ones)
   trace a smooth, decaying sine-like bob/sway rather than a jagged
   zig-zag -- few keyframes force straight segments between them, and
   every segment boundary where the rotation/bob reverses direction
   reads as a sharp corner ("kantig"). translateX also eases down in
   small, ever-shrinking steps instead of four big jumps. */
@keyframes fish-swim-in {
  0%   { transform: translate(140%, 0)    rotate(0deg); }
  10%  { transform: translate(118%, -3%)  rotate(-2deg); }
  20%  { transform: translate(97%, -7%)   rotate(-4deg); }
  30%  { transform: translate(78%, -6%)   rotate(-3deg); }
  40%  { transform: translate(61%, -2%)   rotate(0deg); }
  50%  { transform: translate(46%, 3%)    rotate(2deg); }
  60%  { transform: translate(33%, 6%)    rotate(3deg); }
  70%  { transform: translate(22%, 4%)    rotate(2deg); }
  78%  { transform: translate(14%, 1.5%)  rotate(1deg); }
  84%  { transform: translate(8.5%, 0.5%) rotate(0.5deg); }
  89%  { transform: translate(4.5%, 0%)   rotate(0deg); }
  93%  { transform: translate(2%, 0%)     rotate(0deg); }
  96%  { transform: translate(0.7%, 0%)   rotate(0deg); }
  100% { transform: translate(0, 0)       rotate(0deg); }
}

/* White by default (layer 1); js/hero-height.js toggles .is-dark once
   layer 2 or layer 3 sits behind the fixed logo instead. Transition
   (not an instant swap) eases through the change rather than snapping
   the instant the underlying layer switches. Paths have their own
   fill="#fff" attribute in the markup, but an author stylesheet rule
   always wins over a presentation attribute regardless of specificity,
   so this overrides them cleanly. */
.site-header .logo-link svg path {
  transition: fill 0.3s ease;
}

.site-header .logo-link.is-dark svg path {
  fill: #000;
}

/* Fully hidden once the sticky footer takes over behind it (rather than
   staying white there, per the original white-on-layer-1-and-footer
   plan) -- pointer-events:none so it can't still be clicked while
   invisible. */
.site-header .logo-link.is-hidden {
  opacity: 0;
  pointer-events: none;
}

.logo-link svg {
  display: block;
  width: 300px;
  height: auto;
}

/* 15% smaller than the shared 300px above -- scoped to the sticky
   header instance only, leaving the footer's own .logo-link (same
   markup/class, different element) at the full size. */
.site-header .logo-link svg {
  width: 255px;
}

