/* SECTION - Skip to content link */

/* Visually hidden until focused, then rendered as a real pill over the header.
   Deliberately a new class rather than giving .sr-only (css-utilities.css:54) a
   focus escape: article bodies use .sr-only directly, and screen-reader-links.js
   matches on it -- its HIDDEN_SELECTOR also claims .visually-hidden and
   .screen-reader-only, so all three of those names are spoken for and none of
   them can safely change meaning. See the Guiding Principle in plan.md.
   `:focus`, not `:focus-visible`. The link is only reachable by keyboard in
   practice, so the heuristic buys nothing, and `:focus` also covers a
   programmatic .focus() -- which means there is no state in which this link can
   hold focus while still being invisible. That was the WCAG 2.4.7 failure.
   Deliberately at top level rather than inside the `.header` block below, which
   is scoped to a `.header` class that sits on <html>. The skip link is a sibling
   of <header> and should not depend on that class being present.
   No `outline` is declared here, and as of 2026-10-25 nothing else about the
   ring is either. The focus ring stays declared once, in css-reset.css, and is
   retuned by consumers through --outline-* only. The `border: 0` that used to be
   the one exception is gone with the reset's border; see below. */
.kb-skip-link {
  position: absolute;
  top: 0;
  left: 0;
  /* Clears the header's own --z-index-nav so the revealed pill paints over it.
     Both were hardcoded 20 and 10, which turned out to be exactly Codex's
     --z-index-nav-flyout and --z-index-nav, so this is a rename, not a retune --
     no computed value changes. The names also now say *why* 20 beats 10. */
  z-index: var(--z-index-nav-flyout);
  /* Hidden state: the same clip technique as .sr-only. */
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Every part of the clip has to be undone, or the pill renders 1px tall and
   clipped to nothing while still being focused -- which looks identical to the
   bug this replaces. */
.kb-skip-link:focus {
  /* Colour is not set here: css-reset.css's sitewide --outline-color is
     --form-focus-ring as of 2026-10-02, which is already what this wants. An
     explicit override lived here for a few hours while the default was still
     salmon; it was removed once the default was right, so the ring now follows
     the site rather than pinning its own copy of the token.
     Offset does still need pinning -- the reset falls back to
     max(2px, 0.15em), ~2.4px at this font size, where Codex uses a flat 2px. */
  --outline-offset: 2px;
  /* The `border: 0` that used to sit here is gone, 2026-10-25, and it was not
     this pill's own style -- it existed to cancel the 2px border the reset once
     paired with its outline. That declaration has left css-reset.css, so there
     is nothing to cancel; the reset's comment records why. */
  width: auto;
  height: auto;
  overflow: visible;
  clip-path: none;
  margin: var(--gap-sm);
  padding: 0.75rem 1.25rem;
  border-radius: var(--radius-sm);
  /* Dark on light rather than the brand salmon: this has to read as
     unmistakably "a thing appeared" against a white header, and the reset's
     focus ring already supplies the brand colour around it. */
  background: var(--neutral-text);
  color: var(--neutral-text-inverse);
  font-weight: var(--brand-font-weight-semibold);
  text-decoration: none;
}

/*!SECTION - Skip to content link */

/* SECTION - Header */

.header {
  /* Surface and rule come off the Codex tier as of the navbar build: the design
     fills the bar with `page/bg-middle` rather than pure white, so the header
     matches the page behind it instead of sitting a shade brighter than it.
     #faffff is also --neutral-bg; --page-bg-middle is the one chosen because the
     Figma frame names that variable specifically. The rule was --clr-neutral-40
     (#c2c2c2), noticeably darker and warmer than the design's #c9d7d9. */
  header {
    background: var(--page-bg-middle);
    text-align: center;
    position: relative;
    z-index: var(--z-index-nav);
    width: 100%;
    padding: 1.5rem;
    border-block-end: var(--border-width-regular) solid
      var(--neutral-border-soft);
  }

  .header-logo {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: nowrap;
    cursor: pointer;
    /* --step-2 is left alone deliberately: it resolves to 1.1074rem == 17.72px at
       a 16px root, which is exactly the size the design specifies for
       "Knowledge Base". The scale already lands on the design, so this is a
       colour/weight change only.
       Colour was --clr-neutral-160 (#333333); the design says #212529, which is
       legacy --clr-neutral-180 rather than anything on the Codex tier. Joy's call
       was to take the Codex value, so this is --neutral-text (#10171e) -- darker
       than both, and the role Codex uses for primary text.
       Weight drops 500 -> 400: the design's wordmark label is Sora Regular. */
    a {
      text-decoration: none;
      color: var(--neutral-text);
      font-weight: var(--font-weight-regular);
      font-size: var(--step-2);
    }
  }

  .logo {
    display: flex;
    flex-direction: row;
    align-items: center;
    /* 0.75rem -> 1rem to match the design's 16px between wordmark, rule and
       label. */
    gap: 1rem;
    flex-wrap: nowrap;
    width: fit-content;
    cursor: pointer;
    /* Same value this always had, now named. It applies because .logo is a flex
       item of .header-logo, and z-index does apply to flex items without
       `position`. --z-index-raised (1) is more likely what this actually means --
       lift above its siblings, not "this is the nav layer" -- but nothing else in
       the header sits between 2 and 10 to prove it, so the value is left alone
       rather than retuned on a guess. */
    z-index: var(--z-index-nav);

    /* The `svg` here is the fa-pipe divider -- the Font Awesome kit JS has
       already swapped the <i> for an <svg> by the time this applies, so this
       targets the svg rather than a bare `i`.
       The design's divider is #999999, which is legacy --clr-neutral-60 exactly
       and has no Codex counterpart. --neutral-icon-soft (#96a6a9) is the closest
       on-tier value -- three steps of lightness away, not perceptible at a 1px
       rule -- and it is semantically right, since this renders as an icon.
       Height pinned to the design's 18px; it was 1.15em, which tracked the
       17.72px label and came out ~20px. */
    svg {
      color: var(--neutral-icon-soft);
      height: 1.125rem;
    }

    /* Tracking is the design's -0.1772px over a 17.72px label -- i.e. exactly
       -0.01em, so it is written in em to survive the clamp on --step-2 rather
       than pinned to one px value the fluid size only hits at a 16px root. */
    .logo-text {
      font-family: var(--ff-logo);
      letter-spacing: -0.01em;
      line-height: 1.2;
    }
  }

  /* SECTION - Hamburger Menu */

  /* The two mobile controls. `margin-inline-start: auto` is what pushes them to
     the end of the header's flex row, replacing the absolute positioning the
     hamburger used to carry (`inset-block-start: 25%`, `inset-inline-end: 1.5rem`).
     That had to go: the percentage offset was measured against the header's
     height, so opening the search row underneath -- which grows the header from
     ~75px to ~139px -- dragged the hamburger down into the search field. The
     header's own 1.5rem padding now supplies the 24px end inset the design shows.
     The gap carries the design's pitch: the two glyph centres sit 34.5px apart.
     It was 0.5rem, which was 3.5px too tight -- that figure was derived against a
     27.4px hamburger box, i.e. the *pre-correction* 19.43px bar width plus 8px of
     padding. When .bar was corrected to the frames' real 14px of ink the box
     shrank to 22px and this gap was never re-derived, leaving the pitch at 31px.
     The arithmetic now, from stated numbers only: a 24px search box and a 22px
     hamburger box put 12px and 11px either side of the gap, so the gap is
     34.5 - 12 - 11 == 11.5px. Both box widths are now stated rather than emergent
     -- `inline-size` on .kb-search-toggle, and `.bar`'s own 0.875rem for the
     hamburger -- so this cannot silently drift with a glyph metric again.
     --kb-icon-pitch and --kb-tap-size are declared here, not on the buttons, so
     the pitch exists in one place -- the gap and both hit areas derive from it. */
  .kb-header-actions {
    --kb-icon-pitch: 2.15625rem; /* 34.5px, the design's glyph centre spacing */
    --kb-tap-size: 2.75rem; /* 44px, WCAG 2.5.5 target size */

    display: flex;
    align-items: center;
    gap: 0.71875rem;
    margin-inline-start: auto;
  }

  /* Tap targets, per Joy's call 2026-10-03: 44px, with the glyphs left exactly
     where the frames draw them.

     Done with pseudo-elements rather than by padding the buttons, because padding
     would move things the frames pin. Growing each button to 44px wide would push
     its glyph off the design's 34.5px pitch, and growing the hamburger would walk
     its ink off the 24px end inset; a negative margin to claw that back would make
     two pinned numbers depend on each other. A ::before is inert to layout, so the
     visible boxes and the pitch stay as measured and only the hit area changes.

     The two areas abut without overlapping. Each stops exactly at the midpoint
     between the glyph centres -- half the pitch, 17.25px -- and takes the rest of
     its 44px outward into space nothing else occupies: the search leftward toward
     the logo, the hamburger rightward into the header's own 24px end padding. That
     direction matters. Two 44px-wide areas centred 34.5px apart would overlap by
     9.5px, and in that strip the later one wins the click -- so aiming at the right
     edge of the magnifier would open the nav instead. Overlap is a worse failure
     than a small target, which is why the areas grow outward rather than evenly.

     Widest the hamburger reaches is centre + 26.75px. At the 390px frame its glyph
     centre is at 390 - 24 - 11 == 355, so the area ends at 381.75 and stays inside
     the viewport. Vertically both are 10px taller than their 24px boxes either
     side, which the 75px collapsed header absorbs. */
  .kb-search-toggle,
  .nav-toggle-btn {
    position: relative;

    &::before {
      content: '';
      position: absolute;
      inset-block-start: 50%;
      transform: translateY(-50%);
      inline-size: var(--kb-tap-size);
      block-size: var(--kb-tap-size);
    }
  }

  .kb-search-toggle::before {
    inset-inline-end: calc(50% - var(--kb-icon-pitch) / 2);
  }

  .nav-toggle-btn::before {
    inset-inline-start: calc(50% - var(--kb-icon-pitch) / 2);
  }

  /* Hover and press, added 2026-10-20 at Joy's request -- these two were the only
     interactive things in the mobile header with no pointer feedback at all.

     The treatment is Codex's icon-only button, which this theme has already
     confirmed from DevTools and implemented once: see .kb-locale-chip (1686) and
     its hover at 1719 -- a --radius-round chip filling --neutral-bg-component,
     going to -hover and then -active, transitioned on background-color at
     --motion-duration-control. Same ramp here, with one difference: these buttons
     have no chip at rest, because the mobile frames draw both glyphs bare on the
     header surface. So the rest value is transparent rather than
     --neutral-bg-component and the chip fades in under the pointer, which keeps
     the collapsed header exactly as measured and still lands on the design
     system's own hover and pressed fills.

     **The chip is 32px, not Codex's 40px md.** Figma owns sizes here and the frames
     put the glyph centres 34.5px apart (--kb-icon-pitch), so two 40px chips would
     overlap by 5.5px and the two would visibly fuse into one lozenge the moment
     either was hovered. 32px is the largest round number that clears the pitch,
     leaving 2.5px between neighbours. Derived from the pitch rather than pinned
     independently, so it tracks the frames if the pitch is ever re-measured.

     On ::after rather than the buttons themselves because the visible boxes are
     24px and 22px wide and the pitch arithmetic above is derived from those
     numbers -- painting the chip on the button would mean resizing it, which moves
     the pitch, and would give the two controls differently shaped chips. A
     pseudo-element is inert to layout, the same reason ::before carries the hit
     area. It is a *second* pseudo-element because ::before is 44px and
     deliberately off-centre on the glyph (it grows outward to avoid overlapping
     its neighbour), which is the wrong box to paint.

     `isolation: isolate` is load-bearing. ::after generates after the button's
     content, so it paints over the glyph; `z-index: -1` puts it behind. Without a
     stacking context on the button, a negative z-index child escapes to the
     nearest ancestor that has one and would paint behind the header's own
     --page-bg-middle fill, i.e. vanish. Isolating keeps it inside the button,
     where painting order puts it above the button's (absent) background and below
     the glyph.

     Gated on `@media (hover: hover)` -- the only such gate in this theme, and it
     is here for a specific reason rather than as a habit. These two controls exist
     only below 54.9em, which on real hardware means a touch screen, and a touch
     browser leaves :hover asserted on the last-tapped element. Ungated, tapping
     search would open the row and strand a grey chip under the magnifier until the
     next tap elsewhere. :active is not gated: a pressed state is correct on touch,
     and it is what gives phones their feedback here.

     Left as its own rule rather than folded into the one above so each pseudo
     element keeps its own explanation, but it leans on that rule's
     `position: relative` for the containing block -- the hit area needs it too, so
     there is no case where one survives without the other. */
  .kb-search-toggle,
  .nav-toggle-btn {
    isolation: isolate;

    &::after {
      content: '';
      position: absolute;
      z-index: -1;
      inset-block-start: 50%;
      inset-inline-start: 50%;
      transform: translate(-50%, -50%);
      inline-size: calc(var(--kb-icon-pitch) - 2.5px);
      block-size: calc(var(--kb-icon-pitch) - 2.5px);
      border-radius: var(--radius-round);
      background-color: transparent;
      transition: background-color var(--motion-duration-control)
        var(--ease-in-out);
    }

    @media (hover: hover) {
      &:hover::after {
        background-color: var(--neutral-bg-component-hover);
      }
    }

    /* Instant on press, matching .btn-dropdown:active (1725) -- a click should not
       feel like it is catching up with the pointer. */
    &:active::after {
      background-color: var(--neutral-bg-component-active);
      transition-duration: var(--motion-duration-instant);
    }
  }

  /* Opens the search row. Matches the hamburger's box so the two read as a pair:
     same 4px padding, same bare-button reset, and a 1rem font-size to pin the
     Font Awesome glyph at the design's 16px rather than letting it track whatever
     the header's inherited size happens to be.
     Colour is the design's #10171e == --neutral-icon-strong -- the same value the
     bars take below, and a step darker than the --neutral-icon used for the
     magnifier *inside* the field.
     `inline-size` states the 24px this already computed to (a 16px glyph plus 4px
     either side) rather than leaving it to come out of Font Awesome's glyph
     metrics. The gap above is derived from this number, and the kit swaps the <i>
     for an <svg> whose width tracks the icon's own viewBox aspect -- so an icon
     swap could otherwise move the pitch with nothing in the file explaining it. */
  .kb-search-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    cursor: pointer;
    padding: 4px;
    inline-size: 1.5rem;
    color: var(--neutral-icon-strong);
    font-size: 1rem;
    line-height: 1;
  }

  /* Same single-line treatment as the hamburger's ring below, for the same
     reason -- see that comment. */
  .kb-search-toggle:focus-visible {
    --outline-offset: 2px;
    border-radius: 2px;
  }

  .nav-toggle-btn {
    background: none;
    border: none;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 4px;
  }

  /* Was `outline: none` plus a 2px --clr-primary-100 border and a hardcoded
     #fce1d3 shadow. All three go, by deletion rather than retinting:
     1. --clr-primary-100 is the legacy salmon, which left this the only
        focusable thing in the header not ringed with --form-focus-ring.
     2. border: none -> border: 2px solid moved the hamburger 2px the instant it
        took focus. plan.md:95 traced the shift that had been blamed on .btn to
        this rule; dropping the border is the actual fix.
     3. #fce1d3 was a raw hex on no tier at all.
     Dropping `outline: none` hands the ring back to the sitewide declaration in
     css-reset.css, which already resolves to --form-focus-ring, so only the
     offset needs pinning -- the same single line the four other components kept
     when their own colour overrides were removed. An outline is safe here
     specifically: the button sits in <header>, which has no `overflow: hidden`
     ancestor to clip the ring. The radius is kept so the ring reads as
     deliberate against the 2px bars. */
  .nav-toggle-btn:focus-visible {
    --outline-offset: 2px;
    border-radius: 2px;
  }

  /* Both values come off the mobile frames. The colour was #333333
     (--clr-neutral-160); the design draws this icon in #10171e, which is
     --neutral-icon-strong on the Codex tier.
     The width was 1.214231rem == 19.43px, a figure with no apparent source. The
     design uses Font Awesome's own fa-bars, which is 14px wide at a 16px
     font-size -- measured as exactly 14px of ink in both frames -- so 0.875rem.
     The height and gaps already matched: 3 bars of 2px with 5px between them is
     the glyph's 16px. */
  .bar {
    display: block;
    background-color: var(--neutral-icon-strong);
    height: 0.125rem;
    width: 0.875rem;
    border-radius: 2px;
    /* Was 300ms ease-in-out, while .header-nav's clip-path below was 350ms -- the
       two animate as one gesture, so the bars finished folding 50ms before the
       panel finished opening. Both are --motion-duration-moderate now, which also
       puts them under the reduced-motion zeroing in css-brand-tokens.css; a
       rotating-and-collapsing hamburger is exactly the kind of motion that setting
       exists to switch off, and neither was honouring it. */
    transition:
      transform var(--motion-duration-moderate) var(--ease-in-out),
      opacity var(--motion-duration-moderate) var(--ease-in-out);
  }

  .bar:nth-child(1).is-open {
    transform: translateY(7px) rotate(45deg);
  }
  .bar:nth-child(2).is-open {
    opacity: 0;
  }
  .bar:nth-child(3).is-open {
    transform: translateY(-7px) rotate(-45deg);
  }

  .mobile-navbar .search-wrapper {
    position: relative;
    display: block;
  }

  /* The mobile search field, retuned against the expanded frame (6654:3292).
     Four hardcoded hexes had already been cleared off this rule onto the Codex
     form tier: #c2c2c2 -> --form-border, #333333 -> --form-border-active (Codex
     has no --form-border-hover; "active" is its name for the engaged border and
     hover is the first step into engaged), #ef4623 -> --form-focus-ring so the
     border agrees with the ring drawn around it, and #facac0 deleted outright
     along with `outline: none` -- the sitewide ring in css-reset.css is the focus
     cue now, exactly as it is everywhere else, so only the offset is pinned.
     What the mobile frames change, now that there is one to measure:
     - 2px border -> 1px (--border-width-regular), the width the frame draws.
       Still constant across all three states, so nothing here shifts layout.
     - 0.25rem radius -> --radius-round. The frame's corners are fully round,
       matching the 16rem pill the desktop navbar search uses.
     - `padding: 1rem 3rem` -> a fixed 2.5rem height with asymmetric insets. The
       frame's field is 40px tall, its placeholder starts 39px in from the border
       box (1px border + 2.375rem) and the in-field magnifier sits 12px in, which
       vertical padding alone cannot express.
     - --form-bg and --neutral-text added: this was inheriting whatever the reset
       gives an input, which happened to look right on white but is not stated.
     One measured conflict, left as the Codex value on purpose: the frame resolves
     its `form/border` variable to #96a6a9, but the 1440px navbar frame resolves
     the *same* variable to #809094, which is what --form-border is. Confirmed in
     the rendered pixels, not just the variable dump. #96a6a9 is
     --neutral-icon-soft, i.e. a lighter role than a form border. Taking
     --form-border keeps one border colour across both breakpoints and follows the
     "Codex tokens win over literal hex" call; worth a look in preview, since on
     mobile it reads one step darker than drawn. */
  .mobile-navbar .home-search input {
    border: var(--border-width-regular) solid var(--form-border);
    border-radius: var(--radius-round);
    height: 2.5rem;
    padding-block: 0;
    padding-inline: 2.375rem 1rem;
    width: 100%;
    background-color: var(--form-bg);
    color: var(--neutral-text);
    font-family: var(--brand-font-family-sans), system-ui, sans-serif;
    font-size: var(--step-0);
    line-height: 1.4;
    /* The header sets `text-align: center`, inputs inherit text-align, and this
       row is a direct header child now rather than a child of the nav panel
       (which resets to start). Without this the placeholder floats in the middle
       of the field instead of sitting just past the magnifier, where the frame
       puts it. */
    text-align: start;
    /* Was a bare `300ms` with no easing on either property, so both ran on the UA
       default `ease`. Codex's own default duration rather than the longer
       --motion-duration-moderate: this fires on focus, and a focus cue that takes a
       third of a second to arrive reads as lag rather than as polish.
       --ease-out rather than Codex's --default-transition-timing-function, which is
       ease-in-out: the field is responding to the user, so it should leave
       immediately and settle, not ramp up first.
       `box-shadow` swapped for `background-color` on 2026-10-20: this field never
       had a box-shadow to animate (the ring is the sitewide outline), and Codex's
       focus-visible rule pairs `transition: none` with the outline anyway, so the
       ring is meant to arrive instantly. The fill is the thing that now moves. */
    transition:
      border-color var(--default-transition-duration) var(--ease-out),
      background-color var(--default-transition-duration) var(--ease-out);

    /* The frame's placeholder is #4f5f63 == --neutral-text-soft. `opacity: 1`
       because the UA default fades it, which would land it short of that. */
    &::placeholder {
      color: var(--neutral-text-soft);
      opacity: 1;
    }

    /* Corrected 2026-10-20: this used to darken the border to --form-border-active,
       on the reasoning that Codex has no --form-border-hover and "active" was its
       name for the engaged border. Codex's live rule says otherwise -- hover moves
       the **fill** and nothing else:
         .kb-input-text:hover -> background-color: --background-color-form-hover
       i.e. our --form-bg-hover. The border darkening belongs to :focus alone, so
       hover and focus are now distinguishable rather than hover pre-empting the
       engaged border. */
    &:hover {
      background-color: var(--form-bg-hover);
    }

    /* Corrected 2026-10-20 against Codex's KbAutocomplete in DevTools, which
       resolves the focused field's border to --border-color-form-active, i.e.
       our --form-border-active -- not --form-focus-ring. The ring colour was
       standing in for the border colour here, which read as a blue border inside
       a blue ring; Codex draws a charcoal border inside the blue ring, with the
       2px offset between them doing the separating.
       The fill moves too: --form-bg-active, previously defined and unused. */
    &:focus {
      --outline-offset: 2px;
      border-color: var(--form-border-active);
      background-color: var(--form-bg-active);
    }
  }

  /* css-search.css:74 pins .search-button 20px down and 24px in from its
     positioned ancestor -- offsets for the old 3rem-padded, square-cornered
     field. Against the 40px pill the frame draws, the magnifier is 12px in
     (space/3) and vertically centred, which on a 16px glyph is also 12px down.
     Overridden here rather than there because css-search.css is shared with the
     hero and results-page searches, which are out of scope for this refresh.
     Font Awesome's SVG sizes off the inherited font-size, so without a font-size
     here the glyph tracks the header's.
     Retuned 2026-10-20 to Codex's KbAutocomplete, whose in-field magnifier is
     `text-brand-icon !size-3.5 left-3 top-1/2 -translate-y-1/2`:
     - Colour stays --neutral-icon at rest and turns --brand-icon on focus, in the
       rule below. Codex's SVG carries `text-brand-icon` unconditionally, so a
       literal reading would paint it coral always -- but per Joy the coral is the
       engaged treatment, and an orange magnifier on an idle field reads as a call
       to action the field has not earned. The frame's near-black #384649 is
       --neutral-icon, so rest matches the frame and focus matches Codex.
     - Vertical placement is now a real centring rather than the 12px that was
       derived *as* centring for a 16px glyph in a 40px field. It no longer needs
       re-deriving when either number moves -- which it just did.
     - **Size stays the frame's 16px.** Codex's `!size-3.5` is 14px, so this is a
       Figma/Codex conflict -- and the standing rule is that **Figma wins on
       sizes unless Joy says otherwise** (colour and interaction still follow
       Codex, which is why the glyph's coral and its focus behaviour came from the
       component while its size does not). Briefly taken to 14px on 2026-10-20 and
       reverted the same day.
       Keeping 16px also keeps the centring below exact: a 16px glyph in the
       frame's 40px field centres at precisely the 12px the frame draws, where
       14px would have landed on 13px and silently disagreed with it. */
  .mobile-navbar .search-wrapper .search-button {
    inset-block-start: 50%;
    transform: translateY(-50%);
    inset-inline-start: 0.75rem;
    color: var(--neutral-icon);
    font-size: 1rem;
    line-height: 1;
    /* Same reason as the desktop glyph: this <i> is decorative but it is
       absolutely positioned over the start of the field with `z-index: 10`
       (css-search.css:74-76), so without this a tap on the magnifier -- the most
       obvious place to aim at a search box -- lands on the icon and never focuses
       the input. Costs nothing, since there is nothing here to interact with. */
    pointer-events: none;
  }

  /* Engaged glyph, mobile. Keyed on the wrapper because .search-button is a
     sibling of the field rather than inside it (header.hbs:70-78), the same
     structure that made the desktop rule a no-op. :focus, not :focus-visible, so
     the glyph turns with the field's own fill and border. */
  .mobile-navbar .search-wrapper:has(input:focus) .search-button {
    color: var(--brand-icon);
  }

  /* The hamburger panel. Reveal was `clip-path: inset(0 0 100% 0)` wiping to
     `inset(0 0 0% 0)`; it is a fade-and-settle now, and the reason is the floating
     shadow the mobile frames added (6643:6824). **clip-path clips to the border
     box**, so `inset(0 0 0 0)` -- the fully-open state -- would cut a drop shadow
     off permanently, not just mid-animation. There is no inset value that reveals
     the panel *and* lets the shadow paint, and `clip-path: none` cannot be
     transitioned to.
     Dropping clip-path costs nothing in accessibility, which is the part worth
     stating: clipped regions are not hit-testable, so clip-path was doing double
     duty as a pointer guard. hamburger-nav.js already sets and clears `inert` on
     this element -- and `inert` blocks pointer events, focus and the a11y tree,
     all of which a clip never touched. The markup also ships with `inert` present,
     so a panel whose JS never loads stays unreachable rather than becoming an
     invisible click target. Appearance is all this needs to handle.
     transform is translate-only: scaling a panel that contains text re-rasterises
     the glyphs for the duration of the tween, which on a 176px card reads as the
     labels going briefly fuzzy. */
  .header-nav {
    position: absolute;
    text-align: center;
    inset-block-start: 100%;
    inset-inline-end: 0;
    width: 100%;
    justify-content: flex-end;
    opacity: 0;
    transform: translateY(-0.5rem);
  }

  /* **The transition is declared here, on the open state, and not on the rule
     above -- that placement is the fix for a flash when the viewport crosses the
     breakpoint, and moving it back reintroduces that bug.**

     A transition fires on any computed-value change, including one caused by a
     media query flipping, and it is the *after-change* style that decides whether
     one runs. With the transition on the base rule, dragging the window from
     desktop to mobile animated this panel's opacity from 1 (the desktop block's
     reset) down to 0 over 250ms -- a quarter-second of the mobile card fading out
     over the hero, which reads as the menu having opened by itself. Declaring the
     transition only on `.is-open` means every breakpoint crossing lands on a style
     with no transition at all, so opacity snaps and there is nothing to see.

     The cost is that the panel eases open and snaps shut rather than easing both
     ways. That used to be cited as the theme's existing motion idiom -- `.btn:hover`
     carried `transition-duration` on its own, for the same effect. It no longer
     does: css-buttons.css moved the transition onto the base rule on 2026-10-24,
     because a duration declared only in the hover state reverts to 0s the moment
     the pointer leaves, so the button was easing in and snapping back out. So this
     is a deliberate exception now rather than the house style, and the reason it
     stays is the breakpoint problem above and not precedent. Reverting means a
     transition on the base rule plus JS to suppress it across the breakpoint,
     which is a lot of machinery for the closing half of one fade.

     Duration matches .bar, so the hamburger folds and the panel arrives together.
     See the note there -- the two were 50ms apart before the token swap. */
  .header-nav.is-open {
    opacity: 1;
    transform: none;
    transition:
      opacity var(--motion-duration-moderate) var(--ease-in-out),
      transform var(--motion-duration-moderate) var(--ease-in-out);
  }

  nav ul {
    margin: 0;
    list-style: none;
  }

  /* 54.9em, not the 54.89em this file used to carry and that every other stylesheet
     in the theme still does. **54.89em and 54.9em leave a 0.01em gap -- 878.24px to
     878.4px -- in which NEITHER this block nor the min-width block below matches.**
     Media queries are resolved against the initial font size, so those are fixed
     pixel values, and a drag-resize passes straight through the gap.

     What rendered in there was its own little horror, and it is the flash Joy
     reported: the hamburger visible (`.nav-toggle-btn { display: none }` is in the
     desktop block), the nav absolutely positioned at `width: 100%` with the base
     rule's white fill, and the support row falling all the way back to bare `.btn`
     -- whose `--bg` is `--clr-primary-100`, the brand orange. A full-width white
     panel with an orange pill in it, which is exactly "the mobile dropdown is open
     and the top item is orange".

     Fixed by overlapping rather than by trying to make the two queries exactly
     complementary. At precisely 54.9em both blocks now match and the desktop one
     wins on source order, which is the correct side of the line and is
     deterministic. The alternative was range syntax -- `(width < 54.9em)` and
     `(width >= 54.9em)`, which is genuinely gapless -- but its failure mode is
     unacceptable here: a browser that does not understand it (Safari before 16.4)
     matches neither query at *every* width and renders the gap state permanently.
     An overlap of one hairline degrades to nothing; a gap of one hairline degrades
     to a visible bug, and unsupported range syntax degrades to a broken header.

     **The same gap exists wherever a 54.89em block is paired with a 54.9em one.
     Right now that is only this file** -- css-buttons.css, css-page-custom.css and
     css-page-forethought.css all use 54.89em with no min-width partner, so their
     rules simply stop applying 0.16px early with nothing contradicting them. Worth
     sweeping to one shared breakpoint token in the template phase; not worth
     touching three shared stylesheets for today. */
  @media screen and (max-width: 54.9em) {
    header,
    .header-nav {
      grid-column: none !important;
      display: flex;
    }

    /* `flex-wrap` is what lets the search row open as its own line. The header's
       flex children are the logo, the controls pair and the search row -- the nav
       panel is absolutely positioned and so is not in the flow. The row carries
       `width: 100%`, which cannot share a line with the logo, so it wraps onto a
       second one and the header grows to fit it. That growth is the point: the
       expanded frame pushes the page down rather than overlaying it, which is the
       opposite of how the hamburger panel behaves. */
    header {
      flex-wrap: wrap;
      align-items: center;
    }

    /* The panel is a floating card now, not a full-bleed drawer -- Figma
       6643:6824 ("Mobile - Hamburger Menu Home"), with 6643:7113 and 6643:7256 for
       the expanded and highlighted language states.
       Replaces `width: 100%` + `padding: 1.5rem` + a `--clr-neutral-40` bottom
       rule, i.e. a strip spanning the viewport with a hairline under it.

       The frame is 176px wide and holds a 175px menu whose rows are 168px at x=3.5
       -- the half-pixels are Figma rendering the 1px border, and the self-consistent
       reading is 4px of padding (space/1) + 168px row + 4px = 176px.

       **Both of those numbers are deliberately not used literally.** The frame was
       drawn with "English (US)"; the real control renders `{{current_locale.name}}`,
       which for this locale is "English (United States)" -- 151px at 14px Inter.
       Add the row's own 10px of padding either side and 176px is already overflowing
       before the card's padding is counted, which is why the label was touching the
       card's edge in preview.

       **The card fills the header's content width.** This went through an
       intermediate state worth recording, because the bug it had is instructive:
       `inline-size: max-content` with an 11rem floor and a `calc(100% - 3rem)` cap,
       so the card sized to its own widest row. Two problems, one reported and one
       structural.

       Reported: the card changed width when the language submenu opened. `max-content`
       is measured over whatever is in flow, and the submenu's rows are only in flow
       when Alpine's `x-show` is not holding them at `display: none` -- and they are
       wider than the trigger row, since they carry a locale name plus the 29px indent.
       So opening the submenu grew the card and the whole menu shifted under the thumb
       that had just tapped it. Any content-based width has this property; the fix is
       for the width not to depend on content at all.

       Structural: a touch target is two-dimensional, and only its height was ever
       addressed. A 176px column pinned to the right edge of the viewport makes a
       thumb aim at the far corner for rows whose labels occupy a strip of that. A
       narrow floating card is a *pointer* idiom -- it exists on desktop because a
       menu that spanned a 1440px window would be absurd, and because a mouse can hit
       a 20px row anywhere. Neither constraint applies on a phone, where horizontal
       space is free and the convention is a full-width sheet or drawer (Material's
       navigation drawer, iOS's action sheets). So the card is now the header's content
       width -- `calc(100% - 3rem)` against the header's 1.5rem padding either side,
       which lines its edges up with the wordmark and the hamburger. Fixed width, so
       the submenu cannot move it; full width, so every row is a target the width of
       the screen.

       Still a card and not a bled-to-the-edge drawer: it keeps the frame's border,
       radius and floating shadow, and reads as attached to the control that opened it
       rather than as a new screen.

       The padding is 0.5rem (space/2) rather than the frame's 4px: at 4px the rows
       have almost no air against the card's edge, which reads as cramped at a glance
       even when the arithmetic fits. 8px is one step up the same scale.
       `box-sizing: border-box` is global (css-reset.css:5), so the border is inside
       whatever width this resolves to.

       --shadow-floating is the one composite shadow token in Codex that is not
       broken by the self-referential-var cycle described in css-brand-tokens.css,
       because it suffixes its colour token -color. So unlike .navbar-btn's, this
       shadow can be assembled straight from the four tokens the frame names.

       inset-inline-end is 1.5rem, not 0: the frame does not show the header, and
       1.5rem lines the card's right edge up with the header's own padding, which is
       the same inset the hamburger's ink sits on. Flush to the viewport edge would
       put a rounded, shadowed card half off-screen.

       inset-block-start claws back 1rem of the base rule's `100%`. `100%` is the
       header's full height including its trailing 1.5rem of padding, which left ~26px
       of empty air between the close glyph and the card -- enough to read as
       unattached to the control that opened it. Pulling up 1rem leaves ~10px, close
       enough to look connected while still clearing the glyph. Expressed against the
       header's own padding rather than as an absolute offset, so it tracks the header
       if that padding ever changes; the search row can no longer be open at the same
       time (see hamburger-nav.js), so `100%` is a stable 75px here. */
    .header-nav {
      inline-size: calc(100% - 3rem);
      inset-block-start: calc(100% - 1rem);
      inset-inline-end: 1.5rem;
      padding: 0.5rem;
      flex-direction: column;
      align-items: stretch;
      background: var(--neutral-bg);
      border: var(--border-width-regular) solid var(--neutral-border-soft);
      border-radius: var(--radius-sm);
      box-shadow: 0 var(--shadow-floating-y-offset) var(--shadow-floating-blur)
        var(--shadow-floating-spread) var(--shadow-floating-color);
      /* The frame left-aligns every row. The base rule's `text-align: center` is a
         holdover from the full-width drawer. */
      text-align: start;
    }

    /* Both frames show the wordmark alone -- 144x27 at x=24, with no divider and
       no "Knowledge Base" label. Measured off the rendered PNGs, where the logo's
       ink stops at x=167 in the collapsed *and* expanded frames.
       Hidden rather than deleted from the markup: the label is wanted from 54.9em
       up, and the logo's own alt text already carries the help centre name, so
       nothing accessible is lost by dropping it here. */
    .logo .pipe,
    .logo .logo-text {
      display: none;
    }

    /* Closed by default, opened by mobile-search.js adding .is-open. Both rules
       are scoped to this breakpoint on purpose, so that a row left open and then
       resized past 54.9em cannot out-specify the `.mobile-navbar { display: none }`
       in the wide block below.
       The 1.5rem is the frame's 24px between the logo row and the field. With the
       header's own 1.5rem padding that totals 24 + 27 + 24 + 40 + 24 == 139px, the
       expanded frame's exact height.
       This used to have to beat `.home-page .mobile-navbar { display: none }` in
       css-search.css, which hid the mobile field on the home page because the hero
       search served instead -- the expanded frame *is* the home page, so that had
       stopped holding. It won on load order (css-page-header.css is linked after
       css-search.css in document_head.hbs), which is why no !important was needed.
       That rule was deleted on 2026-10-21 along with the rest of the per-template
       search gating, so there is nothing left to beat. */
    .kb-search-row {
      display: none;
      width: 100%;
      margin-block-start: 1.5rem;
    }

    .kb-search-row.is-open {
      display: block;
    }

    .mobile-navbar {
      width: 100%;
    }

    /* Margin dropped to 0: the gap above the field belongs to .kb-search-row now,
       so that it disappears along with the row instead of padding the header. */
    .mobile-navbar .home-search {
      width: 100%;
      position: relative;
      margin-block-start: 0;
    }

    /* align-items was flex-end, which right-aligned the trigger and hung the
       submenu off the card's trailing edge -- correct for the old full-bleed drawer,
       wrong for 6643:7113, where the user row and its children are flush left like
       every other row. The flex column itself stays: it is what stacks the submenu
       under the trigger now that the submenu is in flow.
       `.dropdown-user { border: none; padding: 0 }` lived here and is gone: the
       submenu rules below reset the same two properties as part of stripping
       .dropdown's card styling, so keeping it would have meant two rules in one file
       zeroing the same border. */
    .navbar-item.user {
      display: flex;
      flex-direction: column;
      align-items: stretch;
    }

    /* align-items was flex-end and the gap 1.25rem -- right-aligned labels with
       20px of air, which is what the full-bleed drawer wanted. The frame stretches
       every row to the card's full width and sets them 38px apart on a 36px row,
       i.e. a 2px gap. Stretch matters beyond alignment: it is what lets a row's
       hover fill span the card instead of hugging its text. */
    .menu {
      display: flex;
      flex-direction: column;
      align-items: stretch;
      gap: 0.125rem;
    }

    /* `margin-inline-start: 1em` is gone: the card's own 0.25rem of padding is the
       only inset the frame shows, and a per-item indent would both narrow the rows
       and stop their fills reaching the card's left edge.
       Also dropped a dead `&:hover { transition-duration: var(--transition-duration) }`
       from this rule. It set a duration and nothing else: no transition-property
       (so `all`), and nothing on the <li> itself changes on hover -- the colour
       changes belong to the <a>/.navbar-btn inside it, and transitions do not
       inherit, so a duration on the parent could never reach them. */
    nav li {
      margin-inline-start: 0;
    }

    /* SECTION - Hamburger menu rows */

    /* One shape for all three rows, because the frame draws one: --radius-sm,
       8px/10px padding, an icon then a label 8px apart, and the label in
       body/regular/base (Inter 400, 14px, 1.4). The three selectors are the three
       things header.hbs puts in this list -- the support link, the language trigger
       and the user/sign-in control -- which otherwise arrive as a .btn pill, a
       transparent dropdown trigger and a bare button respectively.
       Matched as descendants of .header-nav so none of this escapes to the desktop
       bar, where the same classes are a brand pill and two chrome-less triggers.

       **48px tall and 16px, not the frame's 36px and 14px**, and the row box is where
       these rows deliberately leave the design. The relevant published figures:
       Apple's HIG asks for 44x44pt, Material for 48x48dp, WCAG 2.5.5 (Target Size,
       Enhanced, AAA) for 44x44 CSS px, and WCAG 2.5.8 (Target Size, Minimum, AA, new
       in 2.2) for 24x24. 36px clears only the last of those. 48px clears all four and
       is the larger of the two platform figures, which is the right side to err on in
       a menu that is the only way to reach the language picker or sign out.

       The type went with it. 14px is Codex's body size and correct in running text,
       but these are discrete tap targets read at arm's length, and 16px is the
       de-facto mobile body size on both platforms. It also stops a 48px row from
       looking like a 14px label floating in a box.

       The frame's 2px gaps are untouched: each row is its own 48px target and they
       abut, so widening the gaps would only insert dead strips between them.

       height, not min-height: .btn-dropdown carries a height that has to be
       overridden rather than merely raised, and these rows hold one line each.

       The transparent 1px border is reserved, not decorative -- the hover state below
       draws an edge, and a border that only exists on hover would shift the label a
       pixel as the pointer arrives. Padding is 7px/9px so that border plus padding
       puts the ink back on the frame's 8px/10px. Same trick, same numbers, as the
       submenu rows further down; the two sets of rows are one shape at two sizes.

       The `--*` custom props are set *and* the properties they feed are restated
       directly, which looks redundant but is not. `.btn` resolves padding, colour,
       size and border through those props (css-buttons.css:85-112), but
       css-buttons.css's own mobile block then overrides four of them with direct
       declarations on `.navbar-btn`: `padding: 0`, `border: 0`,
       `color: var(--clr-secondary-140)` and `text-decoration: underline`. Custom
       props cannot reach past a direct declaration, so the support row was still
       rendering as an oversized underlined link inside the card. The props stay for
       the two rows that *do* read them and for `.btn`'s own `:hover`/`:active`; the
       direct declarations are what actually lands on the support row.

       `--bg-image: none` joined them 2026-10-24, when `.btn` took Codex's brand
       gradient sitewide. It is not cosmetic: background-image paints *above*
       background-color, so the transparent fill these three rows rely on would
       have lost to a brand-red sweep rather than beaten it in the cascade -- the
       same hazard the scope note on the desktop pill further down warns about
       from the other side. */
    .header-nav .navbar-btn,
    .header-nav .btn-dropdown,
    .header-nav .btn-user {
      --bg: transparent;
      --bg-image: none;
      --fg: var(--neutral-text);
      --border-color: transparent;
      --radius: var(--radius-sm);
      --padding: 0.4375rem 0.5625rem;
      --size: var(--font-size-base);
      --weight: var(--font-weight-regular);
      display: flex;
      align-items: center;
      justify-content: flex-start;
      gap: 0.5rem;
      inline-size: 100%;
      height: 3rem;
      padding: 0.4375rem 0.5625rem;
      border: var(--border-width-regular) solid transparent;
      border-radius: var(--radius-sm);
      background-color: transparent;
      color: var(--neutral-text);
      font-family: var(--brand-font-family-sans), system-ui, sans-serif;
      font-size: var(--font-size-base);
      font-weight: var(--font-weight-regular);
      line-height: 1.4;
      text-align: start;
      text-decoration: none;
      transition:
        background-color var(--motion-duration-control) var(--ease-in-out),
        border-color var(--motion-duration-control) var(--ease-in-out);
    }

    /* An `!important` answering another one: css-buttons.css:581 sets
       `font-size: var(--step-1) !important` on
       `.navbar-btn` inside this same breakpoint. No specificity beats that, and
       css-buttons.css is shared with the article, category and request pages, so it
       is off-limits this phase. Isolated in its own rule rather than folded into the
       shared declarations above, so that when css-buttons.css is cleaned up in the
       template phase this is the only thing to delete.
       It is close to cosmetic now and was not before. --step-1 is fluid and lands
       between 15.75 and 16px, so against the old 14px rows the support row read a
       size and a half larger than its neighbours -- visible in preview. Against 16px
       it is within a quarter-pixel. Kept anyway: three rows resolving to one identical
       value beats two resolving to near-identical ones, and the cleanup is the same
       either way. */
    .header-nav .navbar-btn {
      font-size: var(--font-size-base) !important;
    }

    /* The frame gives rows no resting fill and the card no inner dividers, so the
       hover fill is the only affordance a row has.

       This was --neutral-bg-component-soft (#f2fdfd), the softest step on Codex's
       component ramp, on the reasoning that a row inside an already-raised surface
       wants the quietest available tint. In preview that turned out to be the wrong
       call: #f2fdfd against the card's #faffff is a 1.5% difference in luminance, far
       below the 3:1 that WCAG 1.4.11 asks of a non-text indicator, and it simply was
       not visible. It also meant the two tiers of rows in one card highlighted
       differently -- the submenu rows below were already using -soft-active.

       So both tiers now share one pair of states, and they are the submenu's:
       -soft-active (#deebec) for hover and focus, with a --neutral-border-alpha edge,
       which is the treatment 6643:7256 draws on its highlighted child. One highlight
       in the card, visible, and taken from the design rather than invented.

       The press state steps off the soft ramp to --neutral-bg-component-active
       (#c9d7d9), because -soft-active is now spoken for by hover and a press has to be
       distinguishable from it. Noting the token-name mismatch rather than hiding it:
       the soft ramp's own -hover/-active steps are #e5f2f3/#deebec, so this pair sits
       one step up the family from where the names suggest. The names describe a
       component on the page background; these rows sit on a raised card, where that
       whole ramp lands a step too quiet.

       Replaces `nav li .navbar-btn:hover`'s legacy --clr-neutral-5 fill with
       --clr-neutral-160 text, which also recoloured the label on hover. The frame
       keeps the label --neutral-text throughout.

       :focus-visible is in the selector so a keyboard user gets the fill too, not just
       the sitewide outline -- the submenu rows have always done this and the top-level
       rows were the inconsistent ones. */
    .header-nav .navbar-btn:hover,
    .header-nav .navbar-btn:focus-visible,
    .header-nav .btn-dropdown:hover,
    .header-nav .btn-dropdown:focus-visible,
    .header-nav .btn-user:hover,
    .header-nav .btn-user:focus-visible {
      --bg: var(--neutral-bg-component-soft-active);
      background-color: var(--neutral-bg-component-soft-active);
      --border-color: var(--neutral-border-alpha);
      border-color: var(--neutral-border-alpha);
      --fg: var(--neutral-text);
      color: var(--neutral-text);
    }

    .header-nav .navbar-btn:active,
    .header-nav .btn-dropdown:active,
    .header-nav .btn-user:active {
      --bg: var(--neutral-bg-component-active);
      background-color: var(--neutral-bg-component-active);
      transition-duration: var(--motion-duration-instant);
    }

    /* The frame's row icons are 12px (font/size/xs) in --neutral-icon-strong, in a
       fixed column ahead of the label. Matched on the fa-* classes rather than a bare
       `i`, since the kit JS has already swapped each <i> for an <svg>.
       `flex: none` stops a long locale name squeezing the glyph.

       The column is 24px wide and the glyph 14px inside it, which is why the padding
       and the font-size disagree. A 12px column would be the frame's own figure, but
       the signed-in row's slot holds a photograph (see below) that cannot shrink to
       12px and stay recognisable -- so every label on that row started 8px to the
       right of its neighbours'. Sizing the *column* off the widest occupant and
       drawing the glyph small inside it puts all three labels back on one x. Both
       numbers scaled with the 48px rows: 20/12 against 44px rows became 24/14, which
       keeps the glyph at the same fraction of the row it had before.
       The glyph is centred by the horizontal padding; `box-sizing: border-box` is
       global (css-reset.css:5), so 24px minus 5px either side leaves 14px of ink. An
       <svg> is a replaced element, so padding scales its contents the same way it
       would an <img>.

       The signed-out row's glyph is matched here too, and it changed on 2026-10-21
       when header.hbs went from fa-circle-user to fa-user for the desktop avatar
       (see .kb-user-chip in the wide block). Mobile gains from the same change: this
       column wants a person, which is what the note below says the frame draws, not
       the filled disc circle-user was putting beside the globe and the page glyphs.
       Nothing here needed editing then and nothing needed it on 2026-10-22 either,
       when that glyph gained a wrapper: the selector matches on `[class*='fa-']`, so
       it reaches the <i> whatever its ancestry, and the wrapper is `display:
       contents` at this breakpoint so the <i> is still this row's own flex child.
       See the chip rule at :1091 for that half. */
    .header-nav .menu [class*='fa-'] {
      flex: none;
      /* No `order` here, deliberately. The support link's glyph used to follow its
         label in the markup and was pulled back visually with `order: -1`; it is now
         first in header.hbs instead, so DOM order and the frame's visual order agree
         on every row and there is nothing for the cascade to compensate for. See the
         note in header.hbs for why that is worth a markup change even though the
         reordering was harmless. */
      inline-size: 1.5rem;
      block-size: auto;
      padding-block: 0;
      padding-inline: 0.3125rem;
      font-size: 0.875rem;
      color: var(--neutral-icon-strong);
    }

    /* Signed in, the row's glyph slot holds a photo rather than an icon, and nothing
       was sizing it here: `.user .avatar-img`'s 36px is in the wide block, so mobile
       fell through to `.avatar-img { width: 2rem }` (css-buttons.css:134) -- 32px of
       image in a row that was 36px tall with 8px of padding, so it overflowed.
       24px, not the frame's 12px: the frame only draws the signed-*out* row, where
       the glyph is a line-art person that reads fine at 12px. A cropped photograph
       does not. This is the measurement the icon column above is sized from, so the
       two have to stay in step -- change one and the labels stop aligning. */
    .header-nav .menu .avatar-img {
      flex: none;
      inline-size: 1.5rem;
      block-size: 1.5rem;
    }

    /* No chevrons. None of the three frames draws one -- not on the language row in
       6643:6824, not on it in 6643:7113 with its submenu open, and not on Sign In.
       Each row is a glyph and a label and nothing else.
       Worth being explicit that this costs something: the chevron was the only
       resting cue that the language and user rows open a submenu rather than
       navigating. The `aria-haspopup` / `aria-expanded` pair on the button keeps that
       information for screen readers, and the submenu appearing in flow is the
       feedback after the tap, but a sighted user gets no hint before it. Reverting is
       deleting this rule and restoring `margin-inline-start: auto`, which pushed each
       chevron to the row's trailing edge.
       Hidden here rather than in the markup because the chevrons are wanted nowhere:
       the wide block hides them too (.btn-dropdown .fa-chevron-down), where the globe
       chip and the avatar are the affordance. */
    .header-nav .fa-chevron-down {
      display: none;
    }

    /* There is a mobile frame for the globe now, and it draws a bare 12px glyph
       rather than the 40px chip the 1440px navbar wraps it in. So the chip keeps
       its icon and loses everything that makes it a chip -- previously this rule
       was `display: none`, hiding the globe along with it, on the stated grounds
       that no mobile frame existed to place it in. 6643:6824 is that frame.

       The signed-out avatar's chip is here too, for the same reason and with the
       same effect: the mobile frame draws a bare person in the icon column, not a
       circle. `display: contents` is what makes a wrapper free below this
       breakpoint -- the <i> inside becomes a direct flex child of the row again, so
       `.header-nav .menu [class*='fa-']` at :1030 keeps reaching it and the column
       is laid out exactly as it was when the glyph had no wrapper at all. Without
       this the span would itself be the flex item and the glyph would sit inside an
       unstyled inline box, out of the column's reach. */
    .kb-locale-chip,
    .kb-user-chip {
      display: contents;
    }

    /*!SECTION - Hamburger menu rows */

    /* SECTION - Hamburger submenus */

    /* Figma 6643:7113 ("Mobile - Hamburger Menu Expanded Language") draws the open
       language list *in flow*: the list frame grows from 250px to 327px and pushes
       Contact Support and Sign In down, with the card itself then scrolling. So the
       popover positioning comes off. .dropdown-container is `position: absolute`
       (css-buttons.css:182) and the mobile block adds `inset-inline-end: 20px` (290);
       `inset: auto` clears the latter, and the `top`/`bottom: auto !important` pair at
       184-185 is moot once the box is static.

       0.375rem of block padding is the frame's 6px: the grandchild-menu instance is
       168 tall around a 156px run of children, i.e. 6 + 156 + 6. The 2px above it is
       .menu's gap, which cannot reach inside the <li> that holds both the trigger and
       its submenu, so it is restated as a margin.

       The signed-in user's menu gets the same treatment: it is the same
       "grandchild-menu" component one <li> over, with My Requests and Sign Out
       standing to the user row as the locales do to the language row. Leaving it as a
       white popover floating over a card whose other submenu is inlined would have
       looked like two different menus. Its container turns absolute again from 45em up
       (css-buttons.css:244-247) -- a (0,1,0) rule, out-specified here rather than
       fought with !important. */
    .header-nav .dropdown-container-language,
    .header-nav .dropdown-container-user {
      position: static;
      inset: auto;
      inline-size: 100%;
      margin-block-start: 0.125rem;
      padding-block: 0.375rem;
    }

    /* Both lists carry .dropdown, which is a bordered white card in its own right
       (css-buttons.css:170-179) -- a card inside a card. Everything that made it one
       comes off: the 1px --clr-neutral-40 border, the 0.25rem radius, the white fill
       and the 0.5rem top margin, which the 6px pad above now supplies. `display: flex`
       replaces its `inline-flex` so the list fills the card rather than shrink-wrapping
       its longest locale name, and the 0.5rem gap drops to the frame's 4px (space/1).

       max-height is the frame's *four rows and three gaps*, rescaled: the frame's
       156px is 4x36 + 3x4, and at the 48px rows these menus actually use that becomes
       4x48 + 3x4 = 204px. Four rows is the prescribed figure, not 156px -- the
       scrollbar instance in the frame is 156 tall and aligned to exactly that run,
       which is what rules out four sample children being an accident. A fifth locale
       is cut mid-row and reads as scrollable either way. Replaces both
       `max-height: 48vh` (css-buttons.css:210) and the mobile block's 20rem (310).
       `overflow-y: auto`, not scroll: with twelve locales the track is wanted, with two
       it is not.

       The frame's thumb is 6px of --scroll-thumb at --radius-round. Only the colour is
       actually ours to set: scrollbar-color is the only route to it in Firefox, and
       setting either standard property makes Chrome ignore ::-webkit-scrollbar
       wholesale, so the webkit pseudo-elements are deliberately absent rather than
       written and silently dropped. The width and the thumb's rounding come from the
       UA instead -- `thin` lands near 6-8px depending on the browser, and every
       current engine draws that thumb rounded. One implementation that is approximate
       beats two that disagree.

       scroll-padding-block keeps a focused locale off the scroller's clipping edge.
       An outline is clipped by an overflowing ancestor, so without it the sitewide
       focus ring (css-reset.css:19) would be shaved on whichever row scrolling
       happens to land flush against the top or bottom. */
    .header-nav .languages-list,
    .header-nav .dropdown-user {
      display: flex;
      flex-direction: column;
      gap: 0.25rem;
      border: none;
      border-radius: 0;
      background-color: transparent;
      /* Added 2026-10-22 alongside the rest of this reset, when .dropdown picked
         up the search panel's --shadow-floating-* stack. Mobile draws this list
         *in flow* inside the hamburger card, and a floating shadow on something
         that is not floating reads as a seam across the card. */
      box-shadow: none;
      margin-block-start: 0;
      padding: 0;
      padding-inline-start: 0.625rem;
      max-height: 12.75rem;
      overflow-y: auto;
      overflow-x: hidden;
      scroll-padding-block: 0.25rem;
      scrollbar-width: thin;
      scrollbar-color: var(--scroll-thumb) transparent;
    }

    /* The vertical rule. In the frame it is a 156px line at x=18 inside a 29px-wide
       "Divider Container", with the children's slot starting at x=29 -- so 18px of
       indent, the 1px rule, then 10px (space/2-5) to the child's border box.
       A real border-inline-start rather than a pseudo-element, because the rule is
       structural: it is the indent, not decoration laid over it.
       Hosted on the wrapper *outside* the scroll box so it spans the children's run
       and not the 6px pads, which is what the frame shows -- and so that it does not
       scroll with the list. .dropdown-language is the role="region" wrapper and
       carries nothing else. The user menu has no equivalent wrapper, so .dropdown-user
       hosts the rule itself, which is why this rule follows the reset above instead of
       preceding it: `border: none` there would otherwise erase it. */
    .header-nav .dropdown-language,
    .header-nav .dropdown-user {
      margin-inline-start: 1.125rem;
      border-inline-start: var(--border-width-regular) solid
        var(--neutral-border-soft);
    }

    /* The grandchild rows: the same box as their parents but softer --
       --neutral-text-soft, which is what makes the frame's "Grand Child" labels read
       grey against Contact Support's black -- on --radius-sm.

       48px and 16px for the same reason the parent rows are (see the touch-target note
       up in the rows section). These are the rows with the *most* reason to be
       comfortable: the language list is the one place a user scrolls and taps
       repeatedly, and mis-tapping a locale navigates the whole help centre into a
       language they cannot read back out of.

       The padding is 7px/9px, not the frame's 8px/10px, because of the transparent 1px
       border. 6643:7256 gives the highlighted row a --neutral-border-alpha edge, and
       reserving that edge on every row is what stops the label jumping a pixel as the
       pointer arrives; border plus padding puts the ink back on the frame's 8 and 10.
       min-height rather than height: a locale name long enough to wrap should take two
       lines rather than overflow its row. */
    .header-nav .dropdown-language-item,
    .header-nav .dropdown-user .dropdown-item {
      display: flex;
      align-items: center;
      min-height: 3rem;
      padding: 0.4375rem 0.5625rem;
      border: var(--border-width-regular) solid transparent;
      border-radius: var(--radius-sm);
      background-color: transparent;
      color: var(--neutral-text-soft);
      font-family: var(--brand-font-family-sans), system-ui, sans-serif;
      font-size: var(--font-size-base);
      font-weight: var(--font-weight-regular);
      line-height: 1.4;
      text-decoration: none;
      transition:
        background-color var(--motion-duration-control) var(--ease-in-out),
        border-color var(--motion-duration-control) var(--ease-in-out),
        color var(--motion-duration-control) var(--ease-in-out);
    }

    /* 6643:7256 is 6643:7113 with one child highlighted, and it is wired to hover and
       keyboard focus rather than to a current-language state. It cannot be the latter:
       header.hbs iterates {{#each alternative_locales}}, which Zendesk defines as every
       locale *except* the active one, so the selected language is never in this list --
       it is the label on the trigger row above it. Hover and focus are the only states
       a list of alternatives can be in.
       (If the current locale should appear here with this treatment, that is a template
       change -- the list has to be built from all locales rather than the alternatives
       -- and belongs with the rest of the template phase, not here.)

       Replaces a.dropdown-language-item:hover's --clr-neutral-5 fill and 0.25rem radius
       (css-buttons.css:223-227). transition-duration is restated because that rule also
       carries a bare `transition-duration: var(--transition-duration)` -- 300ms,
       css-root.css:173 -- inside :hover with no transition-property, the theme-wide
       pattern that eases in and snaps out. At (0,3,0) against this rule's (0,4,0) it
       loses, but by a single class, so the duration is pinned rather than left to the
       count. */
    .header-nav .dropdown-language-item:hover,
    .header-nav .dropdown-language-item:focus-visible,
    .header-nav .dropdown-user .dropdown-item:hover,
    .header-nav .dropdown-user .dropdown-item:focus-visible {
      background-color: var(--neutral-bg-component-soft-active);
      border-color: var(--neutral-border-alpha);
      border-radius: var(--radius-sm);
      color: var(--neutral-text);
      transition-duration: var(--motion-duration-control);
    }

    /* Carried the darker press fill down from the parent rows, which previously only
       had it here as a duration change. Both tiers of rows in this card are now one
       pair of states -- -soft-active on hover/focus, -component-active on press -- so a
       tap feels the same wherever in the menu it lands. */
    .header-nav .dropdown-language-item:active,
    .header-nav .dropdown-user .dropdown-item:active {
      background-color: var(--neutral-bg-component-active);
      transition-duration: var(--motion-duration-instant);
    }

    /*!SECTION - Hamburger submenus */

    /* Was `nav li:nth-child(3)`, which silently depended on the language <li>
       being first and always rendering -- in header.hbs the
       {{#if alternative_locales}} sits *inside* that <li>, not around it, so the
       element exists even when there are no alternative locales. The navbar build
       reorders these <li>s to match the design, which would have quietly
       retargeted this rule at the language item instead of the support links.
       Matching on .navbar-btn is stable under reordering and says what it means.
       .navbar-btn is header-only -- confirmed by grep over templates/ -- so it is
       safe to match on directly rather than through a positional selector. */
    nav li:has(> .navbar-btn) {
      margin-block-start: 0;
    }
    /* `nav li .navbar-btn { border-radius: 0 }` and its --clr-neutral-5 /
       --clr-neutral-160 hover are gone. The frame gives these rows --radius-sm and
       keeps the label --neutral-text on hover, both of which the row rules above now
       set. Removed rather than left to lose the cascade: it only lost by one class
       ((0,3,0) over (0,2,2)), which is too fine a margin to leave a contradicting
       rule sitting on.
       `.contact-support { gap: 0.25rem }` is gone for the same reason -- the frame
       spaces every row's icon and label by 8px, and one row at 4px was a holdover
       from when these were pills on a bar. */

    .navbar-search {
      display: none;
    }
  }
  /*!SECTION - Hamburger Menu */

  /* SECTION - Wide screen */
  @media screen and (min-width: 54.9em) {
    /* `padding-inline: 0` is the fix for Joy's "slight shift in the position of the
       logo" when crossing the breakpoint, and the reason is that the header was
       paying for its gutter twice.

       `header` is `.grid-content`, so at this breakpoint its children resolve to the
       `content` grid column -- which already starts `--padding-inline` (--gap-sm,
       24px at any width where either viewport axis exceeds 800px) in from the grid
       container's content box. The base rule's own `padding: 1.5rem` inset that
       content box by a further 24px, so the logo sat at 48px. The mobile block turns
       the grid off (`display: flex`), where only the header's own padding applies and
       the logo sits at 24px. Hence a 24px jump at 878.4px in both directions.

       Zeroing it here rather than adding 24px at mobile, because the Figma frames say
       this is the side that was wrong. The mobile frames draw the wordmark at x=24 --
       which is what flex + the header's own padding already produces. The navbar frame
       (6473:6479) is 1440 wide with the logo at x=184 and the cluster ending 179 from
       the right edge: ~1077px of content, symmetric. That is the content column, not a
       24px gutter -- so the desktop inset is supposed to come from the column
       *centring*, exactly like the page content below, and the extra 24px was never
       part of the design.

       What this produces, with the grid doing the work: the logo sits at
       `content-start`, and because both sides of the grid are symmetric that is simply
       `(viewport - content) / 2`. At 1440 the column is capped at 71.5rem == 1144px, so
       the logo lands at (1440 - 1144) / 2 == 148px. At 878.4px the column is viewport-
       bound, nothing is left over, and the logo is at 24px -- exactly where the mobile
       flex row puts it. The shift is gone because the two sides now agree at the
       crossing and only diverge once there is genuine free space to centre into.

       State it as the centring, not as a sum of side tracks. Each side is a 1fr gutter
       plus a breakout track, and how the free space divides between those two depends
       on --breakout-size, which has already been wrong once (css-utilities.css:753).
       The total is what positions the logo, and the total is fixed by the column being
       centred, so this derivation holds whatever the breakout track does.

       CORRECTED 2026-10-25. This block used to say the column was "125ch-capped at
       ~1080", leaving 312px free, putting the logo at 180px -- "within 4px of the
       frame's 184". Every number after the first was wrong, because 125ch was never
       1080: Open Sans's zero advance is 9.1486px at 16px, so 125ch measured 1143.6px.
       The column has always been ~1144, the free space ~248, and the logo ~148 -- 36px
       INSIDE the frame's 184, not 4px off it. Nobody caught it because the mechanism
       being described is right and the result looks correct; only the arithmetic
       validating it was invented. (The unit is now rem because the Inter swap exposed
       that `ch` is typeface-dependent -- css-utilities.css:750 -- but that change was a
       restoration, not a shift: 71.5rem is the 1144px this always resolved to.)

       AND THEN CLOSED, same day, by measuring the shipped site: THERE IS NO 184 TO
       HIT. Correcting the arithmetic above left an apparent 36px shortfall against the
       frame, and the obvious next move was to cap the column at 67rem (1072px) so the
       logo would land on 184. Measured first, which killed it. Codex's live page at a
       1688px viewport reports `max-width: none` and `padding-inline: 0` on EVERY
       element from <html> down through <main>, and its `main` is 1367px because a
       fixed 321px sidebar takes the rest -- a sidebar-plus-fluid-main shell with no
       content cap anywhere in it. Its header starts at x=321, inside that content
       area rather than spanning the window, and its wordmark sits 87.5px in from the
       header's own edge.

       So frame 6473:6479's `x=184` is a logo placed on a 1440px *artboard*, not a
       width rule the site implements, and the shell it belongs to is not the one this
       theme has. 71.5rem is OUR decision, it has been through every preview, and
       nothing in the shipped product contradicts it. Treat a different column width as
       a fresh design question if anyone raises one -- not as a correctness fix, and not
       as something Codex is owed.

       The general rule this cost us twice in one day: a Figma artboard's dimensions are
       the canvas the designer drew on, and only the live stylesheet says which of those
       numbers is a rule. Same lesson as the dark-mode ink (css-brand-tokens.css).

       Either way, do not "fix" the inset by insetting the logo on its own; that
       reintroduces the mobile/desktop jump this block exists to remove.

       Side benefit that the collision fix below depends on: this returns 48px to the
       content column at every width, which is what makes the search field viable all
       the way down to 54.9em instead of waiting for 56em.
       Block padding is unaffected -- 1rem + the 40px control row + 1rem == the
       design's 72px navbar. It was 2rem, which made it 104px. */
    header {
      align-items: center;
      padding-block: 1rem;
      padding-inline: 0;

      /* How much of the `content` column the logo gets to keep. Consumed by
         .header-nav's max-inline-size below; overridden in the squeeze band at the
         bottom of this file, where the wordmark is all that is left to reserve for.
         Declared on the header rather than on the nav so the two bands set it in one
         place each and the nav's cap is written once.
         323px is the navbar frame's `ps-logos` instance -- wordmark, divider and
         "Knowledge Base" -- plus the 20px the same frame puts between cluster items
         (256 -> 276, 430 -> 450), rounded up to a whole 0.5rem. */
      --kb-logo-reserve: 21.5rem; /* 344px == the frame's 323px logo + 20px */
    }

    /* Hides the hamburger and the search toggle together, in one rule rather than
       one each. The wrapper also has to come out of the flow here specifically
       because header is a grid at this breakpoint, so an empty wrapper would still
       take a grid cell and knock the logo and nav out of the row they now share.
       The old `.nav-toggle-btn { display: none }` is kept below it: the button is
       still a header child in spirit, and leaving the rule means removing or
       re-nesting the wrapper later cannot bring the hamburger back on desktop. */
    .kb-header-actions {
      display: none;
    }

    .nav-toggle-btn {
      display: none;
    }

    /* The structural fix the header audit called for (next-session.md, "Structure
       as it stands"). Both children already resolve to the `content` grid column
       through css-utilities.css's `.grid-content > :not(.breakout, .full-width)`;
       putting both in its FIRST ROW is what makes them share one gutter and one
       centre line, which is what the design shows.
       Two things were wrong before:
       1. .header-nav kept `position: absolute` on desktop -- the old block reset
          top/left/clip-path/background/transition around it but never the position
          itself. As an absolutely positioned grid child its containing block was
          its grid area (header is position: relative), so it was measured against
          a different box than the logo and had no reason to line up with it.
       2. `justify-content: flex-end` was inert, because .header-nav was never a
          flex container at this breakpoint -- `display: flex` is only set in the
          mobile block, and `.cluster-header` on the nav in header.hbs is not
          defined anywhere in the theme. The inner <ul> was the only flex box, so
          the items packed to the start of a full-width nav.
       Both are fixed by making the nav a static, auto-width flex item that the
       grid can align.

       3. The whole navbar went invisible on desktop, which is what this block's
          `opacity: 1; transform: none` now answers. The base .header-nav rule above
          is written for the mobile panel and reveals it from `opacity: 0` +
          `translateY(-0.5rem)`, restored by `.is-open`. Nothing adds `.is-open` at
          this breakpoint -- the hamburger that would is `display: none` -- so the
          base rule's closed state is the *resting* state up here and has to be
          cleared explicitly. This block used to clear `clip-path` instead, because
          the reveal used to be a clip-path wipe; swapping the reveal technique in
          the base rule left this reset pointing at a property nobody sets any more.
          **The general trap: the base .header-nav rule is mobile-first in a file
          whose desktop block is a list of hand-written undos. Any property added
          there needs a matching undo here, and the compiler will not tell you.**
          `nav a { opacity: 1 }` below is not a safety net and reading it as one is a
          mistake -- a non-1 opacity on an ancestor composites the whole subtree, so
          no descendant can opt back out of it.
          `transform: none` matters for a second reason beyond the 8px lift: a
          transform makes an element a containing block for absolutely positioned
          descendants, so the language and user dropdown containers -- which are
          `position: absolute` at this breakpoint -- were resolving against the nav
          rather than the header. */
    .header-logo {
      grid-row: 1;
      align-self: center;
      justify-self: start;
    }

    /* The cap is the structural answer to Joy's "the search either disappears or
       underlaps the Knowledge Base logo", and it replaces chasing a breakpoint number
       -- which was estimated twice and wrong twice.

       The root cause, stated plainly: .header-logo and .header-nav occupy the SAME
       grid cell and separate only by `justify-self: start` / `end`. Two items in one
       cell have no relationship to each other at all, so when their combined width
       outgrows the column they **overlap**. There is no width at which a grid cell
       asks its occupants to compress -- which is why every threshold guess failed, and
       why the failure mode was the field drawn *over* the wordmark rather than merely
       cramped.

       `max-inline-size` turns a non-relationship into a budget. A percentage on a grid
       item resolves against its grid area, so `calc(100% - var(--kb-logo-reserve))` is
       "the content column minus the space the logo needs", and the nav cannot reach the
       logo at any viewport width. Overlap stops being a thing that can happen rather
       than a thing that happens outside a tested range.

       `min-inline-size: 0` is load-bearing, not defensive. A grid item's `min-width:
       auto` resolves to its min-content contribution, which for this cluster is wider
       than the cap at narrow widths -- and a minimum beats a maximum, so the cap would
       simply lose and the overlap would come straight back.

       The cap alone would only clip; what makes it *compress* is the search field being
       the cluster's elastic member (see the "Header search" section at the bottom of
       this file: `flex: 0 1 auto` on .header-search.expand plus `min-inline-size: 0`
       down the chain, and `flex: none` on the siblings below so the pill and the locale
       name hold their size). The field gives up width first, which is the correct
       priority -- a 200px field still works, a wrapped Contact Support pill does not.

       Measured against the frame: the cluster is 638px at full size, and the cap at
       54.9em is 830 - 344 == 486 with the label shown, or 830 - 168 == 662 in the
       squeeze band -- so the band is what makes the field comfortable rather than
       sliver-thin, and both still clear the logo. From ~1192px up the column is
       capped at 71.5rem == 1144px, the cap is 800, and the field sits at its full
       256px. (Was recorded as "~1128px up / column ~1080 / cap 736" -- same
       conclusion, wrong figures, from the 125ch error corrected in the 54.9em
       structural note above.)
       The cluster is also wider here than in the frame: that was drawn with "English
       (US)" where this locale renders `{{current_locale.name}}` == "English (United
       States)", ~69px more. Elasticity absorbs that; a threshold could not, since the
       string is per-locale and no single number is right for all 30. */
    .header-nav {
      grid-row: 1;
      position: static;
      display: flex;
      width: auto;
      max-inline-size: calc(100% - var(--kb-logo-reserve));
      min-inline-size: 0;
      align-self: center;
      justify-self: end;
      text-align: start;
      opacity: 1;
      transform: none;
      transition: none;
      background: none;
      justify-content: flex-end;
      align-items: center;
    }
    .mobile-navbar {
      display: none;
    }
    nav ul {
      display: flex;
      align-items: center;
      /* The design's space/5 (20px) between every control in the cluster. Was
         1rem. The navbar frame confirms it: 256 -> 276 and 430 -> 450. */
      gap: 1.25rem;
      min-inline-size: 0;
      li {
        margin-block-end: 0;
      }
      /* Was 0.3rem, which read as centred only because the row above it wasn't.
         Now that the nav genuinely centres its items, any nudge here pushes the
         signed-out control off the design's centre line. */
      .anon {
        margin-block-start: 0;
      }
    }

    /* Names the search field as the cluster's only elastic member. Flex items default
       to `flex-shrink: 1`, so without this the nav's cap (see .header-nav above) would
       be absorbed by whichever item happened to have the most slack -- in practice the
       Contact Support pill wrapping to two lines and growing the header, or the locale
       name truncating mid-word. Both are worse than a narrower field: the pill is the
       primary CTA and the locale name is the control's entire label, whereas a search
       field is still usable at any width that fits a few characters.
       Written as `:not(.navbar-search)` rather than by listing the three siblings,
       because the <li>s carry no classes of their own -- the support link, language and
       user controls are identified only by what is inside them -- and because the
       navbar build already reordered these once. A positional selector would silently
       re-aim.
       The search <li> itself is left to default `flex: 0 1 auto`; the "Header search"
       section at the bottom of this file is what makes it grow and shrink. That used
       to be true only on the home page -- everywhere else .navbar-search was a fixed
       38px collapsed icon with no business flexing -- and has been true on every
       template since 2026-10-21. */
    nav ul > li:not(.navbar-search) {
      flex: none;
    }

    nav a {
      opacity: 1;
      position: relative;
    }

    /* Contact Support / Support Tools -- the design's brand pill, 40px tall.
       Retuned through the custom-property API .btn already exposes
       (--fg/--bg/--radius/--padding/--weight/--size) rather than by redeclaring
       background and colour, so none of .btn's own rules have to be out-specified.
       The design's 154px width is deliberately NOT pinned: it is just what
       "Contact Support" comes to at this padding, and a fixed width would clip
       longer translations of the same string.
       --font-family-primary is Codex's Sora token and carries no fallbacks of its
       own, hence the stack after it. --step-0 resolves to 14px at a 16px root,
       which is the size the design asks for.

       No `display` here on purpose, though it is now safe rather than forbidden.
       Only one of header.hbs's two support links renders at all -- nested `is`
       blocks on user.role pick between Support Tools and Contact Support
       server-side -- so whichever one exists just takes `display: inline-flex`
       from .btn (css-buttons.css:42) and there is nothing left to restate.
       Worth knowing what this looked like before, because it is a trap that has
       already been fallen into once: both links used to render always, hidden by
       `.agent, .manager, .anonymous, .end_user { display: none }` in
       css-utilities.css:68-72 and css-buttons.css:256-260, with script.js
       revealing the matching one via an inline style. Those gate rules are only
       (0,1,0) while this one is (0,2,0) under .header, in a file that loads after
       both -- so a `display` declared here outranked the gate and showed both
       buttons to every role at once. The gate rules are still in those two files,
       matching nothing now; they were left alone because both files are shared
       and out of this phase's scope. */
    .navbar-btn {
      --bg: var(--brand-bg);
      /* --fixed-white rather than --neutral-text-inverse, same correction and same
         reasoning as css-page-footer.css:365 -- the fill is brand, which does not
         flip, so a label that does would have turned near-black on coral in dark.
         These two are the theme's only brand-filled controls and they were both
         wrong in the same way. */
      --fg: var(--fixed-white);
      --radius: var(--radius-round);
      --weight: var(--font-weight-strong);
      --size: var(--step-0);
      --padding: 0 1.25rem;
      align-items: center;
      justify-content: center;
      height: 2.5rem;
      font-family: var(--font-family-primary), system-ui, sans-serif;
      line-height: 1;

      /* Codex's primary-button hover is a gradient sweep, not a darker flat fill.
         This now mirrors Codex's own implementation, which Joy pulled out of
         DevTools on 2026-10-03 (`.kb-button-strong` in entry.uEn_emt_.css) after
         the first pass here was reconstructed from the Figma state drawings alone.
         The reconstruction had the right technique and the wrong numbers.

         The technique, which Codex uses too: slide an over-wide background rather
         than swap background-image on hover, because **gradients cannot be
         interpolated by a CSS transition** -- a flat-to-gradient swap snaps at any
         duration. `background-position` does interpolate, so the gradient is
         declared once, wider than the button, and only the window onto it moves.

         What changed from the reconstruction. It used a 200% background of two
         panels, which covers resting and hover. Codex uses **300% of three**, and
         the third panel is the reason:

           0% ------------ 33.3% ---------- 66.6% ------------ 100%
           #992008         #992008   ~~~~>  #d73312            #d73312
           \__ panel 1 __/ \___ panel 2 ___/ \___ panel 3 ___/
            flat dark        the gradient      flat brand
            :active (0%)     :hover (50%)      resting (100%)

         So all three states are positions on one background, and **:active becomes
         interpolable too**. The reconstruction had to use `background-image: none`
         to go flat on press, which snapped -- excused at the time as acceptable
         inside a 75ms press. With the third panel there is nothing to excuse.
         Panel 1 is flat --brand-gradient-l-hover (#992008), which is the same hex
         as --brand-gradient-r-active, i.e. exactly Codex's brand pressed colour.
         Codex's own :active position is not in the dump, but a panel that is
         unreachable from both other states and lands precisely on the active
         colour is not there by accident.

         Resting shows flat #d73312 == --brand-bg, so resting is unchanged from
         before any of this existed. --bg stays declared as well: .btn paints it as
         background-color (css-buttons.css:108), so it sits under the gradient as
         the fallback if background-image ever fails to apply.

         The pseudo-element alternative was rejected: a gradient overlay has to
         paint above the button's own background but below its text, and the text
         here is a bare text node with no element to raise, so it would need
         negative z-index plus isolation and would still paint behind the
         background. Sliding a background has none of that.

         Scope note: this rule sits inside the min-width: 54.9em block, so none of
         it reaches the hamburger panel, where the same element is drawn as a flat
         list row. That mattered more before 2026-10-24: .btn now carries this
         gradient at the base scope, so the hamburger rules at :900 switch it off
         with `--bg-image: none` rather than relying on a fill that would have lost
         to it anyway -- background-image paints above background-color, so a
         gradient does not lose that argument in the cascade, it wins it silently.

         transition lives on the base rule, NOT on :hover. `.btn:hover` used to put
         `transition-duration` on its own, which meant every button in the theme
         eased in and then snapped back out, since the duration reverts to 0s the
         moment the pointer leaves. Declaring it here fixed that for this button
         without touching the shared file; css-buttons.css has since been fixed at
         source (2026-10-24), so the two now agree rather than this one being the
         exception.

         Two durations on purpose. The sweep is --motion-duration-slow (0.6s),
         Codex's own figure for this exact property; at the 0.15s default it read
         as abrupt, which is what Joy spotted. The shadow stays on
         --motion-duration-control (0.2s, Codex's duration for buttons) so the
         button acknowledges the pointer immediately while the colour takes its
         time -- a 0.6s shadow lift on a nav control feels like lag. Codex pairs its
         .6s with `transform .6s` instead, for a transform nothing here applies.

         --ease-in-out is Codex's *intent* rather than its shipped behaviour. Its
         utilities layer sets --tw-ease: var(--ease-in-out) for this button, but
         `.kb-button-strong`'s `transition` **shorthand** resets
         transition-timing-function to the initial `ease`, so the value the
         utilities layer carefully picked never applies -- DevTools strikes it
         through. Same family of bug as the .btn:hover one above. Taking the intent. */
      background-image: linear-gradient(
        90deg,
        var(--brand-gradient-l-hover) 0%,
        var(--brand-gradient-l-hover) 33.333%,
        var(--brand-gradient-r-hover) 66.666%,
        var(--brand-gradient-r-hover) 100%
      );
      background-size: 300% 100%;
      background-position: 100% 0;
      box-shadow: 0 var(--shadow-y-offset) var(--shadow-blur) 0
        var(--shadow-regular);
      transition:
        background-position var(--motion-duration-slow) var(--ease-in-out),
        box-shadow var(--motion-duration-control) var(--ease-in-out);
    }

    /* css-buttons.css:15 is explicit that any variant overriding --bg must restate
       hover and active, because .btn:hover/:active are (0,2,0) and would otherwise
       reassert their own fill over this variant's resting colour. (That paragraph
       used to be about the legacy --clr-primary-* ramp; .btn is on the brand roles
       now, and as of 2026-10-24 it carries this same gradient, so the two rules
       agree on every value below. They are still stated here: this block is scoped
       to min-width 54.9em and must keep standing on its own.)
       Codex ships no flat --brand-bg-hover/-active; the brand role keeps its state
       values on the gradient tokens, which is why both states below are positions on
       the base rule's background and not --bg swaps. --bg is still restated so the
       fallback colour under the gradient tracks the state it belongs to.
       The positions are Codex's: 50% for hover (verbatim from
       `.kb-button-strong:hover:not([aria-disabled=true],[disabled])`), 0% for active
       (inferred -- see the panel diagram above).
       Shadow: Codex's `.kb-button-strong:hover` sets background-position and nothing
       else, so its *strong* button does not lift. But `.kb-button-regular:hover`
       does, with exactly
         0 var(--shadow-y-offset-hover) var(--shadow-blur-hover) 0 var(--shadow-hover)
       which is the declaration below, character for character. So the lift is
       Codex's own pattern for a hovered button, applied to the one variant Codex
       happens not to apply it to -- a consistency choice, not an invention. The
       return to resting on press is still ours; Codex documents no pressed shadow.
       Press is --motion-duration-instant for both properties: a click should not
       feel like it is catching up with the pointer. That is an 8x speed-up on the
       sweep, which is the point -- press is immediate, release settles back over the
       full 0.6s because the duration reverts to the base rule's. */
    .navbar-btn:hover {
      --bg: var(--brand-gradient-r-hover);
      background-position: 50% 0;
      box-shadow: 0 var(--shadow-y-offset-hover) var(--shadow-blur-hover) 0
        var(--shadow-hover);
    }

    .navbar-btn:active {
      --bg: var(--brand-gradient-r-active);
      background-position: 0 0;
      box-shadow: 0 var(--shadow-y-offset) var(--shadow-blur) 0
        var(--shadow-regular);
      transition-duration: var(--motion-duration-instant);
    }

    .navbar-btn svg {
      display: none;
    }

    /* Both dropdown triggers -- language and user. .btn-dropdown and .btn-user
       appear only in header.hbs (grep over templates/), so they can be matched
       without going through the shared .navbar-item.
       The design draws neither trigger as a button: there is no fill, no border
       and no 3rem height, just the control's own content on the bar. .btn-dropdown
       pins height: 3rem, which would make the cluster 48px rather than 40px. */
    .btn-dropdown,
    .btn-user {
      --bg: transparent;
      --fg: var(--neutral-text-soft);
      --padding: 0;
      --size: var(--step-0);
      --weight: var(--font-weight-regular);
      --border-color: transparent;
      height: 2.5rem;
      gap: 0.625rem;
      font-family: var(--brand-font-family-sans);
    }

    /* Resting --bg above is (0,2,0) once nested under .header, which beats
       .btn-dropdown:hover's (0,2,0) on source order and would otherwise leave
       these two with no hover feedback at all. Hover moves to the chip instead of
       the whole control, which is what keeps the bar looking like the design while
       the affordance stays. */
    .btn-dropdown:hover,
    .btn-user:hover {
      --bg: transparent;
      --border-color: transparent;
    }

    /* SECTION - The two icon chips */

    /* The globe's chip and the signed-out avatar's. New elements, so new .kb-*
       classes -- the namespace css-page-home.css established for components this
       refresh adds.

       One box, stated once, for both: Joy's call 2026-10-22, and it is what makes
       the pair read as a set rather than as two circles that happen to be near each
       other. Grouped rather than merged into a single class because the box is all
       they share -- the finish differs on purpose, each following its own frame, and
       that is spelt out at .kb-user-chip below.

       This box is Codex's `.kb-button-regular.kb-button-icon-only.kb-button-md` in
       all but name -- that rule is 40px square with --radius-round. Confirmed from
       DevTools 2026-10-03, so the timing here is Codex's for this component rather
       than a borrowed default.
       Named property rather than Codex's `transition-property: all`: these chips
       also carry border-radius and a width that `all` would drag into any future
       size change for free, and an unexplained easing animation is harder to debug
       than a missing one. Both state values on each are flat fills from Codex's own
       ramps, so unlike .navbar-btn this needs nothing but a duration and a curve. */
    .kb-locale-chip,
    .kb-user-chip {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex: none;
      width: 2.5rem;
      height: 2.5rem;
      border-radius: var(--radius-round);

      /* The glyph size, also stated once for both, and the reason each chip is its
         own element wrapping a bare <i> rather than being the <i>.

         The Font Awesome kit sizes an icon through nothing but
         `.svg-inline--fa { height: 1em }`, so the only lever on a glyph is the
         font-size it inherits. Until 2026-10-22 neither chip set one, which left
         both at .btn's `font-size: var(--size)` with --size: var(--step-0) from
         .btn-dropdown/.btn-user -- i.e. 14px, and *fluid*, drifting to 14.22px with
         --step-0's clamp. That was a button's text size doing icon sizing by
         accident, and it is the kind of thing that moves when someone retunes the
         type scale. 1rem is Joy's number: a flat 16px, in both chips, declared.

         Nothing centres the glyph but the flex pair above. On one element the inner
         size has to be derived as padding against the box -- which is what the
         avatar did until today, and it is how its three-way sizing conflict arose
         (see the note at .kb-user-chip). Two elements means the box and the glyph
         are each stated outright and neither is a function of the other. */
      font-size: 1rem;

      transition: background-color var(--motion-duration-control)
        var(--ease-in-out);
    }

    /* The globe. Flat and borderless, deliberately NOT taking two things Codex's
       icon button has, because the Figma navbar frame does not draw them and the
       frame is the design of record for this header: a 1px
       --neutral-border-alpha-strong border, and a resting --shadow-regular that
       lifts to --shadow-hover.

       The avatar beside it *does* take the border, from its own frame. That is now
       the only visible difference between the two chips, and the one thing to change
       if they still don't read as a set. */
    .kb-locale-chip {
      background: var(--neutral-bg-component);
      color: var(--neutral-icon);
    }

    .btn-dropdown:hover .kb-locale-chip {
      background: var(--neutral-bg-component-hover);
    }

    /* Instant on press for the same reason as .navbar-btn:active -- a click should
       not feel like it is catching up with the pointer. */
    .btn-dropdown:active .kb-locale-chip {
      background: var(--neutral-bg-component-active);
      transition-duration: var(--motion-duration-instant);
    }

    /* The signed-out avatar, from the frame's own values (Joy): a
       --border-width-regular --neutral-border-alpha-strong border over
       --neutral-bg-strong. Everything geometric comes from the shared rule above.

       --neutral-icon-inverse for the person, not --neutral-icon. The -strong
       surfaces are Codex's inverted tier -- --neutral-bg-strong is #4f5f63 in light
       and #dce4eb in dark, i.e. dark-on-light and light-on-dark -- so a foreground
       on one has to invert with it. --neutral-icon is #384649 in light, which would
       be near-invisible on #4f5f63. The inverse role is #faffff / #10171e, which
       lands the right way round in both themes with no dark-specific rule, same as
       the error page.

       What this replaced, because the failure is worth recognising again. It was one
       element, `.anon .avatar`, carrying `font-size: 2.25rem` to size itself, and
       three rules were sizing it against each other: css-icons.css:167 puts
       `height/width: 2rem` *and* `padding: 0.5rem` on .avatar, this file asked for
       36px through font-size, and the kit's `height: 1em` sat under both. With
       `box-sizing: border-box` global, a 32px box minus 8px either side left a 16px
       content box for a glyph being asked to render at 36px, so whichever won,
       nothing was the size it claimed -- which is why it looked wrong in *both*
       themes, and that symptom is what gave it away. Splitting the element in two
       ends it: the `avatar` class came off the markup, so css-icons.css no longer
       reaches this at all, and no number here is derived from another.

       Also gone with it: `fa-circle-user`, which drew a disc with the person knocked
       *out* of it, so the silhouette showed whatever was behind the element -- the
       #10171e page in dark, which was the background bug Joy first reported. The
       circle is this element's now and `fa-user` fills. */
    .kb-user-chip {
      border: var(--border-width-regular) solid
        var(--neutral-border-alpha-strong);
      background: var(--neutral-bg-strong);
      color: var(--neutral-icon-inverse);
    }

    /* The hover Joy asked for: "something subtle like we have on the language icon".
       So it is literally the chip's, one tier across -- the globe steps
       --neutral-bg-component to its -hover, this steps --neutral-bg-strong to its
       -hover. No eyeballed colour involved; Codex ships the pair and the
       relationship holds in both themes, moving the fill away from the page
       (#4f5f63 -> #2e3a3d in light, #dce4eb -> #fdfdfe in dark). The dark step is
       the quieter of the two, which is the nature of that ramp rather than something
       to compensate for here.

       Triggered from .btn-user, not from the chip itself, so the whole control
       responds and not just the circle -- the adjacent "Sign in" text is part of the
       same hit target, exactly as the locale name is for the globe.

       No :focus-visible pair, also matching the globe: keyboard users get the
       outline from *:focus-visible (css-reset.css:19) on the button, and a fill
       change underneath it would be the only place in the header that doubles up. */
    .btn-user:hover .kb-user-chip {
      background: var(--neutral-bg-strong-hover);
    }

    .btn-user:active .kb-user-chip {
      background: var(--neutral-bg-strong-active);
      transition-duration: var(--motion-duration-instant);
    }

    /*!SECTION - The two icon chips */

    /* The design shows no chevron on either trigger -- the globe chip and the
       avatar are the affordance. Hidden here rather than deleted from header.hbs
       because the only frame supplied is the 1440px navbar, and the chevrons still
       do their job in the hamburger nav below this breakpoint.
       Matched on the fa-* class, never a bare `i`: the Font Awesome kit JS has
       already replaced each <i> with an <svg> by the time this applies. Scoped
       under .btn-dropdown rather than .navbar-item because article_page.hbs also
       carries .navbar-item, on its sidebar <details>. */
    .btn-dropdown .fa-chevron-down {
      display: none;
    }

    /* Signed in: 40px, up from .avatar-img's 2rem and from the 2.25rem this was
       until 2026-10-21. 36px is what the frame draws, but it is the only thing in
       the cluster that is not 40px -- .navbar-btn, .btn-dropdown/.btn-user and both
       icon chips are all 2.5rem -- so the avatar read as short beside the globe chip
       sitting right next to it. Joy called it on the signed-out one; this is the
       same 4px on the same row and it would have come back the moment she signed in,
       so both moved together.

       This one stays a single element, and correctly so: it is a photo, not a glyph
       in a circle. {{user_avatar}} renders an <img> that fills its box, so there is
       no inner size to state and nothing for a wrapper to do. Reverting the 4px is
       2.25rem here and 2.5rem in the shared chip rule. */
    .user .avatar-img {
      width: 2.5rem;
      height: 2.5rem;
    }

    /* Signed out: see .kb-user-chip in the icon-chips section above. The blank
       avatar is one of the two chips now rather than a one-off rebuilt here, so its
       box, glyph size, fill, border and both states all live beside the globe's. */

    /* ...and the "Sign in" text gets nothing, which is the other half of making
       the hover match the language control's.

       Without this there are two hover effects on one control. The link carries
       .dropdown-item, so it inherits the fill meant for rows inside an open menu:
       a --clr-neutral-5 rectangle in light (css-buttons.css:236) and a
       --neutral-bg-component-soft-active one in dark (css-buttons.css:402). Both
       were firing at the same moment as the circle, from the same pointer, in a
       different shape and a different colour. The locale name has no equivalent --
       the chip is the whole affordance there -- so suppressing this is what "like
       we have on the language icon" actually means.

       Not deleted at source: both of those rules are correct for the rows they
       were written for, in a shared file. .header .btn-user a.dropdown-item:hover
       is (0,3,1), which clears the dark rule's (0,3,0) as well as the light one's
       (0,1,1), so one declaration covers both themes. */
    .btn-user a.dropdown-item:hover {
      background-color: transparent;
    }

    .user-name {
      display: none;
    }
  }

  /* The squeeze band: desktop layout, but not enough room for both the logo label
     and the header's search field. Joy's call, and it is the right priority --
     **lose "Knowledge Base" before losing search.** The wordmark alone still says
     whose help centre this is, and it is still a link home; the search field is the
     primary way into the content and has no substitute in the header.

     Note this band was never gated on the home page while the field it frees room
     for was, so between 2026-10-03 and 2026-10-21 it hid the label on templates
     that had no field to make room for. Ungating the field (see the "Header
     search" section at the bottom of this file) is what makes the trade this
     comment describes true on every template rather than one.

     Why a band is needed at all, rather than this being a free choice: .header-logo
     and .header-nav share one grid cell and separate only by justify-self (see the
     structural note in the 54.9em block above), so when their combined width
     outgrows the `content` column they **collide rather than compress**. Hiding the
     label is the only lever that frees real estate without restructuring the grid.

     The arithmetic, extending the measurements already recorded below for the 72em
     gate: logo ~317px with the label, cluster ~634px with the 256px field, so 951px
     needed against `min(100vw - 96px, 71.5rem)` available. Dropping the label and its
     two 1rem gaps and the pipe saves ~173px -- 317 down to the wordmark's 144 --
     which brings the requirement to ~778px. At 54.9em only 782px is available, a
     4px margin that would have the field touching the wordmark, so the field itself
     waits for 56em (896px, 800px available, ~22px clear -- about the 20px the design
     puts between cluster items anyway). Above 72em both fit and the label returns.

     No restoring rule is needed above 72em: nothing else sets a display on these
     two, so they fall back to being ordinary flex items of .logo. Capped at 72em
     rather than 71.99em deliberately -- see the gap note on the mobile block for why
     an overlapping hairline is the safe direction to err. At exactly 72em the label
     is hidden, which is the same side of the line the field rule resolves to. */
  @media screen and (min-width: 54.9em) and (max-width: 72em) {
    .logo .pipe,
    .logo .logo-text {
      display: none;
    }

    /* The reserve drops with the label it was reserving for.

       **168px against a wordmark that renders at 119px, confirmed in DevTools at a
       875px layout width 2026-10-03** -- so this reserves 49px of clear space, not the
       20px the arithmetic below aimed at. Left as 168 deliberately, on Joy's call after
       seeing it: the 20px figure is the frame's pitch *between cluster items*, and the
       distance from the wordmark to the far end of the bar is a different relationship
       -- the navbar frame puts 116px there (logo ends 507, cluster starts 623). 49px at
       this width is a sensible point on that curve, and the field still gets 144px.

       The 144px this was originally derived from is the mobile frame's measurement, and
       **the rendered wordmark is 119px -- the SVG has no CSS size, so it draws at its
       own intrinsic width, about 0.82x the frame's.** Recorded because a frame
       measurement that was never checked against the render is what made three
       successive breakpoint estimates wrong; see the sixth-preview entry in
       docs/brand-refresh/plan.md.
       This is the band's real payload now. Hiding the label used to be the only lever
       available, because two items sharing a grid cell collide rather than compress and
       nothing could be done about the collision itself; with the cap in place the
       collision is impossible either way, and what the band buys is a *comfortable*
       field instead of a sliver -- 662px of cluster budget at 54.9em rather than 486. */
    header {
      --kb-logo-reserve: 10.5rem; /* 168px == the 144px wordmark + 20px */
    }
  }
}

