:root {
  --color-bg: #071a35;
  --color-bg-alt: #0c2444;
  --color-text: #f3f1ea;
  --color-text-dim: #a9b3c4;
  --color-accent: #ad843e;
  --color-accent-2: #d8b76a;
  --max-width: 1100px;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  background: var(--color-bg);
  color: var(--color-text);
  overflow-x: hidden;
}

a {
  color: inherit;
  text-decoration: none;
}

/* Navbar */
.navbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.25rem 2rem;
  z-index: 100;
  backdrop-filter: blur(8px);
  background: rgba(7, 26, 53, 0.85);
}

.navbar__logo {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.05em;
}

.navbar__logo-icon {
  height: 32px;
  width: auto;
}

.navbar__right {
  display: flex;
  align-items: center;
}

.navbar__links {
  display: flex;
  gap: 2rem;
  list-style: none;
}

.navbar__links a {
  font-size: 0.95rem;
  color: var(--color-text-dim);
  transition: color 0.25s ease;
}

.navbar__links a:hover,
.navbar__links a.is-active {
  color: var(--color-accent-2);
}

.navbar__item--dropdown {
  position: relative;
}

.navbar__item--dropdown > a::after {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: 0.35em;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
}

.navbar__submenu {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 200px;
  margin-top: 0.75rem;
  padding: 0.5rem 0;
  list-style: none;
  background: rgba(7, 26, 53, 0.97);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;
}

.navbar__item--dropdown:hover .navbar__submenu,
.navbar__item--dropdown:focus-within .navbar__submenu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.navbar__submenu a {
  display: block;
  padding: 0.6rem 1.2rem;
  white-space: nowrap;
}

.lang-switch {
  display: flex;
  gap: 0.4rem;
  margin-left: 1.5rem;
}

.lang-switch__btn {
  font-family: inherit;
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  padding: 0.25rem 0.55rem;
  border-radius: 4px;
  border: 1px solid rgba(169, 179, 196, 0.35);
  background: transparent;
  color: var(--color-text-dim);
  cursor: pointer;
  transition: color 0.25s ease, border-color 0.25s ease, background 0.25s ease;
}

.lang-switch__btn:hover {
  color: var(--color-text);
  border-color: var(--color-accent-2);
}

.lang-switch__btn.is-active {
  color: var(--color-bg);
  background: var(--color-accent-2);
  border-color: var(--color-accent-2);
}

.navbar__toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 32px;
  height: 32px;
  margin-left: 1rem;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.navbar__toggle span {
  display: block;
  width: 100%;
  height: 2px;
  background: var(--color-text);
  border-radius: 1px;
  transition: transform 0.25s ease, opacity 0.25s ease;
}

.navbar__toggle[aria-expanded="true"] span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.navbar__toggle[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
}

.navbar__toggle[aria-expanded="true"] span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

@media (max-width: 768px) {
  .navbar {
    flex-wrap: nowrap;
    padding: 1rem 1.5rem;
  }

  .navbar__toggle {
    display: flex;
  }

  .navbar__right {
    justify-content: flex-end;
  }

  /* Positioned absolutely (relative to the fixed .navbar, the nearest positioned ancestor)
     instead of being an in-flow flex-basis:100% item of .navbar__right — that used to make
     the collapsed menu's own min-content width (driven by its widest link label) leak into
     .navbar__right's flex sizing, which could force the whole navbar to wrap onto two rows
     on narrow phones even while the menu was closed. Being absolutely positioned removes it
     from that sizing calculation entirely; a fixed-position ancestor not reflowing when its
     own content grows is also why opening the menu never used to push page content down. */
  .navbar__links {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    padding: 0 1.5rem;
    background: rgba(7, 26, 53, 0.97);
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
  }

  .navbar__links.is-open {
    max-height: 70vh;
    overflow-y: auto;
    padding-bottom: 1rem;
  }

  .navbar__links li {
    width: 100%;
  }

  .navbar__links a {
    display: block;
    padding: 0.75rem 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  }

  /* No hover on touch — the submenu is just always expanded inline under its parent item
     instead of needing a second tap. */
  .navbar__submenu {
    position: static;
    opacity: 1;
    visibility: visible;
    transform: none;
    margin-top: 0;
    padding: 0 0 0 1.25rem;
    background: transparent;
    border: none;
    box-shadow: none;
  }

  .navbar__submenu a {
    padding: 0.6rem 0;
  }
}

/* Hero */
.hero {
  position: relative;
  z-index: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  overflow: hidden;
  background: #ffffff;
  color: var(--color-bg);
}

.hero__bg {
  position: absolute;
  inset: -20%;
  background: radial-gradient(circle at 30% 30%, rgba(173, 132, 62, 0.18), transparent 60%),
              radial-gradient(circle at 70% 70%, rgba(7, 26, 53, 0.07), transparent 60%);
  filter: blur(40px);
  animation: drift 18s ease-in-out infinite alternate;
  z-index: -2;
}

.hero__network {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
}

.hero-edge {
  stroke: rgba(173, 132, 62, 0.45);
  stroke-width: 1.5;
  fill: none;
}

.hero-edge--navy {
  stroke: rgba(7, 26, 53, 0.35);
}

.hero-edge--hub {
  stroke: rgba(173, 132, 62, 0.18);
  stroke-width: 1;
  opacity: 1;
}

.hero-node {
  fill: rgba(173, 132, 62, 0.1);
  stroke: var(--color-accent);
  stroke-width: 1.5;
}

.hero-node--navy {
  fill: rgba(7, 26, 53, 0.06);
  stroke: rgba(7, 26, 53, 0.55);
}

@keyframes drift {
  0%   { transform: translate(0, 0) scale(1); }
  100% { transform: translate(-4%, 3%) scale(1.1); }
}

