/* ==========================================================================
   FONTS — self-hosted
   ========================================================================== */

@font-face { font-family:'Cabinet Grotesk'; src:url('../fonts/CabinetGrotesk-Bold.woff2') format('woff2'); font-weight:700; font-display:swap; }
@font-face { font-family:'Cabinet Grotesk'; src:url('../fonts/CabinetGrotesk-Extrabold.woff2') format('woff2'); font-weight:800; font-display:swap; }
@font-face { font-family:'Satoshi'; src:url('../fonts/Satoshi-Regular.woff2') format('woff2'); font-weight:400; font-display:swap; }
@font-face { font-family:'Satoshi'; src:url('../fonts/Satoshi-Medium.woff2') format('woff2'); font-weight:500; font-display:swap; }
@font-face { font-family:'Satoshi'; src:url('../fonts/Satoshi-Bold.woff2') format('woff2'); font-weight:700; font-display:swap; }

/* ==========================================================================
   TOKENS — light, corporate orange + teal as accents only
   ========================================================================== */

:root {
  --ink: #14161a;
  --ink-soft: #4b4f55;
  --ink-faint: #6b6f75; /* was #878b91 (3.42:1 on white, fails 4.5:1 body-text min) — now 5.05:1 */
  --paper: #ffffff;
  --tint: #f6f5f2;
  --tint-2: #eeece5;
  --line: rgba(20,22,26,.09);
  --line-strong: rgba(20,22,26,.16);

  --orange: #f19a34; /* corporate orange */
  --orange-soft: #fef1de;
  --orange-line: #f6cd93;
  --focus-ring: #c17400; /* darker than --orange specifically for focus rings: 3.64:1 on white / 3.34:1 on tint, both pass the 3:1 UI-component minimum */
  --teal: #62c1d3; /* corporate blue — backgrounds/borders/icons/buttons only, see --teal-deep for text */
  --teal-soft: #e8f6f9;
  --teal-line: #a9dde8;
  --teal-deep: #1c6b7d; /* same hue, darkened for text/icon contrast — 6.09:1 on white, --teal itself is only 2.08:1 */
  --sage: #6fa384; /* muted, desaturated green — the "Info-Kasten" card, deliberately not the vivid --signal green (that's reserved for live-status dots) and not --teal (used by the Klimaschutz strip directly below it, needs to read as a different color) */
  --sage-soft: #eaf4ec;
  --sage-line: #bfe0c8;
  --sage-deep: #2f6b46; /* darkened for text/icon contrast — 6.34:1 on white, 5.54:1 on --sage-soft */
  --signal: #22c55e;
  --signal-soft: #e8f9ee;

  --font-display: 'Cabinet Grotesk','Segoe UI',system-ui,sans-serif;
  --font-body: 'Satoshi','Segoe UI',system-ui,sans-serif;

  --fs-hero: clamp(2.5rem, 1.9rem + 3vw, 4.5rem);
  --fs-h2: clamp(2rem, 1.7rem + 1.4vw, 3rem);
  --fs-h3: clamp(1.25rem, 1.15rem + .4vw, 1.5rem);
  --fs-lead: clamp(1.0625rem, 1rem + .3vw, 1.25rem);

  --ease-out: cubic-bezier(.16,1,.3,1);
  --ease-out-soft: cubic-bezier(.23,1,.32,1);
  --ease-in-out: cubic-bezier(.77,0,.175,1);
  --ease-fly: cubic-bezier(.16,1.36,.42,1);
  --dur-fast: 160ms;
  --dur-base: 220ms;
  --dur-modal: 320ms;

  --container: 1280px;
  --radius-s: 10px;
  --radius-m: 18px;
  --radius-l: 28px;
  --pad: clamp(4rem, 3.25rem + 3vw, 6.5rem);
  --shadow: 0 1px 2px rgba(20,22,26,.04), 0 30px 60px -28px rgba(20,22,26,.22);
  color-scheme: light;
}

/* ==========================================================================
   DARK MODE — same semantic tokens, redefined. Every component below was
   already written against --ink/--paper/--tint/--line/--orange/--teal/--signal
   rather than literal colors, so redefining just these tokens re-themes the
   whole site; the only per-component overrides needed are the handful of
   places that intentionally break that discipline (see below each still using
   a literal color or deliberately fixed pairing). Placed BEFORE the
   high-contrast block further down so high-contrast — a deliberate max-
   contrast override — still wins if a visitor somehow has both engaged.
   ========================================================================== */
html[data-theme='dark'] {
  color-scheme: dark;
  --ink: #f4f5f7;
  --ink-soft: #c7cad0;
  --ink-faint: #9a9ea6;
  --paper: #16181c;
  --tint: #1e2126;
  --tint-2: #262a31;
  --line: rgba(255,255,255,.10);
  --line-strong: rgba(255,255,255,.18);

  --orange: #f2a53d;
  --orange-soft: rgba(242,165,61,.16);
  --orange-line: rgba(242,165,61,.45);
  --focus-ring: #ffb454; /* brighter than --orange for contrast against dark backgrounds, mirroring why the light variant goes darker for contrast against white */
  --teal: #6cc9db;
  --teal-soft: rgba(108,201,219,.16);
  --teal-line: rgba(108,201,219,.4);
  --teal-deep: #8fdcea; /* lightened for text/icon contrast on dark, mirroring why the light variant goes darker for contrast on white */
  --sage: #7fc79b;
  --sage-soft: rgba(111,163,132,.16);
  --sage-line: rgba(111,163,132,.4);
  --sage-deep: #9fe0b6; /* lightened for text/icon contrast on dark, same reasoning as --teal-deep above */
  --signal: #34d399;
  --signal-soft: rgba(52,211,153,.16);
  --shadow: 0 1px 2px rgba(0,0,0,.3), 0 30px 60px -28px rgba(0,0,0,.55);
}
/* .eco-card / .climate-strip take their background from an inline custom property
   (--about-bg / --climate-bg) set per-page in Grav so editors can pick a color. Inline
   styles always beat stylesheet rules, so that literal light-mode hex would otherwise
   survive into dark mode as a pale, out-of-place patch. Override the resolved background
   directly here instead of the custom property, so dark mode always wins. */
html[data-theme='dark'] .eco-card{background:var(--teal-soft);border-color:var(--teal-line)}
html[data-theme='dark'] .climate-strip{background:var(--sage-soft);border-block-color:var(--sage-line)}
/* .nav-pill's glass blur is tied to white specifically (a frosted light pane), needs a dark
   counterpart rather than a token flip */
html[data-theme='dark'] .nav-pill{background:rgba(20,22,26,.72)}
html[data-theme='dark'] [data-scrolled='true'] .nav-pill{background:rgba(20,22,26,.94)}
/* Button label color on a colored (orange/teal) fill needs to stay a fixed dark tone —
   those fills don't get lighter in dark mode, so following --ink (which flips light) here
   would put near-white text on a still-mid-brightness orange/teal. */
