/* Footer -- rebuilt 2026-10-19 against Figma node 6473:6486 (desktop) and the
   footer group of "Home 4 - Mobile" (6587:8371 plus its siblings), file
   pdQFEFrYtL0UPMcq953iJ9.

   This file is new: before the rebuild the footer had no page stylesheet of its
   own and leaned on .cluster-footer (css-components.css), .link-footer /
   .link-footer-alt (css-links.css), .btn-theme-footer (css-buttons.css) and
   .social-icon (css-icons.css). All four of those were footer-only -- a
   `grep -rl` across templates/ found no other template using any of them -- and
   all four sit on the legacy --clr-* ramp rather than the Codex token tier, so
   none of them could express this design. The rebuilt markup uses .kb-footer-*
   exclusively and those rules are now dead; they are left in place for a
   separate cleanup pass rather than deleted here, so this commit stays
   reviewable as one concern.

   WHAT DEVIATES FROM THE FRAME, and why. The frame positions all nine footer
   children absolutely at hand-placed offsets inside a fixed 1440x474 box, so a
   literal translation would be nine `position: absolute` rules that break the
   moment the viewport is not 1440px. Three deliberate departures:

   1. Content width. The frame puts the left column at x=185 (its text at 193,
      because each nav row carries 8px of padding) and right-aligns the whole
      right column at x=1220 -- a 1027px content band inside 1440, with a 193px
      left gutter and a 220px right one. Not symmetric, i.e. hand-placed. The
      footer instead sits in the `content` column of .grid-content, which is the
      same column header.hbs uses, so the footer wordmark lines up with the
      header wordmark on every viewport. That is the thing the design is
      actually asking for and the measurement is noise around it.

   2. Vertical rhythm in the right column. Measured gaps there run 33 / 48 / 26 /
      29px between the five stacked items; the left column's logo-to-nav gap is
      38px. These are four different values for one rhythm. Normalised to a
      single 2rem row-gap, which lands the right column 8px shorter than the
      frame's 276px and the left column within 0px of its 282px.

   3. Bottom padding. The frame has ~130px of empty canvas below the last
      element against 60px above the first. Both ends use --space-xl here (40px
      -> 60px fluid, so exactly 60px at the frame's own 1440px width). Flagged
      rather than reproduced: 130px of trailing whitespace reads as the frame
      being sized to a page row, not as intent. Easy to change if it was.

   4. Type sizes come from css-root.css's step scale, NOT from the frame's px.
      This is the standing rule, stated at css-root.css:120 -- "Codex's own px
      sizes are deliberately NOT used, the step scale above remains the single
      source of truth for size" -- and the precedent is the header, which matched
      the design's 17.72px wordmark by finding the step that resolves to it
      (--step-2) rather than by writing the px token. Same approach here, and the
      scale was built at a 14px base so it lands almost exactly:

        frame 14px   (nav, legal, CTA, toggle, "Follow us")  -> --step-0  (14 -> 14.22px)
        frame 12.44px (legal dividers)                       -> --step--1 (12.44 -> 12.64px)
        frame 12px   (copyright)                             -> --step--1

      The dividers are the tell that this is the right tier: the frame sets them
      in Open Sans at "12.44px", which is this theme's own --step--1 floor value
      that leaked into the Figma file. The copyright is the one real difference --
      the frame says 12px and --step--1 resolves to 12.44, so it renders 0.44px
      larger. Taken on purpose: css-root.css:128 makes --step--1 the
      accessibility floor and says not to size text below it, so a hardcoded 12px
      would be the first thing in the theme to breach it, to save half a pixel.

      Line heights come from the same file's --leading-* set, which is paired 1:1
      to the steps. The frame's 1.4 / 1.3 / 1 are exactly --leading-body-0 /
      --leading-body--1 / --leading-control, because that curve was interpolated
      off Codex's spec in the first place. Those tokens were marked "opt-in only:
      nothing consumes these yet" -- the footer is their first consumer.

      Families and weights still come from the Codex/brand tier, since the rule
      above is about size only. Inter is --brand-font-family-sans (KB4's
      practical stack, the header's idiom) rather than the bare
      --font-family-secondary primitive; Sora on the two button labels is
      --font-family-primary + fallbacks, matching css-page-header.css:1386.

   Everything else -- every colour, radius, border, shadow, weight, the 243px nav
   width, the 36px row height, the 8px nav gap, the 24px social icons, the 10px
   icon gap, the 30px mobile rhythm -- is the frame's. */

