/* SECTION - Search Results
   ========================================================================
   Brand refresh of the search results page, against Figma frame 6473:6268
   ("Search Results", 1440px) in Knowledge-Base-Prototype-v2.0. Replaces the
   pre-refresh build wholesale: that version was written against the legacy
   --clr-* ramp, which has no dark tier, and laid the page out with a
   `.search-page-wrapper > div > div` selector that also matched the
   breadcrumb wrapper's inner <div>.

   READ THIS FIRST IF YOU ARE CHANGING ANYTHING HERE.

   Almost nothing on this page is ours. The Algolia Zendesk HC integration v2
   (the `algoliasearchZendeskHC` call in document_head.hbs) throws away
   whatever is inside `.search-results` --- the bundle does
   `this.$container.innerHTML = templates.instantsearch.layout(...)` --- and
   builds the whole results region itself. So there are only three places a
   change can live:

     1. templates/search_results.hbs, for the hero above the breadcrumbs. That
        is outside `.search-results` and therefore survives.
     2. The `templates.instantsearch` overrides in document_head.hbs, which is
        the vendor's own supported hook for its `layout`, `hit` and
        `hierarchicalItem` markup. We override all three; see that file for the
        contract each one has to keep.
     3. This file, for everything else.

   The id-based hooks below (#algolia-hits, #algolia-facets, #algolia-stats,
   #algolia-categories, #algolia-labels, #algolia-pagination, #algolia-
   facets-open, #algolia-facets-close) are not ours to rename --- the bundle
   does getElementById/querySelector on every one of them. The .ais-* classes
   come from instantsearch.js v2 and are equally fixed.

   Two sources of injected CSS land in <head> *after* this file and so beat it
   on any tie:
     - `templates.instantsearch.css`, four rules interpolated from the `color`
       and `highlightColor` config values (both --neutral-text here).
     - `responsiveCSS` / `responsiveCSSFacets`, the vendor's own 768px
       treatment, added on init and on tapping the filter toggle.
   Where this file has to win against those it says so at the rule.

   A third source does *not* apply, and it is worth knowing why before you fight
   a phantom: the integration also ships a static
   `algoliasearch.zendesk-hc.min.css`, whose `<link>` is commented out in
   document_head.hbs. If it were live it
   would beat several rules here on source order alone --- its
   `#algolia-facets .ais-hierarchical-menu--link` is (1,1,0), the same as the
   row rule below, and sets `display: block; width: 80%; white-space: nowrap`.
   It is still worth reading as documentation of the vendor's DOM, which is how
   the `> div >` in the selected-category selector was found.

   Spacing idiom, which differs by *what* is being spaced. This follows the
   rule style.css:251 sets out for the breadcrumb row:
     - Page rhythm (hero padding, the gap under the breadcrumbs) uses the
       fluid --space-* scale, so it breathes with the viewport.
     - A component's own internals use the literal the Figma variable fixes
       them at (the card's `space/8` = 32px is 2rem at every width, not
       --space-m's 24->36px ramp). Each one names its variable.

   The page-nav at the bottom of the frame is deliberately NOT built from the
   frame. Joy, 2026-10-22: "the pagination at the bottom will be different and
   we'll be going straight from codex. So ignore that format for now." The
   pre-refresh #algolia-pagination rules were held here verbatim while that was
   pending, but the Codex spec arrived later the same day and the Pagination
   section at the end of this file is now built to it --- so that block is gone
   and no --clr-* survives anywhere in this file. Read that section rather than
   the frame if the control is ever retuned.
   ======================================================================== */

/* THE PAGE'S CORNER GRADIENTS ARE GONE FROM HERE, and this page is not losing
   them -- they are sitewide now, on <main>, in css-reset.css. Joy, 2026-10-25:
   "this is something that should be placed as a global setting, rather than page
   by page." This page and the home page were the only two that had them, and
   both local copies came out in the same commit so nothing double-paints.

   WHAT WAS HERE: the same two radial glows the global rule now carries, verbatim
   -- 52.44% x 56.77% at 100% 100% and 52.44% x 56.76% at 0% 0%, both running
   --page-bg-corner to --page-bg-corner-transparent, as background-image
   longhands over the flat --page-bg-middle base that <html> sets. The box moved
   from #page-container to <main>, its parent, which on this page is the same
   height on desktop: the only thing <main> adds is .back-to-top-wrapper, and
   that is position: absolute above 45em.

   THE MARGIN/PADDING SWAP STAYS, and it is no longer load-bearing. style.css:67
   gives every `#page-container` `margin-block-end: 5rem`; margin is outside the
   background box, so until 2026-10-22 the glows stopped 80px short of the footer
   and the flat base showed through as a pale band above the border (reported by
   Joy, who called it a margin issue, which is exactly what it was). Painted on
   <main> that margin is inside the painted box and the wash covers it with no
   swap at all. The swap is kept because the outer height is identical either way
   -- 5rem + content -- so removing it would be a change for its own sake, and
   because .back-to-top-wrapper's `bottom: var(--space-s)` is measured against
   <main>'s bottom edge and the note at css-components.css:1827 depends on this
   5rem being part of what sets it. If this rule is ever emptied further, that
   note is what to read first.

   The `#page-container` half of the selector is a specificity fight, not a
   disambiguation: the margin being overridden is style.css:67's
   `#page-container` at (1,0,0), which a bare class at (0,1,0) loses to whichever
   file loads last. Both selectors name the same element -- search_results.hbs:14
   carries the id and the class together -- so this is (1,1,0) on one box, not a
   descendant chain.

   One thing from the frame deliberately not built: it binds `neutral/bg-soft`
   (#ebf8f8) on the hero layer as a flat fill under the glows. That is within a
   point or two of what the two --page-bg-corner stops already produce over
   --page-bg-middle at the top of the page, so it is not restated -- a third
   opaque layer would only flatten the wash the design is drawing. */
#page-container.search-page-wrapper {
  margin-block-end: 0;
  padding-block-end: 5rem;
}

/* SECTION - Hero
   The band above the breadcrumbs: the query heading and the search field.
   Both are real markup in search_results.hbs rather than vendor output, which
   is the whole reason the design's order (heading, field, breadcrumbs,
   results) is achievable at all --- see the file header.

   Figma draws the hero 302px tall with its content ending at 199px, so 103px
   of it is slack below the field, and part of that slack is a hidden row of
   filter buttons at y=295 that overflows the frame. Discounting the abandoned
   row, what is left reads as a deliberately symmetric band: ~104px of air
   above the heading and ~103px below the field. That ships as one value from
   the space scale on both edges rather than as two measured numbers. */
.kb-search-hero {
  display: flex;
  flex-direction: column;
  /* Figma's 26px between heading and field. */
  gap: var(--space-s);
  align-items: flex-start;
  padding-block: var(--space-3xl);
}

/* Sora SemiBold 20px/1.4 = the frame's `heading/sm`, which is --step-3 and
   --font-bold on this theme's scale. Leading is --leading-heading-3 (1.3)
   rather than the frame's 1.4: the step/leading pairs in css-root.css:133 are
   the theme's own and the heading wraps to at most two lines here, so taking
   the pairing is worth 2px of line box. */
.kb-search-heading {
  font-family: var(--ff-logo);
  font-weight: var(--font-bold);
  font-size: var(--step-3);
  line-height: var(--leading-heading-3);
  color: var(--neutral-text);
  text-wrap: balance;
}

/* Trims the half-leading above and below the heading so the hero's `gap` is
   the only thing setting the space around it. Progressive enhancement, and
   block containers only --- the same treatment and the same caveat as
   css-page-home.css:207. */
@supports (text-box: trim-both cap alphabetic) {
  .kb-search-heading {
    text-box: trim-both cap alphabetic;
  }
}

/* The query itself, filled in by search-results-query.js.

   It is a <q>, not a <span>, and that is the whole localisation story for the
   quotation marks the design draws around the query. Browsers pick the marks
   for `quotes: auto` off the nearest lang attribute, so the curly pair in the
   frame comes out right in every locale the help centre serves without this
   file naming a single character. Hand-writing them would have pinned English
   typography onto 29 translations.

   `:empty` is what keeps a pair of bare quotes off the page before the script
   runs, and on the no-query state (Algolia happily returns results for an
   empty query, so this is reachable). */
.kb-search-query:empty {
  display: none;
}

/* The field.

   This is the positioning context, not the pill --- the pill is the <input>
   itself. That split matters because instantsearch wraps the input in a
   `div.ais-search-box.ais-with-style` of its own at init (the searchBox widget
   is handed `#algolia-query`, which is an <input>, and `wrapInput` is on by
   default). Styling the input directly means the wrapper can appear, or stop
   appearing, without this block caring how many boxes deep the field now sits.
   It is also what the .search-content rule in css-page-header.css does for the
   navbar field.

   560px from the frame; 35rem rather than a px literal so it tracks the user's
   text size. The cap is lifted below 768px --- see the Responsive section. */
.kb-search-field {
  position: relative;
  width: 100%;
  max-width: 35rem;
}

/* Any wrapper the vendor introduces between this box and the input has to
   carry the width through, or the input's `width: 100%` resolves against a
   shrink-to-fit box. Written as a child-of rule rather than naming
   .ais-search-box so it holds if the wrapper's class ever changes. */
.kb-search-field > div {
  width: 100%;
}

/* The pill. `form/bg` and `border-width/regular` are the frame's own.

   The border colour is --form-border (#809094) even though Figma resolves
   `form/border` to #96a6a9 on this instance, and that is not Codex overruling
   the frame. Both fields on this page are instances of the same `autocomplete`
   component: the navbar's (6472:2799) resolves the same variable to #809094,
   which is --form-border exactly. So the component binds #809094 and the hero
   instance carries a local override to #96a6a9 --- which happens to be
   --neutral-icon-soft, an icon token on a form edge. A one-instance override to
   a token from the wrong family is a slip in the file, not a decision, so the
   component's own value stands.

   `radius/round` is not bound on this instance but the frame draws an
   unmistakable pill, and the category rows below bind it explicitly, so the
   token is the right read.

   2.5rem of start padding is measured, not derived: the frame puts the input's
   text box at x=39 inside the 560px field. Stated rather than calc'd off the
   icon's font-size, because the icon is --step-1 and the input is --step-0, so
   a calc would have to restate the icon's size here to be correct --- which is
   the thing it was meant to avoid.

   No focus box-shadow. The frame carries an `active-focused` effect of
   `0 0 0 2px #F69581`, which is the pre-refresh salmon glow --- the same one
   css-page-home.css:783 removed from the home search for standing in front of
   the real focus ring. The sitewide *:focus-visible ring is the cue here too.
   Hover moves the fill only and the border darkens on focus alone, matching
   the decisions already recorded for this same field one breakpoint up at
   css-page-header.css:489. */
.kb-search-field input {
  width: 100%;
  min-height: 2.5rem;
  padding-inline: 2.5rem var(--space-2xs);
  border: var(--border-width-regular) solid var(--form-border);
  border-radius: var(--radius-round);
  background-color: var(--form-bg);
  color: var(--neutral-text);
  /* No font-family. This pinned `var(--ff-primary)` --- Open Sans --- until
     2026-10-25. It was invisible while it stood there, because the document's
     own family was --ff-primary too, so the pin and the inherited value were
     the same face. style.css:40 moved the document to Inter, and at that moment
     this one declaration would have become the only Open Sans input on the
     site. Deleted rather than repointed: css-reset.css:375 already makes form
     controls inherit, so the field now takes the body face by the same route
     every other input on the page does. */
  font-size: var(--step-0);
  line-height: var(--leading-body-0);
  text-align: start;
  transition:
    border-color var(--transition-duration),
    background-color var(--transition-duration);
}

.kb-search-field input:hover {
  background-color: var(--form-bg-hover);
}

.kb-search-field input:focus {
  border-color: var(--form-border-active);
  background-color: var(--form-bg-active);
}

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

/* The magnifier, supplied by us: the vendor's searchBox widget draws no icon
   at v2, so there is nothing here to override or hide.

   --step-1 (~16px) is `font/size/base`, bound on the glyph in both of this
   frame's fields: the hero's (6473:6478) and the navbar's (6472:2799) are the
   same component and both draw it 16x16. That settled a standing question in
   css-page-header.css, where the navbar copy had been pinned at --step-0 on an
   explicit hold --- "there is no measured desktop glyph" --- and asked to be
   re-measured by whoever was next in these frames. It now reads --step-1 too.

   The inset adds the border width rather than being a flat 0.75rem. Figma
   draws the glyph at x=13, which is `space/3` (12) plus
   `border-width/regular` (1): the design's 12px is measured from inside the
   field's edge. This box is not the one carrying that border --- the input is
   --- so an absolute 0.75rem here would land 12px from the outer edge and 11px
   from the inside of the stroke. The calc puts it where the frame does.

   Inert, like its navbar counterpart: it sits over the start of the field, and
   a click aimed there should place the caret rather than land on a decoration. */
