/* ==================== tokens.css ====================
   Design tokens: color/spacing/font variables. Loads first. */
/*
  Breakpoint scale (documented here, applied as literal px values in each
  component file - custom properties can't drive @media conditions):
  1200px - nav collapses to hamburger, tile grids drop to fewer columns
  800px  - tile grids stack to a single column, carousels show one slide
  600px  - type scale and spacing tighten for small screens
*/

:root {
  --color-primary: #c10a27;
  --color-primary-dark: #8c0a27;
  --color-teal: #00839e;
  --color-teal-light: #00a4c0;
  --color-text: #555555;
  --color-heading: #555555;
  --color-bg-light: #f2f2f2;
  --color-bg-lighter: #ededed;
  --color-border: #e5e5e5;
  --color-white: #ffffff;

  --font-family-base: brandon-grotesque, "Segoe UI", Arial, sans-serif;

  --space-xs: 0.5rem;
  --space-sm: 1rem;
  --space-md: 1.5rem;
  --space-lg: 2.5rem;
  --space-xl: 4rem;
}

html {
  overflow-x: hidden;
}

body {
  font-family: var(--font-family-base);
  color: var(--color-text);
}

h1, h2, h3, h4 {
  color: var(--color-heading);
  font-family: var(--font-family-base);
}

/* Leading "_" as a purely visual prefix, not real text - generated via
   ::before/content so it isn't part of the actual DOM text a search
   engine (or a screen reader by default) reads, unlike the real site's
   own underscore which is literal scraped text in the heading string.

   Scoped to this one class, NOT a blanket h1-h4 rule: an exhaustive crawl
   of the real live site (every static page, every subpage, all 18 news
   articles, via curl + heading-text extraction) found the underscore is
   genuinely per-heading, not tied to tag level or page pattern - e.g. the
   home page's hero h1 and every tile-grid title carry it, but /esg/,
   /job/, hvem-er-vi's own intro h1, multifaktorgodkendelse's h1, and
   several news articles' h1s have no underscore at all on the real site;
   conversely a plain h2 subheading on it-outsourcing/service-desk/ and
   the embedded contact-form module's "Kontakt os" heading both do. A
   previous attempt applied this to every h1-h4 site-wide, which was
   wrong - only add this class to a heading after confirming its real,
   exact-text counterpart actually has the underscore. */
.heading-underscore::before {
  content: "_";
}

a {
  color: var(--color-primary);
  font-family: var(--font-family-base);
  font-weight: 600;
  font-style: normal;
}

/* Browsers give form controls their own UA-default font (confirmed via
   getComputedStyle: every <button> on the site - the contact form's
   submit button, the carousel's prev/next buttons, the mega-nav arrow
   toggles, the mobile hamburger, the support overlay's close button -
   computed plain "Arial" instead of the site's own brandon-grotesque
   stack) rather than inheriting from their ancestors like normal text
   elements do. font-family:inherit forces it to actually inherit,
   sitewide, for every button regardless of where it sits in the
   markup - not just the one reported in the contact form. */
button {
  font-family: inherit;
}

/* ==================== site.css ====================
   Original pre-redesign scaffold - contains several leftover
   Bootstrap-scaffold rules (documented extensively in CLAUDE.md's
   history) that layout.css/components.css below deliberately
   override on load-order alone, not specificity. Load order
   (tokens -> site -> layout -> components) is settled - see the
   Tenth pass in CLAUDE.md. Do not reorder these sections. */
html {
  font-size: 14px;
  position: relative;
  min-height: 100%;
}

@media (min-width: 768px) {
  html {
    font-size: 16px;
  }
}

body {
  margin-bottom: 60px;
}

.site-header__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
}

.site-header__brand {
  font-weight: bold;
  text-decoration: none;
}

.site-header__links {
  display: flex;
  gap: 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-main {
  max-width: 60rem;
  margin: 0 auto;
  padding: 1.5rem;
}

.site-footer {
  text-align: center;
  padding: 1.5rem;
  color: #666;
}

.hero__title {
  font-size: 1.75rem;
}

.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.5rem;
  margin: 2rem 0;
}

.tile-grid__item {
  border: 1px solid #e5e5e5;
  border-radius: 0.25rem;
  padding: 1.25rem;
}

.content-page__list {
  padding-left: 2.5rem;
  font-size: 1.3rem;
  line-height: 1.625rem;
}

.content-page__list-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
  list-style: none;
}

.content-page__avatar {
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

.news-list {
  list-style: none;
  padding: 0;
}

.news-list__item {
  border-bottom: 1px solid #e5e5e5;
  padding: 1rem 0;
}

.news-list__link {
  text-decoration: none;
  color: inherit;
  display: block;
}

.contact-form {
  margin: 2rem 0;
  padding: 1.5rem;
  border: 1px solid #e5e5e5;
  border-radius: 0.25rem;
  max-width: 30rem;
}

.contact-form__form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.contact-form__field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.contact-form__input,
.contact-form__textarea {
  padding: 0.85rem;
  /* 2px width, matching the invalid state's own border-width below -
     white, so it's invisible against the input's own white fill in the
     default state (already visually distinct from the surrounding
     colored form background without needing a visible outline of its
     own). Only the *color* changes to signal an error - the box's own
     size never does, which is what used to cause a small layout shift
     (1px -> 2px) every time a field's validation state changed. */
  border: 2px solid var(--color-white);
  border-radius: 0.25rem;
  font: inherit;
}

.contact-form__textarea {
  min-height: 6rem;
  resize: vertical;
}

.contact-form__submit {
  align-self: flex-start;
  padding: 0.5rem 1.5rem;
  border: none;
  border-radius: 0.25rem;
  background-color: #1b6ec2;
  color: #fff;
  cursor: pointer;
}

.contact-form__status {
  margin: 0;
}
/* ==================== layout.css ====================
   Header/nav/footer/support-overlay chrome. */
/* Header / mega-nav / support overlay / footer chrome */

/* The notification banner and the header stick together as one unit -
   .site-sticky-top (their shared wrapper, _Layout.cshtml) carries the
   sticky positioning so both scroll together and then both stay pinned,
   rather than each needing its own offset computed against the other's
   (variable, text-length-dependent) height. */
.site-sticky-top {
  position: sticky;
  top: 0;
  z-index: 100;
}

.site-header {
  /* Not sticky itself anymore (.site-sticky-top, the shared wrapper with
     the notification banner, owns that now) - but .site-header__support
     is position:absolute; top:0; right:0, and needs *this* element as its
     positioned containing block, not the outer wrapper (which would land
     it at the wrapper's own top, i.e. overlapping the banner instead of
     sitting in the header's own top-right corner). */
  position: relative;
  background-color: var(--color-white);
}

/* Only while actually scrolled (nav.js toggles this once window.scrollY
   > 0) - not permanently, which is what the Eleventh pass's box-shadow
   removal was about (a blur-radius shadow bleeds a faint line around
   *every* edge, not just the offset side, and with nothing above the
   header at the very top of the page that read as a stray border rather
   than a real shadow). The negative third value here (spread) shrinks the
   shadow's source box inward before the blur expands it back out, so the
   visible result is concentrated below the header instead of haloing its
   top/side edges the same way. */
.site-header--scrolled {
  box-shadow: 0 4px 6px -4px rgba(0, 0, 0, 0.2);
}

.site-header__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: 0 0 0 1.5rem;
}

.site-header__logo {
  color: #000000;
  /* .site-header__logo is a block-level anchor wrapping an inline SVG -
     without this, the anchor's own line box leaves the classic "inline
     replaced element" baseline/descender gap below the SVG (an 87px-tall
     box around an 80px-tall SVG, the extra 7px sitting entirely below it,
     flush with the SVG's own top). Centering via .site-header__bar's
     align-items:center then centers that *inflated* box, not the actual
     visible logo artwork - visibly landing the logo higher than true
     center. display:flex removes the line-box/baseline model entirely,
     shrinking the anchor to the SVG's true 80px height so
     align-items:center centers what's actually visible. Previously only
     applied on mobile (same 180x80 box there, so the same fix - this was
     never actually mobile-specific, just never carried over to desktop). */
  display: flex;
  align-items: center;
}

.site-header__logo svg {
  width: 11.25rem;
  height: 5rem;
  /* Deliberately no translateY here, unlike the old mobile-only rule this
     was merged from: a CSS transform moves the whole rendered box (ink
     included) as one rigid unit, so it can't actually correct the ink's
     position *relative to its own box* - it only relocates the box
     itself. Measured directly (2026-09-07): with the box-centering fix
     above alone, the SVG's own box already lands almost exactly centered
     in the header bar (11.19px clearance top and bottom, in a bar built
     around this exact 80px-tall box) - adding the old translateY on top
     just pushes the whole already-centered box downward again, visibly
     overshooting. Whatever mobile's translateY was compensating for was
     evidently specific to mobile's own box-centering not landing as
     cleanly - don't reintroduce it here without re-measuring mobile
     itself first. */
}

/* justify-content:flex-start (never previously declared here) explicitly
   overrides site.css's leftover Bootstrap-scaffold `.site-header__nav {
   justify-content: space-between; }` (line 97-102, pre-redesign, never
   removed) - harmless for as long as this element's own height always
   matched its content's height exactly (nothing for space-between to
   distribute), which was true right up until .site-header__nav--open
   became a fixed, full-viewport-height mobile overlay: with real extra
   vertical space to work with, that leftover space-between suddenly
   spread the top-nav and mega-nav lists to the top/bottom of the whole
   overlay instead of stacking them together, which is what "align to
   the top, stacked on top of each other" is asking to restore. */
.site-header__nav {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 1rem;
  padding: 0 0 0.8rem 0;
  flex: 0 0 auto;
}

.site-header__support {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 1;
  display: flex;
  align-items: stretch;
  height: 2.5rem;
  width: auto;
  border: none;
  border-radius: 0;
  overflow: hidden;
  cursor: pointer;
  padding: 0;
  font-family: var(--font-family-base);
}

.site-header__support-label,
.site-header__support-phone {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 0.75rem;
  color: var(--color-white);
  font-weight: 700;
  text-transform: uppercase;
  font-size: 1.25rem;
  line-height: 1;
  text-decoration: none;
}

.site-header__support-label:hover,
.site-header__support-phone:hover {
  color: var(--color-white);
}

.site-header__support-label {
  flex: 0 0 7rem;
}

.site-header__support-label {
  background-color: var(--color-primary);
}

.site-header__support-phone {
  background-color: var(--color-primary-dark);
}

.site-header__top-nav {
  display: flex;
  justify-content: flex-start;
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  /* .site-header__nav's box is sized to mega-nav (the wider row) and
     flush against .site-header__support's right edge (Fortieth pass) -
     since top-nav is narrower than mega-nav but was still left-aligned
     within that same (mega-nav-width) box, its own right edge landed
     underneath the support pill, hiding the last couple of links behind
     it. Taking top-nav out of flow (position:absolute) and anchoring it
     directly, rather than using margin, avoids feeding back into nav's
     own shrink-to-fit width calculation - a margin-right here would
     count toward top-nav's outer size and widen .site-header__nav itself
     (which happened when this was first tried), in turn pulling
     mega-nav's right edge back out of alignment with support again.
     The 19rem offset clears support's own measured width (18.5625rem)
     plus a small gap - re-measure if support's content ever changes. */
  position: absolute;
  top: 0;
  right: 19rem;
  /* With only `right` set (no `left`), auto width shrink-to-fits against
     the *remaining* space in the containing block (.site-header__nav,
     only ~609px wide) rather than against the content's own natural
     size - at this width that's narrower than the links need, wrapping
     "Kontakt os" onto two lines. Forcing the content-based width
     sidesteps that constrained calculation entirely. */
  width: max-content;
}

.site-header__top-link {
  display: block;
  padding: 0.85rem 1.5rem;
  color: var(--color-text);
  text-decoration: none;
  text-transform: uppercase;
  font-size: 0.8rem;
  line-height: 1;
}

.site-header__top-link:hover {
  color: var(--color-primary-dark);
}

.site-header__mega-nav {
  display: flex;
  justify-content: flex-start;
  gap: 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
  font-weight: 600;
  text-transform: uppercase;
  font-size: 1rem;
  /* top-nav is position:absolute (see above) and no longer occupies flow
     space in .site-header__nav's column, so this restores the vertical
     gap between the two rows that .site-header__nav's own `gap` used to
     provide (top-nav's own height, 2.5rem, plus the 1rem row gap). */
  margin-top: 3.5rem;
}

.site-header__mega-item {
  position: relative;
}