/*SECTION - Band */

/* .footer-wrapper keeps its name because css-page-custom.css:52 targets it to
   hide the footer outright on the Forethought-hosted custom pages. It also keeps
   `grid-content full-width`, which is what makes the band full-bleed while
   dropping <footer> into the `content` column (css-utilities.css:784). */
.footer-wrapper {
  /* Belt and braces as of 2026-10-21, not load-bearing on its own any more.
     The band used to detach from the window edges above a ~2279px viewport --
     background and border-top both stopped short with an even gutter either
     side (measured at 2545px as width 2278.75px at left 133). The cause was
     `place-items: center` on <body>, whose `justify-items` half shrink-wrapped
     every in-flow child of <body> that declared no width; <main> and the header
     were immune only because they already set `width: 100%`. That declaration
     has since been deleted at the source, so this one is no longer what makes
     the band full-bleed -- see the <body> rule in style.css for the full
     mechanism and for why it must not come back.
     It stays for two reasons: css-page-custom.css:23 makes <body> a flex column
     on the custom product pages, where an explicit width is the difference
     between stretching and shrink-wrapping if the alignment there is ever
     touched again; and keeping all three band-level boxes (<main>, <header>,
     this) declared the same way means none of them is the odd one out next
     time. */
  width: 100%;
  background: var(--page-bg-middle);
  border-top: var(--border-width-regular) solid var(--neutral-border-soft);
}

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

/*!SECTION - Band */

/*SECTION - Layout

   One grid, two layouts, no duplicated markup.

   The DOM order is the *mobile* order -- logo, CTA, nav, socials, theme toggle,
   legal, copyright -- because that is the order the mobile frame stacks them in,
   and because it is the order that reads correctly to a screen reader and to
   anyone tabbing through. Desktop then re-places six of the seven by grid area.
   The previous footer solved the same problem by shipping the copyright and the
   legal row twice and hiding one copy per breakpoint via [data-display]
   (css-utilities.css:1387); that is gone.

   `grid-row: 2 / -1` on the nav is the load-bearing line. The nav is 212px tall
   and the four items beside it (socials, toggle, legal, copyright) total 100px
   of content plus gaps. If the nav occupied only row 2 it would stretch that one
   row to 212px and strand the socials at the top of it. Spanning rows 2..5
   instead lets the grid distribute the nav's height across all four, so the two
   columns balance on their own instead of needing a magic height.

   grid-template-rows is why that span works, and is not decoration: a negative
   line number resolves against the end edge of the *explicit* grid, so with the
   rows left implicit `-1` would mean row line 1, the span would resolve
   backwards to `1 / 2`, and the nav would be placed on top of the logo. Five
   named rows give `-1` the line 6 it is meant to mean. All five are `auto`, so
   declaring them costs nothing on mobile where the same grid runs one column
   wide with two rows spilling implicitly past them. */

.kb-footer {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-rows: repeat(5, auto);
  justify-content: space-between;
  row-gap: 2rem;
}

/* Centred for the same reason .kb-footer-cta is, and it is the half of the pair
   that needs it: Contact Support is the taller of the two things in row 1, so the
   row is the button's 40px, and a stretched logo div would leave the 32px wordmark
   pinned to the top of it instead of level with the button. That was masked until
   2026-10-21 by the split anchor making this the taller item -- see the note in the
   Logo section. */
.kb-footer-logo {
  grid-area: 1 / 1;
  align-self: center;
}

.kb-footer-nav {
  grid-row: 2 / -1;
  grid-column: 1;
}

/* `align-self` so the button's height is decided by its own content and not by
   whatever shares row 1 with it. A grid item stretches by default, which meant the
   `min-height: 2.5rem` in its skin rule below was a floor the button was never
   actually sitting at -- anything taller opposite dragged it to full row height,
   and until 2026-10-21 the split logo anchor was doing precisely that. Fixing the
   anchor makes row 1 40px tall on its own, so this is not what repairs the
   reported bug; it is here so the button cannot be stretched by a sibling again.
   `center` rather than `start` because that is how the frame relates the two:
   the wordmark's optical centre sits on the button's. */
