body,form,figure{margin:0;padding:0}img{border:0}header,footer,nav,section,aside,article,figure,figcaption{display:block}body{font-size:100.01%}select,input,textarea{font-size:99%}#container,.inside{position:relative}#main,#left,#right{float:left;position:relative}#main{width:100%}#left{margin-left:-100%}#right{margin-right:-100%}#footer{clear:both}#main .inside{min-height:1px}.ce_gallery>ul,.content-gallery>ul{margin:0;padding:0;overflow:hidden;list-style:none}.ce_gallery>ul li,.content-gallery>ul li{float:left}.ce_gallery>ul li.col_first,.content-gallery>ul li.col_first{clear:left}.float_left,.media--left>figure{float:left}.float_right,.media--right>figure{float:right}.block{overflow:hidden}.media{display:flow-root}.clear,#clear{height:.1px;font-size:.1px;line-height:.1px;clear:both}.invisible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.custom{display:block}#container:after,.custom:after{content:"";display:table;clear:both}
@media (max-width:767px){#wrapper{margin:0;width:auto}#container{padding-left:0;padding-right:0}#main,#left,#right{float:none;width:auto}#left{right:0;margin-left:0}#right{margin-right:0}}img{max-width:100%;height:auto}.audio_container audio{max-width:100%}.video_container video{max-width:100%;height:auto}.aspect,.responsive{position:relative;height:0}.aspect iframe,.responsive iframe{position:absolute;top:0;left:0;width:100%;height:100%}.aspect--16\:9,.responsive.ratio-169{padding-bottom:56.25%}.aspect--16\:10,.responsive.ratio-1610{padding-bottom:62.5%}.aspect--21\:9,.responsive.ratio-219{padding-bottom:42.8571%}.aspect--4\:3,.responsive.ratio-43{padding-bottom:75%}.aspect--3\:2,.responsive.ratio-32{padding-bottom:66.6666%}.aspect--9\:16,.responsive.ratio-916{padding-bottom:177.7777%}.aspect--1\:1,.responsive.ratio-11{padding-bottom:100%}
/* ==========================================================================
   GTD Lab — Base stylesheet
   ========================================================================== */

/* --------------------------------------------------------------------------
   Font: Zalando Sans (Medium + Bold)
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Zalando Sans";
  src:
    url("../../files/fonts/ZalandoSans-Medium.woff2") format("woff2"),
    url("../../files/fonts/ZalandoSans-Medium.woff") format("woff"),
    url("../../files/fonts/ZalandoSans-Medium.ttf") format("truetype");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Zalando Sans";
  src:
    url("../../files/fonts/ZalandoSans-Bold.woff2") format("woff2"),
    url("../../files/fonts/ZalandoSans-Bold.woff") format("woff"),
    url("../../files/fonts/ZalandoSans-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   Design tokens
   -------------------------------------------------------------------------- */

:root {
  /* Primary colors */
  --color-common-ground: #F4E7D3;
  --color-lab-magenta: #E6007E;
  --color-lab-blue: #014B7E;

  /* Secondary colors */
  --color-distant-horizon: #9081D1;
  --color-clear-sky: #BBD4FB;
  --color-deep-forest: #143630;
  --color-night-fall: #500B43;

  /* Semantic aliases */
  --color-bg: var(--color-common-ground);
  --color-text: #000000;
  --color-accent: var(--color-lab-magenta);
  --color-link: var(--color-lab-magenta);
  --color-link-hover: var(--color-night-fall);

  /* Typography */
  --font-base: "Zalando Sans", Arial, Helvetica, sans-serif;
  --font-weight-base: 500;
  --font-weight-bold: 700;
  --font-size-nav: 1.25rem;
  /* shared by the language nav and the footer's social links */

  /* Heading scale: each size is a multiple of --font-size-base, so bumping
     the base scales the whole scale with it. h1/h2 are equal on purpose —
     h2 doubles as a visual section divider, used roughly as often as h1,
     not strictly as "the second-level heading". */
  --font-size-base: 1.125rem;
  /* 18px */
  --font-size-h6: calc(var(--font-size-base) * 1.5);
  /* 22.5px */
  --font-size-h5: calc(var(--font-size-base) * 2);
  /* 27px */
  --font-size-h4: calc(var(--font-size-base) * 2.75);
  /* 36px */
  --font-size-h3: calc(var(--font-size-base) * 3.75);
  /* 49.5px */
  --font-size-h1: calc(var(--font-size-base) * 5);
  /* 90px */

  /* Spacing scale: roughly-doubling steps so any two elements that should
     feel "related" vs. "separated" can reach for a consistently bigger
     token rather than an arbitrary one-off value. Reuse these anywhere
     spacing is needed, not just in the footer. Scaled back down on smaller
     screens below — see "Responsive tokens". */
  --space-1: 0.5rem;
  /* 8px   — tight (e.g. gap between icon and label) */
  --space-2: 1rem;
  /* 16px  — default gap/margin */
  --space-3: 2rem;
  /* 32px  — related items in a group */
  --space-4: 3rem;
  /* 48px  — separation between sub-groups */
  --space-5: 5rem;
  /* 80px  — separation between distinct blocks */
  --space-6: 8rem;
  /* 128px — generous section padding */
  --space-7: 12rem;
  /* 192px — large section padding */

  /* Layout */
  --container-width: 960px;
  --container-width-wide: 1920px;
  /* header / footer */
  --container-padding: 1.5rem;
  --z-header: 100;
  --z-hero: 1;

  /* Language nav: shared by the link padding and the container's
     counter-margin, so the two always stay in sync (see below) */
  --lang-nav-padding-inline: 0.75rem;
  --lang-nav-padding-block: 1rem;

  /* .split: shared by the gap between its columns and the distance from an
     accordion header's rule down to the split below it, so those two read
     as the same step and can't drift apart. A plain --space-* step, so it
     scales down on smaller screens like everything else. */
  --split-gap: var(--space-5);
}

/* --------------------------------------------------------------------------
   Responsive tokens
   -------------------------------------------------------------------------- */

/*
  This was the actual mobile bug: the large --space-* steps (and the nav's
  font-size) were only ever defined once, at desktop-appropriate sizes, and
  nothing scaled them down for smaller screens — so e.g. #footer .inside's
  padding-block: var(--space-6) rendered at the same 128px on a phone as on
  a wide desktop. Overriding the custom properties themselves here, instead
  of duplicating every selector that uses them in extra media queries,
  means any current or future rule built on these tokens automatically
  gets a sane mobile value for free.
*/
@media (max-width: 768px) {
  :root {
    --space-5: 3rem;
    --space-6: 5rem;
    --space-7: 7rem;
    --font-size-nav: 1em;
    /* h1/h2 at 5x this would be 90px on a tablet-and-below screen otherwise */
    --font-size-base: 1rem;
    /* Heading multipliers themselves get smaller too, not just the base
       they're a multiple of — at the same 5x/2.75x ratio, even a modestly
       smaller base still produces an oversized, easily-wrapping headline
       on a narrow screen. */
    --font-size-h1: 3.5rem;
    --font-size-h3: 2.25rem;
    --font-size-h4: 1.75rem;
    --font-size-h5: 1.5rem;
    --font-size-h6: 1.25rem;
  }
}

@media (max-width: 480px) {
  :root {
    --space-5: 2rem;
    --space-6: 3rem;
    --space-7: 4rem;
    --font-size-nav: 1em;
    --font-size-base: 0.9375rem;
    --font-size-h1: 2.5rem;
    --font-size-h3: 1.75rem;
    --font-size-h4: 1.5rem;
    --font-size-h5: 1.25rem;
    --font-size-h6: 1.125rem;
  }
}

/* --------------------------------------------------------------------------
   Reset
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
figure,
blockquote,
dl,
dd {
  margin: 0;
}

ul[class],
ol[class] {
  margin: 0;
  padding: 0;
  list-style: none;
}

img,
picture,
svg,
video {
  display: block;
  max-width: 100%;
  height: auto;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

body {
  min-height: 100vh;
  background-color: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-base);
  font-weight: var(--font-weight-base);
  font-size: var(--font-size-base);
  line-height: 1.5;
}

/* Headings stay Medium (not the Bold cut) — size carries the hierarchy
   here, not weight. See the --font-size-h* scale above for how h1–h6
   relate to each other and to the body's own font-size. A tight
   line-height keeps a heading's own wrapped lines close together — the
   1.5 body line-height would otherwise read as a big gap at these sizes. */
h1,
h2,
h3,
h4,
h5,
h6 {
  font-weight: var(--font-weight-base);
  line-height: 1.0;
  /* At these sizes, one long word (a German compound noun especially) can
     easily be wider than the whole container — this lets it break mid-word
     instead of overflowing past the edge. */
  overflow-wrap: break-word;
}

/* The reset above already zeroes out the heading's own margin. Instead of
   a blanket margin-bottom (which would also pad out a heading that's the
   last/only thing in its container), only add space when something
   actually follows it — :has(+ *) matches a heading with a next sibling,
   whatever that sibling is (paragraph, list, image, ...). */
h1:has(+ *),
h2:has(+ *),
h3:has(+ *),
h4:has(+ *),
h5:has(+ *),
h6:has(+ *) {
  margin-bottom: var(--space-3);
}

/* A heading directly after a paragraph needs its own minimum gap — the
   paragraph's own margin is 0 (from the reset), so without this there'd
   be no space at all between the two. */
p+h1,
p+h2,
p+h3,
p+h4,
p+h5,
p+h6 {
  margin-top: var(--space-3);
}

h1,
h2 {
  font-size: var(--font-size-h1);
}

h3 {
  font-size: var(--font-size-h3);
}

h4 {
  font-size: var(--font-size-h4);
}

h5 {
  font-size: var(--font-size-h5);
}

h6 {
  font-size: var(--font-size-h6);
}

/* Same reasoning as the heading spacing above: gap only *between*
   paragraphs (p + p), not a trailing margin under a lone/last one. */
p+p {
  margin-top: var(--space-2);
}

a {
  color: var(--color-link);
  text-decoration-color: currentColor;
}

a:hover,
a:focus {
  color: var(--color-link-hover);
}

/* --------------------------------------------------------------------------
   Buttons (.button / .button-*)
   -------------------------------------------------------------------------- */

.button {
  display: inline-block;
  text-transform: uppercase;
  padding-block: var(--space-2);
  padding-inline: var(--space-3);
  text-decoration: none;
  border-radius: var(--space-3);
  transition: color 0.2s ease, background-color 0.2s ease;

}

.button.secondary {
  color: var(--color-common-ground);
  border: 2px solid var(--color-common-ground);
}

.button.secondary:hover,
.button.secondary:focus {
  color: var(--color-lab-magenta);
  background-color: var(--color-common-ground);
}

/* --------------------------------------------------------------------------
   Text offset (.offset-x / .offset-x-right)
   -------------------------------------------------------------------------- */

/*
  For loosely staggering individual lines of text sideways (e.g. a run of
  <span>...</span><br> lines inside one heading), without any line getting
  cut off and without separate mobile handling.

  Usage: class="offset-x" style="--x-offset: 30%;" on the span/line.

  Why not plain `style="position:relative; left: 30%"` (as before): that
  offset is paint-only — the box never gives up any of its own width to
  make room for the shift, so at typical offsets it just runs past the
  right edge instead of wrapping, and since it's a fixed inline style,
  nothing in this stylesheet could override or fix that up per screen
  size (an inline style beats any external rule for the same property
  short of !important, and !important wouldn't solve the actual problem
  anyway — the mechanism itself, not just its value, has to change).

  This uses margin (a real layout property, not a paint-only offset) for
  the shift, and shrinks max-width by that same amount, so the box's
  right edge can never pass 100% of its container — whatever doesn't fit
  wraps onto another line instead of overflowing. Because both values are
  percentages of the same containing block, a --x-offset of e.g. 30%
  scales down together with the container on a narrow screen exactly like
  a font-size in rem would. min() additionally caps the effective offset
  at 60%, so even a deliberately extreme value (90%, say) still leaves a
  readable column of text rather than squeezing it down to almost
  nothing. Below, the offset is dropped to 0 altogether on small screens —
  even a scaled-down stagger reads as cramped/broken at that width, so it
  just falls back to plain left-aligned text there instead.
*/
.offset-x {
  display: inline-block;
  margin-inline-start: min(var(--x-offset, 0%), 60%);
  max-width: calc(100% - min(var(--x-offset, 0%), 60%));
  overflow-wrap: break-word;
}

@media (max-width: 480px) {
  .offset-x {
    margin-inline-start: 0;
    max-width: 100%;
  }
}

/*
  Same idea, measured from the right edge instead: --x-offset is how far
  the *right* edge of the text should sit in from the container's right
  edge, growing leftward (and wrapping) as needed, never cut off.

  Usage: class="offset-x-right" style="--x-offset: 5%;" on the line.

  This one has to be display:block rather than inline-block: a right-
  anchor only works if the box can claim the *full* available width and
  then text-align its content against that box's own right edge, where
  inline-block's shrink-to-fit width has no fixed edge to anchor against
  in the first place. A max-width capped below 100% still leaves that
  same box flush against the left edge too, so overflow wraps leftward
  with just as much room as .offset-x has.

  Unlike .offset-x, a block box already starts its own new line on its
  own — a <br> straight after one (kept from copying the .offset-x
  pattern, where it's needed) renders as a lone empty line, since the
  browser still gives that stray <br> a line box of its own between the
  two already-stacked blocks. Rather than relying on remembering to strip
  it from the markup, the sibling selector below just neutralizes it.
*/
.offset-x-right {
  display: block;
  max-width: calc(100% - min(var(--x-offset, 0%), 60%));
  text-align: right;
  overflow-wrap: break-word;
}

.offset-x-right+br {
  display: none;
}

@media (max-width: 480px) {
  .offset-x-right {
    max-width: 100%;
    text-align: left;
  }
}

/* --------------------------------------------------------------------------
   Table (key/value info table)
   -------------------------------------------------------------------------- */

/*
  border-collapse so each row's border-top (below) merges into one clean
  2px line across both cells, instead of doubling up at the cell boundary.
*/
table {
  width: 100%;
  border-collapse: collapse;
}

th,
td {
  padding-block: var(--space-2);
  text-align: left;
}

/* scope="row" header cells: label style, not the browser's default bold */
th {
  padding-inline-end: var(--space-3);
  font-weight: var(--font-weight-base);
  text-transform: uppercase;
}

/* The value cells read as the "real" content here — sized up to match a
   heading rather than body text. */
td {
  font-size: var(--font-size-h4);
}

/* tr + tr (not :not(:last-child)) so the line only ever falls *between*
   two rows — never above the first or below the last. Needs one of the
   darker backgrounds under the table to actually be visible. */
tr+tr th,
tr+tr td {
  border-top: 2px solid var(--color-common-ground);
}

/* --------------------------------------------------------------------------
   Layout: full-width sections with a centered, max-width content area
   -------------------------------------------------------------------------- */

/*
  Contao content-element groups (and header/footer) render without an extra
  inner wrapper, so the section itself carries the background *and* the
  padding that centers its content — no nested container needed.
*/

.content-element-group {
  width: 100%;
  padding-inline: max(var(--container-padding), calc((100% - var(--container-width)) / 2));
  padding-block: var(--space-7);
  /* Positioning context + its own stacking context (position + a real
     z-index, not just position alone) for .bg-image below — this keeps a
     background image's negative z-index safely contained to just this
     group, instead of it looking for the next stacking context up the
     tree and potentially slipping behind an earlier, unrelated section.
     overflow:hidden clips that image at the group's own edges, so if it's
     taller than the group's content-driven height it doesn't spill into
     whatever comes after instead of just stopping at the bottom. */
  position: relative;
  z-index: 0;
  overflow: hidden;
}

/* Gap between the group's own content elements (its direct children —
   Contao renders them straight into the group with no extra wrapper).
   Direct-child combinator only, so this doesn't also inject space-6
   between things nested *inside* one content element, like paragraphs in
   a text block — those already have their own p + p spacing. */
.content-element-group>*+* {
  margin-top: var(--space-6);
}

/* Add alongside .content-element-group (in the backend's CSS class field)
   to drop one side's padding — e.g. for two groups that should sit flush
   against each other with no doubled-up gap between them. For a padding
   that should be smaller but not gone, use the .pt- and .pb- classes
   further down instead: same idea, one class per step of the spacing
   scale, and they override the group's own --space-7 the same way these
   do. */
.no-padding-top {
  padding-block-start: 0;
}

.no-padding-bottom {
  padding-block-end: 0;
}

.no-margin-top {
  margin-top: 0;
}

.no-margin-bottom {
  margin-bottom: 0;
}

/*
  Turns an "Image" content element into a full-bleed background for the
  group it's placed in. In Contao 6 this class goes on the image content
  element itself (its own CSS-class field) — that element renders as
  <div class="content-image {this class}"><figure><picture>…<img>…
  so this targets that wrapper div, not the bare <img>.

  Real content element rather than a CSS background-image on purpose: it
  reuses Contao's own file picker and its automatic responsive image/
  srcset generation, so an editor can swap the picture per section without
  any code changes, and it stays a real, optimized <img> rather than one
  fixed-resolution file loaded via CSS regardless of viewport size.

  inset:0 spans the group's full box on all four sides — width AND
  height — escaping its padding the same way position:absolute normally
  would (its containing block is the padding edge of the positioned
  .content-element-group, i.e. that group's outer, unpadded box), same
  full-bleed idea as the group's own background-color utilities, just for
  an image instead of a flat color. The group's own height still comes
  from its normal-flow content (the text etc. sitting on top of this) —
  the image just fills however tall that ends up being.

  Must always fill the box completely regardless of the image's own
  aspect ratio, so width/height are both hard 100% (not the auto height
  from the global img reset — this overrides that), and object-fit:cover
  crops whatever doesn't fit rather than stretching/distorting the image
  itself. (If literal stretching — ignoring the image's own proportions
  entirely — is ever actually wanted instead of cropping, swap cover for
  fill.) cover's own default crops from the center (equal amounts off top
  and bottom) — object-position:top keeps it anchored to the same top
  edge as before, cropping any excess off the bottom instead.

  figure (Contao's own wrapper around the picture/img) needs its own
  explicit width/height:100% too: img's height:100% only has something to
  resolve against if *every* ancestor up to the positioned .bg-image also
  has a definite height, and a plain block element like <figure> doesn't
  inherit or stretch to its parent's height on its own (unlike .bg-image
  itself, which gets a definite height for free from inset:0).

  z-index:-1 relies on the group's own stacking context (see above) to
  stay behind that group's other, normal-flow content without needing
  every other element in it to be positioned itself.
*/
.bg-image {
  position: absolute;
  inset: 0;
  z-index: -1;
  margin: 0;
  pointer-events: none;
}

.bg-image figure {
  width: 100%;
  height: 100%;
}

.bg-image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
}