.hero__content {
  max-width: var(--max-width);
  padding: 0 1.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.hero__logo-heading {
  margin: 0;
}

/* Visible by default (no opacity:0/scale here) — main.js's heroTimeline now does a gsap.from()
   entrance instead of gsap.to(), so the logo only animates IN when GSAP's rAF-driven ticker
   actually ticks. It previously started hidden and relied on that ticker to ever reveal it, which
   left it permanently invisible in any tab that never gains focus/visibility (backgrounded tabs,
   and — caught via Search Console's rendered screenshot — Googlebot's renderer). */
.hero__logo {
  width: min(90%, 560px);
  aspect-ratio: 1112 / 280;
  height: auto;
  filter: drop-shadow(0 10px 40px rgba(173, 132, 62, 0.25));
  animation: heroLogoHeartbeat 1.8s ease-in-out infinite;
}

@keyframes heroLogoHeartbeat {
  0%, 100% { filter: drop-shadow(0 10px 40px rgba(173, 132, 62, 0.25)) drop-shadow(0 0 0 rgba(173, 132, 62, 0)); }
  14%      { filter: drop-shadow(0 10px 40px rgba(173, 132, 62, 0.25)) drop-shadow(0 0 22px rgba(173, 132, 62, 0.55)); }
  28%      { filter: drop-shadow(0 10px 40px rgba(173, 132, 62, 0.25)) drop-shadow(0 0 0 rgba(173, 132, 62, 0)); }
  42%      { filter: drop-shadow(0 10px 40px rgba(173, 132, 62, 0.25)) drop-shadow(0 0 16px rgba(173, 132, 62, 0.4)); }
  70%      { filter: drop-shadow(0 10px 40px rgba(173, 132, 62, 0.25)) drop-shadow(0 0 0 rgba(173, 132, 62, 0)); }
}

/* Visible by default — see .hero__logo's own comment: the hero CTA button used to start hidden
   (opacity:0) and rely on main.js's heroTimeline to reveal it, which never happened if that
   timeline's rAF ticker never ticked (backgrounded tabs, Googlebot's renderer). heroTimeline now
   does a gsap.from() entrance instead, which needs a visible resting state here to animate FROM
   hidden TO — not the other way around. The two other .btn--primary buttons on the site (the
   sandbox CTA's .reveal, integration-layer-detail.html's .fade-in) still start hidden as before:
   both those classes set their own opacity:0 later in this file, overriding this rule's opacity:1
   in the cascade, so their scroll-triggered reveal is unaffected by this change. */
.btn {
  display: inline-block;
  margin-top: 2rem;
  padding: 0.85rem 2rem;
  border: none;
  border-radius: 999px;
  font: inherit;
  font-weight: 600;
  opacity: 1;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.btn--primary {
  background: linear-gradient(90deg, var(--color-accent), var(--color-accent-2));
  color: var(--color-bg);
}

.btn--primary:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 30px rgba(173, 132, 62, 0.4);
}

/* !important on opacity: the entrance animation (see main.js's heroTimeline) sets this element's
   opacity via an inline style once on load, which any plain CSS rule here can't outrank — only
   another !important can. pointer-events: none is what actually blocks a mouse click (see
   main.js's own click-listener fallback for keyboard activation, which pointer-events can't stop). */
.btn--disabled {
  opacity: 0.4 !important;
  cursor: not-allowed;
  pointer-events: none;
}

.hero__active-users {
  margin-top: 1rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: var(--color-text-dim);
  opacity: 0;
  transform: translateY(20px);
}

.hero__active-users-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-accent-2);
  box-shadow: 0 0 0 rgba(216, 183, 106, 0.6);
  animation: activeUsersPulse 2s ease-in-out infinite;
}

@keyframes activeUsersPulse {
  0%   { box-shadow: 0 0 0 0 rgba(216, 183, 106, 0.55); }
  70%  { box-shadow: 0 0 0 6px rgba(216, 183, 106, 0); }
  100% { box-shadow: 0 0 0 0 rgba(216, 183, 106, 0); }
}

/* All 5 slots taken — same red used for error states elsewhere in this project's apps. */
.hero__active-users--full {
  color: #e57373;
  font-weight: 600;
}

.hero__active-users--full .hero__active-users-dot {
  background: #e57373;
  animation: activeUsersPulseFull 2s ease-in-out infinite;
}

@keyframes activeUsersPulseFull {
  0%   { box-shadow: 0 0 0 0 rgba(229, 115, 115, 0.55); }
  70%  { box-shadow: 0 0 0 6px rgba(229, 115, 115, 0); }
  100% { box-shadow: 0 0 0 0 rgba(229, 115, 115, 0); }
}

/* Sandbox host unreachable — not an error state (red, like --full above), just idle: dot stops
   pulsing since there's no "live" data to show, text stays the default dim color. */
.hero__active-users--standby .hero__active-users-dot {
  animation: none;
  opacity: 0.5;
}

.hero__scroll-indicator {
  position: absolute;
  bottom: 2.5rem;
  width: 24px;
  height: 40px;
  border: 2px solid rgba(7, 26, 53, 0.35);
  border-radius: 12px;
  display: flex;
  justify-content: center;
  padding-top: 6px;
}

.hero__scroll-indicator span {
  width: 4px;
  height: 8px;
  background: rgba(7, 26, 53, 0.55);
  border-radius: 2px;
  animation: scrollDot 1.6s ease infinite;
}

@keyframes scrollDot {
  0%   { transform: translateY(0); opacity: 1; }
  100% { transform: translateY(14px); opacity: 0; }
}

/* Sections */
.section {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 7rem 1.5rem;
}

.section--alt {
  max-width: 100%;
  background: var(--color-bg-alt);
}

.section--alt > * {
  max-width: var(--max-width);
  margin-left: auto;
  margin-right: auto;
}

/* This section is left-aligned (unlike the other, centered .section--alt sections), so it must
   use the exact same box-model recipe as .subpage-hero: center a 1100px box against the full
   viewport, then apply the 1.5rem gutter on that box itself — not against an already-1.5rem-padded
   area (which is what .section's own padding would otherwise do here, and which is also what
   .section--alt's own per-child centering would do for a narrower child like .section__text), or
   the content columns land at different left offsets. (integration-layer-detail.html's own
   #technologies/#ohne-mit used to need this too, back when they were top-level sections — now
   they're .ebenen-slider__page panels, whose own rules already give their .section-inner the same
   padding directly — see .ebenen-slider__page .section-inner below.) */
#architecture-diagrams {
  padding-left: 0;
  padding-right: 0;
}

#architecture-diagrams .section-inner {
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}

/* "Architektur im Detail" becomes a small gold eyebrow label sitting right above the slide
   heading ("Operations" etc.), matching the .subpage-hero__eyebrow/.subpage-hero__title pattern
   used in the header above. Scoped to the section's own top-level title via the child combinator
   — the slide headings reuse .section__title too (same tag/color as integrationLayer.technologies.title)
   but sit deep inside .patterns-grid, so they're unaffected by this override. */