.kb-search-field-icon {
  position: absolute;
  inset-inline-start: calc(0.75rem + var(--border-width-regular));
  inset-block-start: 50%;
  transform: translateY(-50%);
  margin: 0;
  color: var(--neutral-icon);
  font-size: var(--step-1);
  line-height: 1;
  pointer-events: none;
}

.kb-search-field input:focus ~ .kb-search-field-icon,
.kb-search-field:has(input:focus) .kb-search-field-icon {
  color: var(--brand-icon);
}
/* !SECTION - Hero */

/* The trail's sitewide spacing is 1.5rem/3rem (style.css:278), kept there so
   seven templates keep their rhythm. This page wants neither: the hero's own
   padding-block-end is already the space above, and the frame puts 32px --- not
   48 --- between the trail and the results. (0,2,0) against the sitewide
   (0,1,0), so this wins wherever the two files land in the cascade, which
   style.css:223 is explicit about not relying on. */
.search-results-page .kb-breadcrumbs {
  margin-block-start: 0;
  margin-block-end: var(--space-m);
}

/* SECTION - Results layout
   `.kb-search-layout` is the root of our `layout` override, so it is the one
   element in the injected tree whose class we choose. Everything below it is
   vendor DOM.

   The frame's columns are 339px and 712px with 23px between them, inside a
   1074px content area. The sidebar ships as a fixed 21.25rem and the results
   column takes the rest, because this theme's content column is 71.5rem
   (1144px) rather than 1074px and a percentage split would hand the extra width
   to the sidebar, which has nothing to do with it.

   `minmax(0, 1fr)` rather than `1fr` on the results track: a hit title is one
   long unbroken string often enough that the automatic minimum would push the
   column past the grid.

   The stats line and the Categories card share row 1 --- in the frame the
   "262 Results" text and the card's top edge are both at y=0 --- so `facets`
   spans all three rows and `align-items: start` keeps the card at its content
   height instead of stretching it down the page.

   `grid-template-rows` IS LOAD-BEARING AND THE `1fr` IS THE WHOLE OF IT. Joy
   reported on 2026-10-23 that the results looked vertically centred on a
   short result set --- "1 result" high up, then 150px of nothing, then the
   card, then 180px more before the paginator. Nothing was centred: every item
   was already at the start of its own row, and the *rows* were the thing with
   the extra height in them. A row-spanning item distributes its height across
   the content-sized tracks it spans, so the sidebar --- 665px tall against
   205px of results --- was pushing 460px of itself into the three rows in
   roughly equal thirds.

   Making the last spanned track flexible is the fix, and it is a spec rule
   rather than a trick: an item that crosses a flexible track is skipped when
   intrinsic track sizes are resolved, so `facets` stops inflating `stats` and
   `hits` at all, and the slack it still needs is found later when the `fr` is
   expanded. Net effect: the two content rows size to their content, the
   paginator sits one row-gap below the last card, and whatever is left of the
   sidebar's height hangs below it in the pager's row.

   The `fr` is on the pager's own row rather than a fourth empty one on
   purpose. A spacer row would work identically, but it would also put a
   `row-gap` between the paginator and the slack --- 16px of dead space at the
   bottom of the page on every result set long enough to out-run the sidebar,
   which is the common case.

   If the sidebar is ever shorter than the results, the `fr` resolves to the
   paginator's own content height and this is a no-op. */
.kb-search-layout {
  display: grid;
  grid-template-columns: 21.25rem minmax(0, 1fr);
  grid-template-areas:
    'facets stats'
    'facets hits'
    'facets pager';
  grid-template-rows: auto auto 1fr;
  align-items: start;
  /* Figma's 23px between columns, 16px between the stats line and the first
     card. */
  column-gap: 1.5rem;
  row-gap: 1rem;
}

#algolia-stats-line {
  grid-area: stats;
}

/* Sora SemiBold 20px, matching the Categories title and the card titles ---
   the frame draws all three at `heading/sm`.

   The string is the vendor's, and it is "262 results" rather than the frame's
   "262 Results": the `stats` override in document_head.hbs already reduces
   that translation to `nb_results(nbHits)` for all 29 locales. Forcing the
   capital with text-transform would mangle languages that do not capitalise
   nouns, and ::first-letter cannot reach past the digits, so the translated
   casing stands.

   `neutral/text-soft` since 2026-10-22, at Joy's request. The request named
   `.ais-nb-results`, which is worth a note because that class does not exist
   on this page: the stats widget's body template is
   `function ({ nbHits, processingTimeMS }) { return translations.stats(...) }`,
   and `nb_results` returns `format_number(nbHits) + " results"` --- one plain
   string, no element around the digits. The whole widget renders as a bare
   text node inside `div.ais-stats--body`, so `#algolia-stats` is the only
   handle there is and the count and its noun have to take the same colour.
   (Which is the right outcome anyway. Splitting them would mean parsing the
   translated string for digits, and the digits are not even in the same
   position in all 29 locales.) */
#algolia-stats {
  font-family: var(--ff-logo);
  font-weight: var(--font-bold);
  font-size: var(--step-3);
  line-height: var(--leading-heading-3);
  color: var(--neutral-text-soft);
}

/* The mobile filter toggle, hidden here rather than absent: the bundle does
   `getElementById('algolia-facets-open').addEventListener(...)` during
   _handleResponsiveness, unconditionally, so removing it from the `layout`
   override would throw before the page ever rendered. The vendor's
   responsiveCSS turns it back on at 768px; see the media query at the end. */
#algolia-facets-open {
  display: none;
}

/* SECTION - Categories sidebar
   `#algolia-facets` is the card. It holds the vendor's close button, the
   hierarchical menu (#algolia-categories) and the label refinement list
   (#algolia-labels).

   Card treatment is the frame's and is identical to the home page's cards:
   neutral/bg, 1px neutral/border-soft, radius/md, and a 0 2px 6px
   shadow/regular drop shadow. Spelled out rather than shared with
   css-page-home.css because the ~40 nested selectors in that file sit inside a
   `.home-page` block and are in live specificity fights across files
   (css-page-home.css:50) --- lifting the card rule out of it to share here
   would move every one of them.

   `padding: 1.25rem 1rem` is `space/5` and `space/4` off the component, both
   named variables rather than artboard slack, so the asymmetry is the design's
   and is kept. */
#algolia-facets {
  grid-area: facets;
  padding: 1.25rem 1rem;
  border: var(--border-width-regular) solid var(--neutral-border-soft);
  border-radius: var(--radius-md);
  background-color: var(--neutral-bg);
  box-shadow: 0 var(--shadow-y-offset) var(--shadow-blur) 0
    var(--shadow-regular);
}

/* The widget's own header element, which the bundle fills with its localised
   "Categories" string. Sora SemiBold: the frame's `heading/xs` is 19px, which
   is --step-3's ~20px --- the nearest step, and the same one the card titles
   and the stats line take, which is what the frame is drawing too.

   The 0.5rem of inline padding is not decoration. It lines the title up with
   the *text* of the rows below it, which are inset by that much so their hover
   pill can extend past it --- in the frame the "Categories" text sits at x=8
   inside a 307px-wide title box for exactly this reason. */
.ais-header {
  margin-block-end: var(--space-3xs);
  padding-inline: 0.5rem;
  color: var(--neutral-text);
  font-family: var(--ff-logo);
  font-weight: var(--font-bold);
  font-size: var(--step-3);
  line-height: var(--leading-heading-3);
}

/* One category row --- a layout rule now, and only that. Read the state rules
   below it before changing anything here: as of 2026-10-23 the row carries no
   fill, no border and no lift in any state, and everything that looks like a
   state lives on the checkbox.

   It was built as the Codex SideNavigation item, read directly: the seven
   `page` instances down the frame's sidebar (6820:10199 and its siblings) are
   instances of `type=external, state=default, mode=maximized`, node
   26638:14496 in the Codex Component Library, and on 2026-10-22 Joy asked for
   the row to be aligned with that component rather than approximated from the
   page frame alone. Three things came out of reading it: the arrow is
   vertically centred, hover is a lifted 6px card rather than a flat pill, and
   there is a selected state this file had no treatment for.

   TWO OF THOSE THREE ARE GONE, and it is worth being clear that the reading was
   not wrong --- the component changed underneath it. The arrow became a
   checkbox on 2026-10-23, so the row stopped being an instance of
   SideNavigation at all, and its states were replaced wholesale by Checkbox's
   (21379:547). The centring survives because it is the one finding about
   layout rather than state. The hover card and the selected fill are recorded
   as deleted in the rules that used to hold them.

   Centred, not top-aligned. An earlier note here claimed the frame edge-aligned
   the arrow on two-line rows and that `space-between` was holding it there. It
   does not: in the sidebar's first row --- 56px tall, a two-line label --- the
   arrow sits at y=20.5 and is 15px tall, so its centre is 28px down, exactly
   half the row. The component is `items-center` and so is this.

   `space-between` goes with it. The component gives the label `flex: 1 0 0` and
   lets the arrow fall at the end, which is the same result while there are two
   children and the better one if the vendor ever reinstates a third (the count
   span the override in document_head.hbs drops).

   0.5rem is the component's `space/2` padding --- a literal, because the
   component fixes it at 8px (style.css:251 for the same decision on the
   breadcrumb row; --space-2xs would render 12px at any desktop width).

   The radius is the one place this rule overrules the frame, and it is a bug
   fix. Figma binds `radius/round` at rest and `radius/sm` from hover onward,
   which is what this had until Joy caught the symptom on 2026-10-22: moving
   along the list flashed a pill. A resting row has a transparent fill and a
   transparent border, so its radius is not visible in the resting state at all
   --- the only moment it can be seen is the 300ms *after* a row loses hover,
   when the shape has already snapped back to 9999px while the fill and border
   are still fading out. The artboard cannot show that, because an artboard has
   no out-transition. Shipping `radius/sm` at rest costs nothing visually (there
   is no visible box to be round) and the fade-out keeps the shape it had. The
   state rules below consequently say nothing about radius.

   The border is transparent at rest and the padding is short by exactly its
   width. The reason it was written that way is spent: hover and selected each
   used to add a 1px border, and taking the width back out of the padding kept a
   row from growing 2px and shoving every row below it down. Nothing adds a
   border to this row any more --- see the hover rule below for why the pill went
   --- so what the pair does now is hold the row at the frame's 36px (a 20px line
   plus 2x7px padding plus 2x1px border) and keep the label's inset from the
   row's outer edge at the 8px the frame measures, which is also what
   .ais-header's 0.5rem lines the "Categories" title up with. Both still matter,
   and the 36px matters more than it did: the row gap at :573 is derived from
   it.

   The rest border's SECOND job is gone as of 2026-10-25, and it is recorded
   here because it was the stated reason this border could not be deleted.
   css-reset.css used to pair the sitewide focus outline with a 2px border
   painting *inside* the box, and this rule's (1,1,0) against the reset's (0,1,0)
   meant declaring a border here blocked it. The reset's border is gone, so that
   argument no longer holds -- but the 36px arithmetic above still needs the
   2x1px, so the border stays on its first job alone.

   What came out of this rule on 2026-10-23, with the pill: the zeroed
   transparent box-shadow (it existed only so hover's lift had something to
   interpolate from, and there is no lift), and three of the four transitioned
   properties. `color` is the only thing the row itself still animates, because
   the label's one colour step when the row is refined is the only thing that
   still changes here. Everything that used to move on the row now moves on the
   checkbox, which carries its own transition.

   0.625rem is Codex's `space/2-5` --- the Checkbox component's gap between box
   and label, read off the Figma component on 2026-10-23 (21379:547). It
   replaces the 0.5rem this row inherited from SideNavigation, which is a
   different component with a different gap. Both are literals for the same
   reason the padding is.

   The `#algolia-categories` prefix is what makes the colour stick, and it is
   the only reason this rule is not a bare class like its neighbours.
   `templates.instantsearch.css` injects
   `.ais-hierarchical-menu--link { color: <config color> }` into <head> after
   every theme stylesheet, and that config value is --neutral-text. A bare
   `.ais-hierarchical-menu--link` here would be (0,1,0) against its (0,1,0) and
   would lose on source order, so the rows would come out full-strength ink
   instead of the --neutral-text-soft the frame draws. The id takes this to
   (1,1,0). The widget mounts on that container, so the prefix is always true.

   No `transition: color` here, deleted 2026-10-23 for the same reason the box
   lost its own transition (the long version is on .kb-category-box): the only
   thing that changes this colour is the refined state, the refined state
   arrives on a remounted element, and a transition cannot animate a value an
   element was born with. Give the row a hover colour and it comes back. */
#algolia-categories .ais-hierarchical-menu--link {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: calc(0.5rem - var(--border-width-regular));
  border: var(--border-width-regular) solid transparent;
  border-radius: var(--radius-sm);
  color: var(--neutral-text-soft);
  font-size: var(--step-0);
  font-weight: var(--font-regular);
  line-height: var(--leading-body-0);
  text-decoration: none;
}

