/* ==========================================================================
   header.module — the fixed dock, the frosted bar, and the nav that is a panel
   below 56em and an inline row above it.

   Buttons and links come from base.css and are not redefined here.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The fixed dock

   .vz-header-dock is declared in templates/partials/header.html rather than
   module.html, because it has to sit outside HubSpot's generated wrapper div,
   which is only as tall as the header itself. Its CSS lives here so all header
   styling is in one file.

   Fixed, not sticky. Sticky pins against the nearest scrolling ancestor and is
   silently cancelled by an `overflow` value on anything above it — and above
   this module sit .body-wrapper plus whatever dnd wrappers the portal emits.
   Fixed resolves against the viewport instead; the trade is that it takes no
   space in the flow, which .vz-header-spacer gives back.

   A fixed ancestor is NOT a containing block for fixed descendants — only
   transform / filter / backdrop-filter are. That is why the frosted layers are
   siblings of the bar's content rather than a filter on the bar or the dock: a
   backdrop-filter out here would re-anchor the skip link and the mobile nav
   panel to this box and collapse both.
   -------------------------------------------------------------------------- */
.vz-header-dock {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: var(--vz-z-sticky);
  /* The chrome arrives before the page does — a bar dropping in from off the
     top edge, rather than the shared vz-rise, which travels up from below and
     would read as content rising. 480ms, ahead of the hero's own ladder.

     `backwards` and NOT `both`, and the distinction is load-bearing: with
     `both` the transform survives the animation, and a transformed ancestor
     makes `position: fixed` resolve against the dock instead of the viewport —
     breaking both the skip link and the full-height nav panel, which are fixed
     and live in here. */
  animation: vz-header-drop 480ms var(--vz-ease-expo) backwards;
}

@keyframes vz-header-drop {
  from { opacity: 0; transform: translateY(-100%); }
}

/* The keyframe animates from hidden, so removing it is the whole alternative.
   Declared before the @supports block below, which re-states this branch: the
   scroll-linked lift is position, not motion, and has to survive here. */
@media (prefers-reduced-motion: reduce) {
  .vz-header-dock { animation: none; }
}

/* The announcement bar scrolls away while the frosted bar stays pinned — the
   one thing sticky gave for free and fixed does not. The dock rises by the
   announcement's measured height over the first --vz-announce-h pixels of
   scroll, then holds.

   `inset-block-start` and not a transform: a transform here is exactly what
   would re-anchor the skip link and the panel, where offsetting a fixed box
   repositions it without making it a containing block.

   --vz-announce-h is measured in module.js. Unset — JS blocked, or no
   announcement configured — it is 0px, the range is empty and the dock never
   moves, which is also the no-support fallback. */
@supports (animation-timeline: scroll()) {
  @keyframes vz-header-dock-lift {
    to { inset-block-start: calc(-1 * var(--vz-announce-h, 0px)); }
  }

  .vz-header-dock {
    animation: vz-header-drop 480ms var(--vz-ease-expo) backwards,
               vz-header-dock-lift linear both;
    animation-timeline: auto, scroll(root block);
    animation-range: normal, 0px var(--vz-announce-h, 0px);
  }

  @media (prefers-reduced-motion: reduce) {
    .vz-header-dock {
      animation: vz-header-dock-lift linear both;
      animation-timeline: scroll(root block);
      animation-range: 0px var(--vz-announce-h, 0px);
    }
  }
}

/* --------------------------------------------------------------------------
   1a. The spacer — the flow space the fixed dock gave up

   An empty box immediately after the dock, exactly as tall as the chrome, so
   the first section starts below the header rather than behind it.

   Two declarations rather than one because the fallback matters: with JS the
   measured --vz-announce-h is exact and tracks a dismissal to 0px, and without
   it the :has() branch still knows an announcement was rendered and holds a
   plausible one-line height back.
   -------------------------------------------------------------------------- */
.vz-header-spacer { block-size: var(--vz-header-h); }

.vz-header-dock:has(.vz-announce) + .vz-header-spacer {
  block-size: calc(var(--vz-header-h) + var(--vz-announce-h, 2.75rem));
}

/* Both branches again, plus the contained pill's top offset: the pill hangs
   --vz-bar-inset below whatever is above it, so the chrome is that much taller.
   Covering only one branch leaves a page with an announcement bar 16px short —
   a band of body background above the first section.

   Four rules rather than two because :has() takes the specificity of its
   heaviest argument: the two single-condition rules are both (0,3,0) and would
   be settled by source order on a page that is contained AND has an
   announcement. The double-:has() rule is (0,4,0) and settles it outright. */
.vz-header-dock:has(.vz-header--contained) + .vz-header-spacer {
  block-size: calc(var(--vz-header-h) + var(--vz-bar-inset));
}
.vz-header-dock:has(.vz-announce):has(.vz-header--contained) + .vz-header-spacer {
  block-size: calc(var(--vz-header-h) + var(--vz-bar-inset) + var(--vz-announce-h, 2.75rem));
}

/* The chrome announces itself, so a first section can choose to run under it.

   Published as a variable rather than read from --vz-header-h directly, and it
   has to happen in this file: --vz-header-h is in theme.css and is therefore
   always defined, so a section reading it would compensate for chrome that is
   not there. This declaration exists only when the header does.

   It is the BAR's height, never the announcement's — the announcement is opaque
   content that scrolls away, so a first section starts below it, not behind it.

   --vz-bar-inset is declared here on the root rather than beside the rest of
   the pill's rules, because a custom property never resolves upward and both
   the contract below and the spacer need the number. One declaration, three
   consumers. */
:root {
  --vz-bar-inset: var(--vz-space-s);
  --vz-chrome-h: var(--vz-header-h);
}

/* The contained bar is not flush with the top of its dock, so the height a
   first section pulls up by is the bar plus that gap. :has() rather than
   something module.js sets, because it has to be right before a single frame of
   script has run. */
:root:has(.vz-header--contained) {
  --vz-chrome-h: calc(var(--vz-header-h) + var(--vz-bar-inset));
}

/* Set while the mobile panel is open. Scroll lock on the root, so the page
   behind the panel doesn't move. */
.vz-nav-locked { overflow: hidden; }