#architecture-diagrams > .section-inner > .section__title {
  font-size: 1rem;
  font-weight: 700;
  color: var(--color-accent-2);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 0.75rem;
}

.section__title {
  font-size: clamp(1.8rem, 4vw, 2.5rem);
  font-weight: 700;
  margin-bottom: 1.5rem;
}

.section__eyebrow {
  color: var(--color-accent-2);
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 0.75rem;
}

.section__text {
  color: var(--color-text-dim);
  font-size: 1.1rem;
  max-width: 65ch;
  line-height: 1.7;
}

.section__text + .section__text {
  margin-top: 1rem;
}

/* color, not opacity: .reveal's scroll animation drives opacity via inline style, which would
   otherwise stomp a plain opacity value back to 1 the moment this fades in. */
.section__text--hint {
  font-size: 0.9rem;
  color: rgba(169, 179, 196, 0.65);
  margin-top: 0.75rem;
}

.subsection__title {
  font-size: clamp(1.1rem, 2vw, 1.4rem);
  font-weight: 700;
  color: var(--color-accent-2);
  margin-top: 3.5rem;
  margin-bottom: 1.5rem;
}

/* When a subsection has its own eyebrow label right above the title (e.g. "Zentralisiert" above
   "Weniger Verbindungen, weniger Risiko"), the big separating gap belongs above the eyebrow, not
   between the eyebrow and its own title — otherwise the label reads as floating, disconnected
   from the heading it labels. */
.section__eyebrow:not(:first-child) {
  margin-top: 3.5rem;
}

.section__eyebrow + .subsection__title {
  margin-top: 0;
}

/* Reveal-on-scroll base state (GSAP overrides via ScrollTrigger) */
.reveal {
  opacity: 0;
  transform: translateY(40px);
}

#advantages .section__title,
#advantages .section__text,
#values .section__title,
#values .section__text {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
}

#about .section__title,
#about .section__text,
#about .subsection__title,
#sandbox .section__title,
#sandbox .section__text,
#expertise .section__title,
#expertise .section__text,
#expertise .subsection__title,
#contact .section__title,
#contact .section__text {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
}