/* Not yet — adding them: manual padding-top/padding-bottom, one class per
   spacing-scale step, for whenever an element needs breathing room that
   isn't already covered by a more specific rule (e.g. a single element
   inside a content-element-group, rather than the group itself). */
.pt-1 {
  padding-block-start: var(--space-1);
}

.pt-2 {
  padding-block-start: var(--space-2);
}

.pt-3 {
  padding-block-start: var(--space-3);
}

.pt-4 {
  padding-block-start: var(--space-4);
}

.pt-5 {
  padding-block-start: var(--space-5);
}

.pt-6 {
  padding-block-start: var(--space-6);
}

.pt-7 {
  padding-block-start: var(--space-7);
}

.pb-1 {
  padding-block-end: var(--space-1);
}

.pb-2 {
  padding-block-end: var(--space-2);
}

.pb-3 {
  padding-block-end: var(--space-3);
}

.pb-4 {
  padding-block-end: var(--space-4);
}

.pb-5 {
  padding-block-end: var(--space-5);
}

.pb-6 {
  padding-block-end: var(--space-6);
}

.pb-7 {
  padding-block-end: var(--space-7);
}

/* Center an element's own text. Note that text-align inherits, so this
   centers everything inside whatever carries it — put it on the single
   element that should be centered (e.g. one hyperlink element's CSS class
   field), not on the surrounding group, unless the whole group's contents
   really should be centered. */