.site-header__mega-trigger-row {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.site-header__mega-trigger {
  display: block;
  color: var(--color-text);
  font: inherit;
  font-weight: 600;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  padding: 0.3rem 0;
}

.site-header__mega-trigger:hover {
  color: var(--color-primary-dark);
}

.site-header__mega-arrow-toggle {
  display: flex;
  align-items: center;
  background: none;
  border: none;
  margin: 0;
  padding: 0.3rem;
  color: inherit;
  cursor: pointer;
}

/* .site-header__mega-nav's own box already ends flush with .site-header__support's
   right edge (both ultimately anchored to .site-header__bar's right edge), so
   the only remaining gap is each element's own trailing padding - matching the
   arrow-toggle's right padding to the support-phone span's (0.75rem) makes the
   visible arrow icon's right edge land exactly on the phone number's last
   digit, at any desktop width, without hardcoding a pixel offset. */
.site-header__mega-item:last-child .site-header__mega-arrow-toggle {
  padding-right: 0.75rem;
}

/* Real site's top-nav active link (`.TopActive`, confirmed via
   getComputedStyle on the live /nyheder/ page) shares this exact
   underline treatment with the mega-nav's own active state - same
   selector list rather than a duplicate rule. */
.site-header__mega-trigger--active,
.site-header__top-link--active {
  text-decoration: underline;
  text-decoration-color: var(--color-primary-dark);
  text-decoration-thickness: 2px;
}

.site-header__mega-arrow {
  width: 0.6rem;
  height: 0.6rem;
  transition: transform 0.15s ease;
}

.site-header__mega-item--open .site-header__mega-arrow {
  transform: rotate(180deg);
}

.site-header__mega-panel {
  display: none;
  position: absolute;
  top: 100%;
  /* .site-header__mega-item's own right edge is the arrow-toggle button's
     outer edge, which is 0.3rem further right than the arrow icon itself
     (the button's own padding) - offset by that same amount so the panel's
     right edge lands on the visible arrow, not the invisible button box. */
  right: 0.3rem;
  min-width: 14rem;
  background-color: var(--color-white);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  border-radius: 0.25rem;
  list-style: none;
  margin: 0;
  padding: 0.5rem 0;
  z-index: 10;
}

/* The last item's arrow-toggle has extra right padding (0.75rem, tuned to
   meet the support phone number's last digit) - match it here too. */
.site-header__mega-item:last-child .site-header__mega-panel {
  right: 0.75rem;
}

.site-header__mega-item--open .site-header__mega-panel {
  display: block;
}

@media (hover: hover) {
  .site-header__mega-item:hover .site-header__mega-panel {
    display: block;
  }
}

.site-header__mega-link {
  display: block;
  /* Right padding = trigger-row gap (0.25rem) + arrow-toggle's own
     left padding (0.3rem) + arrow icon width (0.6rem) = 1.15rem - since
     the panel's right edge already sits flush with the arrow icon's own
     right edge, this exactly cancels the distance back to the trigger
     label's right edge, so the right-aligned text below lines up with
     the trigger text above it, not just with the arrow. */
  padding: 0.3rem 1.15rem 0.3rem 0.625rem;
  color: var(--color-text);
  text-decoration: none;
  text-transform: uppercase;
  font-weight: 400;
  white-space: nowrap;
  text-align: right;
}

.site-header__mega-link:hover {
  color: var(--color-primary-dark);
  background-color: var(--color-bg-light);
}

.site-header__mega-link--active {
  color: var(--color-primary-dark);
}

.site-header__toggle {
  display: none;
  flex-direction: column;
  gap: 0.3rem;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.5rem;
}

.site-header__toggle-bar {
  width: 1.6rem;
  height: 0.15rem;
  background-color: var(--color-heading);
  transition: transform 0.2s ease, opacity 0.2s ease;
}

@media (max-width: 1200px) {
  /* The support bar is a sibling of .site-header__bar (not inside the
     collapsible nav), so it renders unconditionally - open or closed -
     matching the real site's mobile header, which shows it as a fixed
     top strip regardless of menu state. */
  .site-header__support {
    position: static;
    width: 100%;
    height: 3.125rem;
  }

  .site-header__support-label,
  .site-header__support-phone {
    flex: 1 1 50%;
  }

  .site-header__toggle {
    display: flex;
  }

  /* .site-header__nav lives inside the same flex row as the logo and
     toggle button - when opened, force it onto its own full-width row
     within that row (rather than escaping via position:absolute) so it
     pushes the rest of the page down, matching the real mobile menu.
     .site-header__bar's own padding-left (1.5rem, desktop's logo inset)
     would otherwise carry over into the opened nav row too, stacking on
     top of the lists' own 40px indent below and landing items 24px
     further right than the real page's exact 40px. Moving that inset
     onto the logo directly and zeroing it here keeps the logo in the
     same visual spot while decoupling the nav row from it. */
  .site-header__bar {
    flex-wrap: wrap;
    padding-left: 0;
    /* Moves the toggle button in from the true right edge - it had no
       inset at all (bar's own padding-right was 0), unlike the logo's
       symmetric 1.5rem inset on the left. */
    padding-right: 1rem;
  }

  .site-header__logo {
    /* display:flex/align-items:center and the svg's own translateY are
       now set in the base (non-media-query) rule above - this box size
       (180x80) is identical on mobile, so nothing here needs to redeclare
       them anymore. Only this inset is genuinely mobile-specific. */
    margin-left: 1.5rem;
  }

  .site-header__toggle[aria-expanded="true"] .site-header__toggle-bar:nth-child(1) {
    transform: translateY(0.45rem) rotate(45deg);
  }

  .site-header__toggle[aria-expanded="true"] .site-header__toggle-bar:nth-child(2) {
    opacity: 0;
  }

  .site-header__toggle[aria-expanded="true"] .site-header__toggle-bar:nth-child(3) {
    transform: translateY(-0.45rem) rotate(-45deg);
  }

  /* In-flow expansion (pushes page content down), not a floating
     dropdown card - matches the real mobile menu's own behavior. */
  .site-header__nav {
    display: none;
    /* Real menu's horizontal inset comes entirely from each list's own
       padding-left (see below), not from the container - confirmed via
       getComputedStyle: .navItems itself has zero padding, and both
       .upper/.lower read exactly 40px, matching the browser's default
       <ul> padding-inline-start (both lists reset list-style/margin but
       apparently not padding at this breakpoint on the real site).
       Zeroing this container's own horizontal padding avoids stacking a
       second inset on top of that. */
    padding: var(--space-md) 0;
    align-items: stretch;
    /* Real mobile menu (.navItems .upper { margin-bottom:30px }) puts a
       visibly bigger gap between the plain-link group and the section
       group than between items within either group - this is that gap. */
    gap: 1.875rem;
  }

  /* Full-viewport overlay when open (previously an in-flow expansion
     that pushed page content down) - explicit request. Positioned below
     the header itself (support bar + logo/toggle row) rather than
     covering it, so the toggle - already showing as an X via the
     rotated-bars rules above - stays reachable to close the menu.
     --mobile-nav-top is set by nav.js from the header's own rendered
     height at the moment the menu opens: by then the header has already
     shrunk back to its closed-state height, since this nav no longer
     occupies flow space in it once it's fixed rather than in-flow.
     overflow here (the page's own scroll is locked separately, below)
     means a menu taller than the remaining viewport scrolls within
     itself instead of the page scrolling underneath it. */
  .site-header__nav--open {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: var(--mobile-nav-top, 6.75rem);
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0;
    background-color: var(--color-white);
    overflow: hidden auto;
    z-index: 50;
  }

  /* Locks the page's own scroll while the full-viewport overlay above is
     open, so only the nav's own overflow (set on the rule above) scrolls -
     otherwise both the page underneath and the menu on top would scroll
     together, which reads as broken/janky on touch. CSS-only via :has(),
     no JS needed. <html>, not <body>, is what actually needs this: <html>
     already carries its own explicit overflow-x (hidden, for the site's
     various 100vw full-bleed sections - see tokens.css), and per the CSS
     Overflow spec a root element with a non-"visible" overflow of its own
     breaks the usual body-to-viewport propagation - so <html> is the
     actual scrolling element in this codebase, not <body>. Scoped inside
     this same media query so it only ever engages at the width where
     .site-header__nav--open means "full-viewport overlay" in the first
     place - at desktop width the class can still be present in the DOM
     (nav.js doesn't check viewport width before toggling it) but means
     nothing there, and shouldn't lock scroll either. */
  html:has(.site-header__nav--open) {
    overflow: hidden;
  }

  .site-header__top-nav,
  .site-header__mega-nav {
    flex-direction: column;
    justify-content: flex-start;
    /* Real .upper/.lower li both use margin-top:15px between items. */
    gap: 0.9375rem;
    /* The 40px real inset described above. */
    padding-left: 2.5rem;
  }

  /* Undo the desktop-only "take top-nav out of flow to dodge the support
     pill" positioning - on mobile, support isn't inline with top-nav at
     all (it's its own full-width bar above the whole header), so top-nav
     goes back to being a normal stacked block in the open menu column. */
  .site-header__top-nav {
    position: static;
  }

  /* Real .upper li is font-size:1em - notably smaller than the
     section-trigger group below it - but its visible text sits inside an
     <a>, which (confirmed via getComputedStyle on the real page) renders
     at font-weight:600 from the site's own global anchor rule, not the
     400 the bare <li> rule alone would suggest; reading only the <li>/<ul>
     -level CSS and skipping the actual text-carrying <a> is the exact
     "check the innermost element" mistake this project has hit before
     (Fourteenth pass) and repeated here on the first attempt at this fix.
     The desktop rule's own horizontal padding (0.85rem 1.5rem) is also
     dropped here: it made sense centering a clickable box in a
     horizontal row, but on mobile it just misaligned this group's left
     edge against .mega-trigger's (which has no horizontal padding of its
     own) - confirmed both groups share one left edge on the real page. */
  .site-header__top-link {
    padding: 0;
    font-size: 1rem;
    font-weight: 600;
  }

  .site-header__mega-nav {
    margin-top: 0;
  }

  /* Real .lower is font-size:1.2em - bigger than .upper - but its <a>'s
     own rendered weight is also 600 (same global anchor rule, same
     mistake as .top-link above averted here too), not the 700 the <li>
     rule alone suggests. */
  .site-header__mega-trigger {
    padding: 0;
    font-size: 1.2rem;
    font-weight: 600;
  }

  /* Real .dropdownlinks svg is 15x15 with a 15px gap from its label -
     the desktop-tuned 0.6rem arrow and 0.25rem gap (right for a compact
     inline dropdown trigger) both read as too small once this group's
     own text grows to 1.2rem. */
  .site-header__mega-trigger-row {
    gap: 0.9375rem;
  }

  .site-header__mega-arrow {
    width: 0.9375rem;
    height: 0.9375rem;
  }

  /* Real submenu (.lower li ul) sits at its trigger's own left edge but
     carries its own 40px padding-left (same browser-default <ul> padding
     as the top-level lists, not overridden here either), landing its
     items a further 40px in - 80px total from the viewport edge,
     confirmed via getComputedStyle. */
  .site-header__mega-panel {
    position: static;
    box-shadow: none;
    padding-left: 2.5rem;
  }

  /* Real submenu links render at the same size/weight as their own
     trigger (1.2rem/600, confirmed via getComputedStyle - not a smaller/
     lighter sub-level style), with margin-top:15px between items just
     like every other list on this menu. Right-aligned text is a
     desktop-dropdown-only treatment - the mobile in-flow submenu list
     stays left-aligned like the rest of the menu. */
  .site-header__mega-link {
    font-size: 1.2rem;
    font-weight: 600;
    text-align: left;
  }

  .site-header__mega-panel li {
    margin-top: 0.9375rem;
  }
}

/* Support overlay */

.support-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  background-color: rgba(0, 0, 0, 0.85);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
  padding: var(--space-md);
}

.support-overlay--open {
  opacity: 1;
  visibility: visible;
}

.support-overlay__panel {
  position: relative;
  background-color: var(--color-white);
  border-radius: 0.75rem;
  padding: var(--space-lg);
  max-width: 32rem;
  width: 100%;
}

.support-overlay__close {
  position: absolute;
  top: 0.5rem;
  right: 1rem;
  background: none;
  border: none;
  font-size: 3.75rem;
  line-height: 1;
  color: var(--color-text);
  cursor: pointer;
}

.support-overlay__text p {
  text-align: left;
  font-size: 1.3rem;
  line-height: 1.625rem;
}

.support-overlay__link {
  display: inline-block;
  background-color: var(--color-teal-light);
  color: var(--color-white);
  text-decoration: none;
  padding: 0.75rem 1.5rem;
  border-radius: 0.75rem;
  font-weight: 700;
}

.support-overlay__link:hover {
  background-color: var(--color-teal);
}

/* Footer - real pattern (verified against the live page's own computed
   grid: `.container` inside `footer` is `display:grid`,
   `grid-template-columns: 190px 210px 290px`, with named areas
   `"its-logo its-logo social-logos" ". contact-info contact-info"
   ". partners partners"` - a genuine 3-column grid, not a single stacked
   column. Logo+socials sit in row 1 (logo spanning columns 1-2, flush to
   its right edge; socials in column 3); the address block and partner
   badges both start at column 2's left edge in the rows below - offset
   right of the logo's own left edge, not aligned with it. Content sits
   flush at the footer's true top-left corner (zero padding measured both
   sides) against a large "Bomærke" emblem (the logo's swoosh mark, blown
   up as a watermark - not the "bue" arc dividers used elsewhere) bleeding
   off the top-right corner. */

/* No margin-top: the real footer sits flush against whatever module
   precedes it, zero gap (confirmed via getBoundingClientRect on several
   pages - the footer's own real top edge exactly equals the preceding
   module's bottom edge every time). A margin here added a visible gap
   the live page never has. */
.site-footer {
  position: relative;
  min-height: 42.8rem;
  background-color: var(--color-primary-dark);
  background-image: url("/media/icons/itsit-bomaerke.svg");
  background-position: right -50vw top -45vw;
  background-size: cover;
  background-repeat: no-repeat;
  color: var(--color-white);
  text-align: left;
  padding: 0;
}

.site-footer__inner {
  position: relative;
  display: grid;
  grid-template-columns: 11.875rem 13.125rem 18.125rem;
  grid-template-areas:
    "logo logo socials"
    ". contact contact"
    ". badges badges";
}

.site-footer__logo {
  grid-area: logo;
  justify-self: end;
  align-self: end;
  display: block;
}

.site-footer__logo svg {
  width: 17.5rem;
  height: 9.375rem;
}

