/* ============================================================
   Header / Top navigation
   ============================================================ */
header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--charcoal);
  color: #fff;
  border-bottom: 3px solid var(--teal);
  display: flex;
  align-items: center;
  gap: var(--space-8);
  padding: 0 var(--container-x);
  height: var(--header-h);
}

header .brand {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  transition: opacity var(--t-base);
}
header .brand:hover {
  text-decoration: none;
  opacity: 0.85;
}
header .brand img {
  display: block;
  height: 34px;
  width: auto;
}

header nav { flex: 1; }

header nav ul {
  list-style: none;
  display: flex;
  gap: 2px;
  align-items: center;
  flex-wrap: wrap;
}

header nav a {
  display: inline-block;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  color: rgba(255, 255, 255, 0.78);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1;
  border-bottom: 2px solid transparent;
  transition: all var(--t-base);
}
header nav a:hover {
  color: #fff;
  text-decoration: none;
  background: rgba(255, 255, 255, 0.04);
}
header nav a.nav-active {
  color: var(--gold);
  border-bottom-color: var(--gold);
}

.menu-toggle {
  display: none;
  background: none;
  border: none;
  color: #fff;
  width: 40px;
  height: 40px;
  cursor: pointer;
}
.menu-toggle svg { width: 24px; height: 24px; }

/* EmSA wordmark on the right of the header. nav has flex:1 so this is
   naturally pushed to the right edge on desktop. Hidden on mobile to keep
   the hamburger row uncluttered. */
.emsa-logo {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  transition: opacity var(--t-base);
}
.emsa-logo img {
  display: block;
  height: 45px;
  width: auto;
}
.emsa-logo:hover { opacity: 0.8; text-decoration: none; }

@media (max-width: 980px) {
  header {
    height: auto;
    padding: 14px 20px;
    flex-wrap: wrap;
    gap: var(--space-4);
  }
  header nav {
    order: 3;
    flex-basis: 100%;
    display: none;
  }
  header nav.open { display: block; }
  header nav ul {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding-top: var(--space-2);
  }
  header nav a {
    display: block;
    padding: 14px 8px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 0;
  }
  header nav a.nav-active { border-bottom-color: var(--gold); }
  .menu-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  /* On mobile the EmSA wordmark stays visible on the right while the
     hamburger sits next to the home button on the left. */
  .emsa-logo {
    display: flex;
    margin-left: auto;
  }
}


/* ============================================================
   Submenu (dropdowns on desktop, accordion on mobile)
   ============================================================ */

header nav .has-submenu {
  position: relative;
}

/* Toggle button is hidden on desktop (hover handles it). Shown on mobile. */
header nav .submenu-toggle {
  display: none;
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.78);
  cursor: pointer;
  padding: 0;
  font-family: inherit;
  font-size: 1rem;
  line-height: 1;
}

/* Submenu list. The base header rule (display: flex on every header nav ul)
   would otherwise make submenu items lay out horizontally; ul.submenu opts
   out and is hidden by default. */
header nav ul.submenu {
  list-style: none;
  display: none;
  flex-direction: column;
  margin: 0;
  padding: 0;
  gap: 0;
}

/* Desktop: open as absolute dropdown on hover or keyboard focus-within.
   Note: the (hover: hover) gate was removed intentionally — Windows touchscreen
   laptops often report (hover: none) even when a mouse is connected, which
   silently suppressed the dropdown. Width gate alone is enough; mobile rules
   below take over for narrow viewports. */
@media (min-width: 981px) {
  header nav .has-submenu:hover > ul.submenu,
  header nav .has-submenu:focus-within > ul.submenu {
    display: block;
    position: absolute;
    top: 100%;
    left: 0;
    min-width: 240px;
    background: var(--charcoal);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-top: 2px solid var(--teal);
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
    padding: 6px 0;
    z-index: 60;
  }
  header nav .submenu a {
    display: block;
    padding: 9px 18px;
    border: none;
    border-radius: 0;
    color: rgba(255, 255, 255, 0.78);
    font-size: var(--text-sm);
    font-weight: 500;
    line-height: var(--lh-normal);
    background: transparent;
  }
  header nav .submenu a:hover,
  header nav .submenu a:focus {
    background: rgba(255, 255, 255, 0.06);
    color: #fff;
    text-decoration: none;
  }
  header nav .submenu a.nav-active {
    color: var(--gold);
  }
}

/* Mobile (hamburger nav open): toggle button visible, submenu opens
   inline as an indented accordion controlled by aria-expanded. */
@media (max-width: 980px) {
  header nav .has-submenu {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
  }
  header nav .has-submenu > a {
    flex: 1;
  }
  header nav .submenu-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    color: rgba(255, 255, 255, 0.78);
    border-left: 1px solid rgba(255, 255, 255, 0.06);
    transition: transform var(--t-base);
  }
  header nav .submenu-toggle[aria-expanded="true"] {
    color: var(--gold);
    transform: rotate(180deg);
  }
  header nav ul.submenu {
    flex-basis: 100%;
  }
  header nav .has-submenu .submenu-toggle[aria-expanded="true"] ~ ul.submenu {
    display: block;
    background: rgba(0, 0, 0, 0.18);
  }
  header nav .submenu a {
    display: block;
    padding: 12px 8px 12px 32px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.04);
    border-radius: 0;
    font-size: var(--text-sm);
    font-weight: 500;
    color: rgba(255, 255, 255, 0.7);
  }
  header nav .submenu a.nav-active {
    color: var(--gold);
    border-bottom-color: var(--gold);
  }
}


/* ============================================================
   Hero (landing page intro band)
   ============================================================ */
.hero {
  position: relative;
  overflow: hidden;
  background: linear-gradient(135deg, #1a1a1a 0%, #2D2D2D 55%, #1d4d3f 100%);
  color: #fff;
  padding: var(--space-20) var(--container-x) var(--space-24);
}
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url('../img/bg-texture-lines.jpeg');
  background-size: cover;
  opacity: 0.07;
  mix-blend-mode: overlay;
  pointer-events: none;
}
.hero::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 6px;
  background: linear-gradient(to bottom, var(--teal) 0%, var(--gold) 100%);
}
.hero-inner {
  position: relative;
  max-width: var(--max-w);
  margin: 0 auto;
}
.hero h1 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-h1);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  color: #fff;
  max-width: 900px;
  margin: 0;
}
.hero .lead {
  color: rgba(255, 255, 255, 0.78);
  font-size: var(--text-lead);
  line-height: 1.65;
  max-width: 760px;
  margin-top: var(--space-6);
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 18px;
  font-family: var(--font-display);
}
.eyebrow::before {
  content: "";
  width: 24px;
  height: 2px;
  background: var(--gold);
}

@media (max-width: 600px) {
  .hero { padding: var(--space-14) 20px var(--space-16); }
}

/* Breadcrumb on dark hero — overrides the default light-bg breadcrumb colours */
.hero .breadcrumb {
  color: rgba(255, 255, 255, 0.55);
  margin-bottom: 18px;
}
.hero .breadcrumb a {
  color: rgba(255, 255, 255, 0.82);
  text-decoration: none;
  transition: color var(--t-base);
}
.hero .breadcrumb a:hover,
.hero .breadcrumb a:focus {
  color: var(--gold);
}
.hero .breadcrumb .sep {
  color: rgba(255, 255, 255, 0.3);
}


/* ============================================================
   Body wrap (landing page topic-block container)
   ============================================================ */
.body-wrap {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: var(--section-y) var(--container-x) 0;
}

.body-wrap > h2 {
  position: relative;
  margin-top: 0;
  margin-bottom: 14px;
  padding-left: 20px;
}
.body-wrap > h2::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  bottom: 8px;
  width: 4px;
  background: var(--teal);
  border-radius: 2px;
}
/* Section break: every h2 inside .body-wrap that follows another flow
   element gets the section-break top margin. Without this rule the
   section-end gap had to live on the first paragraph's margin-bottom,
   which broke as soon as a section ran longer than one paragraph
   (paragraph 1 had a huge bottom margin, paragraphs 2+ had the default,
   so the gap between p1 and p2 was visibly larger than between p2 and p3).
   The first h2 in .body-wrap is unaffected because it has no preceding
   sibling. */
.body-wrap > * + h2 {
  margin-top: var(--space-12);
}
.body-wrap > h2 + p,
.body-wrap p.body-lead {
  color: var(--grey-700);
  margin-top: 0;
  /* Match the default paragraph margin so the gap between paragraph 1
     and paragraph 2 of a section is the same as the gap between
     paragraphs 2 and 3. The section-end gap lives on the next h2's
     margin-top (rule above), not here. */
  margin-bottom: var(--space-4);
  padding-left: 20px;
  /* font-size, line-height, and max-width intentionally NOT set here;
     the global .body-wrap > p rule below handles them so the lead
     paragraph and subsequent paragraphs share one typographic scale and
     one column. The only remaining lead-paragraph distinction is the
     muted grey-700 color above. */
}
.body-wrap p.body-lead {
  margin-bottom: var(--space-6);
}
/* Bold prose-link style: every direct-child paragraph link inside
   .body-wrap (lead or otherwise) renders as a display-font emphasized
   link with a 2px teal-light underline border instead of the base 1px
   underline. Keeps in-prose links visually consistent across a section
   regardless of which paragraph they appear in (the previous rule was
   scoped to the first paragraph after each h2, so a mid-prose link in a
   non-lead paragraph fell back to the regular inline style and looked
   inconsistent next to its lead-paragraph siblings — see the iec-62443
   Worked Example for the originally reported case). The .body-lead
   descendant selector is retained so nested p.body-lead (if any) also
   picks up the style. margin-top is intentionally NOT set here so the
   link does not push below the baseline mid-sentence. */
.body-wrap > p a:not(.glossary-term):not(.tag),
.body-wrap p.body-lead a:not(.glossary-term):not(.tag) {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--teal-dark);
  border-bottom: 2px solid var(--teal-light);
  padding-bottom: 2px;
  text-decoration: none;
  transition: all var(--t-base);
}
.body-wrap > p a:not(.glossary-term):not(.tag):hover,
.body-wrap p.body-lead a:not(.glossary-term):not(.tag):hover {
  border-bottom-color: var(--teal);
  color: var(--teal);
  text-decoration: none;
}

/* Global indent and width cap: every direct-child paragraph (and
   step-column block) in .body-wrap aligns with the h2 left edge AND shares
   the same 780px reading-measure cap. Without the indent rule, paragraphs
   2+ in a multi-paragraph section flushed left of the indented first-after-
   h2 paragraph. Without the width cap, the first paragraph after each h2
   was held to 780px by the "lead" rule above while subsequent paragraphs
   and .step-columns expanded to the full 1180px body-wrap, producing a
   visible width jump between p1 and p2 (and between an intro paragraph and
   the step-columns grid that followed). */
.body-wrap > p,
.body-wrap > .step-columns {
  padding-left: 20px;
  max-width: 960px;
  font-size: 1.05rem;
  line-height: var(--lh-relaxed);
}

/* Two-column layout for ordered-step lists, e.g. the eight IEC 62443 steps.
   Markup:
     <div class="step-columns">
       <div class="step"><h3>1. ...</h3><p>...</p></div>
       <div class="step"><h3>2. ...</h3><p>...</p></div>
       ...
     </div>
   CSS multi-column gives natural column-first reading order (1-4 left,
   then 5-8 right) without grid-row math. break-inside: avoid keeps each
   numbered step intact across the column gap. */
.step-columns .step {
  margin-bottom: var(--space-6);
  break-inside: avoid;
}
.step-columns .step > h3 {
  margin-top: 0;
  margin-bottom: var(--space-2);
}
.step-columns .step > p:last-child {
  margin-bottom: 0;
}
@media (min-width: 860px) {
  .step-columns {
    column-count: 2;
    column-gap: var(--space-10);
  }
}

/* Grey-boxed variant of .step-columns: each card becomes a muted grey box, used
   to set the column sections apart on the Key Management pages. The teal
   .mitigation-grid is reserved for the symmetric-vs-asymmetric comparison.
   Scoped to the --boxed modifier so plain .step-columns elsewhere is unchanged. */
.step-columns--boxed .step {
  background: var(--grey-100);
  border-left: 4px solid var(--grey-400);
  border-radius: var(--radius-sm);
  padding: var(--space-4) var(--space-5);
}
.step-columns--boxed .step > h3 {
  color: var(--ink);
}
.step-columns--boxed .step a {
  color: var(--teal-dark);
}

@media (max-width: 600px) {
  .body-wrap > h2 { padding-left: 14px; }
  .body-wrap > h2::before { width: 3px; }
  .body-wrap > p,
  .body-wrap > .step-columns,
  .body-wrap > h2 + p,
  .body-wrap p.body-lead { padding-left: 14px; }
}

/* Right-floated illustration inside a body-wrap paragraph. Text wraps to
   the left. Section boundaries clear the float so each h2 (and the
   step-columns block) starts below any preceding figure. On narrow
   viewports the float is dropped and the image becomes a centered block
   above its paragraph context. */
.body-wrap img.figure-right {
  float: right;
  max-width: min(45%, 480px);
  height: auto;
  margin: 0 0 var(--space-4) var(--space-6);
  border-radius: 4px;
}
.body-wrap > h2,
.body-wrap > .step-columns {
  clear: right;
}
@media (max-width: 720px) {
  .body-wrap img.figure-right {
    float: none;
    display: block;
    max-width: 100%;
    margin: var(--space-4) auto;
  }
}
/* Size modifiers for .figure-right. Defaults stay at min(45%, 480px).
   Modifier rules are scoped to min-width: 721px so the mobile rule above
   (which drops the float and forces 100% width) wins on small viewports
   despite the modifier's higher specificity. */
@media (min-width: 721px) {
  .body-wrap img.figure-right.figure-sm {
    max-width: min(34%, 360px);
  }
  .body-wrap img.figure-right.figure-lg {
    max-width: min(60%, 640px);
  }
}


/* ============================================================
   Routing question card grid
   ============================================================ */
ul.routing-questions {
  list-style: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  margin: var(--space-6) 0 var(--space-14);
}
ul.routing-questions li {
  background: #fff;
  border: 1px solid var(--grey-200);
  border-radius: var(--radius-md);
  transition: all var(--t-slow);
  box-shadow: var(--shadow-sm);
  margin: 0;
  padding: 0;
}
ul.routing-questions li:hover {
  border-color: var(--teal);
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}
ul.routing-questions a {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  padding: 18px 20px;
  color: var(--ink);
  font-weight: 600;
  font-size: 0.97rem;
  line-height: var(--lh-snug);
  font-family: var(--font-display);
  text-decoration: none;
}
ul.routing-questions a:hover {
  color: var(--teal-dark);
  text-decoration: none;
}
ul.routing-questions a::after {
  content: "→";
  flex-shrink: 0;
  color: var(--teal);
  font-weight: 700;
  transition: transform var(--t-base);
}
ul.routing-questions li:hover a::after {
  transform: translateX(4px);
}

@media (max-width: 980px) {
  ul.routing-questions { grid-template-columns: 1fr; }
}


/* ============================================================
   Product callout aside (EmSA Training)
   ============================================================ */
.product-callout {
  background: linear-gradient(135deg, var(--ink) 0%, var(--charcoal) 100%);
  color: #fff;
  padding: var(--space-10) clamp(28px, 4vw, 48px);
  border-radius: var(--radius-md);
  margin: var(--space-8) 0 var(--space-16);
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-6) var(--space-8);
  align-items: center;
}
/* When markup is unwrapped (h3 + p + a as direct children),
   make text span full width and let the CTA wrap onto its own row. */
.product-callout > h3,
.product-callout > p {
  grid-column: 1 / -1;
}
.product-callout > .cta-link {
  justify-self: start;
}

/* Leading clickable icon — same href as the CTA, slight zoom on hover */
.callout-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 84px;
  height: 84px;
  flex-shrink: 0;
  background: rgba(255, 255, 255, 0.06);
  border-radius: var(--radius-md);
  padding: 12px;
  transition: transform 0.25s ease, background 0.25s ease;
}
.callout-icon:hover {
  transform: scale(1.08);
  background: rgba(255, 255, 255, 0.12);
  text-decoration: none;
}
.callout-icon:focus-visible {
  transform: scale(1.08);
  background: rgba(255, 255, 255, 0.12);
  outline: 2px solid var(--gold);
  outline-offset: 3px;
}
/* tier: soft — single standalone icon on a plain card surface. */
.callout-icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: var(--icon-shadow-soft);
}
.product-callout::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url('../img/bg-texture-lines.jpeg') center / cover;
  opacity: 0.06;
  pointer-events: none;
}
.product-callout::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: var(--gold);
}
.product-callout > * { position: relative; z-index: 1; }

.product-callout h3 {
  font-family: var(--font-display);
  font-weight: 700;
  color: #fff;
  font-size: 1.4rem;
  letter-spacing: var(--ls-tight);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 10px;
}
.product-callout h3::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--gold);
}