/* --------------------------------------------------------------------------
   2. Skip link — first focusable thing on the page

   Fixed rather than absolute on focus: the dock rises out of view by the
   announcement bar's height, and an absolute skip link would ride up with it.
   -------------------------------------------------------------------------- */
.vz-header__skip {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.vz-header__skip:focus {
  position: fixed;
  inset-block-start: var(--vz-space-2xs);
  inset-inline-start: var(--vz-space-2xs);
  inline-size: auto;
  block-size: auto;
  clip-path: none;
  z-index: var(--vz-z-toast);
  padding: 0.6rem 0.9rem;
  border-radius: var(--vz-radius-s);
  background: var(--vz-color-ink-deep);
  color: var(--vz-color-white);
  font-size: var(--vz-step--1);
  font-weight: 700;
  text-decoration: none;
}
/* Lime, because the pill it lands on is ink. Stated here because base.css
   scopes its ring to .vz-dnd / .vz-section / .vz-btn / .vz-link and this is
   none of those — without it the first thing a keyboard visitor focuses falls
   back to whatever ring the browser draws. */
.vz-header__skip:focus-visible {
  outline: 2px solid var(--vz-color-accent);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   3. Announcement bar
   -------------------------------------------------------------------------- */
.vz-announce {
  background: var(--vz-color-ink-deep);
  color: var(--vz-color-white);
  font-size: var(--vz-step--1);
}

/* align-items:flex-start with no flex-wrap keeps the × on the first line at
   the top right however many lines the message wraps to. */
.vz-announce__inner {
  display: flex;
  align-items: flex-start;
  gap: var(--vz-space-s);
  padding-block: 0.65rem;
}

/* min-inline-size:0 lets the text wrap instead of shoving the button out. */
.vz-announce__text {
  flex: 1 1 auto;
  min-inline-size: 0;
  margin: 0;
}

/* Lime, not white — 13.6:1 on the dark surface, and it matches how base.css treats
   links on the ink surface. */
.vz-announce .vz-link { color: var(--vz-color-accent); }

/* 44px touch target, pulled back out of the flow with negative margins so it
   does not set the bar's height. -1rem is measured: the × advance is 10.06px
   inside a 44px box, so hanging it 16px past the content edge lands the glyph
   within a pixel of the menu button below it. */
.vz-announce__dismiss {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  margin-block: -0.7rem;
  margin-inline-end: -1rem;
  padding: 0;
  border: 0;
  border-radius: var(--vz-radius-s);
  background: none;
  color: var(--vz-color-on-dark);
  font-family: var(--vz-font);
  font-size: 1.375rem;
  line-height: 1;
  cursor: pointer;
  transition: color var(--vz-dur) var(--vz-ease);
}
.vz-announce__dismiss:hover { color: var(--vz-color-white); }

/* base.css only switches its ring to lime inside .vz-section--bg-ink, and this
   bar is chrome rather than a section — so the link inherited the light-surface
   ring and drew #2844a6 on navy, 2.05:1. Lime is 13.6:1 here. */
.vz-announce :focus-visible {
  outline: 2px solid var(--vz-color-accent);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   4. The bar
   -------------------------------------------------------------------------- */
.vz-header {
  position: relative;   /* containing block for the frosted layers */

  /* One type style for everything in the bar that reads as a control, so the
     row scans as one row rather than as small text beside a large pill.
     Declared once and consumed at both breakpoints, which would otherwise drift
     apart the first time either is touched.

     --vz-step--1, not --vz-step-0: at the body size the pill grows to ~50px
     inside a 76px bar and competes with the wordmark. 600 rather than 700 — at
     15px in a light bar, four bold links read as four shouts. */
  --vz-bar-type-size: var(--vz-step--1);
  --vz-bar-type-weight: 600;

  /* How far the frosted layers hang below the bar to finish their fade. */
  --vz-glass-tail: 1.75rem;

  /* No fill of its own: the frosted layers are the bar's surface, and an opaque
     white underneath them is what they would blur. The border is transparent
     rather than absent so the geometry is the same in either state. */
  background: none;
  border-block-end: 1px solid transparent;
}
.vz-header__inner {
  /* Positioned so it paints over the frosted layers, which are absolute and
     would otherwise cover it. No z-index, so no new stacking context and the
     fixed nav panel inside keeps painting against the dock's. */
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--vz-space-s);
  min-block-size: var(--vz-header-h);
}

/* Read progress, drawn along the bar's leading edge: 2px, gutter to gutter, no
   layout, no hit area. A landing page is one long scroll with no other
   wayfinding, and the bar is pinned in front of the visitor the whole way down.

   Top edge, not the bottom: the bottom is a dissolve rather than a line, and a
   hard 2px rule laid across it fights the effect. The announcement lifts away
   within the first 45px of scroll, so from the moment there is any progress to
   report this edge is the top edge of the viewport.

   Brand blue and not lime: lime on white is about 1.3:1 and would be invisible,
   where blue at 4.46:1 is exactly the rules-and-icons role it is reserved for.

   Declared only inside the @supports/no-preference pair, so with no scroll
   timeline nothing is drawn. `scale: 0 1` as the base makes an inactive
   timeline — a document too short to scroll — degrade to nothing rather than to
   a full-width stripe claiming the page is finished. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes vz-header-progress {
      to { scale: 1 1; }
    }
    .vz-header::after {
      content: '';
      position: absolute;
      inset-block-start: 0;
      inset-inline: 0;
      block-size: 2px;
      /* Through the role variable rather than straight to --vz-color-primary:
         the bar inverts under dark sections, where blue is 3.92:1. */
      background: var(--vz-bar-progress);
      transform-origin: 0 50%;
      scale: 0 1;
      pointer-events: none;
      animation: vz-header-progress linear both;
      animation-timeline: scroll(root block);
      transition: background-color var(--vz-dur) var(--vz-ease);
    }

    /* On the pill this rule is drawn across a curve, so 2px laid gutter to
       gutter overhangs both ends — where the bar's own edge has already turned
       away underneath it. Inset by the radius, which on a pill is half the
       bar's height, so the track starts and stops where the top edge is
       straight. An honest shorter track, not a clipped one.

       Not drawn inside .vz-header__glass, which is display:none in three states
       — a read indicator that disappears when a visitor asks their OS to reduce
       transparency is worse than a slightly shorter one.

       --vz-header-h and not --vz-radius-pill: the pill radius is 999px, which
       resolves to half the box's height but does not report that to calc(). */
    .vz-header--contained::after {
      inset-inline: calc(var(--vz-header-h) / 2);
      border-radius: 2px;
    }
  }
}