.site-footer__socials {
  grid-area: socials;
  justify-self: center;
  align-self: center;
  display: flex;
  gap: 0.9375rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-footer__social-link svg {
  width: 2rem;
  height: 2rem;
  color: var(--color-white);
}

.site-footer__contact {
  grid-area: contact;
  font-style: normal;
  font-size: 1.3rem;
  line-height: 1.625rem;
  margin-top: 0.5rem;
}

.site-footer__contact p {
  margin: 0 0 0.675rem;
}

.site-footer__contact p:nth-last-child(2) {
  margin-top: 1.25rem;
}

.site-footer__contact a {
  color: var(--color-white);
  text-decoration: none;
}

.site-footer__badges {
  grid-area: badges;
  display: flex;
  align-items: flex-end;
  gap: 1.875rem;
}

.site-footer__badge {
  height: 2.5625rem;
  width: auto;
  color: var(--color-white);
}

.site-footer__badge--isae {
  height: 4.0625rem;
}

/* The desktop grid above is built from exact, hand-measured real pixel
   values (Twentieth pass) with no responsive fallback - its fixed
   `690px` total column width doesn't shrink, so on any narrower
   viewport the footer silently overflowed the page horizontally
   (confirmed via document.body.scrollWidth exceeding window.innerWidth
   on mobile - not visible as a scrollbar since `overflow-x:hidden` on
   `html` clips it instead, but the address/badges content past the
   viewport edge was genuinely inaccessible either way). Stacks to a
   single column below the point the fixed grid can no longer fit. */
@media (max-width: 800px) {
  .site-footer {
    min-height: 0;
    /* The decorative emblem's position (`right -50vw top -45vw`) and
       `background-size:cover` were tuned against the desktop footer's own
       fixed 42.8rem height (Twentieth/Twenty-first passes) - at a mobile
       width, with that height now auto (min-height:0 above) and much
       shorter, the same offsets blow the emblem up far larger than
       intended, overlapping the logo/socials. No mobile-specific position
       was ever established for it, so hiding it here rather than
       guessing at new offsets - it's a decorative accent, not content. */
    background-image: none;
  }

  .site-footer__inner {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1.5rem;
    padding: 2rem 1.5rem;
  }

  .site-footer__logo {
    align-self: flex-start;
  }

  /* Same story as the background emblem above: the 17.5rem/9.375rem
     desktop size (Twentieth pass) has no mobile counterpart, and at a
     narrow viewport it dominates the whole footer instead of reading as
     a logo. No real-site mobile measurement to match here either (the
     real footer's own mobile layout wasn't part of this project's scope
     originally) - halved pragmatically to a size that fits comfortably
     within a phone-width column. */
  .site-footer__logo svg {
    width: 8.75rem;
    height: 4.6875rem;
  }

  .site-footer__badges {
    flex-wrap: wrap;
  }
}

/* ==================== components.css ====================
   Reusable content components (hero, tile-grid, carousel,
   contact-form, employee cards, etc). Loads last - wins cascade
   ties against site.css's plain selectors without !important. */
/* Real palette/typography for existing component classes, plus new
   image-backed variants. Loads after site.css so these rules win ties
   against its plain versions of the same selectors - no !important. */

/* Real site has zero body margin. site.css has a leftover Bootstrap-
   scaffold `body { margin-bottom: 60px; }` that was never actually
   reset to 0 for the other sides either (the browser's own UA default of
   8px applied on top/left/right) - shifting every full-bleed section on
   every page by 8px from the true viewport edge. tokens.css loads BEFORE
   site.css (not after - checked _Layout.cshtml's link order directly),
   so the reset has to live here to actually win. */
body {
  margin: 0;
}

/* Another leftover Bootstrap-scaffold rule: site.css sets `html {
   font-size: 14px }`, only reaching the intended 16px above a 768px
   media query. Since every type-scale fix in this file is rem-based,
   this silently shrank all of it by 12.5% on every viewport narrower
   than 768px - i.e. on real phones. Confirmed the real site's root font
   size does NOT vary by viewport (its own computed heading/body sizes at
   a 500px-wide viewport are identical to desktop) - so this is a bug to
   remove, not a deliberate mobile type scale to preserve. */
html {
  font-size: 16px;
}

/* site.css's own scaffold rule for this class sets `padding: 1.5rem` on
   all four sides; this rule only ever overrode `max-width`, so the
   scaffold's *vertical* padding silently survived - creating a real
   top-of-page and bottom-of-page gap on every page, since virtually
   every content page is now built from full-bleed (100vw) modules that
   escape the horizontal padding entirely but are still normal-flow
   children affected by the vertical padding. Left/right padding is kept
   (re-declared explicitly, not just left alone) since non-full-bleed
   content narrower than the .employee-section/.employee-grid breakout
   width still relies on it for its side inset. */
.site-main {
  max-width: 75rem;
  padding-top: 0;
  padding-right: 1.5rem;
  padding-bottom: 0;
  padding-left: 1.5rem;
}

/* Explicit request: pages that don't end with the contact form get a bit
   of breathing room before the footer. Contact-form-ending pages are
   excluded on purpose - that module's own full-bleed teal/blue band is
   meant to sit flush against the footer, matching every other
   module-to-module transition sitewide (padding-bottom:0 above stays
   correct for that general case; this is additive margin only for the
   one case that needs it).

   Can't just check :has(> .contact-form:last-child): _ContactForm.cshtml
   emits its own trailing <script> tag(s) (its JS include, plus the
   reCAPTCHA loader when a site key is configured) as siblings right
   after the <section>, and CSS child-indexing counts every element node
   - including non-rendered ones like <script> - so those scripts, not
   the section, are .site-main's real last child. The three :not()
   clauses below cover "last", "second-to-last with one trailing
   script", and "third-to-last with two trailing scripts" - the only
   shapes _ContactForm.cshtml can actually produce - rather than
   assuming which one applies. A few pages (some news articles) embed
   the contact form followed by a real carousel module - those
   correctly still get the margin, since the page doesn't end with the
   contact form even though it contains one.

   Also excludes a page ending in a grey content box (a standalone
   .about-hero with no split rows following - e.g. esg.cshtml - or an
   .about-split whose own last row is the plain/grey variant, not the
   white --card one - e.g. it-sikkerhed/veeam.cshtml): those get the
   same spacing amount below, but as the module's own grey
   padding-bottom instead of a margin gap here, since margin is
   transparent and would show the page's plain white background
   through it, undoing the grey continuity this is specifically for. */
.site-main:not(:has(> .contact-form:last-child)):not(:has(> .contact-form + script:last-child)):not(:has(> .contact-form + script + script:last-child)):not(:has(> .about-hero:last-child)):not(:has(> .about-split:last-child > .about-split__row:last-child > .about-split__text:not(.about-split__text--card))) {
  margin-bottom: var(--space-lg);
}

.site-main > .about-hero:last-child {
  padding-bottom: var(--space-lg);
}

/* :has() can't nest another :has() inside its own argument (invalid per
   spec) - .about-split__text--card sits directly on the row's text
   panel itself (not some further descendant), so this only ever needed
   a plain :not(.class) here, not a second :has(), same fix applied to
   the .site-main exclusion above. */
.site-main > .about-split:last-child:has(> .about-split__row:last-child > .about-split__text:not(.about-split__text--card)) {
  padding-bottom: var(--space-lg);
}

/* Danish compound nouns can be long enough as a single unbreakable word
   (e.g. "Multifaktorgodkendelse", 23 characters) to overflow a narrow
   mobile viewport at this font-size with nothing to break on - confirmed
   this isn't just a local gap, the real live site has the identical
   overflow on this exact heading at a matching width. Unlike a scraped
   content quirk, a silently-clipped, partly-unreadable heading is an
   accidental implementation bug, not a design choice worth reproducing -
   fixed here rather than left to match. overflow-wrap:break-word only
   engages when a word doesn't otherwise fit, so normal wrapping elsewhere
   is unaffected. */
h1.content-page__title,
.content-page__title {
  font-size: 2.5rem;
  font-weight: 900;
  line-height: 1.2;
  overflow-wrap: break-word;
}

/* margin-bottom is explicit rather than left to the browser default -
   this class is applied to both <h2> and <h3> elements interchangeably
   (whichever heading level the real source used), and the UA default
   margin differs between them (0.83em vs 1em) even though both render
   at the same 2rem font-size here - which made the gap to the following
   paragraph visibly inconsistent (measured 47px vs 53px on the same
   /job/ page) purely from an arbitrary tag choice, not a real design
   difference. */
.content-page__subtitle {
  font-size: 2rem;
  font-weight: 900;
  line-height: 1.2;
  margin-top: var(--space-md);
  margin-bottom: var(--space-md);
  overflow-wrap: break-word;
}

/* Real hero+split ("imageAndDivModule") pages use a genuine two-tier
   heading hierarchy this project's single uniform 2rem size didn't
   reproduce: a row's own primary heading is sometimes real <h1> (40px,
   same as the page title) and its nested sub-headings are consistently
   real <h3> (30px) - confirmed via getComputedStyle across 3 independent
   pages/modules (job, om-os/hvem-er-vi, om-os/compliance - 8 <h3>
   instances total, all 30px). The <h3>-is-always-smaller half of this
   generalizes safely and is scoped to .about-hero/.about-split so it
   never touches a plain .content-page article's own <h3> use (unverified,
   left alone). The <h1>-sized half does NOT generalize by tag alone -
   some real row headings that are locally tagged <h2> stay at genuine
   <h2>/32px on the real site (e.g. "Din succes er vores succes" and
   "Uopfordret ansøgning" on /job/, confirmed via the same live check) -
   so that half is applied per-instance via .content-page__subtitle--lg,
   not a blanket h2 selector. */
.about-hero h3.content-page__subtitle,
.about-split h3.content-page__subtitle {
  font-size: 1.875rem;
}

.content-page__subtitle--lg {
  font-size: 2.5rem;
}

/* Used on the <h3>s converted from a sole teaser-span <p> (see the
   teaser-to-header migration) - these title a single following
   paragraph/list rather than a whole page section, so they read better
   only slightly larger than .content-page__text's own 1.3rem body copy
   instead of the base rule's 2rem (or the about-hero/about-split
   context's 1.875rem). The selector repeats both the `h3` type and both
   classes specifically to match/exceed the specificity of the
   .about-hero/.about-split-scoped h3 rule above (0,2,1) - a bare
   `.content-page__subtitle--sm` would lose to that rule wherever this
   heading sits inside a split-row. margin-bottom no longer set here -
   see the shared h3.content-page__subtitle rule below, which now zeroes
   it for every h3 variant (this one included) uniformly. */
h3.content-page__subtitle.content-page__subtitle--sm {
  font-size: 1.5rem;
}

/* Explicit request: the gap between any h3 heading and the paragraph
   right after it should come entirely from the paragraph's own
   margin-top, not from the heading's margin-bottom - zeroing it here
   (rather than in each size variant above) covers all three at once:
   the base 2rem size, the about-hero/about-split-scoped 1.875rem, and
   the --sm 1.5rem variant, none of which otherwise touch margin-bottom
   themselves. Scoped to h3 specifically (not the shared
   .content-page__subtitle class also used by h1/h2/h4) - only h3 was
   asked for. Specificity (0,1,1) beats the base rule's (0,1,0) - h2/h4
   still get the base var(--space-md) margin-bottom, unaffected. */
h3.content-page__subtitle {
  margin-bottom: 0;
}

/* The other half of the same request: a paragraph immediately after an
   h3 gets a small fixed top margin instead of the browser's UA-default
   1em (.content-page__text sets no margin-top of its own, so 1em -
   relative to its own 1.3rem font-size, 20.8px - is what it was
   falling back to and collapsing against the h3's margin-bottom
   above). Tightened further per explicit request, 0.5em (10.4px) ->
   0.25rem (4px) - switched from an em-relative value to a fixed rem
   one at the same time, since "0.25rem" was asked for literally rather
   than "half of 0.5em" again. Scoped to .content-page__text
   specifically (not a bare + p) to match how every real paragraph on
   this site is already marked up, consistent with the rest of this
   stylesheet's class-based targeting. */
h3.content-page__subtitle + .content-page__text {
  margin-top: 0.25rem;
}

/* Explicit request: h4 (the deepest heading level in use - numbered
   sub-items nested under an h3, e.g. secure-cloud-desktop.cshtml's "1.
   Maksimal sikkerhed" list, one level more specific than the h3 --sm
   labels above) gets a flat 1.35rem font-size (tuned down from an
   initial 1.55rem/"0.25rem over body text" once seen live) rather than
   inheriting the base rule's 2rem - just under h3--sm's own 1.5rem,
   correctly the smaller of the two now that h4 nests one level deeper
   than h3 in the markup. margin-bottom:0 is the same spacing rule
   already applied to h3 - Specificity (0,1,1) beats the base rule's
   (0,1,0), h2/h3 unaffected. */
h4.content-page__subtitle {
  font-size: 1.35rem;
  margin-bottom: 0;
}

/* Same spacing rule as h3's own paragraph-after-heading treatment above,
   applied to h4. */
h4.content-page__subtitle + .content-page__text {
  margin-top: 0.25rem;
}

/* Real site's body paragraph text (confirmed via getComputedStyle across
   several content pages: om-os, it-outsourcing, compliance) is 1.3rem
   with a 1.25x line-height, not the browser's unstyled 1rem default this
   class was silently falling back to - the single biggest contributor to
   "content looks smaller than the real site" across the whole site,
   since this class is reused by every plain content page and by the
   .about-hero/.about-split components alike. */
.content-page__text {
  font-size: 1.3rem;
  line-height: 1.625rem;
  overflow-wrap: break-word;
  /* Explicit request: paragraph-to-paragraph and heading-to-paragraph
     spacing inside a grey .about-split__text (or white --card) panel
     read noticeably looser than the same text inside .about-hero__card
     - measured live: 42px between paragraphs in a split-row panel vs.
     21px in a hero card, 45px from a subtitle down to its paragraph
     vs. 24px. Root cause: .about-hero__card is plain block flow, where
     adjacent siblings' margins collapse to whichever is larger; .about-
     split__text is display:flex (flex-direction:column, for the
     right-anchored --card variant's own alignment needs), and flex
     items' margins never collapse - they sum instead - roughly
     doubling every gap that used to rely on collapsing (this class had
     no margin of its own before, so it was purely riding the browser's
     UA-default 1em top+bottom, invisible in block flow but additive in
     flex flow). Explicit margin-top:0 here, paired with margin-bottom
     below matching that same 1em (as a fixed 1.3rem, since font-size is
     also 1.3rem - no visual change from the previous UA default), makes
     a flex sum between two paragraphs equal exactly one paragraph's own
     margin-bottom - identical to what block-flow collapse already
     produced, so .about-hero__card's own already-correct spacing is
     the *target*, not something this changes. Doesn't affect margin-top
     directly following an h3 - that pair already has its own more
     specific override (below) that wins on specificity. */
  margin-top: 0;
  margin-bottom: 1.3rem;
}

/* Other half of the same fix: a paragraph immediately BEFORE a subtitle
   needs the reverse treatment - its own margin-bottom (1.3rem, above)
   would otherwise still sum with the subtitle's margin-top in flex
   flow (1.3rem + var(--space-md) = ~44.8px, still too big), whereas in
   block flow the subtitle's own larger margin-top (24px) already wins
   the collapse outright and the paragraph's margin-bottom was never
   actually contributing anything. Zeroing it here changes nothing in
   block flow (collapse still resolves to the subtitle's own margin-top,
   the same already-correct value .about-hero__card uses) and fixes the
   flex sum to match it exactly (0 + 24px). :has() rather than a
   tag-specific selector so this covers a subtitle of any level (h2/h3/
   h4) uniformly - the subtitle's own margin-top is what's supplying
   the gap, not anything about ITS type. */
.content-page__text:has(+ .content-page__subtitle) {
  margin-bottom: 0;
}

/* Recurring content pattern across the site: a bold lead-in sentence (or
   two) before the rest of a paragraph's body text, scraped originally as
   a <strong> wrapping just that lead-in. A <strong> can't correctly stay
   block-level while remaining valid HTML inside its <p> (block content
   auto-closes a paragraph), so the tag itself is swapped for a plain,
   semantically-empty <span> - the "strong importance" meaning wasn't
   really what this is (a structural/typographic role, not emphasis) -
   with this class doing the actual block+bold styling instead. */
.teaser {
  display: block;
  font-weight: 700;
}

.content-page__text a,
.content-page__list a {
  color: var(--color-primary);
}

/* Employee directory (medarbejdere) - real pattern verified against the
   live page's own module CSS: a full-bleed light-gray intro band, then per-
   department card grids with a 350x200 landscape photo (not a circular
   avatar), job title above the bold name, contact links below. */

/* Real site's modules always sit flush against one another with zero
   gap, confirmed via getBoundingClientRect across every module boundary
   checked (medarbejdere's 4 employee modules and the footer among them)
   - a module's own internal spacing (padding, a child's margin) never
   leaks out as a visible gap to the next module. margin-bottom here and
   on .employee-section below would otherwise add exactly that kind of
   gap. */
.employee-intro {
  position: relative;
  left: 50%;
  width: 100vw;
  margin-left: -50vw;
  background-color: var(--color-bg-lighter);
}

/* Real site's employee module (intro band + card grid) is a genuinely
   wider fixed column than the rest of the site's plain content
   (`grid-template-columns: auto 1460px auto` on both the intro
   background and the grid wrapper, confirmed via the real stylesheet) -
   91.25rem, not the 75rem `.site-main` cap used everywhere else. */
.employee-intro__inner {
  max-width: 91.25rem;
  margin: 0 auto;
  padding: var(--space-lg) 1.5rem var(--space-md);
}

/* Real `.introTekst` has no horizontal padding at all once its 1460px
   column is reached - its title/text render flush to the column edge
   (confirmed via getBoundingClientRect on the live h1, identical to its
   container's width, not inset from it). Below that width the left/right
   padding above still applies, as a safety margin the real site's own
   grid effectively provides for free via its now-collapsed `auto`
   columns. */
@media (min-width: 1460px) {
  .employee-intro__inner {
    padding-left: 0;
    padding-right: 0;
  }
}

.employee-intro__title {
  font-size: 2.5rem;
  font-weight: 900;
  margin-bottom: var(--space-sm);
}

/* Real page has zero margin between department modules (each is its own
   flush "columnItemModuleEmployee" instance) - the ~27px gap before each
   department heading (measured via getBoundingClientRect on the real
   page) is that module's own internal top padding, not inter-module
   margin, so it doesn't leak into a gap against the previous module. */
.employee-section {
  padding-top: 1.6875rem;
}

/* .employee-section is a normal-flow child of .site-main (75rem cap), so
   reaching the real 91.25rem width needs the same break-out-of-parent
   trick used for full-bleed sections elsewhere, just to a fixed width
   instead of 100vw. Gated to viewports that can actually fit it without
   the centering math pushing content off-screen - narrower viewports
   fall back to .site-main's normal 75rem column, which is an acceptable
   simplification of the real site's own extra breakpoint at 1475px (see
   the project's established policy of a curated breakpoint set instead
   of replicating the live site's ~20 organic ones). */
@media (min-width: 1460px) {
  .employee-section {
    position: relative;
    left: 50%;
    width: 91.25rem;
    margin-left: -45.625rem;
  }
}

.employee-section__title {
  font-size: 2rem;
  font-weight: 900;
  margin-bottom: var(--space-md);
}

.employee-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-md);
}