.kb-footer-cta {
  grid-area: 1 / 2;
  align-self: center;
}

.kb-footer-social {
  grid-area: 2 / 2;
}

.kb-footer-theme {
  grid-area: 3 / 2;
}

.kb-footer-legal {
  grid-area: 4 / 2;
}

.kb-footer-copyright {
  grid-area: 5 / 2;
}

/* Everything in the right column is right-aligned at a single edge in the
   frame. `justify-self: end` on the column rather than `justify-items` on the
   grid, so the left column is unaffected. */
.kb-footer-cta,
.kb-footer-social,
.kb-footer-theme,
.kb-footer-legal,
.kb-footer-copyright {
  justify-self: end;
}

/*!SECTION - Layout */

/*SECTION - Logo */

/* 170x32 in the frame. knowbe4-logo.svg -- the asset header.hbs already uses --
   is the same wordmark at 119x22; 5.41:1 against the frame's 5.31:1, i.e. the
   same artwork measured with and without its optical bearing. Sized by height
   with `width: auto` so the 0.1 of aspect ratio is absorbed rather than
   squashing the mark.
   Deliberately `img` and not a class: there are two of these as of 2026-10-20,
   one per theme, and both want the same box so that nothing shifts when the
   theme changes. Only `display` differs between them, and that is decided at
   the end of css-utilities.css -- which needs its selector to out-specify the
   `display: block` below. See the note there before changing either. */
.kb-footer-logo img {
  display: block;
  height: 2rem;
  width: auto;
}

/* The anchor needs a `display` of its own, and the reason is layout, not only the
   focus ring. It wraps the two <img>s above, which are `display: block` -- and an
   inline box containing a block-level box gets *split* around it (CSS 2.1
   §9.2.1.1): the <a> becomes an empty inline fragment above the wordmark, the
   img's own block, then a second empty fragment below. Those fragments are what
   the focus ring was drawing around, which is why it rendered as two tall slivers
   to the left of the logo rather than a ring around it -- and why each sliver had
   width at all, since `*:focus-visible` used to put a 2px border on either side
   of a box with nothing in it. That border left css-reset.css on 2026-10-25, so
   the slivers would be zero-width now; the `display` below is still the fix,
   because the split fragments are the defect and they also cause the Contact
   Support symptom described next.

   The same split explains the Contact Support button, reported in the same breath:
   the fragments make .kb-footer-logo taller than its 2rem wordmark, row 1 grows to
   match, and .kb-footer-cta takes the row's height because a grid item is
   `align-self: stretch` by default. One cause, two symptoms; see the align-self
   note at :156 for why that side is pinned as well.

   `flex` rather than `block`, so the whitespace text nodes between the two <img>s
   generate no boxes at all instead of relying on them collapsing. `width:
   fit-content` so the ring hugs the wordmark instead of the 243px left column that
   .kb-footer-logo stretches to fill -- the nav below sets that width, not the logo.

   header.hbs has the identical markup and never showed this, because its anchor
   carries .logo (css-page-header.css:118), which is already `display: flex` with
   `width: fit-content`. The footer's was the one anchor in the theme with no rule
   at all; a sweep of the templates found no third instance. */
.kb-footer-logo a {
  display: flex;
  width: fit-content;
}

/*!SECTION - Logo */

/*SECTION - Nav links */

.kb-footer-nav {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  /* This width is the label-to-arrow separation, not just a box size -- see the
     .kb-footer-link note below. The frame's 243px. The mobile cap below reuses
     the value, so there is one column width in the file rather than two. */
  width: 15.1875rem; /* 243px */
}

/* The frame draws these as list rows, not as links in a cluster: a 243px-wide
   36px-tall box with the label hard left and an outbound arrow hard right.
   justify-content: space-between is what produces that, and is why the 10px
   `gap` in the frame never actually applies -- it is the minimum, not the
   measured distance. Kept anyway as the floor for a label long enough to fill
   the row.
   The consequence worth knowing: because the arrow is pinned to the far edge,
   the nav's width and the gap between label and arrow are the same measurement.
   Any change to .kb-footer-nav's width is a change to how far apart they read,
   which is why that width is capped on mobile rather than set to 100%. */