/* --------------------------------------------------------------------------
   4a. The frosted bar

   The bar is translucent and the page runs underneath it, so the blur is what
   keeps the row legible over whatever is passing: this page alone scrolls
   white, cream, brand blue and deep navy under this box.

   PROGRESSIVE, and the four layers are why. One masked backdrop-filter fades
   the blurred image's OPACITY across the mask and composites it over the sharp
   original — at 50% that is half of each, a double exposure rather than a
   softer blur. Four layers of doubling radius, each masked to a shorter run
   than the last, blur the result of the one below instead: the top of the bar
   carries all four (~15px) and the radius steps down to nothing past the bottom
   edge, so the bar dissolves rather than ending on a line.

   --vz-glass-tail is that release, and it is why the stack hangs below the bar:
   finishing the fade exactly on the bar's edge would put the hardest step of
   the gradient on the one line the effect exists to remove. pointer-events:none
   because the tail sits over live page content.

   The layers are absolutely positioned children, NOT a filter on .vz-header or
   the dock: a backdrop-filter makes its element a containing block for fixed
   descendants, and one level up the nav panel would resolve its
   `inset-block-end: 0` against a 64px box and collapse.

   Saturation rides on the widest layer only — on four stacked layers it
   compounds to 6x and turns the brand blue underneath into a cartoon. It is a
   role variable because the surface-matched states have to switch it off: once
   the tint IS the section's colour, compositing is a no-op at any alpha, but
   saturation is not — #2844a6 at 1.6x is #1743e0, which shows through in the
   tail as a brighter halo along the bottom, a band edge in the one place this
   section exists to remove one. It stays at 1.6 on white and cream, where what
   is underneath is real content the glass should enrich.
   -------------------------------------------------------------------------- */
.vz-header__glass {
  position: absolute;
  inset-block-start: 0;
  inset-block-end: calc(-1 * var(--vz-glass-tail));
  inset-inline: 0;
  pointer-events: none;
}

.vz-header__glass-band {
  position: absolute;
  inset: 0;
  -webkit-backdrop-filter: blur(var(--vz-glass-blur));
  backdrop-filter: blur(var(--vz-glass-blur));
  /* `black` is a stencil, not a colour: a mask reads alpha only. The prefixed
     pair is for Safari before 15.4. */
  -webkit-mask-image: linear-gradient(to bottom, black var(--vz-glass-solid), transparent var(--vz-glass-fade));
  mask-image: linear-gradient(to bottom, black var(--vz-glass-solid), transparent var(--vz-glass-fade));
}

/* 2 / 4 / 8 / 16, so the top of the bar carries about 30px of blur. The radius
   has to beat the type it dissolves: at 15px a .vz-display headline stayed
   legible through the glass, a ghosted second headline behind the nav row. */
.vz-header__glass-band:nth-child(1) {
  --vz-glass-blur: 2px;
  --vz-glass-solid: 72%;
  --vz-glass-fade: 100%;
  -webkit-backdrop-filter: blur(2px) saturate(var(--vz-glass-sat));
  backdrop-filter: blur(2px) saturate(var(--vz-glass-sat));
}
.vz-header__glass-band:nth-child(2) { --vz-glass-blur: 4px;  --vz-glass-solid: 52%; --vz-glass-fade: 80%; }
.vz-header__glass-band:nth-child(3) { --vz-glass-blur: 8px;  --vz-glass-solid: 34%; --vz-glass-fade: 62%; }
.vz-header__glass-band:nth-child(4) { --vz-glass-blur: 16px; --vz-glass-solid: 16%; --vz-glass-fade: 44%; }

/* The tint, painted last so it sits over the blur rather than under it.

   The alphas are not a preference. 80% is the floor anywhere the row's own text
   sits: white at 80% over this page's darkest surface lands the bar at #d0d0d0,
   which holds --vz-color-ink at about 9:1 over every surface the page scrolls
   past. It drops below that only in the tail, where nothing of the header's own
   is drawn.

   The colour is --vz-glass-tint rather than white so surface matching can flip
   it; the ramp is the part that was measured and must not drift. */
.vz-header__glass::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom,
    color-mix(in oklab, var(--vz-glass-tint) 86%, transparent) 0%,
    color-mix(in oklab, var(--vz-glass-tint) 80%, transparent) 60%,
    color-mix(in oklab, var(--vz-glass-tint) 58%, transparent) 76%,
    transparent 100%);
}

/* Two ways back to the plain white bar, both restoring the hairline the
   dissolve replaced. Without backdrop-filter the layers would tint but never
   blur, which is a washed-out bar over live text; and a visitor who asked their
   OS to reduce transparency has asked for exactly this.

   Both go through --vz-bar-solid, so the opaque bar inverts too — it has to,
   since the row's colours come from the same role variables and an opaque white
   bar carrying the dark state's white nav text has nothing legible in it. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .vz-header__glass { display: none; }
  .vz-header {
    background: var(--vz-bar-solid);
    border-block-end-color: var(--vz-bar-rule);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .vz-header__glass { display: none; }
  .vz-header {
    background: var(--vz-bar-solid);
    border-block-end-color: var(--vz-bar-rule);
  }
}

/* And a third: with the panel up the page behind is covered and scroll-locked,
   so there is nothing to reveal — but the tail would still hang a fading white
   band over the top of the ink panel, which reads as a seam. */
.vz-header[data-vz-nav-open="true"] .vz-header__glass { display: none; }
.vz-header[data-vz-nav-open="true"] { background: var(--vz-bar-solid); }