.text-center {
  text-align: center;
}

/* Same centering formula as .content-element-group, just swapped to the
   wide 1920px container instead of the regular 960px one — same as
   header/footer already use. Written standalone (not nested under
   .content-element-group) so it works either added alongside that class
   on a group, or on its own on a single element that needs the extra
   width without being part of a group at all. Relies on cascade order
   (same specificity as .content-element-group, declared after it) when
   combined with that class, so keep it below .content-element-group in
   the file if either ever moves. */
.container-wide {
  padding-inline: max(var(--container-padding), calc((100% - var(--container-width-wide)) / 2));
}

/* Positioning context for anything inside an article that needs to
   position itself (absolutely) relative to it. */
.mod_article {
  position: relative;
  padding-top: var(--space-3);
}

/* The hero article itself, and whatever directly follows it, don't need
   that offset — reset it back to 0 for both. */
#hero,
#hero+.mod_article {
  padding-top: 0;
}

header,
footer {
  width: 100%;
  padding-inline: max(var(--container-padding), calc((100% - var(--container-width-wide)) / 2));
}

/* Fixed, transparent header that overlays the page: the section scrolling
   underneath stays visible behind the menu, which is also why the menu's
   own color has to follow that section (see .nav-on-light further down).
   Because it overlays rather than pushing content down, give whatever sits
   at the top of the page enough top padding/margin to clear its height. */