.kb-footer-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.625rem;
  /* The frame's 36px row, and min-height is what has to produce it. Block
     padding is deliberately absent: at 0.5rem it added 16px to a ~20px line box
     and a 2px border, so the row computed to ~38px and min-height never applied
     -- the height floated with --step-0's clamp instead of being the frame's
     number. align-items: center already centres the label in the 36px, so the
     block padding was buying nothing except the overshoot. The hover background
     still fills the full 36px for the same reason. */
  min-height: 2.25rem; /* 36px */
  padding-inline: 0.5rem;
  border: var(--border-width-regular) solid transparent;
  border-radius: var(--radius-sm);
  color: var(--neutral-text-soft);
  font-family: var(--brand-font-family-sans), system-ui, sans-serif;
  font-size: var(--step-0);
  font-weight: var(--font-weight-regular);
  line-height: var(--leading-body-0);
  text-decoration: none;
  transition:
    background-color var(--motion-duration-control) var(--ease-in-out),
    border-color var(--motion-duration-control) var(--ease-in-out),
    color var(--motion-duration-control) var(--ease-in-out);
}

/* The border is declared transparent at rest rather than added on hover: the
   frame's hovered row has a 1px --neutral-border-alpha border and the resting
   ones have none, and introducing a border on hover would shift the label and
   the arrow by 1px each. Reserving the box up front keeps the row still.

   "Products + Pricing" is drawn in this state in the frame (6473:6513 carries
   the background and border the other four lack). Reading that as the hover
   state rather than as a permanent one: it is the first row, there is no
   current-page meaning available to a footer nav pointing at www.knowbe4.com,
   and Codex's `child` component ships exactly these two states as default and
   active. */
.kb-footer-link:hover {
  background: var(--neutral-bg-component-soft-active);
  border-color: var(--neutral-border-alpha);
  color: var(--neutral-text);
}

/* Press continues past hover onto the next background step. Codex has no
   pressed state drawn for `child`, so this follows the same -hover -> -active
   progression the back-to-top button uses (css-components.css:1901). */
.kb-footer-link:active {
  background: var(--neutral-bg-component-active);
}

/* The arrow uses the same replace-on-hover treatment as the "Need more help?"
   cards on the home page, per Joy on 2026-10-19. The full derivation of the
   technique is written out once at css-page-home.css:503 and not repeated here;
   the short version is two stacked copies in a 1em box clipped by
   overflow: hidden, so the arrow reads as being *replaced* rather than drifting
   away, built as a transition rather than the Figma export's one-shot keyframe
   because a transition reverses cleanly when the pointer leaves.

   Three things differ from the home card, all of them forced by the size:

   --kb-arrow-travel is in em here, not the card's 30px. That 30px was measured
   off the export against an arrow box of --step-4 (22.43px), i.e. 1.34 box
   widths. This box is --step-0 (14px), so reusing 30px literally would be 2.14
   box widths -- the arrow would clear the clip in the first third of the 200ms
   and the rest of the travel would happen out of sight, reading as a snap rather
   than the card's slide. 1.34em restates the ratio, which is the part that
   carries the motion.

   The glyph is fa-regular, which is the weight the footer frame specifies for
   this icon; the card's is fa-solid. Matching the behaviour, not the weight.

   Colour is left inheriting from .kb-footer-link rather than adopting the card's
   --neutral-icon-soft -> --neutral-icon-strong shift, so the arrow stays the same
   colour as the label it sits beside through every state. That is what the footer
   frame draws. The card's lighter resting arrow was a separate call Joy made in
   preview about that card specifically, so it is not assumed to carry over --
   easy to switch if it should. */
.kb-footer-link-arrow {
  --kb-arrow-travel: 1.34em;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  overflow: hidden;
  width: 1em;
  height: 1em;
  font-size: var(--step-0);
}

/* Targets svg, not i: the Font Awesome kit (document_head.hbs:219) swaps each
   <i> for an inline <svg> and carries the classes across. Same reason the home
   card's rules are written against svg. */
.kb-footer-link-arrow svg {
  grid-area: 1 / 1;
}

.kb-footer-link-arrow .kb-arrow-in {
  translate: calc(var(--kb-arrow-travel) * -1) var(--kb-arrow-travel);
}

/* Paired with :focus-visible for the same reason the card is -- the arrow is
   part of the affordance, so a keyboard user shouldn't be the only one who never
   sees it move. */