/* The row gap, added 2026-10-23 at Joy's request for more air between items.
   The value is Codex's, not a preference: the Checkbox component's List example
   stacks 20px boxes on a 38px pitch, i.e. 18px of page between one box and the
   next (measured off the Figma render, 21379:547 --- box edges at y=2729/2748,
   next box at 2767). This row is 36px tall with a 20px box centred in it, so it
   already supplies 16px of that from its own padding, and 2px is the whole
   remaining difference. Pitch comes out at 38px, matching Codex exactly.

   It also lands the halo spacing on Codex's: a 32px halo (see the checkbox
   section) in a 38px pitch leaves 6px between the halos of adjacent rows, which
   is what the component's own list leaves.

   If this ever needs to read airier, this one declaration is the lever and
   nothing is derived from it --- 0.5rem would give a 44px pitch. It is a
   deliberately uniform gap rather than one that only opens under checked rows:
   a gap that appeared on refinement would shift every row below it on each
   click, which is a worse version of the crowding it would be fixing.

   Sibling margins rather than `gap` on a flex column, because the two lists
   this has to cover are reached differently. `+` works at both levels --- lvl0
   items are siblings of each other and so are the items inside a lvl1 list ---
   while the step between a parent row and the first of its nested children is
   the lvl1 list's own top margin, since that list is a sibling of the parent's
   Template <div> and not of any item. Setting `display: flex` on the vendor's
   list elements to get `gap` would also be a wider bet on markup the vendor
   owns. */
#algolia-categories
  .ais-hierarchical-menu--item
  + .ais-hierarchical-menu--item {
  margin-block-start: 0.125rem;
}

/* Hover --- and the row no longer has a visible hover state of its own. This is
   the 2026-10-23 change Joy asked the question behind: whether the highlight on
   the whole box-plus-label should go. It should, and the Figma Checkbox
   component (21379:547) is what settles it rather than taste.

   WHAT THIS ROW USED TO DO, and why it was defensible until now: it was built
   as Codex's SideNavigation item (26638:14503), whose hover variant is a lifted
   card --- a 1px `neutral/border-alpha` border, a
   `neutral/bg-component-soft-hover` fill, and the 0/6px/16px `shadow/hover`
   lift the home page's cards use. All three are recorded as deleted here rather
   than just removed, because the reading was correct for the component it was
   reading.

   WHY IT GOES. SideNavigation is a navigation component and this row stopped
   being navigation on 2026-10-23, when the trailing arrow became a checkbox.
   The component it is now an instance of is Checkbox, and Checkbox's twelve
   state cells (3 selections x 4 states, both themes) put *every* state on the
   box and its halo. Not one of them tints the row. So the pill was the old
   component's state treatment outliving the old component, and keeping it meant
   a checked row announced itself twice --- coral box plus filled pill --- with
   the louder of the two signals being the one that is not the control.

   Nesting made that actively wrong, not just redundant. `showParentLevel` marks
   a parent refined when a child is, so the pill landed at full strength on the
   row the reader did not tick; see the indeterminate note in document_head.hbs
   for the part of this that is still open.

   What is left on the row is the label's colour step (in the refined rule
   below) and the pointer, which the <a> gives for free. The hover affordance
   for the whole row is the halo on the box: the row is still the click target,
   and a 32px disc lighting up at the left of it is the component's own answer
   to "something under this pointer is live".

   `text-decoration: none` stays and is not cargo. The reset underlines anchors
   on hover, and this one is an anchor whichever role it carries. */
#algolia-categories .ais-hierarchical-menu--link:hover {
  text-decoration: none;
}

/* Focus. A claim that used to sit here is now measured as false: "the component
   set has no focus variant --- Codex draws focus with a separate FocusRing
   component --- so this is the theme's own, not the design's." The Checkbox
   component has a focus variant in every one of its three selection rows, and
   it is a 2px `form/focus-ring` ring around the halo. That was never visible
   from SideNavigation, which is what this rule was written against.

   So the indicator moves to the box with everything else, and the row's own
   outline is suppressed here. That is a deliberate call and the one judgement
   in this change worth a keyboard pass:

   - For it: the ring is the design's; two indicators on one tab stop is the
     same defect `81fd7cd` fixed on the dropdown rows, where the reset's outline
     and a border drew the ring twice; and the ring sits on the box, which is
     the thing Space actually toggles, so it teaches the control's nature to the
     one group of readers who cannot see the pointer affordance.
   - Against it: the tab stop is a 307px row and the indicator is a 36px circle
     at the left end of it, which is a smaller mark than the reset gives every
     other focusable control in the theme.

   2.4.7 is met either way --- the ring is plainly visible --- so this is a
   legibility trade rather than a conformance one. `outline: none` is the whole
   lever: deleting that one declaration brings the row outline back alongside
   the ring.

   No fill and no transition override left. The ring does not ease in (the halo
   transitions background-color only, not box-shadow --- see the checkbox
   section), so the reason `transition: none` was here is gone with it. */
#algolia-categories .ais-hierarchical-menu--link:focus-visible {
  outline: none;
  text-decoration: none;
}

/* The refined category. All that is left of this rule is the label's colour
   step, and that step is the row's entire remaining state treatment.

   It used to be SideNavigation's `state=active` variant (26638:14510) --- a 1px
   `neutral/border-alpha` border, `neutral/bg-component-soft-active` behind it,
   `neutral/text` on the label and no lift. The box and the fill went on
   2026-10-23 with the hover pill, for the reason the hover rule gives at
   length; the ink is the one part that survived, because it is type colour
   rather than a second box and it is the only thing on the row that says "you
   are filtering by this" once the pill is gone.

   This is a deliberate departure from the Checkbox component, which appears to
   set `neutral/text` on its label in every enabled state --- its twelve state
   cells all read at the same weight. Keeping the soft-to-full step costs
   nothing, is free in both themes, and is a real signal on a thirteen-row list
   where only one row can be refined at a time. Codex's own list is four rows of
   independent checkboxes and has less need of it. If the resting list ever
   wants to read darker, `--neutral-text-soft` in the base rule is the lever,
   and this rule then has nothing left to do.

   `--item__active` is instantsearch's class, not ours. The widget is built with
   bemHelper('ais-hierarchical-menu'), whose modifier form is
   `block--element__modifier`, and RefinementList puts
   `cx(cssClasses.item, { [cssClasses.active]: facetValue.isRefined })` on the
   <div> it wraps each row in --- so the refined row's wrapper carries
   `ais-hierarchical-menu--item__active`.

   The bare `div` in the middle is not padding in the selector. instantsearch's
   Template component renders
   `createElement('div', { ...rootProps, dangerouslySetInnerHTML })`, so the
   anchor the `hierarchicalItem` override returns is always one anonymous,
   classless <div> below the item wrapper:

     div.ais-hierarchical-menu--item.ais-hierarchical-menu--item__active
       div                                  <- Template's root, unstyled
         a.ais-hierarchical-menu--link
       div.ais-hierarchical-menu--list__lvl1 <- nested children, if any

   The vendor's own stylesheet writes the same `__active > div > --link` chain,
   which is what put me onto it; that file is commented out in
   document_head.hbs, but its selectors are still written against this DOM.

   Both child combinators are load-bearing. The widget runs with
   showParentLevel on, so refining a section marks its parent category refined
   too and nests the child list inside the parent's wrapper --- as the sketch
   above shows, that list is itself a direct <div> child. A descendant selector
   would paint the parent row and then every row nested under it; `> div >`
   reaches the Template root and stops, because the sub-list's own direct
   children are item wrappers rather than links.

   This used to be two selectors, the second a copy ending
   `:is(:hover, :focus-visible)`, so that a refined row kept its fill when
   hovered: (1,3,1) against hover's and focus's (1,2,0). Neither of those rules
   declares a colour any more, so there is no tie left to win and the duplicate
   came out. If anything colour-bearing is ever added back to :hover, it comes
   back with it. */
#algolia-categories
  .ais-hierarchical-menu--item__active
  > div
  > .ais-hierarchical-menu--link {
  color: var(--neutral-text);
}

/* `flex: 1 0 0` and a zeroed min-size are the component's. The label takes the
   row's free space and the arrow falls at the end of it, which is what replaced
   `space-between` above. The min-size is the half that actually matters: a flex
   item's automatic minimum is its content, and for a 50-character section path
   that is wider than the 307px sidebar, so without it a long category would
   force the row wider than the card instead of wrapping inside it.

   Two-line rows are 56px in the frame against 36px for one line, i.e. exactly
   one extra 20px line box with the padding unchanged --- so the clamp is the
   design's own and not a guess. A third line gets an ellipsis.

   The component itself is `whitespace-nowrap` with a single-line ellipsis, and
   this is the one place the row deliberately departs from it. Every instance in
   the frame's sidebar has that nowrap lifted, and the first two of the seven are
   drawn at 56px with wrapped two-line labels; the page's own frame is the more
   specific authority. Ellipsising at 307px would also cut "Admin Support /
   Conducting Ongoing Training Campaigns" --- the frame's own first row --- after
   its first three words.

   `overflow-wrap` is the component's `word-break: break-word` in the standard
   spelling, and only bites on a single unbreakable token. */
.ais-hierarchical-menu--label {
  display: -webkit-box;
  flex: 1 0 0;
  min-inline-size: 0;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  overflow-wrap: break-word;
}

/* SECTION - The filter checkbox
   ------------------------------------------------------------------------
   Replaces the trailing `fa-arrow-up-right` on 2026-10-23, at Joy's request.
   The arrow came from the frame's `type=external` SideNavigation instances and
   was advertising navigation on a control that refines the result set in place;
   the markup note in document_head.hbs has the full reasoning, including why a
   chevron is not the replacement either.

   WHY THIS IS A DRAWN <span> AND NOT AN <input type=checkbox>. A real input is
   the obvious implementation and it breaks nested rows, in a way that is worth
   spelling out because it looks like it should work --- the widget's
   handleItemClick has an explicit branch for it:

     if ('INPUT' === e.target.tagName) return void this.refine(t, r);
     for (var a = e.target; a !== e.currentTarget; ) {
       if ('LABEL' === a.tagName && a.querySelector('input[type="checkbox"]'))
         return;
       'A' === a.tagName && a.href && e.preventDefault();
       a = a.parentNode;
     }
     e.stopPropagation();
     this.refine(t, r);

   Both of the early returns skip the `stopPropagation()` at the bottom. The
   handler is bound to each item <div>, and a refined row's child list is
   rendered *inside* its parent's item <div> (the DOM sketch above) --- so a
   click on a child row bubbles through its parent's handler too. With an <a>
   that is harmless, because the full walk runs and stops the event. With a
   <label> wrapping a checkbox, the child returns early, the browser forwards
   the click to the input, that second event reports `target.tagName ===
   'INPUT'`, the child refines and returns early again, and the event carries on
   up to the parent, which refines on top of it. Two toggleRefinement calls on a
   single-path hierarchical facet in one tick: the child's refinement is
   replaced by the parent's. Keeping the <a> keeps the stopPropagation.

   `aria-checked` on the anchor is both the announced state and the style hook
   below, so the two cannot drift. `role=checkbox` replaces the link role on
   purpose; Space is wired up in assets/search-results-query.js, since an <a>
   does not answer it and a role=checkbox is expected to.

   EVERY VALUE BELOW IS MEASURED OFF THE FIGMA COMPONENT, which Joy supplied on
   2026-10-23 (Codex Component Library, 21379:547). The box was approximated
   before that from the page frame and a DevTools read of the Vue
   implementation, and three of the approximations were wrong. Where a number
   here looks arbitrary, it was read out of the render at 1x and is recorded in
   the rule it belongs to.

   Two things the component contradicted, so they are not reinstated by accident:

   - THE BOX IS 20px, NOT 16px, AND TAKES --radius-sm. The old note here said
     "no --radius-* token is small enough --- the scale starts at --radius-sm's
     6px, which on a 16px box reads as a squircle rather than a checkbox." The
     premise was the 16px, which was this file's guess; `size-5` plus
     `rounded-sm` is the component, so the token fits after all and the 0.25rem
     literal is gone. 20px is also exactly the row's line box, so the row stays
     36px and nothing below it moves.
   - FORM-TIER TOKENS WERE THE WRONG TIER. The old note argued "--form-bg /
     --form-border are the only pair in the token set with a dark tier written
     for one". True of the tokens, but Checkbox does not use them: it is
     `bg-neutral-soft` on `border-neutral-strong`. Sampled from the render those
     are #ebf8f8 on #384649 light and #172029 on #99aec1 dark, which is
     --neutral-bg-soft and --neutral-border-strong in both tiers exactly. So the
     neutral tier was right all along and carries dark mode with no rule, which
     is the same payoff the error page and the header avatar took.

   The one form token that survives is --form-focus-ring, on the halo below, and
   it is the right one: #177da9 light, #9884de dark, both matching the render to
   the byte. css-brand-tokens.css:850 already notes that the dark ring is indigo
   rather than teal by design.

   Hover does NOT step the box's border. A rule here used to do that
   (--form-border-active) on the reasoning that "the row is already changing
   fill, border and lift underneath, so the box matching that would be a third
   thing moving" --- both halves are spent now, since the row changes nothing
   and the component's hover variant leaves the box untouched and lights the halo
   instead. The rule is deleted rather than emptied.

   NO TRANSITION ON THIS RULE, and that is a deletion rather than an omission.
   It carried `transition: background-color, border-color` at
   --default-transition-duration until 2026-10-23, which was dead the moment
   hover stopped touching the box: nothing changes either property on an
   element that survives long enough to transition. The checked state changes
   both, but it arrives on a brand-new element (see the keyframes below), and a
   transition cannot animate a value an element was born with. Put a hover or
   disabled treatment back on the box and the transition comes back with it. */