.employee-card__photo {
  display: block;
  width: 100%;
  aspect-ratio: 350 / 200;
  object-fit: cover;
}

.employee-card__role {
  margin: 0.75rem 0 0;
  font-size: 0.9rem;
  color: var(--color-text);
}

.employee-card__name {
  margin: 0 0 0.5rem;
  font-size: 1.875rem;
  font-weight: 900;
}

.employee-card__contact {
  margin: 0;
}

.employee-card__contact a {
  font-size: 1.3rem;
  color: var(--color-text);
  font-weight: 200;
  text-decoration: none;
}

.employee-card__contact a:hover {
  color: var(--color-primary);
}

@media (max-width: 1200px) {
  .employee-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 600px) {
  .employee-grid {
    grid-template-columns: 1fr;
  }
}

/* Hero - plain white intro block (the live page's own hero text module).
   Real pattern is full-bleed (no centered max-width container) with the
   title/text pinned near the left edge, not a centered narrow column -
   verified against custom.css (fixed px margin-left, fixed/percentage
   widths on the title/paragraph themselves, not on the section). */

/* Real site's hero module uses margin (75px top/bottom), not padding, for
   its vertical spacing, and has zero internal vertical padding - our
   version used padding instead, which (among other things) left zero gap
   between the hero and the job-recruitment banner directly below it,
   unlike the real page's 75px gap there. Was 51px (75px minus
   .site-main's old 1.5rem/24px top padding) - now that .site-main's
   vertical padding is explicitly zeroed (see its own rule), the full
   75px applies directly with no offset needed.
   box-sizing:border-box is load-bearing here, not decorative: this project
   has no global box-sizing reset, so the default content-box would add
   this rule's own left/right padding on TOP of the 100vw width, making the
   rendered box 48px wider than the true viewport on every page that uses
   it - invisible as a scrollbar (html has overflow-x:hidden) but visibly
   clipping the right edge of the hero's own text. Any other full-bleed
   (100vw) rule that ever gains its own horizontal padding needs the same
   treatment - the ones that don't set padding on themselves (.about-hero,
   .about-split, .tile-grid--split, .employee-intro) are unaffected. */
.hero {
  position: relative;
  left: 50%;
  width: 100vw;
  box-sizing: border-box;
  margin-left: -50vw;
  margin-top: 75px;
  margin-bottom: 75px;
}

/* Same centered-column treatment as .employee-intro__inner (see that
   rule's own comment), so the hero's text starts at the same indentation
   as the first content block on a page like /medarbejdere/ instead of
   the hero's own previous flush-1.5rem-from-the-true-edge inset.
   Deliberately does NOT copy .employee-intro__inner's own
   @media (min-width: 1460px) zero-padding override: that override
   relies on .employee-section also breaking out to exactly 1460px at
   that same breakpoint, so the two edges coincide with no leftover
   gutter. .hero has no such matching breakout, so zeroing its padding
   left the hero's own text flush (or, for a range of widths just above
   1460px, only a few px from) the true viewport edge - reported live at
   a ~1470px window width. The hero must always keep some gutter, on
   every page it appears on, so its own 1.5rem padding is never
   dropped - this makes it diverge slightly from
   .employee-intro__inner's flush edge at very wide viewports, which is
   the accepted trade-off. */
.hero__inner {
  max-width: 91.25rem;
  margin: 0 auto;
  padding: 0 1.5rem;
}

.hero__title {
  max-width: 35rem;
  font-size: 2.5rem;
  font-weight: 900;
  line-height: 1.1;
  margin-bottom: var(--space-sm);
}

.hero__text {
  max-width: 45%;
  font-size: 1.3rem;
  line-height: 1.625rem;
  margin-bottom: var(--space-md);
}

@media (max-width: 800px) {
  .hero__text {
    max-width: 65%;
  }
}

@media (max-width: 600px) {
  /* The real site does NOT shrink this heading on mobile - confirmed via
     getComputedStyle at a 500px-wide viewport, still 40px/2.5rem, same as
     desktop. This override was an unverified design assumption; only the
     max-width (a layout concern, not a font-size one) still applies. */
  .hero__title {
    max-width: 100%;
  }

  .hero__text {
    max-width: 100%;
  }
}

/* Recruitment CTA banner - a distinct content module on the live site
   (background office photo, white heading/text/link, curved "bue" SVG
   dividers top and bottom), not a plain decorative strip. */

/* Real module (module1WithLink) is a 5-row CSS grid whose rows sum to a
   fixed 904px above ~1600px width and 500px below it - not the 352px/224px
   this used to approximate. justify-content:flex-end + the bottom padding
   below reproduce the real grid's "top" row (mostly empty background, ~2/3
   of the height) sitting above the text instead of the text floating
   dead-center in a much shorter box. The real 1600px break has no exact
   equivalent in this project's curated 1200/800/600 scale (a deliberate
   simplification, see tokens.css) - 1200px is the nearest one and absorbs
   it, same approach used elsewhere for off-scale real breakpoints.
   box-sizing:border-box for the same reason as .hero above - this rule's
   own horizontal padding would otherwise add on top of the 100vw width
   (no global box-sizing reset exists in this project), overflowing the
   true viewport and clipping this banner's own text at the right edge.
   No margin-bottom: real modules sit flush against one another with
   zero gap (confirmed via getBoundingClientRect across several pages -
   this module's real bottom edge exactly equals the next module's top,
   every time), so this used to add a gap the real page doesn't have.

   text-shadow is deliberate, not real-site fidelity: white text sits
   directly on a per-page background photo here (unlike every other
   white-text spot on the site - .tile-grid__item--text and
   .contact-form are white on a flat known color, never an image), so
   contrast isn't guaranteed against every possible photo. A soft, dark
   shadow keeps it readable regardless of what's behind it without
   looking like a heavy drop-shadow. Inherited by .hero-banner__title/
   __text below rather than set on each individually - harmlessly
   inherited by .hero-banner__bue too, but text-shadow has no effect on
   an SVG shape's fill, so it's a no-op there. */
.hero-banner {
  position: relative;
  left: 50%;
  width: 100vw;
  box-sizing: border-box;
  margin-left: -50vw;
  min-height: 56.5rem;
  background-size: cover;
  background-position: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  text-align: center;
  color: var(--color-white);
  padding: 2.5rem 1.5rem 8.0625rem;
}

.hero-banner__bue {
  position: absolute;
  left: 0;
  width: 100%;
  color: var(--color-white);
  line-height: 0;
}

.hero-banner__bue--top {
  top: 0;
}

.hero-banner__bue--bottom {
  bottom: 0;
}

.hero-banner__content {
  position: relative;
  max-width: 34rem;
}

.hero-banner__title {
  color: var(--color-white);
  font-size: 2.5rem;
  font-weight: 900;
  margin-bottom: var(--space-sm);
}

.hero-banner__text {
  font-size: 1.3rem;
  line-height: 1.625rem;
  margin-bottom: var(--space-md);
}

@media (max-width: 1200px) {
  .hero-banner {
    min-height: 31.25rem;
    padding-bottom: 5.5rem;
  }
}

/* Pill "Læs mere" buttons - shared by hero/tile-grid links only (the
   carousel's own version is a completely different, plain-text style -
   see .carousel__slide-link below, do not add it back to this shared
   rule). Real live-site pattern (verified against custom.css): a light
   pill with red text/border, never a solid red fill - white pill on the
   dark tile-grid text panels, light-gray pill elsewhere. */

.hero__link,
.tile-grid__link {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  flex-shrink: 0;
  gap: 0.5rem;
  padding: 0.35rem 1rem;
  border-radius: 2rem;
  border: 2px solid var(--color-primary);
  text-decoration: none;
  font-weight: 600;
  text-transform: uppercase;
  font-size: 1rem;
  background-color: var(--color-bg-light);
  color: var(--color-primary);
  /* .hero-banner sets text-shadow on itself for its own white-on-photo
     heading/text (see above) and this link inherits it despite sitting on
     its own opaque light pill background, where it serves no purpose. */
  text-shadow: none;
}

/* Chevron arrow after the link text. Uses an inline data-URI mask rather
   than referencing the sprite's <symbol id="next"> by URL fragment - a
   <symbol> isn't independently paintable outside a <use>, so it silently
   renders as an empty/invisible mask when targeted this way. */
.hero__link::after,
.tile-grid__link::after {
  content: "";
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  flex-shrink: 0;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 4l9 8-9 8' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 4l9 8-9 8' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.hero__link:hover {
  background-color: var(--color-white);
}

/* Tile grid */

.tile-grid__title {
  font-size: 2.5rem;
  font-weight: 900;
  margin-bottom: var(--space-sm);
}

.tile-grid__text {
  font-size: 1.3rem;
  line-height: 1.25;
  margin-bottom: var(--space-md);
}

/* Real pattern (confirmed against live markup): each landing page is a
   stack of image+text PAIRS, alternating order - not text overlaid on its
   own background image. No margin-top/bottom: real modules sit flush
   against their neighbors with zero gap (confirmed via
   getBoundingClientRect on several pages) - this tileBlockModule
   included, flush with both the news-carousel module above it and the
   contact-form module below.

   Explicit margin:0 is load-bearing, not decorative: this element also
   carries the bare `.tile-grid` class (markup is `class="tile-grid
   tile-grid--split"`), and site.css has its own leftover pre-redesign
   `.tile-grid { margin: 2rem 0; }` scaffold rule - same specificity as
   this one, but site.css loads before components.css, so simply
   omitting `margin` here (relying on "it's just not set") let that
   32px scaffold margin silently win instead of resolving to 0. Same bug
   class as the body-margin/html-font-size/footer-text-align scaffold
   leaks found earlier in this project - removing a property here isn't
   enough, it has to be zeroed explicitly. */
.tile-grid--split {
  position: relative;
  left: 50%;
  width: 100vw;
  margin: 0 0 0 -50vw;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
}

.tile-grid__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 28rem;
}

/* border-radius:0 explicitly, not just omitted: the markup also carries
   the bare `.tile-grid__item` class (`class="tile-grid__item tile-grid
   __item--image"`), and site.css's own leftover scaffold rule for that
   class sets `border-radius: 0.25rem` - this rule already overrides that
   scaffold's `border`/`padding`, but never touched its `border-radius`,
   so a 4px rounding silently survived on every tile image. Same bug
   class as the .tile-grid--split margin leak above - a property only
   gets reset by being explicitly set to the desired value, not by being
   left alone. */
.tile-grid__item--image {
  background-size: cover;
  background-position: center;
  border: none;
  border-radius: 0;
  padding: 0;
  min-height: 28rem;
}

/* Same scaffold-leak fix as .tile-grid__item--image above - this
   element also carries the bare `.tile-grid__item` class. */
.tile-grid__item--text {
  border-radius: 0;
  /* A grid item's default min-width is `auto`, which lets its content's
     own intrinsic (unwrapped) width force the item - and with it the
     whole 1fr track/row - wider than its column, rather than letting
     text wrap to fit. Only shows up as overflow once the heading/pill
     content's natural width plus this item's own padding gets close to
     the track's available space, which is what the single-column mobile
     layout does. min-width:0 lets the item shrink to the track's actual
     size and wrap normally instead. */
  min-width: 0;
  border: none;
  padding: var(--space-lg);
  color: var(--color-white);
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Default side (text-a: image left, text right) - the text block hugs
     the shared edge with the image, on this row's left. text-b overrides
     to flex-end below. This only moves each child's own box; it doesn't
     touch text-align, so wrapped lines stay left-aligned regardless of
     which side the block itself sits on. */
  align-items: flex-start;
}

.tile-grid__item--text-a {
  background-color: var(--color-primary);
}

.tile-grid__item--text-b {
  background-color: var(--color-primary-dark);
  /* text-b's image is on this row's right, so the text block hugs its
     own right edge instead (toward the image), pushing any leftover
     width to the outer/left side. */
  align-items: flex-end;
}

.tile-grid__item--text .tile-grid__title,
.tile-grid__item--text .tile-grid__text {
  /* Caps the readable measure - without this, a wide viewport lets these
     panels' own (very wide, up to half the full-bleed row) column width
     stretch a paragraph edge-to-edge well past a comfortable line length.
     width:100% up to that cap, rather than leaving these flex children to
     size via fit-content, which could still let a long paragraph's own
     max-content width win and overflow unwrapped. Deliberately doesn't
     include .tile-grid__link here - that's a compact pill button, not
     prose, and forcing it to width:100% stretched it to the full 45rem
     instead of sizing to its own text (same class of bug as the Sixth
     pass's "Læs mere pills stretching to the full width of their text
     panel" fix - align-self:flex-start on the shared link rule already
     keeps it sized to its own content; it just needs to not be
     overridden back to width:100% here). */
  width: 100%;
  max-width: 45rem;
}

.tile-grid__item--text .tile-grid__title,
.tile-grid__item--text .tile-grid__text {
  color: var(--color-white);
}

.tile-grid__item--text .tile-grid__link {
  background-color: var(--color-white);
  border-color: var(--color-white);
  color: var(--color-primary);
}

.tile-grid__item--text .tile-grid__link:hover {
  background-color: var(--color-bg-light);
}

/* The shared .hero__link/.tile-grid__link rule hardcodes align-self:
   flex-start (a Sixth-pass fix for when this container used to stretch
   every child to full width - harmless for text-a, but on text-b it pins
   the button to the row's outer/left edge while its sibling title/text
   sit flush right toward the image, completely disconnecting it from
   them. Following the container's own flex-end here instead keeps the
   button directly under the text block regardless of which side it's on. */
.tile-grid__item--text-b .tile-grid__link {
  align-self: flex-end;
}