/* SECTION - Header search
   A real inline field sitting alongside the header's other controls -- 256px,
   expanded from the start, Salesforce-style -- rather than the collapsed icon
   that grew over its siblings on focus. Desktop only: below 54.9em the field is
   .kb-search-row inside the header (see the mobile block above) and
   .navbar-search is display:none, on every template.

   **Ungated 2026-10-21 — Joy: "initially we had the header on the home page
   displaying the search differently than the other templates, but we'll need it
   to look identically on every page."** Every selector in this block was
   `html.home-page`-prefixed from the day it was built, because the pill was the
   home page's treatment and every other template kept the collapsed icon out of
   css-search.css. The gate is now off all sixteen, and the collapsed treatment
   is deleted from css-search.css rather than left sitting underneath this one --
   see the section comment there for what came out, including two live defects.

   Four things that gate was costing, beyond the inconsistency Joy spotted:

   - **Every template except the home page still had a pre-refresh header.** The
     collapsed field was the legacy salmon skin: a 2px --clr-primary-100 border,
     a #fce1d3 glow and a 0.25rem radius on focus.
   - **The search-results page had no header search at all**, via
     `.search-results-page .navbar-search { display: none }`, and
     templates/search_results.hbs rendered no search field of its own -- its
     `.search-results` div was empty -- so there was no way to refine a search
     from the page you land on after searching.
     **Reversed on 2026-10-22: that page is hidden again, and this reading of the
     old rule was wrong.** It was not an oversight to be corrected; it was
     holding back a conflict with the page's own search implementation. The
     block at the end of this section is the restored rule and carries the whole
     story.
     **Paid off later the same day.** The brand refresh of that page gave it a
     hero field of its own, `#algolia-query` in search_results.hbs, which is
     exactly the "search field in the page body, where that implementation
     expects one" this note called for -- the Algolia InstantSearch half binds
     its searchBox to that id rather than to `#query`. So the cost named here is
     settled, and it was settled in the page template rather than by unhiding
     this form. Nothing about the rule at the end of this section changes.
   - **The squeeze band above was hiding "| Knowledge Base" for nothing** on
     every other template. That band exists to free room for this field and was
     never gated, so it was paying the field's cost on the pages that had no
     field. It earns its keep everywhere now.
   - Six declarations in this block existed only to out-specify or undo
     css-search.css. They are gone with it, and the `!important` on
     `display` with them.

   Still at top level rather than inside the `.header` block above, and still in
   this file rather than css-page-home.css: every css-page-*.css is loaded
   sitewide, and these selectors target header.hbs's own markup, none of which
   is a descendant of anything a page template renders.

   **Gated at 54.9em -- the same breakpoint as the rest of the desktop header, so the
   field is present at every width where the navbar is.** It got here the long way:
   72em, then 56em, both of which were widths at which the field was *guaranteed to
   fit* rather than widths at which it was wanted, and the second estimate was still
   wrong -- Joy reported the field underlapping the wordmark between 880 and 900px.

   The reason a guaranteed-fit width was needed at all was structural, and it is now
   fixed rather than worked around. .header-logo and .header-nav share one grid cell
   and separate only by `justify-self`, so they had no relationship to each other and
   **overlapped** instead of compressing once their combined width outgrew the
   `content` column. No threshold can be correct under that: the cluster's width
   depends on `{{current_locale.name}}`, which is per-locale across 30 locales, so
   there is no single number that fits them all. The fix is the `max-inline-size` cap
   on .header-nav in the 54.9em block plus the elasticity below, which together make
   the field give up width and make overlap impossible at any viewport size. With that
   in place the gate no longer has a job to do, so it matches the navbar's own
   breakpoint.

   What each band now looks like, using the navbar frame's real measurements (logo
   323px, cluster 638px, 20px pitch -- node 6473:6479) and the content column freed up
   by `padding-inline: 0` on the header:
     54.9em-72em  column ~830, label hidden, reserve 168 -> 662 of cluster budget;
                  the 638px cluster fits, and the longer locale name trims the field
                  rather than colliding.
     72em and up  column capped at 71.5rem == 1144, label back, reserve 344 -> 800;
                  the field sits at its full 256px.
   The squeeze band above is still pulling its weight: without it the reserve at
   54.9em would be 344 and the budget 486, which fits but leaves the field thin.

   One consequence of ungating worth having measured in preview rather than
   assumed: the cluster on a non-home template used to be ~416px and is now
   ~638px like the home page's, so those headers are using the elasticity for the
   first time. It is the same chain the home page has been previewed on three
   times, and `flex: none` on the non-search <li>s still means the field is the
   only thing that gives -- but the locale name is the one variable the frame
   never drew, so a long one is where to look if anything collides. */