#algolia-categories .kb-category-box {
  position: relative;
  z-index: 1;
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border: var(--border-width-regular) solid var(--neutral-border-strong);
  border-radius: var(--radius-sm);
  background-color: var(--neutral-bg-soft);
}

/* The halo --- the "nice animation when they are clicked" Joy asked for, and
   the reason the box gained a wrapper element.
   ------------------------------------------------------------------------
   In the component this is a pseudo-element on the button that wraps the box:
   a 32px round disc, centred on the 20px box, transparent at rest, with
   `transition: background-color .15s ease-in-out` and nothing else animated.
   Joy read that rule out of DevTools before the Figma link arrived and it is
   quoted here because it is the whole mechanism:

     .kb-checkbox::before {
       background-color: rgba(0, 0, 0, 0);
       content: ""; position: absolute;
       top: 50%; left: 50%; transform: translate(-50%, -50%);
       height: calc(var(--spacing) * 8); width: calc(var(--spacing) * 8);
       border-radius: 3.40282e+38px;
       transition: background-color 0.15s ease-in-out;
     }

   WHAT IT ANIMATES TO, measured rather than guessed. A 1px horizontal strip
   through the focus cell of the component's states matrix reads, outward from
   the box: #384649 border, then seven pixels of #deebec, then 2px of #177da9,
   then the page. So the disc fills with --neutral-bg-component (#deebec light,
   #2e3e4e dark --- both exact) and the focus ring is 2px of --form-focus-ring
   sitting immediately outside the disc, with no gap. Hover is the same disc
   with no ring. There is no third value: the component does not tint the disc
   differently when the box is checked.

   WHY A WRAPPER ELEMENT AND NOT A PSEUDO-ELEMENT ON THE BOX. Both of the box's
   own pseudo-elements would do, except for paint order: an absolutely
   positioned ::before is in the positioned layer and paints *above* its
   statically positioned parent's background, so a 32px disc drawn from the box
   would wash over the box's own fill and border rather than sitting behind
   them. `z-index: -1` does not rescue it --- a negative-z child paints behind
   its parent's background only by escaping to an ancestor stacking context,
   which here would put it behind #algolia-facets' card fill and make it
   invisible. The component solves it with two elements (`button.kb-checkbox`
   carries the disc, the inner `div` carries `relative z-1`) and so does this:
   .kb-category-check is the positioning parent, .kb-category-box takes the z-1.
   Same shape of fix, and the same reasoning, as the avatar/globe chip split in
   css-page-header.css --- one element cannot hold both a box and the thing
   drawn around it without deriving one from the other.

   `inset: -0.375rem` rather than a stated 32px. The component's disc is the box
   plus 6px on every side; expressing it as a negative inset means it is still
   the box plus 6px if the box size ever changes, and there is no second number
   to keep in step. Round via --radius-round, which is the component's 9999px.

   ONLY background-color transitions, which is the component's choice and also
   this file's: the ring is a box-shadow and it appears at once, the way the
   focus rule above used to argue for with `transition: none`. A ring that eased
   in would lag the keyboard.

   --default-transition-duration is Codex's 0.15s and is used here in place of
   this file's usual --transition-duration (300ms), because 0.15s is the value
   in the component's own rule above. It also gets reduced motion for free:
   css-brand-tokens.css:1121 zeroes this token under prefers-reduced-motion, so
   the disc snaps instead of fading with no rule here. This is the only
   transition left in the section --- every other piece of motion on the
   checkbox is a keyframe animation, for the reason below.

   WHY THE REST OF THE SECTION IS ANIMATIONS AND NOT TRANSITIONS, now read out
   of the bundle rather than inferred. The vendor's category list is rendered
   with React (react-dom, instantsearch.js v1's RefinementList component), and
   `_generateFacetItem` builds each row's React key as

     path + '/' + isRefined + '/' + count

   so a row whose refinement or count changes is not patched, it is *unmounted
   and remounted*. Ticking a row changes its own isRefined and most siblings'
   counts, so the clicked element is gone before any state could transition on
   it. That rules out :active for good and it rules out transitioning the
   checked fill --- but it is exactly the condition a CSS animation wants, since
   animations run on insertion. So "animate on click" is buildable after all:
   the box fills and the tick draws from @keyframes further down, firing because
   the row is new, not because anything on it changed.

   The halo is the one thing that still has to be a transition: it answers the
   pointer, and on a freshly mounted row the pointer is already inside, so there
   is no hover change to animate and nothing for an animation to be keyed to
   either. It fades on the next row the pointer reaches.

   What survives of the old note here: the halo reads as a click response partly
   because the pointer is over the row. That is still a hover state doing some
   of that work --- it is just no longer doing all of it. */
#algolia-categories .kb-category-check {
  position: relative;
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
}

#algolia-categories .kb-category-check::before {
  content: '';
  position: absolute;
  inset: -0.375rem;
  border-radius: var(--radius-round);
  background-color: transparent;
  transition: background-color var(--default-transition-duration)
    var(--ease-in-out);
}

/* Hover and focus both fill the disc; focus adds the ring. Driven from the row
   rather than the box, because the row is the click target and the tab stop ---
   the component's own trigger is the box, but ours is 307px wide.

   2px is Codex's `border-width/heavy`, which is NOT in this theme's token set
   (css-brand-tokens.css has --border-width-regular and nothing else), so it is
   a literal here. Worth adding to the token file in the token phase rather than
   as a side effect of this page. */
#algolia-categories
  .ais-hierarchical-menu--link:is(:hover, :focus-visible)
  .kb-category-check::before {
  background-color: var(--neutral-bg-component);
}

#algolia-categories
  .ais-hierarchical-menu--link:focus-visible
  .kb-category-check::before {
  box-shadow: 0 0 0 2px var(--form-focus-ring);
}

/* Checked. --brand-bg is one of the few tokens with no dark tier at all
   (#d73312 in both themes), which is why it is safe here and why the tick below
   is --fixed-white rather than --neutral-icon-inverse: the inverse role flips
   to #10171e in dark and would put a near-black tick on an unchanged coral
   fill. White on #d73312 is 4.0:1, over the 3:1 a non-text graphic needs.

   The border is --brand-border and not the fill colour repeated, which is a
   correction from the Figma file. The Vue implementation drops the border
   outright on check (`group-data-[state=checked]:border-none`) and this rule
   approximated that by painting the border the same coral as the fill; the
   component draws a 1px #ef4523 rim on the #d73312 fill, read straight off the
   render's box edges. --brand-border is that value and, like --brand-bg, has no
   dark tier --- so the checked box is identical in both themes, which is what
   the component's dark states panel shows too.

   Nothing needs to outrank hover any more: hover no longer touches the box, so
   a checked box keeps its brand fill without this rule having to win a tie. It
   stays after the hover rules on source order regardless, which costs nothing
   and keeps the section reading in state order.

   THE ANIMATION IS THE CLICK FEEDBACK Joy asked for on 2026-10-23, and the
   keyframe runs from the *unchecked* pair rather than from nothing: the box the
   reader just ticked arrives already coral, so playing the neutral values back
   as the start of a 0.2s ramp is what puts the fill change on screen at all.
   The two `from` colours are the base rule's own, repeated here because a
   keyframe cannot say "whatever this was before" --- if --neutral-bg-soft or
   --neutral-border-strong ever changes up there, change it here too.

   --motion-duration-control, not --default-transition-duration: 0.2s is the
   token the brand file reserves for "the thing the user clicks", and the halo's
   0.15s is the component's hover value rather than a click value. They are
   deliberately different numbers on deliberately different events, and both
   zero under prefers-reduced-motion.

   There is no `to`, so the animation ends on the declarations above and no fill
   mode is needed. It fires on insertion, which is every time this row is
   rebuilt and not only when it was just clicked --- a checked row whose count
   changes will replay it. That is a 0.2s re-fill of a box that is already the
   right colour, which is why it is accepted rather than guarded. */
#algolia-categories
  .ais-hierarchical-menu--link[aria-checked='true']
  .kb-category-box {
  border-color: var(--brand-border);
  background-color: var(--brand-bg);
  animation: kb-category-box-check var(--motion-duration-control)
    var(--ease-in-out);
}

@keyframes kb-category-box-check {
  from {
    border-color: var(--neutral-border-strong);
    background-color: var(--neutral-bg-soft);
  }
}

/* The tick: a rectangle with two borders, rotated --- the breadcrumb
   separator's technique (style.css:392), which is the fourth thing in this
   theme to need a shape the Font Awesome kit cannot give it. There is no
   webfont, so a codepoint renders tofu, and an <input> cannot carry a
   pseudo-element at all; a drawn shape sidesteps both.

   Declared only in the checked state, so there is nothing to toggle and no
   opacity transition to tune.

   SIZED OFF THE COMPONENT'S OWN MARK, and in rem rather than em. The component
   draws a real Font Awesome check (`font/family/icon`, weight
   `font/weight/icon-solid`), which this theme cannot: the kit is JS-only with
   no webfont, so a codepoint renders tofu. What it can do is match the mark's
   geometry, and the render gives it at 1x --- inside the 20px box (x 464..484)
   the ink spans x 469..479 and y 2773..2782, so an 11 x 10px mark on a 2px
   stroke, with the short arm running (4, +3) and the long arm (6, -9) from the
   vertex. Arm lengths 5px and ~10.8px, i.e. a 2.16:1 ratio rather than the 1.8
   this rule had.

   So `inline-size` is the long arm and `block-size` is the short one: with
   border-box sizing, 0.6875rem/11px of bottom border and 0.3125rem/5px of left
   border, both 2px thick. That rotates to an 11.3px visual square, against the
   component's 11 x 10 --- as close as a two-edge approximation gets to a real
   glyph. The em units came out with the 16px box: the box is a fixed 1.25rem
   now rather than a multiple of the row's type, so a mark that tracked
   --step-0 would drift away from the box it sits in.

   The rotation is -45deg and does NOT mirror under [dir='rtl']: a checkmark is
   a glyph-like mark with a fixed handedness, not a directional affordance like
   the breadcrumb chevron.

   THE translateY IS DERIVED, not an eyeballed nudge, and the derivation is the
   paginator arrows' with one term those do not need. Rotating about the box
   centre keeps the *element* centred but not the ink, because only two of the
   four edges are drawn.

   The first version of this rule took the hairline limit: the vertex lands
   (w+h)/2 x sin45 below centre, the arm tips (w-h)/2 x sin45 above it, so the
   ink's centre sits h x sin45 / 2 below the box's --- 35.4% of the block-size,
   1.77px here.

   A 2px stroke is not a hairline, and that gap is the whole of what Joy
   reported on 2026-10-23. The lowest ink is still the outer vertex, but the
   highest is no longer the outer arm tip: it is the *inner* corner of the long
   arm, one stroke in from the edge, at -(w-h+2t)/2 x sin45. Average the two and
   the stroke cancels most of the correction --- (h-t) x sin45 / 2, which is
   1.06px rather than 1.77px. The old value overshot by 0.7px, and that is
   exactly what the screenshot shows: ink in rows 4..13 of the box's 20, four
   pixels of air above the mark and six below it.

   -23.5% is the centre of mass rather than the centre of that bounding box,
   which is the better target for a mark this asymmetric. Integrating the drawn
   L, mitre included, puts the centroid 1.175px below the element centre against
   the bbox's 1.061px; they differ by a tenth of a pixel, so either reads as
   centred, and the mass is the one the eye tracks. Checked against Joy's 1x
   screenshot: the ink's coverage-weighted centroid sat at 9.20 in the 20px box,
   0.80px high, where the model predicts 0.60px --- agreement to 0.2px, which is
   as close as a thresholded read of an antialiased shape gets.

   The percentage basis is the block-size, so the number is only stable while
   the stroke stays 2px against a 5px block: the fraction is
   sin45 x (1 - t/h) / 2. Change either and recompute rather than nudging. The
   transform list reads right-to-left, so the mark is rotated first and then
   lifted on screen.

   Left in as a known difference rather than matched: the component's own tick
   sits about 1px LOW in its box (ink centre y=2777.5 against the box's 2776.5),
   so Codex is a pixel below geometric centre where this is now on it.
   Reproducing that bias means about -3.5%, near enough to deleting the
   translate altogether. Not done, because centred is what was asked for and 1px
   is inside the margin the measurement itself carries.

   HORIZONTALLY THERE IS NOTHING TO CORRECT, which is worth stating because the
   screenshot looks like there is. The rotated ink's extremes are
   +/-(w+h)/2 x sin45 either side of the element centre --- symmetric by
   construction, since the inner corners fall inside that span on both ends ---
   and the flex rules above centre the element in the box. The measured ink
   reads about half a pixel left, and the model says it cannot be the mark, so
   it is the box's own fractional position in a grid-sized sidebar. Deliberately
   no x term. If it still reads left after this change, the lever is the first
   argument of the translate, in px, and it wants a fresh measurement first.

   THE DRAW-IN is the other half of the click feedback, and it is on this rule
   rather than the box's so the tick arrives a beat after the fill: the delay is
   --motion-duration-instant (75ms), the one token in the set small enough to
   read as sequence rather than lag, and the ramp itself is the 0.15s the halo
   uses. `both` is load-bearing with a delay --- without it the tick would paint
   at full size for 75ms and then animate from nothing, which is the artefact
   this is meant to avoid.

   scale(0.4) rather than 0: a two-border shape scales its stroke with it, and
   from 0 the first frames are sub-pixel and read as a flicker. Both the
   translate and the rotate are repeated in the keyframe because a transform
   list interpolates as a whole --- drop either and the mark would spin or jump
   into place. */