@media (max-width: 800px) {
  .tile-grid {
    grid-template-columns: 1fr;
  }

  .tile-grid__row {
    grid-template-columns: 1fr;
  }

  .tile-grid__item--image {
    min-height: 12rem;
    /* Real rows alternate image-left/text-right and text-left/image-right
       for visual variety at 2-column desktop width - which, in source
       order, means every other row has the text tile *before* the image
       tile in the markup. Collapsed to a single column here, that source
       order would stack text-then-image on those alternating rows
       instead of image-then-text - `order` reshuffles the visual stacking
       without touching the markup, so every row shows its image first
       once stacked, regardless of which one is first in the HTML. */
    order: -1;
  }

  /* text-b's desktop flex-end (hugging the image on its right, in the
     2-column layout) has no image to align toward once stacked into a
     single column - reset to the same left alignment as text-a so mobile
     doesn't right-align text with nothing adjacent to justify it. */
  .tile-grid__item--text-b {
    align-items: flex-start;
  }

  /* Same reason as the container reset just above - the link's own
     align-self would otherwise still pin it to flex-end (a per-element
     override, not inherited from the container), leaving it stranded on
     the opposite side from the now-left-aligned title/text. */
  .tile-grid__item--text-b .tile-grid__link {
    align-self: flex-start;
  }
}

/* Contact form */

/* margin-top/bottom explicitly 0, not just omitted: site.css's leftover
   scaffold rule for this same class sets `margin: 2rem 0` - this rule
   already overrides that scaffold's max-width/padding/border (see the
   Sixth pass), but had never overridden its margin, so a 32px gap to
   the modules on both sides silently survived every fix since. Real
   modules sit flush with zero gap (confirmed via getBoundingClientRect
   on several pages), so margin has to be zeroed outright here, same as
   .tile-grid--split just above. */
.contact-form {
  position: relative;
  left: 50%;
  width: 100vw;
  max-width: 100vw;
  margin: 0 0 0 -50vw;
  padding: 0;
  background-color: var(--color-teal);
  color: var(--color-white);
  border: none;
  /* Same scaffold-leak bug class as .tile-grid__item (Seventy-seventh
     pass): site.css's own leftover .contact-form { border-radius:
     0.25rem } was never explicitly reset here despite border/margin/
     padding all being overridden - combined with overflow:hidden below,
     this rounded the whole full-bleed band's corners and clipped the
     media photo panel into them. */
  border-radius: 0;
  overflow: hidden;
}

/* Real background colors differ slightly per contact-form instance
   (verified via inline module-content styles on the live pages): CSP is
   #00839E (--color-teal), Lars Bo is #00A4C0 (--color-teal-light). */
.contact-form--larsbo {
  background-color: var(--color-teal-light);
}

.contact-form--media {
  display: flex;
  align-items: stretch;
  padding: 0;
}

.contact-form__media {
  flex: 0 0 50%;
  background-size: cover;
  background-position: center;
}

.contact-form__content {
  position: relative;
  flex: 1 1 auto;
  max-width: 65rem;
  padding: var(--space-lg);
  /* contact-form.js pins an inline min-height here (measured via
     getBoundingClientRect, which always returns the full border-box
     height) to keep the box from collapsing during the success state -
     without this, content-box sizing (the default, and there's no
     global border-box reset in this project) would treat that
     min-height as the *content* height and add the 40px/side padding
     on top again, inflating the box by 80px. Same fix already used for
     .hero/.hero-banner/.carousel, same reason. */
  box-sizing: border-box;
}

/* Success state: .contact-form__body (title+form) is hidden and this
   overlays the same box instead, centered - contact-form.js pins an
   explicit inline min-height on .contact-form__content (measured just
   before hiding the body) so the box doesn't collapse once its only
   normal-flow content disappears; this absolutely-positioned overlay
   then centers within that preserved height. [hidden] needs its own
   rule at higher specificity than the plain .contact-form__success
   selector below - same display:flex/[hidden] cascade-tie fix already
   used for .about-hero__video-placeholder. */
.contact-form__success[hidden] {
  display: none;
}

.contact-form__success {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 0 var(--space-lg);
  font-size: 2.5rem;
  font-weight: 900;
  text-align: center;
}

/* .contact-form__content's own padding (var(--space-lg), above) is
   already symmetric - 40px top and bottom - but .contact-form__title's
   unreset UA-default <h2> margin (measured: 33.2px top *and* bottom)
   added on top of the container's own 40px top padding, while the last
   element (.contact-form__status, already margin:0) added nothing to
   the bottom - so the box's *visible* top gap measured 73px against a
   bottom gap of exactly 40px, even though the CSS padding itself was
   never actually asymmetric. margin-top:0 removes that accidental
   addition entirely (top gap now equals the container's own 40px
   padding, same as the bottom); margin-bottom keeps a deliberate,
   explicit value for title-to-form spacing instead of leaving it to
   the same accidental UA default. */
.contact-form__title {
  font-size: 2.5rem;
  font-weight: 900;
  margin: 0 0 var(--space-md);
}

.contact-form__title,
.contact-form__success {
  color: var(--color-white);
}

.contact-form__input,
.contact-form__textarea {
  font-size: 1.3rem;
}

/* Floating label, matching the real site's own contact-form widget: the
   label sits inside the field, in the same spot the typed text will go,
   and disappears once the field has a value - confirmed on the live site
   this is purely value-driven (visible while empty, even when focused;
   gone only once something is typed), not a focus-triggered animation,
   so the CSS-only `:placeholder-shown` technique reproduces it exactly
   without any JS. Requires the input to come *before* the label in the
   markup (sibling selector) and carry a non-empty placeholder (a single
   space) so `:placeholder-shown` has something to key off. */
.contact-form__field {
  position: relative;
}

.contact-form__label {
  position: absolute;
  top: 0;
  left: 0;
  /* Matches .contact-form__input/.contact-form__textarea's own padding,
     so the floating label sits exactly where typed text will go -
     plus the 2px border width, since this label is positioned relative
     to .contact-form__field (the input's own border isn't part of that
     offset the way it would be if the label were nested inside the
     input's own padding box). */
  padding: calc(0.85rem + 2px);
  font-size: 1.3rem;
  font-weight: 400;
  color: #555555;
  pointer-events: none;
  transition: opacity 0.1s ease;
}

.contact-form__input:not(:placeholder-shown) + .contact-form__label,
.contact-form__textarea:not(:placeholder-shown) + .contact-form__label {
  opacity: 0;
  visibility: hidden;
}

/* Per-field validation: a red border on the invalid input/textarea, plus
   its own message right below that field (.contact-form__error) - a
   *separate* whole-form message (.contact-form__form-error, e.g. a
   reCAPTCHA failure or "correct the marked fields") sits on its own just
   above the submit button instead, per explicit user request
   distinguishing the two. */
.contact-form__field--invalid .contact-form__input,
.contact-form__field--invalid .contact-form__textarea {
  /* border-width intentionally left alone here - already 2px by default
     (see .contact-form__input/.contact-form__textarea above), so only the
     color needs to change on error, not the box's own size. */
  border-color: var(--color-primary);
}

/* Matches the invalid border's own color, per explicit user request.
   .contact-form__field is its own flex column with a tighter 0.25rem gap
   (site.css) than .contact-form__form's 1rem, so this needs its own
   small top margin - the whole-form message below doesn't, since the
   form's own gap already provides that spacing above it. */
.contact-form__error {
  margin: 0.25rem 0 0;
  font-size: 0.9rem;
  font-weight: 700;
  /* Plain white, per explicit request - the field's own border already
     turns red on error (see .contact-form__field--invalid), so that
     carries the "which field" signal on its own; the text itself doesn't
     also need to be a red/pink tone to read as an error, and white gives
     the best contrast against this teal background (~4.4:1) of any
     option tried. */
  color: var(--color-white);
}

/* Inverted from the per-field style, per explicit user request: red
   background instead of red text, so it reads as a distinct callout
   block rather than another line of plain text - padding is what
   actually gives the background something to be visible around. */
.contact-form__form-error {
  margin: 0;
  padding: 0.5rem 0.75rem;
  /* border-box so the ≥1200px max-width below (matched to the input's own
     width) includes this element's own padding rather than adding on top
     of it - otherwise the box would render 1.5rem wider than the input
     it's meant to match. */
  box-sizing: border-box;
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--color-white);
  background-color: var(--color-primary);
}

.contact-form__error:empty,
.contact-form__form-error:empty {
  display: none;
}

/* Puts each field's error message beside it instead of below, so a field
   failing validation adds *horizontal* space (which the row already had,
   since the error paragraph is empty/display:none otherwise) rather than
   *vertical* space that pushes every field below it down the page. Below
   this width, falls back to the existing stacked layout untouched.
   Deliberately NOT the same 800px breakpoint used elsewhere for the
   media-panel stacking - measured directly (2026-09-07): the content
   column here is capped separately (50rem) and shared 50/50 with the
   photo panel, so right above 800px the input itself only has ~215px
   left once the error column and gap are subtracted - too cramped to be
   worth it. 1200px is where the input comfortably clears ~300px. */
@media (min-width: 1200px) {
  .contact-form__field {
    flex-direction: row;
    /* Centers the error message against the field's own height, rather
       than its top edge - matters most for .contact-form__textarea
       (much taller than a single-line input), where flex-start would
       leave the error stranded near the top instead of alongside the
       middle of the field. */
    align-items: center;
    /* The base rule's 0.25rem gap was tuned for the tight vertical space
       between an input and its error message stacked below it - too
       cramped as a horizontal gap between them side by side. */
    gap: 1rem;
  }

  .contact-form__input,
  .contact-form__textarea {
    flex: 1 1 auto;
    /* Without this, a flex item's default min-width:auto lets its own
       content (or just its own natural size) resist shrinking below that,
       which would push the error column out of the row instead of
       sharing it - same fix as .tile-grid__item--text's own min-width:0
       above, same underlying flexbox behavior. */
    min-width: 0;
  }

  .contact-form__error {
    flex: 0 0 12rem;
    margin: 0;
  }

  /* Overrides the global .contact-form__error:empty{display:none} at this
     breakpoint only: without this, an empty error paragraph is removed
     from the row entirely, so the input's own flex:1 1 auto width
     depends on whether *that specific field* currently has an error -
     fields resize as errors toggle on/off instead of staying a constant
     width. Reserving the column's width always, even blank, keeps every
     input the same size regardless of validation state - it's the row
     (and so the space the error column needs) that accounts for errors,
     never the input itself. */
  .contact-form__error:empty {
    display: block;
  }

  /* Without this, the whole-form message stretches to the full row width
     (.contact-form__form's default flex-column stretch) - wider than any
     individual field, since that row also includes the 12rem error
     column + 1rem gap reserved beside each field. Subtracting the same
     13rem here matches it back to the input's own width exactly. */
  .contact-form__form-error {
    max-width: calc(100% - 13rem);
  }
}

.contact-form__submit {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background-color: var(--color-bg-light);
  color: var(--color-primary);
  border: none;
  border-radius: 2rem;
  padding: 0.6rem 1.5rem;
  font-size: 1.2rem;
  font-weight: 700;
  text-transform: uppercase;
}