html[data-theme='dark'] .btn--primary,
html[data-theme='dark'] .btn--accent{color:#14161a}
html[data-theme='dark'] .dash-row-status{color:#34d399}

/* ==========================================================================
   RESET
   ========================================================================== */

*,*::before,*::after{box-sizing:border-box}
*{margin:0}
html,body{height:100%}
html{scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){ html{scroll-behavior:auto} }
body{
  font-family:var(--font-body);color:var(--ink-soft);background:var(--paper);
  line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
}
body::after{
  content:'';position:fixed;inset:0;z-index:1;pointer-events:none;opacity:.025;mix-blend-mode:multiply;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Ambient background — a handful of large, pale, fixed circles that stay put while
   content scrolls over them. Kept very low-opacity and sparse so it reads as texture,
   not decoration competing with content. */
.bg-ambient{position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none}
.bg-ambient-c{position:absolute;border-radius:50%}
.bg-ambient-c--1{width:24rem;height:24rem;top:-5rem;left:6vw;background:var(--teal);opacity:.08}
.bg-ambient-c--2{width:3.5rem;height:3.5rem;top:14rem;left:2vw;background:var(--orange);opacity:.12}
.bg-ambient-c--3{width:28rem;height:28rem;top:38vh;left:-4vw;background:var(--ink-faint);opacity:.05}
.bg-ambient-c--4{width:22rem;height:22rem;top:46vh;left:14vw;background:var(--ink-faint);opacity:.05}
.bg-ambient-c--5{width:2.75rem;height:2.75rem;top:62vh;right:26vw;background:var(--orange);opacity:.14}
.bg-ambient-c--6{width:2rem;height:2rem;top:66vh;right:22vw;background:var(--ink-faint);opacity:.09}
.bg-ambient-c--7{width:18rem;height:18rem;top:7rem;right:-6rem;background:#d9a86c;opacity:.13}
img,svg{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
ul{list-style:none;padding:0}
h1,h2,h3,h4{font-family:var(--font-display);color:var(--ink);line-height:1.05;font-weight:700;letter-spacing:-0.01em;text-wrap:balance}
:focus-visible{outline:2px solid var(--focus-ring);outline-offset:3px}

.container{width:100%;max-width:var(--container);margin-inline:auto;padding-inline:clamp(1.25rem,4vw,2.5rem)}
.section{padding-block:var(--pad);position:relative}
.section--tight{padding-block:clamp(3rem, 2.5rem + 3vw, 5rem)}
.eyebrow{display:inline-flex;align-items:center;gap:.55em;font-size:.8125rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--orange)}
.eyebrow::before{content:'';width:7px;height:7px;border-radius:50%;background:currentColor}
.section-head{max-width:40rem;margin-bottom:clamp(2.5rem,2rem + 2vw,4rem)}
.section-head h2{margin-top:.6rem}
.section-head p{font-size:var(--fs-lead);color:var(--ink-faint);margin-top:1rem;text-wrap:pretty}
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;padding:0}

/* ==========================================================================
   BUTTONS
   ========================================================================== */

.btn{position:relative;display:inline-flex;align-items:center;gap:.6em;padding:.95em 1.7em;border-radius:999px;font-weight:700;font-size:.9375rem;transition:transform var(--dur-fast) var(--ease-out-soft),box-shadow var(--dur-base) var(--ease-out-soft)}
.btn:active{transform:scale(.96)}
.btn--primary{background:var(--teal);color:var(--ink)}
.btn--accent{background:var(--orange);color:var(--ink)}
/* Crisp, near-neutral elevation on hover instead of a big soft color blob —
   the accent color lives only in the ping ring below, not smeared into the
   shadow too. Sized for a small pill button, not the larger card --shadow
   token (that one's blur radius reads as a stray bar on something this
   small). */
.btn--primary:hover,.btn--accent:hover{box-shadow:0 1px 2px rgba(20,22,26,.06),0 8px 16px -8px rgba(20,22,26,.35)}
.btn--outline{border:1.5px solid var(--line-strong);color:var(--ink)}
.btn--outline:hover{border-color:var(--ink-faint);background:var(--tint)}
@media (hover:hover) and (pointer:fine){ .btn:hover{transform:translateY(-2px)} }
.btn--icon{width:1.05em;height:1.05em;transition:transform var(--dur-base) var(--ease-out-soft)}
.btn:hover .btn--icon{transform:translate(2px,-2px)}

/* Single "signal ping" ring on the filled CTAs, echoing the same expanding-
   ring language already used for the live-status dot (see signal-pulse
   elsewhere in this file) but a one-shot pulse in the button's own accent
   color, not the reserved --signal green (one color, one meaning). No
   background-position/gradient tricks this time — those broke on reflow;
   box-shadow on a pseudo-element has no such dependency. */
.btn--primary::after,.btn--accent::after{
  content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;
}
/* Fixed rgba triplets, not color-mix()/currentColor — this has to work on
   every browser without exception, no modern-CSS-function dependency. */
.btn--primary::after{box-shadow:0 0 0 0 rgba(98,193,211,.55)}
.btn--accent::after{box-shadow:0 0 0 0 rgba(241,154,52,.55)}
html[data-theme='dark'] .btn--primary::after{box-shadow:0 0 0 0 rgba(108,201,219,.55)}
html[data-theme='dark'] .btn--accent::after{box-shadow:0 0 0 0 rgba(242,165,61,.55)}
@media (hover:hover) and (pointer:fine){
  .btn--primary:hover::after{animation:btn-ping-teal 700ms var(--ease-out) 1}
  .btn--accent:hover::after{animation:btn-ping-orange 700ms var(--ease-out) 1}
  html[data-theme='dark'] .btn--primary:hover::after{animation:btn-ping-teal-dark 700ms var(--ease-out) 1}
  html[data-theme='dark'] .btn--accent:hover::after{animation:btn-ping-orange-dark 700ms var(--ease-out) 1}
}
@keyframes btn-ping-teal{0%{box-shadow:0 0 0 0 rgba(98,193,211,.55)}100%{box-shadow:0 0 0 10px rgba(98,193,211,0)}}
@keyframes btn-ping-orange{0%{box-shadow:0 0 0 0 rgba(241,154,52,.55)}100%{box-shadow:0 0 0 10px rgba(241,154,52,0)}}
@keyframes btn-ping-teal-dark{0%{box-shadow:0 0 0 0 rgba(108,201,219,.55)}100%{box-shadow:0 0 0 10px rgba(108,201,219,0)}}
@keyframes btn-ping-orange-dark{0%{box-shadow:0 0 0 0 rgba(242,165,61,.55)}100%{box-shadow:0 0 0 10px rgba(242,165,61,0)}}
@media (prefers-reduced-motion:reduce){
  .btn--primary:hover::after,.btn--accent:hover::after{animation:none}
}

/* ==========================================================================
   NAV
   ========================================================================== */

.site-header{position:fixed;top:clamp(.75rem,1.5vw,1.5rem);left:0;right:0;z-index:100;display:flex;justify-content:center;pointer-events:none}
.nav-pill{pointer-events:auto;display:flex;align-items:center;gap:clamp(1.25rem,3vw,2.5rem);width:min(calc(100% - 2.5rem),78rem);padding:.85rem 1rem .85rem 1.5rem;border-radius:999px;background:rgba(255,255,255,.72);border:1px solid var(--line);backdrop-filter:blur(16px) saturate(1.4);-webkit-backdrop-filter:blur(16px) saturate(1.4);transition:background var(--dur-base) var(--ease-out-soft),box-shadow var(--dur-base) var(--ease-out-soft)}
[data-scrolled='true'] .nav-pill{box-shadow:0 1px 2px rgba(20,22,26,.03), 0 14px 28px -20px rgba(20,22,26,.16);background:rgba(255,255,255,.94)}
.brand{display:flex;align-items:center;gap:.6rem;flex:0 0 auto}
.brand-mark{width:34px;height:34px;flex:none;object-fit:contain;border-radius:6px}
.brand-logo-img{height:56px;width:auto;max-width:14rem;flex:none;object-fit:contain}
/* Two uploaded logos (light/dark) sit in the DOM together and swap via display, matching
   the .theme-toggle-icon sun/moon pattern above rather than a background-image swap, since
   an <img> needs a real src either way and this keeps both variants equally cacheable. */
.brand-logo-img--dark{display:none}
html[data-theme='dark'] .brand-logo-img--light{display:none}
html[data-theme='dark'] .brand-logo-img--dark{display:block}
.brand-word{font-family:var(--font-display);font-weight:800;font-size:1.05rem;color:var(--ink);line-height:1}
.brand-tagline{display:block;font-weight:500;font-size:.6875rem;color:var(--ink-faint)}
.primary-nav{display:none;flex:1 1 auto}
.primary-nav ul{display:flex;align-items:center;gap:clamp(1.1rem,1.8vw,1.9rem);justify-content:center}
.primary-nav a{position:relative;font-size:.9625rem;font-weight:500;color:var(--ink-soft);padding:.5rem .1rem}
.primary-nav a::after{content:'';position:absolute;left:0;right:100%;bottom:4px;height:1px;background:var(--orange);transition:right var(--dur-base) var(--ease-out-soft)}
.primary-nav a:hover{color:var(--ink)}
.primary-nav a:hover::after{right:0}
.primary-nav a.is-active{color:var(--teal-deep)}
.nav-actions{display:flex;align-items:center;gap:.6rem;flex:0 0 auto}
.nav-cta{display:none;flex:0 0 auto}
.nav-toggle{flex:0 0 auto;width:42px;height:42px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;background:var(--ink);color:var(--paper);transition:transform var(--dur-fast) var(--ease-out-soft)}
.nav-toggle:active{transform:scale(.96)}
.nav-toggle-icon{position:relative;width:16px;height:11px}
.nav-toggle-icon span{position:absolute;left:0;width:100%;height:1.5px;background:currentColor;transition:transform var(--dur-base) var(--ease-out-soft),opacity var(--dur-base) var(--ease-out-soft),top var(--dur-base) var(--ease-out-soft)}
.nav-toggle-icon span:nth-child(1){top:0}
.nav-toggle-icon span:nth-child(2){top:5px}
.nav-toggle-icon span:nth-child(3){top:10px}
[data-menu-open='true'] .nav-toggle-icon span:nth-child(1){top:5px;transform:rotate(45deg)}
[data-menu-open='true'] .nav-toggle-icon span:nth-child(2){opacity:0}
[data-menu-open='true'] .nav-toggle-icon span:nth-child(3){top:5px;transform:rotate(-45deg)}
@media (min-width:900px){ .primary-nav{display:flex} .nav-cta{display:inline-flex} .nav-toggle{display:none} }

.mobile-menu{position:fixed;inset:0;z-index:99;display:flex;flex-direction:column;justify-content:center;padding:6rem 2rem 3rem;background:var(--paper);opacity:0;transform:scale(.97);transform-origin:top right;visibility:hidden;transition:opacity var(--dur-modal) var(--ease-out-soft),transform var(--dur-modal) var(--ease-out-soft),visibility 0s linear var(--dur-modal)}
[data-menu-open='true'] .mobile-menu{opacity:1;transform:scale(1);visibility:visible;transition:opacity var(--dur-modal) var(--ease-out-soft),transform var(--dur-modal) var(--ease-out-soft),visibility 0s}
.mobile-menu ul{display:flex;flex-direction:column;gap:.2rem}
.mobile-menu a{font-family:var(--font-display);font-size:clamp(1.75rem,6vw,2.5rem);font-weight:700;padding:.35em 0;display:inline-block;border-bottom:1px solid var(--line)}
.mobile-menu a.is-active{color:var(--teal-deep)}
.mobile-menu .btn{margin-top:2rem;align-self:flex-start}

/* ==========================================================================
   HERO — cinematic focus-pull open: blur/scale/opacity resolve together,
   background depth drifts slower than foreground (parallax)
   ========================================================================== */

.hero{position:relative;min-height:100svh;display:flex;align-items:center;padding-top:4rem;padding-bottom:3rem;overflow:hidden}
/* Base padding-top stays 4rem so tall desktop screens keep their original spacing. The floating nav pill ends ~104-108px from the top; on shorter viewports the centred hero content would slide under it, so the tightened block for viewport heights up to 880px (further down, next to .hero-scrollcue) raises the top padding and compresses the vertical rhythm. <=700px has its own value further down. */
.hero-grid{position:relative;z-index:1;display:grid;gap:clamp(2.5rem,4vw,3rem);align-items:center}
@media (min-width:980px){ .hero-grid{grid-template-columns:1.05fr .95fr} }

.hero-open{opacity:0;filter:blur(6px)}
.hero-open.is-in{opacity:1;filter:blur(0);transition:opacity .9s var(--ease-out),filter .9s var(--ease-out)}
.hero-fly-left{transform:translateX(-130px) rotate(-3deg)}
.hero-fly-left.is-in{transform:none;transition:opacity .9s var(--ease-out),filter .9s var(--ease-out),transform 1.05s var(--ease-fly)}
.hero-fly-right{transform:translateX(160px) rotate(4deg) scale(.92)}
.hero-fly-right.is-in{transform:none;transition:opacity .9s var(--ease-out),filter .9s var(--ease-out),transform 1.15s var(--ease-fly)}

.hero-eyebrow{display:inline-flex;align-items:center;gap:.5rem;font-size:.75rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--teal-deep);background:var(--teal-soft);border:1px solid var(--teal-line);padding:.4rem .85rem;border-radius:999px;margin-bottom:1.5rem}
.hero-eyebrow i{width:7px;height:7px;border-radius:50%;background:var(--signal);box-shadow:0 0 0 3px var(--signal-soft);animation:signal-pulse 2.4s var(--ease-in-out) infinite}
@keyframes signal-pulse{0%,100%{box-shadow:0 0 0 3px var(--signal-soft)}50%{box-shadow:0 0 0 6px var(--signal-soft)}}
.hero-title{font-size:var(--fs-hero);letter-spacing:-.02em}
.hero-title em{font-style:normal;color:var(--orange)}
.hero-lede{max-width:32rem;font-size:var(--fs-lead);color:var(--ink-soft);margin-top:1.5rem;text-wrap:pretty}
.hero-actions{display:flex;flex-wrap:wrap;gap:.9rem;margin-top:2.25rem}

.hero-notice{display:inline-flex;align-items:center;gap:.7rem;max-width:100%;margin-top:1rem;padding:.4rem 1rem .4rem .4rem;border:1px solid var(--teal-line);border-radius:999px;background:var(--teal-soft);font-size:.875rem;font-weight:600;color:var(--ink);transition:border-color var(--dur-base) var(--ease-out-soft)}
.hero-notice-tag{flex:none;padding:.25rem .65rem;border-radius:999px;background:var(--teal-deep);color:#fff;font-size:.6875rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
html[data-theme='dark'] .hero-notice-tag{color:#14161a}
.hero-notice-text{min-width:0;text-wrap:balance}
.hero-notice-arrow{flex:none;width:14px;height:14px;color:var(--teal-deep);transition:transform var(--dur-fast) var(--ease-out-soft)}
@media (hover:hover) and (pointer:fine){
  .hero-notice:hover{border-color:var(--teal-deep)}
  .hero-notice:hover .hero-notice-arrow{transform:translateX(3px)}
}
@media (max-width:420px){ .hero-notice{border-radius:1.25rem;align-items:flex-start;padding-right:.9rem} .hero-notice-arrow{margin-top:.35rem} }

.dash{position:relative;background:var(--paper);border:1px solid var(--line);border-radius:var(--radius-l);box-shadow:var(--shadow);padding:1.5rem;max-width:26rem;margin-inline:auto;margin-top:1.5rem}
.dash-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.1rem}
.dash-title{font-size:.8125rem;font-weight:700;color:var(--ink-faint);letter-spacing:.04em;text-transform:uppercase}
.dash-dots{display:flex;gap:.35rem}
.dash-dots span{width:7px;height:7px;border-radius:50%;background:var(--line)}
.dash-chart{width:100%;height:auto;margin-bottom:1.1rem}
.dash-chart path{fill:none;stroke:var(--orange);stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round}
.dash-rows{display:flex;flex-direction:column;gap:.65rem}
.dash-row{display:flex;align-items:center;justify-content:space-between;font-size:.875rem;padding:.65rem .85rem;border-radius:var(--radius-s);background:var(--tint)}
.dash-row-label{display:flex;align-items:center;gap:.6rem;color:var(--ink)}
.dash-row-label .dot{width:7px;height:7px;border-radius:50%;background:var(--signal);box-shadow:0 0 0 3px var(--signal-soft);animation:signal-pulse 2.4s var(--ease-in-out) infinite}
.dash-row-status{font-size:.75rem;font-weight:700;color:#1a9a56}
.dash-float{position:absolute;background:var(--paper);border:1px solid var(--line);border-radius:var(--radius-m);box-shadow:var(--shadow);padding:.85rem 1.1rem;font-size:.8125rem}
.dash-float b{display:block;font-family:var(--font-display);font-size:1.25rem;color:var(--ink);font-variant-numeric:tabular-nums}
.dash-float--1{top:-4.5rem;left:-1.5rem}
.dash-float--2{bottom:-4.5rem;right:-1.25rem}
.dash{margin-top:3.25rem;margin-bottom:3rem}

.hero-scrollcue{position:absolute;left:50%;bottom:1.75rem;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;gap:.5rem;font-size:.75rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-faint);z-index:1}
.hero-scrollcue span{width:1px;height:28px;background:linear-gradient(var(--ink-faint),transparent);animation:scrollcue-breathe 2.2s var(--ease-in-out) infinite}
@keyframes scrollcue-breathe{0%,100%{opacity:.4;transform:scaleY(.7);transform-origin:top}50%{opacity:1;transform:scaleY(1);transform-origin:top}}
@media (prefers-reduced-motion:reduce){ .hero-scrollcue{display:none} }
/* stacked (mobile/tablet) hero is taller than the viewport and its lower float tile overhangs the bottom edge - the cue would sit on top of it */
@media (max-width:979px){ .hero-scrollcue{display:none} }
/* Short desktop/laptop viewports (height up to ~880px, e.g. 15.6" at 125%): tighten the hero's vertical rhythm so the whole hero incl. the scroll cue fits the first screen. Below ~620px it cannot fit at all, so the cue is dropped there. Only for the two-column layout (>=980px); the stacked layout is taller than any screen anyway and hides the cue. */
@media (max-height:880px) and (min-width:980px){
  .hero{padding-top:clamp(6.5rem,4rem + 8svh,8rem);padding-bottom:2.5rem}
  .hero-eyebrow{margin-bottom:1rem}
  .hero-title{font-size:clamp(2.25rem,1.5rem + 2.4vw,3.75rem)}
  .hero-lede{margin-top:1rem}
  .hero-actions{margin-top:1.5rem}
}
@media (max-height:620px){ .hero-scrollcue{display:none} }

/* ticker/marquee removed — merged into ZAHLEN & FAKTEN below */

/* ==========================================================================
   PROCESS — horizontal pinned scroll, cards gain weight when centered
   ========================================================================== */

.process{background:color-mix(in srgb, var(--paper) 94%, transparent)}
.process-pin{overflow:hidden;min-height:min(64svh,32rem);display:flex;flex-direction:column;justify-content:center;padding-top:clamp(7.5rem,9vw,9rem);will-change:transform}
/* Heading now lives inside .process-pin (moved in so it stays visible for the whole pin
   instead of scrolling away before it), so process-pin's own padding-top is what clears
   the sticky nav; this margin-bottom is what separates the heading from the cards below —
   section-head's usual one stays 0 so the two don't double up. */
.process-head{margin-bottom:clamp(.5rem,1vw,.75rem)}
.process-head .section-head{margin-bottom:0}
.process-track{display:flex;align-items:center;gap:clamp(1.25rem,2.5vw,2rem);width:max-content;padding:1.5rem max(1.25rem,calc(50% - min(78vw,24rem) / 2))}
.process-card{width:min(78vw,24rem);flex:none;background:var(--paper);border:1px solid var(--line);border-radius:var(--radius-l);padding:clamp(1.1rem,1.6vw,1.4rem) clamp(1.75rem,2.5vw,2.25rem);transform:scale(.9) translateY(12px);opacity:.85}
.process-card-index{font-family:var(--font-display);font-size:2.5rem;font-weight:800;color:var(--orange-line)}

/* Scan checklist (Arbeitsweise) — same un-boxed, iconographic register as the Leistungen
   cloud effect (no card/background, just the shapes themselves floating on the section),
   adapted to what Arbeitsweise is actually about: a magnifying glass glides smoothly down
   a 4-line checklist — one line per process card — checking each one off as it passes.
   Glass position is lerped between two cached line positions (measured once, like
   cardCenters above, not read every frame) off self.progress directly, no easing beyond
   the scrub itself. */
.process-wip{width:100%;max-width:18rem;margin:clamp(.75rem,1.5vw,1.25rem) auto 0;display:flex;flex-direction:column;align-items:center;gap:1rem}
.wip-scan-stage{position:relative;width:100%}
.wip-doc{background:var(--paper);border:1px solid var(--line);border-radius:var(--radius-m);padding:1rem 1.25rem;display:flex;flex-direction:column;gap:.85rem}
.wip-doc-line{display:flex;align-items:center;gap:.65rem}
.wip-doc-check{position:relative;width:15px;height:15px;flex:none;border-radius:50%;border:2px solid var(--line-strong);transition:border-color var(--dur-base) var(--ease-out-soft)}
.wip-doc-check::after{content:'';position:absolute;inset:3px;border-radius:50%;background:var(--orange);transform:scale(0);transition:transform var(--dur-fast) var(--ease-fly)}
.wip-doc-line.is-checked .wip-doc-check{border-color:var(--orange)}
.wip-doc-line.is-checked .wip-doc-check::after{transform:scale(1)}
/* .wip-doc-textline replaces the old plain .wip-doc-bar-as-the-whole-line: .wip-doc-bar is
   now just the faint pill "track" underneath, and .wip-doc-text (the step's own title,
   reused from .process-card h3) reveals on top of it via a scroll-scrubbed clip-path as the
   glass passes — main.js drives both per frame off the same raw/lineFrac the checkmark
   timing already uses, no CSS transition (this is a continuous scroll value, the scrub IS
   the easing, same rule as everywhere else driven by self.progress). */
.wip-doc-textline{flex:1;position:relative;height:14px}
.wip-doc-bar{position:absolute;top:50%;left:0;right:0;height:8px;transform:translateY(-50%);border-radius:999px;background:var(--tint)}
.wip-doc-text{position:absolute;inset:0;display:flex;align-items:center;font-family:var(--font-display);font-size:.75rem;font-weight:700;color:var(--ink-soft);white-space:nowrap;overflow:hidden;clip-path:inset(0 100% 0 0)}
.wip-doc-line.is-checked .wip-doc-text{color:var(--ink)}
.wip-glass{position:absolute;top:0;left:0;width:34px;height:34px;pointer-events:none}
.wip-glass-lens{position:absolute;top:0;left:0;width:22px;height:22px;border-radius:50%;border:3px solid var(--orange);background:radial-gradient(circle at 32% 28%,color-mix(in srgb,var(--orange) 22%,transparent),color-mix(in srgb,var(--orange) 8%,transparent));box-shadow:0 0 14px 3px color-mix(in srgb,var(--orange) 45%,transparent)}
.wip-glass-handle{position:absolute;top:15px;left:15px;width:15px;height:3px;border-radius:999px;background:var(--orange);transform:rotate(45deg);transform-origin:left center}
.wip-status{display:flex;align-items:center;gap:.5rem;font-family:var(--font-display);font-weight:700;font-size:.8125rem;line-height:1;letter-spacing:.02em;color:var(--ink-soft);background:var(--orange-soft);border-radius:999px;padding:.5rem 1rem .5rem .75rem}
.wip-status-dot{width:8px;height:8px;border-radius:50%;background:var(--orange);flex:none}

/* Cloud-upload visual — Leistungen pin. Packets travel server -> cloud along a quadratic
   bezier computed per-frame in JS (transform: translate only, no offset-path), looped via
   the same progress-driven cycle pattern as the gear cluster above. */
.switcher-cloud{width:100%;max-width:22rem;margin:clamp(2rem,4vw,3rem) auto 0;display:flex;flex-direction:column;align-items:center;gap:1rem}
.cloud-stage{position:relative;width:100%;height:clamp(7rem,13vw,9rem)}
.cloud-icon{position:absolute;top:6%;left:50%;transform:translateX(-50%);width:clamp(3.5rem,6vw,4.5rem);height:clamp(2rem,3.4vw,2.5rem)}
.cloud-blob{position:absolute;border-radius:50%;background:var(--teal)}
.cloud-blob--1{width:55%;height:100%;left:0;bottom:8%}
.cloud-blob--2{width:65%;height:120%;left:28%;bottom:14%}
.cloud-blob--3{width:45%;height:85%;left:58%;bottom:6%}
.cloud-base{position:absolute;left:2%;right:2%;bottom:0;height:42%;border-radius:999px;background:var(--teal)}
.cloud-packets{position:absolute;inset:0}
.cloud-packet{position:absolute;top:0;left:0;width:7px;height:7px;border-radius:2px;background:var(--orange);opacity:0}
.server-icon{position:absolute;bottom:2%;left:50%;transform:translateX(-50%);width:clamp(2.25rem,4vw,2.75rem);display:flex;flex-direction:column;gap:4px}
.server-bar{position:relative;height:8px;border-radius:3px;background:var(--line-strong)}
.server-bar::before{content:'';position:absolute;left:6px;top:50%;transform:translateY(-50%);width:4px;height:4px;border-radius:50%;background:var(--orange)}
.cloud-label{display:flex;align-items:center;gap:.5rem;font-family:var(--font-display);font-weight:700;font-size:.8125rem;letter-spacing:.02em;color:var(--ink-soft)}
.cloud-label-dot{width:8px;height:8px;border-radius:50%;background:var(--teal-deep);flex:none}

/* Pin scroll-progress rail — fills the dead space centering leaves below the pinned
   content; the fill itself is scrub-driven (JS sets scaleX to self.progress directly,
   no transition — the scrub IS the easing), only the step-label color uses a real
   transition since that's a discrete highlight change, not a continuous scroll value. */
.pin-progress{width:100%;max-width:34rem;margin:clamp(2.5rem,4vw,4rem) auto 0}
.process-pin .pin-progress,.switcher-pin .pin-progress,.zf-pin .pin-progress{margin-top:clamp(1.25rem,2.5vw,2rem)}
.pin-progress-track{height:3px;border-radius:999px;background:var(--line-strong);overflow:hidden}
.pin-progress-fill{height:100%;width:100%;background:var(--orange);border-radius:999px;transform:scaleX(0);transform-origin:left}
.pin-progress-steps{display:flex;justify-content:space-between;margin-top:.75rem;font-family:var(--font-display);font-weight:800;font-size:.8125rem;letter-spacing:.05em;color:var(--ink-faint)}
.pin-progress-steps span{transition:color var(--dur-base) var(--ease-out-soft)}
.pin-progress-steps span.is-active{color:var(--orange)}
.process-card h3{font-size:var(--fs-h3);margin-top:.6rem}
.process-card-text{margin-top:.4rem;color:var(--ink-faint);font-size:.9375rem}

/* ==========================================================================
   LEISTUNGEN — pinned slide sequence, panes scale+clip in with weight
   ========================================================================== */

/* padding-block:0, not just padding-bottom — .switcher-pin already supplies its own
   top clearance for the nav pill, so the generic .section padding-top on top of that
   just stacks extra dead space above it, same reasoning .process (no .section class)
   and .zahlen-fakten (padding-block:0) already apply. */
.switcher-section{padding-block:0}
/* Heading now lives inside .switcher-pin (moved in so it stays visible for the whole pin
   instead of scrolling away before it), so switcher-pin's own padding-top is what clears
   the sticky nav; .switcher-head's margin-bottom is what separates the heading from the
   panel below — section-head's usual one stays 0 so the two don't double up. */
.switcher-head{margin-bottom:clamp(1.5rem,3vw,2.25rem)}
.switcher-head .section-head{margin-bottom:0}
.switcher-pin{padding-top:clamp(7.5rem,9vw,9rem);padding-bottom:clamp(1.5rem,2.5vw,2.5rem);min-height:min(78svh,36rem);display:flex;flex-direction:column;justify-content:center;will-change:transform}
.switcher{display:grid;gap:2rem;width:100%}
@media (min-width:900px){ .switcher{grid-template-columns:.85fr 1.15fr;align-items:stretch} }
.switcher-list{display:flex;flex-direction:column;justify-content:center;gap:.5rem}
.switcher-tab{display:flex;align-items:center;gap:1rem;padding:.85rem 1.25rem;border-radius:var(--radius-m);border:1px solid transparent;text-align:left;transition:background var(--dur-base) var(--ease-out-soft),border-color var(--dur-base) var(--ease-out-soft),transform var(--dur-fast) var(--ease-out-soft)}
.switcher-tab:active{transform:scale(.96)}
.switcher-tab-index{font-family:var(--font-display);font-weight:800;color:var(--ink-faint);font-size:.9375rem}
.switcher-tab-title{font-family:var(--font-display);font-weight:700;color:var(--ink);font-size:1.0625rem}
.switcher-tab:hover{background:var(--tint)}
.switcher-tab[aria-selected='true']{background:var(--orange-soft);border-color:var(--orange-line)}
.switcher-tab[aria-selected='true'] .switcher-tab-index{color:var(--ink)} /* was var(--orange) on orange-soft bg = 2.09:1, fails 4.5:1 */

.switcher-panel{position:relative;background:var(--tint);border:1px solid var(--line);border-radius:var(--radius-l);padding:clamp(1.5rem,2.75vw,2.25rem);min-height:min(34svh,16rem);display:flex;flex-direction:column;justify-content:center;overflow:hidden}
/* base/no-JS state: only the marked-active pane shows, driven by the static data-active attribute */
.switcher-pane{position:absolute;inset:0;padding:clamp(1.75rem,2.5vw,2.25rem);display:flex;flex-direction:column;justify-content:center;pointer-events:none;opacity:0}
.switcher-pane[data-active='true']{opacity:1;pointer-events:auto}
.switcher-pane.is-current{pointer-events:auto}
/* fallback (reduced motion / no GSAP): instant, transition-based switching, JS toggles .is-active */
.no-scrub .switcher-pane{opacity:0;pointer-events:none;transform:scale(.97);transition:opacity .3s var(--ease-out-soft),transform .3s var(--ease-out-soft)}
.no-scrub .switcher-pane.is-active{opacity:1;transform:none;pointer-events:auto}
.switcher-pane-index{font-family:var(--font-display);font-size:2.25rem;font-weight:800;line-height:1;color:var(--orange)}
.switcher-pane h3{font-size:var(--fs-h3);margin-top:.6rem}
.switcher-pane-text{margin-top:.75rem;font-size:.9375rem;color:var(--ink-soft);max-width:34rem;text-wrap:pretty}

/* ==========================================================================
   ZAHLEN & FAKTEN — pinned, horizontal, INSPIRED by .process-pin's proven
   mechanic (Arbeitsweise) rather than copying its card-box look: a
   horizontal track is translated continuously via gsap.set based on scroll
   progress, and each item's scale/opacity is a continuous function of its
   distance from the centered "focus" position (main.js, same mapRange
   technique process-pin already uses) — but the visual identity here is
   its own thing: bare oversized numerals/wordmarks with no card border at
   all, letting the typography itself carry the weight. Two independent
   horizontal lanes (facts, then partners) keep the whole section short
   instead of stacking six-plus items in a tall column. */

.zf-pin{min-height:min(70svh,34rem);display:flex;flex-direction:column;justify-content:center;padding-top:clamp(7.5rem,9vw,9rem);will-change:transform}
/* Heading now clears the sticky nav via zf-pin's own padding-top, same as .process-head/
   .switcher-head — zf-head's margin-bottom is what separates it from the lanes below;
   section-head's usual one stays 0 so the two don't double up. */
.zf-head{margin-bottom:clamp(1.5rem,3vw,2.25rem)}
.zahlen-fakten{padding-block:0} /* zf-pin handles its own top clearance (padding-top) and bottom buffer (pin-progress) — the generic .section padding-block would just stack extra space on top of that, same reasoning as .process having no .section class at all */
.zahlen-fakten .section-head{margin-bottom:0}
.zf-lane{overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent,black 8%,black 92%,transparent);mask-image:linear-gradient(90deg,transparent,black 8%,black 92%,transparent)}
.zf-partners-caption{margin:clamp(2rem,4vw,3rem) auto clamp(.5rem,1vw,.75rem);max-width:28rem;text-align:center;font-size:.8125rem;color:var(--ink-faint);text-wrap:pretty}

.zf-facts-track{display:flex;align-items:baseline;gap:clamp(2.5rem,6vw,4.5rem);width:max-content;padding-block:.5rem}
.zf-fact{flex:none;display:flex;flex-direction:column;align-items:center;gap:.35rem;opacity:.35;transform:scale(.8)}
.zf-fact b{
  font-family:var(--font-display);font-weight:800;font-size:clamp(1.75rem,1.5rem + 1.4vw,2.375rem);color:var(--ink-faint);
  font-variant-numeric:tabular-nums;line-height:1;
}
.zf-fact-label{font-size:.6875rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);white-space:nowrap}
.zf-fact.is-active b{color:var(--orange)}
.zf-fact.is-active .zf-fact-label{color:var(--ink-soft)}

.zf-partners-track{display:flex;align-items:baseline;gap:clamp(1.75rem,4vw,3rem);width:max-content;padding-block:.5rem}
.zf-partner{
  flex:none;display:flex;flex-direction:column;align-items:center;gap:.3rem;
  opacity:.35;transform:scale(.85);white-space:nowrap;
}
.zf-partner b{font-family:var(--font-display);font-weight:700;font-size:clamp(1.125rem,1rem + .8vw,1.625rem);color:var(--ink-faint);white-space:nowrap}
.zf-partner-label{font-size:.6875rem;font-weight:600;letter-spacing:.04em;color:var(--ink-faint);white-space:nowrap}
.zf-partner.is-active b{color:var(--partner-color, var(--orange))}
.zf-partner.is-active .zf-partner-label{color:var(--ink-soft)}

/* Partner network (Zahlen & Fakten) — same un-boxed, iconographic register as the
   Arbeitsweise scan and Leistungen cloud effects, but themed to the PARTNER half of this
   section rather than the stats half (the trend of rising numbers already covers "Zahlen"
   via the count-up figures above): starts as a single point (the hub, "us") and grows into
   a fully-connected hexagon of 6 partner nodes as scroll progress goes 0 -> 1 — nodes pop in
   as their own spoke reaches them, ring edges only once both nodes they join already exist
   (main.js owns the reveal order; geometry itself is static, only stroke-dasharray/
   dashoffset per edge and each node's scale/opacity animate). The satellite whose index
   matches the currently-focused partner (same raw-index formula the partner lane itself
   uses) still highlights along with its spoke once revealed. */
.zf-wip{width:100%;max-width:8rem;margin:clamp(1rem,2vw,1.5rem) auto 0;display:flex;flex-direction:column;align-items:center;gap:.85rem}
.zf-network{width:100%;height:auto}
.zf-network-line{stroke:var(--line-strong);stroke-width:1.5;opacity:0;transition:stroke var(--dur-base) var(--ease-out-soft)}
.zf-network-line.is-active{stroke:var(--orange)}
.zf-network-hub{fill:var(--orange);transform-box:fill-box;transform-origin:center}
.zf-network-node{fill:var(--orange-line);opacity:0;transform-box:fill-box;transform-origin:center;transition:fill var(--dur-base) var(--ease-out-soft)}
.zf-network-node.is-active{fill:var(--orange)}
.zf-wip-status{display:flex;align-items:center;gap:.5rem;width:max-content;max-width:none;font-family:var(--font-display);font-weight:700;font-size:.8125rem;line-height:1;letter-spacing:.02em;color:var(--ink-soft);background:var(--orange-soft);border-radius:999px;padding:.5rem 1rem .5rem .75rem;white-space:nowrap}
.zf-wip-status-dot{width:8px;height:8px;border-radius:50%;background:var(--orange);flex:none}

@media (prefers-reduced-motion:reduce){
  .zf-fact,.zf-partner{opacity:1!important;transform:none!important}
}
.zf-lane.no-scrub .zf-fact,.zf-lane.no-scrub .zf-partner{opacity:1!important;transform:none!important}

/* ==========================================================================
   MISSION + UMWELT — scrub reveal
   ========================================================================== */

/* Own (smaller) padding-block instead of the generic .section --pad rhythm — this
   section's content (a two-line eyebrow+paragraph next to a compact info card) is much
   shorter than the pinned storytelling sections --pad was tuned for, so the shared value
   left a disproportionate amount of dead space above/below it. */
.mission{background:color-mix(in srgb, var(--tint) 94%, transparent);padding-block:clamp(2rem,1.5rem + 2vw,3.5rem)}
/* Menu jumps (scrollIntoView / #hash) honour scroll-margin-top. The floating nav pill ends ~84-104px from the top, so the section's own top padding alone left the mission text underneath it; margin = 9rem clearance minus the padding the section already has. */
.mission{scroll-margin-top:calc(9rem - clamp(2rem,1.5rem + 2vw,3.5rem))}
.mission-grid{display:grid;gap:clamp(2.5rem,4vw,4rem)}
@media (min-width:960px){ .mission-grid{grid-template-columns:1fr 1fr} }
/* Grid's default align-items:stretch makes both columns match the taller one's height —
   fine when mission_text is long enough to fill it, but a shorter edit (this field is
   content-editable in Grav, so its length isn't fixed) previously left the extra space
   stranded below the text since it stayed top-aligned. Centering distributes it evenly
   above/below instead, so the column reads as intentional at any text length. */
/* container-type:inline-size turns this column into a query container, so mission-copy
   below can size itself off the column's own rendered width (cqi) instead of the full
   viewport (vw) — scales correctly whether this ends up in a half-width desktop grid cell
   or a full-width mobile stack, without needing a separate breakpoint for each. */
.mission-grid > .reveal-left{display:flex;flex-direction:column;justify-content:center;container-type:inline-size}
.mission-copy{font-size:clamp(1.25rem,1.1rem + 1cqi,1.5rem);color:var(--ink);font-weight:500;line-height:1.45;text-wrap:pretty}
.mission-copy .word{opacity:.2}
.mission-side{display:flex;flex-direction:column;gap:1.25rem}
.eco-card{background:var(--about-bg, var(--teal-soft));border:1px solid var(--teal-line);border-radius:var(--radius-l);padding:clamp(1.75rem,3vw,2.25rem);display:flex;align-items:flex-start;gap:1.25rem}
.eco-icon{flex:none;width:2.75rem;height:2.75rem;border-radius:999px;background:var(--paper);display:flex;align-items:center;justify-content:center;color:var(--teal-deep);box-shadow:0 1px 2px rgba(20,22,26,.06)}
.eco-icon svg{width:1.375rem;height:1.375rem}
.eco-body{display:flex;flex-direction:column}
.eco-label{font-size:.75rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--teal-deep)}
.eco-figure{font-family:var(--font-display);font-size:clamp(1.5rem,1.2rem + 1.5vw,2.0625rem);font-weight:800;color:var(--ink);line-height:1.15;margin-top:.4rem}
.eco-text{font-size:.9375rem;color:var(--ink-soft);margin-top:.5rem}
.eco-text a{color:var(--teal-deep);font-weight:700;text-decoration:underline;text-underline-offset:.2em}