#algolia-categories
  .ais-hierarchical-menu--link[aria-checked='true']
  .kb-category-box::after {
  content: '';
  inline-size: 0.6875rem;
  block-size: 0.3125rem;
  /* Physical sides, for the reason style.css:404 gives: the shape is which two
     edges are drawn before the rotation, so letting logical properties flip
     them under RTL would cancel against the rotation. */
  border-bottom: 2px solid var(--fixed-white);
  border-left: 2px solid var(--fixed-white);
  transform: translate(0, -23.5%) rotate(-45deg);
  animation: kb-category-tick-draw var(--default-transition-duration)
    var(--ease-out) var(--motion-duration-instant) both;
}

@keyframes kb-category-tick-draw {
  from {
    opacity: 0;
    transform: translate(0, -23.5%) rotate(-45deg) scale(0.4);
  }
}
/* !SECTION - The filter checkbox */

/* The nested child list. Until 2026-10-23 this had no rule anywhere in the
   theme --- it appeared in one comment, as a line in the DOM sketch above, and
   nowhere else --- so a refined category's sections rendered at exactly their
   parent's inset and the hierarchy was invisible. Joy reported it.

   The indent and the rule together are Codex's live product sidebar, which she
   supplied as the reference: nested items step in from a hairline that runs the
   height of the group. --neutral-border-alpha is the one the row's own hover
   border uses, and it is a true alpha pair (#10171e1a light, #ffffff1a dark),
   so it reads as the same weight of line on both surfaces with no dark rule.

   The two lengths are an alignment rather than a pair of chosen numbers. The
   row's box lives on the <a>, which carries 0.5rem of padding, so 0.5rem of
   margin stands the hairline exactly on the group's content edge --- flush with
   the left of the parent's own checkbox, directly above it. 0.75rem of padding
   then clears the line before the child's checkbox begins, which puts a child
   label 20px in from its parent's. The alternative was to centre the line under
   the parent's checkbox (1rem) for the connector look of a file tree; this
   sidebar is a filter and not a tree, and the flush edge keeps all three
   verticals --- hairline, parent box, child box --- on a readable rhythm.

   Only `__lvl1` exists in practice. The widget is configured with two
   attributes --- category.title then section.full_path --- so the tree is two
   levels deep and there is no `__lvl2` to write a rule for. If a third
   attribute is ever added, this becomes a `[class*='--list__lvl']:not(__lvl0)`
   job rather than another copy of this rule.

   WORTH KNOWING, because it looks like this rule is not working: most of what
   reads as an un-indented child is not a child. Several Zendesk category titles
   genuinely contain a slash --- `PAB / Admin Support` and
   `Installing and Configuring / Settings and Customization` are both real
   values of category.title in the live index --- so they are siblings of `PAB`
   at level 0, sorted next to it by the widget's `name:asc`. A genuine child is
   a section, and its label is the leaf alone (`Admin Support`), because the
   helper derives it as `last(path.split(separator))` and section.full_path uses
   the ` > ` the widget is configured with. Nothing needs stripping from these
   labels. */
#algolia-categories .ais-hierarchical-menu--list__lvl1 {
  /* The row gap, for the one step the `+ --item` rule cannot reach: this list
     is a sibling of the parent's Template <div>, not of any item, so the space
     between a refined parent and the first of its children is this margin. Same
     0.125rem, same derivation. */
  margin-block-start: 0.125rem;
  margin-inline-start: 0.5rem;
  padding-inline-start: 0.75rem;
  border-inline-start: var(--border-width-regular) solid
    var(--neutral-border-alpha);
  animation: kb-category-sublist-in var(--motion-duration-moderate)
    var(--ease-out);
}

/* The reveal, added 2026-10-23: Joy's "I'm not a big fan of the snapping open".
   ------------------------------------------------------------------------
   An animation rather than a transition for the reason the checkbox section
   gives at length --- this <ul> does not exist until the parent is refined, and
   a transition has no start value on an element that was just inserted. Being
   newly inserted is exactly what starts an animation, so the open state is the
   one piece of motion on this page that is free.

   IT FIRES ON OPEN AND NOT ON EVERY CLICK, which is the thing that makes this
   safe to put on a vendor-rendered list. The nested list is passed to
   RefinementListItem as `subItems` and rendered as a *sibling* of the Template
   <div>, not inside its dangerouslySetInnerHTML --- so it is its own React
   subtree. Refining a child changes that child's key and remounts the child
   row; it does not touch the parent's key (path/isRefined/count, with isRefined
   still true and a level-0 count that the helper computes without the level-0
   refinement applied), so the <ul> element itself survives and does not replay.
   Tick a sibling category and the old list unmounts, the new one mounts and
   animates --- which is correct, because that is an open.

   --motion-duration-moderate is the token the brand file reserves for "changes
   that move layout", which is precisely what this is, and it zeroes under
   prefers-reduced-motion. --ease-out rather than --ease-in-out: the motion
   starts at the moment of the click, so there is nothing for an ease-in to ramp
   out of.

   THE HEIGHT GROWTH IS PROGRESSIVE, and the fade is the part everyone gets.
   Animating to `block-size: auto` needs `interpolate-size: allow-keywords`
   (Chromium 129, Safari 26, not Firefox as of 2026-10), so it sits behind
   @supports and browsers without it get the fade and the 4px slide, which read
   as an open on their own. Delete the @supports block and nothing breaks.

   The reason it is worth having: without it the rows below jump their full
   height in one frame while the list fades in over 250ms, so the fade lands on
   motion that already finished. The alternatives were both worse --- a
   max-block-size ramp eases against a cap rather than the content, so a
   two-item list would finish in a fifth of the duration, and a
   grid-template-rows 0fr/1fr reveal needs a wrapper element around the items,
   which is markup the widget owns.

   `overflow: clip` only matters while the height is short of the content, so it
   lives in the @supports block with the growth rather than on the rule itself.
   overflow-clip-margin buys back 3px for the child rows' focus rings: a halo
   ring is 34px inside a 36px row, so the first child's would otherwise sit
   flush against the clip edge and lose a pixel. */
@keyframes kb-category-sublist-in {
  from {
    opacity: 0;
    translate: 0 -0.25rem;
  }
}

@supports (interpolate-size: allow-keywords) {
  #algolia-categories .ais-hierarchical-menu--list__lvl1 {
    interpolate-size: allow-keywords;
    overflow: clip;
    overflow-clip-margin: 0.1875rem;
    animation-name: kb-category-sublist-in, kb-category-sublist-grow;
  }

  @keyframes kb-category-sublist-grow {
    from {
      block-size: 0;
    }
  }
}

/* The vendor's hierarchicalItem template renders a `--count` span; the
   override in document_head.hbs drops it, because the frame draws no counts.
   This rule is here for the refinementList below, which still emits counts of
   its own and is hidden rather than overridden. */
.ais-hierarchical-menu--count {
  display: none;
}

/* Label/tag facets, off since before the refresh and left off: the frame's
   sidebar is Categories and nothing else. Its header and checkbox list are
   vendor markup that no rule in this file styles, so showing it would put an
   unstyled block under a styled one. */
#algolia-labels {
  display: none;
}

/* The responsive panel's close button, which only has a job once
   responsiveCSSFacets has turned the sidebar into a fixed overlay. Hidden at
   desktop for the same reason #algolia-facets-open is: the bundle binds a
   listener to it by id during _handleResponsiveness and would throw if the
   `layout` override omitted it.

   Still the vendor's <div> pair rather than a <button>, unlike the filter
   toggle. A button needs an accessible name, and there is no dynamic-content
   key for "close" to build one from --- inventing an English literal for 29
   locales is worse than leaving the vendor's own markup alone. The mobile
   facets panel's keyboard story is a pre-existing gap, not one introduced
   here, and mobile is not in this frame. */
#algolia-facets-close {
  display: none;
}
/* !SECTION - Categories sidebar */

/* SECTION - Result cards */
#algolia-hits {
  grid-area: hits;
}

/* The gap between cards is declared on both levels because the hits widget
   puts an `.ais-hits` div between its container and the items, and which of
   the two is the flex parent is the vendor's business, not ours. Whichever one
   holds the items, the 16px lands. */
#algolia-hits,
#algolia-hits .ais-hits {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* `.search-result` is the card, and the four data-algolia-* attributes on it
   are load-bearing: _addClickListener walks up from a clicked
   `a.search-result-link` to the nearest `.search-result` and reads
   objectid/articleid/position/queryid off it to report the click. Keep all
   five names if this card is ever restructured again --- without them click
   analytics silently stops, and the console message the bundle logs only fires
   for the missing-ancestor case.

   `position: relative` is for the whole-card click overlay below. 2rem is the
   component's `space/8`. */
.search-result {
  position: relative;
  padding: 2rem;
  border: var(--border-width-regular) solid var(--neutral-border-soft);
  border-radius: var(--radius-md);
  background-color: var(--neutral-bg);
  box-shadow: 0 var(--shadow-y-offset) var(--shadow-blur) 0
    var(--shadow-regular);
  transition: all 200ms ease-in-out;
}

/* Hover and active mirror the home page's cards exactly (css-page-home.css:264
   and :276), including the reasoning: the shadow goes to `none` on press so
   that it reads as the inverse of the hover lift rather than an undo of it,
   and the border darkening on press is ours rather than Codex's because at
   this size the shadow alone is a weak cue. */
.search-result:hover {
  border-color: var(--neutral-border);
  box-shadow: 0 var(--shadow-y-offset-hover) var(--shadow-blur-hover) 0
    var(--shadow-hover);
}

.search-result:active {
  border-color: var(--neutral-border-strong);
  box-shadow: none;
}

/* The card's focus ring is the *link's*, drawn on the card. The link fills the
   card via the overlay below, so a ring on the anchor's own box would trace
   the title text and leave the rest of the clickable area out of it.

   This one writes `outline` out longhand rather than setting --outline-offset
   the way the home page's cards do (css-page-home.css:309). Those cards ARE
   the focusable element, so the sitewide `*:focus-visible` rule matches them
   and they only have to retune its custom properties. This card is a <div>
   with the anchor inside it, so that rule never matches it and there is no
   ring here to retune -- it has to be drawn. --form-focus-ring and the flat
   2px/2px are the same values css-reset.css:19 would have produced.

   Ordered after :hover/:active on purpose, same as the home page's cards: all
   three are in the same specificity class and source order is what decides a
   card that is both hovered and focused. */
.search-result:has(.search-result-link:focus-visible) {
  outline: 2px solid var(--form-focus-ring);
  outline-offset: 2px;
  box-shadow: none;
  transition: none;
}

/* Cancels the sitewide ring on the anchor, now that the card draws one.
   `border: 0` went with it until 2026-10-25, and the note that used to be here
   called it "the one that matters": css-reset.css paired its outline with
   `border: 2px solid var(--form-focus-ring)` painting *inside* the box, and
   this anchor declares no border of its own, so that border appeared from
   nowhere on keyboard focus and reflowed the card header. The reset no longer
   has it, so the cancellation is dead and gone.
   `outline: none` is the half that was never about the border and still stands
   -- the cue is the card's ring on the rule above. (0,3,0) against the reset's
   (0,1,0), which is more than it needs now but costs nothing. */
.search-result .search-result-link:focus-visible {
  outline: none;
}