.contact-form__submit::after {
  content: "";
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  flex-shrink: 0;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 4l9 8-9 8' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 4l9 8-9 8' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.contact-form__submit:hover {
  background-color: var(--color-white);
}

/* Loading state: the text and chevron are replaced by a spinning ring
   while the form is submitting - setFormDisabled (contact-form.js) toggles
   this class in lockstep with the button's own `disabled` attribute, so
   the spinner always tracks the same "is this request in flight" state
   the rest of the form is already disabled for. */
.contact-form__spinner {
  display: none;
  width: 1rem;
  height: 1rem;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: contact-form-spin 0.6s linear infinite;
}

.contact-form__submit--loading .contact-form__submit-text,
.contact-form__submit--loading::after {
  display: none;
}

.contact-form__submit--loading .contact-form__spinner {
  display: inline-block;
}

@keyframes contact-form-spin {
  to {
    transform: rotate(360deg);
  }
}

/* Google's own default floating reCAPTCHA badge is hidden - permitted by
   Google's terms only when the required attribution notice is shown
   instead, which .contact-form__recaptcha-notice below does on every page
   that embeds this partial. Functionality (score computation) is
   unaffected - this only hides the visual widget. */
.grecaptcha-badge {
  visibility: hidden;
}

.contact-form__recaptcha-notice {
  margin: var(--space-sm) 0 0;
  font-size: 0.85rem;
  color: rgba(255, 255, 255, 0.7);
}

.contact-form__recaptcha-notice a {
  color: inherit;
  text-decoration: underline;
}

.contact-form__recaptcha-notice a:hover {
  text-decoration: none;
}

/* Hides the reCAPTCHA attribution notice while the "message sent"
   success state is showing - contact-form.js toggles .contact-form__success's
   own [hidden] attribute (never removes/re-adds the element), so this
   general-sibling selector tracks it with no JS change needed. */
.contact-form__success:not([hidden]) ~ .contact-form__recaptcha-notice {
  display: none;
}

@media (max-width: 600px) {
  .contact-form--media {
    flex-direction: column;
  }

  .contact-form__media {
    flex: 0 0 auto;
    width: 100%;
    /* Matches .about-split__image's own stacked-mode height (21rem) -
       explicit request to keep every stacked photo the same height
       regardless of which component it's in. */
    height: 21rem;
    margin-bottom: var(--space-sm);
  }
}

/* Carousel */

/* Vertical spacing is padding, not margin: real modules sit flush
   against each other with zero external gap (confirmed via
   getBoundingClientRect - this module's own real top/bottom padding is
   roughly 27px/95px of breathing room *inside* its box, never a margin
   bleeding into the next module). A margin here would add extra space
   on top of whatever the neighboring module already accounts for. */
/* Dynamic width up to a cap, centered on the page regardless of which
   regime applies: `margin-left:50%` shifts the box's left edge to the
   horizontal center of its containing block (.site-main) - which is
   itself centered in the viewport via its own `margin:0 auto`, so this
   lands at the true viewport center no matter how wide .site-main's own
   column is. `translateX(-50%)` then re-centers the box around that
   same point using *its own* rendered width, whatever that ends up
   being - 100vw-derived below 80rem of viewport, or exactly 80rem
   above it. This is deliberately not the plain `left:50%;
   margin-left:-50vw` trick used elsewhere in this project: that one
   only centers correctly when the box is exactly 100vw wide, and pins
   to the left edge instead of centering once a max-width caps it
   narrower - this component needed the version that centers in both
   regimes. box-sizing:border-box for the same reason as .hero/
   .hero-banner: this rule's own horizontal padding would otherwise add
   on top of the width instead of being included in it. */
.carousel {
  position: relative;
  box-sizing: border-box;
  width: 100vw;
  max-width: 80rem;
  margin-left: 50%;
  transform: translateX(-50%);
  padding: var(--space-lg) 4.5rem;
  /* Reserves room for the nav buttons outside the slide track itself,
     so they sit clear of the cards instead of overlapping them - the
     buttons are absolutely positioned at 0/0, which resolves against
     this padding box's outer edge, landing them in this reserved space.
     4.5rem leaves a clear ~2.3rem gap between each 2.2rem-wide button and
     the card row (button width + gap = padding). */
}

/* Single-item (mobile) mode: carousel.js toggles this once only one card
   fits per row. Used to drop the side padding to 0 entirely so the card
   could use the carousel's full width - but with left/right both 0, the
   nav buttons (position:absolute, left:0/right:0 against this same
   padding box) landed directly on top of the card's own edges instead of
   beside it. Keeps a real (smaller than desktop's 4.5rem, since mobile
   viewports can't spare that much) gutter instead - matched by
   SINGLE_ITEM_SIDE_PADDING in carousel.js, which sizes the single slide
   to what's actually left over rather than the full outer width. */
/* Single-item mode moves the nav out of its absolute overlay (see
   .carousel__nav below) and into the same row as the heading instead,
   right-aligned - an explicit user request specific to this mode, not a
   fidelity concern (this carousel's whole nav-button treatment is
   already a deliberate non-1:1 reimplementation - see the Forty-ninth
   pass). Grid areas place .carousel__heading/__nav/__viewport by name
   rather than DOM order, since the nav element comes after the viewport
   in the markup (unrelated to this - see _Carousel.cshtml) but needs to
   render above it, next to the heading. row-gap replaces the heading's
   own margin-bottom (below) as the heading-to-viewport spacing, so the
   nav (vertically centered via align-items) centers against the
   heading's actual text box, not against text-plus-margin. */
.carousel--single {
  padding-left: 2.75rem;
  padding-right: 2.75rem;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "heading nav"
    "viewport viewport";
  align-items: center;
  row-gap: var(--space-sm);
}

/* margin:0 (not just margin-bottom, which is all the row-gap rework
   above needed) - h2 carries a UA-default margin-top (0.83em, ~23px at
   this font-size) that was never cleared, so align-items:center was
   centering the heading's own *margin box* (content + that 23px) against
   nav's margin-free box, landing the heading's text visibly lower than
   the nav buttons instead of level with them - confirmed via
   getComputedStyle (headingMarginTop: 23.24px) and getBoundingClientRect
   (the two centers were off by ~11.6px, almost exactly half that
   margin). */
.carousel--single .carousel__heading {
  grid-area: heading;
  margin: 0;
}

.carousel--single .carousel__viewport {
  grid-area: viewport;
}

/* Overrides the multi-item overlay positioning entirely - static in the
   grid's own "nav" cell (right-aligned by the 1fr/auto column split
   above) instead of absolute over the card row, so justify-content can
   go back to a plain flex-end pairing (space-between existed only to
   push the two buttons to opposite overlay edges) and pointer-events
   back to auto (nothing underneath to click through to anymore). */
.carousel--single .carousel__nav {
  position: static;
  transform: none;
  grid-area: nav;
  justify-content: flex-end;
  gap: 0.5rem;
  pointer-events: auto;
}

.carousel__heading {
  font-size: 1.75rem;
  font-weight: 900;
  margin-bottom: var(--space-sm);
}

/* A flex row with align-items:stretch only equalizes each slide's
   *overall* height to the tallest sibling - it can't align individual
   rows (date/title/text/link) across slides, since each .carousel__slide
   is its own independent grid with no shared track sizing. That's fine
   while every title is 1-2 lines (all slides land the same overall
   height by coincidence, since only the title row varies and it's
   bounded to the same range), but a title needing 3+ lines stretches
   that one slide's overall box without necessarily growing that
   slide's *title row specifically* - the extra space can land
   anywhere, throwing off row alignment for exactly the tall-title
   slide. Grid + subgrid fixes this at the root: every slide shares the
   *same* four row tracks (defined once, here), each sized to the
   tallest requirement across all slides for that row - so date/title/
   text/link always land at the same y-position on every card, however
   long any one title/date/text turns out to be. */
.carousel__viewport {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: var(--carousel-slide-width, 16rem);
  /* Title row floor (2nd value) is 2 lines at .carousel__slide-title's
     own line-height x font-size (2 x 1.0 x 1.6rem = 3.2rem) - keep this
     in sync if that line-height/font-size ever changes again. */
  grid-template-rows: minmax(1.875rem, auto) minmax(3.2rem, auto) minmax(7.5rem, auto) minmax(1.875rem, auto);
  /* `gap` is a shorthand for BOTH row-gap and column-gap - in a grid
     (unlike the flex row this used to be), that meant the 2rem meant
     only as breathing room *between slides* was also inserted as a 2rem
     gap *between every row within a slide* (date/title/text/link),
     adding 32px of unwanted space above and below the title that never
     existed under the old flex layout. column-gap only, row-gap 0. */
  column-gap: 2rem;
  row-gap: 0;
  /* hidden, not auto: a user must never be able to free-drag/swipe-scroll
     this track directly (native touch-scroll on mobile let a swipe leave
     a slide stopped at an arbitrary partial position, defeating the
     whole-card-alignment work above). carousel.js still moves it by
     setting scrollLeft directly - `overflow: hidden` only blocks the
     browser's own scroll UI/gestures (scrollbar, wheel, touch-drag), it
     does not block script-driven scrollLeft assignment, so the prev/next
     buttons and the touchstart/touchmove/touchend swipe handler (which
     calls the exact same goToPrev/goToNext as the buttons, always landing
     on a whole slide) keep working exactly as before. */
  overflow-x: hidden;
  /* Setting only overflow-x forces the browser to compute overflow-y as
     auto too (per spec, an axis left as the visible default gets promoted
     to auto once the other axis is anything else) - turning this into a
     vertical scroll container clipped to whatever height the slides
     report, silently hiding any slide content that needed more room
     than that. Explicit overflow-y:visible keeps only the intended
     horizontal scrolling and lets the viewport (and every row track
     within it) grow to fit whichever slide's content is actually
     tallest, rather than clipping it. */
  overflow-y: visible;
  /* Tells the browser only a vertical touch-pan is its own native gesture
     here - a horizontal touch-drag is left to our JS swipe handler
     instead of being interpreted as a scroll/back-navigation gesture,
     without this the browser can still swallow the first horizontal
     touchmove deciding whether to scroll before our handler sees it. */
  touch-action: pan-y;
  scroll-behavior: smooth;
  padding-bottom: 0.5rem;
  scrollbar-width: none;
}

.carousel__viewport::-webkit-scrollbar {
  display: none;
}

/* Real cards have no box chrome at all - no border, shadow, background,
   or radius, confirmed via getComputedStyle on the live `.submodule-wrap`
   (all resolve to none/transparent/0). Just plain text columns separated
   by the gap below. Width is set by carousel.js (--carousel-slide-width)
   so only whole cards are ever visible, at no less than a minimum width -
   16rem here is just the pre-JS fallback for the first paint, matched by
   .carousel__viewport's grid-auto-columns.

   Each card is also a fixed-row-height grid (date/title/text/link), not a
   content-driven stack - confirmed via getComputedStyle on 6 different
   real cards with different text lengths, all reporting the identical
   `grid-template-rows: 30px 70px 120px 30px` and landing their "LÆS
   MERE" link at the exact same y-position regardless of content length.
   A plain flex-column stack (what this used to be) lets a short-text
   card's link float higher than a long-text card's, which doesn't match.

   grid-template-rows:subgrid (rather than each slide defining its own
   independent row tracks) is what actually guarantees that alignment
   for every slide, not just the common case: subgridding means all
   slides share the *same* row tracks from .carousel__viewport, each
   sized to the tallest requirement across every slide for that row - a
   title needing 3 lines grows the shared title-row track (and every
   other slide's date/text/link along with it), rather than only
   stretching its own slide's overall box and leaving that one slide's
   internal rows misaligned relative to its siblings (which is what an
   own-grid-per-slide + flex align-items:stretch approach cannot avoid -
   stretching the whole box doesn't guarantee the extra space lands in
   the right row). Nothing here has a hard cap - genuinely long content
   in any row just grows that shared track, on every slide, rather than
   ever being clipped. */
.carousel__slide {
  scroll-snap-align: start;
  grid-row: 1 / -1;
  display: grid;
  grid-template-rows: subgrid;
}

.carousel--media .carousel__slide {
  text-align: center;
}

.carousel__image {
  width: 100%;
  height: 10rem;
  object-fit: cover;
  border-radius: 0.25rem;
  margin-bottom: 0.5rem;
}

.carousel--media .carousel__image {
  width: 6rem;
  height: 6rem;
  border-radius: 50%;
  margin: 0 auto 0.5rem;
}

.carousel__slide-date {
  font-size: 1.2rem;
  font-weight: 400;
  color: var(--color-heading);
  margin: 0;
}

.news-list__item-date {
  font-size: 1.3rem;
  font-weight: 400;
  color: var(--color-heading);
  margin: 0;
}

/* .news-list__title/__item-title/__item-text had no explicit font-size
   at all, silently falling back to the browser's default h1/h2/p sizing
   (32px/700, 24px/700, 16px) - well short of the real site's real values
   for these same roles. */
.news-list__title {
  font-size: 2.5rem;
  font-weight: 900;
}

.news-list__item-title {
  font-size: 2rem;
  font-weight: 900;
  line-height: 1.1;
  margin-top: var(--space-xs);
}

.news-list__item-text {
  font-size: 1.3rem;
  font-weight: 400;
  line-height: 1.625rem;
  margin-bottom: 1.1875rem;
}

/* Same plain-text "Læs mere" link as .carousel__slide-link (Sixty-fifth
   pass) - not the hero/tile-grid pill style. Real /nyheder/ listing's own
   card markup confirms each item is a plain (non-linked) block with a
   single separate <a>Læs mere</a> at the end, the same shape as the
   carousel's own slides - not a whole-card-wrapping anchor, which is why
   this is its own sibling link rather than nested inside anything. */
.news-list__item-link {
  display: block;
  padding: 0;
  border: none;
  border-radius: 0;
  background-color: transparent;
  color: var(--color-primary-dark);
  text-decoration: none;
  font-weight: 600;
  text-transform: uppercase;
  font-size: 1.1rem;
}

/* Real /nyheder/ listing paints a light-grey band behind its title and
   the top of the first row of cards - confirmed via getComputedStyle on
   the live page: a `background-image: linear-gradient(#f2f2f2 0%,
   #f2f2f2 100%)` sized to `100% 250px`, no-repeat, positioned at the top
   of the module. A plain background-color would fill the element's
   entire (very tall) height instead of stopping at 250px, which is why
   this needs the same gradient-as-solid-color trick as the real CSS
   rather than a plain background-color. */
.news-list__band {
  position: relative;
  left: 50%;
  width: 100vw;
  margin-left: -50vw;
  background-image: linear-gradient(var(--color-bg-light) 0%, var(--color-bg-light) 100%);
  background-repeat: no-repeat;
  background-size: 100% 250px;
  background-position: 0 0;
}

/* Centered wrapper for both the title and the list, so they share the
   same left edge - confirmed via getBoundingClientRect on the real
   page: its <h1> and its news list wrapper report the exact same
   `left`. Previously the title lived directly in .site-main's own
   column while only the list (below) had its own separate self-centering
   rule, so the title rendered flush against the true viewport edge
   (inside the new full-bleed .news-list__band, which has no padding of
   its own) instead of lining up with the cards below it - a real,
   visible misalignment against the rest of the site. */
/* padding-top matches .employee-intro__inner's own top padding
   (var(--space-lg)) - the same "grey band containing a page title"
   shape used on /medarbejdere/. Without it, the title's own UA-default
   margin-top had nothing to stop it collapsing straight through this
   element and .news-list__band above it (neither had padding/border to
   block the collapse), so the gap ended up in the wrong place entirely:
   a visible gap between the site header and the grey band's own top
   edge (which should be flush, matching every other page and the real
   site) instead of breathing room between the band and the title. */
.news-list__band-inner {
  box-sizing: border-box;
  max-width: 103rem;
  margin: 0 auto;
  padding: var(--space-lg) 1.5rem 0;
}

/* Real /nyheder/ listing is a wrapping row of fixed 500px-wide image
   cards (confirmed via getBoundingClientRect on the live page: items are
   flex children with a fixed basis, not a discrete column-count
   breakpoint - at wide viewports 3 fit per row, narrower ones 2, then 1,
   purely from flex-wrap reflow), not the plain text list this was built
   as originally. 100rem is the real 3-column content width (3*31.25rem
   cards + 2*3.125rem gaps) - .news-list__band-inner's own 103rem max-width
   (100rem plus its 1.5rem-each-side padding) is what actually caps this at
   3 columns; site.css's own scaffold .news-list__item rule (border-bottom
   + padding, meant for a plain divided list) is explicitly zeroed here
   rather than left alone, same lesson as every other scaffold-leak fix
   this project has hit. */
.news-list {
  display: flex;
  flex-wrap: wrap;
  gap: 3.125rem;
}

.news-list__item {
  flex: 0 1 31.25rem;
  border-bottom: none;
  padding: 0;
}

/* Explicit user design decision, not a fidelity fix - the real live site
   keeps its listing left-aligned at every width. Once the row is down to
   one card per line, the item's own flex-grow:0 correctly stops it from
   stretching to fill the row - it only shrinks below its 31.25rem basis
   once the container itself is narrower than that (confirmed via
   getBoundingClientRect: at a mid-range single-column width the item
   stays at a flat 500px, sitting flush left in a much wider row with all
   the leftover space to its right), so justify-content:center on the
   row is what centers the item *box* itself. Its own text stays
   left-aligned (the initial `text-align: start` - a brief center-aligned-
   text version of this rule was tried and then explicitly reverted,
   keeping only the box centering) - do not reintroduce text-align:center
   here. 68.625rem is the exact viewport width where a 2nd column stops
   fitting - 2 * 31.25rem cards + 1 * 3.125rem gap = 65.625rem of interior
   width, plus .news-list__band-inner's own 2 * 1.5rem side padding -
   derived from the same numbers .news-list__band-inner's comment above
   uses for the 3-column cap, not a curated breakpoint. */
@media (max-width: 68.625rem) {
  .news-list {
    justify-content: center;
  }

  /* Once the items themselves center as a block (not just their text),
     the title needs the exact same width/centering so its own left edge
     lands on the items' left edge instead of staying at
     .news-list__band-inner's own fixed padding inset - giving it the
     same 31.25rem max-width as .news-list__item, centered the same way,
     is what makes the two align regardless of viewport width within
     this range, rather than matching a single width by coincidence. */
  .news-list__title {
    max-width: 31.25rem;
    margin-left: auto;
    margin-right: auto;
  }
}

.news-list__item-image {
  display: block;
  width: 100%;
  aspect-ratio: 2 / 1;
  object-fit: cover;
  border-radius: 0;
  margin-bottom: var(--space-sm);
}

/* line-height brought down from this font's normal ~1.52 (measured
   directly: a single line of .carousel__slide-title text renders 39px
   tall at this 25.6px font-size) - first halved to 0.76 at the user's
   request, then nudged up to 0.9 ("a bit" more) after that read as too
   cramped once seen live, then nudged up again to 1.0 (another "a bit"
   more) - each step a deliberate design choice, not a real-site
   fidelity value.

   min-height (2 x 1.0em = 2em) is the floor and -webkit-line-clamp:2
   is the ceiling - together they pin every title at *exactly* two
   lines regardless of its own text length: a short one-line title
   still reserves the full two-line height (so it doesn't leave the row
   shorter than its siblings), and a title long enough to need a 3rd
   line is truncated with an ellipsis rather than growing the row - a
   deliberate reversal of the previous pass's "never clip, let it grow"
   rule, scoped to just this element at the user's explicit request.
   .carousel__slide-text below is NOT reverted the same way - only the
   header was asked for here. */
/* Real title row reserves noticeably more than two lines actually need
   (a fixed 70px row against ~56px of real 2-line content at its own
   line-height) - confirmed via getBoundingClientRect on all 6 of the
   home page's real cards: title-box height is a uniform 70px regardless
   of the title's own line count, and the *shortest* real title still
   measured >0px of visible gap before its paragraph starts (the 2-line
   case leaves ~14px unused at the bottom of the box; shorter titles
   leave more). Locally, min-height:1.8em (above) sizes the box to
   *exactly* two lines with nothing extra, so a title that happens to
   fill both lines gets zero gap to the text below it even though real
   never lets that happen - margin-bottom is the explicit minimum this
   was missing, matched to real's own smallest (2-line-full) reserve. */
.carousel__slide-title {
  font-size: 1.6rem;
  font-weight: 900;
  line-height: 1;
  min-height: 2em;
  margin: 0 0 0.875rem;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Same line-clamp trick as .carousel__slide-title, at the user's
   explicit request - a deliberate reversal of the "never clip, let it
   grow" rule this used to have, scoped to just these two elements. A
   genuinely long description is now truncated with an ellipsis (7
   lines, reduced from an initial 10 once seen live) rather than growing
   the card indefinitely; the title/text
   attribute set alongside this in _Carousel.cshtml gives the full text
   back as a native hover tooltip so nothing is permanently lost. */
/* margin-bottom matches the real site's own minimum gap before the
   "LÆS MERE" link (measured via getBoundingClientRect across all 6 of
   the home page's real cards: 19-45px depending on the description's
   own length, same "fixed-height box with top-aligned text" pattern
   already found for the title-to-text gap - 19px is the smallest,
   i.e. the floor once the text's own box is closest to full) - same
   reasoning as .carousel__slide-title's own margin-bottom above. */
.carousel__slide-text {
  font-size: 1.2rem;
  line-height: 1.625rem;
  margin: 0 0 1.1875rem;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 7;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* In single-item mode only one card is ever visible at a time (the rest
   are off-screen, reached by scrolling) - so the multi-card row-alignment
   reasoning behind clamping (Sixty-ninth/Seventy-fifth passes) doesn't
   apply, and the full title/text can render instead. `display:block`
   undoes `-webkit-box`, which `-webkit-line-clamp` requires to have any
   effect - clamp/overflow alone wouldn't be enough to remove it. */
.carousel--single .carousel__slide-title,
.carousel--single .carousel__slide-text {
  display: block;
  -webkit-line-clamp: unset;
  overflow: visible;
}

/* The above only removed the clamp - the shared subgrid row tracks
   (.carousel__viewport's grid-template-rows, Sixty-seventh pass) still
   floored every row to a minimum height (1.875rem/2.88rem/7.5rem/1.875rem)
   for cross-slide alignment, and .carousel__slide-title's own min-height:
   1.8em (above) still reserved a fixed 2-line box - both pointless in
   single-item mode, since only one card is ever visible and there's
   nothing to align against. Drops the row template entirely (the slide
   stacks as a plain block instead of a subgrid, each row sized purely to
   its own content) and zeroes the title's min-height floor, so a
   one-line title/short description take up only the room they actually
   need, and a long one grows freely with nothing capping it. */
/* Even with the row template gone, a CSS grid's single implicit row is
   still ONE shared track spanning every column (slide) in it - sized to
   whichever slide's content is tallest, and (by the default
   align-items:stretch) every other slide then stretches to match it.
   In single-item mode that meant every one of the 18 cards rendered at
   the same height (the globally tallest one's), even though only one is
   ever visible at a time - a short card would show a large empty gap
   below its own content just because a completely different, off-screen
   card happens to be longer. align-items:start stops the *stretch* half
   of that; carousel.js's own syncSlideHeight (called on load/scroll/
   resize while carousel--single is active) handles the rest by setting
   the viewport's own height to match whichever slide is currently
   scrolled into view, so the visible card's real height - not some
   unrelated sibling's - is what the container actually shows. */
.carousel--single .carousel__viewport {
  grid-template-rows: none;
  align-items: start;
}

.carousel--single .carousel__slide {
  display: block;
  grid-row: auto;
}

.carousel--single .carousel__slide-title {
  min-height: 0;
}

/* NOT a pill - confirmed via getComputedStyle on the real news carousel's
   own "LÆS MERE" link (inside .submodule-wrap): padding 0, no border, no
   background, block display, dark-red text with no chevron icon at all.
   Sharing the hero/tile-grid pill rule here was wrong - this module's
   real link is plain text, a completely different pattern from those
   two modules' actual pill buttons. */
.carousel__slide-link {
  display: block;
  padding: 0;
  border: none;
  border-radius: 0;
  background-color: transparent;
  color: var(--color-primary-dark);
  text-decoration: none;
  font-weight: 600;
  text-transform: uppercase;
  font-size: 1.1rem;
}

/* Real nav buttons overlay the carousel itself (absolutely positioned at
   its left/right edges, vertically centered) rather than sitting in their
   own row below it - confirmed via getBoundingClientRect showing both
   buttons positioned outside the card row's own box, vertically centered
   on it. */
.carousel__nav {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  transform: translateY(-50%);
  display: flex;
  justify-content: space-between;
  pointer-events: none;
}

.carousel__button {
  pointer-events: auto;
  width: 2.2rem;
  height: 2.2rem;
  border: none;
  border-radius: 0;
  background-color: var(--color-primary);
  color: var(--color-white);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.carousel__button svg {
  width: 0.9rem;
  height: 0.9rem;
}

.carousel__button:disabled {
  opacity: 0.35;
  cursor: default;
}

/* About-page hero + alternating image/text sections (om-os subpages).
   Real pattern verified against custom.css's `imageAndDivModule` styles:
   style-2 is a full-width photo with a floating white card overlapping its
   bottom edge (not centered, offset toward the left); style-1/style-3
   alternate a photo with a text panel, where style-3's text panel gets a
   white "card" background and style-1's stays on the page's plain gray.

   The real .module-div's own margin-bottom:140px (the source of the
   8.75rem this used to carry) never reaches the *next* module - measured
   directly via getBoundingClientRect, the real imageAndDivModule's outer
   box ends exactly where the following module begins, zero gap, on every
   page checked. That margin lives on the card, contained within the
   real module's own box (a block-formatting-context boundary this
   project's own `.about-hero` doesn't have) - copying its value onto our
   OUTER wrapper instead added a real, visible gap the live page doesn't
   have. */
/* Real imageAndDivModule.style-2 has its own module-level grey
   background-color (confirmed via getComputedStyle: rgb(237,237,237),
   the same --color-bg-lighter used by .about-split__text's plain-row
   variant) - invisible in the normal case since the opaque image and
   white card cover the whole module, but it shows through wherever
   either is missing (e.g. an article whose real hero photo has an empty
   src - see the Thirty-third/Thirty-sixth passes - genuinely renders
   grey there on the real page, not the plain white this project had
   assumed from a screenshot at the time). */
/* display:flow-root (not just position:relative, which doesn't do this)
   establishes a new block formatting context - without it, .about-hero__card's
   own margin-bottom (added so a grey first split row gets a gap below a
   hero card that reaches the hero's own bottom edge) collapses straight
   through .about-hero's bottom edge instead of being contained inside
   it: confirmed via getBoundingClientRect that .about-hero's own bottom
   was landing exactly at the card's bottom (i.e. not expanded by the
   card's 40px margin at all), with that 40px instead sitting in the gap
   *between* .about-hero and .about-split, where neither element's own
   grey background paints - showing the page's plain white background
   through it instead. */
.about-hero {
  position: relative;
  left: 50%;
  width: 100vw;
  margin-left: -50vw;
  background-color: var(--color-bg-lighter);
  display: flow-root;
}

.about-hero__image {
  display: block;
  width: 100%;
  height: 46.875rem;
  object-fit: cover;
}

.about-hero__card {
  position: relative;
  margin-top: -23.125rem;
  margin-left: 4.8125rem;
  width: 40.5625rem;
  max-width: calc(100% - 4.8125rem);
  background-color: var(--color-white);
  padding: 7.1875rem 11.875rem;
}

/* Real site's own breakpoint for this exact module (imageAndDivModule.style-2,
   confirmed via its live stylesheet) is 1200px, not 800px - and below it the
   fixed 750px image height and the card's overlap are both dropped entirely
   (height: auto, margin-top: unset), not just shrunk. A fixed 750px-tall
   photo persisting down to phone widths (the previous @media 800px block
   only adjusted the card, never the image) is what made the hero look
   wrong-height on tablet/mobile. */
@media (max-width: 1200px) {
  .about-hero__image {
    height: auto;
  }

  .about-hero__card {
    margin-top: 0;
    margin-left: 0;
    width: auto;
    max-width: 100%;
    padding: 3.125rem 3.75rem;
  }
}

.about-hero__card .content-page__title:first-child {
  margin-top: 0;
}

.about-hero__video {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin: var(--space-md) 0;
}

.about-hero__video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
}

/* Cookiebot's auto-blocking mode (data-cookieconsent="marketing" on the
   iframe, src swapped in from data-src once consent is given) leaves an
   empty box behind until then - this covers it with an explicit message
   and a button back into the consent dialog, rather than a blank gap.
   Visible by default (matches the pre-consent state) - video-consent.js
   hides it once Cookiebot confirms marketing consent. */
/* [hidden] and .about-hero__video-placeholder are equal specificity (0,1,0
   each) - without this, the plain display:flex below wins the cascade tie
   by source order and the browser's own [hidden]{display:none} UA rule
   never takes effect, so setting the hidden property from JS stops
   visually hiding the element. */
.about-hero__video-placeholder[hidden] {
  display: none;
}

.about-hero__video-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  padding: var(--space-md);
  text-align: center;
  background-color: var(--color-bg-lighter);
  border: 1px solid var(--color-border);
}

.about-hero__video-placeholder p {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.3;
  color: var(--color-text);
}

.about-hero__video-placeholder button {
  border: none;
  border-radius: 2rem;
  padding: 0.5rem 1.25rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-white);
  background-color: var(--color-primary);
  cursor: pointer;
}

.about-hero__video-placeholder button:hover {
  background-color: var(--color-primary-dark);
}

/* Same grey already used by the plain (non-card) rows' own
   .about-split__text background and by .about-hero - applied here to
   the whole content area instead of leaving it to each row, so nothing
   in this area (the gap the previous pass's alternating-row margin
   introduces between two rows, or a style-1 row's image under-filling
   its own box - see .about-split__row:not(:has(.about-split__text--card))
   .about-split__image above) ever shows a stray sliver of the page's
   own white background. White (--card) rows still stand out exactly as
   before, since they set their own explicit white background on top of
   this. */
.about-split {
  position: relative;
  left: 50%;
  width: 100vw;
  margin-left: -50vw;
  background-color: var(--color-bg-lighter);
}

.about-split__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 24rem;
}