.sandbox-cta {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Tabs for switching between multiple views of the same pattern-card (e.g. "Operations" now,
   more views can be added later as another .diagram-tab/.diagram-view pair sharing a data-view
   value — see main.js's tab-click handler). */
/* Unlike the other .pattern-card uses (individual cards in a grid), this one holds the whole
   tabbed diagram section — it keeps the .pattern-card class only so architecture-diagrams.js's
   .closest(".pattern-card")/arrow-flow querySelectorAll(".pattern-card") still find it, but its
   boxed-card look (background/border/padding/hover-lift) is stripped so the content runs the
   full width of the section instead of sitting in a separate, narrower framed box.
   Compound selector (not just .pattern-card--tabbed) is required here: .pattern-card itself is
   declared later in this file, and with equal specificity the later rule wins on ties — so a
   single-class override placed before it would keep losing to .pattern-card's own border. */
.pattern-card.pattern-card--tabbed {
  background: none;
  border: none;
  padding: 0;
  text-align: left;
}

.pattern-card.pattern-card--tabbed:hover {
  transform: none;
  border-color: transparent;
}

/* Same left-right "book page" slider as .ebenen-slider on integration-layer-detail.html — 3
   slides (Operations/Development/Scaling), each with its own h2.section__title instead of
   a clickable tab, swiped between via js/architecture-diagrams.js. Same carousel-wipe classes
   (is-active/is-leaving/is-entering-back/is-leaving-back) — see .ebenen-slider__page's own
   comment in this file for the full reasoning. */
.diagram-slider {
  position: relative;
}

.diagram-slider__stage {
  position: relative;
  overflow: hidden;
  touch-action: pan-y;
  cursor: grab;
  /* Dragging left/right to swipe shouldn't highlight the slide text underneath the cursor. */
  -webkit-user-select: none;
  user-select: none;
  /* min-height is set per-active-slide by js/architecture-diagrams.js — animate it alongside the
     slides' own 0.6s transform wipe so switching to a shorter/taller view resizes the stage
     smoothly instead of snapping (same technique as .ebenen-slider__stage). */
  transition: min-height 0.6s ease;
}

.diagram-slider__stage:active {
  cursor: grabbing;
}

.diagram-slider__slide {
  position: absolute;
  inset: 0;
  width: 100%;
  transform: translateX(100%);
  transition: transform 0.6s ease;
  pointer-events: none;
}

.diagram-slider__slide.is-entering-back {
  transform: translateX(-100%);
}

.diagram-slider__slide.is-active {
  transform: translateX(0);
  pointer-events: auto;
}

.diagram-slider__slide.is-leaving {
  transform: translateX(-100%);
}

.diagram-slider__slide.is-leaving-back {
  transform: translateX(100%);
}

/* Same swipe hint as .ebenen-slider__swipe-hint — a small mouse outline with a dot drifting
   left/right, hinting you can drag between the 3 views instead of clicking a tab. */
.diagram-slider__swipe-hint {
  width: 40px;
  height: 24px;
  margin: 1.5rem auto 0;
  border: 2px solid var(--color-accent-2);
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-right: 6px;
  pointer-events: none;
}

.diagram-slider__swipe-hint span {
  width: 8px;
  height: 4px;
  background: var(--color-accent-2);
  border-radius: 2px;
  animation: ebenenSwipeDot 1.6s ease infinite;
}

/* prefers-reduced-motion fallback (see js/architecture-diagrams.js) — drops the 3 slides back
   into plain stacked flow, i.e. all 3 views shown one after another, no motion. */
.diagram-slider--static .diagram-slider__stage {
  min-height: auto;
}

.diagram-slider--static .diagram-slider__slide {
  position: static;
  opacity: 1;
  transform: none;
  pointer-events: auto;
  margin-bottom: 3rem;
}

.diagram-slider--static .diagram-slider__swipe-hint {
  display: none;
}

.pattern-diagram.architecture-diagram,
.pattern-diagram.scaling-diagram,
.pattern-diagram.development-diagram {
  max-width: 1040px;
}

.team-box {
  fill: rgba(255, 255, 255, 0.03);
  stroke: rgba(255, 255, 255, 0.18);
  stroke-width: 1.5;
}

/* Repository container in the Development diagram — one gold-tinted box holding every
   ENTEG-RA-provided library, each row tagged with a code icon + real Maven coordinate. */
.dev-repo {
  fill: rgba(216, 183, 106, 0.05);
  stroke: rgba(216, 183, 106, 0.4);
  stroke-width: 1.5;
}

.dev-repo-divider {
  stroke: rgba(216, 183, 106, 0.2);
  stroke-width: 1;
}

/* Artifact coordinate under each module's name — makes "this is a code library pulled in as a
   build dependency" explicit instead of leaving it at a vague "module". */
.dev-module-coord {
  fill: var(--color-text-dim);
  font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
  font-size: 9px;
  text-anchor: start;
  dominant-baseline: middle;
}

.team-service {
  fill: rgba(173, 132, 62, 0.1);
  stroke: var(--color-accent);
  stroke-width: 1.3;
}

.team-error {
  fill: rgba(201, 112, 79, 0.18);
  stroke: #c9704f;
  stroke-width: 1.3;
}

.team-error-text {
  fill: #c9704f;
  font-size: 12px;
  font-weight: 700;
  text-anchor: middle;
  dominant-baseline: middle;
}

.team-plus-badge {
  fill: rgba(173, 132, 62, 0.25);
  stroke: var(--color-accent);
  stroke-width: 1.3;
}

.team-plus-text {
  fill: var(--color-accent-2);
  font-size: 13px;
  font-weight: 700;
  text-anchor: middle;
  dominant-baseline: middle;
}

.team-scale-bracket {
  stroke: rgba(216, 183, 106, 0.5);
  stroke-width: 1.3;
  stroke-dasharray: 4 3;
  fill: none;
}

/* Vertical axis (more teams) and horizontal axis (more services per team) each fade in/out on
   their own loop, offset from each other, so the two scaling demos read as two separate,
   sequential "this is happening" moments rather than everything popping at once. */
.team-scale-v {
  animation: teamScaleV 7s ease-in-out infinite;
}

.team-scale-h {
  animation: teamScaleH 7s ease-in-out infinite;
  animation-delay: 3.5s;
}

@keyframes teamScaleV {
  0%, 50% { opacity: 0; transform: translateY(10px); }
  65%, 85% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(10px); }
}

@keyframes teamScaleH {
  0%, 50% { opacity: 0; transform: translateX(-10px); }
  65%, 85% { opacity: 1; transform: translateX(0); }
  100% { opacity: 0; transform: translateX(-10px); }
}

.architecture-diagram .pattern-label {
  font-size: 15px;
}

.architecture-diagram .pattern-label--small {
  font-size: 13px;
}

.pattern-arrow--alert {
  stroke: #c9704f;
  stroke-dasharray: 4 3;
}

.alert-mail-fly {
  animation: alertMailFly 3s ease-in-out infinite;
}

@keyframes alertMailFly {
  0%   { transform: translateX(0); opacity: 0; }
  8%   { opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translateX(230px); opacity: 0; }
}

.pattern-label.label-start {
  text-anchor: start;
}

.arch-boundary {
  fill: url(#hatch-enteg-ra);
  stroke: rgba(216, 183, 106, 0.3);
  stroke-width: 1.5;
}

.arch-node-circle {
  fill: rgba(255, 255, 255, 0.04);
  stroke: rgba(255, 255, 255, 0.28);
  stroke-width: 1.5;
}

.arch-hub {
  fill: rgba(173, 132, 62, 0.14);
  stroke: var(--color-accent);
  stroke-width: 2;
  animation: hexPulse 2.4s ease-in-out infinite;
}

.arch-icon {
  fill: none;
  stroke: var(--color-accent-2);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Integration patterns */
.patterns-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
  /* No top margin: "Architektur im Detail" and "Operations" should sit as tight together as the
     .section__eyebrow/.section__title pair ("Ebene I"/"Integration Platform") does — the gap
     between them comes entirely from the title's own margin-bottom below. */
  margin-top: 0;
}

.pattern-card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  padding: 2.25rem;
  text-align: center;
  transition: transform 0.3s ease, border-color 0.3s ease;
}

.pattern-card:hover {
  transform: translateY(-6px);
  border-color: var(--color-accent);
}

.pattern-card h3 {
  color: var(--color-accent-2);
  margin-bottom: 0.5rem;
  font-size: 1.3rem;
}

.pattern-card p {
  color: var(--color-text-dim);
  font-size: 0.98rem;
  line-height: 1.6;
  max-width: 55ch;
  margin: 0 auto;
}

.pattern-diagram {
  display: block;
  width: 100%;
  max-width: 640px;
  height: auto;
  margin: 2rem auto 0;
  overflow: visible;
}

.pattern-box {
  fill: rgba(255, 255, 255, 0.04);
  stroke: rgba(255, 255, 255, 0.22);
  stroke-width: 1.5;
}

.pattern-box--sm {
  fill: rgba(255, 255, 255, 0.03);
}

.pattern-box--active {
  fill: rgba(173, 132, 62, 0.1);
  stroke: var(--color-accent);
  filter: drop-shadow(0 0 6px rgba(173, 132, 62, 0.35));
}

.pattern-arrow {
  fill: none;
  stroke: rgba(255, 255, 255, 0.28);
  stroke-width: 1.5;
}

.pattern-arrowhead {
  fill: rgba(255, 255, 255, 0.35);
}

.pattern-group {
  fill: none;
  stroke: var(--color-accent-2);
  stroke-width: 1.5;
  stroke-dasharray: 6 5;
}

.pattern-group-label {
  fill: var(--color-accent-2);
  font-size: 10.5px;
  font-weight: 600;
  text-anchor: middle;
}

/* Data-flow effect lives on the arrow itself now (a marching dashed stroke), not on a
   separate traveling dot — set once the arrow's card scrolls into view (see main.js). Sum of
   the dasharray sets the animation's loop distance (see @keyframes pattern-arrow-flow). */
.pattern-arrow--flowing {
  stroke: var(--color-accent-2);
  stroke-dasharray: 5 6;
  animation: pattern-arrow-flow 0.9s linear infinite;
}

@keyframes pattern-arrow-flow {
  to {
    stroke-dashoffset: -11;
  }
}

.pattern-label {
  fill: var(--color-text);
  font-size: 12px;
  text-anchor: middle;
  dominant-baseline: middle;
}

.pattern-label--small {
  font-size: 10.5px;
  fill: var(--color-text-dim);
}

@keyframes hexPulse {
  0%, 100% {
    opacity: 1;
    filter: drop-shadow(0 0 0 rgba(173, 132, 62, 0));
  }
  50% {
    opacity: 0.85;
    filter: drop-shadow(0 0 5px rgba(173, 132, 62, 0.7));
  }
}

/* Bereiche (Branchen) */
.industries-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1.5rem;
  margin-top: 2.5rem;
}