/* --------------------------------------------------------------------------
   4b. Surface matching — the bar wears the colour of whatever is under it

   The wordmark is the reason. Its "sense" is lime, a colour reserved for dark
   surfaces where it is 13.6:1 — and over this bar it lands on none of them,
   because the tint composites whatever is passing underneath into a variable
   light grey within a couple of points of the lime's own luminance. Measured at
   the wordmark's vertical centre, where the tint ramp resolves to 82%, a white
   bar leaves "sense" at 1.29:1 and a bar over brand blue at 1.04:1 —
   isoluminant.

   Opacity cannot fix it, which is what decided the approach: a fully opaque
   white bar tops out at 1.29:1, the lime-on-white ceiling. The surface has to
   change colour, not density. (Not an AA failure — 1.4.3 and 1.4.11 both exempt
   logotypes. The target is "never worse than the wordmark on white".)

   THE TINT IS THE SECTION'S OWN COLOUR, not a light/dark polarity. Five states
   rather than two, because a two-state version over brand blue tinted the bar
   deep navy and read as a navy band laid across a blue section — right polarity,
   wrong colour, and the seam was the most visible thing in the fold. Tinting
   with the section's own token at the same alphas composites the bar to exactly
   that token, so there is no band at all, and the blur still does the work of
   keeping the row legible.

   MEASURE THE COMPOSITE, NOT THE TOKEN — that is what this table is. The tint
   is 80% at the wordmark's vertical centre over a page that is already the same
   colour, so the composite IS the band's own value and every figure below is
   read against that:

     state     tint                     bar       row text     mark: "VIZ"/"sense"
     light     --vz-color-white         #ffffff   ink 15.43   4.46 /  1.29
     surface   --vz-color-surface       #fbf3ee   ink 14.07   4.07 /  1.18
     lilac     --vz-color-lilac         #beabff   ink  7.68   solid 10.46
     brand     --vz-color-primary-deep  #2844a6   wht  8.51   8.51 /  8.51
     ink       --vz-color-ink-deep      #0c173b   wht 17.48   3.92 / 13.57

   Brand blue is the one state that needs the logo switched: the wordmark's
   "VIZ" is #3e6aff, which on #2844a6 is 1.91:1 — two blues a few points apart,
   half a logo nobody can see. brightness(0) invert(1) makes it a solid white
   silhouette at 8.5:1 and leaves alpha alone, so any uploaded wordmark works.
   Ink is deliberately left alone: the blue is 3.92:1 there, weak but visible,
   and the lime is at its best.

   LILAC IS THE SECOND STATE THAT SWITCHES THE LOGO, and it goes the other way.
   Untouched, "VIZ" is 2.22:1 on #beabff and "sense" is 1.56:1 — the blue half
   lands worse than on any other state and worse than the "never worse than the
   wordmark on white" target this section is held to. A white silhouette is not
   available on a light band (2.01:1), so brightness(0) makes it a solid ink one
   at 10.46:1: the mirror of the brand state's filter, minus the invert. It
   costs the mark its two colours on this one band, which is the trade, and it
   is the same trade brand blue already makes.

   The blues move too, and unlike everything inside a section they cannot be
   left to base.css §3 — the bar is CHROME, not a descendant of the band, so the
   band's re-route never reaches it and the tokens have to be named here. This
   is the same trap the announcement bar's focus ring hit at §3. On #beabff:
   --vz-color-primary is 2.22:1, under 1.4.11's 3:1 for the underline and the
   progress line; --vz-color-primary-deep is 4.24:1, which the ring survives at
   3:1 but the hover link does not at 4.5:1. Both take
   --vz-color-on-lilac-accent, 6.99:1, and the CTA pill takes it as a fill with
   white on it at 14.03:1.

   Everything the bar draws switches through the four blocks below rather than
   through [data-vz-surface] selectors scattered across the file, so the rules
   that consume the roles never learn the state exists. Which section is
   underneath is module.js's job.

   THE CONTRACT, for anything built on this later: the bar copies a section
   carrying .vz-section--bg-surface, --bg-lilac, --bg-brand or --bg-ink — which
   every module emits from its own Background field, so no per-page wiring — and
   falls back to white for everything else and for a page with no JS. A section
   may also declare data-vz-surface-dark, read as `ink`, for a section that is
   dark for some reason other than its background token.

   :not([data-vz-nav-open="true"]) suspends all of it while the mobile panel is
   up: the panel covers the page, so there is no surface to answer to, and the
   bar above it is deliberately white — a white burger would be a menu button
   nobody can see.

   AND ALL OF IT IS THE FULL-WIDTH BAR'S. The full bar spans the screen and has
   no edges of its own, so becoming the section is the only way it avoids
   reading as a slab laid across it. A pill is already a discrete object with a
   ring around it and page on three sides, so copying the band buys nothing and
   costs the one material that says "floating". :not(.vz-header--contained) on
   the four blocks below is the enforcement — module.js skips the scroll
   listener on a pill, but the state still has to be right for a bar whose
   data-vz-surface arrives from anywhere else.
   -------------------------------------------------------------------------- */

/* Registered so the tint can be transitioned: an unregistered custom property
   is a token substitution and jumps between values, where syntax:'<color>'
   makes it interpolable. Where @property is unsupported the plain declaration
   below still applies and the tint snaps to the same end state. */
@property --vz-glass-tint {
  syntax: '<color>';
  inherits: true;
  initial-value: #ffffff;
}

.vz-header {
  --vz-glass-tint:     var(--vz-color-white);
  --vz-bar-ink:        var(--vz-color-ink);
  --vz-bar-link-hover: var(--vz-color-primary-deep);
  --vz-bar-underline:  var(--vz-color-primary);
  --vz-bar-ring:       var(--vz-color-primary-deep);
  --vz-bar-progress:   var(--vz-color-primary);
  --vz-bar-solid:      var(--vz-color-white);
  --vz-bar-rule:       var(--vz-color-rule);
  --vz-bar-cta-bg:     var(--vz-color-primary-deep);
  --vz-bar-cta-ink:    var(--vz-color-white);
  --vz-bar-cta-hover:  var(--vz-color-primary-hover);
  --vz-bar-logo:       none;
  --vz-glass-sat:      1.6;

  /* Only the tint needs declaring here — every other role is consumed by a
     property that carries its own transition. They all have to land together: a
     bar still darkening while the links have gone white is 200ms of white on
     white. */
  transition: --vz-glass-tint var(--vz-dur) var(--vz-ease);
}

/* Cream — the only match that changes nothing but the surface colour: ink on
   cream is 14.1:1, so every role the light state declared still holds. */
.vz-header:not(.vz-header--contained)[data-vz-surface="surface"]:not([data-vz-nav-open="true"]) {
  --vz-glass-tint:     var(--vz-color-surface);
  --vz-bar-solid:      var(--vz-color-surface);
  --vz-glass-sat:      1;
}