/* Klimaschutz strip — deliberately the un-fancy sibling of .eco-card above: no icon, no
   card elevation, no big figure treatment, just a thin banner. --climate-bg is set inline
   per-page from a Grav field, with this var(--teal-soft) fallback so it still looks right
   before an editor ever touches the field. */
.climate-strip{background:var(--climate-bg, var(--sage-soft));border-block:1px solid var(--sage-line)}
.climate-strip-inner{padding-block:.75rem;display:flex;flex-wrap:wrap;align-items:baseline;justify-content:center;gap:.4rem .6rem;text-align:center}
.climate-strip-label{font-size:.75rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--sage-deep);flex:none}
.climate-strip-text{font-size:.8125rem;color:var(--ink-soft)}
.climate-strip-text a{color:var(--sage-deep);font-weight:700;text-decoration:underline;text-underline-offset:.2em}
.mission-signature{font-size:.9375rem;color:var(--ink-faint);text-align:center;text-wrap:pretty}

/* ==========================================================================
   KARRIERE
   ========================================================================== */

.career{background:color-mix(in srgb, var(--paper) 0%, transparent);padding-bottom:2rem}
.career{scroll-margin-top:calc(9rem - var(--pad))} /* same nav clearance as .mission above; --pad is this section's top padding */
.career .section-head{max-width:47.5rem}
.career-panel{background:var(--orange-soft);border:1px solid var(--orange-line);border-radius:var(--radius-l);padding:clamp(1.75rem,3vw,3rem);margin-top:clamp(1.25rem,2.5vw,2rem)}
.career-grid{display:grid;gap:clamp(2rem,4vw,3rem)}
@media (min-width:820px){ .career-grid{grid-template-columns:1fr 1fr} }
.career-col h3{font-size:var(--fs-h3);margin-bottom:1.1rem}
.career-list{display:flex;flex-direction:column;gap:.85rem}
.career-list li{display:flex;align-items:flex-start;gap:.7rem;font-size:.9375rem;line-height:1.55;color:var(--ink-soft)}
.career-list svg{width:18px;height:18px;flex:none;margin-top:.2rem;color:var(--orange)}
/* The 5th, keywords-only benefit item — no checkmark (it's not a discrete claim like the
   others, just a loose list of extras), so no icon gap to match; padding-left keeps its
   text starting at the same position as the checked items' text above it. Smaller/muted
   per request ("minimal kleiner") rather than a full type-scale step down. */