/* Title row: title, then the arrow. 0.75rem is the frame's `space/3`, and the
   gap now applies once rather than twice --- the vote badge sat between them
   until 2026-10-22, when Joy had it removed outright.

   `flex-start`, not `center`. In the frame the arrow sits at y=0 of the header
   while the title box runs to 56px on a two-line title, so it stays pinned to
   the first line rather than drifting to the middle of a wrapped title. */
.search-result-link-wrapper {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  margin: 0;
}

/* Sora SemiBold 20px/1.4 = `heading/sm`, with this theme's own leading pairing
   for the same reason as the hero heading. `flex: 1` is what holds the arrow at
   the end of the row and makes a long title wrap inside its own box instead of
   pushing it out of the card.

   Prefixed with the container id for the same reason the category rows are:
   `templates.instantsearch.css` injects a `.search-result-link { color: ... }`
   rule after this file. It happens to inject the same --neutral-text this
   wants, so a bare class would look correct today -- and would silently start
   losing the moment someone retunes the `color` value in the config. (1,1,0)
   settles it either way. */
#algolia-hits .search-result-link {
  flex: 1;
  min-inline-size: 0;
  color: var(--neutral-text);
  font-family: var(--ff-logo);
  font-weight: var(--font-bold);
  font-size: var(--step-3);
  line-height: var(--leading-heading-3);
  text-wrap: balance;
  text-decoration: none;
}

.search-result-link:hover {
  text-decoration: none;
}

/* Makes the whole card the click target. Kept from the pre-refresh build,
   where it was already doing this.

   Worth knowing what it costs: the overlay sits over the snippet, so the
   snippet text cannot be selected. That is the trade the pattern has always
   made here, and it is the reason the arrow is decorative --- it could not
   receive a pointer event anyway.

   z-index 1, not the 1000 this used to carry. It only has to clear its own
   stacking context's static siblings. */
.search-result-link::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* The query highlight inside a title. Both declarations have to be here rather
   than inherited: `templates.instantsearch.css` injects
   `.search-result-link .ais-highlight { color: <highlightColor> }` into <head>
   after this file, so the (1,2,0) here is what carries the fill, and the fill
   is the same --neutral-bg-component-active that css-search.css gives the
   autocomplete panel's title highlight. The `highlightColor` note in
   document_head.hbs explains why that token in particular: it is the darkest
   neutral fill that still
   clears contrast under --neutral-text, which is what lets the config's
   `highlightColor` stay ordinary body ink. */
#algolia-hits .search-result-link .ais-highlight {
  background-color: var(--neutral-bg-component-active);
  font-weight: var(--font-bolder);
}

/* `body/regular/base` --- 14px/1.4, neutral/text-soft. Inter in the frame,
   which is Codex's sans; this theme's body face is --brand-font-family-sans
   (style.css:40) and the element inherits it, so nothing is declared. That
   sentence read --ff-primary until 2026-10-25, when the body face moved off it.

   Two lines then ellipsis: the frame's snippet box is 40px against a 20px line
   box, and the config asks Algolia for `body_safe:40` words, so the clamp is
   the visual half of a limit that is already being applied server-side.
   0.5rem above it is `space/2`. */
.search-result-body {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  margin-block-start: 0.5rem;
  color: var(--neutral-text-soft);
  font-size: var(--step-0);
  line-height: var(--leading-body-0);
}

/* An article with no indexed body gets no snippet at all --- `_snippetResult`
   is absent on those records, and the hit override renders the element empty
   rather than throwing. Without this the card would still carry the 8px above
   it and sit a notch taller than its neighbours for nothing. */
.search-result-body:empty {
  display: none;
}

/* Snippet highlights get weight only. A second tinted chip inside a clamped
   two-line excerpt reads as damage rather than emphasis. */
.search-result-body .ais-highlight {
  font-weight: var(--font-bolder);
}

/* Section path. `showHitsFullPath: true` is still set in the config and the
   value still reaches the hit template, but the frame's card does not show it
   --- so the override in document_head.hbs leaves it out of the markup
   entirely and there is nothing here to hide. The path has not gone missing
   from the page: it is what the Categories sidebar is built from
   (hierarchicalMenu's second attribute is `section.full_path`).

   This rule exists only to stop the element reappearing unstyled if someone
   restores the vendor's default `hit` template without reading this far. */
.search-result-path {
  display: none;
}

/* SECTION - Card arrow
   `font/size/2xl` = 24px on the card's ClickableIcon; --step-4 is the nearest
   step at ~22.8px and is what css-page-home.css:701 uses for the same glyph on
   the help cards. Solid rather than regular, which is the frame's binding. The
   sidebar rows carried a regular-weight arrow of their own until 2026-10-23,
   when it gave way to the filter checkbox at :717, so this is now the only
   trailing arrow on the page.

   A single static glyph. Until 2026-10-22 this was the theme's two-copy travel
   animation (css-page-home.css:676): two stacked arrows in a 1em box clipped by
   overflow: hidden, the resting copy leaving top-right as a second arrived from
   bottom-left. Joy asked for it off the results column, having already had it
   off the category rows, so the whole mechanism went --- the grid stack, the
   clip, the --kb-arrow-travel offsets and the `prefers-reduced-motion` block
   that gated them. The .kb-arrow-out / .kb-arrow-in pair in the `hit` override
   went with it. Nothing on this page animates an arrow now; the home page and
   footer still do, and that is where the pattern lives if it is ever wanted
   back here.

   The colour shift stays, and is not what came out: `neutral/icon-soft` at rest
   to `neutral/icon-strong` on hover is a state, not a motion, and it is the
   card's only hover cue --- the card itself takes no fill. (Which is why this
   differs from the Categories rows, where the row's own fill, border and lift
   carry the state and nothing inside it changes colour on hover.) It keys off
   `.search-result` rather than the arrow so the glyph responds to the whole
   712px card, and pairs in :focus-visible for the reason css-page-home.css:714
   gives: the arrow is the affordance, so a keyboard user should not be the only
   one who never sees it respond. */
.search-result-arrow {
  flex-shrink: 0;
  font-size: var(--step-4);
  line-height: 1;
  color: var(--neutral-icon-soft);
  transition: color 200ms linear;
}

.search-result:is(:hover, :has(.search-result-link:focus-visible))
  .search-result-arrow {
  color: var(--neutral-icon-strong);
}
/* !SECTION - Card arrow */
/* !SECTION - Result cards */

/* The empty state. Not in the frame, so this is the house voice applied to the
   vendor's `noResult` template rather than a design being implemented. The
   two action links inside it (.ais-change-query and .ais-clear-filters) are
   wired by _bindNoResultActions and come from the translation strings, one of
   which the `no_result_for` override in document_head.hbs already replaces. */
#no-results-message {
  padding: 2rem;
  border: var(--border-width-regular) solid var(--neutral-border-soft);
  border-radius: var(--radius-md);
  background-color: var(--neutral-bg);
  color: var(--neutral-text-soft);
  font-size: var(--step-0);
  line-height: var(--leading-body-0);
  text-align: center;
}

#no-results-message p + p {
  margin-block-start: var(--space-2xs);
}

#no-results-message .ais-link {
  cursor: pointer;
  text-decoration: underline;
}

/* SECTION - Responsive
   One column below 768px, which is the width the vendor's own responsiveCSS
   switches at. Matching it matters: at that width responsiveCSS reveals
   #algolia-facets-open and hides #algolia-facets, so a grid still reserving a
   21.25rem sidebar track would leave a dead column where the card used to be.

   The frame is desktop-only (1440px), so nothing below is a design being
   implemented --- it is the pre-refresh file's own breakpoint behaviour,
   re-expressed against the new grid. */
@media screen and (max-width: 48em) {
  .kb-search-layout {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      'stats'
      'facets'
      'hits'
      'pager';
    /* Nothing spans anything here, so there is no slack to park and the `1fr`
       above would land on `hits` by position --- harmless in an auto-height
       container, but it would start mattering the moment this grid is given a
       height. The areas re-declare all four rows as `auto`. */
    grid-template-rows: none;
  }

  /* With the toggle visible, this row has two children instead of one. The
     vendor floats the toggle right to get them side by side; this puts the
     count first and the toggle at the end, which is the reading order the
     desktop layout already has (stats above, filters beside). */
  #algolia-stats-line {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2xs);
  }

  /* Beats the vendor's injected `#algolia-facets-open { display: block;
     text-align: center; cursor: pointer; float: right; padding: 0 9px }` ---
     (1,1,0) against its (1,0,0), which is what lets the float go. Dropping the
     float is the point: a floated flex item has its float ignored anyway, and
     leaving `display: block` on it would stretch the pill across the row.
     The toggle is the one element here the bundle renders as a <button> at our
     request (it has visible text, so it gets an accessible name for free); the
     close button in the overlay does not, for the reason recorded above.

     `nowrap` and `flex-shrink: 0` keep the label on one line, which it was not
     until 2026-10-22 --- "Filter results" was breaking after "Filter" and
     rounding the pill into an oval. Both are needed, and for different
     failures. `flex-shrink: 0` is what stops `space-between` from squeezing
     this item: a flex item's automatic minimum is min-content, so the button
     was being handed a width of its longest *word* and wrapping to fit it.
     `white-space: nowrap` is for the longer translations --- the string is the
     vendor's `translations.filter` and several locales render it longer than
     the English (German's "Ergebnisse filtern" among them), so an unbroken
     label has to be stated rather than left to depend on the text fitting.
     The count beside it is the element that yields now; it wraps readably and
     the button does not. */
  .kb-search-layout #algolia-facets-open {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    float: none;
    padding: 0.5rem 0.75rem;
    white-space: nowrap;
    border: var(--border-width-regular) solid var(--neutral-border-soft);
    border-radius: var(--radius-round);
    background-color: var(--neutral-bg);
    color: var(--neutral-text);
    font-size: var(--step-0);
    cursor: pointer;
  }

  /* The overlay panel. `responsiveCSSFacets` is injected into <head> on click
     and removed again on close, so while the panel is open it is always the
     last stylesheet in the document and its `#algolia-facets` is (1,0,0) ---
     an exact specificity tie with our card rule at :423, won on source order.
     Three of its declarations
     have to be taken back, and the id on its own cannot do it; `.kb-search-
     layout` lifts this to (1,1,0), the same device the toggle rule above uses.

     `background: white` is the one that mattered. The panel's ink is unchanged
     and correct --- the rows are --neutral-text-soft, and the trailing arrows
     they still carried when this was reported were --neutral-icon, both
     #b2c3d2 in dark --- so a forced white fill put pale
     blue-grey text on white and the whole filter list read as barely-there
     lavender. Nothing was wrong with the colours; there was a white card under
     them. Restating --neutral-bg is therefore not a new choice, it is the card's
     own value being defended: #1e2a35 in dark, which also reads correctly as a
     raised sheet over the #10171e page, and it brings the forced
     `overflow-y: scroll` scrollbar back in line at the same time (a dark UA
     scrollbar track against a white panel is the black strip down the right
     edge).

     `padding: 20px 0` loses the card's inline padding, which put every row hard
     against the viewport edge --- the 8px of inset visible in the overlay was
     the row's own, not the container's. Border and radius go because a panel
     pinned to all four sides has no corners or edges to draw: the border became
     a hairline down the left of the viewport and the radius clipped nothing.
     The card's box-shadow needs no counterpart --- it falls outside the viewport
     from a full-bleed element, and --shadow-regular is a dead token besides. */
  .kb-search-layout #algolia-facets {
    padding: 1.25rem 1rem;
    border: 0;
    border-radius: 0;
    background-color: var(--neutral-bg);
  }

  /* The vendor's overlay drops the card's own box, so the close control has to
     come back with it. Scoped inside the media query only --- at desktop the
     rule above still hides it.

     The ✖ is a bare text node in markup we own, with no colour of its own, so
     it inherits --- stated here because the vendor's interpolated `css` colours
     `#algolia-facets-open` and nothing else in this panel, and an uncoloured
     glyph on a surface that just changed is the thing most likely to be missed. */
  .kb-search-layout #algolia-facets-close {
    display: flex;
    justify-content: flex-end;
    color: var(--neutral-text);
    font-size: var(--step-3);
    cursor: pointer;
  }

  .kb-search-hero {
    padding-block: var(--space-xl);
  }

  /* Full width below 768px, per Joy on 2026-10-22. The 35rem cap the frame
     draws is a desktop measure: it keeps the field from running the length of a
     1440px hero, and there is nothing to cap at phone widths --- 560px is
     already wider than the viewport, so the cap either does nothing or, on the
     larger phones and small tablets that land just under this breakpoint,
     leaves a short field floating at the start edge of a row with empty space
     after it. `none` rather than `100%` because `width: 100%` on the box above
     is what sets the width; this only lifts the ceiling. */
  .kb-search-field {
    max-width: none;
  }
}
/* !SECTION - Responsive */

/* RTL: the UA's own clear button on a search field sits on the wrong side.
   Unrelated to anything above and deliberately unscoped --- it has always
   applied sitewide from this file, and the fields it corrects are the header's
   `type=search` inputs on every template, not this page's `type=text` one.
   Preserved as-is rather than moved, because moving it is a header change. */