.product-callout p {
  color: rgba(255, 255, 255, 0.72);
  font-size: 1rem;
  line-height: var(--lh-normal);
  max-width: 600px;
  margin: 0;
}

.product-callout .cta-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--gold);
  color: var(--charcoal);
  padding: 13px 22px;
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-sm);
  white-space: nowrap;
  transition: all var(--t-base);
}
.product-callout .cta-link:hover {
  background: #fff;
  color: var(--ink);
  text-decoration: none;
}

@media (max-width: 980px) {
  .product-callout {
    grid-template-columns: 1fr;
    gap: var(--space-5);
    padding: var(--space-8) 28px;
  }
  .product-callout .cta-link { justify-self: start; }
  .callout-icon { width: 64px; height: 64px; padding: 10px; justify-self: start; }
}

/* ---------- Two-column row wrapping for back-to-back product callouts ----------
   Place consecutive <aside class="product-callout"> blocks inside a single
   <div class="product-callout-row"> to render them side by side. A trailing
   odd-count item spans the full row so it doesn't sit lonely in a half-column. */
.product-callout-row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-6);
  margin: var(--space-8) 0 var(--space-16);
}
.product-callout-row > .product-callout {
  margin: 0;
  grid-template-columns: 1fr;
  text-align: left;
  align-items: start;
}
.product-callout-row > .product-callout > * { grid-column: 1 / -1; }
.product-callout-row > .product-callout > .callout-icon { justify-self: start; }
.product-callout-row > .product-callout > .cta-link    { justify-self: start; }
/* Trailing odd item: span full width, restore horizontal layout */
.product-callout-row > .product-callout:last-child:nth-child(odd) {
  grid-column: 1 / -1;
  grid-template-columns: auto 1fr auto;
  align-items: center;
}
.product-callout-row > .product-callout:last-child:nth-child(odd) > * {
  grid-column: auto;
}
.product-callout-row > .product-callout:last-child:nth-child(odd) > h3,
.product-callout-row > .product-callout:last-child:nth-child(odd) > p {
  grid-column: 1 / -1;
}

@media (max-width: 760px) {
  .product-callout-row { grid-template-columns: 1fr; }
  .product-callout-row > .product-callout:last-child:nth-child(odd) {
    grid-template-columns: 1fr;
  }
}


/* ============================================================
   FAQ section
   ============================================================ */
.faq {
  background: var(--grey-100);
  margin: 0 calc(-1 * var(--container-x));
  padding: var(--section-y) var(--container-x);
  border-top: 1px solid var(--grey-200);
  border-bottom: 1px solid var(--grey-200);
  position: relative;
}

.faq > h2 {
  max-width: var(--max-w);
  margin: 0 auto var(--space-2);
  padding-left: 0;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-h2);
  line-height: var(--lh-tight);
  letter-spacing: -0.015em;
  color: var(--ink);
}
.faq > h2::before { display: none; }
.faq > h2::after {
  content: "FAQ";
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--ls-eyebrow);
  color: var(--teal);
  background: var(--teal-light);
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
}

.faq h3,
.faq > h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.05rem;
  line-height: var(--lh-snug);
  color: var(--ink);
  margin: 0;
}

.faq > h3 {
  max-width: var(--max-w);
  margin-left: auto;
  margin-right: auto;
  margin-top: var(--space-8);
  padding: var(--space-6) var(--space-6) 0;
  background: #fff;
  border: 1px solid var(--grey-200);
  border-bottom: none;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}
.faq > h3::before {
  content: "Q";
  display: inline-block;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--teal);
  color: #fff;
  font-size: var(--text-xs);
  font-weight: 700;
  text-align: center;
  line-height: 26px;
  margin-right: var(--space-3);
  vertical-align: middle;
  font-family: var(--font-mono);
}

.faq > p {
  max-width: var(--max-w);
  margin: 0 auto;
  background: #fff;
  border: 1px solid var(--grey-200);
  border-top: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  padding: var(--space-2) var(--space-6) var(--space-6) 62px;
  font-size: 0.97rem;
  color: var(--grey-700);
  line-height: var(--lh-relaxed);
}
.faq > p a { font-weight: 600; }

@media (max-width: 600px) {
  .faq > h3 { padding: var(--space-5) 18px 0; }
  .faq > p  { padding: var(--space-2) 18px var(--space-5) 56px; }
}


/* ============================================================
   Footer
   ============================================================ */
footer {
  background: var(--ink);
  color: rgba(255, 255, 255, 0.55);
  padding: var(--space-14) var(--container-x) var(--space-8);
  border-top: 3px solid var(--teal);
  text-align: center;
}

footer .footer-copy {
  font-family: var(--font-display);
  font-weight: 600;
  color: rgba(255, 255, 255, 0.8);
  font-size: 0.95rem;
  margin-bottom: var(--space-6);
}