/* Lilac. Closest to cream in shape — every ink role holds, the tint is the
   band's own colour and the saturation boost comes off — and the two blues and
   the logo are what separate them. See the table above for why each moves. */
.vz-header:not(.vz-header--contained)[data-vz-surface="lilac"]:not([data-vz-nav-open="true"]) {
  --vz-glass-tint:     var(--vz-color-lilac);
  --vz-bar-link-hover: var(--vz-color-on-lilac-accent);
  --vz-bar-underline:  var(--vz-color-on-lilac-accent);
  --vz-bar-ring:       var(--vz-color-on-lilac-accent);
  --vz-bar-progress:   var(--vz-color-on-lilac-accent);
  --vz-bar-solid:      var(--vz-color-lilac);
  --vz-bar-cta-bg:     var(--vz-color-on-lilac-accent);
  --vz-bar-cta-hover:  var(--vz-color-on-lilac-accent-hover);
  --vz-bar-logo:       brightness(0);
  --vz-glass-sat:      1;
}

/* The two dark surfaces share one inverted role set and differ only in the tint
   and fill, which the block below gives brand blue. "dark" is here for the
   two-state contract this replaced: nothing emits it now, and a page running an
   older module.js gets the ink state rather than a bar with no rule at all. */
.vz-header:not(.vz-header--contained):is([data-vz-surface="brand"], [data-vz-surface="ink"], [data-vz-surface="dark"]):not([data-vz-nav-open="true"]) {
  --vz-glass-tint:     var(--vz-color-ink-deep);
  --vz-bar-ink:        var(--vz-color-white);
  --vz-bar-link-hover: var(--vz-color-accent);
  --vz-bar-underline:  var(--vz-color-accent);
  --vz-bar-ring:       var(--vz-color-accent);
  --vz-bar-progress:   var(--vz-color-accent);
  --vz-bar-solid:      var(--vz-color-ink-deep);
  --vz-bar-rule:       var(--vz-color-rule-invert);
  --vz-bar-cta-bg:     var(--vz-color-accent);
  --vz-bar-cta-ink:    var(--vz-color-ink-deep);
  --vz-bar-cta-hover:  var(--vz-color-accent-hover);
  --vz-glass-sat:      1;
}

/* Brand blue. Same specificity as the block above — (0,4,0) both ways, since
   :is() and :not() take the weight of their heaviest argument — so this wins on
   source order alone and must stay after it. */
.vz-header:not(.vz-header--contained)[data-vz-surface="brand"]:not([data-vz-nav-open="true"]) {
  --vz-glass-tint:     var(--vz-color-primary-deep);
  --vz-bar-solid:      var(--vz-color-primary-deep);
  --vz-bar-logo:       brightness(0) invert(1);
}

/* --------------------------------------------------------------------------
   4c. The contained pill — bar_style "contained", the opt-in shape

   Same bar, smaller box. The full-width bar is the default and is the one that
   carries surface matching; this shape is always the same translucent white
   glass over every band. Mechanically that is one thing: the three state blocks
   above are scoped :not(.vz-header--contained), so every role stays at its
   light-state value and no rule below this line knows the states exist.

   WHAT ELSE CHANGES IS THE EDGE. The four glass bands exist so the bar can end
   in a dissolve rather than on a line, and a pill is nothing but edge. So the
   tail goes to 0, the ramp collapses to one clipped layer, and a 1px ring draws
   the line the dissolve was avoiding — a ring or a shadow, never both, so there
   is no drop shadow under this pill at any width.

   WHERE THE RADIUS AND THE CLIP GO IS NOT A STYLE CHOICE. The clip lives on
   .vz-header__glass and never on .vz-header or the dock: a filter,
   backdrop-filter or transform on those makes them a containing block for fixed
   descendants, and the skip link and the full-height panel are both fixed and
   both live inside the bar. An overflow clip does not re-anchor a fixed
   descendant but would visibly cut the panel off at the bar's bottom edge.
   border-radius does neither, which is why the three opaque states at the end
   of this section are allowed to put one on .vz-header.
   -------------------------------------------------------------------------- */
.vz-header--contained {
  /* The pill's ends land on .vz-container's content edge, so they line up with
     every headline on the page: min(100%, maxw) - 2 x gutter, which is these
     three lines and nothing else. */
  inline-size: calc(100% - 2 * var(--vz-gutter));
  max-inline-size: calc(var(--vz-maxw) - 2 * var(--vz-gutter));
  margin-inline: auto;

  /* The float. Read from the root rather than declared here, because the spacer
     and --vz-chrome-h need the same number and neither can see this element.
     The margin collapses through the module's wrapper and stops at the dock,
     which is out of flow and therefore its own formatting context. */
  margin-block-start: var(--vz-bar-inset);

  /* The glass tail, switched off. Zero rather than deleted, since the glass
     wrapper reads the variable in both states.

     Note that hero.module floors its top padding to clear the 1.75rem tail; in
     this state that floor is over-generous and harmless, because it is open
     space above a fold rather than a collision. */
  --vz-glass-tail: 0px;
}

/* The gutter is right for a bar that spans the screen and too much for one
   already inset by it: at 360px the pill's content box is 40px narrower than
   the full bar's, and the nav-less configuration is already a tight fit. 16px
   still clears the curve and gives that case its room back; it opens to 24px on
   a desktop pill, where 16px inside a 76px bar reads mean. */
.vz-header--contained .vz-header__inner {
  padding-inline: clamp(var(--vz-space-s), 2.2vw, var(--vz-space-m));
}

/* overflow:clip so the one remaining backdrop-filter is cut to the curve — a
   backdrop-filter is not clipped by a parent's border-radius on its own — and
   border-radius:inherit on the children so the tint and ring follow. */
.vz-header--contained .vz-header__glass {
  border-radius: var(--vz-radius-pill);
  overflow: clip;
}

/* Four bands down to one. The stack's whole job was the ramp, and clipped to a
   pill with no tail there is no run to ramp across — bands two to four would be
   four copies of the same rectangle, four backdrop-filters deep, for an effect
   nobody can see. One layer at 14px, one pass instead of four.

   Both mask stops at 100%, which is a mask that never fades: the pill is opaque
   glass to its own edge, and the edge is the ring. */