.career-list .career-list-extra{font-size:.75rem;color:var(--ink-faint);padding-left:calc(18px + .7rem)}
.career-cta{margin-top:2.25rem;padding-top:1.75rem;border-top:1px solid var(--orange-line);display:flex;flex-wrap:wrap;align-items:center;gap:1.25rem}
.career-email{font-size:.875rem;color:var(--ink-faint)}
.career-cta-info{margin-inline-start:auto}

/* Career info popup — native <dialog> for free focus-trap/Escape/backdrop-click handling.
   @starting-style drives the enter transition (no JS animation state needed); the same
   properties also cover the exit, since a plain (non-modal) close skips the transition
   otherwise. Modals stay centered per convention, so no trigger-anchored transform-origin. */
.career-dialog{width:min(92vw,34rem);max-height:min(86vh,42rem);overflow-y:auto;margin:auto;padding:clamp(1.75rem,3vw,2.5rem);border:1px solid var(--line);border-radius:var(--radius-l);background:var(--paper);color:var(--ink);box-shadow:var(--shadow);opacity:0;transform:scale(.96);transition:opacity var(--dur-modal) var(--ease-out-soft),transform var(--dur-modal) var(--ease-out-soft),overlay var(--dur-modal) allow-discrete,display var(--dur-modal) allow-discrete}
.career-dialog[open]{opacity:1;transform:scale(1)}
@starting-style{ .career-dialog[open]{opacity:0;transform:scale(.96)} }
.career-dialog::backdrop{background:rgba(20,22,26,.55);opacity:0;transition:opacity var(--dur-modal) var(--ease-out-soft),overlay var(--dur-modal) allow-discrete,display var(--dur-modal) allow-discrete}
.career-dialog[open]::backdrop{opacity:1}
@starting-style{ .career-dialog[open]::backdrop{opacity:0} }
.career-dialog h3{font-size:var(--fs-h3);padding-right:2.5rem}
.career-dialog h4{font-size:.9375rem;font-weight:700;margin-top:1.5rem;margin-bottom:.75rem}
.career-dialog-text{font-size:.9375rem;line-height:1.6;color:var(--ink-soft)}
.career-dialog-text+.career-dialog-text{margin-top:1rem}
.career-dialog-text a{color:var(--orange);font-weight:700;text-decoration:underline;text-underline-offset:.2em}
.career-dialog-tags{display:flex;flex-wrap:wrap;gap:.5rem}
.career-dialog-tags li{font-size:.8125rem;font-weight:600;color:var(--ink-soft);background:var(--tint);border:1px solid var(--line);border-radius:999px;padding:.4rem .85rem}
.career-dialog-close{position:absolute;top:1.25rem;right:1.25rem;width:34px;height:34px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;color:var(--ink-soft);transition:background var(--dur-fast) var(--ease-out-soft),color var(--dur-fast) var(--ease-out-soft)}
.career-dialog-close:hover{background:var(--tint);color:var(--ink)}
.career-dialog-close svg{width:16px;height:16px}
/* Same "reads fine on phone, undersized on an iPad-class viewport" issue already fixed
   for .a11y-toggle above — the popup close button never got the matching bump. */