@media screen and (min-width: 54.9em) {
  /* This selector matches two elements, and both need what it sets: the <li> and the
     `div.search.navbar-search` inside it. On the <li> -- a flex item of the <ul> --
     `flex: 1 1 auto` is what lets the field take the slack and give it back, and
     `min-inline-size: 0` defeats the automatic minimum size that would otherwise pin
     the item at its min-content width and push the cluster back over the logo. On the
     inner div the flex properties are inert (it is a block child of the <li>) and the
     minimum is harmless, so one rule covers both without naming them separately.

     Four declarations came off here on 2026-10-21, all of them undoing
     css-search.css rather than describing this field: `display: flex` no longer
     needs `!important` now that `.home-page .navbar-search { display: none }` is
     gone; `height: auto` / `width: auto` were resetting the collapsed icon's
     2.375rem square; `justify-content: flex-start` was resetting its `center` and
     is the default anyway; and `margin-inline-end: 0` was cancelling a 1rem
     margin that would have put 36px between the field and Contact Support where
     the design has 20px. The <ul>'s own 1.25rem gap is the cluster's one spacing
     mechanism now, which is what the design draws. */
  .navbar-search {
    display: flex;
    flex: 1 1 auto;
    min-inline-size: 0;
  }

  /* Sizing and skin come off the design: 16rem == its 256px (was 18rem/288px),
     40px tall to match the rest of the cluster, and the Codex form tier in place
     of the 1px --clr-neutral-160 (#333333) border css-search.css gave this, which
     was far heavier than the design's #809094. The radius was already a pill via
     `100vh`; --radius-round is the same shape named as a token. Figma reports
     70px here, which is just "pill" expressed as a literal at this height.

     `position: relative` and `display: flex` used to be undoing css-search.css's
     `position: absolute` (which let the collapsed field expand over its
     neighbours) and restating its `display: flex`. Both are stated here on their
     own merits now rather than dropped with that rule: the flex row is how the
     input fills the pill, and a positioned wrapper bounds any absolutely
     positioned descendant -- which today means Algolia's dropdown, even though
     Algolia also positions it against its own injected `.algolia-autocomplete`
     span.

     `overflow: visible` has come off, though. It was cancelling css-search.css's
     `overflow: hidden` -- there to let the collapsed field animate its width
     without spilling the input -- and `visible` is the initial value, so with
     that rule gone there is nothing left to cancel. Nothing re-clips further up
     either: <header> has no `overflow: hidden` (see :291) and neither does the
     nav or .search-content. That matters because the focus ring below is a real
     outline rather than the box-shadow that used to stand in for one.

     `font-size` is restated for the same reason as the input's box below -- it
     came from the deleted base rule, and the magnifier is sized off it. */
  .header-search.expand {
    position: relative;
    /* A plain declaration, and it has to stay one. This was
       `display: flex !important` for part of 2026-10-22, to beat an inline
       `style="display: none"` that something outside this theme puts on this
       <form> after load -- Joy caught it in DevTools, struck through. An
       `!important` author declaration is the one thing in the cascade that
       outranks a non-important inline style attribute, so it did work, and no
       amount of extra specificity would have.

       It was still the wrong fix, because it was aimed at the wrong scope. That
       injection happens on exactly one template. Joy confirmed in preview on
       2026-10-22 that an article page runs Algolia's autocomplete normally, with
       this form visible and css-search.css's `.aa-*` rules applying; the search
       results page is the only one that hides the form and replaces the panel
       with Zendesk's own `zd-autocomplete` custom elements. That page renders
       its results into the empty `.search-results` div in
       templates/search_results.hbs from the Algolia InstantSearch integration,
       and whatever does that claims `#query` on the way past.

       **Identified 2026-10-22, reading the bundle.** It is the InstantSearch
       half's own `_hideAutocomplete`, and the inline style is not a side
       effect -- it is the whole method. It walks up from each `#query` input to
       the nearest <form>, then keeps walking while each parent has exactly one
       child, and sets `style.display = 'none'` on whatever it stops at. It runs
       when `instantsearch.hideAutocomplete` is true, which is its default, and
       the InstantSearch half only renders on the search results page, which is
       why the injection lands on exactly one template.

       So "whatever does that" above was right about the scope and the blame.
       Two consequences worth keeping:
         - There was never a version of this that CSS could win cleanly. The
           `!important` worked because it beats a non-important inline style,
           and that was the only reason.
         - The walk is unbounded upwards. Give that form a chain of
           single-child ancestors and the method hides a region rather than a
           field. Not a live problem with this header's markup, but it is the
           reason not to "simplify" the wrappers around the navbar search.
       There used to be a second, older rule fighting the same opponent on the
       same form in css-page-search-results.css -- it hid a submit <input> the
       takeover injects, with `!important`, for the same cascade reason. It came
       out with that file's rewrite on 2026-10-22: the block at the end of this
       section hides the whole header form on that template, so there is no
       longer a submit button there to fight over.

       So the field is hidden on that one template instead (see the
       .search-results-page block at the end of this section) and this stays
       plain. Forcing a form another bundle has claimed to remain visible left it
       looking like ours and behaving like something else's; there was never a
       page where ours was being hidden and needed rescuing.

       Not to be confused with the `!important` that came off
       `html.home-page .navbar-search` on 2026-10-21 (see the note at :2065) --
       different element, and that one was beating a stylesheet rule. Checked
       while this was being diagnosed: master's css-search.css declared
       `.header-search { display: flex }` with no `!important` either, so the
       inline style was never a regression from the ungating. It was always
       winning on that page, and only became *visible* once the field stopped
       being a 38px collapsed circle there and started being a 256px pill that is
       conspicuous by its absence -- which is the grey crescent Joy reported:
       with the form gone .search-content collapses to nothing and the
       absolutely-positioned .search-icon (:2307) is stranded under the Contact
       Support pill. */
    display: flex;
    /* 16rem is the width the field *wants*; `flex: 0 1 auto` makes it the width the
       field gets whenever there is room, and the first thing surrendered when there
       is not. flex-basis stays `auto` so the 16rem below is still the single place
       the design's 256px is written -- no second copy to keep in step -- and
       flex-grow is 0 so a wide header does not stretch the field past the frame.
       `min-inline-size: 0` for the same reason as on .navbar-search: the automatic
       minimum would hold the input at its min-content width and the cluster would
       overflow the nav's cap back onto the logo.
       This is what makes the cap on .header-nav compress rather than clip, and it is
       deliberately the field that absorbs -- see the `flex: none` note in the 54.9em
       block for why the pill and the locale name must not. */
    flex: 0 1 auto;
    width: 16rem;
    min-inline-size: 0;
    max-width: 100%;
    height: 2.5rem;
    /* css-search.css transitioned `width` over 400ms for the collapsed search's
       expand-on-focus. That rule is gone, so this is no longer an override -- but
       it still has to be *stated*, because the width is flex-driven and every
       viewport resize is a width change: a blanket transition here would animate
       the field lagging 400ms behind the window during a drag. Only the two
       properties the focus state actually moves are named. The ring is
       deliberately not among them: Codex's focus-visible rule carries
       `transition: none` next to the outline, so the ring arrives instantly and
       only the fill and border ease in. Durations and easing match the mobile
       field at :391 for the same reasons given there. */
    transition:
      background-color var(--default-transition-duration) var(--ease-out),
      border-color var(--default-transition-duration) var(--ease-out);
    border: var(--border-width-regular) solid var(--form-border);
    border-radius: var(--radius-round);
    background: var(--form-bg);
    font-size: var(--step-0);
  }

  /* Codex hovers the **fill only** -- .kb-input-text:hover sets
     background-color: --background-color-form-hover and nothing else. No border
     move, no icon change; the border darkening is reserved for the engaged
     states. --form-bg-hover (css-brand-tokens.css:474) was another token defined
     and never consumed.
     Declared before :focus-within on purpose. The two carry equal weight on the
     fill, a focused field is usually also hovered, and source order is what
     decides -- so focus has to come second to win.
     One selector is enough even though the glyph is painted over this pill
     without being inside it (header.hbs:138): the glyph is `pointer-events: none`
     below, so a cursor crossing it hovers the field underneath rather than
     escaping the field's own :hover. An earlier version of this rule carried a
     second `:has(.search-icon:hover)` selector to cover that gap; letting the
     pointer fall through is the better answer and the gap no longer exists. */
  .header-search.expand:hover {
    background: var(--form-bg-hover);
  }

  /* From Codex's own KbAutocomplete in DevTools (2026-10-20). Codex draws the
     focused text field as *three* things, and this rule owns the inner two:
       .kb-input-text:focus  ->  background-color: --background-color-form-active
                                 border-color:     --border-color-form-active
     which are our --form-bg-active and --form-border-active. The fill moving on
     focus is the part that was missing -- --form-bg-active was defined in
     css-brand-tokens.css:472 and consumed nowhere, so the field used to hold
     --form-bg through every state. Border width stays 1px in both states, so
     taking focus still shifts nothing.

     Three declarations came off here on 2026-10-21 with css-search.css's
     `.header-search:focus-within`, which was the legacy salmon state -- a 2px
     --clr-primary-100 border, a #fce1d3 shadow, a 0.25rem radius and a white
     fill. `width: 16rem` was holding the field at its own width against that
     rule's `width: 30%`; `border-radius` was holding the pill against its
     0.25rem; and `z-index: auto` was undoing its `z-index: 1000`, which is worth
     recording because the symptom was odd -- the glyph is a *sibling* of this
     wrapper rather than a child of it (header.hbs:145), so lifting the wrapper
     put its background above the glyph and the magnifier vanished the moment the
     field took focus. All three are initial values again now, by subtraction.

     What that rule's `box-shadow: 0 0 0 2px #fce1d3` was doing is the one thing
     this rule never overrode -- see the css-search.css section comment. */
  .header-search.expand:focus-within {
    border-color: var(--form-border-active);
    background: var(--form-bg-active);
  }

  /* The third thing: the ring, which Codex keys to :focus-visible rather than
     :focus and draws as an outline, 2px solid --form-focus-ring at a flat 2px
     offset, with `box-shadow: none !important` beside it. So the box-shadow that
     used to mimic a ring here is gone and the sitewide outline does the work --
     made possible by `overflow: visible` above.
     In practice this is not a behaviour change: browsers match :focus-visible on
     a text input however it was focused, mouse included, so the ring still
     appears on click exactly as the box-shadow did. What changes is the geometry
     -- a real outline sits outside the border box and follows the pill's radius.
     Keyed to `:has(input:focus-visible)` rather than the wrapper's own
     :focus-within because those two are not the same test: :focus-within follows
     plain :focus, so it would also ring the pill on any programmatic focus, while
     Codex rings on :focus-visible. The field has no other focusable inside it, so
     today the visible result is identical -- this is the selector that stays
     right if one is ever added. Matched on the bare element and not `.aa-input`,
     which is Algolia's class and is only on the input when
     settings.instant_search is on -- the pill is the wrapper's only input either
     way.
     offset is written flat rather than left to the reset, whose default computes
     max(2px, 0.15em) and would land wider than Codex's 2px; css-reset.css:14-17
     expects components that care to pin it themselves. */
  .header-search.expand:has(input:focus-visible) {
    outline: 2px solid var(--form-focus-ring);
    outline-offset: 2px;
  }

  /* One ring, not two: the input's own would now paint inside the wrapper's. */
  .header-search.expand input:focus-visible {
    outline: none;
  }

  /* **Matched on `input`, not `.aa-input`, as of 2026-10-21.** `.aa-input` is
     Algolia's class and is only on the element when settings.instant_search is
     on -- which it is by default (manifest.json) but is an admin checkbox, and
     with it off this rule used to miss entirely: the input kept the UA's own
     white box and a 1rem start padding that put the placeholder under the
     magnifier. The pill is the wrapper's only input either way, so the bare
     element selector is both simpler and correct in both configurations.

     width, border and the block/end padding migrated here from css-search.css's
     deleted `.expand .aa-input`, which was the only rule zeroing the UA's input
     chrome -- css-reset.css touches neither property. They are not new
     behaviour; they are the same values stated where the rest of the field is.
     `opacity: 1` and `cursor: text` are gone instead: both were undoing that
     rule's collapsed state, where the input was invisible and the whole pill
     read as a button.

     2.375rem start padding == Codex's `pl-9.5` (38px), which is also what the
     mobile field uses (:371). With the magnifier at 14px the glyph ends at
     29.5px, leaving the ~8.5px gap to the placeholder that the component draws.
     The `!important` this carried is gone with the `.header-search
     input#query.aa-input` selector whose id it had to beat. */
  .header-search.expand input {
    width: 100%;
    border: 0;
    padding-block: 0;
    padding-inline: 2.375rem 1rem;
    /* The pill above owns the surface, so the input gets out of the way rather
       than painting a second, squarer box inside it. */
    background-color: transparent;
    color: var(--neutral-text);
    font-family: var(--brand-font-family-sans);
    font-size: var(--step-0);
    line-height: 1.4;
  }

  .header-search.expand input::placeholder {
    color: var(--neutral-text-soft);
    opacity: 1;
  }

  /* The magnifier sits inside the field itself, matching the hero search bar's
     icon placement, so this is the box it is positioned against. */
  .search-content {
    position: relative;
    display: flex;
    align-items: center;
    /* Third and last link in the shrink chain -- .navbar-search, this, then
       .header-search.expand. A flex item anywhere along it that keeps `min-width:
       auto` stops the whole chain, since the widest unshrinkable link decides the
       cluster's floor. */
    flex: 1 1 auto;
    min-inline-size: 0;
  }

  /* Retuned against Codex's KbAutocomplete 2026-10-20. Its in-field magnifier is
     the same icon we use (Font Awesome `far` magnifying-glass) and carries:
       absolute  top-1/2  left-3  -translate-y-1/2  text-brand-icon  !size-3.5
     so one thing changes from what was here:
     - Vertical placement is `top: 50%` + a -50% translate, Codex's own
       centring, rather than relying on the abspos static position inheriting
       .search-content's `align-items: center`. Same result at 40px, but it stays
       centred if the field's height is ever retuned.
     left-3 is 0.75rem, which is what this already had.
     **No explicit box size. The font-size was a hold until 2026-10-22; it is
     now measured.**
     Codex's `!size-3.5` is 14px, but size is the one axis where Figma outranks
     Codex unless Joy says otherwise -- and for a while there was no measured
     desktop glyph to apply that rule to, only the mobile field (6654:3292) at
     16px. So this pinned the value the icon had always rendered at,
     `var(--step-0)` == ~14px, rather than inventing a desktop number or
     copying mobile's across, and asked to be re-measured.

     **Measured 2026-10-22 and changed to --step-1 (~16px).** This field at
     1440px is node 6472:2799 (256x40) and binds the glyph to `font/size/base`
     == 16 at 16x16. It is the same `autocomplete` component instance as the
     search results page's hero field (6473:6478, 560x40), which draws it
     identically -- so the two breakpoints do NOT differ by 2px, which the hold
     had flagged as "plausible but currently unverified". Mobile's 16px was the
     right number all along and the desktop 14 was the outlier.

     Either way the kit's `height: 1em` sizes the svg off this the way every
     other icon in this theme is sized.
     Pinned here rather than inherited as of 2026-10-20. It used to arrive via a
     `.search-submit` rule in css-search.css, but the glyph has dropped that class
     (see header.hbs for why -- the same rule's broken `calc` was blowing the svg
     up to 300px) and the rule itself was deleted on 2026-10-21. The <i> is a
     *sibling* of .header-search, so it cannot inherit that wrapper's font-size
     either; left alone it would take .search-content's, which is not a size
     anyone chose for an icon. Same number, now stated where it is used.
     (That note about measuring the 1440px navbar frame is discharged -- see
     the measurement above.)
     The glyph stays --neutral-icon at rest. Codex's SVG carries `text-brand-icon`
     unconditionally, but per Joy the brand coral is the *engaged* treatment here
     -- an orange magnifier on an idle field reads as a call to action the field
     has not earned yet. The coral is applied on focus below instead. */
  .search-icon {
    position: absolute;
    font-size: var(--step-1);
    /* The design's space/3 from the field edge to the icon. */
    inset-inline-start: 0.75rem;
    inset-block-start: 50%;
    transform: translateY(-50%);
    margin: 0;
    color: var(--neutral-icon);
    line-height: 1;
    /* The glyph is decorative as of 2026-10-20 (header.hbs), so it has no
       business intercepting the pointer: it sits on top of the field, and a
       click aimed at the start of the input used to land on an inert control
       instead of placing the caret. Falling through also means the field's own
       :hover covers the glyph's area, which is why the rule above needs only one
       selector. */
    pointer-events: none;
  }

  /* The glyph goes brand coral while the field is engaged, and only then.
     **Scoped to .search-content, not to .header-search.** header.hbs makes the
     glyph a *sibling* of the .header-search wrapper -- both are children of
     .search-content -- so every `.header-search ... .search-submit` descendant
     selector this theme ever had matched nothing. That included
     `.header-search:focus-within .search-submit` in css-search.css, which was
     believed to turn this icon white on focus-within and give it a dark glow; it
     never applied, the rule that used to sit here reverting it was dead code
     resting on the same wrong structure, and both are now deleted. Hence no
     `box-shadow: none` here: there is no shadow to cancel.
     `:has(input:focus)` rather than `:focus-visible` to stay in step with the
     fill and border, which key off :focus -- the icon should turn with the field,
     not with the ring. Hover deliberately absent: Codex moves only the fill on
     hover, so an idle-but-hovered field keeps the neutral glyph.
     Nothing here touches border or outline, and there is no longer anything to
     touch: the glyph is an aria-hidden <i>, so the reset's `*:focus-visible`
     treatment (css-reset.css:19-67) can never reach it. Earlier versions of this
     block worked around that rule because the glyph was a focusable <button>. */
  .search-content:has(input:focus) .search-icon {
    color: var(--brand-icon);
  }
}