.vz-header--contained .vz-header__glass-band:nth-child(n + 2) { display: none; }

.vz-header--contained .vz-header__glass-band:nth-child(1) {
  --vz-glass-solid: 100%;
  --vz-glass-fade: 100%;
  border-radius: inherit;
  -webkit-backdrop-filter: blur(14px) saturate(var(--vz-glass-sat));
  backdrop-filter: blur(14px) saturate(var(--vz-glass-sat));
}

/* The tint keeps the measured alphas — 86% at the top, 80% where the row's text
   sits. The shape changed; the material did not. What ends is the run to
   transparent: with no tail below the bar, a gradient reaching 0% alpha would
   make the bottom of the pill see-through.

   The ring rides on this pseudo-element rather than the wrapper, because an
   inset shadow on the wrapper would be painted under its own tint and
   disappear. --vz-bar-rule resolves to --vz-color-rule and stays there, since
   the pill never leaves the light state. */
.vz-header--contained .vz-header__glass::after {
  border-radius: inherit;
  background: linear-gradient(to bottom,
    color-mix(in oklab, var(--vz-glass-tint) 86%, transparent) 0%,
    color-mix(in oklab, var(--vz-glass-tint) 80%, transparent) 100%);
  box-shadow: inset 0 0 0 1px var(--vz-bar-rule);
}

/* The three states where the glass goes away and .vz-header paints its own
   fill: no backdrop-filter support, reduced transparency, and the open panel.
   The element carrying the shape is display:none in all three, so the shape
   moves here — radius and ring both, and the bottom hairline back to
   transparent, since a straight line across the underside of a pill is the seam
   this shape exists not to have.

   Declared after the three blocks they mirror, which they have to be:
   .vz-header--contained and .vz-header are both (0,1,0). */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .vz-header--contained {
    border-radius: var(--vz-radius-pill);
    border-block-end-color: transparent;
    box-shadow: inset 0 0 0 1px var(--vz-bar-rule);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .vz-header--contained {
    border-radius: var(--vz-radius-pill);
    border-block-end-color: transparent;
    box-shadow: inset 0 0 0 1px var(--vz-bar-rule);
  }
}

/* The open panel. The pill stays a pill and stays inset: a bar that changes
   shape under the thumb that just tapped it is worse than a rounded white bar
   floating over a full-width ink sheet. */
.vz-header--contained[data-vz-nav-open="true"] {
  border-radius: var(--vz-radius-pill);
  border-block-end-color: transparent;
  box-shadow: inset 0 0 0 1px var(--vz-bar-rule);
}

/* The states are in the selector list because the boilerplate re-types every
   anchor on hover, focus and active — base.css's button rules have the full
   account. Here it is the broken-image case that suffers: the alt text would go
   from 700 ink to the link font's weight, underlined, under a pointer. */
.vz-header__logo,
.vz-header__logo:is(:hover, :focus, :active) {
  display: flex;
  align-items: center;
  margin-inline-end: auto;   /* everything else sits right */
  /* If the image fails to load the alt text renders, and without this it
     arrives as underlined default-blue link text. Through the role variable so
     it inverts with the bar rather than staying ink on an ink surface. */
  color: var(--vz-bar-ink);
  font-weight: 700;
  text-decoration: none;
  transition: color var(--vz-dur) var(--vz-ease);
}
/* Sized by height with width:auto, so a re-uploaded logo of any proportion
   stays undistorted.

   The filter is the brand-blue case and is `none` in every other state, routed
   through the role variable so this rule never learns the states exist. The
   transition is on `filter` rather than the variable: an unregistered custom
   property is a token substitution, but the property substituted INTO still
   transitions when its computed value changes. Interpolating from `none` is
   well defined, so this cross-fades rather than snapping. */
.vz-header__logo img {
  block-size: 2rem;
  inline-size: auto;
  max-inline-size: 55vw;
  filter: var(--vz-bar-logo);
  transition: filter var(--vz-dur) var(--vz-ease);
}

/* Below 30em the pill does not fit beside the wordmark and the menu button, so
   it lives in the panel instead (.vz-header__nav-cta). */
.vz-header__inner .vz-header__cta { display: none; }

/* The bar's own button. base.css owns the pill — shape, fill, hover, press —
   and this narrows the type to the bar's, nothing else. Two classes deep so it
   beats .vz-btn's own font-size whatever the stylesheet order.

   Dropping to --vz-step--1 takes the pill from ~50px to ~44px, exactly the
   touch floor, hence the explicit minimum rather than trusting the em padding,
   which would follow the type down the next time either number moves.

   The states are listed because the boilerplate re-types every anchor; three
   deep rather than two, because base.css's own .vz-btn state rule is (0,2,0)
   and this has to outrank it in every state as well as at rest. */
.vz-header .vz-header__cta,
.vz-header .vz-header__cta:is(:hover, :focus, :active) {
  font-size: var(--vz-bar-type-size);
  font-weight: var(--vz-bar-type-weight);
  min-block-size: 2.75rem;

  /* The fill, taken off .vz-btn--primary and onto the bar's role variables: the
     bar inverts under dark sections, where the action colour has to be lime and
     never the blue, which is 3.92:1 there. base.css still owns the shape, the
     leading and the press. */
  background: var(--vz-bar-cta-bg);
  color: var(--vz-bar-cta-ink);
  transition: background-color var(--vz-dur) var(--vz-ease),
              color var(--vz-dur) var(--vz-ease);
}
/* Same (0,3,0) as the rule above and later in the file, which is what carries
   it — the hover shade would otherwise be flattened by the resting fill. */
.vz-header .vz-header__cta:is(:hover, :focus) {
  background: var(--vz-bar-cta-hover);
}

/* --------------------------------------------------------------------------
   5. Nav — the ink panel below 56em, an inline row above it
   -------------------------------------------------------------------------- */
/* Fixed, not absolute, and pinned to the viewport bottom: the panel fills the
   space under the bar whether the announcement is on screen or already scrolled
   away. Sized off the bar instead (100dvh minus the bar) it overhangs the
   bottom of the screen by the announcement's height, pushing the button below
   the fold.

   --vz-nav-top is the bar's own bottom edge, published on open by module.js —
   the page is scroll-locked while the panel is up, so it cannot go stale. The
   fallback is the correct value at the top of the page. */