.industry-card {
  text-align: center;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  padding: 1.75rem 1rem;
  transition: transform 0.3s ease, border-color 0.3s ease;
}

.industry-card:hover {
  transform: translateY(-6px);
  border-color: var(--color-accent);
}

.industry-card--more {
  border-style: dashed;
}

.industry-card__icon {
  width: 48px;
  height: 48px;
  margin: 0 auto 0.85rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.28);
  display: flex;
  align-items: center;
  justify-content: center;
}

.industry-card__icon svg {
  width: 24px;
  height: 24px;
}

.industry-card h3 {
  color: var(--color-text);
  font-size: 1rem;
}

/* Technologiestack */
.skills-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1.5rem;
}

.skill-card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  padding: 1.75rem;
  text-align: center;
  transition: transform 0.3s ease, border-color 0.3s ease;
}

.skill-card:hover {
  transform: translateY(-6px);
  border-color: var(--color-accent);
}

.skill-card h3 {
  margin-bottom: 0.5rem;
  color: var(--color-accent-2);
}

.skill-card p {
  color: var(--color-text-dim);
  font-size: 0.95rem;
}

.skill-icons {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
}

.skill-icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.08);
  cursor: default;
  transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.skill-icon img {
  width: 1.5rem;
  height: 1.5rem;
  object-fit: contain;
}

.skill-icon:hover,
.skill-icon:focus-visible {
  transform: translateY(-3px);
  border-color: var(--color-accent);
  background: rgba(255, 255, 255, 0.08);
}

.skill-icon::after {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(-4px);
  white-space: nowrap;
  background: var(--color-bg-alt);
  color: var(--color-text);
  border: 1px solid var(--color-accent);
  border-radius: 6px;
  padding: 0.3rem 0.6rem;
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: normal;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 10;
}

.skill-icon::before {
  content: "";
  position: absolute;
  top: calc(100% + 3px);
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-bottom-color: var(--color-accent);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
  z-index: 10;
}

.skill-icon:hover::after,
.skill-icon:focus-visible::after {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

.skill-icon:hover::before,
.skill-icon:focus-visible::before {
  opacity: 1;
  visibility: visible;
}

/* About */
.about-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1.5rem;
  margin-top: 2.5rem;
  margin-bottom: 3rem;
}

.about-stat {
  text-align: center;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  padding: 1.75rem 1rem;
  transition: transform 0.3s ease, border-color 0.3s ease;
}

.about-stat:hover {
  transform: translateY(-6px);
  border-color: var(--color-accent);
}

.about-stat__icon {
  width: 48px;
  height: 48px;
  margin: 0 auto 0.85rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.28);
  display: flex;
  align-items: center;
  justify-content: center;
}

.about-stat__icon svg {
  width: 24px;
  height: 24px;
}

.about-stat__number {
  font-size: 2.1rem;
  font-weight: 700;
  color: var(--color-accent-2);
  line-height: 1;
}

.about-stat__label {
  color: var(--color-text-dim);
  font-size: 0.85rem;
  margin-top: 0.4rem;
}

.about-stat__countries {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  height: 2.1rem;
}

.about-stat__country-map {
  width: auto;
  height: 34px;
}

.about-stat__country-label {
  font-size: 6px;
  font-weight: 700;
  fill: var(--color-accent-2);
  letter-spacing: 0.02em;
}

.about-team {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.75rem;
}

.about-team__card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  padding: 2.25rem 1.75rem;
  text-align: center;
  transition: transform 0.3s ease, border-color 0.3s ease;
}

.about-team__card:hover {
  transform: translateY(-6px);
  border-color: var(--color-accent);
}

.about-team__avatar {
  width: 96px;
  height: 96px;
  margin: 0 auto 1.1rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.04);
  border: 1.5px solid var(--color-accent);
  display: flex;
  align-items: center;
  justify-content: center;
}

.about-team__avatar svg {
  width: 48px;
  height: 48px;
}

.about-team__name {
  color: var(--color-text);
  font-size: 1.25rem;
  margin-bottom: 0.2rem;
}

.about-team__role {
  color: var(--color-accent-2);
  font-size: 0.95rem;
  font-weight: 600;
  margin-bottom: 0.9rem;
}

.about-team__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
  list-style: none;
  padding: 0;
  margin: 0;
}

.about-team__tags li {
  background: rgba(255, 255, 255, 0.06);
  color: var(--color-text-dim);
  font-size: 0.8rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.12);
}