/* No header search on the search results page, at any width. **Joy's call,
   2026-10-22: "let's hide the search field completely on the search results
   page. But keep it on every other page."**

   This is the same shape as the `.search-results-page .navbar-search
   { display: none }` that came off on 2026-10-21 (see :2002 and the
   css-search.css section comment), restored deliberately rather than by
   accident. That rule read like an oversight when the gating came off -- it left
   a page with no search field at all -- but it was holding back a real conflict:
   the search results page's own search implementation takes over this <form>,
   hides it inline, injects a submit <input> into it that
   css-page-search-results.css:234 then has to hide again, and drives a
   `zd-autocomplete` panel off the input instead of the Algolia dropdown
   css-search.css styles. The field looked present and behaved like a different
   component. Hiding it is the honest state, and it is one template out of
   fourteen.

   Both bands and the trigger, so there is nothing left to reach:
   - `.navbar-search` is the desktop <li>, beating `.navbar-search
     { display: flex }` at :2074 on specificity (0,2,0 over 0,1,0). Media queries
     do not enter into the cascade, so this works from outside that block.
   - `.kb-search-row` is the mobile row, listed twice -- bare and with
     `.is-open` -- because mobile-search.js adds that class on tap and
     `.kb-search-row.is-open { display: block }` at :799 is (0,2,0). The bare
     selector alone would be winning only on source order.
   - `.kb-search-toggle` is the mobile magnifier button. Hidden as well as
     neutralised: a button whose only job is to open a row that cannot open is
     worse than absent, and `aria-controls` would point at a hidden element.

   The class arrives from templates/search_results.hbs, which is why that
   template's inline script moved to the top of the file -- at the bottom, where
   it was, the header had already been laid out and the field flashed in before
   these rules could apply.

   What this does *not* do is re-show "| Knowledge Base" in the 54.9em-72em
   squeeze band on this page. The band exists to free room for a field this page
   no longer has, so it is strictly speaking paying for nothing here -- but
   un-squeezing one template would put a per-template difference back into the
   logo, which is the thing Joy asked to have removed. The label stays hidden
   where it is hidden everywhere else.

   Worth a look in preview: whether the `zd-autocomplete` element still gets
   appended as the last child of <body> on this page now that its input cannot be
   focused. It should not -- it is built on interaction -- but it positions itself
   with inline left/top/width and would be a stray box if it did. */
.search-results-page .navbar-search,
.search-results-page .kb-search-toggle,
.search-results-page .kb-search-row,
.search-results-page .kb-search-row.is-open {
  display: none;
}
/* !SECTION - Header search */