.vz-header__nav {
  display: none;
  position: fixed;
  inset-block-start: var(--vz-nav-top, calc(var(--vz-announce-h, 0px) + var(--vz-header-h)));
  inset-block-end: 0;
  inset-inline: 0;
  overflow-y: auto;
  padding: var(--vz-space-l) var(--vz-gutter) var(--vz-space-2xl);
  background: var(--vz-color-ink-deep);
  opacity: 0;
  translate: 0 -0.5rem;
  transition: opacity var(--vz-dur) var(--vz-ease-expo),
              translate var(--vz-dur) var(--vz-ease-expo),
              display var(--vz-dur) allow-discrete;
}
/* Column, so the button anchors to the bottom of a full-height panel:
   navigation at the top, the action within thumb reach. */
.vz-header[data-vz-nav-open="true"] .vz-header__nav {
  display: flex;
  flex-direction: column;
  row-gap: var(--vz-space-l);
  opacity: 1;
  translate: 0 0;
}
/* Entrance for a display:none -> block change. Without @starting-style support
   the panel simply appears — no half-state. */
@starting-style {
  .vz-header[data-vz-nav-open="true"] .vz-header__nav {
    opacity: 0;
    translate: 0 -0.5rem;
  }
}

.vz-header__nav-list {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}
/* Full-width rules between rows. Never an inline-start stripe. */
.vz-header__nav-item + .vz-header__nav-item {
  border-block-start: 1px solid var(--vz-color-rule-invert);
}
/* States in the selector list, here and again in the inline row: the
   boilerplate re-types every anchor at a specificity that beats a bare class,
   which in this panel meant a 21px row dropping from 700 to the link font's
   weight and gaining an underline under the thumb. */
.vz-header__nav-link,
.vz-header__nav-link:is(:hover, :focus, :active) {
  display: block;
  padding-block: 0.85rem;
  color: var(--vz-color-white);
  font-size: var(--vz-step-1);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: var(--vz-lh-heading);
  text-decoration: none;
}

/* --------------------------------------------------------------------------
   5a. The current section — the scroll spy's aria-current, in the panel

   Lime because the panel is ink: 13.6:1 there, and already what the panel's
   focus ring uses. White at 700 is the resting row, so the only thing that
   changes is hue — no weight step, no box, and no inline-start stripe, which
   would be a second alignment to keep true against the full-width rules.

   Selected on the attribute alone, so there is no class for module.js to keep
   in step. Both states are listed because the boilerplate re-types every anchor
   at (0,1,1) on hover and focus, which a bare (0,1,0) resting rule loses to.

   The underline is the second signal, because 1.4.1 does not accept hue as the
   only carrier of a state. Here it is drawn at rest; in the inline row the
   retracted underline that row already declares is simply coloured in. */
.vz-header__nav-link[aria-current],
.vz-header__nav-link[aria-current]:is(:hover, :focus, :active) {
  color: var(--vz-color-accent);
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.32em;
}

/* auto pushes it to the bottom of the panel; align-self stops the column's
   stretch from turning the pill into a full-width block. */
.vz-header__nav .vz-header__nav-cta {
  margin-block-start: auto;
  align-self: start;
}

/* Rows and the button rise in sequence as the panel opens — one gesture rather
   than a fade per element. Reuses the shared vz-rise keyframe. */
.vz-header[data-vz-nav-open="true"] .vz-header__nav-item,
.vz-header[data-vz-nav-open="true"] .vz-header__nav-cta {
  animation: vz-rise 420ms var(--vz-ease-expo) backwards;
  animation-delay: calc(60ms + var(--vz-i, 0) * 45ms);
}

/* Lime focus ring on the ink panel. The extra element selector keeps this ahead
   of base.css's .vz-btn:focus-visible whatever the load order. */
.vz-header__nav a:focus-visible,
.vz-header__nav .vz-btn:focus-visible {
  outline: 2px solid var(--vz-color-accent);
  outline-offset: 3px;
}

/* --------------------------------------------------------------------------
   6. Menu button — two bars that become an X. Transform only.
   -------------------------------------------------------------------------- */
.vz-header__toggle {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  margin-inline-end: -0.6rem;
  padding: 0;
  border: 0;
  border-radius: var(--vz-radius-s);
  background: none;
  color: var(--vz-bar-ink);
  cursor: pointer;
  transition: color var(--vz-dur) var(--vz-ease);
}

/* The bar's two bare focusables — this button and the wordmark. Neither is a
   .vz-btn or a .vz-link and neither sits inside a .vz-section, so base.css's
   ring never reaches them and both fell back to the browser's default.

   --vz-bar-ring, so the ring makes the same light/dark switch: deep blue on an
   inverted bar is 2.2:1. */
.vz-header__logo:focus-visible,
.vz-header__toggle:focus-visible {
  outline: 2px solid var(--vz-bar-ring);
  outline-offset: 3px;
  border-radius: 2px;
}

.vz-header__burger {
  position: relative;
  inline-size: 1.375rem;
  block-size: 0.75rem;
}
.vz-header__burger::before,
.vz-header__burger::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  block-size: 2px;
  border-radius: 2px;
  background: currentColor;
  transition: translate var(--vz-dur) var(--vz-ease),
              rotate var(--vz-dur) var(--vz-ease);
}
.vz-header__burger::before { inset-block-start: 0; }
.vz-header__burger::after  { inset-block-end: 0; }

.vz-header[data-vz-nav-open="true"] .vz-header__burger::before {
  translate: 0 0.312rem;
  rotate: 45deg;
}
.vz-header[data-vz-nav-open="true"] .vz-header__burger::after {
  translate: 0 -0.312rem;
  rotate: -45deg;
}

/* --------------------------------------------------------------------------
   7. From 30em: the pill returns to the bar, the panel's copy stands down
   -------------------------------------------------------------------------- */
@media (min-width: 30em) {
  .vz-header__inner .vz-header__cta { display: inline-flex; }
  .vz-header__nav .vz-header__nav-cta { display: none; }
}

/* With no nav links there is no menu button and no panel, so the bar keeps the
   button at every width. */
.vz-header--no-nav .vz-header__cta { display: inline-flex; }