@media (min-width:701px){
  .career-dialog-close{width:40px;height:40px}
  .career-dialog-close svg{width:18px;height:18px}
}
/* Same iPadOS-desktop-mode caveat as .a11y-toggle: a width query alone isn't enough. */
@media (pointer:coarse){
  .career-dialog-close{width:44px;height:44px}
  .career-dialog-close svg{width:20px;height:20px}
}
@media (prefers-reduced-motion:reduce){
  .career-dialog{transition:opacity var(--dur-modal) linear}
  .career-dialog,.career-dialog[open]{transform:none!important}
}

/* Gravs eigene Form-Plugin-Feldstruktur (forms/default/*.html.twig), im
   Popup umgestylt. Klassen sind vom Plugin fest vorgegeben (form-field,
   form-label, form-data, form-input-wrapper, form-errors, buttons, button) —
   nicht umbenennen, nur umstylen. */
.career-dialog .form-wrapper{margin-top:1.5rem}
.career-dialog .form-field{margin-bottom:1.1rem}
.career-dialog .form-label{margin-bottom:.4rem}
.career-dialog .form-label label{font-size:.8125rem;font-weight:700;color:var(--ink)}
.career-dialog .form-label .required{color:var(--orange);margin-left:.15em}
.career-dialog .form-input-wrapper,
.career-dialog .form-textarea-wrapper{position:relative}
.career-dialog .form-input-wrapper input,
.career-dialog .form-textarea-wrapper textarea{
  box-sizing:border-box;width:100%;padding:.7em .9em;border-radius:var(--radius-s);border:1.5px solid var(--line-strong);
  background:var(--paper);color:var(--ink);font-family:var(--font-body);font-size:.9375rem;
  transition:border-color var(--dur-fast) var(--ease-out-soft);
}
.career-dialog .form-input-wrapper input:focus-visible,
.career-dialog .form-textarea-wrapper textarea:focus-visible{border-color:var(--orange);outline:none}
.career-dialog .form-textarea-wrapper textarea{display:block;min-width:100%;max-width:100%;min-height:6rem;resize:vertical}
.career-dialog .form-errors{margin-top:.4rem}
.career-dialog .form-errors .form-message{font-size:.8125rem;color:#c0392b;display:flex;align-items:center;gap:.4em}
.career-dialog .form-field.has-errors .form-input-wrapper input,
.career-dialog .form-field.has-errors .form-textarea-wrapper textarea{border-color:#c0392b}
/* Honeypot stays functionally present but must never take visual space or
   be reachable by keyboard — the plugin already inlines a visually-hidden
   style, this is just a safety net if that config is ever turned off. */
.career-dialog .form-honeybear{position:absolute!important;width:1px!important;height:1px!important;overflow:hidden!important;clip:rect(1px,1px,1px,1px)!important}
.career-dialog .buttons{margin-top:1.5rem;display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}
/* Same treatment as .btn--accent (see BUTTONS section): a crisp neutral elevation
   plus a one-shot orange ping ring on ::after, not the old standalone soft-glow
   this button used to have on its own. Grav's form-plugin button template has no
   hook to add .btn/.btn--accent classes directly, so the effect is duplicated
   here rather than templated in. Keep both in sync if the ring is ever retuned. */
.career-dialog .button{
  position:relative;display:inline-flex;align-items:center;gap:.6em;padding:.85em 1.6em;border-radius:999px;
  font-weight:700;font-size:.9375rem;background:var(--orange);color:var(--ink);border:none;cursor:pointer;
  transition:transform var(--dur-fast) var(--ease-out-soft),box-shadow var(--dur-base) var(--ease-out-soft);
}
.career-dialog .button:hover{box-shadow:0 1px 2px rgba(20,22,26,.06),0 8px 16px -8px rgba(20,22,26,.35)}
@media (hover:hover) and (pointer:fine){ .career-dialog .button:hover{transform:translateY(-2px)} }
.career-dialog .button:active{transform:scale(.96)}
.career-dialog .button::after{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;box-shadow:0 0 0 0 rgba(241,154,52,.55)}
html[data-theme='dark'] .career-dialog .button::after{box-shadow:0 0 0 0 rgba(242,165,61,.55)}
@media (hover:hover) and (pointer:fine){
  .career-dialog .button:hover::after{animation:btn-ping-orange 700ms var(--ease-out) 1}
  html[data-theme='dark'] .career-dialog .button:hover::after{animation:btn-ping-orange-dark 700ms var(--ease-out) 1}
}
@media (prefers-reduced-motion:reduce){ .career-dialog .button:hover::after{animation:none} }
/* Grav's own inline success/error banner after submit (forms/form-messages
   → partials/form-messages.html.twig): <div class="notices {status} {green|red|yellow}"> */
.career-dialog .form-wrapper .notices{padding:.85rem 1.1rem;border-radius:var(--radius-s);font-size:.875rem;margin-bottom:1rem}
.career-dialog .form-wrapper .notices.green{background:var(--teal-soft);border:1px solid var(--teal-line);color:var(--teal-deep)}
.career-dialog .form-wrapper .notices.red,
.career-dialog .form-wrapper .notices.yellow{background:#fbe4e1;border:1px solid #e0958a;color:#8a2e22}
/* Google's reCAPTCHA badge, relocated next to the submit button by the inline
   script in hero.html.twig. Google's own inline styles fix it at 256x60px --
   too big to sit next to a pill button, and scale() alone doesn't shrink the
   box it occupies in the flex row (only what's painted inside it), so the
   layout would keep reserving the full 256px width and leave a gap where the
   badge visually ends. The negative margin claws that reserved space back;
   !important is required throughout since these compete with Google's own
   inline styles, which always win on specificity otherwise. */
#project-request-dialog .grecaptcha-badge{
  transform:scale(.6)!important;transform-origin:0 50%!important;
  margin:0 -102px 0 0!important;
}
@media (max-width:480px){
  /* Stack under the button instead of squeezing sideways once the row wraps. */
  #project-request-dialog .grecaptcha-badge{margin:.5rem 0 0!important}
}

/* Project-request popup reuses .career-dialog but carries a full form (name, email,
   company, phone, message textarea) instead of a short info text, so the shared
   86vh/42rem cap made it scroll on most screens. Taller cap, ID-scoped so the
   career info popup (short text only) keeps its original size. Width intentionally
   left at .career-dialog's default (min(92vw,34rem)) -- the textarea's real width
   bug was a wrong CSS selector (form-textarea-wrapper, not form-input-wrapper),
   fixed above; the dialog itself was never meant to grow. */
#project-request-dialog{max-height:min(94vh,54rem)}

/* ==========================================================================
   FINAL CTA
   ========================================================================== */

.final-cta{background:linear-gradient(0deg, color-mix(in srgb,var(--orange-soft) 60%,transparent) 0%, transparent 100%);padding-block:var(--pad) 4rem}
.final-cta-inner{max-width:36rem;margin-inline:auto;text-align:center}
.final-cta-inner h2{font-size:var(--fs-h2);margin-top:.6rem}
.final-cta-inner p{margin-top:1rem;color:var(--ink-faint);font-size:var(--fs-lead)}
.final-cta-inner .hero-actions{justify-content:center;margin-top:2.25rem}

/* Envelope + paper plane — the terminal-typing device read as confusing (and repeated the
   CTA button's own text right above it), so this drops text entirely: a purely visual
   "sending a message" beat. The envelope idles with a small continuous float/tilt; every
   cycle a paper plane launches from the flap, arcs up and away fading out, then resets —
   an infinite CSS keyframe loop, not scroll-tied, direct rather than abstract. */
.final-cta-envelope{position:relative;width:96px;height:70px;margin:2rem auto 0}
.envelope-icon{width:100%;height:100%;filter:drop-shadow(0 8px 16px rgba(20,22,26,.08));animation:cta-envelope-float 3.4s var(--ease-in-out) infinite}
.envelope-plane{position:absolute;width:22px;height:22px;left:52%;top:38%;opacity:0;animation:cta-envelope-send 3.4s var(--ease-out) infinite}
@keyframes cta-envelope-float{0%,100%{transform:translateY(0) rotate(0deg)}50%{transform:translateY(-4px) rotate(-1.5deg)}}
@keyframes cta-envelope-send{
	0%{transform:translate(0,0) scale(.4) rotate(-8deg);opacity:0}
	8%{opacity:1}
	55%{transform:translate(38px,-46px) scale(1) rotate(-8deg);opacity:1}
	72%{opacity:0}
	100%{transform:translate(38px,-46px) scale(1) rotate(-8deg);opacity:0}
}

/* ==========================================================================
   FOOTER
   ========================================================================== */

.site-footer{background:color-mix(in srgb, var(--tint) 94%, transparent);border-top:1px solid var(--line);padding-block:clamp(3rem,5vw,4.5rem) 2rem}
.footer-grid{display:grid;gap:2.5rem}
/* Equal 1fr columns force "Rechtliches" (short content) to the same width as
   "Anschrift" (much more content) — the leftover space then piles up entirely
   inside the last track, right against the divider line below, making the
   whole block read as left-heavy even though the container itself is centered.
   Sizing each column to its own content and letting the grid distribute the
   real leftover space between them (not inside one of them) reads as balanced. */
@media (min-width:780px){ .footer-grid{grid-template-columns:repeat(3,max-content);justify-content:space-between} }
.footer-brand address{font-style:normal;color:var(--ink-faint);font-size:.9375rem;line-height:1.8}
.footer-brand h4,.footer-col h4{font-size:.8125rem;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-bottom:1rem}
.footer-col ul{display:flex;flex-direction:column;gap:.6rem}
.footer-col a{font-size:.9375rem;color:var(--ink-soft)}
.footer-col a:hover{color:var(--ink)}
.footer-col a.is-active{color:var(--teal-deep);font-weight:700}
.footer-bottom{margin-top:clamp(2.5rem,4vw,3.5rem);padding-top:1.5rem;border-top:1px solid var(--line);display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between;align-items:center;font-size:.8125rem;color:var(--ink-faint)}

/* Floating back-to-top — was a plain footer link to #site-header (only reachable once
   already scrolled all the way down, and jumping to a position:fixed header's nominal
   flow position is unreliable across browsers). Now a fixed FAB that stays put over the
   whole page, appearing once there's meaningfully somewhere to scroll back FROM, and
   scrolls smoothly via JS rather than relying on the anchor jump. */
.scroll-top{position:fixed;right:clamp(1rem,3vw,2rem);bottom:clamp(1rem,3vw,2rem);z-index:90;width:42px;height:42px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:var(--paper);border:1px solid var(--line);color:var(--ink-soft);box-shadow:var(--shadow);opacity:0;transform:translateY(10px) scale(.9);pointer-events:none;transition:opacity var(--dur-base) var(--ease-out-soft),transform var(--dur-base) var(--ease-out-soft),border-color var(--dur-fast) var(--ease-out-soft),color var(--dur-fast) var(--ease-out-soft)}
.scroll-top.is-visible{opacity:1;transform:none;pointer-events:auto}
.scroll-top:hover{border-color:var(--ink-faint);color:var(--ink)}
.scroll-top:active{transform:scale(.96)}
.scroll-top.is-visible:active{transform:scale(.96)}
@media (hover:hover) and (pointer:fine){ .scroll-top.is-visible:hover{transform:translateY(-3px)} }

/* ==========================================================================
   REVEAL — dramatic fly-in: large travel distance, slight rotation, overshoot
   ========================================================================== */

.reveal{opacity:0;filter:blur(5px);transform:translateY(70px) scale(.92) rotate(-1.2deg)}
.reveal.is-visible{
  opacity:1;filter:blur(0);transform:none;
  transition:opacity .8s var(--ease-out),filter .8s var(--ease-out),transform 1.05s var(--ease-fly);
}

/* alternate direction per grid item for a livelier, less uniform entrance */
.reveal-stagger>*{opacity:0;filter:blur(4px);transform:translateY(56px) scale(.9) rotate(-1.5deg)}
.reveal-stagger>*:nth-child(even){transform:translateY(56px) scale(.9) rotate(1.5deg)}
.reveal-stagger.is-visible>*{
  opacity:1;filter:blur(0);transform:none;
  transition:opacity .7s var(--ease-out),filter .7s var(--ease-out),transform .95s var(--ease-fly);
}
.reveal-stagger.is-visible>*:nth-child(1){transition-delay:0ms}
.reveal-stagger.is-visible>*:nth-child(2){transition-delay:90ms}
.reveal-stagger.is-visible>*:nth-child(3){transition-delay:180ms}
.reveal-stagger.is-visible>*:nth-child(4){transition-delay:270ms}
.reveal-stagger.is-visible>*:nth-child(5){transition-delay:360ms}

/* directional variants for elements that should visibly slide in from a side */
.reveal-left{opacity:0;filter:blur(5px);transform:translateX(-90px) rotate(-2deg)}
.reveal-left.is-visible{opacity:1;filter:blur(0);transform:none;transition:opacity .8s var(--ease-out),filter .8s var(--ease-out),transform 1s var(--ease-fly)}
.reveal-right{opacity:0;filter:blur(5px);transform:translateX(90px) rotate(2deg)}
.reveal-right.is-visible{opacity:1;filter:blur(0);transform:none;transition:opacity .8s var(--ease-out),filter .8s var(--ease-out),transform 1s var(--ease-fly)}

@media (prefers-reduced-motion:reduce){
  .reveal,.reveal-stagger>*,.reveal-left,.reveal-right{opacity:1!important;transform:none!important;filter:none!important}
  .hero-open{opacity:1!important;filter:none!important;transform:none!important}
  .hero-scrollcue span,.hero-eyebrow i,.dash-row-label .dot,.envelope-icon,.envelope-plane{animation:none}
  .envelope-plane{opacity:0}
  .pin-progress{display:none} /* purely a scroll-scrub progress cue; pin-scrubbing itself is off under reduced motion */
  .process-wip,.switcher-cloud{display:none} /* decorative scroll-scrub visuals, no content of their own */
}

.skip-link{position:absolute;top:-100px;left:1rem;z-index:200;background:var(--ink);color:var(--paper);padding:.75rem 1.25rem;border-radius:var(--radius-s);transition:top var(--dur-base) var(--ease-out-soft)}
.skip-link:focus{top:1rem}

/* prose (legal pages) */
.legal-hero{padding-top:clamp(8rem,6rem + 4vw,11rem);padding-bottom:clamp(2.5rem,4vw,4rem)}
.legal-hero h1{font-size:clamp(2.25rem,1.9rem + 2vw,3.5rem)}
.prose{max-width:50rem;font-size:1rem;line-height:1.75}
.prose h2{font-size:1.375rem;margin-top:3.5rem;padding-top:2rem;border-top:1px solid var(--line);margin-bottom:.75rem}
.prose h2:first-child{margin-top:0;padding-top:0;border-top:none}
.prose h4{font-family:var(--font-display);font-size:1.0625rem;font-weight:700;color:var(--ink);margin-top:2rem;margin-bottom:.5rem}
.prose p{margin-top:1rem;color:var(--ink-soft)}
.prose p:first-child{margin-top:0}
.prose ul{margin-top:1rem;padding-left:1.25rem;color:var(--ink-soft);display:flex;flex-direction:column;gap:.5rem}
.prose li{padding-left:.25rem}
.prose strong{color:var(--ink);font-weight:700}
.prose em{font-style:normal;display:block;margin-top:2rem;font-size:.875rem;color:var(--ink-faint)}
.prose a{color:var(--orange);text-decoration:underline;text-underline-offset:.2em}

/* FAQ accordion (E-Rechnung) — one item open at a time (main.js). Expand/collapse animates
   max-height (0 -> a cap comfortably above the longest answer) rather than the newer
   grid-template-rows 0fr/1fr trick — the latter left a stray sliver of content visible in
   testing here despite matching the documented recipe (overflow:hidden + min-height:0 on
   the grid item), so max-height was used instead for a reliably fully-collapsed rest state.
   Icons cross-fade per the project's contextual-icon convention (opacity/scale/blur), same
   as .theme-toggle-icon in the header. */
.faq-list{margin-top:2.5rem;display:flex;flex-direction:column;gap:.75rem}
.faq-item{border:1px solid var(--line);border-radius:var(--radius-m);background:var(--paper);overflow:hidden}
.faq-question{width:100%;display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:1.1rem 1.4rem;font-family:var(--font-display);font-weight:700;font-size:1.0625rem;color:var(--ink);text-align:left;transition:background var(--dur-fast) var(--ease-out-soft)}
.faq-question:hover{background:var(--tint)}
.faq-icon{position:relative;width:20px;height:20px;flex:none;color:var(--ink-soft)}
.faq-icon-svg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) scale(1);opacity:1;filter:blur(0);transition:opacity var(--dur-fast) var(--ease-out-soft),transform var(--dur-fast) var(--ease-out-soft),filter var(--dur-fast) var(--ease-out-soft)}
.faq-icon-svg--minus{opacity:0;transform:translate(-50%,-50%) scale(.25);filter:blur(4px)}
.faq-item.is-open .faq-icon-svg--plus{opacity:0;transform:translate(-50%,-50%) scale(.25);filter:blur(4px)}
.faq-item.is-open .faq-icon-svg--minus{opacity:1;transform:translate(-50%,-50%) scale(1);filter:blur(0)}
.faq-answer{max-height:0;overflow:hidden;transition:max-height var(--dur-modal) var(--ease-out-soft)}
.faq-item.is-open .faq-answer{max-height:60rem}
.faq-answer-inner.prose{max-width:none;padding:0 1.4rem 1.25rem}
.faq-contact{margin-top:2.5rem;padding-top:2rem;border-top:1px solid var(--line);max-width:50rem;color:var(--ink-soft)}
.faq-contact p:first-child{text-wrap:balance}
.faq-contact p{margin-top:.5rem}
.faq-contact strong{color:var(--ink)}