/* Explicit request: when a row's white card panel is the first grid
   column (text-first rows), it sat flush against the true left edge of
   the viewport with no breathing room at all, since .about-split is a
   full-bleed 100vw section and this row has no gap/padding of its own.
   margin-left on the card panel itself (rather than padding on the
   row) reveals the grey behind it - a grid item's margin box fills its
   whole track by default (justify-self:stretch), so margin-left shrinks
   only its own border box in from the left while the border box's
   *right* edge stays put at the track's own right edge, i.e. still
   exactly flush against the image column with zero gap - explicit
   request. Originally implemented as padding-left on the row instead;
   switched here because the row also becomes the single stacked column
   on mobile (see the max-width:800px block below), so row-level padding
   was pushing the *image* in from the left too, not just the card, once
   they shared that one column - a margin scoped to the card itself
   never touches the image in either layout. :first-child scopes this to
   a card that's the row's left column on desktop - a card in the second
   column sits against the row's own right edge instead, a separate
   (not requested) case. Tripled (1.5rem -> 4.5rem) per explicit
   request. */
.about-split__text--card:first-child {
  margin-left: 4.5rem;
}

.about-split__image {
  background-size: cover;
  background-position: center;
  min-height: 24rem;
}

/* Real site's .style-1 rows (plain-gray text panel, no white card - see
   .about-split__text vs. --card below) size their own .module-img
   differently from style-3: contained rather than cropped, locked to the
   row's full width (plain "contain" alone doesn't guarantee that - it
   picks whichever axis is smaller, which for this project's actual image/
   row proportions is usually height, leaving the sides letterboxed
   instead), anchored to the top so any resulting under-fill lands at the
   bottom rather than being centered. No row/image-level modifier exists
   to target style-1 rows directly (only .about-split__text--card, on the
   sibling text panel) - :has() picks out the ones missing it rather than
   adding a new class across every content file that uses this component. */
.about-split__row:not(:has(.about-split__text--card)) .about-split__image {
  background-size: 100% auto;
  background-position: top;
  background-repeat: no-repeat;
}

/* Card rows keep background-size:cover (unlike the style-1 rows above -
   these have real, often much longer, text content, so the row can be
   very tall and "contain" would letterbox badly) - but background-
   position:center was cropping to the *source photo's* own vertical
   middle regardless of where that landed within the row. Since the text
   panel's heading naturally sits at the top of the panel (its content
   routinely exceeds the row's own min-height, per real content on pages
   like it-sikkerhed/it-og-cyber-sikkerhed/, so justify-content:center
   below has no visible room left to actually center anything in), the
   image's own visible crop should anchor to the same edge, not the
   source photo's middle. */
/* background-position:top alone (previous pass) anchored the *image's
   own* content to the top of its box, but the actual heading text sits
   lower than that: .about-split__text's own padding (var(--space-lg),
   40px) plus .content-page__subtitle's own margin-top (var(--space-md),
   24px, confirmed identical for both the plain and --lg size variants -
   only font-size differs between them) - 64px total, measured directly
   via getBoundingClientRect on a real card row, not assumed.

   First attempt added padding-top:64px expecting background-position to
   respect it - it silently didn't, because background-origin defaults
   to padding-box, and (despite the name) that means the padding area is
   INCLUDED in the box background-position/background-size measure
   against, not excluded from it - so with no border, "padding-box" and
   "border-box" are the same box, and the added padding had zero effect
   on where the image actually rendered (confirmed via a live screenshot
   after shipping it: the photo was still flush with the row's true top
   edge, no inset at all). background-origin:content-box is what
   actually excludes padding from that measurement, making the image
   anchor to the *content* box's top edge - i.e., 64px below the border
   box - matching what the comment already claimed happened.

   That alone STILL wasn't enough - shipped it, checked a live screenshot
   again, and the image was still flush at the top with no visible white
   strip at all. Root cause: .about-split__image never sets
   background-repeat (base rule doesn't touch it, so it's the CSS
   default "repeat"), and background-clip is still the default
   border-box - so once background-origin shrank the image's own sizing/
   position box to content-box (64px shorter), the single cover-sized
   tile no longer filled the full border-box paint area on its own, and
   "repeat" tiled a second copy of the image into that leftover 64px
   strip instead of leaving it for background-color to show through -
   which, being another crop of the same photo, looked close enough to
   the original that the bug wasn't visually obvious at a glance (this
   is the same tiling trap the grey/style-1 image rule above already
   guards against with its own explicit no-repeat). background-repeat:
   no-repeat stops that: the single content-box-positioned tile paints
   once, and the color fills the untouched strip above it instead. */