@media (prefers-reduced-motion: no-preference) {
  .kb-footer-link-arrow svg {
    transition: translate 200ms linear;
  }

  .kb-footer-link:is(:hover, :focus-visible) .kb-arrow-out {
    translate: var(--kb-arrow-travel) calc(var(--kb-arrow-travel) * -1);
  }

  .kb-footer-link:is(:hover, :focus-visible) .kb-arrow-in {
    translate: 0 0;
  }
}

/*!SECTION - Nav links */

/*SECTION - Contact Support

   Codex `button-md`, emphasis "strong". The gradient, its three-panel geometry,
   the 300% background-size, the two transition durations and the hover lift are
   all the same recipe as the header's primary button; the full derivation --
   including why a flat-to-gradient swap cannot be transitioned and why the third
   panel is what makes :active interpolable -- is written out once at
   css-page-header.css:1380 and deliberately not repeated here. If that recipe
   changes, these two rules change with it. */

.kb-footer-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 2.5rem;
  padding-inline: 1.25rem;
  border: var(--border-width-regular) solid transparent;
  border-radius: var(--radius-round);
  background-color: var(--brand-bg);
  background-image: linear-gradient(
    90deg,
    var(--brand-gradient-l-hover) 0%,
    var(--brand-gradient-l-hover) 33.333%,
    var(--brand-gradient-r-hover) 66.666%,
    var(--brand-gradient-r-hover) 100%
  );
  background-size: 300% 100%;
  background-position: 100% 0;
  box-shadow: 0 var(--shadow-y-offset) var(--shadow-blur) 0
    var(--shadow-regular);
  /* --fixed-white, not --neutral-text-inverse. Was the latter, which is a bug the
     light theme could not show: "inverse" means the opposite of the page, so it
     flips to #10171e in dark -- but everything under it here (--brand-bg and both
     gradient stops) is brand, and brand is one colour in both themes, so the label
     would have gone near-black on coral the moment the theme flipped while the
     button beneath it did not move. The fixed tier exists for exactly this: a
     foreground on a surface that never changes. Codex agrees from its own side --
     `.kb-button-strong` sets `color: var(--fixed-white)`, and sampling its dark-mode
     screenshot gives a literal #ffffff label.
     Contrast with the skip link at css-page-header.css:66, which keeps
     --neutral-text-inverse and is right to: its background is --neutral-text, so
     the pair flips together and stays legible either way. */
  color: var(--fixed-white);
  font-family: var(--font-family-primary), system-ui, sans-serif;
  font-size: var(--step-0);
  font-weight: var(--font-weight-strong);
  line-height: var(--leading-control);
  text-decoration: none;
  transition:
    background-position var(--motion-duration-slow) var(--ease-in-out),
    box-shadow var(--motion-duration-control) var(--ease-in-out);
}

.kb-footer-cta:hover {
  background-position: 50% 0;
  box-shadow: 0 var(--shadow-y-offset-hover) var(--shadow-blur-hover) 0
    var(--shadow-hover);
}

.kb-footer-cta:active {
  background-position: 0 0;
  box-shadow: 0 var(--shadow-y-offset) var(--shadow-blur) 0
    var(--shadow-regular);
  transition-duration: var(--motion-duration-instant);
}

/*!SECTION - Contact Support */

/*SECTION - Socials */

.kb-footer-social {
  display: flex;
  align-items: center;
  gap: 1rem; /* 17px in the frame, between the label and the icon row */
}

/* body/strong/base in the frame: the only 600-weight text in the footer. */
.kb-footer-social-label {
  color: var(--neutral-text-soft);
  font-family: var(--brand-font-family-sans), system-ui, sans-serif;
  font-size: var(--step-0);
  font-weight: var(--font-weight-strong);
  line-height: var(--leading-body-0);
}

.kb-footer-social-list {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}

/* The frame ships these as five flattened 24x24 SVGs, but they are the X,
   Facebook, LinkedIn, Instagram and YouTube marks in that order -- the same five
   in the same order the footer already had as Font Awesome brand glyphs. Drawing
   them from the loaded FA Pro kit instead of adding five SVG assets: same marks,
   no new requests, and they follow the colour token.

   24px is the frame's *box*; the glyph inside it is smaller, hence a 1.5rem
   square with a 1.25rem font-size rather than a 1.5rem font-size. */