header {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: var(--z-header);
  background: transparent;
}

/* --------------------------------------------------------------------------
   Background color utilities
   -------------------------------------------------------------------------- */

/* Primary colors */
.bg-common-ground {
  background-color: var(--color-common-ground);
}

.bg-lab-magenta {
  background-color: var(--color-lab-magenta);
  color: var(--color-common-ground);
}

.bg-lab-blue {
  background-color: var(--color-lab-blue);
  color: var(--color-common-ground);
}

/* Secondary colors */
.bg-distant-horizon {
  background-color: var(--color-distant-horizon);
  color: var(--color-common-ground);
}

.bg-clear-sky {
  background-color: var(--color-clear-sky);
  color: var(--color-common-ground);
}

.bg-deep-forest {
  background-color: var(--color-deep-forest);
  color: var(--color-common-ground);
}

.bg-night-fall {
  background-color: var(--color-night-fall);
  color: var(--color-common-ground);
}

/* --------------------------------------------------------------------------
   Scroll reveal (.fade-in)
   -------------------------------------------------------------------------- */

/*
  Scroll-triggered reveal, usable on any element. animation-trigger (a true
  CSS-only "fire once, never reverse, at any scroll speed" mechanism) isn't
  implemented in any shipping browser yet, and animation-timeline: view()
  is scroll-scrubbed (reverses on scroll-back) — neither actually gives
  "fires once when it enters the viewport, full stop" today. So this is
  driven by a small IntersectionObserver script (files/js/fade-in.js) that
  adds .is-visible once, the first time an element crosses ~10% up from
  the bottom of the viewport, then stops observing it — CSS below just
  handles the resulting opacity/transform transition.

  Gated behind .js (added by that same script as its first line, before it
  even looks for .fade-in elements): if the script fails to load or run,
  .fade-in elements are never hidden in the first place, rather than being
  stuck invisible forever waiting for a script that never showed up.

  --fade-in-transform lets an element combine the slide-up with a transform
  of its own (e.g. #hero-logo's centering translate below) — since a
  transform *transition* on an element with its own static transform would
  otherwise have the same "last declaration wins, doesn't merge" problem
  the old keyframe-based version had. Composing translate functions inside
  one shared `transform` value like this just adds their offsets together,
  since none of them involve rotation/scale to reorient the axes.
*/
.js .fade-in {
  opacity: 0;
  /* Empty fallback, not `none` — `none` can't be mixed with an actual
     transform function like translateY() in one value (invalid, and the
     whole declaration gets dropped), whereas an empty fallback just drops
     out, leaving plain `translateY(2rem)` for elements with no transform
     of their own. */
  transform: var(--fade-in-transform, ) translateY(2rem);
  transition: opacity 0.8s ease-out, transform 0.8s ease-out;
}