[dir='rtl'] input[type='search']::-webkit-search-cancel-button {
  position: absolute;
  left: 10px;
}

/* SECTION - Pagination
   ------------------------------------------------------------------------
   Codex's Paginator, as far as the vendor's DOM reaches. Replaces the
   pre-refresh block that was held here verbatim --- Joy, 2026-10-22: "the
   pagination at the bottom will be different and we'll be going straight from
   codex. So ignore that format for now." The Codex spec arrived later the same
   day, so this is that instruction being discharged, and the last --clr-* in
   this file went with the old block.

   The box geometry is from the live component in DevTools, so 36x36 is a
   computed value and not an artboard measurement. THE STATES ARE FROM THE FIGMA
   COMPONENT SET, supplied by Joy on 2026-10-23: `_paginator-navbutton` at node
   6738:55535 (the ten-cell parts frame) and 22030:2166 (the documentation frame,
   which carries the same matrix in both themes). That set is a 5x2 matrix ---
   state=default|hover|focus|active|disabled against type=icon|text --- and it is
   the authority for everything below the geometry:

                        type=icon (the arrows)      type=text (page numbers)
     default            1px border-soft rim,        no rim, no fill,
                        no fill                     ink text-soft
     hover              + fill soft-hover           + rim border-soft,
                                                    fill soft-hover, ink text
     focus              hover, plus a 2px           hover, plus the same ring
                        form/focus-ring ring
     active             fill soft-active,           fill soft-active, 1px
                        1px --neutral-border        --neutral-border, ink text,
                                                    weight strong
     disabled           rim kept, opacity .5        no rim, opacity .5,
                                                    ink text-soft

   `state=active` is the CURRENT PAGE and not a pressed state --- it is the cell
   that matches `[data-selected]` in the live CSS exactly, fill, rim and weight.
   There is no pressed cell in the set.

   Mapped onto this theme: --spacing * 9 is 2.25rem, --text-sm is --step--1,
   --font-weight-strong is --font-bold (600), and every colour token is named the
   same here minus Codex's property prefixes.

   ON THE FONT, and this note has now been wrong twice, so here is the whole
   history. It first claimed --font-family-primary is --ff-primary, "both Open
   Sans"; the second half was wrong, because Codex's `font/family/primary` is
   SORA. The correction kept the --ff-primary pin and called it deliberate,
   reasoning from Joy's breadcrumb call (2026-10-22) that Codex's Sora is wrong
   for this theme. That conclusion does not follow: her call was Sora -> INTER,
   not Sora -> Open Sans. The pin only looked harmless because the document's
   family was --ff-primary too, so it changed nothing.

   style.css:40 moved the document to Inter on 2026-10-25, and the pin is gone
   with it. The digits inherit Inter now, which is both what Codex's matrix
   means by `font/family/secondary` for body type and what the breadcrumb
   precedent actually said. Nothing on this page pins Open Sans any more, and
   --ff-primary has no live reference left anywhere in the theme.

   DARK MODE NEEDS NO RULE, and the documentation frame is what confirms it
   rather than the usual bet. Every value in the matrix is a semantic role whose
   two tiers are already in css-brand-tokens.css, and the one that looks like a
   surprise is the proof: the focus ring renders VIOLET in Codex's dark cells,
   which is --form-focus-ring's own dark tier (#9884de against #177da9 light).
   Nothing here is hardcoded, so both themes follow.

   WHAT THIS CANNOT REACH, and it is structural rather than a styling gap. The
   design draws `< 1 2 3 4 5 ... 50 >`: a five-page window, an ellipsis, and the
   last page as a number. instantsearch v2's Paginator renders a *contiguous*
   window and nothing else --- its `pages()` returns `range(start, end)`, there
   is no ellipsis in the component at all, and the window is `padding: 3`, i.e.
   up to seven pages. The theme cannot retune even that much: the bundle
   hardcodes `pagination({ container: '#algolia-pagination', cssClasses: { root:
   'pagination' } })`, and unlike `hit` and `hierarchicalItem` there is no
   `templates.instantsearch.pagination` key to override, so `padding`,
   `showFirstLast` and `labels` are all out of reach from here. Closing that gap
   means either replacing the widget or post-processing its output in JS; this
   block is the CSS half, which is the same either way.

   SETTLED BY JOY ON 2026-10-23, so none of the three gaps above is outstanding
   any more: "We don't need an ellipsis or the results count, or the dropdown box
   where you can choose items per page. I know we're not able to do that
   ourselves." So the contiguous window is the shipped behaviour, not a
   placeholder, and the structural job described above is closed rather than
   deferred. She closed the sidebar's 10-category facet cap the same day on the
   same grounds --- "we don't need it" --- so nothing on this page is waiting on
   the theme owning widget output.

   The two chrome pieces Codex draws around the pager --- "Showing 1 - 20 of
   1000 results" and the items-per-page <select> --- are deliberately absent
   rather than unreachable, and Codex supports that: its Visibility props
   `showResults` and `showItemsPerPage` turn exactly those two off, leaving the
   list and the arrows. The count already appears once on this page, in
   #algolia-stats under the heading, and the vendor fixes the page size in its
   config, so a select here would have nothing to set. That is why
   #algolia-pagination is centred below rather than carrying Codex's
   `justify-between` row --- with no siblings to sit opposite, there is nothing
   to justify between.

   One interim consequence worth knowing. `showFirstLast` defaults true, so the
   widget also emits `<<` and `>>` items the design has no slot for. `__first`
   is hidden below --- page 1 is already in the window whenever it matters ---
   but `__last` is kept, because it is the only way to reach the end of a long
   result set and it is doing exactly the job the design's "50" does. So the
   shipped row reads `< 1 2 3 4 5 6 7 > >>` until the structure question is
   settled.

   The vendor DOM these selectors are written against, confirmed in the bundle
   (bemHelper('ais-pagination'), Pagination.js and PaginationLink.js):

     ul.ais-pagination.pagination
       li.ais-pagination--item.ais-pagination--item__first[__disabled]
         a|span.ais-pagination--link
       li.ais-pagination--item.ais-pagination--item__previous[__disabled]
       li.ais-pagination--item.ais-pagination--item__page[__active]
       li.ais-pagination--item.ais-pagination--item__next[__disabled]
       li.ais-pagination--item.ais-pagination--item__last[__disabled]

   `__page`, `__active` and `__disabled` are additional classes alongside
   `--item`, not replacements for it. The link is an <a> when it is actionable
   and a <span> when it is not, which is why the rules below address
   `.ais-pagination--link` rather than a tag.

   The box is on the link and not the <li>, because the link is the click target
   and Codex puts the whole 36x36 circle on its <button>. Styling the <li>
   instead would draw a circle the pointer only partly activates.
   ------------------------------------------------------------------------ */
#algolia-pagination {
  grid-area: pager;
  display: flex;
  justify-content: center;
  margin-block-start: var(--space-s);
}

/* 8px between items, restoring what the pre-refresh block had and correcting
   what this comment used to claim. It said "no gap --- the 36px boxes are their
   own spacing", reasoning from the parts frame, where the cells are laid out for
   documentation rather than assembled. Joy asked on 2026-10-23 for space between
   the page numbers and the arrows, and MEASURING THE ASSEMBLED PAGINATOR in the
   documentation frame's Examples/Basic preview settles it: the item pitch there
   is a uniform 44px, 36px box plus 8px, and it is the same between every pair
   --- the arrow and the first page, two numbers, the ellipsis and the last page.
   So the gap is the design's, it is not a right-hand-side special case, and the
   old block was right.

   A literal 0.5rem and not --space-2xs, which also starts at 0.5rem but is
   fluid and reaches 0.75rem on a wide viewport. The boxes it separates are a
   fixed 2.25rem, so a gap that grew would pull the row off the design's rhythm
   at exactly the widths where it is most visible. Same reasoning as the drawn
   marks below being in fixed rem. */
#algolia-pagination .pagination {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

#algolia-pagination .ais-pagination--item {
  display: flex;
}

/* The design has no first-page button: page 1 sits in the window whenever the
   window starts there, and when it does not, `<` walks back toward it. `__last`
   is deliberately NOT hidden with it --- see the section note above. */
#algolia-pagination .ais-pagination--item__first {
  display: none;
}

/* The box every state is drawn on. Two things here are load-bearing.
   The transparent `border` is held at rest, not added on hover, so the 36px box
   is the same size in every state and the rim can be coloured in by a
   `border-color` longhand further down.

   This comment used to say the SHORTHAND had to stay, because `*:focus-visible`
   in css-reset.css added `border: 2px solid var(--form-focus-ring)` sitewide and
   the shorthand at ID specificity was what kept it out --- the same addition
   that doubled the focus ring on the dropdown rows (81fd7cd), the breadcrumb
   trail and the share menu. That declaration was deleted at source on
   2026-10-25, so the shorthand is no longer defending against anything; it is
   just the tidiest way to state a 1px transparent rim.

   The rest ink is --neutral-text-soft, which is `state=default, type=text` in
   the Figma matrix. Only hover, focus and the current page raise it to
   --neutral-text; the arrows override it with --neutral-icon below. Until
   2026-10-23 this was --neutral-text flat, which painted every unvisited page
   number at full strength and left the current page nothing to step up from. */
#algolia-pagination .ais-pagination--link {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 2.25rem;
  height: 2.25rem;
  border: var(--border-width-regular) solid transparent;
  border-radius: var(--radius-round);
  background-color: transparent;
  color: var(--neutral-text-soft);
  /* No font-family -- see the note above. It inherits Inter from style.css:40. */
  font-size: var(--step--1);
  font-weight: var(--font-regular);
  line-height: 1;
  text-decoration: none;
  transition:
    background-color var(--transition-duration),
    border-color var(--transition-duration),
    color var(--transition-duration);
}

/* Hover and focus share one treatment, because the Figma state matrix draws
   them identically apart from the ring: `state=hover` and `state=focus` are the
   same fill and the same rim in both `type=icon` and `type=text`, and focus adds
   a `.focus-ring` layer on top of that rather than replacing it.

   TWO CORRECTIONS TO WHAT THIS BLOCK USED TO SAY, both from the matrix Joy
   supplied on 2026-10-23 (Codex Component Library, node 6738:55535 for the
   parts and 22030:2166 for the documented states in both themes):

   1. HOVER HAS A FILL. The old rule here was `border-color` and nothing else,
      read off `[data-type=page]:hover` in DevTools, and the comment argued the
      restraint was deliberate on a row of small targets. The matrix shows
      --neutral-bg-component-soft-hover behind the rim in all four non-default
      cells. Same resolution as the checkbox round: where DevTools and Figma
      disagree, the render wins.
   2. THE ARROWS DO GET HOVER. The old comment flagged their exclusion as "a
      real affordance cost and the design's call". It was neither --- it was
      Codex's CSS being scoped to `[data-type=page]` where the component set
      has a full five-state row for `type=icon` too. So this is keyed on the
      item rather than on `__page`, and that open question is closed.

   `__active` is excluded rather than left to lose on specificity. The matrix has
   no active+hover and no active+focus cell, and the current page's fill
   (soft-active) is DARKER than the hover fill, so letting hover through would
   make the selected page read as less selected the moment the pointer crossed
   it. The old rule's note that Codex "softens its ring on hover" was true of a
   border-only hover and does not survive a fill. This is the one judgement call
   in the change and it is one selector to reverse.

   `__disabled` is excluded because the vendor renders those links as <span>, so
   they are hover targets without being actionable. They cannot be focus targets,
   which is why the focus half of the selector needs no matching guard. */
#algolia-pagination
  .ais-pagination--item:not(
    .ais-pagination--item__active,
    .ais-pagination--item__disabled
  ):hover
  .ais-pagination--link,
#algolia-pagination
  .ais-pagination--item:not(.ais-pagination--item__active)
  .ais-pagination--link:focus-visible {
  border-color: var(--neutral-border-soft);
  background-color: var(--neutral-bg-component-soft-hover);
}

/* The ink step is page numbers only: --neutral-text-soft to --neutral-text,
   which is `type=text` hover and focus in the matrix. Kept out of the rule above
   so it cannot reach the arrows, and after 2026-10-23 that separation does more
   than match the matrix: the arrows hold `color: transparent` to hide the
   vendor's text glyph, so a `color` reaching them here would bring the double
   chevron back on hover. Their marks are painted from --neutral-icon directly
   and so stay put, which is what every icon cell in the matrix wants anyway. */
#algolia-pagination
  .ais-pagination--item__page:not(.ais-pagination--item__active):hover
  .ais-pagination--link,
#algolia-pagination
  .ais-pagination--item__page:not(.ais-pagination--item__active)
  .ais-pagination--link:focus-visible {
  color: var(--neutral-text);
}