/* ==========================================================================
   ACCESSIBILITY WIDGET
   ========================================================================== */

html[data-fontscale='-1']{font-size:93.75%}
html[data-fontscale='1']{font-size:112.5%}
html[data-fontscale='2']{font-size:125%}

html[data-contrast='high']{
  --ink:#000000; --ink-soft:#000000; --ink-faint:#1a1a1a;
  --paper:#ffffff; --tint:#ffffff; --tint-2:#f0f0f0;
  --line:rgba(0,0,0,.55); --line-strong:rgba(0,0,0,.7);
  --orange:#a34e00; --orange-soft:#ffe6cc; --orange-line:#a34e00;
  --teal:#00646b; --teal-soft:#d9f2f3; --teal-line:#00646b;
  --sage:#0b6b3a; --sage-soft:#d7f2e1; --sage-line:#0b6b3a;
}
html[data-contrast='high'] .btn--primary{background:#000;color:#fff}

html[data-motion='reduce']{scroll-behavior:auto}
html[data-motion='reduce'] *,
html[data-motion='reduce'] *::before,
html[data-motion='reduce'] *::after{
  animation-duration:.001ms !important;
  animation-iteration-count:1 !important;
  transition-duration:.001ms !important;
  scroll-behavior:auto !important;
}

.a11y-widget{position:relative;flex:0 0 auto}
.a11y-toggle{width:38px;height:38px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--line);color:var(--ink-soft);transition:border-color var(--dur-fast) var(--ease-out-soft),color var(--dur-fast) var(--ease-out-soft),transform var(--dur-fast) var(--ease-out-soft)}
/* Fixed 38px reads fine next to the header's clamp()-scaled type on a phone/small desktop,
   but everything else in the nav pill grows on a bigger screen while this stayed pinned at
   38px, making it look undersized on an iPad-class viewport — bump it (and its sibling
   theme toggle) up a notch from tablet width onward. */