.js .fade-in.is-visible {
  opacity: 1;
  transform: var(--fade-in-transform, ) translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .js .fade-in {
    opacity: 1;
    /* Same empty-fallback composition as above, not a blind `none` — a
       reduced-motion visitor should still land at an element's own resting
       transform (e.g. #hero-logo's centering), just without the motion. */
    transform: var(--fade-in-transform, );
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   Language nav (mod_customnav)
   -------------------------------------------------------------------------- */

/*
  The fixed menu sits over whatever section happens to be scrolled under
  it, so its color has to follow that background. Previously this was
  mix-blend-mode: difference against white, which auto-inverts against
  anything — but blending can only ever produce |white − backdrop|, never
  two *specific* colors, and what's wanted here is exactly common-ground
  or exactly black.

  So common-ground is the default, since most sections are dark — and if a
  new dark background ever gets added without updating the script's list,
  that's the harmless failure: it stays common-ground, which is correct
  over something dark anyway.

  Detection still works off the *dark* sections though, not the light
  ones: light is the structural fallback here (the body background is
  common-ground, as is any group without a .bg-* class, the article's own
  top padding and the footer), so it can't be enumerated reliably, while
  the dark backgrounds can. files/js/fade-in.js therefore watches the dark
  sections and sets .nav-on-light on <html> whenever *none* of them is
  behind the menu — i.e. the light state is the negation of a signal that
  is actually measurable.

  The transition lives on the base rule (not the state rule), so it
  applies in both directions.
*/

.mod_customnav .level_1 {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;

  /* Cancels out each link's inline padding below, so it's the link *text*
     — not its padding box — that stays flush with the header's edge (and
     therefore the 1920px line). Add/change padding on the links and this
     keeps the alignment correct automatically. */
  margin-inline: calc(-1 * var(--lang-nav-padding-inline));
}

.mod_customnav .level_1 a {
  display: block;
  padding-inline: var(--lang-nav-padding-inline);
  padding-block: var(--lang-nav-padding-block);
  font-size: var(--font-size-nav);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  text-decoration: none;
  color: var(--color-common-ground);
  opacity: 0.5;
  transition: opacity 0.1s ease, color 0.3s ease;
}

.nav-on-light .mod_customnav .level_1 a {
  color: var(--color-text);
}

.mod_customnav .level_1 a:hover,
.mod_customnav .level_1 a:focus {
  /* Only opacity — the color belongs to the light/dark state above, so
     hover must not set one of its own or it would override it. */
  opacity: 1;
}

/* Contao adds this class itself when the item is the current page or an
   ancestor of it (e.g. the language's root page) — see the root-page nav
   setup below. Keep the current language fully visible, not just on hover. */
.mod_customnav .level_1 a.trail {
  opacity: 1;
}

/* --------------------------------------------------------------------------
   Interactive hero (wind-dithered canvas)
   -------------------------------------------------------------------------- */

/*
  This is its own sectioning <header> for the hero, not the site's fixed
  nav header above — but the generic `header` rule still matches it by tag,
  handing it z-index: var(--z-header) since this block never set its own.
  Combined with it landing later in the DOM than the real nav header, equal
  z-index ties break in DOM order, so the hero painted over the nav and hid
  it. The explicit lower z-index here fixes that regardless of DOM order.
  (`position: relative` already wins over the generic rule's `fixed` via
  this selector's higher specificity, so that part was never the problem.)
*/
.gtd-header {
  position: relative;
  z-index: var(--z-hero);
  width: 100%;
  height: 100vh;
  height: 100dvh;
  /* tracks the *visible* viewport as mobile toolbars show/hide, so the hero doesn't jump or clip; falls back to 100vh above where unsupported */
  min-height: 480px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-lab-magenta);
}

#ditherCanvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* Canvas stays a small raster buffer, scaled up purely via CSS (GPU
     compositing) instead of a manual drawImage upscale per frame — far
     slower in Safari, which falls back to a software path for that. */
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
}

/* Centered independently of .gtd-header's own flex centering (rather than
   relying on it), so this still centers correctly regardless of what else
   ends up alongside it in the header. Positioned above the canvas, which
   paints at the default stack level since it never sets its own z-index. */
#hero-logo {
  position: absolute;
  top: 50%;
  left: 50%;
  /* Feeds .fade-in's transform composition above, so the slide-up and the
     centering combine instead of one replacing the other. */
  --fade-in-transform: translate(-50%, -50%);
  z-index: 1;
  width: 100%;
  max-width: 1920px;
}

#hero-logo figure {
  display: flex;
  align-items: center;
  justify-content: center;
  padding-inline: var(--space-2);
}

/* This element's own centering — needed as a resting position for whenever
   .fade-in's rules *don't* apply (e.g. before the "js" class lands). Its id
   selector would otherwise always outrank .fade-in's class-based transform
   declarations and permanently pin this at dead-center with no slide at
   all, animated or not; :where() strips that specificity back to zero so
   it only wins by default, never over .fade-in's from/to/reduced-motion
   states (which each already fold --fade-in-transform in above anyway). */
:where(#hero-logo) {
  transform: translate(-50%, -50%);
}

/*
  Hero with a still image instead of the animated canvas: no .gtd-header
  and no module at all, just the #hero article with an image element that
  carries the .bg-image class (same class as the section backgrounds
  above — the absolute/inset/cover mechanics are identical).

  What that class can't bring with it is a height. Inside a content group
  the group's own content gives it one; here .bg-image takes the only
  element out of normal flow, so the article would collapse to nothing and
  #hero-logo, which centers itself against this article, would have
  nothing to center in. Hence the same 100dvh/min-height as .gtd-header.

  Keyed off :has() rather than a second class to add in the backend: the
  image being a full-bleed background *is* the condition, so a hero that
  contains one behaves accordingly and a normal article never does. The
  image is a direct child because Contao renders content elements straight
  into .mod_article with no inner wrapper.

  z-index: 0 gives the article its own stacking context, so .bg-image's
  z-index: -1 stays behind this hero's content instead of searching up the
  tree for a context and slipping behind the page itself.

  The image is anchored to its top edge (from .bg-image img); for a hero
  photo where the subject sits in the middle, override object-position to
  center on that one element.
*/
#hero:has(> .bg-image) {
  height: 100vh;
  height: 100dvh;
  min-height: 480px;
  overflow: hidden;
  z-index: 0;
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

/* Logo / links split 50/50, generous top+bottom padding, a big gap between
   the two columns. align-items:start keeps both columns starting at the
   same top edge, so the logo's top lines up with .socials' top. */
#footer .inside {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: start;
  column-gap: var(--space-7);
  padding-block: var(--space-6);
}

/* Capped at ~1/3 of the 1920px wide container, well below the 50/50 grid
   column's own width. min(640px, 100%), not a flat 640px — this selector
   is more specific than the global img reset's max-width:100%, so a flat
   640px here would fully replace (not combine with) that reset and stop
   the logo from ever shrinking below 640px, overflowing any viewport
   narrower than that. min() keeps both constraints in effect at once. */
#footer .logo img {
  max-width: min(480px, 100%);
}

@media (max-width: 480px) {

  /* Matches the .socials 1-column breakpoint below, so logo and links
     don't end up squeezed side by side at the same width the socials
     grid decided was too narrow for even 2 columns. */
  #footer .inside {
    grid-template-columns: 1fr;
    row-gap: var(--space-4);
  }
}