/* Contact form */
.contact-form {
  max-width: 480px;
  margin: 2.5rem auto 0;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  text-align: left;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.form-group label {
  font-size: 0.9rem;
  color: var(--color-text-dim);
}

.form-group input,
.form-group textarea {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 10px;
  padding: 0.7rem 0.9rem;
  color: var(--color-text);
  font-family: inherit;
  font-size: 0.95rem;
  resize: vertical;
  transition: border-color 0.25s ease, background 0.25s ease;
}

.form-group input:focus,
.form-group textarea:focus {
  outline: none;
  border-color: var(--color-accent-2);
  background: rgba(255, 255, 255, 0.07);
}

.contact-form__submit {
  align-self: flex-start;
  margin-top: 0.5rem;
  padding: 0.85rem 2rem;
  border: none;
  border-radius: 999px;
  font-weight: 600;
  font-family: inherit;
  font-size: 0.95rem;
  cursor: pointer;
  background: linear-gradient(90deg, var(--color-accent), var(--color-accent-2));
  color: var(--color-bg);
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.contact-form__submit:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 30px rgba(173, 132, 62, 0.4);
}

/* Footer */
.footer {
  text-align: center;
  padding: 2.5rem 1.5rem;
  color: var(--color-text-dim);
  font-size: 0.9rem;
}

/* .page-detail (integration-layer-detail.html, integration-solutions-detail.html) — same font
   pairing as the internal ENTEG-RA_Service_Integration-Service-Libraries.docx (Century Gothic
   headings, Calibri body), scoped to these detail pages rather than changed sitewide. Declared as
   system-font stacks, not a webfont import: both are commonly pre-installed (Windows/Office, many
   Mac systems), and this avoids adding a third-party font CDN to the CSP
   (staticwebapp.config.json's default-src 'self') or sending visitor IPs to one, matching the
   vendored-locally approach already used for js/vendor/gsap.min.js instead of loading it from a
   CDN. Visitors without either font just fall back gracefully to the next stack entry. */
body.page-detail {
  font-family: Calibri, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

body.page-detail .section__title,
body.page-detail .subsection__title,
body.page-detail .subpage-hero__title,
body.page-detail .pattern-card h3 {
  font-family: "Century Gothic", "Copperplate Gothic", Futura, "Trebuchet MS", sans-serif;
}

/* Detail pages (.page-detail layout) want body text to fill the same width as the headings above
   it, unlike the homepage's narrower 65ch reading-width paragraphs. */
body.page-detail .section__text {
  max-width: none;
}

/* Simple, GSAP-free scroll reveal for the detail pages (js/subpage-reveal.js) — the site's own
   .reveal class relies on main.js's GSAP/ScrollTrigger wiring (see style.css's own ".reveal"
   comment: it sets opacity:0 permanently until JS overrides it), which these pages deliberately
   don't load, since main.js also carries a lot of index.html-specific logic (hero canvas, sandbox
   session polling) they have no use for. */
.fade-in {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}

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

/* Lightweight header for subpages: no GSAP entrance animation (see subpage-hero-network.js's
   own comment on why), but the same hexagon-node-network texture as the homepage hero — scoped
   to just this header band. The rest of the page (below the header) keeps its normal section
   backgrounds, no network. No overflow:hidden here on purpose — see .page-network's comment on
   why that used to clip the full-bleed network down to just this column's own width. */
.subpage-hero {
  position: relative;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 8rem 1.5rem 3rem;
  text-align: left;
}

/* Full-bleed within a max-width-constrained parent: js/subpage-hero-network.js sets left/width
   directly in px (computed from the header's own offset from the viewport edge), rather than
   the usual left:50%+translateX(-50%)+width:100vw trick — that trick's math only works if
   nothing clips it, and .subpage-hero used to have overflow:hidden (needed for nothing else;
   the svg's own default overflow:hidden already keeps it from spilling vertically on its own),
   which silently clipped the network down to just this 1100px column instead of the full
   viewport width. Vertical containment (top/height) still doesn't need overflow:hidden for the
   same reason. */
.page-network {
  position: absolute;
  top: 0;
  height: 100%;
  z-index: -1;
  pointer-events: none;
}

/* integration-solutions-detail.html only: same wrapper role as .page-network above, but holding
   3 hex layers instead of 1 — js/subpage-hero-network.js positions/sizes this container the
   same full-bleed way, then generates a layout into each .page-network-slide inside it. */
.page-network-carousel {
  position: absolute;
  top: 0;
  height: 100%;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}

/* Same carousel wipe scheme as .subpage-hero__slider-slide, driven by the same is-active/
   is-leaving toggling in js/subpage-hero-slider.js, so the background moves in lockstep with
   the text. Also hidden (opacity:0) whenever it's neither active nor actively leaving — belt
   and braces on top of the position-based hiding: opacity isn't in the transition list, so it
   snaps instantly the moment a class is added/removed, masking whatever the transform is doing
   at that exact instant rather than depending on the transform reset timing alone. */
.page-network-slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: translateX(100%);
  transition: transform 0.6s ease;
  opacity: 0;
}

.page-network-slide.is-active {
  transform: translateX(0);
  opacity: 1;
}

.page-network-slide.is-leaving {
  transform: translateX(-100%);
  opacity: 1;
}

.page-network-slide.is-entering-back {
  transform: translateX(-100%);
}

.page-network-slide.is-leaving-back {
  transform: translateX(100%);
  opacity: 1;
}

.page-network-edge {
  stroke: rgba(173, 132, 62, 0.2);
  stroke-width: 1.2;
  fill: none;
}

.page-network-edge--dim {
  stroke: rgba(243, 241, 234, 0.05);
}

.page-network-node {
  fill: rgba(173, 132, 62, 0.04);
  stroke: rgba(173, 132, 62, 0.3);
  stroke-width: 1.2;
}

.page-network-node--dim {
  fill: rgba(243, 241, 234, 0.02);
  stroke: rgba(243, 241, 234, 0.07);
}

.subpage-hero__back {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 1.1rem;
  border: 1px solid var(--color-accent-2);
  border-radius: 999px;
  background: transparent;
  color: var(--color-accent-2);
  font-weight: 600;
  font-size: 0.9rem;
  margin-bottom: 1.5rem;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.subpage-hero__back:hover {
  color: var(--color-bg);
  border-color: var(--color-accent);
  background: var(--color-accent-2);
}

.subpage-hero__back-icon {
  flex-shrink: 0;
}

.subpage-hero__eyebrow {
  color: var(--color-accent-2);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 1rem;
}

.subpage-hero__title {
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 700;
  margin-bottom: 1.25rem;
}

/* Auto-rotating summary slider (integration-solutions-detail.html only — see
   js/subpage-hero-slider.js) standing in for a static intro paragraph. Slides are stacked via
   position:absolute inside a fixed-height stage and crossfade between .is-active states, each
   with its own translucent backdrop since the hexagon network sits behind it in this header.
   .subpage-hero__slider--static (added by the script under prefers-reduced-motion) drops the
   stack back into normal flow as a plain list. */
.subpage-hero__slider {
  max-width: 760px;
}

.subpage-hero__slider-stage {
  position: relative;
  min-height: clamp(220px, 27vw, 270px);
  overflow: hidden;
  /* Swipe/drag support (see js/subpage-hero-slider.js) — pan-y keeps vertical page scrolling
     working on touch while handing horizontal gestures to JS instead of the browser. */
  touch-action: pan-y;
  cursor: grab;
  /* Dragging left/right to swipe shouldn't highlight the slide text underneath the cursor. */
  -webkit-user-select: none;
  user-select: none;
}

.subpage-hero__slider-stage:active {
  cursor: grabbing;
}

/* Carousel wipe: resting state (no modifier class) is parked off-stage to the right, ready to
   enter — .is-active brings it to center, .is-leaving carries it further left and off-stage.
   The one thing that broke this before: once an outgoing slide's exit finished, removing
   .is-leaving must NEVER animate it back to its right-side rest spot — see js/subpage-hero-
   slider.js, which forces that specific change through instantly (transition disabled) instead
   of letting the normal transition run it, and ALSO defensively re-primes every incoming slide
   to a clean right-side start before showing it, in case it still had .is-leaving on it from an
   interrupted earlier cycle.
   .is-entering-back / .is-leaving-back mirror that same scheme for the reverse (previous-slide)
   direction, used when a left-to-right drag goes back: entering-back parks the incoming slide
   off-stage left instead of the default right, leaving-back carries the outgoing slide off-stage
   right instead of left. Same instant-reset handling in JS applies to both. */
.subpage-hero__slider-slide {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 4rem 1.85rem;
  background: rgba(12, 36, 68, 0.5);
  border-left: 3px solid var(--color-accent);
  border-radius: 8px;
  color: var(--color-text-dim);
  font-size: 1.1rem;
  line-height: 1.7;
  transform: translateX(100%);
  transition: transform 0.6s ease;
  pointer-events: none;
}

.subpage-hero__slider-slide.is-entering-back {
  transform: translateX(-100%);
}

.subpage-hero__slider-slide.is-active {
  transform: translateX(0);
  pointer-events: auto;
}

.subpage-hero__slider-slide.is-leaving {
  transform: translateX(-100%);
}

.subpage-hero__slider-slide.is-leaving-back {
  transform: translateX(100%);
}

.subpage-hero__slider-dots {
  display: flex;
  gap: 0.7rem;
  margin-top: 1rem;
}

@media (max-width: 768px) {
  .subpage-hero__slider-dots {
    justify-content: center;
  }
}

.subpage-hero__slider-dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 1px solid var(--color-accent-2);
  background: transparent;
  padding: 0;
  cursor: pointer;
  transition: background 0.2s ease;
}

.subpage-hero__slider-dot.is-active {
  background: var(--color-accent-2);
}

.subpage-hero__slider--static .subpage-hero__slider-stage {
  min-height: auto;
}

.subpage-hero__slider--static .subpage-hero__slider-slide {
  position: static;
  opacity: 1;
  transform: none;
  pointer-events: auto;
  margin-bottom: 0.75rem;
}

.subpage-hero__slider--static .subpage-hero__slider-dots {
  display: none;
}

/* integration-layer-detail.html only: "Ebene I" (Integration Platform) and "Ebene II"
   (Integration Services) as two full-section "book pages" you slide between left/right instead
   of scrolling past one stacked under the other — see #ebenenSlider and js/ebenen-slider.js.
   Same carousel-wipe mechanic as .subpage-hero__slider-slide (own block since these pages are
   full sections with their own backgrounds/diagrams, not small text slides, and there are only 2
   of them, no synced background layer to drive). Deliberately NOT given the .section/.section--alt
   classes: it needs to stay full-bleed (for .ebenen-slider__page--alt's edge-to-edge background)
   while still centering each page's own .section-inner column itself, so it defines its own
   padding/width rules below instead of inheriting those classes' behavior. */
.ebenen-slider {
  position: relative;
}

.ebenen-slider__stage {
  position: relative;
  overflow: hidden;
  touch-action: pan-y;
  cursor: grab;
  /* Dragging left/right to swipe shouldn't highlight the page text underneath the cursor. */
  -webkit-user-select: none;
  user-select: none;
  /* min-height is set per-active-page by js/ebenen-slider.js's resizeStageToFitPage — animate it
     alongside the pages' own 0.6s transform wipe so switching to a shorter/taller "Ebene" resizes
     the stage smoothly instead of snapping. */
  transition: min-height 0.6s ease;
}

.ebenen-slider__stage:active {
  cursor: grabbing;
}

/* Same 7rem/1.5rem padding #cta-cockpit's own .section-inner gets for free from its section's own
   .section padding — .ebenen-slider itself has none (needed for .ebenen-slider__page--alt's
   edge-to-edge background), so each page's .section-inner carries that padding directly instead,
   plus the 1100px centering every .section-inner on this page needs. Since it's inside the page
   (not on .ebenen-slider), the page's own background wraps around it too. */
.ebenen-slider__page .section-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 7rem 1.5rem;
}