/* The current page, which is `state=active` in the matrix. All four declarations
   now hold in every interaction, which is a change: this rule used to lose its
   border-color to the hover rule on purpose, and the comment here described that
   as Codex "softening the selected page's ring". That was true while hover was a
   bare border-color swap and it does not survive hover gaining a fill --- so
   hover and focus exclude `__active` instead, and this rule no longer depends on
   losing anything. Its (1,2,0) is now uncontested rather than deliberately
   beaten; see the note on the hover rule above for why that was the call. */
#algolia-pagination .ais-pagination--item__active .ais-pagination--link {
  border-color: var(--neutral-border);
  background-color: var(--neutral-bg-component-soft-active);
  color: var(--neutral-text);
  font-weight: var(--font-bold);
}

/* SUBSECTION - The chevrons
   ------------------------------------------------------------------------
   --neutral-icon is Codex's `.text-neutral-icon` on the prev and next buttons.

   Drawn, not typed. Until 2026-10-23 these were the widget's own labels ---
   the text glyphs `‹` `›` `»`, hardcoded in the bundle with no template key to
   override --- scaled up to --step-2 so a glyph rendering at roughly 40% of
   its font-size landed near the ~10px of Codex's real chevron icon. Joy asked
   for them revisited, and the honest reason to is that type was never the
   right material: `‹` is a single guillemet, a punctuation mark whose weight,
   apex angle and vertical centring are Open Sans's decisions rather than the
   design's, and all three visibly disagree with a 1.5px icon stroke.

   So the glyph is hidden and the mark is drawn in a pseudo-element, the
   breadcrumb separator's technique (style.css:392) --- a rectangle with two of
   its four borders, rotated 45deg.

   HIDING IT TAKES `color: transparent`, which is the fix for the double chevron
   Joy saw on 2026-10-23: our drawn mark and the vendor's glyph were both
   painting, overlapping at different weights. `font-size: 0` did not stop it,
   and the reason is still unexplained --- the computed `font-size` on the link
   measures `0px` in the browser that showed the doubling, so the declaration
   lands, and nothing in this theme or the vendor's bundle competes on it. A
   glyph that paints at roughly 14px out of a 0px font size should not be
   possible; it is, so do not trust `font-size: 0` to hide text here. The
   declaration stays only for its second job, keeping the text from taking width
   in the flex row, and see the note on the pair below for how well it does
   that one.

   `color: transparent` is safe here where it would not be on the numbers,
   because the arrows' ink is only ever the glyph --- the mark takes
   --neutral-icon directly rather than inheriting it, which is why the borders
   below name the token instead of using currentColor.

   Hiding the text costs nothing in the accessibility tree: the vendor already
   puts a real name on these links. Its PaginationLink adds
   `aria-label="Previous" / "Next" / "Last"` in the enabled branch, and an
   aria-label beats text content, so `‹` was never the accessible name --- a
   correction to what this comment claimed before 2026-10-23. Disabled arrows
   render as a `<span>` with no label and no text, which is correct: they are
   not interactive and have nothing to announce.

   Sizes are rem and the borders px, both deliberately, since the pseudo-element
   inherits the `font-size: 0` and anything in em would compute to nothing.
   0.5rem square draws a mark 11px tall and 6px wide --- the rotated square's
   diagonal is its side x root 2, and only half of that width is drawn --- which
   sits a shade taller than the ~10px icon and reads correctly in a 36px box.
   One declaration is the lever if it wants tuning.

   THE MARKS ARE POSITIONED AND NOT FLEX ITEMS, which is the 2026-10-23 fix for
   the second half of the chevron trouble. As flex items they shared the line
   with the glyph, and the glyph keeps its advance width even at a 0px font size
   --- measured off a 1:1 screenshot, `‹` claimed 4px and `»` 8px, which pushed
   the single marks 2px off centre and spread the pair to 15px where these rules
   predicted 7, so it read as two separate chevrons. `position: absolute` takes
   the marks out of that line, which is the only fix that holds: a negative
   margin tuned to close the gap would have been a magic number fitted to a font
   metric, wrong the moment the body face changes --- which it then did, on
   2026-10-25.

   `top`/`left` and not the logical pair, for the same reason the borders below
   are physical. The centring is `left: 50%` plus a -50% translate, and that
   arithmetic assumes the offset positions the box's left edge; under RTL
   `inset-inline-start` resolves to `right`, positions the other edge, and the
   -50% then lands the mark 4px off centre. Physical is direction-independent
   here precisely because the anchor is the middle.

   THE -35% IS NOT A FUDGE, which is worth saying because it looks like one.
   Rotating about the box centre leaves the drawn corner, and so the whole
   visible mark, in one half of the box: it spans from the centre out to the
   half-diagonal, side x root 2 / 2. Centring that span means shifting it back
   by half of it again, side x root 2 / 4, which is 35.4% of the element's
   inline size --- so a percentage translate stays correct if the 0.5rem above
   ever changes, where a px correction would not. Positive for the left-pointing
   mark, negative for the right. It composes with the -50% centring rather than
   replacing it: both are translations, so they commute, and both resolve
   against the element's own 0.5rem box whichever order they are written in.

   The last-page button needs two marks, so it draws ::before and ::after, and
   with both out of flow the separation is an offset rather than a margin. The
   rule below has the arithmetic; the short of it is that the same 35% is also
   the half ink width, so no new constant appears. */
/* The rim is the arrows' and not the numbers', which is the clearest structural
   difference between the two rows of the matrix: `state=default, type=icon`
   carries a 1px --neutral-border-soft rim, `state=default, type=text` carries no
   border at all. So the arrows read as buttons at rest and the page numbers read
   as text until they are touched. The base rule's transparent 1px border already
   reserves the space on both, so nothing shifts when a number gains its rim on
   hover. Added 2026-10-23; the DevTools-only build had the arrows borderless. */
#algolia-pagination .ais-pagination--item__previous .ais-pagination--link,
#algolia-pagination .ais-pagination--item__next .ais-pagination--link,
#algolia-pagination .ais-pagination--item__last .ais-pagination--link {
  border-color: var(--neutral-border-soft);
  color: transparent;
  font-size: 0;
}

#algolia-pagination
  .ais-pagination--item__previous
  .ais-pagination--link::before,
#algolia-pagination .ais-pagination--item__next .ais-pagination--link::before,
#algolia-pagination .ais-pagination--item__last .ais-pagination--link::before,
#algolia-pagination .ais-pagination--item__last .ais-pagination--link::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  inline-size: 0.5rem;
  block-size: 0.5rem;
}

/* Pointing with the reading direction: `>`. --neutral-icon and not currentColor,
   because the link's `color` is now transparent to hide the vendor's glyph and
   the mark would inherit that and vanish with it. The disabled state still
   carries, since it dims with `opacity` on the link rather than with ink.
   Physical border sides for the reason style.css:404 gives --- the shape is
   which two edges are drawn before the rotation, so logical sides flipping
   under RTL would cancel against the rotation below. */
#algolia-pagination .ais-pagination--item__next .ais-pagination--link::before,
#algolia-pagination .ais-pagination--item__last .ais-pagination--link::before,
#algolia-pagination .ais-pagination--item__last .ais-pagination--link::after {
  border-top: 1.5px solid var(--neutral-icon);
  border-right: 1.5px solid var(--neutral-icon);
  transform: translate(-50%, -50%) translateX(-35%) rotate(45deg);
}

/* Against it: `<`. The opposite two edges, which puts the drawn corner on the
   other side of the box, so the same rotation serves and only the correction
   changes sign. */
#algolia-pagination
  .ais-pagination--item__previous
  .ais-pagination--link::before {
  border-bottom: 1.5px solid var(--neutral-icon);
  border-left: 1.5px solid var(--neutral-icon);
  transform: translate(-50%, -50%) translateX(35%) rotate(45deg);
}

/* The pair, which needs no new constant --- 35% of the box is a half ink width,
   which is exactly the offset that sits two arms next to each other. The first
   arm takes that offset on top of its own centring correction, so -70%; on the
   second the offset and the correction are equal and opposite and cancel, which
   is why it is left centred. The marks are 0.354rem of ink apiece, so the pair
   spans 0.707rem and reads as one `»`. */
#algolia-pagination .ais-pagination--item__last .ais-pagination--link::before {
  transform: translate(-50%, -50%) translateX(-70%) rotate(45deg);
}

#algolia-pagination .ais-pagination--item__last .ais-pagination--link::after {
  transform: translate(-50%, -50%) rotate(45deg);
}

/* RTL mirrors the arrows, because these point the way the pages run. Rotation
   alone, as the breadcrumb separator does it (style.css:409) and for the same
   reason: `transform` is one property, so a `scaleX(-1)` would have to restate
   the rotation anyway, and -135deg lands the drawn corner on the opposite side
   of the box in one value. The correction follows the mark and swaps sign with
   it, and so does the pair's offset, which keeps `»` mirroring to `«`. */
[dir='rtl']
  #algolia-pagination
  .ais-pagination--item__next
  .ais-pagination--link::before,
[dir='rtl']
  #algolia-pagination
  .ais-pagination--item__last
  .ais-pagination--link::before,
[dir='rtl']
  #algolia-pagination
  .ais-pagination--item__last
  .ais-pagination--link::after {
  transform: translate(-50%, -50%) translateX(35%) rotate(-135deg);
}

[dir='rtl']
  #algolia-pagination
  .ais-pagination--item__previous
  .ais-pagination--link::before {
  transform: translate(-50%, -50%) translateX(-35%) rotate(-135deg);
}

[dir='rtl']
  #algolia-pagination
  .ais-pagination--item__last
  .ais-pagination--link::before {
  transform: translate(-50%, -50%) translateX(70%) rotate(-135deg);
}

[dir='rtl']
  #algolia-pagination
  .ais-pagination--item__last
  .ais-pagination--link::after {
  transform: translate(-50%, -50%) rotate(-135deg);
}
/* !SUBSECTION - The chevrons */

/* Focus. Codex's rule is `outline: 2px solid var(--form-focus-ring)` at
   `outline-offset: 2px`, with `box-shadow: none !important` and
   `transition: none` --- confirmed in DevTools on the selected button, with its
   `outline-style: var(--tw-outline-style)` struck through in favour of the
   explicit `solid`.

   Width, style and colour all come free: css-reset.css:19 already resolves to
   2px solid --form-focus-ring sitewide. Only two things need saying here.
   --outline-offset, because the sitewide default is --outline-size, which
   computes max(2px, 0.15em) and so runs a touch wide of Codex's flat 2px (the
   divergence css-reset.css:14 documents, which components pin themselves). And
   `transition: none`, because the ring should land at once rather than easing in
   behind this rule's own border-color transition --- the same reasoning as the
   category rows at :564.

   Codex's `box-shadow: none !important` has no counterpart because these items
   carry no shadow in any state; it is there to clear a Tailwind ring utility
   that does not exist in this theme. Codex also zeroes plain `:focus`, which
   css-reset.css handles sitewide.

   THE 2px OFFSET IS NOW MEASURED, not just read off DevTools, because the Figma
   focus cell looks like it disagrees and does not. `.focus-ring` there is an
   `inset: -2px` layer with a 2px border, which describes a ring sitting FLUSH
   against the box. Sampling the rendered cell (node 12576:4423) across its
   centre scanline gives 2px of #177da9, one blended pixel, 1px of #c9d7d9, then
   exactly 36px of #e5f2f3 fill, and the same again mirrored --- 44px overall. A
   36px border-box plus `outline-offset: 2px` plus a 2px ring is 44px exactly;
   flush against a 36px box it would be 42px, and flush outside Figma's
   outside-drawn 1px stroke 46px. So the artboard's ring is positioned against
   the stroked bounds rather than the box, DevTools was right, and this rule
   needed no change. Do not "correct" the offset to 0 from the Figma layer.

   The fill and the rim are NOT here --- focus shares them with hover in the rule
   above, which is how the matrix draws it. */
#algolia-pagination .ais-pagination--link:focus-visible {
  --outline-offset: 2px;
  transition: none;
}

/* Disabled, which is `<< < ` on page 1 and `> >>` on the last page. Codex's
   rule is `background: 0 0; cursor: default; opacity: .5`; the background is
   already transparent here, so only the other two are restated. The vendor
   renders these as <span> rather than <a>, so there is no pointer target to
   suppress --- `cursor` is stated anyway, because the <li> around it is a flex
   box the pointer still enters.

   Both disabled cells in the matrix come out right with no further rule, which
   is worth stating so the absence does not read as an oversight.
   `state=disabled, type=icon` keeps its rim, because the arrows' rim is declared
   unconditionally above rather than per-state; `type=text` keeps none, because
   the base border is transparent; and both ink at --neutral-text-soft, which is
   now the base. Opacity does the rest, including dimming the drawn chevrons,
   since it applies to the link and they are its pseudo-elements. */
#algolia-pagination .ais-pagination--item__disabled .ais-pagination--link {
  opacity: 0.5;
  cursor: default;
}
/* !SECTION - Pagination */
/* !SECTION - Search Results */