footer .footer-links {
  list-style: none;
  padding: var(--space-6) 0;
  margin: 0 0 var(--space-7, 28px);
  display: flex;
  gap: var(--space-2) var(--space-7, 28px);
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
footer .footer-links a {
  color: rgba(255, 255, 255, 0.65);
  font-size: 13px;
  font-weight: 600;
  font-family: var(--font-display);
  display: inline-flex;
  align-items: center;
  transition: color var(--t-fast);
}
footer .footer-links a:hover {
  color: var(--gold);
  text-decoration: none;
}
footer .footer-links a svg { display: block; }


footer .footer-legal {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
  display: flex;
  gap: var(--space-2) var(--space-5);
  flex-wrap: wrap;
  justify-content: center;
}
footer .footer-legal a {
  font-size: var(--text-xs);
  color: rgba(255, 255, 255, 0.4);
  letter-spacing: 0.04em;
  font-family: var(--font-display);
  font-weight: 500;
  transition: color var(--t-fast);
}
footer .footer-legal a:hover {
  color: var(--gold);
  text-decoration: none;
}

@media (max-width: 600px) {
  footer .footer-links { gap: var(--space-2) var(--space-4); }
  footer .footer-legal { gap: var(--space-2) var(--space-4); }
}


/* ============================================================
   Language toggle (legal pages only)
   ============================================================ */
.lang-toggle {
  font-size: var(--text-sm);
  margin: var(--space-3) 0 0;
  letter-spacing: 0.02em;
}
.lang-toggle a {
  display: inline-block;
  padding: 4px 12px;
  border: 1px solid var(--grey-300);
  border-radius: var(--radius-md);
  color: var(--charcoal);
  font-family: var(--font-display);
  font-weight: 500;
  text-decoration: none;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.lang-toggle a:hover,
.lang-toggle a:focus {
  background: var(--teal-light);
  color: var(--teal-deep);
  border-color: var(--teal);
}

/* Override for dark hero — the legal pages use the standard .hero (dark
   gradient), so the default charcoal-on-light styling above is invisible
   there. Switch to light text + translucent border. */
.hero .lang-toggle a {
  color: rgba(255, 255, 255, 0.85);
  border-color: rgba(255, 255, 255, 0.35);
}
.hero .lang-toggle a:hover,
.hero .lang-toggle a:focus {
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border-color: var(--gold);
}


/* ============================================================
   Other site components (preserved from previous design)
   ============================================================ */

.breadcrumb {
  font-size: var(--text-sm);
  color: var(--grey-600);
  margin: 0 0 var(--space-4);
  letter-spacing: 0.02em;
}

.breadcrumb a {
  color: var(--grey-700);
}

.breadcrumb a:hover,
.breadcrumb a:focus {
  color: var(--teal-dark);
}

.breadcrumb .sep {
  margin: 0 var(--space-2);
  color: var(--grey-300);
}

.tag {
  display: inline-block;
  font-size: var(--text-sm);
  font-family: var(--font-display);
  font-weight: 700;
  background: #0A4A38;              /* deeper than --teal-deep for stronger white-on-dark contrast */
  color: #ffffff !important;       /* pill text is white in every context; beats ambient link-color rules
                                      (.body-wrap > p a, .scope-notice a, callouts, etc.) that would else win */
  padding: 3px var(--space-2);
  border-radius: var(--radius-sm);
  margin-right: var(--space-2);
  vertical-align: middle;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
/* Trailing pill row: standards/regulation pills always sit together on their own
   line at the end of the paragraph they annotate (never inline in the sentence),
   for consistent use across the site. */
.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.tag-row .tag {
  margin: 0;
}
a.tag {
  text-decoration: none;
  color: #ffffff;
  transition: background-color 0.15s ease, transform 0.15s ease;
}
a.tag:hover {
  background: var(--teal-dark);
  text-decoration: none;
}

/* Dictionary-style glossary list: a grid of small white boxes with the term
   bold (display font) followed by an inline description (body font). Lives
   directly inside .body-wrap on /resources/terms-and-definitions/. */
ul.glossary-list {
  list-style: none;
  padding: 0 0 0 20px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin: var(--space-4) 0 var(--space-10);
  max-width: 100%;
}
ul.glossary-list > li {
  background: #fff;
  border: 1px solid var(--grey-200);
  border-radius: var(--radius-md);
  padding: 14px 18px;
  box-shadow: var(--shadow-sm);
  font-size: 1rem;
  line-height: var(--lh-snug);
  color: var(--charcoal);
  scroll-margin-top: calc(var(--header-h, 64px) + 12px);
  transition: border-color var(--t-base);
}
ul.glossary-list > li:hover { border-color: var(--teal); }
ul.glossary-list > li > strong {
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 700;
}
ul.glossary-list .see-also {
  display: block;
  margin-top: 6px;
  font-size: 0.92em;
  color: var(--grey-700);
}
ul.glossary-list > li :target,
ul.glossary-list > li:target { border-color: var(--gold); box-shadow: 0 0 0 2px rgba(240, 162, 46, 0.25); }
@media (max-width: 720px) {
  ul.glossary-list { grid-template-columns: 1fr; }
}

/* Inline glossary cross-link. Inherits font weight, family, size and colour
   so it never breaks the reading flow; only the 1-px underline distinguishes
   it. Hover darkens the underline without touching the text colour. */
.glossary-term {
  font: inherit;
  color: inherit;
  border-bottom: 1px solid rgba(45, 45, 45, 0.25);
  padding-bottom: 0;
  text-decoration: none;
  transition: border-color 0.15s ease;
}
.glossary-term:hover {
  color: inherit;
  border-bottom-color: var(--teal-dark);
  text-decoration: none;
}

.sl-matrix {
  font-size: var(--text-sm);
  table-layout: fixed;
  width: 100%;
}

.sl-matrix th,
.sl-matrix td {
  overflow-wrap: break-word;
}

.sl-matrix th {
  font-size: var(--text-xs);
  text-align: center;
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  line-height: var(--lh-snug);
  vertical-align: bottom;
}

.sl-matrix td {
  text-align: center;
  font-family: var(--font-mono);
}

.sl-matrix td:first-child {
  text-align: left;
  font-family: var(--font-body);
  font-weight: 600;
  background: var(--grey-50);
  color: var(--ink);
}

.sl-matrix td.cell-fit {
  background: var(--teal-light);
  color: var(--teal-deep);
  font-weight: 700;
}

.sl-matrix td.cell-na {
  background: var(--gold-soft);
}

.sl-matrix td.cell-partial {
  background: var(--teal-light);
}

.sl-matrix tr.matrix-subheader th {
  padding-top: var(--space-6);
  border-top: 2px solid var(--grey-200);
}

.sl-matrix caption {
  caption-side: bottom;
  text-align: left;
  font-size: var(--text-sm);
  color: var(--grey-600);
  padding-top: var(--space-2);
  line-height: var(--lh-snug);
}

.matrix-legend {
  font-size: var(--text-sm);
  color: var(--grey-600);
  margin-top: 0;
}

.cra-matrix {
  width: 100%;
  font-size: var(--text-body);
  table-layout: fixed;
  border-collapse: collapse;
  margin: var(--space-4) 0 var(--space-6);
}

.cra-matrix th,
.cra-matrix td {
  overflow-wrap: break-word;
  padding: var(--space-3) var(--space-4);
  vertical-align: top;
  text-align: left;
  line-height: var(--lh-snug);
  border-bottom: 1px solid var(--grey-200);
}

.cra-matrix th {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--ink);
  background: var(--grey-50);
  border-bottom: 2px solid var(--grey-300);
  vertical-align: bottom;
}

.cra-matrix th:first-child,
.cra-matrix td:first-child {
  width: 12ch;
  font-family: var(--font-mono);
  color: var(--teal-deep);
  white-space: nowrap;
}

.cra-matrix td[data-tip] {
  position: relative;
  cursor: help;
  text-decoration: underline dotted var(--grey-400);
  text-underline-offset: 3px;
}

.cra-matrix td[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  z-index: 20;
  left: 0;
  top: calc(100% + 6px);
  width: max-content;
  max-width: 360px;
  padding: var(--space-3) var(--space-4);
  background: var(--ink);
  color: #fff;
  border-radius: 4px;
  font-size: var(--text-sm);
  font-family: var(--font-body);
  font-weight: 400;
  line-height: var(--lh-snug);
  text-transform: none;
  text-decoration: none;
  white-space: normal;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

.cra-matrix td[data-tip]:hover::after,
.cra-matrix td[data-tip]:focus::after {
  opacity: 1;
  visibility: visible;
}

/* Hero visual for the CRA Requirements page: a file/document icon that
   gets "filled in" with dotted bullet rows one by one, then a checkmark
   badge fades in at the bottom-right. */
.hero-visual--cra-checklist {
  position: relative;
  width: 240px;
  height: 280px;
  margin: 0 auto;
}

.hero-visual--cra-checklist .cra-file {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* Small uppercase label above the first checklist row, used to distinguish
   the CRA-requirements visual from the IEC-62443-SL2 visual since both
   pages reuse this same checklist treatment. Label text is provided per
   page in the HTML ("CRA" vs "62443"). Positioned just above cra-item-1
   (top: 34%) at the same left edge (left: 24%). Fades in slightly before
   the first bullet so it reads as a heading for the rows that follow. */
.hero-visual--cra-checklist .cra-label {
  position: absolute;
  top: 20%;
  left: 24%;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.5rem;
  letter-spacing: 0.08em;
  color: var(--gold);
  text-transform: uppercase;
  line-height: 1;
  opacity: 0;
  animation: cra-item-pop 0.4s ease-out forwards;
  animation-delay: 0.3s;
}

.hero-visual--cra-checklist .cra-item {
  position: absolute;
  left: 24%;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 12px;
  opacity: 0;
  transform: translateX(-8px);
  animation: cra-item-pop 0.4s ease-out forwards;
}

.hero-visual--cra-checklist .cra-item-1 { top: 34%; width: 27%; animation-delay: 0.6s; }
.hero-visual--cra-checklist .cra-item-2 { top: 44%; width: 46%; animation-delay: 1.1s; }
.hero-visual--cra-checklist .cra-item-3 { top: 54%; width: 52%; animation-delay: 1.6s; }
.hero-visual--cra-checklist .cra-item-4 { top: 64%; width: 42%; animation-delay: 2.1s; }
.hero-visual--cra-checklist .cra-item-5 { top: 74%; width: 50%; animation-delay: 2.6s; }

.hero-visual--cra-checklist .cra-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--gold);
  flex-shrink: 0;
}

.hero-visual--cra-checklist .cra-line {
  flex: 1;
  height: 3px;
  background: var(--gold);
  border-radius: 2px;
}

.hero-visual--cra-checklist .cra-check {
  position: absolute;
  bottom: -25px;
  right: 20px;
  width: 84px;
  height: 84px;
  opacity: 0;
  transform: scale(0.5);
  animation: cra-check-pop 1.1s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  animation-delay: 3.1s;
}

@keyframes cra-item-pop {
  from {
    opacity: 0;
    transform: translateX(-8px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes cra-check-pop {
  from {
    opacity: 0;
    transform: scale(0.5);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-visual--cra-checklist .cra-label,
  .hero-visual--cra-checklist .cra-item,
  .hero-visual--cra-checklist .cra-check {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* Hero visual for the Zoning / Segmentation page: the SPsec secure-bridge
   illustration sits behind a small SPsec icon that slides back and forth
   along the green line in the top-left corner. The icon's animation
   container matches the bridge PNG's natural aspect ratio (578x584) so
   the image fills it edge-to-edge with no object-fit padding. Icon size
   and keyframe waypoints are expressed as percentages of the container,
   so the whole animation scales with the image at any viewport width
   and stays locked to the visible lines in the PNG. The
   .hero-visual.hero-visual--zoning-bridge chained selector raises
   specificity above the base .hero-visual rule AND the mobile
   media-query override of .hero-visual. */
.hero-visual.hero-visual--zoning-bridge {
  position: relative;
  width: 100%;
  max-width: 436px;
  aspect-ratio: 578 / 584;
  margin: 0 auto;
}

.hero-visual.hero-visual--zoning-bridge .zb-bridge {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.hero-visual--zoning-bridge .zb-icon {
  position: absolute;
  top: 42.4%;
  left: 13.5%;
  width: 14.7%;
  height: auto;
  filter: var(--icon-shadow-strong);
  animation: zb-traverse 5s ease-in-out infinite alternate;
}

@keyframes zb-traverse {
  0%   { top: 42.4%; left: 13.5%; }
  49%  { top: -1.3%; left: 13.5%; }
  76%  { top: -1.3%; left: 38.2%; }
  100% { top: 20.5%; left: 38.2%; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-visual--zoning-bridge .zb-icon { animation: none; }
}

.cra-matrix th:nth-child(2),
.cra-matrix td:nth-child(2) {
  width: 38%;
}

.cra-matrix caption {
  caption-side: bottom;
  text-align: left;
  font-size: var(--text-sm);
  color: var(--grey-600);
  padding-top: var(--space-2);
  line-height: var(--lh-snug);
}

.glossary h3 {
  border-bottom: 1px solid var(--grey-200);
  padding-bottom: var(--space-2);
  color: var(--teal-deep);
}

.resource-card {
  border: 1px solid var(--grey-200);
  border-radius: var(--radius-md);
  padding: var(--space-5) var(--space-8);
  margin-bottom: var(--space-4);
  background: #ffffff;
  transition: box-shadow var(--t-base), border-color var(--t-base);
}

.resource-card:hover {
  box-shadow: var(--shadow-sm);
  border-color: var(--grey-300);
}

.resource-card h3 {
  margin-top: 0;
  color: var(--teal-deep);
}

.resource-card .resource-host {
  margin-top: var(--space-1);
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  color: var(--grey-700);
}


/* ============================================================
   Defense-in-depth layered diagram
   ============================================================ */
.did-diagram {
  margin: var(--space-8) 0 var(--space-12);
  max-width: 720px;
}

.did-diagram svg {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.did-diagram figcaption {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--grey-600);
  line-height: var(--lh-snug);
  font-style: italic;
}


/* ---------- Defense-in-Depth raster graphic: clickable shell hotspots ---------- */

.did-graphic {
  text-align: center;        /* centre the reduced graphic in its band */
}
.did-graphic-map {
  position: relative;
  display: inline-block;     /* shrink-wraps to the image's rendered size */
  line-height: 0;            /* removes the inline-image baseline gap */
  max-width: 867px;          /* ~20% smaller than the full body-wrap width; still caps to container (100%) below this */
  width: 100%;
}
.did-graphic-map img {
  display: block;
  width: 100%;
  height: auto;
}
.did-hotspot {
  position: absolute;
  background: transparent;
  transition: background 200ms ease;
  cursor: pointer;
  text-decoration: none;     /* anchor has no visible text */
}
.did-hotspot:hover,
.did-hotspot:focus-visible {
  /* Soft radial spotlight: strongest at the shell's centre, fades to
     fully transparent before reaching the rect edge — no visible
     rectangle outline, no edge contrast against neighbouring shells. */
  background: radial-gradient(ellipse at center,
    rgba(240, 162, 46, 0.55) 0%,
    rgba(240, 162, 46, 0.28) 40%,
    rgba(240, 162, 46, 0.00) 80%);
  outline: none;
}
.did-hotspot-sofa { top: 19%;   left: 43.3%; width: 42.4%; height:  8%;   }
.did-hotspot-aem  { top: 30%;   left: 43.3%; width: 42.4%; height: 26%;   }
.did-hotspot-lid  { top: 61%;   left: 45.2%; width: 19.2%; height: 11%;   }
.did-hotspot-fs   { top: 61%;   left: 63.6%; width: 19.2%; height: 11%;   }
.did-hotspot-blm  { top: 73.2%; left: 43.3%; width: 42.4%; height: 16.2%; }
.did-hotspot-pap  { top: 12.2%; left: 36.5%; width:  6.5%; height: 73.6%; }
.did-hotspot-grb  { top: 12.2%; left: 86%;   width:  5.5%; height: 73.6%; }


/* ---------- Landing hero side visual: layered shells centred, icons orbit ---------- */

.hero--with-visual .hero-inner {
  display: flex;
  align-items: center;
  gap: clamp(24px, 4vw, 56px);
}
.hero--with-visual .hero-text {
  flex: 1 1 auto;
  min-width: 0;
}

.hero-visual {
  position: relative;
  flex: 0 0 auto;
  width: clamp(220px, 28vw, 320px);
  aspect-ratio: 1;
}
.hv-center {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 96%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.04);
  display: block;
  object-fit: contain;
}
.hv-center > img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 50%;
  display: block;
  /* tier: soft — central asset icon; orbiting icons around it carry the strong halo. */
  filter: var(--icon-shadow-soft);
}
a.hv-center {
  text-decoration: none;
  cursor: pointer;
}
a.hv-center:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 4px;
}
/* Plain-icon variant (used on /risk-assessment/): smaller, no circle backdrop.
   aspect-ratio: 1 inherits from .hv-center so the box stays square regardless
   of whether the parent's flex layout enforces a 1:1 outer box. */
.hv-center.hv-center--plain {
  width: 67%;
  background: transparent;
  border-radius: 0;
}
/* Per-icon orbit ring: each icon has its own orbit container with a distinct
   rotation pivot (transform-origin offset off container centre by 50% of that
   icon's width in X and Y) and a distinct period. The three orbits trace
   different circles around different centres. */
.hv-orbit {
  position: absolute;
  inset: 0;
  pointer-events: none;       /* let clicks fall through to the icons */
}
.hv-orbit-top {
  transform-origin: calc(50% + 21px) calc(50% - 21px);
  animation: hv-orbit-rot 40s linear infinite;
}
.hv-orbit-bl {
  transform-origin: calc(50% - 18px) calc(50% + 18px);
  animation: hv-orbit-rot 50s linear infinite;
}
.hv-orbit-br {
  transform-origin: calc(50% + 19.5px) calc(50% + 19.5px);
  animation: hv-orbit-rot 45s linear infinite;
}
@keyframes hv-orbit-rot { to { transform: rotate(360deg); } }

.hv-icon {
  position: absolute;
  width: 56px;
  height: 56px;
  object-fit: contain;
  /* tier: strong — orbiting icon, crosses container backgrounds. */
  filter: var(--icon-shadow-strong);
  animation: hv-counter 40s linear infinite;
  pointer-events: auto;
  -webkit-user-drag: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
/* Each orbiting icon has its own size in the +25% to +50% range so the three
   read as a varied set rather than a uniform trio. Counter-rotation duration
   matches each icon's parent orbit so it stays upright. */
.hv-icon-top { width: 84px; height: 84px; top: -8%;    left: calc(50% - 42px); }
.hv-icon-bl  { width: 72px; height: 72px; bottom: -2%; left: -2%;  animation-duration: 50s; }
.hv-icon-br  { width: 78px; height: 78px; bottom: -2%; right: -2%; animation-duration: 45s; }

/* Barely-noticeable hover affordance on the three orbiting icons — hints that
   they react to the cursor (they are the click targets that trigger the reveal)
   without giving the trick away. Pointer cursor + small brightness lift only;
   no transform so the orbit/counter-rotation math is left untouched. */
.hv-icon-top,
.hv-icon-bl,
.hv-icon-br {
  cursor: pointer;
  transition: filter 200ms ease-out;
}
.hv-icon-top:hover,
.hv-icon-bl:hover,
.hv-icon-br:hover {
  filter: var(--icon-shadow-strong) brightness(1.08);
}

/* Hero click-reveal sequence. */
body.reveal-active .hv-center img,
body.reveal-active .hv-trail {
  animation: reveal-fade 1.5s ease-out forwards;
}
@keyframes reveal-fade {
  to { opacity: 0; }
}

body.reveal-active .hv-orbit-top {
  animation: reveal-spiral-top 3s cubic-bezier(.45, 0, .7, 1) forwards;
}
body.reveal-active .hv-orbit-bl {
  animation: reveal-spiral-bl 3.2s cubic-bezier(.45, 0, .7, 1) forwards;
}
body.reveal-active .hv-orbit-br {
  animation: reveal-spiral-br 2.8s cubic-bezier(.45, 0, .7, 1) forwards;
}
@keyframes reveal-spiral-top {
  0%   { transform-origin: calc(50% + 21px) calc(50% - 21px);
         transform: rotate(var(--reveal-angle, 0deg)) scale(1); opacity: 1; }
  15%  { transform-origin: 50% 50%;
         transform: rotate(var(--reveal-angle, 0deg)) scale(1); opacity: 1; }
  80%  { transform-origin: 50% 50%;
         transform: rotate(calc(var(--reveal-angle, 0deg) + 1440deg)) scale(0.12); opacity: 1; }
  100% { transform-origin: 50% 50%;
         transform: rotate(calc(var(--reveal-angle, 0deg) + 2160deg)) scale(0); opacity: 0; }
}
@keyframes reveal-spiral-bl {
  0%   { transform-origin: calc(50% - 18px) calc(50% + 18px);
         transform: rotate(var(--reveal-angle, 0deg)) scale(1); opacity: 1; }
  15%  { transform-origin: 50% 50%;
         transform: rotate(var(--reveal-angle, 0deg)) scale(1); opacity: 1; }
  80%  { transform-origin: 50% 50%;
         transform: rotate(calc(var(--reveal-angle, 0deg) + 1440deg)) scale(0.12); opacity: 1; }
  100% { transform-origin: 50% 50%;
         transform: rotate(calc(var(--reveal-angle, 0deg) + 2160deg)) scale(0); opacity: 0; }
}
@keyframes reveal-spiral-br {
  0%   { transform-origin: calc(50% + 19.5px) calc(50% + 19.5px);
         transform: rotate(var(--reveal-angle, 0deg)) scale(1); opacity: 1; }
  15%  { transform-origin: 50% 50%;
         transform: rotate(var(--reveal-angle, 0deg)) scale(1); opacity: 1; }
  80%  { transform-origin: 50% 50%;
         transform: rotate(calc(var(--reveal-angle, 0deg) + 1440deg)) scale(0.12); opacity: 1; }
  100% { transform-origin: 50% 50%;
         transform: rotate(calc(var(--reveal-angle, 0deg) + 2160deg)) scale(0); opacity: 0; }
}

.reveal-figure {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 32%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%) scale(0) rotate(0deg);
  transform-origin: center center;
  opacity: 0;
  pointer-events: none;
  filter: drop-shadow(0 8px 18px rgba(0, 0, 0, 0.4));
  z-index: 2;
}
body.reveal-active .reveal-figure {
  animation: reveal-figure-grow 4.8s 2.2s cubic-bezier(.12, .65, .2, 1) forwards;
}
@keyframes reveal-figure-grow {
  0%   { opacity: 0; transform: translate(-50%, -50%) rotate(0deg)    scale(0.05); }
  8%   { opacity: 1; transform: translate(-50%, -50%) rotate(360deg)  scale(0.18); }
  100% { opacity: 1; transform: translate(-50%, -50%) rotate(1440deg) scale(3.12); }
}

.reveal-credits {
  position: absolute;
  top: calc(100% + 1.5em);
  left: 5%;
  right: 5%;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(0.95rem, 1.7vw, 1.3rem);
  line-height: 1.35;
  letter-spacing: 0.02em;
  color: var(--gold);
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
  opacity: 0;
  clip-path: inset(0 100% 0 0);
  pointer-events: none;
  z-index: 3;
}
body.reveal-active .reveal-credits {
  animation: reveal-credits-show 1.5s 6s ease-out forwards;
}
@keyframes reveal-credits-show {
  0%   { opacity: 1; clip-path: inset(0 100% 0 0); }
  100% { opacity: 1; clip-path: inset(0 0 0 0); }
}

@media (prefers-reduced-motion: reduce) {
  body.reveal-active .hv-center img,
  body.reveal-active .hv-trail,
  body.reveal-active .hv-orbit-top,
  body.reveal-active .hv-orbit-bl,
  body.reveal-active .hv-orbit-br {
    animation: none;
    opacity: 0;
  }
  body.reveal-active .reveal-figure {
    animation: none;
    opacity: 1;
    transform: translate(-50%, -50%) scale(3.12);
  }
  body.reveal-active .reveal-credits {
    animation: none;
    opacity: 1;
    clip-path: inset(0 0 0 0);
  }
}

/* Outro: fade the figure and credits out while the original hero elements
   fade back in. Replaces the previous full-page reload. Rules are placed
   after the reveal-active block so they win the cascade while both classes
   are simultaneously applied. */
body.reveal-outro .reveal-figure {
  animation: reveal-figure-hide 0.9s ease-in forwards;
}
@keyframes reveal-figure-hide {
  0%   { opacity: 1; transform: translate(-50%, -50%) rotate(1440deg) scale(3.12); }
  100% { opacity: 0; transform: translate(-50%, -50%) rotate(1440deg) scale(2.6); }
}
body.reveal-outro .reveal-credits {
  animation: reveal-credits-hide 0.7s ease-in forwards;
}
@keyframes reveal-credits-hide {
  to { opacity: 0; }
}
body.reveal-outro .hv-center img,
body.reveal-outro .hv-trail {
  animation: reveal-restore-opacity 0.9s 0.2s ease-out forwards;
}
@keyframes reveal-restore-opacity {
  to { opacity: 1; }
}
body.reveal-outro .hv-orbit-top,
body.reveal-outro .hv-orbit-bl,
body.reveal-outro .hv-orbit-br {
  animation: reveal-orbit-restore 0.9s 0.2s ease-out forwards;
}
@keyframes reveal-orbit-restore {
  0%   { transform: rotate(0deg) scale(0); opacity: 0; }
  100% { transform: rotate(0deg) scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  body.reveal-outro .reveal-figure,
  body.reveal-outro .reveal-credits {
    animation: none;
    opacity: 0;
  }
  body.reveal-outro .hv-center img,
  body.reveal-outro .hv-trail,
  body.reveal-outro .hv-orbit-top,
  body.reveal-outro .hv-orbit-bl,
  body.reveal-outro .hv-orbit-br {
    animation: none;
    opacity: 1;
  }
}

@keyframes hv-counter { to { transform: rotate(-360deg); } }

/* Per-icon orbit trail: conic-gradient arc whose centre coincides with the
   parent orbit's transform-origin. The bright end (gradient stop at 90deg) is
   pinned to the icon's compass angle relative to that orbit centre; the trail
   fades 90deg counter-clockwise behind it. Mask is a px-based annulus at the
   icon's orbit radius so the trail rides exactly on the icon's path. */
.hv-trail {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.hv-trail-top {
  background: conic-gradient(from 259deg at calc(50% + 21px) calc(50% - 21px),
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% + 21px) calc(50% - 21px),
                transparent 46%, #000 47%, #000 50%, transparent 51%);
          mask: radial-gradient(circle at calc(50% + 21px) calc(50% - 21px),
                transparent 46%, #000 47%, #000 50%, transparent 51%);
}
/* BL and BR trails extend past .hv-orbit bounds (inset: -25px) so the annulus
   does not clip where it grazes the container's bottom and right edges. The
   top trail stays at inset: 0 because its orbit stays inside the container. */
.hv-trail-bl {
  inset: -25px;
  background: conic-gradient(from 135deg at calc(50% - 18px) calc(50% + 18px),
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% - 18px) calc(50% + 18px),
                transparent 154px, #000 156px, #000 162px, transparent 164px);
          mask: radial-gradient(circle at calc(50% - 18px) calc(50% + 18px),
                transparent 154px, #000 156px, #000 162px, transparent 164px);
}
.hv-trail-br {
  inset: -25px;
  background: conic-gradient(from 45deg at calc(50% + 19.5px) calc(50% + 19.5px),
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% + 19.5px) calc(50% + 19.5px),
                transparent 148px, #000 150px, #000 156px, transparent 158px);
          mask: radial-gradient(circle at calc(50% + 19.5px) calc(50% + 19.5px),
                transparent 148px, #000 150px, #000 156px, transparent 158px);
}

@media (prefers-reduced-motion: reduce) {
  .hv-orbit,
  .hv-icon { animation: none; }
}

@media (max-width: 860px) {
  .hero--with-visual .hero-inner {
    flex-direction: column;
    align-items: flex-start;
  }
  .hero-visual {
    align-self: center;
    width: clamp(200px, 60vw, 260px);
  }
}

/* ---------- Hero side visual: per-icon orbits + arc trails (used on /risk-assessment/) ----------
   Three question-mark icons orbit the central risk-o-meter, each on its own ring
   with a distinct rotation pivot (transform-origin offset off container centre)
   and a distinct period (~43.5 s / 46 s / 41.7 s) so they drift relative to
   each other. Each ring carries a gold conic-gradient arc trail whose centre
   coincides with the ring's transform-origin and whose mask annulus rides at
   the icon's orbit radius (calibrated for hero-visual W = 320 px).
   Follows the recipe in .claude/ORBIT_TRAILS.md. */
.risk-orbit {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.risk-orbit-top {
  transform-origin: calc(50% + 28px) calc(50% - 28px);
  animation: hv-orbit-rot 43.5s linear infinite;
}
.risk-orbit-br {
  transform-origin: calc(50% + 26px) calc(50% + 26px);
  animation: hv-orbit-rot 41.7s linear infinite;
}
.risk-icon {
  position: absolute;
  width: 90px;
  height: 90px;
  object-fit: contain;
  /* tier: strong — orbiting icon, can pass over container borders. */
  filter: var(--icon-shadow-strong);
}
/* Per-icon positions place each question mark in a distinct quadrant of the
   container. Counter-rotation matches the parent ring's per-icon period so the
   icon stays upright. */
.risk-icon-top {
  top: -8%;
  left: calc(50% - 45px);
  animation: hv-counter 43.5s linear infinite;
}
.risk-icon-br {
  bottom: -2%;
  right: -2%;
  animation: hv-counter 41.7s linear infinite;
}
/* Per-icon trails: conic-gradient arc centred on the ring's transform-origin,
   masked to a thin annulus at the icon's orbit radius. The bright stop (90deg
   into the gradient sweep) lands at the icon's compass angle; the trail fades
   counter-clockwise (compass-decreasing) for 90deg behind it. All three trails
   use inset: -25px so the annulus does not clip where it brushes the container
   edges at the doubled icon size (gotcha #2 in ORBIT_TRAILS.md). Mask radii are
   in px (gotcha #5), calibrated for W = 320 px. */
.risk-trail {
  position: absolute;
  inset: -25px;
  pointer-events: none;
}
/* Top: pivot (188, 132); icon centre (160, 19.4); r ≈ 116 px; compass θ ≈ 346°;
   conic from = θ − 90 ≈ 256°. (Icon shrunk to 90 px → centre moved up → radius grew.) */
.risk-trail-top {
  background: conic-gradient(from 256deg at calc(50% + 28px) calc(50% - 28px),
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% + 28px) calc(50% - 28px),
                transparent 111px, #000 113px, #000 119px, transparent 121px);
          mask: radial-gradient(circle at calc(50% + 28px) calc(50% - 28px),
                transparent 111px, #000 113px, #000 119px, transparent 121px);
}
/* BR: pivot (186, 186); icon centre (281.4, 281.4); r ≈ 135 px; compass θ = 135°;
   conic from = 45°. (Icon shrunk to 90 px → centre moved toward corner → radius grew.) */
.risk-trail-br {
  background: conic-gradient(from 45deg at calc(50% + 26px) calc(50% + 26px),
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% + 26px) calc(50% + 26px),
                transparent 130px, #000 132px, #000 138px, transparent 140px);
          mask: radial-gradient(circle at calc(50% + 26px) calc(50% + 26px),
                transparent 130px, #000 132px, #000 138px, transparent 140px);
}
@media (prefers-reduced-motion: reduce) {
  .risk-orbit,
  .risk-icon { animation: none; }
}

/* ---------- Hero side visual: zoom-in image ---------- */

.hero-visual--zoom-in > img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transform-origin: 50% 50%;
  /* tier: strong — image scales in from 0.82. */
  filter: var(--icon-shadow-strong);
  animation: zoom-in 1.2s cubic-bezier(.2, .8, .3, 1) forwards;
}
@keyframes zoom-in {
  from { transform: scale(0.82); opacity: 0; }
  to   { transform: scale(1);    opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-visual--zoom-in > img { animation: none; opacity: 1; transform: none; }
}

/* ---------- Hero side visual: threat cloud + orbiting icons (used on /threats/) ----------
   Background image zooms in once inside an inner wrapper that owns overflow:
   hidden so the zoom stays clipped. Orbiting icons live as siblings of the
   wrapper, directly under .hero-visual--threat-cloud (overflow: visible) so
   they may extend past the hero-visual bounds without being clipped.
   Per-icon orbits follow the recipe in .claude/ORBIT_TRAILS.md:
   key icon orbits clockwise around an upper-left pivot (compass 315°);
   net icon orbits counter-clockwise around a lower-right pivot (compass 135°).
   Trails are gold conic-gradient arcs masked to a thin annulus at each
   orbit's radius. */
.hero-visual--threat-cloud { overflow: visible; }
.threat-bg-wrap {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
.threat-bg {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 96%;
  height: 96%;
  object-fit: contain;
  transform: translate(-50%, -50%) scale(0.82);
  opacity: 0;
  /* tier: strong — background scales in then orbiting icons cross it. */
  filter: var(--icon-shadow-strong);
  animation: threat-bg-zoom 1.2s cubic-bezier(.2, .8, .3, 1) forwards;
}
@keyframes threat-bg-zoom {
  from { transform: translate(-50%, -50%) scale(0.82); opacity: 0; }
  to   { transform: translate(-50%, -50%) scale(1);    opacity: 1; }
}

/* Key icon: pivot upper-left of container centre (50% - 18px in each axis),
   icon placed top-left so it sits at compass 315° from the pivot. Orbit radius
   ~132 px at W=320. Trail fits inside the container so inset: 0 is enough. */
.hv-orbit-threats-key {
  transform-origin: calc(50% - 18px) calc(50% - 18px);
  animation: hv-orbit-rot 28s linear infinite;
}
.hv-icon-threats-key {
  position: absolute;
  width: 72px;
  height: 72px;
  top: 4%;
  left: 4%;
  object-fit: contain;
  /* tier: strong — orbiting icon. */
  filter: var(--icon-shadow-strong);
  animation: hv-counter 28s linear infinite;
}
.hv-trail-threats-key {
  background: conic-gradient(from 225deg at calc(50% - 18px) calc(50% - 18px),
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% - 18px) calc(50% - 18px),
                transparent 127px, #000 129px, #000 134px, transparent 136px);
          mask: radial-gradient(circle at calc(50% - 18px) calc(50% - 18px),
                transparent 127px, #000 129px, #000 134px, transparent 136px);
}

/* Net icon: pivot lower-right of container centre (50% + 18px in each axis),
   icon placed bottom-right so it sits at compass 135° from the pivot. Orbits
   counter-clockwise via hv-orbit-rot-ccw, so the trail extends clockwise of
   the icon: bright stop at sweep 0deg (= compass 135°, via from: 135deg),
   fading to transparent at sweep 90deg. Counter-rotation uses an explicit
   reverse keyframe so the icon stays upright while parent spins backwards. */
.hv-orbit-threats-net {
  transform-origin: calc(50% + 18px) calc(50% + 18px);
  animation: hv-orbit-rot-ccw 36s linear infinite;
}
@keyframes hv-orbit-rot-ccw { to { transform: rotate(-360deg); } }
.hv-icon-threats-net {
  position: absolute;
  width: 72px;
  height: 72px;
  bottom: 4%;
  right: 4%;
  object-fit: contain;
  /* tier: strong — orbiting icon. */
  filter: var(--icon-shadow-strong);
  animation: hv-counter-cw 36s linear infinite;
}
@keyframes hv-counter-cw { to { transform: rotate(360deg); } }
.hv-trail-threats-net {
  background: conic-gradient(from 135deg at calc(50% + 18px) calc(50% + 18px),
              rgba(240, 162, 46, 0.55) 0deg,
              rgba(240, 162, 46, 0)    90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% + 18px) calc(50% + 18px),
                transparent 127px, #000 129px, #000 134px, transparent 136px);
          mask: radial-gradient(circle at calc(50% + 18px) calc(50% + 18px),
                transparent 127px, #000 129px, #000 134px, transparent 136px);
}

@media (prefers-reduced-motion: reduce) {
  .threat-bg          { animation: none; transform: translate(-50%, -50%); opacity: 1; }
  .hv-orbit-threats-key,
  .hv-orbit-threats-net,
  .hv-icon-threats-key,
  .hv-icon-threats-net { animation: none; }
}

/* ---------- Regulations + Standards two-column layout (used on /why-now/) ---------- */

.regs-standards-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(24px, 4vw, 56px);
  margin-bottom: var(--space-6);
}
@media (min-width: 860px) {
  .regs-standards-grid {
    grid-template-columns: 1fr 1fr;
  }
}
.regs-standards-grid > div > h2:first-child {
  position: relative;
  margin-top: 0;
  margin-bottom: 14px;
  padding-left: 20px;
}
.regs-standards-grid > div > h2:first-child::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  bottom: 8px;
  width: 4px;
  background: var(--teal);
  border-radius: 2px;
}
@media (max-width: 600px) {
  .regs-standards-grid > div > h2:first-child { padding-left: 14px; }
  .regs-standards-grid > div > h2:first-child::before { width: 3px; }
}

/* ---------- Hero side visual: falling-names pile (used on /why-now/) ---------- */

.hero-visual--pile {
  overflow: hidden;
}
.pile-name {
  position: absolute;
  font-family: var(--font-display);
  font-weight: 700;
  white-space: nowrap;
  letter-spacing: 0.02em;
  opacity: 0;
  transform-origin: 50% 50%;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
  animation: pile-drop 1s cubic-bezier(.45, 1.5, .45, 1) forwards;
}
@keyframes pile-drop {
  0%   { opacity: 0; transform: translateY(-180%) rotate(0deg); }
  100% { opacity: 1; transform: translateY(0)     rotate(var(--rot, 0deg)); }
}
@media (prefers-reduced-motion: reduce) {
  .pile-name { animation: none; opacity: 1; transform: rotate(var(--rot, 0deg)); }
}

.pn-1 { bottom:  6%; left:  5%; font-size: 28px; color: var(--gold);                --rot:  -8deg; animation-delay: 0.05s; }
.pn-2 { bottom: 14%; left: 31%; font-size: 22px; color: rgba(255, 255, 255, 0.92);  --rot:  10deg; animation-delay: 0.20s; }
.pn-3 { bottom: 26%; left: 13%; font-size: 22px; color: var(--teal-light);          --rot:  -3deg; animation-delay: 0.35s; }
.pn-4 { bottom: calc(14% - 9px); left: 54%; font-size: 18px; color: rgba(255, 255, 255, 0.7);   --rot:  14deg; animation-delay: 0.50s; }
.pn-5 { bottom: 40%; left: 41%; font-size: 18px; color: var(--gold);                --rot: -10deg; animation-delay: 0.65s; }
.pn-6 { bottom: 50%; left:  0%; font-size: 16px; color: var(--teal-light);          --rot:   5deg; animation-delay: 0.80s; }
.pn-7 { bottom: 60%; left: 28%; font-size: 16px; color: rgba(255, 255, 255, 0.78);  --rot:  -6deg; animation-delay: 0.95s; }

/* ---------- Hero side visual: diagonal chain reveal (used on /threats/software-updates/ and /solutions/secure-bootloader/) ----------
   Bottom-left → middle → top-right. Container itself scales subtly during reveal,
   so the bounding "box" of the animation visibly changes size. */
.hero-visual--diagonal-chain {
  animation: diag-box-grow 2.4s ease-in-out forwards;
}
@keyframes diag-box-grow {
  0%   { transform: scale(0.78); }
  55%  { transform: scale(1.06); }
  100% { transform: scale(1); }
}
.diag-item {
  position: absolute;
  width: 24%;
  height: auto;
  object-fit: contain;
  opacity: 0;
  transform: scale(0.5);
  animation: diag-arrive 0.55s cubic-bezier(.4, 1.4, .5, 1) forwards;
  /* tier: strong — icon scales in from 0.5 to full size. */
  filter: var(--icon-shadow-strong);
}
.diag-item-1 { bottom:  6%; left:   6%; animation-delay: 0.20s; }
.diag-item-2 { bottom: 40%; left:  38%; animation-delay: 0.80s; }
.diag-item-3 { top:     6%; right:  6%; animation-delay: 1.40s; }
@keyframes diag-arrive {
  to { opacity: 1; transform: scale(1); }
}

/* Connector lines between the three diagonal icons. Lines draw in just before
   the next icon arrives, so the eye reads chip → chip_key → package_box. */
.diag-link {
  position: absolute;
  width: 0;
  height: 2px;
  background: var(--teal);
  border-radius: 1px;
  opacity: 0;
  transform-origin: 0% 50%;
  filter: drop-shadow(0 0 3px rgba(13, 148, 136, 0.5));
  animation: diag-link-grow 0.55s ease-out forwards;
}
@keyframes diag-link-grow {
  to { width: 32%; opacity: 1; }
}
.diag-link-1 {
  top: 76%;
  left: 22%;
  transform: rotate(-46deg);
  animation-delay: 0.55s;   /* between item-1 (lands ~0.75s) and item-2 (lands ~1.35s) */
}
.diag-link-2 {
  top: 44%;
  left: 54%;
  transform: rotate(-43deg);
  animation-delay: 1.15s;   /* between item-2 and item-3 (lands ~1.95s) */
}
@media (prefers-reduced-motion: reduce) {
  .hero-visual--diagonal-chain { animation: none; transform: scale(1); }
  .diag-item { animation: none; opacity: 1; transform: scale(1); }
  .diag-link { animation: none; opacity: 1; width: 32%; }
}

/* ---------- Hero side visual: top-down mitigation flow (used on /solutions/mitigation-flowchart/) ----------
   Question mark → attacker → alert, drawn top to bottom with vertical connectors,
   then the shield-with-key fades in last, settling over the attacker to read as
   "protection placed over the threat". Taller-than-wide box holds the 3-icon column. */
.hero-visual.hero-visual--mitigation-flow {
  width: clamp(200px, 26vw, 300px);
  aspect-ratio: 3 / 4;
  animation: diag-box-grow 2.4s ease-in-out forwards;
}
.mf-item {
  position: absolute;
  left: 50%;
  z-index: 2;                 /* icons sit above the connector lines */
  /* The three marks are a matched set authored at equal pixel height but
     different widths, so they must be sized by HEIGHT, not width, to read
     as the same size (sizing by width makes the narrow exclamation render
     ~2x taller). 24% is the question mark's earlier size reduced by 25%. */
  height: 24%;
  width: auto;
  object-fit: contain;
  opacity: 0;
  transform: translateX(-50%) scale(0.5);
  animation: mf-arrive 0.55s cubic-bezier(.4, 1.4, .5, 1) forwards;
  /* tier: strong — icon scales in from 0.5 to full size. */
  filter: var(--icon-shadow-strong);
}
.mf-item-1 { top:  3%; animation-delay: 0.20s; }
.mf-item-2 { top: 37%; animation-delay: 0.90s; }
.mf-item-3 { top: 71%; animation-delay: 1.60s; }
@keyframes mf-arrive {
  to { opacity: 1; transform: translateX(-50%) scale(1); }
}

/* Vertical connectors between the stacked icons. transform-origin top so the
   line draws downward toward the next icon just before it arrives. */
.mf-link {
  position: absolute;
  left: 50%;
  z-index: 1;                 /* connector lines sit behind the icons */
  width: 2px;
  margin-left: -1px;
  background: var(--teal);
  border-radius: 1px;
  opacity: 0;
  transform: scaleY(0);
  transform-origin: 50% 0%;
  filter: drop-shadow(0 0 3px rgba(13, 148, 136, 0.5));
  animation: mf-link-grow 0.5s ease-out forwards;
}
.mf-link-1 { top: 27%; height: 10%; animation-delay: 0.55s; }
.mf-link-2 { top: 61%; height: 10%; animation-delay: 1.25s; }
@keyframes mf-link-grow {
  to { opacity: 1; transform: scaleY(1); }
}

/* Shield-with-key arrives last: large enough to fully cover the attacker,
   centred over it, and fading in slowly (3x the icon arrival) so the eye
   reads the protection settling over the threat. */
.mf-shield {
  position: absolute;
  left: 50%;
  top: 28%;
  z-index: 3;                 /* shield covers everything, including the attacker */
  /* Sized by height like the marks; covers the attacker.
     Nudged right and down to sit squarely over the hooded figure. */
  height: 33.6%;
  width: auto;
  margin-left: 10px;
  margin-top: 10px;
  object-fit: contain;
  opacity: 0;
  transform: translateX(-50%) scale(0.6);
  animation: mf-shield-in 4.1s cubic-bezier(.4, 1.4, .5, 1) forwards;
  animation-delay: 3.15s;
  /* tier: strong — the protective control settling over the threat. */
  filter: var(--icon-shadow-strong);
}
@keyframes mf-shield-in {
  to { opacity: 1; transform: translateX(-50%) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-visual--mitigation-flow { animation: none; transform: scale(1); }
  .mf-item { animation: none; opacity: 1; transform: translateX(-50%) scale(1); }
  .mf-link { animation: none; opacity: 1; transform: scaleY(1); }
  .mf-shield { animation: none; opacity: 1; transform: translateX(-50%) scale(1); }
}

/* ---------- Hero side visual: fan-in card grid (used on /solutions/) ----------
   Wider container + 2×2 grid so each icon roughly doubles in visible size. */
.hero-visual.hero-visual--fan {
  width: clamp(260px, 34vw, 400px);
}
.hero-visual--fan {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-template-rows: repeat(2, 1fr);
  gap: 4%;
  padding: 4%;
  place-items: center;
}
.fan-icon {
  width: 100%;
  height: 100%;
  max-height: 100%;
  object-fit: contain;
  opacity: 0;
  transform: scale(0.8) rotate(-3deg);
  /* tier: strong — each icon scales and rotates into place. */
  filter: var(--icon-shadow-strong);
  animation: fan-fade 0.6s ease-out forwards;
}
.fan-icon-1 { animation-delay: 0.05s; }
.fan-icon-2 { animation-delay: 0.20s; }
.fan-icon-3 { animation-delay: 0.35s; }
.fan-icon-4 { animation-delay: 0.50s; }
@keyframes fan-fade {
  to { opacity: 1; transform: scale(1) rotate(0); }
}
@media (prefers-reduced-motion: reduce) {
  .fan-icon { animation: none; opacity: 1; transform: none; }
}

/* ---------- Hero side visual: stack of papers (used on /resources/) ----------
   Three file cards form a pile in the upper area; download icon lands separately
   below the pile so it has its own clear space. */
.stack-card {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 42%;
  height: 42%;
  object-fit: contain;
  opacity: 0;
  /* tier: strong — cards drop in and overlap; halo keeps them legible. */
  filter: var(--icon-shadow-strong);
  animation: stack-drop 0.7s cubic-bezier(.4, 1.4, .5, 1) forwards;
}
.stack-card-1 { --rot:  -9deg; --dx: -12%; --dy: -38%; animation-delay: 0.10s; }
.stack-card-2 { --rot: -12deg; --dx: -45%; --dy: -42%; animation-delay: 0.40s; }
.stack-card-3 { --rot:  12deg; --dx:  45%; --dy: -32%; animation-delay: 0.70s; }
.stack-card-4 { --rot:   0deg; --dx:   0%; --dy:  60%; width: 32%; height: 32%; animation-delay: 1.00s; }
@keyframes stack-drop {
  from { opacity: 0; transform: translate(-50%, -160%) rotate(0); }
  to   { opacity: 1; transform: translate(calc(-50% + var(--dx, 0)), calc(-50% + var(--dy, 0))) rotate(var(--rot, 0)); }
}
@media (prefers-reduced-motion: reduce) {
  .stack-card { animation: none; opacity: 1; transform: translate(calc(-50% + var(--dx, 0)), calc(-50% + var(--dy, 0))) rotate(var(--rot, 0)); }
}

/* Hero side visual: file pile (used on /resources/terms-and-definitions/). */
.pile-card {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 38%;
  height: 38%;
  object-fit: contain;
  opacity: 0;
  filter: var(--icon-shadow-strong);
  animation: pile-card-drop 0.75s cubic-bezier(.35, 1.3, .5, 1) forwards;
}
.pile-card-1 { --rot:  -7deg; --dx: -10%; --dy: -22%; animation-delay: 0.05s; }
.pile-card-2 { --rot:  11deg; --dx:  16%; --dy: -30%; animation-delay: 0.15s; }
.pile-card-3 { --rot: -13deg; --dx: -24%; --dy:   0%; animation-delay: 0.25s; }
.pile-card-4 { --rot:   6deg; --dx:  12%; --dy:  -8%; animation-delay: 0.35s; }
.pile-card-5 { --rot:  -2deg; --dx: -16%; --dy:  15%; animation-delay: 0.45s; }
.pile-card-6 { --rot:  17deg; --dx:  28%; --dy:  12%; animation-delay: 0.55s; }
.pile-card-7 { --rot:  -9deg; --dx: -32%; --dy:  27%; animation-delay: 0.65s; }
.pile-card-8 { --rot:   4deg; --dx:   4%; --dy:  33%; animation-delay: 0.75s; }
.pile-card-9 { --rot:  -5deg; --dx:  20%; --dy:  21%; animation-delay: 0.85s; }
@keyframes pile-card-drop {
  from { opacity: 0; transform: translate(-50%, -160%) rotate(0deg); }
  to   { opacity: 1; transform: translate(calc(-50% + var(--dx, 0%)), calc(-50% + var(--dy, 0%))) rotate(var(--rot, 0deg)); }
}
@media (prefers-reduced-motion: reduce) {
  .pile-card { animation: none; opacity: 1; transform: translate(calc(-50% + var(--dx, 0%)), calc(-50% + var(--dy, 0%))) rotate(var(--rot, 0deg)); }
}

/* ---------- Hero side visual: breath-pulse (used on /contact/) ---------- */

.hero-visual--breath > img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 75%;
  height: 75%;
  object-fit: contain;
  transform: translate(-50%, -50%) scale(1);
  /* tier: strong — icon scales 1.0 → 1.05 → 1.0 (breath pulse). */
  filter: var(--icon-shadow-strong);
  animation: breath-pulse 2.4s ease-in-out infinite;
}
@keyframes breath-pulse {
  0%, 100% { transform: translate(-50%, -50%) scale(1);    }
  50%      { transform: translate(-50%, -50%) scale(1.05); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-visual--breath > img { animation: none; }
}

/* ---------- Hero side visual: magnify tour (used on /defense-in-depth/bus-load-monitoring/) ---------- */

.hero-visual--magnify {
  overflow: hidden;
}
.magnify-bg {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 92%;
  height: 92%;
  object-fit: contain;
  transform: translate(-50%, -50%);
  opacity: 0.7;
  /* tier: soft — static background beneath the touring magnifier. */
  filter: var(--icon-shadow-soft);
}
.magnify-glass {
  position: absolute;
  width: 38%;
  height: auto;
  object-fit: contain;
  /* tier: strong — glass tours the four quadrants of the background. */
  filter: var(--icon-shadow-strong);
  animation: magnify-tour 7s ease-in-out infinite;
}
@keyframes magnify-tour {
  0%, 100% { top:  6%; left:  8%; }
  25%      { top:  6%; left: 54%; }
  50%      { top: 56%; left: 54%; }
  75%      { top: 56%; left:  8%; }
}
@media (prefers-reduced-motion: reduce) {
  .magnify-glass { animation: none; top: 30%; left: 30%; }
}

/* ---------- Hero side visual: deflect (used on /defense-in-depth/local-injection-detection/) ---------- */

.deflect-shield {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 60%;
  height: 60%;
  object-fit: contain;
  transform: translate(-50%, -50%);
  /* tier: soft — static shield centre piece. */
  filter: var(--icon-shadow-soft);
}
.deflect-bug {
  position: absolute;
  width: 26%;
  height: auto;
  top: 8%;
  right: 6%;
  object-fit: contain;
  /* tier: strong — bug deflects off the shield (translate + rotate). */
  filter: var(--icon-shadow-strong);
  animation: deflect 2s ease-in-out forwards 0.4s;
}
@keyframes deflect {
  0%   { transform: translate(0, 0) rotate(0);                 opacity: 1; }
  35%  { transform: translate(-55%, 55%) rotate(-15deg);       opacity: 1; }
  50%  { transform: translate(-50%, 50%) rotate(-10deg);       opacity: 1; }
  100% { transform: translate(40%, -40%) rotate(45deg);        opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .deflect-bug { animation: none; opacity: 0.6; }
}

/* ---------- Hero side visual: spoof alert (used on /solutions/local-injection-detection/) ---------- */

.spoof-attacker {
  position: absolute;
  top: 4%;
  left: 8%;
  width: 38%;
  height: auto;
  object-fit: contain;
  /* tier: soft — static endpoint icon. */
  filter: var(--icon-shadow-soft);
}
.spoof-chip {
  position: absolute;
  bottom: 4%;
  right: 8%;
  width: 38%;
  height: auto;
  object-fit: contain;
  /* tier: soft — static endpoint icon. */
  filter: var(--icon-shadow-soft);
}
.spoof-alert {
  position: absolute;
  width: 9%;
  height: auto;
  top: 22%;
  left: 30%;
  object-fit: contain;
  transform: scale(0.5);
  opacity: 0;
  /* tier: strong — alert travels across the scene. */
  filter: var(--icon-shadow-strong);
  animation: spoof-alert-travel 5s ease-in-out infinite;
}
@keyframes spoof-alert-travel {
  0%   { top: 22%; left: 30%; transform: scale(0.5); opacity: 0; }
  8%   { top: 24%; left: 32%; transform: scale(0.6); opacity: 1; }
  35%  { top: 42%; left: 46%; transform: scale(1.4); opacity: 1; }
  58%  { top: 60%; left: 60%; transform: scale(0.5); opacity: 1; }
  64%  { top: 60%; left: 60%; transform: scale(0.5); opacity: 0; }
  100% { top: 60%; left: 60%; transform: scale(0.5); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .spoof-alert { animation: none; opacity: 1; transform: scale(1); top: 42%; left: 46%; }
}

/* ---------- Hero side visual: protected frame relay (used on /solutions/frame-security/) ---------- */

.hero-visual.hero-visual--frame-relay {
  /* Wider, shorter container so two 33%-larger chips can sit with ~3× the original
     gap between their inner edges. Aspect ~1.6:1 keeps vertical bulk modest. */
  width: clamp(360px, 45vw, 520px);
  aspect-ratio: 1.6 / 1;
}
.hero-visual--frame-relay { overflow: hidden; }
.fs-chip {
  position: absolute;
  top: 22%;
  width: 31%;
  height: auto;
  object-fit: contain;
  /* tier: soft — chips are static endpoints; moving box/shield pass over them. */
  filter: var(--icon-shadow-soft);
}
.fs-chip-left  { left: 4%;  }
.fs-chip-right { right: 4%; }

.fs-box {
  position: absolute;
  top: 56%;
  left: 28%;
  width: 15%;
  height: auto;
  object-fit: contain;
  opacity: 0;
  z-index: 2;
  /* tier: strong — box travels left → right, overlapping chips at each end. */
  filter: var(--icon-shadow-strong);
  animation: fs-box-trip 7s ease-in-out infinite;
}
.fs-shield {
  position: absolute;
  top: 41%;
  left: 28%;
  width: 15%;
  height: auto;
  object-fit: contain;
  opacity: 0;
  z-index: 3;
  /* tier: strong — shield travels with the box, sitting on top of it. */
  filter: var(--icon-shadow-strong);
  animation: fs-shield-trip 7s ease-in-out infinite;
}
@keyframes fs-box-trip {
  0%   { left: 28%; opacity: 0; }
  4%   { left: 28%; opacity: 1; }
  20%  { left: 28%; opacity: 1; }
  50%  { left: 57%; opacity: 1; }
  58%  { left: 57%; opacity: 1; }
  64%  { left: 57%; opacity: 0; }
  100% { left: 57%; opacity: 0; }
}
@keyframes fs-shield-trip {
  0%   { left: 28%; opacity: 0; }
  16%  { left: 28%; opacity: 0; }
  20%  { left: 28%; opacity: 1; }
  50%  { left: 57%; opacity: 1; }
  55%  { left: 57%; opacity: 0; }
  100% { left: 57%; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .fs-box    { animation: none; opacity: 1; left: 42.5%; }
  .fs-shield { animation: none; opacity: 1; left: 42.5%; }
}

/* ---------- Hero side visual: shield crack + score reveal (used on /risk-assessment/cvss-for-can/) ----------
   Story: shield is intact (pre-disclosure), shield cracks (vulnerability found),
   then a CVSS bar chart reveals left-to-right at the shield's lower-right —
   each successive bar visibly taller than the last (the source PNG's bars
   increase in height left to right, so a left-to-right clip-path reveal
   produces that "score growing" read). 3.5s one-shot via `forwards` so the
   final cracked + scored state is preserved for the reader.

   Layering: shield_cracked.png sits permanently in the background at full
   opacity; shield_alone.png is stacked on top of it (same coordinates) and
   fades out, revealing the cracked one underneath. Since the cracked PNG is
   slightly darker than the intact one, that handoff reads as a slight dim
   during the crack — no extra brightness filter needed. */

.sc-shield,
.sc-shield-cracked {
  position: absolute;
  top: 4%;
  left: 8%;
  width: 77%;
  height: auto;
  object-fit: contain;
  /* tier: strong — alone fades out into the cracked beneath; both participate
     in the animated scene with the revealing bars. */
  filter: var(--icon-shadow-strong);
}
.sc-shield-cracked {
  opacity: 1;
  z-index: 1;
}
.sc-shield {
  opacity: 1;
  z-index: 2;
  animation: sc-shield-out 3.5s ease-in-out forwards;
}
.sc-bars {
  position: absolute;
  bottom: 19%;
  right: 20%;
  width: 57%;
  height: auto;
  object-fit: contain;
  opacity: 0;
  clip-path: inset(0 100% 0 0);
  z-index: 3;
  /* tier: strong — bars reveal left-to-right via clip-path animation. */
  filter: var(--icon-shadow-strong);
  animation: sc-bars-grow 3.5s ease-in-out forwards;
}
@keyframes sc-shield-out {
  0%   { opacity: 1; }
  11%  { opacity: 1; }
  35%  { opacity: 0; }
  100% { opacity: 0; }
}
@keyframes sc-bars-grow {
  0%   { opacity: 0; clip-path: inset(0 100% 0 0); }
  45%  { opacity: 0; clip-path: inset(0 100% 0 0); }
  50%  { opacity: 1; clip-path: inset(0 100% 0 0); }
  88%  { opacity: 1; clip-path: inset(0 0    0 0); }
  100% { opacity: 1; clip-path: inset(0 0    0 0); }
}
@media (prefers-reduced-motion: reduce) {
  .sc-shield { animation: none; opacity: 0; }
  .sc-bars   { animation: none; opacity: 1; clip-path: none; }
}

/* ---------- Hero side visual: pillars reveal (used on /risk-assessment/iec-62443/) ----------
   Story: the building (regulation / law) sits on top of the IEC 62443 pillars
   (persons / processes / technology). The building fades from left to right
   over ~4 s, revealing the pillars that hold it up — the structural framework
   behind the regulation. One-shot via `forwards` so the final state (pillars
   exposed) is preserved.

   Soft left-to-right fade implemented via mask-image + mask-position sweep.
   Mask is a horizontal gradient 3× the element width with a transparent zone,
   a 15-percentage-point soft transition, and a black zone. Sliding the mask
   from its rightmost position (whole element under the black/opaque zone) to
   its leftmost position (whole element under the transparent zone) sweeps the
   fade across. Soft edge sits in the mid 15 % of the mask width, which lands
   ~5 % of the visible element wide — gentle but visible. */

.pr-pillars,
.pr-building {
  position: absolute;
  top: 6%;
  left: 6%;
  width: 88%;
  height: 88%;
  object-fit: contain;
}
.pr-pillars  {
  z-index: 1;
  /* No shadow — pillars and building are large illustrative graphics, not icons.
     The shadow tier system targets icon-style PNGs; large flat illustrations
     read cleanly on the hero background without halo. */
}
.pr-building {
  top: 7%;
  left: 7%;
  width: 86%;
  height: 86%;
  z-index: 2;
  /* No shadow — large illustrative graphic, not an icon (see .pr-pillars). */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, transparent 35%, black 50%, black 100%);
          mask-image: linear-gradient(to right, transparent 0%, transparent 35%, black 50%, black 100%);
  -webkit-mask-size: 300% 100%;
          mask-size: 300% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: 100% 0%;
          mask-position: 100% 0%;
  animation: pr-building-dissolve 4.5s ease-in-out forwards -0.6s;
}
@keyframes pr-building-dissolve {
  0%   { -webkit-mask-position: 100% 0%;          mask-position: 100% 0%; }
  100% { -webkit-mask-position:   0% 0%;          mask-position:   0% 0%; }
}
@media (prefers-reduced-motion: reduce) {
  .pr-building {
    animation: none;
    -webkit-mask-image: none;
            mask-image: none;
    opacity: 0;
  }
}

/* ---------- Hero side visual: file + lock (used on /privacy/ and /privacy/de/) ----------
   Story: a document (file.png) zooms into view, then a lock (orange_lock.png)
   fades in at the lower-right corner — the document is being protected. 3 s
   one-shot via `forwards`; final state preserved. */

.pv-file {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 70%;
  height: 70%;
  object-fit: contain;
  transform: translate(-50%, -50%) scale(0.5);
  opacity: 0;
  z-index: 1;
  /* tier: strong — file zooms in from scale 0.5 to 1. */
  filter: var(--icon-shadow-strong);
  animation: pv-file-zoom 3s ease-in-out forwards;
}
.pv-lock {
  position: absolute;
  bottom: 12%;
  right: 12%;
  width: 24%;
  height: auto;
  object-fit: contain;
  opacity: 0;
  z-index: 2;
  /* tier: strong — lock fades in on top of the file. */
  filter: var(--icon-shadow-strong);
  animation: pv-lock-fade 3s ease-in-out forwards;
}
@keyframes pv-file-zoom {
  0%   { transform: translate(-50%, -50%) scale(0.5); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scale(1);   opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1);   opacity: 1; }
}
@keyframes pv-lock-fade {
  0%   { opacity: 0; }
  45%  { opacity: 0; }
  70%  { opacity: 1; }
  100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .pv-file { animation: none; opacity: 1; transform: translate(-50%, -50%) scale(1); }
  .pv-lock { animation: none; opacity: 1; }
}

/* ---------- Hero side visual: file + paragraph sign (used on /imprint/ and /imprint/de/) ----------
   Story: a document zooms into view, then a § paragraph symbol (universal mark
   of statute / German legal-text reference) fades in at the lower-right — the
   document is a legal one (§5 TMG imprint). Mirrors the /privacy/ file-lock
   pattern; only the second element differs (text glyph instead of an icon
   image). 3 s one-shot via `forwards`; final state preserved. */

.ip-file {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 70%;
  height: 70%;
  object-fit: contain;
  transform: translate(-50%, -50%) scale(0.5);
  opacity: 0;
  z-index: 1;
  /* tier: strong — file zooms in from scale 0.5 to 1. */
  filter: var(--icon-shadow-strong);
  animation: pv-file-zoom 3s ease-in-out forwards;
}
.ip-paragraph {
  position: absolute;
  bottom: 8%;
  right: 18%;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(56px, 8vw, 96px);
  line-height: 1;
  color: var(--gold);
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
  opacity: 0;
  z-index: 2;
  animation: pv-lock-fade 3s ease-in-out forwards;
}
@media (prefers-reduced-motion: reduce) {
  .ip-file      { animation: none; opacity: 1; transform: translate(-50%, -50%) scale(1); }
  .ip-paragraph { animation: none; opacity: 1; }
}

/* ---------- Hero side visual: gear rotate (used on /defense-in-depth/frame-security/) ---------- */

.hero-visual--gear > img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 70%;
  height: 70%;
  object-fit: contain;
  transform: translate(-50%, -50%) rotate(0);
  /* tier: strong — gear rotates a full turn. */
  filter: var(--icon-shadow-strong);
  animation: gear-turn 1.1s cubic-bezier(.4, .8, .3, 1) forwards 0.3s;
}
@keyframes gear-turn {
  from { transform: translate(-50%, -50%) rotate(0); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-visual--gear > img { animation: none; }
}

/* ---------- Hero side visual: alert popup (used on /defense-in-depth/anomaly-event-monitoring/) ---------- */

.alert-bg {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 88%;
  height: 88%;
  object-fit: contain;
  transform: translate(-50%, -50%);
  /* tier: soft — static background beneath alert popups. */
  filter: var(--icon-shadow-soft);
}
.alert-pop {
  position: absolute;
  width: 11%;
  height: auto;
  object-fit: contain;
  opacity: 0;
  transform: scale(0.5);
  animation: alert-pop-in 0.4s cubic-bezier(.5, 1.6, .5, 1) forwards;
  /* tier: strong — alert pops up onto the background icon. */
  filter: var(--icon-shadow-strong);
}
@keyframes alert-pop-in {
  to { opacity: 1; transform: scale(1); }
}
.alert-pop-1 { top: 12%; right: 12%; animation-delay: 0.6s; }
.alert-pop-2 { top: 50%; left: 10%;  animation-delay: 1.0s; }
/* Second mark sits ~50% of its own height higher than its original 14% bottom
   anchor. Icon is 11% wide with height: auto on a 158x453 PNG -> intrinsic
   height ~= width * 2.867, so half-height ~= 15.77% of the hero-visual square. */
.alert-pop-3 { bottom: 29.77%; right: 22%; animation-delay: 1.4s; }
@media (prefers-reduced-motion: reduce) {
  .alert-pop { animation: none; opacity: 1; transform: scale(1); }
}

/* ---------- Hero side visual: slide from right (used on /threats/physical-access/) ---------- */

.hero-visual--slide-right > img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 80%;
  height: 80%;
  object-fit: contain;
  /* tier: strong — image slides in from the right. */
  filter: var(--icon-shadow-strong);
  animation: slide-right 1s cubic-bezier(.3, 1.2, .4, 1) forwards;
}
@keyframes slide-right {
  from { transform: translate(60%, -50%);  opacity: 0; }
  to   { transform: translate(-50%, -50%); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-visual--slide-right > img { animation: none; opacity: 1; transform: translate(-50%, -50%); }
}

/* ---------- Hero side visual: descend from above (used on /threats/remote-attack/) ---------- */

.hero-visual--descend > img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 80%;
  height: 80%;
  object-fit: contain;
  /* tier: strong — image descends from above the frame. */
  filter: var(--icon-shadow-strong);
  animation: descend 1.2s cubic-bezier(.3, 1.2, .4, 1) forwards;
}
@keyframes descend {
  from { transform: translate(-50%, -260%); opacity: 0; }
  to   { transform: translate(-50%, -50%);  opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-visual--descend > img { animation: none; opacity: 1; transform: translate(-50%, -50%); }
}

/* ---------- Hero side visual: five concentric defense layers (used on /defense-in-depth/) ----------
   Center icon (chip) is the asset being defended. Five orbits, all centered on
   50%/50% (concentric, no pivot offset), each carrying one defensive-shell icon
   with its own gold conic-gradient arc trail (recipe: .claude/ORBIT_TRAILS.md).
   Direction alternates per orbit (CW/CCW/CW/CCW/CW) for visual interest;
   starting compass angles are evenly distributed 0deg / 72deg / 144deg / 216deg /
   288deg for a balanced initial frame; periods slow inner-to-outer (38/46/54/62/70 s)
   so adjacent rings drift relative to each other.
   All six icons (center + five orbiting) are 40 px square — same size per spec.
   Radii: 35 / 60 / 90 / 120 / 150 px at W=320. Mask annulus uses px (gotcha #6).
   Only orbit 5 (r=150) gets inset: -25px as defense against sub-pixel edge clipping
   near the container border (gotcha #2). */

.did-orbit {
  position: absolute;
  inset: 0;
  transform-origin: 50% 50%;
  pointer-events: none;
}
.did-orbit-1 { animation: hv-orbit-rot     38s linear infinite; }
.did-orbit-2 { animation: hv-orbit-rot-ccw 46s linear infinite; }
.did-orbit-3 { animation: hv-orbit-rot     54s linear infinite; }
.did-orbit-4 { animation: hv-orbit-rot-ccw 62s linear infinite; }
.did-orbit-5 { animation: hv-orbit-rot     70s linear infinite; }

.did-icon {
  position: absolute;
  width: 60px;
  height: 60px;
  object-fit: contain;
  /* tier: strong — orbits across the centre chip and adjacent ring icons. */
  filter: var(--icon-shadow-strong);
}
/* Compass theta = 0deg: icon center at (W/2, W/2 - r). */
.did-icon-1 {
  top: calc(50% - 35px - 30px);
  left: calc(50% - 30px);
  animation: hv-counter 38s linear infinite;
}
/* Compass theta = 72deg: dx = r * sin(72) = 57.07, dy = -r * cos(72) = -18.54. */
.did-icon-2 {
  top: calc(50% - 18.54px - 30px);
  left: calc(50% + 57.07px - 30px);
  animation: hv-counter-cw 46s linear infinite;
}
/* Compass theta = 144deg: dx = r * sin(144) = 52.90, dy = -r * cos(144) = 72.81. */
.did-icon-3 {
  top: calc(50% + 72.81px - 30px);
  left: calc(50% + 52.90px - 30px);
  animation: hv-counter 54s linear infinite;
}
/* Compass theta = 216deg: dx = r * sin(216) = -70.53, dy = -r * cos(216) = 97.08. */
.did-icon-4 {
  top: calc(50% + 97.08px - 30px);
  left: calc(50% - 70.53px - 30px);
  animation: hv-counter-cw 62s linear infinite;
}
/* Compass theta = 288deg: dx = r * sin(288) = -142.66, dy = -r * cos(288) = -46.35. */
.did-icon-5 {
  top: calc(50% - 46.35px - 30px);
  left: calc(50% - 142.66px - 30px);
  animation: hv-counter 70s linear infinite;
}

.did-trail {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* Arc length 270deg (was 90deg) to better show each defensive layer as a near-
   full ring. CW orbits (1, 3, 5): conic from (theta - 270) with bright at 270deg,
   fades CCW behind the icon. CCW orbits (2, 4): conic from theta with bright at
   0deg, fades CW for 270deg behind the icon. */
.did-trail-1 {
  background: conic-gradient(from 90deg at 50% 50%,
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 270deg,
              transparent              270.01deg);
  -webkit-mask: radial-gradient(circle at 50% 50%,
                transparent 32px, #000 34px, #000 36px, transparent 38px);
          mask: radial-gradient(circle at 50% 50%,
                transparent 32px, #000 34px, #000 36px, transparent 38px);
}
.did-trail-2 {
  background: conic-gradient(from 72deg at 50% 50%,
              rgba(240, 162, 46, 0.55) 0deg,
              rgba(240, 162, 46, 0)    270deg,
              transparent              270.01deg);
  -webkit-mask: radial-gradient(circle at 50% 50%,
                transparent 57px, #000 59px, #000 61px, transparent 63px);
          mask: radial-gradient(circle at 50% 50%,
                transparent 57px, #000 59px, #000 61px, transparent 63px);
}
.did-trail-3 {
  background: conic-gradient(from 234deg at 50% 50%,
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 270deg,
              transparent              270.01deg);
  -webkit-mask: radial-gradient(circle at 50% 50%,
                transparent 87px, #000 89px, #000 91px, transparent 93px);
          mask: radial-gradient(circle at 50% 50%,
                transparent 87px, #000 89px, #000 91px, transparent 93px);
}
.did-trail-4 {
  background: conic-gradient(from 216deg at 50% 50%,
              rgba(240, 162, 46, 0.55) 0deg,
              rgba(240, 162, 46, 0)    270deg,
              transparent              270.01deg);
  -webkit-mask: radial-gradient(circle at 50% 50%,
                transparent 117px, #000 119px, #000 121px, transparent 123px);
          mask: radial-gradient(circle at 50% 50%,
                transparent 117px, #000 119px, #000 121px, transparent 123px);
}
.did-trail-5 {
  inset: -25px;
  background: conic-gradient(from 18deg at 50% 50%,
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 270deg,
              transparent              270.01deg);
  -webkit-mask: radial-gradient(circle at 50% 50%,
                transparent 147px, #000 149px, #000 151px, transparent 153px);
          mask: radial-gradient(circle at 50% 50%,
                transparent 147px, #000 149px, #000 151px, transparent 153px);
}
@media (prefers-reduced-motion: reduce) {
  .did-orbit,
  .did-icon { animation: none; }
}

/* ---------- Hero side visual: glitch jitter (used on /threats/protocol-weaknesses/) ---------- */

.hero-visual--glitch > img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 80%;
  height: 80%;
  object-fit: contain;
  transform: translate(-50%, -50%);
  animation: glitch-jitter 0.8s steps(8, end) 0.6s 2;
}
@keyframes glitch-jitter {
  0%   { transform: translate(-50%, -50%);                  filter: none; }
  10%  { transform: translate(calc(-50% - 3px), -50%);      filter: hue-rotate(15deg); }
  20%  { transform: translate(calc(-50% + 3px), -50%);      filter: none; }
  30%  { transform: translate(-50%, -50%);                  filter: hue-rotate(-10deg); }
  40%  { transform: translate(calc(-50% - 2px), calc(-50% - 1px)); filter: none; }
  60%  { transform: translate(calc(-50% + 2px), -50%);      filter: none; }
  100% { transform: translate(-50%, -50%);                  filter: none; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-visual--glitch > img { animation: none; }
}

/* ---------- Hero side visual: enlarged risk-o-meter with single load pulse (used on /risk-assessment/) ----------
   33% larger than the standard plain hv-center; pulses once on page load (no looping). */
/* Pixel-based orbit / arc-trail math freeze. The conic-gradient pivot
   offsets and radial-gradient mask radii on these hero visuals are
   absolute pixels tuned for a 320 x 320 design frame. Letting the
   container shrink (which the base .hero-visual width clamp and its
   mobile media query would otherwise do) makes the trail annulus, the
   conic pivot, and the orbiting icon's path all drift relative to each
   other and the arcs visibly clip. Chained-class specificity here
   overrides both the base .hero-visual width rule and the mobile media
   query, since they are both single-class selectors. */
.hero-visual.hero-visual--shells-orbits,
.hero-visual.hero-visual--threat-cloud,
.hero-visual.hero-visual--physical-orbit,
.hero-visual.hero-visual--risk-meter,
.hero-visual.hero-visual--access-orbit,
.hero-visual.hero-visual--gateways-orbit,
.hero-visual.hero-visual--key-scatter,
.hero-visual.hero-visual--key-pair,
.hero-visual.hero-visual--handover,
.hero-visual.hero-visual--symkey,
.hero-visual.hero-visual--asympub,
.hero-visual.hero-visual--devlife {
  width: 320px;
  min-width: 320px;
  height: 320px;
  flex-shrink: 0;
}

/* ---------- Hero side visual: scattered keys (used on /key-management/) ----------
   A loose cluster of key icons drifting on irregular, non-circular paths to
   suggest keys with no clear origin or destination. Deliberately NOT the
   hv-orbit pattern: each icon runs its own multi-waypoint keyframe walk with its
   own duration and a negative start delay, so the group never synchronizes and
   the motion reads as unpredictable. Eased segments between non-collinear
   waypoints give the back-and-forth half-curve feel. */
.hero-visual--key-scatter {
  position: relative;
  overflow: visible;
}
.key-float {
  position: absolute;
  width: 52px;
  height: 52px;
  object-fit: contain;
  filter: drop-shadow(0 4px 9px rgba(0, 0, 0, 0.22));
  will-change: transform;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
.key-float-1 { top: 16%; left: 15%; width: 80px; height: 80px; animation-name: key-scatter-a; animation-duration: 16.6s; animation-delay: -2s; }
.key-float-2 { top: 53%; left: 58%; width: 76px; height: 76px; animation-name: key-scatter-b; animation-duration: 23.4s; animation-delay: -5s; }
.key-float-3 { top: 16%; left: 55%; width: 84px; height: 84px; animation-name: key-scatter-c; animation-duration: 19.8s; animation-delay: -1s; }
.key-float-4 { top: 58%; left: 16%; width: 78px; height: 78px; animation-name: key-scatter-d; animation-duration: 26.2s; animation-delay: -7s; }
.key-float-5 { top: 37%; left: 39%; width: 90px; height: 90px; animation-name: key-scatter-e; animation-duration: 20.8s; animation-delay: -3s; }
.key-float-6 { top: 66%; left: 42%; width: 76px; height: 76px; animation-name: key-scatter-f; animation-duration: 25.2s; animation-delay: -4s; }

@keyframes key-scatter-a {
  0%   { transform: translate(0, 0) rotate(0deg); }
  14%  { transform: translate(36px, 20px) rotate(10deg); }
  27%  { transform: translate(12px, 52px) rotate(-6deg); }
  41%  { transform: translate(-28px, 30px) rotate(8deg); }
  58%  { transform: translate(-40px, -16px) rotate(-12deg); }
  72%  { transform: translate(-8px, -44px) rotate(6deg); }
  86%  { transform: translate(30px, -26px) rotate(-9deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes key-scatter-b {
  0%   { transform: translate(0, 0) rotate(0deg); }
  12%  { transform: translate(-30px, 26px) rotate(-8deg); }
  29%  { transform: translate(-46px, -10px) rotate(12deg); }
  44%  { transform: translate(-14px, -40px) rotate(-5deg); }
  61%  { transform: translate(28px, -30px) rotate(9deg); }
  76%  { transform: translate(44px, 12px) rotate(-11deg); }
  90%  { transform: translate(16px, 40px) rotate(7deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes key-scatter-c {
  0%   { transform: translate(0, 0) rotate(0deg); }
  17%  { transform: translate(24px, -34px) rotate(7deg); }
  33%  { transform: translate(48px, 2px) rotate(-10deg); }
  50%  { transform: translate(20px, 38px) rotate(6deg); }
  67%  { transform: translate(-26px, 46px) rotate(-8deg); }
  83%  { transform: translate(-38px, -18px) rotate(11deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes key-scatter-d {
  0%   { transform: translate(0, 0) rotate(0deg); }
  15%  { transform: translate(-22px, -30px) rotate(-9deg); }
  31%  { transform: translate(18px, -46px) rotate(8deg); }
  48%  { transform: translate(42px, -8px) rotate(-6deg); }
  64%  { transform: translate(22px, 34px) rotate(10deg); }
  81%  { transform: translate(-24px, 28px) rotate(-7deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes key-scatter-e {
  0%   { transform: translate(0, 0) rotate(0deg); }
  13%  { transform: translate(34px, 18px) rotate(9deg); }
  26%  { transform: translate(8px, 44px) rotate(-7deg); }
  39%  { transform: translate(-30px, 36px) rotate(6deg); }
  55%  { transform: translate(-44px, 4px) rotate(-11deg); }
  70%  { transform: translate(-18px, -38px) rotate(8deg); }
  88%  { transform: translate(26px, -28px) rotate(-6deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes key-scatter-f {
  0%   { transform: translate(0, 0) rotate(0deg); }
  16%  { transform: translate(-28px, 22px) rotate(-8deg); }
  30%  { transform: translate(-40px, -14px) rotate(10deg); }
  47%  { transform: translate(-6px, -42px) rotate(-5deg); }
  63%  { transform: translate(30px, -32px) rotate(7deg); }
  79%  { transform: translate(40px, 16px) rotate(-10deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

@media (prefers-reduced-motion: reduce) {
  .key-float { animation: none; }
}

/* Three key icons orbiting the scattered cluster, each around its own centre;
   the three centres form a triangle (top, lower-left, lower-right) and one key
   (hv-ok-c) is mirrored. The element sits centred on its orbit centre (left/top =
   centre - half size); rotate(theta) translateX(radius) rotate(-theta) sweeps it
   around while keeping the key upright (the two rotations cancel orientation, the
   translate sets the radius). */
.hv-orbit-key {
  position: absolute;
  width: 54px;
  height: 54px;
  object-fit: contain;
  filter: drop-shadow(0 4px 9px rgba(0, 0, 0, 0.22));
  will-change: transform;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.hv-ok-a { left: 133px; top: 81px;  animation-name: hv-orbit-a; animation-duration: 17s; }
.hv-ok-b { left: 81px;  top: 183px; animation-name: hv-orbit-b; animation-duration: 21s; }
.hv-ok-c { left: 185px; top: 183px; animation-name: hv-orbit-c; animation-duration: 19s; }

@keyframes hv-orbit-a {
  from { transform: rotate(0deg)    translateX(94px) rotate(0deg); }
  to   { transform: rotate(360deg)  translateX(94px) rotate(-360deg); }
}
@keyframes hv-orbit-b {
  from { transform: rotate(0deg)    translateX(88px) rotate(0deg); }
  to   { transform: rotate(-360deg) translateX(88px) rotate(360deg); }
}
@keyframes hv-orbit-c {
  from { transform: rotate(0deg)    translateX(90px) rotate(0deg)    scaleX(-1); }
  to   { transform: rotate(360deg)  translateX(90px) rotate(-360deg) scaleX(-1); }
}
@media (prefers-reduced-motion: reduce) {
  .hv-orbit-key { animation: none; }
}

/* ---------- Hero side visual: regulations key_exchange overlay (used on
   /key-management/regulations-standards/) ----------
   Reuses the .hero-visual--pillars-reveal graphic from /risk-assessment/iec-62443/.
   A key_exchange icon pulses in over the centre of that graphic and fades back
   out, on repeat. It carries the strong icon shadow so it keeps contrast against
   the illustration underneath, matching the hero icons on the site landing. */
.rs-keyx {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 60%;
  height: 60%;
  transform: translate(-50%, -50%);
  object-fit: contain;
  z-index: 3;
  filter: var(--icon-shadow-strong);
  opacity: 0;
  animation: rs-keyx-pulse 5s ease-in-out infinite;
}
@keyframes rs-keyx-pulse {
  0%, 100% { opacity: 0; }
  35%, 65% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .rs-keyx { animation: none; opacity: 0; }
}

/* ---------- Hero side visual: symmetric/asymmetric key pair (used on
   /key-management/symmetric-vs-asymmetric/) ----------
   Two chip_key icons start overlapped at centre, separate left and right, then
   return. The right icon flips horizontally (scaleX 1 -> -1) over one full
   period, passing edge-on while separated, so the keys overlap identical on one
   pass and mirrored on the next. The mirror stands for the right key switching
   from symmetric to asymmetric. */
.hero-visual--key-pair {
  position: relative;
  overflow: visible;
}
.kp-key {
  position: absolute;
  top: calc(50% - 72px);
  left: calc(50% - 72px);
  width: 144px;
  height: 144px;
  object-fit: contain;
  filter: var(--icon-shadow-strong);
  will-change: transform;
  animation-duration: 8s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
.kp-key-left  { z-index: 1; animation-name: kp-move-left; }
.kp-key-right { z-index: 2; animation-name: kp-move-right; }

@keyframes kp-move-left {
  0%, 50%, 100% { transform: translateX(0); }
  25%, 75%      { transform: translateX(-80px); }
}
@keyframes kp-move-right {
  0%   { transform: translateX(0) scaleX(1); }
  25%  { transform: translateX(80px) scaleX(0); }
  50%  { transform: translateX(0) scaleX(-1); }
  75%  { transform: translateX(80px) scaleX(0); }
  100% { transform: translateX(0) scaleX(1); }
}
@media (prefers-reduced-motion: reduce) {
  .kp-key { animation: none; }
}

/* ---------- Hero side visual: handover (used on /key-management/handover/) ----------
   Two chip bodies (chip.png) sit left and right; a single key (database_key_only.png)
   travels between them, hopping up and over and settling onto each chip so it
   momentarily reads as a keyed chip, then lifting off and crossing back. The key
   (ownership and authority) is what changes hands. */
.hero-visual--handover {
  position: relative;
  overflow: visible;
}
.ho-chip {
  position: absolute;
  top: 87px;
  width: 146px;
  height: 146px;
  object-fit: contain;
  filter: var(--icon-shadow-strong);
}
.ho-chip-left  { left: 0; }
.ho-chip-right { left: 174px; }
.ho-key {
  position: absolute;
  top: 105px;
  left: 18px;
  width: 110px;
  height: 110px;
  object-fit: contain;
  z-index: 3;
  filter: var(--icon-shadow-strong);
  will-change: transform;
  animation: handover-key 6s ease-in-out infinite;
}
@keyframes handover-key {
  0%, 16%  { transform: translate(0, 0); }
  33%      { transform: translate(87px, -54px); }
  50%, 66% { transform: translate(174px, 0); }
  83%      { transform: translate(87px, -54px); }
  100%     { transform: translate(0, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .ho-key { animation: none; }
}

/* ---------- Hero side visual: symmetric key into db + chip (used on
   /key-management/symmetric-key-management/) ----------
   The database (left) is static and always carries its key. The chip (right)
   rides a factory line: it slides in from the top, an empty plain chip. Two
   identical keys start overlapped at top centre, then split: one travels
   down-left and is absorbed into the database (one more key added to the store),
   one travels down-right and lands centred on the chip, overlaying it exactly so
   it reads as a keyed chip. The chip and its key then slide out the bottom
   together, and a fresh empty chip arrives from the top. The keys use
   database_key_only.png and the chip uses chip.png. */
.hero-visual--symkey {
  position: relative;
  overflow: hidden;
}
.sk-db {
  position: absolute;
  left: 8px;
  top: 96px;
  width: 132px;
  height: 132px;
  object-fit: contain;
  filter: var(--icon-shadow-strong);
  z-index: 1;
}
.sk-chip {
  position: absolute;
  left: 173px;
  top: 86px;
  width: 145px;
  height: 145px;
  object-fit: contain;
  filter: var(--icon-shadow-strong);
  z-index: 1;
  animation: symkey-chip 9s ease-in-out infinite;
}
.sk-key {
  position: absolute;
  width: 116px;
  height: 116px;
  object-fit: contain;
  z-index: 3;
  filter: var(--icon-shadow-strong);
  will-change: transform;
  animation-duration: 9s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
.sk-key-left  { left: 44px;  top: 128px; animation-name: symkey-key-left; }
.sk-key-right { left: 188px; top: 100px; animation-name: symkey-key-right; }

@keyframes symkey-chip {
  0%   { transform: translateY(-170px); opacity: 0; }
  6%   { transform: translateY(-170px); opacity: 1; }
  20%  { transform: translateY(0);      opacity: 1; }
  52%  { transform: translateY(0);      opacity: 1; }
  72%  { transform: translateY(190px);  opacity: 1; }
  74%  { transform: translateY(190px);  opacity: 0; }
  75%  { transform: translateY(-170px); opacity: 0; }
  100% { transform: translateY(-170px); opacity: 0; }
}
@keyframes symkey-key-right {
  0%, 20% { transform: translate(-94px, -90px); opacity: 0; }
  28%     { transform: translate(-94px, -90px); opacity: 1; }
  46%     { transform: translate(0, 0);         opacity: 1; }
  52%     { transform: translate(0, 0);         opacity: 1; }
  72%     { transform: translate(0, 190px);     opacity: 1; }
  74%     { transform: translate(0, 190px);     opacity: 0; }
  100%    { transform: translate(-94px, -90px); opacity: 0; }
}
@keyframes symkey-key-left {
  0%, 20% { transform: translate(58px, -118px); opacity: 0; }
  28%     { transform: translate(58px, -118px); opacity: 1; }
  46%     { transform: translate(0, 0);         opacity: 1; }
  54%     { transform: translate(0, 0);         opacity: 0; }
  100%    { transform: translate(58px, -118px); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .sk-chip { animation: none; transform: none; opacity: 1; }
  .sk-key { animation: none; opacity: 0; }
}

/* ---------- Hero side visual: asymmetric, private stays / public published
   (used on /key-management/asymmetric-key-management/) ----------
   A device (chip) fades and zooms into view. A key pair is then born inside it:
   both keys fade in within the chip, one normal (private) and one mirrored
   (public). The private key stays inside the device; the public key leaves the
   chip and drifts off to the top-right, fading as it is published to the world.
   No store, no database: the private key never leaves the device, and the mirror
   shows the public half is the related but distinct counterpart. */
.hero-visual--asympub {
  position: relative;
  overflow: visible;
}
.ap-chip {
  position: absolute;
  left: 67px;
  top: 67px;
  width: 186px;
  height: 186px;
  object-fit: contain;
  filter: var(--icon-shadow-strong);
  z-index: 1;
  animation: asympub-chip 12s ease-in-out infinite;
}
.ap-key {
  position: absolute;
  left: 95px;
  top: 95px;
  width: 130px;
  height: 130px;
  object-fit: contain;
  filter: var(--icon-shadow-strong);
  will-change: transform;
  animation-duration: 12s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
.ap-private { z-index: 2; animation-name: asympub-private; }
.ap-public  { z-index: 3; animation-name: asympub-public; }
/* PRIV / PUB text labels riding on each key. Each label box matches its key's
   box (95px / 130px) and flex-centres the text; it reuses the key's translate
   keyframes so it tracks the key. The public label uses a non-mirrored variant
   (asympub-public-label) so the text stays readable while the key art is flipped. */
.ap-label {
  position: absolute;
  left: 95px;
  top: 95px;
  width: 130px;
  height: 130px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--ink);
  text-shadow: 0 0 3px rgba(255, 255, 255, 0.9), 0 1px 2px rgba(255, 255, 255, 0.7);
  will-change: transform;
  animation-duration: 12s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  pointer-events: none;
}
.ap-label-priv { z-index: 4; animation-name: asympub-private; }
.ap-label-pub  { z-index: 5; animation-name: asympub-public-label; }
/* Inner span tilts the text 45deg along its key axis (PRIV up-right, PUB down-right,
   matching the mirrored key art) and nudges it toward that key's blade so the two
   labels separate and do not overlap where the keys meet on first appearance. */
.ap-label > span { display: inline-block; }
.ap-label-priv > span { transform: translate(-16px, 18px) rotate(-45deg); }
.ap-label-pub  > span { transform: translate(16px, 18px) rotate(45deg); }
/* The public key fans out as copies to three recipients (people, cloud/network,
   code) to show it is meant to be distributed freely, in contrast to the single
   private key kept in the chip. Each mirrored copy shrinks as it travels and
   dissolves into its recipient icon, which fades in on arrival. */
.ap-dest {
  position: absolute;
  width: 77px;
  height: 77px;
  object-fit: contain;
  filter: var(--icon-shadow-strong);
  opacity: 0;
  z-index: 2;
  animation: ap-dest-appear 12s ease-in-out infinite;
}
.ap-dest-users { left: 252px; top: 0; }
.ap-dest-cloud { left: 260px; top: 122px; }
.ap-dest-code  { left: 252px; top: 244px; }
.ap-pub-copy {
  position: absolute;
  left: 95px;
  top: 95px;
  width: 130px;
  height: 130px;
  object-fit: contain;
  filter: var(--icon-shadow-strong);
  opacity: 0;
  z-index: 3;
  will-change: transform;
  animation-duration: 12s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
.ap-pub-b { animation-name: ap-pub-b; }
.ap-pub-c { animation-name: ap-pub-c; }

@keyframes asympub-chip {
  0%   { transform: scale(0.4); opacity: 0; }
  7%   { transform: scale(1);   opacity: 1; }
  42%  { transform: scale(1);   opacity: 1; }
  75%  { transform: scale(1);   opacity: 0; }
  100% { transform: scale(1);   opacity: 0; }
}
@keyframes asympub-private {
  0%, 10% { transform: translate(-13px, 0); opacity: 0; }
  15%     { transform: translate(-13px, 0); opacity: 1; }
  42%     { transform: translate(-13px, 0); opacity: 1; }
  75%     { transform: translate(-13px, 0); opacity: 0; }
  100%    { transform: translate(-13px, 0); opacity: 0; }
}
/* Labeled public key splits from the chip and fans straight out to the cloud
   recipient in one move (no fade, no waypoint), then stays put until the outro. */
@keyframes asympub-public {
  0%, 10% { transform: translate(13px, 0) scaleX(-1) scale(1);    opacity: 0; }
  15%     { transform: translate(13px, 0) scaleX(-1) scale(1);    opacity: 1; }
  22%     { transform: translate(13px, 0) scaleX(-1) scale(1);    opacity: 1; }
  35%     { transform: translate(139px, 0) scaleX(-1) scale(0.5); opacity: 1; }
  42%     { transform: translate(139px, 0) scaleX(-1) scale(0.5); opacity: 1; }
  75%     { transform: translate(139px, 0) scaleX(-1) scale(0.5); opacity: 0; }
  100%    { transform: translate(139px, 0) scaleX(-1) scale(0.5); opacity: 0; }
}
/* PUB label rides the key out of the chip, then peels off as the fan-out starts
   so it never shrinks to an unreadable size. */
@keyframes asympub-public-label {
  0%, 10% { transform: translate(13px, 0) scale(1);   opacity: 0; }
  15%     { transform: translate(13px, 0) scale(1);   opacity: 1; }
  22%     { transform: translate(13px, 0) scale(1);   opacity: 1; }
  29%     { transform: translate(55px, 0) scale(0.7); opacity: 0; }
  100%    { transform: translate(13px, 0) scale(1);   opacity: 0; }
}
/* Copy fans straight out to the users recipient (top-right), then stays put. */
@keyframes ap-pub-b {
  0%, 10% { transform: translate(13px, 0) scaleX(-1) scale(1);        opacity: 0; }
  15%     { transform: translate(13px, 0) scaleX(-1) scale(1);        opacity: 1; }
  22%     { transform: translate(13px, 0) scaleX(-1) scale(1);        opacity: 1; }
  35%     { transform: translate(131px, -122px) scaleX(-1) scale(0.5); opacity: 1; }
  42%     { transform: translate(131px, -122px) scaleX(-1) scale(0.5); opacity: 1; }
  75%     { transform: translate(131px, -122px) scaleX(-1) scale(0.5); opacity: 0; }
  100%    { transform: translate(131px, -122px) scaleX(-1) scale(0.5); opacity: 0; }
}
/* Copy fans straight out to the code recipient (bottom-right), then stays put. */
@keyframes ap-pub-c {
  0%, 10% { transform: translate(13px, 0) scaleX(-1) scale(1);       opacity: 0; }
  15%     { transform: translate(13px, 0) scaleX(-1) scale(1);       opacity: 1; }
  22%     { transform: translate(13px, 0) scaleX(-1) scale(1);       opacity: 1; }
  35%     { transform: translate(131px, 122px) scaleX(-1) scale(0.5); opacity: 1; }
  42%     { transform: translate(131px, 122px) scaleX(-1) scale(0.5); opacity: 1; }
  75%     { transform: translate(131px, 122px) scaleX(-1) scale(0.5); opacity: 0; }
  100%    { transform: translate(131px, 122px) scaleX(-1) scale(0.5); opacity: 0; }
}
/* Each recipient fades in as its key arrives, holds while all three are fanned
   out, then fades out slowly with everything in the outro. */
@keyframes ap-dest-appear {
  0%, 22% { opacity: 0; }
  30%     { opacity: 1; }
  42%     { opacity: 1; }
  75%     { opacity: 0; }
  100%    { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .ap-chip { animation: none; transform: scale(1); opacity: 1; }
  .ap-key { animation: none; }
  .ap-private { opacity: 1; }
  .ap-public { opacity: 0; }
  .ap-label { animation: none; }
  .ap-label-priv { opacity: 1; transform: translate(-13px, 0); }
  .ap-label-pub { opacity: 0; }
  .ap-dest { animation: none; opacity: 1; }
  .ap-pub-copy { animation: none; opacity: 0; }
}

/* ---------- Hero side visual: device lifecycle (used on
   /key-management/fieldbus-device/) ----------
   A big teal cycle-arrow (the lifecycle) turns slowly clockwise in the
   background, one revolution per loop. The chip device fades in and stays. A key
   moves in and seats into it (credentials), and stays there while the stage icons
   pass over the device: a handshake (handover), then an orange gear turning left
   at the arrow's pace (operation and config). Shortly before the end the key
   leaves, a trash icon fades in, the chip zooms down into it (end of life, keys
   destroyed), the trash fades slowly, and the loop pauses before starting over. */
.hero-visual--devlife {
  position: relative;
  overflow: visible;
}
.dl-arrow {
  position: absolute;
  left: 10px;
  top: 10px;
  width: 300px;
  height: 300px;
  object-fit: contain;
  z-index: 1;
  animation: dl-arrow-spin 18s linear infinite;
}
.dl-stage {
  position: absolute;
  left: 100px;
  top: 100px;
  width: 120px;
  height: 120px;
  object-fit: contain;
  z-index: 2;
  filter: var(--icon-shadow-strong);
  will-change: transform, opacity;
  animation-duration: 18s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}
.dl-chip      { left: 85px; top: 85px; width: 150px; height: 150px; animation-name: dl-chip; }
.dl-keyin     { animation-name: dl-keyin; }
.dl-handshake { animation-name: dl-handshake; }
.dl-trash     { animation-name: dl-trash; }
.dl-gear {
  left: 115px;
  top: 115px;
  width: 90px;
  height: 90px;
  animation-name: dl-gear-fade, dl-gear-spin;
  animation-duration: 18s, 18s;
  animation-timing-function: ease-in-out, linear;
  animation-iteration-count: infinite, infinite;
}

@keyframes dl-arrow-spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }
@keyframes dl-gear-spin  { from { transform: rotate(0); } to { transform: rotate(-360deg); } }
@keyframes dl-chip {
  0%, 2% { transform: scale(1); opacity: 0; }
  7%     { transform: scale(1); opacity: 1; }
  72%    { transform: scale(1); opacity: 1; }
  78%    { transform: translateY(18px) scale(0.12); opacity: 0; }
  79%    { transform: scale(1); opacity: 0; }
  100%   { transform: scale(1); opacity: 0; }
}
@keyframes dl-keyin {
  0%, 8%  { transform: translate(-90px, 0);   opacity: 0; }
  12%     { transform: translate(0, 0);        opacity: 1; }
  64%     { transform: translate(0, 0);        opacity: 1; }
  70%     { transform: translate(95px, -72px); opacity: 0; }
  71%     { transform: translate(-90px, 0);    opacity: 0; }
  100%    { transform: translate(-90px, 0);    opacity: 0; }
}
@keyframes dl-handshake {
  0%, 20% { opacity: 0; }
  25%     { opacity: 1; }
  33%     { opacity: 1; }
  37%     { opacity: 0; }
  100%    { opacity: 0; }
}
@keyframes dl-gear-fade {
  0%, 34% { opacity: 0; }
  39%     { opacity: 1; }
  56%     { opacity: 1; }
  60%     { opacity: 0; }
  100%    { opacity: 0; }
}
@keyframes dl-trash {
  0%, 71% { opacity: 0; }
  75%     { opacity: 1; }
  78%     { opacity: 1; }
  89%     { opacity: 0; }
  100%    { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .dl-arrow, .dl-stage { animation: none; }
  .dl-chip { opacity: 1; }
  .dl-keyin { opacity: 1; transform: translate(0, 0); }
  .dl-handshake, .dl-gear, .dl-trash { opacity: 0; }
}

.hero-visual--risk-meter > .hv-center.hv-center--plain {
  width: 89%;
  animation: risk-meter-pulse 0.9s ease-in-out 1;
}
@keyframes risk-meter-pulse {
  0%, 100% { transform: translate(-50%, -50%) scale(1);    }
  50%      { transform: translate(-50%, -50%) scale(1.08); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-visual--risk-meter > .hv-center.hv-center--plain { animation: none; }
}

/* ---------- Hero side visual: DB9 plug with multimeter orbiting (used on /threats/physical-access/) ----------
   Single multimeter icon orbits the centered DB9 plug counter-clockwise on the
   hv-orbit / hv-trail recipe (see .claude/ORBIT_TRAILS.md). Pivot is upper-right of
   container centre (50% + 20px, 50% - 20px), icon sits at top-right so it lands at
   compass 45deg from the pivot, orbit radius ~116 px at W=320. Trail extends
   clockwise of the icon (CCW spin means the comet streams CW behind the icon):
   conic from 45deg with bright stop at 0deg, fading to transparent at 90deg.
   Annulus stays inside .hero-visual so inset: 0 is enough. Gold matches the
   landing-page hero palette. */
.hero-visual--physical-orbit > .hv-center.hv-center--plain {
  width: 45%;
}
.hv-orbit-pa {
  transform-origin: calc(50% + 20px) calc(50% - 20px);
  animation: hv-orbit-rot-ccw 40s linear infinite;
}
.hv-icon-pa {
  position: absolute;
  width: 104px;
  height: 104px;
  top: 6%;
  right: 6%;
  object-fit: contain;
  /* tier: strong — orbiting icon. */
  filter: var(--icon-shadow-strong);
  animation: hv-counter-cw 40s linear infinite;
}
/* Icon +33% (78 → 104 px) shrinks the orbit radius because the icon's anchor
   stays at top/right: 6% and its larger half-size pulls the centre inward
   toward the container centre. New centre (248.8, 71.2), pivot (180, 140),
   r ≈ 97 px, compass θ = 45°, conic from = 315°. (Sweep stop order kept
   matching the CCW orbit direction.) */
.hv-trail-pa {
  background: conic-gradient(from 45deg at calc(50% + 20px) calc(50% - 20px),
              rgba(240, 162, 46, 0.55) 0deg,
              rgba(240, 162, 46, 0)    90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% + 20px) calc(50% - 20px),
                transparent 91px, #000 94px, #000 100px, transparent 103px);
          mask: radial-gradient(circle at calc(50% + 20px) calc(50% - 20px),
                transparent 91px, #000 94px, #000 100px, transparent 103px);
}
@media (prefers-reduced-motion: reduce) {
  .hv-orbit-pa,
  .hv-icon-pa { animation: none; }
}

/* ---------- Hero side visual: gate alternating open/locked (used on /threats/remote-attack/) ----------
   Two stacked images cross-fade continuously so the gate appears to open and close. */
.hero-visual--gate-cycle { position: relative; }
.gate-img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 78%;
  height: auto;
  object-fit: contain;
  transform: translate(-50%, -50%);
}
.gate-open   { animation: gate-cycle-open   4s ease-in-out infinite; }
.gate-locked { animation: gate-cycle-locked 4s ease-in-out infinite; }
@keyframes gate-cycle-open {
  0%, 25%, 100% { opacity: 1; }
  50%, 75%      { opacity: 0; }
}
@keyframes gate-cycle-locked {
  0%, 25%, 100% { opacity: 0; }
  50%, 75%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .gate-open,
  .gate-locked { animation: none; }
  .gate-locked { opacity: 1; }
  .gate-open   { opacity: 0; }
}

/* ---------- Hero side visual: hacker breaching the network (used on /threats/protocol-weaknesses/) ----------
   network_attention sits centred. The hacker rests bottom-right, then slides
   toward the network while shrinking dramatically (going "into" the system),
   then reverses out. Smooth ease-in-out loop. */
.hero-visual--breach { overflow: hidden; }
.breach-network {
  position: absolute;
  top: 76%;
  left: 50%;
  width: 52%;
  height: auto;
  object-fit: contain;
  transform: translate(-50%, -50%);
}
.breach-hacker {
  position: absolute;
  top: 6%;
  right: 6%;
  width: 36%;
  height: auto;
  object-fit: contain;
  transform-origin: center;
  /* tier: strong — hacker icon shrinks and moves toward the centre. */
  filter: var(--icon-shadow-strong);
  animation: breach-hacker 8.75s ease-in-out infinite;
}
@keyframes breach-hacker {
  0%, 100% { transform: translate(0, 0)        scale(1);    }
  50%      { transform: translate(-72%,  90%)  scale(0.24); }
}
@media (prefers-reduced-motion: reduce) {
  .breach-hacker { animation: none; }
}

/* ---------- Hero side visual: converge (used on /solutions/secure-object-fieldbus-access/) ----------
   Static network_attention background fills most of the square. A larger
   key-exchange handshake icon starts near the top-right corner and travels
   diagonally toward the centre while shrinking to half its starting size,
   landing on top of the network icon. Single-shot on load, no looping. */
.converge-bg {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 88%;
  height: 88%;
  object-fit: contain;
  transform: translate(-50%, -50%);
  /* tier: soft — static background icon beneath the converging fly-in. */
  filter: var(--icon-shadow-soft);
}
.converge-fly {
  position: absolute;
  top: 5%;
  left: 85%;
  width: 60%;
  height: auto;
  object-fit: contain;
  transform-origin: center;
  transform: translate(-50%, -50%) scale(1);
  /* tier: strong — handshake icon flies in from top-right and shrinks. */
  filter: var(--icon-shadow-strong);
  animation: converge-fly 5s ease-in-out infinite;
}
@keyframes converge-fly {
  0%   { top: 5%;  left: 85%; transform: translate(-50%, -50%) scale(1);   opacity: 0; }
  10%  { top: 5%;  left: 85%; transform: translate(-50%, -50%) scale(1);   opacity: 1; }
  40%  { top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.5); opacity: 1; }
  70%  { top: 5%;  left: 85%; transform: translate(-50%, -50%) scale(1);   opacity: 1; }
  80%  { top: 5%;  left: 85%; transform: translate(-50%, -50%) scale(1);   opacity: 0; }
  100% { top: 5%;  left: 85%; transform: translate(-50%, -50%) scale(1);   opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .converge-fly {
    animation: none;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.5);
    opacity: 1;
  }
}

/* ---------- Unbulleted list with teal chevron markers (use anywhere a bare ul looks too casual) ---------- */
ul.bulletless {
  list-style: none;
  margin: var(--space-2) 0 var(--space-4);
  padding-left: 0;
}
ul.bulletless > li {
  position: relative;
  padding-left: 20px;
  margin-bottom: var(--space-3);
  line-height: var(--lh-relaxed);
}
ul.bulletless > li::before {
  content: "\203A";
  position: absolute;
  left: 4px;
  top: -2px;
  color: var(--teal);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.3em;
}
ul.bulletless > li:last-child {
  margin-bottom: 0;
}

/* ---------- Scope-notice box (used where a section is explicitly out of scope) ----------
   Single full-width prominent block with a thicker gold left bar to distinguish it
   from the teal mitigation cards. */
.scope-notice {
  background: var(--teal-light);
  border-left: 6px solid var(--gold);
  border-radius: var(--radius-sm);
  padding: var(--space-5) var(--space-6);
  margin: var(--space-6) 0 var(--space-8);
}
.scope-notice > h3 {
  margin-top: 0;
  margin-bottom: var(--space-3);
  color: var(--teal-deep);
}
.scope-notice > p:last-child {
  margin-bottom: 0;
}
.scope-notice a {
  color: var(--teal-dark);
}

/* ---------- Mitigation cards (used on /threats/physical-access/) ----------
   Compact callout grid. Each card has the teal-light backdrop reserved for
   callout-style components and a teal-deep title. */
.mitigation-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin: var(--space-4) 0 var(--space-8);
}
@media (min-width: 700px) {
  .mitigation-grid {
    grid-template-columns: 1fr 1fr;
  }
}
.mitigation {
  background: var(--teal-light);
  border-left: 4px solid var(--teal);
  border-radius: var(--radius-sm);
  padding: var(--space-4) var(--space-5);
}
.mitigation > h3 {
  margin-top: 0;
  margin-bottom: var(--space-2);
  color: var(--teal-deep);
}
.mitigation > p:last-child {
  margin-bottom: 0;
}
.mitigation a {
  color: var(--teal-dark);
}

/* ---------- Table footnote (small explanatory line below a table) ---------- */
table:has(+ p.table-footnote) {
  margin-bottom: var(--space-2);
}
.table-footnote {
  margin-top: 0;
  margin-bottom: var(--space-8);
  font-size: var(--text-sm);
  color: var(--grey-700);
  line-height: var(--lh-snug);
}
.table-footnote sup {
  margin-right: 2px;
}

/* ---------- Hero side visual: machinery enclosure with lock-gear orbiting (used on /defense-in-depth/access-limitation/) ----------
   Single lock_gear_config icon orbits the centred machinery image clockwise.
   Pivot is offset upper-right of the container centre by 25% of the icon's
   width (20 px) in each axis. Orbit radius ~123 px at W=320, fully inside the
   container, so the trail uses inset: 0. Icon compass angle from pivot is 45°,
   so the conic `from` is 45 − 90 = −45° (315°). See .claude/ORBIT_TRAILS.md. */
.hero-visual--access-orbit > .hv-center.hv-center--plain {
  width: 78%;
}
.hv-orbit-access {
  transform-origin: calc(50% + 20px) calc(50% - 20px);
  animation: hv-orbit-rot 40s linear infinite;
}
.hv-icon-access {
  width: 80px;
  height: 80px;
  top: 4%;
  right: 4%;
  animation-duration: 40s;
}
.hv-trail-access {
  background: conic-gradient(from 315deg at calc(50% + 20px) calc(50% - 20px),
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% + 20px) calc(50% - 20px),
                transparent 119px, #000 121px, #000 126px, transparent 128px);
          mask: radial-gradient(circle at calc(50% + 20px) calc(50% - 20px),
                transparent 119px, #000 121px, #000 126px, transparent 128px);
}

/* ---------- Hero side visual: router with cloud + gate icons orbiting (used on /defense-in-depth/secure-gateways/) ----------
   Two orbiting icons around the centred router. cloud_wlan_hotspot orbits in
   the upper-left quadrant (wireless / cloud side); gate_gateway_locked orbits
   in the lower-right (on-prem / gate side). Each has its own pivot, period
   and orbit radius; both trails fit inside the container so inset: 0. Compass
   angles: cloud at 315° (upper-left from pivot), gate at 135° (lower-right
   from pivot); conic `from` = θ − 90°. See .claude/ORBIT_TRAILS.md. */
.hero-visual--gateways-orbit > .hv-center.hv-center--plain {
  width: 70%;
}
.hv-orbit-sg-cloud {
  transform-origin: calc(50% - 19.5px) calc(50% - 19.5px);
  animation: hv-orbit-rot 38s linear infinite;
}
.hv-icon-sg-cloud {
  width: 78px;
  height: 78px;
  top: 4%;
  left: 4%;
  animation-duration: 38s;
}
.hv-trail-sg-cloud {
  background: conic-gradient(from 225deg at calc(50% - 19.5px) calc(50% - 19.5px),
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% - 19.5px) calc(50% - 19.5px),
                transparent 121px, #000 123px, #000 128px, transparent 130px);
          mask: radial-gradient(circle at calc(50% - 19.5px) calc(50% - 19.5px),
                transparent 121px, #000 123px, #000 128px, transparent 130px);
}
.hv-orbit-sg-gate {
  transform-origin: calc(50% + 20.5px) calc(50% + 20.5px);
  animation: hv-orbit-rot 47s linear infinite;
}
.hv-icon-sg-gate {
  width: 82px;
  height: 82px;
  bottom: 4%;
  right: 4%;
  animation-duration: 47s;
}
.hv-trail-sg-gate {
  background: conic-gradient(from 45deg at calc(50% + 20.5px) calc(50% + 20.5px),
              rgba(240, 162, 46, 0)    0deg,
              rgba(240, 162, 46, 0.55) 90deg,
              transparent              90.01deg);
  -webkit-mask: radial-gradient(circle at calc(50% + 20.5px) calc(50% + 20.5px),
                transparent 117px, #000 119px, #000 124px, transparent 126px);
          mask: radial-gradient(circle at calc(50% + 20.5px) calc(50% + 20.5px),
                transparent 117px, #000 119px, #000 124px, transparent 126px);
}