/* Social links: responsive grid, 3x2 down to 2x3 down to 1x6. Plain,
   uppercase, medium-weight black text with no hover state at all — so
   there's deliberately no :hover rule overriding the generic one below. */
#footer .socials {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
}

@media (max-width: 768px) {
  #footer .socials {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 480px) {

  /* Drop the grid entirely rather than stepping it down to 1 column —
     wrapping flex items (same treatment as .legal below) break onto a new
     line only once a row actually runs out of width, instead of forcing
     one link per line regardless of how short it is. */
  #footer .socials {
    display: flex;
    flex-wrap: wrap;
  }
}

#footer .socials a {
  color: var(--color-text);
  font-size: var(--font-size-nav);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  text-decoration: none;
}

#footer .socials a:hover,
#footer .socials a:focus {
  /* Explicitly cancel the site-wide hover color (a:hover above) —
     "no hover state" means it has to stay black on hover too. */
  color: var(--color-text);
}

/* Legal links: same black/uppercase/medium treatment, but at the base
   text size, and set apart from the social grid above it. */
#footer .legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

#footer .legal a {
  color: var(--color-text);
  font-size: 1rem;
  font-weight: var(--font-weight-base);
  text-transform: uppercase;
  text-decoration: none;
}

#footer .legal a:hover,
#footer .legal a:focus {
  color: var(--color-text);
}

/* --------------------------------------------------------------------------
   Slider (.content-swiper)
   -------------------------------------------------------------------------- */

/*
  Contao 6's slider element is Swiper (14.x), and its own stylesheet is
  driven almost entirely by custom properties — so most of the brand look
  comes from redefining those rather than from overriding its rules. Set on
  the element wrapper, not :root, so Swiper's blue-by-default theme color
  can't leak into anything else.

  How many slides are visible is *not* set here: Swiper measures and sets
  slide widths in JavaScript, so it has to be a JS option. See
  templates/content_element/swiper/columns.html.twig for the 1 / 2 / 3
  column variant, its breakpoints, and the markup the rules below assume.
*/
.content-swiper {
  --swiper-theme-color: var(--color-lab-magenta);
  --swiper-navigation-size: 2.75rem;
  /* Negative, so the arrows sit in the page gutter *beside* the slider
     rather than on top of the first and last card. Brought back inside
     below, once the gutter is too narrow to hold them. */
  --swiper-navigation-sides-offset: -4rem;
}

/*
  The arrows' positioning context. Without it they'd resolve against the
  whole element wrapper — which includes the headline above the slider —
  and `top: 50%` would center them on headline-plus-slider instead of on
  the cards. The frame wraps only .swiper and the controls, and carries no
  overflow of its own, which is also what lets the arrows sit outside the
  slider: .swiper itself has overflow:hidden (that's what crops the slides
  at the container edge), so a button inside it could never stick out.
*/
.content-swiper .slider-frame {
  position: relative;
}

/* Slides are flex children of .swiper-wrapper, so height:auto lets them
   stretch to the tallest one in view instead of each sizing to its own
   content — otherwise cards with a longer text end lower than the rest.
   Making the slide itself a flex container passes that full height on to
   the card inside it, so the card's white ground reaches the bottom of the
   row even when its own text is shorter. (A percentage height on the card
   would depend on the slide having a definite height, which a stretched
   flex item only has indirectly.) */
.content-swiper .swiper-slide {
  height: auto;
  display: flex;
}

.content-swiper .swiper-slide>* {
  flex: 1;
  /* Flex items don't shrink below their content by default, which a long
     unbroken word in a card would otherwise widen the slide with. */
  min-width: 0;
}

/* The design uses arrows only. The element stays in the DOM (Swiper is
   configured with it and keeps it updated); remove this rule to show the
   bullets, and add bottom padding on .swiper so they don't sit on the
   cards. */
.content-swiper .swiper-pagination {
  display: none;
}

/* Swiper 14 dropped the icon font its arrows used to be drawn with; it
   now appends a chevron <svg> into each button on init instead. That one
   is switched off in the slider template (navigation.addIcons: false) and
   a full-shaft arrow drawn here instead — a character costs no extra
   request and scales with the type scale.

   The rule below is the belt to that braces: Contao emits the slider
   script only once per page, so a slider using the default template
   rendering after this one would supply its own init without addIcons and
   the injected chevron would come back on top of the character. */
.content-swiper .swiper-navigation-icon {
  display: none;
}

.content-swiper .swiper-button-prev,
.content-swiper .swiper-button-next {
  border-radius: 50%;
  background-color: var(--color-lab-magenta);
  color: var(--color-common-ground);
  font-size: var(--font-size-nav);
  line-height: 1;
}

.content-swiper .swiper-button-prev::after {
  content: "\2190";
}

.content-swiper .swiper-button-next::after {
  content: "\2192";
}

/* The slider's own headline field ("SPEAKER" in the design). Entered there
   rather than as a separate headline element, it lands inside this wrapper
   and gets the small, bold, uppercase label treatment instead of the h1/h2
   display size the rest of the site uses. */