/* That configuration is the one tight fit in the whole header: on a 360px
   screen the wordmark plus a full-size pill needs about 5px more than the
   content box, and the pill eats into the right gutter while the left one stays
   put. Two points off the wordmark buys the room back. */
.vz-header--no-nav .vz-header__logo img { block-size: 1.75rem; }
@media (min-width: 30em) {
  .vz-header--no-nav .vz-header__logo img { block-size: 2rem; }
}
/* And back onto the desktop step. This third rung is not redundant: the desktop
   rule is (0,1,1), this chain is (0,2,1), and a media query contributes no
   specificity — so without it the 30em rung above wins at every width from 56em
   up and a nav-less header keeps a 32px wordmark where every other header has a
   38px one. The override two rules up exists only for the 360px fit. */
@media (min-width: 56em) {
  .vz-header--no-nav .vz-header__logo img { block-size: 2.375rem; }
}

/* --------------------------------------------------------------------------
   8. From 56em: the panel becomes the inline nav row
   -------------------------------------------------------------------------- */
@media (min-width: 56em) {
  /* --vz-header-h steps to 4.75rem here, declared in theme.css alongside the
     base value because base.css's scroll-padding on the root element reads the
     same number and a custom property never resolves upward. */
  .vz-header__logo img { block-size: 2.375rem; max-inline-size: none; }

  /* The row's two groups have to be told apart: at the mobile gap the last nav
     link sat 16px from the button while the links sat 24px apart, so the action
     read as a fifth nav item that happened to have a fill. The button now sits
     further from its neighbour than any two links do.

     Both gaps are fluid, which is a fit constraint as much as rhythm: this row
     turns on at 896px and has to hold a wordmark, up to six links and a button
     in about 820px, and it is the one place in the library where running out of
     room clips rather than wraps — the dock is fixed, so an overflowing row
     does not even extend the document's scroll width to reveal itself. */
  .vz-header__inner { gap: clamp(var(--vz-space-m), 2.6vw, var(--vz-space-l)); }

  .vz-header__nav,
  .vz-header[data-vz-nav-open="true"] .vz-header__nav {
    display: flex;
    flex-direction: row;   /* resets the panel's column */
    align-items: center;
    gap: var(--vz-space-m);
    position: static;
    inset: auto;
    overflow: visible;
    padding: 0;
    background: none;
    opacity: 1;
    translate: none;
    transition: none;
  }

  .vz-header__nav-list {
    display: flex;
    align-items: center;
    /* The inner half of the pair above — always narrower than the row's gap, so
       the button never reads as one more link. */
    gap: clamp(var(--vz-space-s), 1.9vw, var(--vz-space-m));
  }
  .vz-header__nav-item + .vz-header__nav-item { border-block-start: 0; }

  /* Same size and weight as the button beside them, so the row reads as one set
     of controls rather than small links next to a large pill.

     The padding is a pointer target, not spacing: at 0.25rem the link was a
     25px band inside a 76px bar, which is a lot of bar that looks clickable and
     is not. 0.55rem takes it to ~36px without touching the gaps.

     The underline is declared at rest and only coloured in on hover, so nothing
     about the box changes when the pointer arrives — no reflow, and the rule
     lands on the text's own offset rather than the bottom of the padding. */
  .vz-header__nav-link,
  .vz-header__nav-link:is(:hover, :focus, :active) {
    padding-block: 0.55rem;
    color: var(--vz-bar-ink);
    font-size: var(--vz-bar-type-size);
    font-weight: var(--vz-bar-type-weight);
    letter-spacing: normal;
    /* .vz-btn's leading, restated rather than inherited: this rule overrides the
       panel's heading leading, and leaving it would put the links and the button
       label on two different line boxes. */
    line-height: 1.2;
    text-decoration: underline;
    text-decoration-color: transparent;
    text-decoration-thickness: 2px;
    text-underline-offset: 0.32em;
    transition: color var(--vz-dur) var(--vz-ease),
                text-decoration-color var(--vz-dur) var(--vz-ease);
  }
  .vz-header__nav-link:hover {
    color: var(--vz-bar-link-hover);
    text-decoration-color: var(--vz-bar-underline);
  }

  /* The current section, in the row: the hover state, held. The same two role
     variables, so the mark follows the bar's surface like everything else —
     deep blue on light and cream, lime on brand and ink — and there is no fifth
     colour to measure against four backgrounds.

     Hovering the current link is therefore a no-op, which is correct. Hovering
     a different one still reads, because that link changes and this one does
     not. */
  .vz-header__nav-link[aria-current],
  .vz-header__nav-link[aria-current]:is(:hover, :focus, :active) {
    color: var(--vz-bar-link-hover);
    text-decoration-color: var(--vz-bar-underline);
  }

  /* Off the panel's fixed lime and onto the role variable: the panel is always
     ink, but this row sits in a bar that inverts, so the ring follows the
     surface rather than the breakpoint. */
  .vz-header__nav a:focus-visible,
  .vz-header__nav .vz-btn:focus-visible { outline-color: var(--vz-bar-ring); }

  /* The panel entrance belongs to the panel only. */
  .vz-header[data-vz-nav-open="true"] .vz-header__nav-item,
  .vz-header[data-vz-nav-open="true"] .vz-header__nav-cta { animation: none; }

  .vz-header__toggle { display: none; }
}

/* --------------------------------------------------------------------------
   9. Reduced motion — the panel and the X arrive instantly
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .vz-header__nav { transition: none; }
  .vz-header[data-vz-nav-open="true"] .vz-header__nav-item,
  .vz-header[data-vz-nav-open="true"] .vz-header__nav-cta { animation: none; }
  .vz-header__burger::before,
  .vz-header__burger::after { transition: none; }
  .vz-header__nav-link,
  .vz-announce__dismiss { transition: none; }

  /* The surface switch arrives instantly rather than crossfading. A colour
     crossfade would be defensible on its own but not as a partial one: the nav
     row's colour transition is already stood down above, and a tint still
     travelling while the links have finished is the white-on-white interval the
     shared duration exists to prevent. All of it or none of it. */
  .vz-header,
  .vz-header__logo,
  .vz-header__logo:is(:hover, :focus, :active),
  .vz-header__toggle,
  .vz-header::after,
  .vz-header .vz-header__cta,
  .vz-header .vz-header__cta:is(:hover, :focus, :active) { transition: none; }
}