.about-split__row:has(.about-split__text--card) .about-split__image {
  background-position: top;
  background-origin: content-box;
  /* background-origin alone only controls the box that background-size/
     -position are measured AGAINST - it does NOT clip the painted image
     to that box. background-clip defaults to border-box, so "cover"
     (which only guarantees the image is >= the origin box on both axes,
     not == it) is free to paint past the content-box edge into the
     padding area whenever a given photo's aspect ratio makes it overflow
     vertically rather than horizontally to satisfy cover - confirmed
     live: some card-row photos happened to only overflow horizontally
     against their own row's box (cropped left/right, no vertical
     spillover - background-clip's default didn't matter, so the top/
     bottom grey looked correct purely by luck of that photo's aspect
     ratio) while others overflow vertically and visibly painted straight
     through the supposedly-grey padding-top/padding-bottom strips,
     covering them entirely with photo - even with padding-bottom pushed
     to 300px inline as a live test, still zero grey showed until
     background-clip was added. Matching background-clip:content-box is
     what actually guarantees the padding strips are never painted over,
     regardless of any individual photo's own proportions. */
  background-clip: content-box;
  background-repeat: no-repeat;
  /* .about-split__text--card's own padding-top (var(--space-xl), below)
     plus .content-page__subtitle's own margin-top (var(--space-md)) - keep
     in sync with that rule if either value changes. */
  padding-top: calc(var(--space-xl) + var(--space-md));
  /* Matching grey space under the image too, not just above it - same
     value as padding-top, same background-clip:content-box mechanism
     (above) now guarantees it actually renders regardless of photo
     aspect ratio. */
  padding-bottom: calc(var(--space-xl) + var(--space-md));
  box-sizing: border-box;
  /* Grey again, explicit request - reverses the previous white-revert
     above (kept as history: white was chosen back then specifically
     because the tall-row contain rule below also exposes this same
     inset area *below* a shorter photo, not just above it, and grey
     there read as a gap separating the image from the white box).
     Reusing --color-bg-lighter - the same grey the plain (non-card)
     rows' own .about-split__text and this whole .about-split section
     already use - rather than introducing a new value. */
  background-color: var(--color-bg-lighter);
  /* Explicit request: a row whose card panel holds an unusually large
     amount of text (many paragraphs wrapping within the 45rem cap
     above) should stop growing this image past a sane size, rather
     than letting "cover" zoom the photo further and further to fill
     whatever height the text demanded - the same problem the deleted
     about-split.js JS toggle used to solve by switching to
     background-size:contain past a measured height. A *conditional*
     pure-CSS equivalent (detect the row's own rendered height, then
     change this sibling's background-size) isn't achievable: it would
     need container-type:size on the row, and size containment forces
     the contained element's own height to stop depending on its
     content - exactly the row-grows-to-fit-the-text behavior this
     component depends on. A real circular dependency, not a missing
     trick.
     max-height sidesteps needing to detect tallness at all: for any
     row shorter than this cap, align-items:stretch (the row's default,
     untouched) still fills the image's box to the row's own height
     exactly as before - visually unchanged from a plain uncapped cover.
     Only once the row's own auto-height genuinely exceeds the cap does
     max-height stop the image's box from growing further; stretch's
     own spec-defined fallback for a maxed-out item is start-alignment,
     so the (now shorter-than-the-row) box sits flush at the top -
     consistent with background-position:top above - and the leftover
     row height below it falls outside the image element's own box
     entirely, showing the grey background-color already set above
     through the row's own grid cell (the row itself has no background,
     so that grey is .about-split's, not a new rule). "cover" still
     paints the photo inside that (now bounded) box, so this bounds how
     far cover can ever zoom a photo rather than literally swapping in
     background-size:contain - given the constraint above, this is the
     closest a pure-CSS rule can get to the old JS's own effect. 50rem
     (800px) reuses that deleted script's own HEIGHT_THRESHOLD value,
     already tuned against real content on this site (confirmed live on
     it-sikkerhed/it-og-cyber-sikkerhed's own longest row) rather than
     picking a fresh number. */
  max-height: 50rem;
}

.about-split__text {
  padding: var(--space-lg);
  display: flex;
  flex-direction: column;
  justify-content: center;
  background-color: var(--color-bg-lighter);
}

/* Global rule for this component, explicit request: the panel itself
   (this element) keeps growing to fill its grid column at any width,
   but its own text content should never grow into an unreadably wide
   measure - capped independently via each direct child, not by capping
   .about-split__text itself.

   width:100% alongside max-width:45rem (rather than max-width alone) is
   deliberate: on a --card panel (align-items:flex-end below), a child
   left at its default cross-axis sizing shrinks-to-fit its OWN content
   instead of stretching - a short heading ends up a narrow box, a long
   paragraph ends up (via its much wider max-content size clamped by
   max-width) a full 45rem box, and right-anchoring both independently
   left their LEFT edges out of line with each other - short lines
   sitting noticeably closer to the panel's right edge than the
   paragraph text above/below them, which reads as if the text itself
   had gone ragged/right-aligned even though no text-align was ever set.
   width:100% forces every child to the SAME computed width (100% of the
   flex line, then clamped to 45rem) regardless of its own content
   length, so left edges line up uniformly and the whole block moves as
   one right-anchored unit - normal left-aligned reading text inside it,
   never per-line right alignment. */
.about-split__text > * {
  width: 100%;
  max-width: 45rem;
  /* Without this, a child with its own padding (e.g. <ul>'s UA-default
     padding-left for the list marker indent) renders wider than 45rem
     regardless - max-width caps the content box by default, and padding
     adds on top of that. border-box makes 45rem the actual rendered
     (outer) width for every child type, not just ones with no padding
     of their own. */
  box-sizing: border-box;
}

/* Extra breathing room above a white card's own heading, on top of the
   var(--space-lg) every panel already gets from .about-split__text -
   explicit request, scoped to --card only (grey panels keep the plain
   var(--space-lg)). The card row's own image (below) reads its inset
   off this same total, so that change lives there too - keep them in
   sync if this value changes again. */
/* Explicit request: once .about-split__text > *'s 45rem cap (above)
   leaves real spare width in a wide panel, the text was left stranded
   against the panel's own left edge with a large empty gap before the
   image - align-items:flex-end moves it to the panel's own right edge
   instead (align-items is the cross-axis for this flex-direction:column
   container, i.e. horizontal here). Grey panels keep the default
   left-aligned start. */
.about-split__text--card {
  background-color: var(--color-white);
  padding-top: var(--space-xl);
  /* Extra breathing room on top of the plain var(--space-lg) every panel
     gets from .about-split__text, explicit request - the text block is
     right-anchored flush against this panel's own edge (align-items:
     flex-end, below), so without a wider right inset than the other
     three sides it sat right up against the image with no gap. */
  padding-right: var(--space-xl);
  align-items: flex-end;
}

/* Design rule for this component's alternating white/grey row pattern
   (explicit request, applies to any future page built the same way):
   consecutive .about-split__row elements normally sit flush - matching
   this project's general "modules sit flush, no leaking margin"
   convention (Sixty-sixth pass) - which reads fine while adjacent rows
   share a background color, but produces an abrupt, margin-less seam
   wherever a white (--card) row meets a grey (plain) one or vice versa.
   Both directions share one margin value so this reads as a single
   deliberate design decision, not two coincidentally-matching numbers.
   :has() detects each row's own color via its .about-split__text
   modifier (there's no color class on the row element itself) rather
   than requiring a new class added to every content file using this
   component. Extended to the .about-hero -> first-row boundary too,
   for the (rarer) page whose first row happens to be a white card -
   .about-hero is always grey, so that transition needs the same rule. */
.about-split__row:has(.about-split__text--card) + .about-split__row:not(:has(.about-split__text--card)),
.about-split__row:not(:has(.about-split__text--card)) + .about-split__row:has(.about-split__text--card) {
  margin-top: var(--space-lg);
}

.about-hero + .about-split:has(> .about-split__row:first-child .about-split__text--card) {
  margin-top: var(--space-lg);
}

/* The other direction of that same boundary: .about-hero itself is
   always grey, but .about-hero__card - the first white box on any page
   that has one - floats inside it via a negative margin-top overlap,
   in normal flow rather than absolutely positioned, so the hero section
   auto-sizes to contain it. Whenever the card's own content is tall
   enough that its bottom edge reaches (or nearly reaches) the hero's
   own bottom edge, there's no grey hero backdrop left showing below it
   - so a hero immediately followed by a plain grey first split row
   (confirmed live on /job/: no gap at all between the card's own last
   paragraph and the next row) reads as an abrupt white-to-grey seam,
   the same bug class the rule above already fixes for split-to-split
   and hero-to-split-when-white-first-row. Margin goes on the card
   itself (not a margin-top on .about-split, like the rule above uses)
   since this is specifically about needing room *below the white box*,
   per the design rule - matching the space this whole boundary already
   uses elsewhere. Applied unconditionally on whether the card actually
   reaches the hero's edge on any given page (that's real-content-
   dependent and unknowable from CSS alone) - harmless when it doesn't,
   since both sides are the same grey either way.

   :not(:has(...)) (mirroring the rule above) doesn't work here - :has()
   can't be nested inside another :has()'s argument, even via :not(); a
   hard restriction per the Selectors spec, and the browser rejects the
   whole selector as invalid rather than degrading gracefully (confirmed:
   this silently dropped the rule entirely - querySelectorAll with the
   same selector throws "not a valid selector" - until caught by
   checking the actual computed margin came back 0). Reaches the text
   element directly via a child combinator instead, so :not() only ever
   wraps a plain class check, never another :has(). */
.about-hero:has(+ .about-split > .about-split__row:first-child > .about-split__text:not(.about-split__text--card)) .about-hero__card {
  margin-bottom: var(--space-lg);
}

@media (max-width: 800px) {
  .about-split__row {
    grid-template-columns: 1fr;
  }

  .about-split__image {
    /* 50% taller than the original 14rem, explicit request. */
    min-height: 21rem;
    /* Same fix as .tile-grid__item--image (Seventy-seventh pass): rows
       alternate image-first/text-first in source order for desktop
       variety, so collapsed to one column here a "card" row (text first
       in the markup) would otherwise stack text-then-image. Never had
       this fix applied when .about-split was originally built. Bare
       .about-split__image (not scoped to --card) so this also covers a
       grey (style-1) row on the rarer page whose own markup happens to
       put its text before its image. */
    order: -1;
  }

  /* Explicit request: stacked (single-column) rows should show a plain
     full-width cover image, not the desktop-only treatment built for a
     2-column row - the style-1 rows' locked-width "100% auto" sizing and
     the card rows' top/bottom inset (padding-top/-bottom, for lining the
     photo up with the text panel's own heading and matching space below
     it - both meaningless once image and text no longer share a row)
     both exist to solve problems specific to that side-by-side layout.
     Repeats each rule's own selector (rather than a single bare
     .about-split__image) so this wins by equal specificity + later
     source order, the same technique already used elsewhere in this
     file for a media-query override.

     padding-bottom:0 was missing here until the desktop rule grew a
     padding-bottom of its own (to match its padding-top) - background-
     origin:border-box above already made the leftover padding-bottom
     harmless on its own (cover sized/positioned against the full
     border-box regardless), but once background-clip:content-box was
     added to fix a real desktop bug (a photo whose aspect ratio
     overflowed vertically under cover would otherwise paint straight
     through the unclipped padding areas), that same leftover
     padding-bottom started clipping the bottom of the mobile image
     clean off instead - background-clip:content-box has no per-media
     override, so it still applies here, and content-box now excludes a
     padding-bottom this layout was never supposed to have. Reset here
     for the same reason padding-top already was. */
  .about-split__row:has(.about-split__text--card) .about-split__image,
  .about-split__row:not(:has(.about-split__text--card)) .about-split__image {
    background-size: cover;
    background-position: center;
    background-origin: border-box;
    padding-top: 0;
    padding-bottom: 0;
  }

  /* .about-split__text--card:first-child's 4.5rem left inset (above) is a
     desktop-only visual offset for the white card sitting beside its image
     in a 2-column row. Stacked to one column here, it just pushes the card
     off the true left edge with grey showing through - explicit request to
     remove it in this mode. */
  .about-split__text--card:first-child {
    margin-left: 0;
  }
}

/* ==================== Notification banner ====================
   Sitewide critical-outage notice (Views/Shared/_NotificationBanner.cshtml),
   shown on every page when App_Data/banner.json's isActive flag is true.
   Deliberately sits in normal document flow above the header rather than
   fixed/sticky - .site-header takes over the sticky top:0 spot once the
   banner scrolls past. Non-dismissible by design (no close control): the
   goal is cutting down repeat phone calls about a known outage, not
   letting a visitor scroll it away and forget it's there. */
.notification-banner {
  background-color: var(--color-primary);
  color: var(--color-white);
  /* The support pill's own "SUPPORT" segment sits flush below this, in the
     exact same red - with no border, the two read as one continuous red
     shape rather than two distinct elements. */
  border-bottom: 1px solid var(--color-white);
}

.notification-banner__inner {
  max-width: 91.25rem;
  margin: 0 auto;
  padding: var(--space-sm) var(--space-md);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-sm);
}

.notification-banner__icon {
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
}

.notification-banner__header {
  font-size: 1.125rem;
  font-weight: 700;
  text-transform: uppercase;
}

.notification-banner__text {
  font-size: 1.125rem;
}

.notification-banner__link {
  color: var(--color-white);
  font-weight: 700;
  text-decoration: underline;
  white-space: nowrap;
}

.notification-banner__link:hover {
  text-decoration: none;
}

@media (max-width: 800px) {
  .notification-banner__inner {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* Sits inline in the header bar, in the open space between the logo and
   the nav (.site-header__bar is flex/space-between, so that gap exists on
   any viewport wide enough not to wrap) - a passive "which environment am
   I on" cue for developers/testers, placed where it's noticeable without
   displacing any real nav content. */
.test-environment-badge {
  /* Pulls it in from .site-header__bar's own gap (var(--space-md), the
     same spacing used between every other bar child) so it sits closer
     to the logo than that default. */
  margin-left: -0.75rem;
  /* .site-header__bar is justify-content:space-between with 3 flex
     children now (logo, this badge, nav) - without this, that gap
     redistributes so the badge drifts toward the middle of the bar
     instead of hugging the logo. An auto margin on this side consumes
     all the remaining flexible space itself, overriding space-between
     for this specific gap and pushing nav away instead. */
  margin-right: auto;
  padding: 0.2rem 0.6rem;
  background-color: var(--color-primary);
  color: var(--color-white);
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  border-radius: 0.25rem;
  user-select: none;
}