.content-swiper> :is(h1, h2, h3, h4, h5, h6) {
  margin-bottom: var(--space-4);
  font-size: var(--font-size-nav);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* Once the gutter beside the 960px container drops below the 4rem the
   arrows need — around 1090px viewport width — they move back inside and
   overlap the cards' edges instead of being cut off. A .container-wide
   slider needs this raised, since its gutter runs out much earlier. */
@media (max-width: 1100px) {
  .content-swiper {
    --swiper-navigation-sides-offset: 0.5rem;
  }
}

/* --------------------------------------------------------------------------
   Card (.card)
   -------------------------------------------------------------------------- */

/*
  A white card: an element group holding an image element followed by text
  elements. Used as a slider slide and as the left column inside an
  accordion panel, so the rules below are written once and reached two
  ways:

    .card                                   — the class, usable anywhere
    .content-swiper .swiper-slide > .content-element-group
                                            — a slide always *is* a card,
                                              so the slider needs no class
                                              added in the backend

  :is() keeps that one selector per rule instead of two parallel copies.
  When adding a card somewhere new, use the class.

  The group is Contao's section wrapper by default — full-bleed centering
  padding, --space-7 block padding, its own stacking context — none of
  which makes sense once it's a card, so that's all reset here.

  White rather than common-ground on purpose: the design puts white cards
  on the cream page background, so this is the one place white is the
  point rather than a default to be replaced. It's one line to change.
*/
:is(.card, .content-swiper .swiper-slide>.content-element-group) {
  --card-padding: 1.5rem;
  --card-background: #FFFFFF;

  padding: 0 0 var(--card-padding);
  border-radius: 0.5rem;
  background-color: var(--card-background);
  /* Clips the portrait into the rounded top corners. */
  overflow: hidden;
}

/* The group's own between-elements gap is --space-6, a section-level step.
   Inside a card it has to be a fraction of that. */
:is(.card, .content-swiper .swiper-slide>.content-element-group)>*+* {
  margin-top: var(--space-2);
}

/* Inner padding on everything except the portrait, which stays flush to
   the card's edges. Applied to the children rather than the card itself so
   the two can differ without an extra wrapper. */
:is(.card, .content-swiper .swiper-slide>.content-element-group)> :not(.content-image) {
  padding-inline: var(--card-padding);
}

/* Portraits differ in proportion; a fixed ratio keeps every card's image
   block the same height, which is what makes a row of them read as a grid
   — and keeps a single card from jumping in height when the picture is
   swapped. */
:is(.card, .content-swiper .swiper-slide>.content-element-group) .content-image img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

/* The speaker's name. Uppercase is presentational, so editors type normal
   casing; the smaller margin replaces the site-wide --space-3 under a
   heading, which is a section-level step. */
:is(.card, .content-swiper .swiper-slide>.content-element-group) :is(h1, h2, h3, h4, h5, h6) {
  margin-bottom: var(--space-2);
  font-size: var(--font-size-h6);
  font-weight: var(--font-weight-bold);
  line-height: 1.1;
  text-transform: uppercase;
}

/* A step below the base size: the card is a summary next to whatever it
   sits beside, so its text shouldn't compete with the running text. */
:is(.card, .content-swiper .swiper-slide>.content-element-group) p {
  font-size: 1rem;
  line-height: 1.4;
}

/*
  Meta row: day, time and topic, with the topic pushed to the right edge
  and a rule underneath. Add "card-meta" to the CSS class field of a text
  element whose content is a plain bullet list —

      • Donnerstag   • 17.30 Uhr   • Technologie

  — so an editor can enter it with the list button and no markup. The rule
  sits on the list, not on the element, because the element carries the
  card's inner padding and a border there would run edge to edge.
*/
.card-meta ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-2);
  margin: 0;
  padding: 0 0 var(--space-1);
  border-bottom: 2px solid currentColor;
  list-style: none;
  font-size: 0.8125rem;
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* Last item to the right edge — auto margin rather than space-between, so
   the first two stay together as a pair however many items there are. */