.kb-footer-social-link {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  color: var(--neutral-icon);
  font-size: 1.25rem;
  text-decoration: none;
  transition: color var(--motion-duration-control) var(--ease-in-out);
}

.kb-footer-social-link:hover {
  color: var(--brand-bg);
}

/*!SECTION - Socials */

/*SECTION - Theme toggle

   Codex `button-md`, emphasis "regular" (6497:2423). Same component as the
   back-to-top button, so the state ramp is identical and documented at
   css-components.css:1854 -- resting --neutral-bg-component with the regular
   shadow, hover a background step plus a taller softer shadow, press the next
   step with the shadow dropped outright.

   min-width is the frame's 84px: the label flips between "Light" and "Dark",
   which are different widths, and without a floor the button would resize under
   the pointer on click. */
.kb-footer-theme {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-width: 5.25rem;
  min-height: 2.5rem;
  padding: 0.75rem 1rem;
  border: var(--border-width-regular) solid var(--neutral-border-alpha-strong);
  border-radius: var(--radius-round);
  background: var(--neutral-bg-component);
  box-shadow: 0 var(--shadow-y-offset) var(--shadow-blur) 0
    var(--shadow-regular);
  color: var(--neutral-text);
  font-family: var(--font-family-primary), system-ui, sans-serif;
  font-size: var(--step-0);
  font-weight: var(--font-weight-strong);
  line-height: var(--leading-control);
  cursor: pointer;
  transition:
    background-color var(--motion-duration-control) var(--ease-in-out),
    box-shadow var(--motion-duration-control) var(--ease-in-out);
}

.kb-footer-theme:hover {
  background: var(--neutral-bg-component-hover);
  box-shadow: 0 var(--shadow-y-offset-hover) var(--shadow-blur-hover) 0
    var(--shadow-hover);
}

.kb-footer-theme:active {
  background: var(--neutral-bg-component-active);
  box-shadow: none;
}

/* Ordered after :hover/:active on purpose -- all three are (0,2,0), so source
   order is what lets the focus ring survive on a button that is hovered and
   focused at once. Same reasoning as css-components.css:1920. */
.kb-footer-theme:focus-visible {
  --outline-offset: 2px;
  box-shadow: none;
  transition: none;
}

.kb-footer-theme-icon {
  color: var(--neutral-icon-strong);
  font-size: var(--step-1);
}

/*!SECTION - Theme toggle */

/*SECTION - Legal row and copyright */

/* flex-wrap and justify-content: center are for the mobile frame, where this row
   is full-width and breaks across two lines (6587:8286). On desktop the row fits
   and neither does anything, so they are declared once here rather than inside
   the media query. */
.kb-footer-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
}

.kb-footer-legal-link {
  color: var(--neutral-text-soft);
  font-family: var(--brand-font-family-sans), system-ui, sans-serif;
  font-size: var(--step-0);
  font-weight: var(--font-weight-regular);
  line-height: var(--leading-body-0);
  text-decoration: underline;
  text-underline-position: from-font;
  transition: color var(--motion-duration-control) var(--ease-in-out);
}

/* --brand-text, which is the sitewide link hover. `a:not([class])` in
   css-reset.css carries it for every unclassed link; these four legal links are
   classed, so they had quietly sat out that 2026-10-25 decision and kept a
   --neutral-text hover of their own. Joy, 2026-10-29: "we need to update the
   hover color on the kb-footer-legal links to our default brand hover color."

   Light #992008 on the footer's --page-bg-middle #faffff is 8.12:1, dark
   #fdb5a6 on #10171e is 10.60:1 -- AAA in both tiers, and in both a step UP
   from the rest ink (6.61:1 and 10.00:1), so the state spends no contrast.

   THE RATIO IS NOT WHAT CARRIES THE CHANGE, though, because a contrast figure
   cannot see a same-surface state change -- both inks are measured against the
   same footer, so the comparison has to be against the REST INK. There the
   shift is dE00 33.8 light and 29.3 dark, where the --neutral-text it replaces
   managed 23.4 and 12.2. The dark tier is the reason that is worth recording:
   grey-to-near-white was by far the quietest of the four measurements and is
   now the second loudest.

   :active takes the same ink and is listed second, for the reason
   `a:not([class])` in css-reset.css orders it that way -- releasing the mouse
   inside the link then lands back on :hover rather than on rest. One hover
   model, sitewide. The transition is already on .kb-footer-legal-link. */