@media (min-width:701px){
  .a11y-toggle,.theme-toggle{width:44px;height:44px}
  .a11y-toggle svg,.theme-toggle-icon{width:20px;height:20px}
}
/* iPadOS Safari requests the desktop version of most sites by default, which reports a
   desktop-sized viewport (well past 701px) and then scales that whole rendered page down
   to fit the physical screen — so the 44px/20px bump above technically applies but still
   renders visibly smaller on a real iPad than the numbers suggest. `pointer: coarse`
   detects an actual touch input directly, independent of whatever viewport width Safari
   is pretending to have, so it isn't fooled by desktop-mode the way a width query is. */
@media (pointer:coarse){
  .a11y-toggle,.theme-toggle{width:48px;height:48px}
  .a11y-toggle svg,.theme-toggle-icon{width:22px;height:22px}
}
.a11y-toggle:active{transform:scale(.96)}
.a11y-toggle:hover{border-color:var(--ink-faint);color:var(--ink)}
.a11y-panel{
  position:absolute;top:calc(100% + .6rem);right:0;z-index:110;width:15rem;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius-m);box-shadow:var(--shadow);
  padding:1rem;display:flex;flex-direction:column;gap:.75rem;
  transform-origin:top right;
  opacity:0;transform:translateY(-6px) scale(.98);visibility:hidden;pointer-events:none;
  transition:opacity var(--dur-base) var(--ease-out-soft),transform var(--dur-base) var(--ease-out-soft),visibility 0s linear var(--dur-base);
}
.a11y-widget[data-open='true'] .a11y-panel{opacity:1;transform:none;visibility:visible;pointer-events:auto;transition:opacity var(--dur-base) var(--ease-out-soft),transform var(--dur-base) var(--ease-out-soft),visibility 0s}
.a11y-row{display:flex;align-items:center;justify-content:space-between;font-size:.875rem;color:var(--ink)}
.a11y-steps{display:flex;gap:.25rem}
.a11y-steps button{width:28px;height:28px;border-radius:8px;border:1px solid var(--line);font-size:.75rem;font-weight:700;display:inline-flex;align-items:center;justify-content:center}
.a11y-steps button[aria-pressed='true']{background:var(--ink);color:var(--paper);border-color:var(--ink)}

/* Theme toggle — same round icon-button footprint as .a11y-toggle for visual parity. Two
   icons sit stacked (absolute + fill-box-free plain layout, no SVG transform-box needed
   since they're not being scaled from a live coordinate) and cross-fade per the project's
   contextual-icon convention: opacity 0->1, scale .25->1, blur 4px->0. */
.theme-toggle{position:relative;width:38px;height:38px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--line);color:var(--ink-soft);transition:border-color var(--dur-fast) var(--ease-out-soft),color var(--dur-fast) var(--ease-out-soft),transform var(--dur-fast) var(--ease-out-soft)}
.theme-toggle:active{transform:scale(.96)}
.theme-toggle:hover{border-color:var(--ink-faint);color:var(--ink)}
.theme-toggle-icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) scale(1);opacity:1;filter:blur(0);transition:opacity var(--dur-fast) var(--ease-out-soft),transform var(--dur-fast) var(--ease-out-soft),filter var(--dur-fast) var(--ease-out-soft)}
.theme-toggle-icon--moon{opacity:0;transform:translate(-50%,-50%) scale(.25);filter:blur(4px)}
html[data-theme='dark'] .theme-toggle-icon--sun{opacity:0;transform:translate(-50%,-50%) scale(.25);filter:blur(4px)}
html[data-theme='dark'] .theme-toggle-icon--moon{opacity:1;transform:translate(-50%,-50%) scale(1);filter:blur(0)}
.a11y-toggle-row input{width:2.25rem;height:1.35rem;appearance:none;border-radius:999px;background:var(--tint-2);border:1px solid var(--line);position:relative;cursor:pointer;transition:background var(--dur-base) var(--ease-out-soft)}
.a11y-toggle-row input::before{content:'';position:absolute;top:1px;left:1px;width:calc(1.35rem - 4px);height:calc(1.35rem - 4px);border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.3);transition:transform var(--dur-base) var(--ease-out-soft)}
.a11y-toggle-row input:checked{background:var(--orange)}
.a11y-toggle-row input:checked::before{transform:translateX(0.9rem)}

/* ==========================================================================
   SHORT VIEWPORT MODE (<=900px tall) — on a normal-width but height-limited
   window (a 15" laptop at 100% scaling commonly nets ~850-900px of actual
   browser viewport once the taskbar and address bar are subtracted), the
   pinned sections' fixed top padding (sized to clear the floating nav pill)
   plus their min-height floors added up to more than the viewport can show
   while pinned — since a pin locks the scroll position, whatever doesn't fit
   simply has no way to become visible. This is a HEIGHT condition, entirely
   independent of the <=700px WIDTH-based mobile mode below (a tall phone and
   a short-but-wide laptop window are different problems); both can apply at
   once with no conflict since the mobile mode already disables pinning
   entirely. Pin scrubbing itself is untouched here — only the fixed
   clearances shrink so the same content fits inside a shorter box. */
@media (max-height:900px) {
  /* .nav-pill floats at top:clamp(.75rem,1.5vw,1.5rem) with its own ~70px of height, so its
     bottom edge lands anywhere from ~83px (narrow) to ~100px (wide) below the viewport top —
     padding-top has to clear the WIDER case with room to spare, not just split the difference. */
  .process-pin,.switcher-pin,.zf-pin{padding-top:clamp(7rem,8vw,7.5rem)}
  .process-pin{min-height:min(50svh,24rem)}
  .switcher-pin{min-height:min(56svh,25rem);padding-bottom:clamp(.75rem,1.5vw,1.25rem)}
  .zf-pin{min-height:min(52svh,24rem)}
  .switcher-head,.zf-head,.process-head{margin-bottom:clamp(.75rem,1.5vw,1.25rem)}
  .switcher-panel{min-height:min(22svh,10rem)}
  .switcher-tab{padding:.6rem 1rem}
  .switcher-list{gap:.25rem}
  .process-pin .pin-progress,.switcher-pin .pin-progress,.zf-pin .pin-progress{margin-top:clamp(.5rem,1vw,.75rem)}

  /* Leistungen and Arbeitsweise stayed cramped even after the spacing pass above — .zf-pin
     was already fine here and is untouched. Their remaining slack isn't whitespace anymore,
     it's the scroll-scrub animations (the scanning-checklist visual, the cloud-upload
     illustration) and the card/tab chrome sized for a full-height desktop pin. Both
     animations stay — they're part of what sells the pin, cutting them was the wrong call —
     just at a smaller footprint; both are driven off live stage.clientWidth/clientHeight and
     getBoundingClientRect reads in main.js (updateProcessWip, updateSwitcherCloud), so they
     re-derive every position from whatever size the stage actually renders at here. */
  .process-wip{max-width:13rem;margin-top:clamp(.5rem,1vw,.75rem);gap:.65rem}
  .wip-doc{padding:.7rem .9rem;gap:.6rem}
  .switcher-cloud{max-width:15rem;margin-top:clamp(1rem,2vw,1.5rem);gap:.65rem}
  .cloud-stage{height:clamp(5rem,8vw,6rem)}
  .process-card{padding:clamp(.85rem,1.3vw,1.1rem) clamp(1.4rem,2vw,1.75rem)}
  .process-card-index{font-size:1.85rem}
  .switcher-panel,.switcher-pane{padding:clamp(1.1rem,2vw,1.5rem)}
  .switcher-pane-index{font-size:1.75rem}
}

/* ==========================================================================
   MOBILE SIMPLIFIED MODE (<=700px) — below this width main.js never creates a
   GSAP pin for Arbeitsweise / Leistungen / Zahlen & Fakten at all; it falls
   through to the same static fallback already built for prefers-reduced-motion
   (see the final `else` branch there). This block is the layout half of that:
   restore normal section spacing where the pin used to own it, hide the
   decorative animations that have no meaning without continuous scroll
   progress (magnifying glass, cloud packets, partner-network build-up,
   progress rail), and turn the two horizontal layouts into native
   touch-scrollable, snap-aligned carousels instead of clipped/empty overflow.
   Nothing in this block is reachable above 700px — desktop and tablet
   (iPad Air's 820px portrait included) render exactly as before.
   ========================================================================== */