/* Carousel wipe: same is-active/is-leaving/is-entering-back/is-leaving-back scheme as
   .subpage-hero__slider-slide — see that rule's own comment for the full reasoning. */
.ebenen-slider__page {
  position: absolute;
  inset: 0;
  width: 100%;
  transform: translateX(100%);
  transition: transform 0.6s ease;
  pointer-events: none;
}

.ebenen-slider__page.is-entering-back {
  transform: translateX(-100%);
}

.ebenen-slider__page.is-active {
  transform: translateX(0);
  pointer-events: auto;
}

.ebenen-slider__page.is-leaving {
  transform: translateX(-100%);
}

.ebenen-slider__page.is-leaving-back {
  transform: translateX(100%);
}

.ebenen-slider__page--alt {
  background: var(--color-bg-alt);
}

/* Swipe hint instead of arrows/dots — same idea as the homepage hero's .hero__scroll-indicator
   (a mouse outline with a dot that drifts and fades, hinting "there's more this way"), just
   rotated: a horizontal mouse shape with the dot drifting left/right instead of a vertical one
   drifting down, since dragging is the only way to switch here. Overlaid on the stage itself
   (see .ebenen-slider__stage's position:relative), centered, right above whichever page's own
   eyebrow is showing. */
.ebenen-slider__swipe-hint {
  width: 40px;
  height: 24px;
  margin: 1.5rem auto 0;
  border: 2px solid var(--color-accent-2);
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-right: 6px;
  pointer-events: none;
}

.ebenen-slider__swipe-hint span {
  width: 8px;
  height: 4px;
  background: var(--color-accent-2);
  border-radius: 2px;
  animation: ebenenSwipeDot 1.6s ease infinite;
}

@keyframes ebenenSwipeDot {
  0% {
    transform: translateX(0);
    opacity: 1;
  }
  100% {
    transform: translateX(-14px);
    opacity: 0;
  }
}

/* prefers-reduced-motion fallback (see js/ebenen-slider.js) — drops the two pages back into
   plain stacked flow, i.e. exactly the original pre-slider layout, no motion. */