.kb-footer-legal-link:hover,
.kb-footer-legal-link:active {
  color: var(--brand-text);
}

/* The frame sets these pipes in Open Sans at 12.44px -- the legacy `step--1`
   scale, left over from before the type tokens landed. Taken as
   --font-size-xs in the footer's own family instead; a 0.44px difference in a
   divider is not worth keeping a fifth font family alive for. aria-hidden in the
   markup, so this is purely a drawn separator. */
.kb-footer-legal-divider {
  color: var(--neutral-text-soft);
  font-size: var(--step--1);
  line-height: var(--leading-control);
  user-select: none;
}

.kb-footer-copyright {
  margin: 0;
  color: var(--neutral-text-soft);
  font-family: var(--brand-font-family-sans), system-ui, sans-serif;
  font-size: var(--step--1);
  font-weight: var(--font-weight-regular);
  line-height: var(--leading-body--1);
}

/*!SECTION - Legal row and copyright */

/*SECTION - Mobile

   54.9em is the header's own breakpoint (css-page-header.css:517), not a new
   one: below it the header collapses to the hamburger, so the footer collapsing
   to one column at the same width means the page has a single layout switch
   rather than two near-misses. The footer's old 45em breakpoint only ever
   governed which duplicate copyright was hidden and goes away with the
   duplicates. It lived at style.css:148 as a 44.9em block and is now deleted --
   worth knowing that Zendesk injects style.css after every <link> in
   document_head.hbs, so a footer rule there outranks this file on load order no
   matter how it is written, and `#footer-nav { gap: 2rem }` in that block was
   beating .kb-footer-nav's 8px gap until 2026-10-19. The Footer section of
   style.css is now deliberately empty and documents why it has to stay that
   way.

   Joy's call on the frame's own mobile geometry (2026-10-19): the mobile frame
   pins this column to a fixed 243px at left: 73px inside a 390px device, which
   is 37% of the screen given over to gutter and is the desktop nav width carried
   across rather than a mobile measurement. Implemented fluid instead -- the
   stacking order, the 30px rhythm and the centring are all the frame's, but the
   rows sit in .grid-content's normal page gutter rather than at a fixed offset,
   so the layout holds from 320px to 430px.
   Fluid applies to the legal and copyright rows. The nav is capped, because
   there its width is the label-to-arrow distance rather than a container size --
   see the rule below. */

@media screen and (max-width: 54.9em) {
  .kb-footer {
    grid-template-columns: 1fr;
    justify-items: center;
    row-gap: 1.875rem; /* 30px */
  }

  /* Unwind every desktop placement so the seven children fall back to DOM
     order, which is already the mobile order. */
  .kb-footer-logo,
  .kb-footer-nav,
  .kb-footer-cta,
  .kb-footer-social,
  .kb-footer-theme,
  .kb-footer-legal,
  .kb-footer-copyright {
    grid-area: auto;
    grid-row: auto;
    grid-column: auto;
    justify-self: center;
  }

  /* Full-bleed rows within the column, per 6587:8299. */
  .kb-footer-legal,
  .kb-footer-copyright {
    width: 100%;
  }

  /* The nav is the exception, and it used to be in the rule above. Full-bleed is
     wrong here for the reason in the .kb-footer-link note: the arrow is pinned
     to the row's far edge, so stretching the nav to the device width strands it
     a few hundred pixels from its own label, which is what it was doing.

     Capped at the desktop column instead. width: 100% is kept as the shrink
     behaviour -- without it the nav is shrink-to-fit and collapses to the widest
     label, which is narrower than 243px and leaves ragged rows -- and max-width
     is what holds the separation. Below ~243px of available column the cap stops
     applying and the rows narrow with the screen, which is the right failure
     direction on a 320px device.
     This also lands close to where the frame puts it: 6587:8299 pins 243px at
     left: 73px in a 390px device, so the frame's column is centred too. We get
     the centring from the grid's justify-items rather than a fixed offset, which
     is the fluid-gutter call noted above. */
  .kb-footer-nav {
    width: 100%;
    max-width: 15.1875rem; /* 243px, the same column as desktop */
  }

  .kb-footer-copyright {
    text-align: center;
  }
}

/*!SECTION - Mobile */