.swipe-hint{display:none}
@media (max-width:700px) {
  /* "Fernwartung" (TeamViewer remote-support link) makes little sense as a one-tap action on
     a phone — TeamViewer's own remote-control flow assumes a desktop/laptop being controlled,
     not the phone itself. Hidden here only, not for the 700-900px tablet range, which still
     gets the full desktop-style experience. */
  .mobile-menu .btn{display:none}

  /* .hero's 4rem padding-top was sized before the floating nav-pill existed in its current
     (taller, always-visible) mobile form — at this width the pill's bottom edge sits at
     ~84px, so the hero-eyebrow badge (right at the top of hero content) was rendering
     partly underneath it. */
  .hero{padding-top:6.5rem}

  .process,.switcher-section,.zahlen-fakten{padding-block:clamp(3rem,10vw,4rem)}
  .process-pin,.switcher-pin,.zf-pin{min-height:auto;padding-top:0;padding-bottom:0;overflow:visible!important}
  .process-head,.switcher-head,.zf-head{margin-bottom:clamp(1.5rem,6vw,2rem)}

  .process-wip,.switcher-cloud,.zf-wip,.pin-progress{display:none}

  /* Leistungen's panel used the same fixed min-height as desktop (min(34svh,16rem)), sized
     for short placeholder copy — the real service descriptions are full paragraphs and, with
     .switcher-panel's overflow:hidden, were getting silently clipped on phones instead of
     visibly overflowing. Rather than guess a bigger fixed height (still wrong the next time
     this Grav-editable text changes length), let the panel size itself to whichever pane is
     actually showing: only the active one stays in normal flow, the rest are removed from it
     entirely (position:absolute is what forced the fixed-height container in the first
     place). Costs the desktop crossfade-on-switch transition here, which is an acceptable
     trade at this width — an instant swap reads fine on a phone tab list. */
  .switcher-panel{min-height:auto;overflow:visible}
  .switcher-pane{position:static;display:none}
  .switcher-pane.is-active{display:flex}

  /* width:100% overrides the desktop width:max-content — that value exists so GSAP can
     translateX the track across its own full content width; here it would instead make
     the "scroll container" exactly as wide as its content, leaving nothing to scroll. */
  /* align-items:stretch overrides the desktop :center — desktop keeps each card at its own
     content height since GSAP's focus effect draws the eye elsewhere, but side-by-side in a
     swipeable row the height difference between e.g. "Konzept" (shorter text, fewer wrapped
     lines) and its neighbors reads as that one card being smaller, not just vertically
     centered in extra space. Stretching makes every card match the tallest one. */
  .process-track{width:100%;align-items:stretch;overflow-x:auto;-webkit-overflow-scrolling:touch;scroll-snap-type:x mandatory;scrollbar-width:none}
  .process-track::-webkit-scrollbar{display:none}
  .process-card{scroll-snap-align:center;transition:border-color var(--dur-base) var(--ease-out-soft)}
  /* Desktop's per-frame focus effect (scale/opacity/border scrubbed by GSAP) has no scroll
     progress to key off here — main.js instead toggles .is-active on whichever card is
     nearest the track's center as the user swipes, and this is the resulting look: a
     border/index-color pop instead of the continuous scale. */
  .process-card.is-active{border-color:var(--orange-line)}
  .process-card.is-active .process-card-index{color:var(--orange)}

  /* zf-lane is the scroll container (overflow-x), the *-track inside it is the flex row of
     variable-width items — padding-inline here (not on the lane) is what lets the first/last
     item actually reach the container's center; 32vw is a generous stand-in for "roughly half
     an item's width" since, unlike the fixed-width process cards, these vary per label length. */
  .zf-lane{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
  .zf-lane::-webkit-scrollbar{display:none}
  .zf-facts-track,.zf-partners-track{scroll-snap-type:x mandatory;padding-inline:32vw}
  .zf-fact,.zf-partner{scroll-snap-align:center}

  /* Swipe hint — hidden entirely above 700px, persistent (not dismissed after first scroll)
     below it: a hint that vanishes the instant a user so much as nudges the row risks
     disappearing before it's actually registered, especially on a fast/careless swipe. */
  .swipe-hint{display:flex;align-items:center;justify-content:center;gap:.4rem;margin-top:.75rem;font-size:.75rem;font-weight:600;letter-spacing:.02em;color:var(--ink-faint)}
  .swipe-hint-icon{display:inline-block;animation:swipe-hint-nudge 1.6s var(--ease-in-out) infinite}
  /* The partners lane opens on its LAST item (see main.js — it mirrors desktop's inverted-
     progress start), so the only direction left to explore is back toward the earlier ones:
     arrow and nudge both point left instead of the default right. */
  .swipe-hint--reverse .swipe-hint-icon{animation-name:swipe-hint-nudge-reverse}
}
/* ==========================================================================
   REDUCED-MOTION STATIC MODE (desktop/tablet, >700px) - main.js sets
   html.motion-static when the scroll-scrubbed pins are switched off. Without
   scrubbing nothing moves the horizontal tracks any more, so they must not stay
   a single clipped row (Zahlen & Fakten) or grow a scrollbar (Arbeitsweise):
   both become plain wrapping layouts where everything is visible at once.
   (<=700px keeps its snap carousels, see the mobile block above.)
   ========================================================================== */
html.motion-static .process-pin,html.motion-static .zf-pin{min-height:auto;overflow:visible!important;padding-bottom:0}
html.motion-static .process-wip,html.motion-static .zf-wip,html.motion-static .pin-progress{display:none}
html.motion-static .process-track{width:100%;flex-wrap:wrap;justify-content:center;align-items:stretch;padding-inline:0}
html.motion-static .process-card{width:min(100%,22rem);flex:1 1 15rem;max-width:22rem;opacity:1;transform:none}
html.motion-static .zf-lane{overflow:visible;-webkit-mask-image:none;mask-image:none}
html.motion-static .zf-facts-track,html.motion-static .zf-partners-track{width:100%;flex-wrap:wrap;justify-content:center;align-items:baseline;column-gap:clamp(2rem,5vw,3.5rem);row-gap:1.75rem;padding-inline:0}
html.motion-static .zf-partners-track{column-gap:clamp(1.5rem,3.5vw,2.5rem);row-gap:1.25rem}
@keyframes swipe-hint-nudge{0%,100%{transform:translateX(0)}50%{transform:translateX(4px)}}
@keyframes swipe-hint-nudge-reverse{0%,100%{transform:translateX(0)}50%{transform:translateX(-4px)}}
@media (prefers-reduced-motion:reduce){ .swipe-hint-icon{animation:none} }

/* "Jetzt bewerben" popup: same .career-dialog form styling as "Projekt anfragen", plus
   the file-upload field. The form plugin renders that as a Dropzone widget (its own
   dropzone.min.css is loaded by the field template), restyled here to the theme tokens.
   The plain <input type="file"> the field markup starts with is hidden - Dropzone
   creates its own picker. */
.career-dialog-note{margin-top:1rem;font-size:.75rem;line-height:1.5;color:var(--ink-faint)}
.career-dialog-note a{color:var(--ink-soft);text-decoration:underline;text-underline-offset:.2em}
.career-dialog .dropzone{
  position:relative;min-height:5.5rem;padding:1rem;border:1.5px dashed var(--line-strong);border-radius:var(--radius-s);
  background:var(--tint);display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:.75rem;
  cursor:pointer;transition:border-color var(--dur-fast) var(--ease-out-soft),background var(--dur-fast) var(--ease-out-soft);
}
.career-dialog .dropzone:hover,
.career-dialog .dropzone.dz-drag-hover{border-color:var(--orange);background:var(--orange-soft)}
.career-dialog .dropzone > input[type=file]{display:none}
.career-dialog .dropzone .dz-message{width:100%;margin:0;text-align:center;font-size:.875rem;font-weight:600;color:var(--ink-soft)}
/* Uploaded files as compact rows (name left, size, remove right) instead of Dropzone's
   default big gradient thumbnail tiles with the name in a clipped white overlay pill -
   that layout is built for image previews and looked broken for PDF/DOCX. */
.career-dialog .dropzone .dz-preview{
  position:relative;display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;width:100%;min-height:0;margin:0;
  padding:.55rem .8rem;background:var(--paper);border:1px solid var(--line);border-radius:var(--radius-s);
  text-align:left;cursor:default;
}
.career-dialog .dropzone .dz-preview .dz-image,
.career-dialog .dropzone .dz-preview .dz-success-mark,
.career-dialog .dropzone .dz-preview .dz-error-mark{display:none}
.career-dialog .dropzone .dz-preview .dz-details{
  position:static;z-index:auto;opacity:1;flex:1;min-width:0;max-width:none;padding:0;text-align:left;
  display:flex;align-items:center;gap:.6rem;font-size:.8125rem;line-height:1.3;color:var(--ink);
}
.career-dialog .dropzone .dz-preview .dz-filename{order:1;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600;color:var(--ink)}
.career-dialog .dropzone .dz-preview .dz-size{order:2;flex:none;margin:0;font-size:.75rem;color:var(--ink-faint)}
.career-dialog .dropzone .dz-preview .dz-filename span,
.career-dialog .dropzone .dz-preview .dz-size span{background:none;padding:0;border:0;border-radius:0}
.career-dialog .dropzone .dz-preview .dz-filename:hover span{background:none;border:0}
.career-dialog .dropzone .dz-preview .dz-size strong{font-weight:600;color:var(--ink-soft)}
.career-dialog .dropzone .dz-preview .dz-remove{
  position:static;flex:none;margin-left:auto;font-size:1.1rem;line-height:1;color:var(--ink-faint);text-decoration:none;
}
.career-dialog .dropzone .dz-preview .dz-remove:hover{color:var(--ink)}
.career-dialog .dropzone .dz-preview .dz-progress{
  position:absolute;top:auto;bottom:0;left:.8rem;right:.8rem;width:auto;height:3px;margin:0;transform:none;
  background:var(--line);border:0;border-radius:2px;
}
.career-dialog .dropzone .dz-preview .dz-progress .dz-upload{background:var(--orange)}
.career-dialog .dropzone .dz-preview.dz-error .dz-error-message{
  position:static;display:block;opacity:1;width:100%;padding:0;top:auto;left:auto;transform:none;
  background:none;font-size:.75rem;color:#c0392b;
}

/* "Jetzt bewerben" popup: its trigger on the page is the teal .btn--primary, so the submit
   button inside the popup is teal too (the shared .career-dialog .button is orange, matching
   the orange "Projekt anfragen" trigger). Same teal ping ring as .btn--primary. */
#job-application-dialog .button{background:var(--teal)}
html[data-theme='dark'] #job-application-dialog .button{color:#14161a}
#job-application-dialog .button::after{box-shadow:0 0 0 0 rgba(98,193,211,.55)}
html[data-theme='dark'] #job-application-dialog .button::after{box-shadow:0 0 0 0 rgba(108,201,219,.55)}
@media (hover:hover) and (pointer:fine){
  #job-application-dialog .button:hover::after{animation:btn-ping-teal 700ms var(--ease-out) 1}
  html[data-theme='dark'] #job-application-dialog .button:hover::after{animation:btn-ping-teal-dark 700ms var(--ease-out) 1}
}
@media (prefers-reduced-motion:reduce){ #job-application-dialog .button:hover::after{animation:none} }