.ebenen-slider--static .ebenen-slider__stage {
  min-height: auto;
}

.ebenen-slider--static .ebenen-slider__page {
  position: static;
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.ebenen-slider--static .ebenen-slider__swipe-hint {
  display: none;
}

.diagram-pair {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
  margin-top: 2.5rem;
  align-items: stretch;
}

@media (min-width: 800px) {
  .diagram-pair {
    grid-template-columns: 1fr 1fr;
  }
}

/* Both diagram-pair cards stretch to the row's tallest card (align-items: stretch above); the
   label stays pinned at the top, while the diagram itself grows to fill the rest of the card and
   centers vertically — so a shorter diagram (e.g. the single hub diagram next to the taller
   3-diagram stack) still looks visually balanced instead of stuck at the top with dead space
   below it. */
.diagram-pair .pattern-card {
  display: flex;
  flex-direction: column;
}

.pattern-diagram-wrap,
.pattern-stack {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.5rem;
}

/* Full column width, and no top margin — .pattern-diagram's own margin: 2rem auto 0 is meant to
   space a single diagram below its card's h3 title, which would otherwise also stack between
   each of these three, making the gaps far bigger than .pattern-stack's own gap suggests. */
.pattern-diagram--sm {
  max-width: 100%;
  margin-top: 0;
}

.diagram-pair__label {
  color: var(--color-accent-2);
  font-weight: 700;
  margin-bottom: 0.5rem;
}

.value-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.75rem;
  margin-top: 2.5rem;
}

/* Connectors & Adapter sits at the apex, pointing down at the two platforms it connects to
   (Messaging/Event-Streaming and API Gateway) below it. The arrow SVG's viewBox is stretched
   to the container's actual width (preserveAspectRatio="none"), so its 25%/75% anchor points
   stay aligned with the two bottom cards' centers at any width — vector-effect keeps the stroke
   from being distorted by that non-uniform stretch. */
.tech-triangle {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.75rem;
  margin-top: 2.5rem;
}

/* Below 700px the apex card and the two .tech-triangle__bottom cards stack into one column with
   no arrows to fill the visual gap between different-length content (see .tech-triangle__arrows'
   own mobile media query) — a shared min-height keeps all 3 the same size regardless of how many
   lines each card's own title/text wraps to, matching .tech-triangle's own gap above so the
   spacing between every pair of cards reads the same too. */
@media (max-width: 699px) {
  .tech-triangle .value-card {
    min-height: 280px;
  }
}

@media (max-width: 699px) {
  .tech-triangle {
    position: relative;
  }
}

/* Mobile-only stand-in for .tech-triangle__arrows — see the HTML comment on
   .tech-triangle__arrows--mobile. Hidden by default; shown and positioned only below 699px,
   where the 3 cards stack in one column and .tech-triangle__arrows (above) hides itself. */
.tech-triangle__arrows--mobile {
  display: none;
}

@media (max-width: 699px) {
  .tech-triangle__arrows--mobile {
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    /* Sits entirely in #technologies .section-inner's own 1.5rem (24px) right gutter, so it
       never overlaps the cards themselves — only the arrowhead tips reach close to their edge.
       left:100% (not right:0) starts the box AT the card's right edge and extends rightward
       into that gutter, rather than leftward back over the card. */
    left: 100%;
    width: 22px;
    height: 100%;
    overflow: visible;
    pointer-events: none;
  }
}

.value-card--apex {
  width: 100%;
  max-width: 340px;
}

.tech-triangle__arrows {
  width: 100%;
  max-width: 700px;
  height: 50px;
  display: block;
  overflow: visible;
}

.tech-triangle__bottom {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.75rem;
  width: 100%;
  max-width: 700px;
}

@media (min-width: 700px) {
  .tech-triangle__bottom {
    grid-template-columns: 1fr 1fr;
  }
}

/* Below 700px .tech-triangle__bottom collapses to a single stacked column, so the two-pronged
   arrow (pointing at where the bottom cards' centers are in the two-column layout) no longer
   matches — hide it rather than show a diagonal fork over a single-column stack. */
@media (max-width: 699px) {
  .tech-triangle__arrows {
    display: none;
  }
}

.value-card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  padding: 1.75rem;
}

/* Cards with a .value-card__link footer drop the card's own padding in favor of
   .value-card__body's, so the link can span edge-to-edge and pick up the card's own
   bottom corner radius instead of floating as a separate pill inside the padding. */
.value-card--linked {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}

.value-card__body {
  padding: 1.75rem;
  flex: 1;
}

.value-card__icon {
  width: 44px;
  height: 44px;
  margin-bottom: 1rem;
}

.value-card__header {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: 0.75rem;
}

.value-card__header .value-card__icon {
  margin-bottom: 0;
  flex-shrink: 0;
}

.value-card__header .value-card__title {
  margin-bottom: 0;
}

.value-card__title {
  color: var(--color-accent-2);
  font-weight: 700;
  margin-bottom: 0.5rem;
  font-size: 1.05rem;
}

.value-card__text {
  color: var(--color-text-dim);
  font-size: 0.95rem;
  line-height: 1.6;
}

.value-card__link {
  display: block;
  margin-top: auto;
  padding: 0.9rem 1.75rem;
  background: linear-gradient(90deg, var(--color-accent), var(--color-accent-2));
  color: var(--color-bg);
  font-weight: 600;
  font-size: 0.9rem;
  text-align: center;
  transition: filter 0.2s ease;
}

.value-card__link:hover {
  filter: brightness(1.08);
}

.subpage-cta {
  text-align: center;
  padding: 5rem 1.5rem 7rem;
}

.subpage-cta__text {
  color: var(--color-text-dim);
  margin-bottom: 1.5rem;
}

/* The Integration Cockpit blurb directly above the CTA button, in the same section/background —
   centered like the CTA itself, and the CTA's own big padding-top is dropped since this blurb
   already provides the lead-in spacing (no need to double it). */
/* body.page-detail .section__text resets max-width to none (this page wants its regular
   section paragraphs to run full width) — matching that selector's specificity here, and
   declaring it later in the file, is what lets this narrower max-width win over it. */
body.page-detail .cta-cockpit__text {
  text-align: center;
  max-width: 42ch;
  margin: 0 auto;
}

.cta-cockpit .subpage-cta {
  padding-top: 1.5rem;
}