.card-meta li:last-child {
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   Accordion (.content-accordion)
   -------------------------------------------------------------------------- */

/*
  Contao 6's accordion is handorgel, whose shipped stylesheet is a plain
  light-UI default (white buttons, #eee rules, grey panels) that fights the
  brand. Everything here is transparent instead, with currentColor rules —
  so one accordion works unchanged in a cream section and in a magenta or
  blue one, following whatever text color that section set.
*/

/*
  The section header's tag comes from the editor's "Section headline" field
  and defaults to h2 — which on this site means --font-size-h1, i.e. ~90px
  for an accordion row. The button inherits its font-size, so resetting it
  on the header fixes both.
*/
.content-accordion .handorgel__header {
  font-size: var(--font-size-nav);
  line-height: 1.2;
}

/* handorgel closes the list off with its own 1px #eee line under the whole
   element — cancelled here, because the last row's own rule already ends
   the list and the two would otherwise stack up as a doubled edge. */
.content-accordion {
  border-bottom: none;
}

/* The rule sits *under* each row (not above), so a list of rows reads as
   rows with a closing line and no stray line above the first one —
   handorgel's own border-top has to be cancelled explicitly for that, or
   its #eee line stays above every row on top of ours below it. */
.content-accordion .handorgel__header__button {
  padding: var(--space-3) 0;
  border-top: none;
  border-bottom: 2px solid currentColor;
  background-color: transparent;
  color: inherit;
  font-family: inherit;
  font-weight: var(--font-weight-base);
  transition: opacity 0.1s ease;
}

.content-accordion .handorgel__header__button:hover,
.content-accordion .handorgel__header__button:focus-visible {
  opacity: 0.6;
}

.content-accordion .handorgel__content {
  border-top: none;
  background-color: transparent;
}

.content-accordion .handorgel__content__inner {
  padding: var(--space-3) 0 var(--space-4);
}

/* --------------------------------------------------------------------------
   Accordion: programme section header (.program-header)
   -------------------------------------------------------------------------- */

/*
  Used by templates/content_element/accordion/program.html.twig, which turns
  one section headline into a grid of columns, each of which can hold
  several stacked lines:

      11:30 // Raum 2 | Dr. Hans Thilo Klagenfurthner // Surveillance Tech | Keeping up with the pedestrians
      └─ column 1 ──┘  └─ column 2 ─────────────────────────────────────┘  └─ column 3 ───────────────────┘

  The columns are their own stacked blocks, not shared grid rows — that's
  deliberate and is what the design needs: a speaker name wrapping onto a
  second line must push only *that* column's next line down, not the room
  number in the column beside it.

  Only the content columns are in the token; the indicator's `auto` track is
  appended in the rule, so adding a fourth column is a one-line change here
  and never collides with the indicator.
*/
.program-header {
  --program-columns: 9rem 16rem 1fr;

  display: grid;
  grid-template-columns: var(--program-columns) auto;
  gap: var(--space-3);
  /* Top, not baseline: the columns hold different numbers of lines at very
     different sizes, and their first lines should start level. */
  align-items: start;
  text-align: left;
}

.program-header__line {
  display: block;
}

/* Columns 1 and 2 are the small, bold, uppercase metadata. Editors type
   normal casing — the uppercasing is presentational and belongs here, not
   in the content. */
.program-header__cell:nth-child(1),
.program-header__cell:nth-child(2) {
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* Fixed-width digits, so times line up column-wise row after row even
   though the font isn't monospaced. */
.program-header__cell:nth-child(1) {
  font-variant-numeric: tabular-nums;
}

/* Everything after the speaker's own name in column 2 — the topic or track
   — steps back. Set with opacity rather than a grey, so it also works on a
   magenta or blue section where the text is cream, not black. */
.program-header__cell:nth-child(2) .program-header__line:nth-child(n+2) {
  opacity: 0.3;
}

/* The session title carries the row. Bold at h4 size — change either of
   these two lines alone if it should be quieter. */
.program-header__cell:nth-child(3) {
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-bold);
  line-height: 1.05;
}

/*
  Open/close affordance. Pinned to line -2 (the start of the last explicit
  column) rather than a column number, so it stays at the right edge
  whether a row supplies two, three or four columns.
*/
.program-header::after {
  content: "+";
  grid-column: -2;
  font-size: var(--font-size-h4);
  line-height: 1;
}

.handorgel__header--open .program-header::after {
  content: "\2212";
}

/*
  Below the tablet breakpoint the three columns plus the large title have no
  room side by side, so each column becomes its own row and the indicator
  stays pinned to the first one. Same breakpoint the rest of the site uses
  (see "Responsive tokens"); the title shrinks on its own, because it's
  built on --font-size-h4.
*/
@media (max-width: 768px) {
  .program-header {
    --program-columns: 1fr;

    gap: var(--space-2);
  }

  .program-header__cell {
    grid-column: 1;
  }

  .program-header::after {
    grid-row: 1;
    grid-column: 2;
  }
}

/* --------------------------------------------------------------------------
   Two-column split (.split)
   -------------------------------------------------------------------------- */

/*
  Two columns side by side inside one element group — in the design a .card
  on the left and running text on the right. Add "split" to the group's CSS
  class field; its direct children become the columns, so two children give
  two columns and a third would give three.

  --split-columns is the ratio. It can't be set from the backend, so change
  it here (or add a modifier class beside .split) if a section ever wants
  50/50 instead of the 1/3-to-2/3 the design uses.

  align-items: start so the shorter column stays its own height instead of
  the card stretching to match a long text beside it.
*/
.split {
  display: grid;
  grid-template-columns: var(--split-columns, 1fr 2fr);
  gap: var(--split-gap);
  align-items: start;
}

/* The grid gap already separates the columns; the group's own --space-6
   margin between children would stack on top of it and push the second
   column down by a full section's worth. Specific enough to win over that
   rule wherever this block ends up in the file. */
.content-element-group.split>*+* {
  margin-top: 0;
}

/* One column below the tablet breakpoint the rest of the site uses: at
   960px the left column would be under 300px wide, which a portrait plus
   a meta row can't carry. */
@media (max-width: 768px) {
  .split {
    grid-template-columns: 1fr;
  }
}

/*
  A group used as an accordion panel's content is not a page section: drop
  the full-bleed centering padding and the --space-7 block padding, which
  would otherwise indent the panel's content away from the header above it
  and add a section's worth of air inside every open row.

  Direct child only — a .card nested deeper inside keeps its own padding.
*/
.handorgel__content__inner>.content-element-group {
  padding: 0;
}

/*
  The panel's top padding is the distance from the header's rule down to
  the content under it. Where that content is a .split, it should be the
  same step as the gap between the split's own two columns — so both read
  as one consistent unit of space rather than two arbitrary ones. Hence
  --split-gap for both.

  Scoped with :has() rather than changed on the panel outright, so a panel
  holding anything else keeps its own tighter --space-3.
*/
.content-accordion .handorgel__content__inner:has(>.split) {
  padding-top: var(--split-gap);
}

/*
  A row of plain links, e.g. "Website / Mastodon / Bluesky" under the text.
  Add "link-row" to the CSS class field of a text element whose content is
  a bullet list of links — same idea as .card-meta: the editor uses the
  list button, CSS does the rest.
*/
.link-row ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.link-row a {
  color: var(--color-text);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

.link-row a:hover,
.link-row a:focus {
  color: var(--color-link);
}

/* --------------------------------------------------------------------------
   Day switch (.day-switch)
   -------------------------------------------------------------------------- */

/*
  Two-segment switch between the two event days. Build it as an element
  group with the class "day-switch", holding two hyperlink elements — each
  of those renders as <div class="content-hyperlink …"><a>…</a></div>, so
  its CSS class field is where one of the .bg-* utilities goes to colour
  that half, plus "active" on the one pointing at the current page.

  The group is Contao's section wrapper by default, so its full-bleed
  padding, its full width and the --space-6 gap between its children all
  have to go; what's left is a plain flex row.

  overflow:hidden is what rounds the two colours into one shared pill: the
  halves stay square and the container clips them, so the seam between
  them stays a straight edge while the outer corners round.
*/
.content-element-group.day-switch {
  display: flex;
  width: max-content;
  max-width: 100%;
  padding: 0;
  border-radius: 0.75rem;
  overflow: hidden;
}

.content-element-group.day-switch>*+* {
  margin-top: 0;
}

/*
  Colour comes entirely from the .bg-* class on each hyperlink element —
  nothing here sets a background, so any pair of the existing utilities
  works. The text is common-ground on both halves whichever backgrounds
  were picked, and the inactive day is that same colour at half strength
  rather than a second, dimmer colour: that way a new background pairing
  never needs a matching text colour to go with it.

  Same 0.5 → 1 step and 0.1s timing as the language nav, so the two
  interactive elements on the page behave identically.
*/
.day-switch a {
  display: block;
  padding: var(--space-2) var(--space-3);
  color: var(--color-common-ground);
  font-size: var(--font-size-nav);
  font-weight: var(--font-weight-bold);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  /* "Tag 01" must not break across two lines in a narrow viewport. */
  white-space: nowrap;
  opacity: 0.5;
  transition: opacity 0.1s ease;
}

.day-switch .active a,
.day-switch a:hover,
.day-switch a:focus {
  /* Explicitly repeated: the site-wide a:hover would otherwise pull the
     text to the link hover colour on the way past. */
  color: var(--color-common-ground);
  opacity: 1;
}

/*
  Placement in the hero: bottom right, its right edge on the same 1920px
  line as the nav above it and the footer below — same formula as those,
  so the three can't drift apart.

  #hero is position:relative already (from .mod_article), and it is as
  tall as the hero itself in both variants: the canvas one gets its height
  from .gtd-header, the still-image one from #hero:has(> .bg-image).

  z-index 2 puts it over the canvas and the logo, which both sit at 1, and
  still well under the fixed nav at --z-header.

  Only the placement is scoped to the hero — the switch itself is a plain
  block anywhere else.
*/
#hero .day-switch {
  position: absolute;
  z-index: 2;
  bottom: var(--space-4);
  right: max(var(--container-padding), calc((100% - var(--container-width-wide)) / 2));
}
