/* rx-block: rx-footer */
/* The dark band. Full bleed, and the panel floats on it — Figma draws the
   band 1657 wide on a 1512 frame (3078:3779), i.e. deliberately past both
   edges. */
.rx-footer {
  /* Width 2 — see the note on .rx-footer__panel. Declared here so the panel
     and the legal row cannot drift apart. */
  --rx-footer-w: min(100% - var(--rx-gutter) * 2, var(--rx-container-max));

  padding-block: var(--rx-footer-pt, var(--rx-space-8)) var(--rx-space-8);
  background-color: var(--rx-inverse-bg);
}

/* --- Continuing someone else's band --------------------------------------
   When the page ends with the CTA band, the footer's dark band is not a new
   surface — it is the SAME dark run continuing, and it should not start with
   air of its own. Figma draws the two as one rectangle (3001:454, 1884 tall)
   with the cream panel sitting 1051 down it, which is exactly where our CTA
   band ends. Our 64px of top padding was pushing the panel 64px below that.

   That gap was not merely wrong spacing, it was VISIBLE as a bug: the band's
   rings are 1479 across and run ~190 past the bottom of the band, so the
   band's own overflow slices them. In Figma that slice is hidden because the
   cream panel is sitting on top of it. Give the panel its designed position
   and the panel covers the cut again — the arcs meet the cream edge at
   x = centre +/- 496 and +/- 85, both a long way inside the panel's corners.

   :has() rather than a sibling selector because the footer is a global
   partial and lives OUTSIDE <main>, so there is no sibling relationship to
   select on. It also degrades the right way: without :has() the footer keeps
   its padding and the seam is merely a gap again, not a broken layout.

   IT KEYS ON A DECLARED CONTRACT, NOT ON ANOTHER MODULE'S BLOCK NAME. This
   used to list `.rx-ctaband`, `.rx-solcta` and `.rx-newsletter` — three block
   names reached for from outside their own modules, enforced by nothing: not
   the selector (a :has() that matches nothing is valid), not lint-css.mjs (it
   has no opinion on :has()), not a build (there isn't one). Renaming a block,
   or adding a fourth closing band, broke or missed the seam with no error
   anywhere — the footer just quietly got its 64px back and the band's rings
   went back to showing their cut edge in the gap, which reads as an unrelated
   regression in a module nobody touched.

   `data-rx-join` is that dependency turned the right way round. A block that
   ends a page as a card emits it — the four above do, through
   partials/atoms.html — and its VALUE is the width the block actually finishes
   at, which is the one thing the footer needs to know. A new closing band opts
   in by saying so, rather than by being added to a selector in a module it
   does not own; renaming a block cannot break this, because no block name
   appears here at all.

   Still :has() and not a sibling selector: the footer is a global partial and
   lives OUTSIDE <main>, so there is no sibling relationship to select on. It
   degrades the same way — without :has() the footer keeps its padding and the
   seam is merely a gap again, not a broken layout. */
body:has([data-rx-join]) .rx-footer {
  --rx-footer-pt: 0;
}

/* --- Being the BOTTOM of that card ---------------------------------------
   The band above does not stop at the screen edge any more — it settles at
   width 2 (rx-frame--snug), so it has visible sides and visible corners. A
   full-bleed dark band under it then reads as a second, wider surface that
   the card is sitting on, which is the "another section tacked on the bottom"
   problem: the dark run steps outward exactly where the two meet.

   So the footer stops being a band and becomes the bottom of the same card:
   the frame's open width, square across the join, round at the floor. The
   frame squares its own bottom corners with rx-frame--joined; this squares
   the top ones. Between them the seam has no geometry at all.

   THE WIDTHS AGREE BY DERIVATION, not by a matching literal. --rx-frame-x-snug
   is the same token the frame's open state is built from, so retuning the snug
   inset moves both edges together.

   The travel is not a problem, which is worth stating because it looks like
   one: the band's clip is still moving from width 3 while the footer is fixed
   at width 2, so for most of the scroll they disagree. They cannot be SEEN to
   disagree. The frame settles at `entry 100%` — the moment the band's bottom
   edge reaches the bottom of the viewport — and that edge is the seam. The
   join only becomes visible once the travel is already finished, and
   rx-frame--once holds it open from then on.

   THE WIDTH IS THE BAND'S, WHATEVER THE EDITOR SET IT TO. This used to key on
   `.rx-ctaband.rx-frame--snug` — the band's own frame MECHANISM class — which
   worked only because every closing band hardcoded `rx-frame--snug` and could
   not be set to anything else. Once Width and Travel became editor fields that
   selector had a hole in it with no error anywhere: a band settling at the
   default column matched neither this rule nor anything else, so the footer
   went full bleed under an inset card — the exact "second surface tacked on
   the bottom" this section exists to prevent.

   So the band NAMES its settled width and the footer resolves it to the same
   token the band's own frame is built from. Every value is listed, so there is
   no width a band can be set to that this does not answer; the fallback below
   is full bleed, which is what a band with no join declares by not emitting the
   attribute at all.

   ~= and not =, deliberately: the value is written by a macro, and `~=` matches
   it as one of a whitespace-separated list, so a stray space out of the macro
   cannot silently stop the seam matching.

   The order matters as much as the values. These are all one specificity, so
   the LAST matching rule wins — which is why the default sits first and each
   named width overrides it. */
body:has([data-rx-join]) .rx-footer {
  --rx-join-x: var(--rx-frame-x-full);
  --rx-join-r: 0px;
}

body:has([data-rx-join~="narrow"]) .rx-footer {
  --rx-join-x: var(--rx-frame-x-narrow);
  --rx-join-r: var(--rx-frame-r-inset);
}

body:has([data-rx-join~="indented"]) .rx-footer {
  --rx-join-x: var(--rx-frame-x-indented);
  --rx-join-r: var(--rx-frame-r-inset);
}

body:has([data-rx-join~="default"]) .rx-footer {
  --rx-join-x: var(--rx-frame-x-inset);
  --rx-join-r: var(--rx-frame-r-inset);
}

body:has([data-rx-join~="wide"]) .rx-footer {
  --rx-join-x: var(--rx-frame-x-wide);
  --rx-join-r: var(--rx-frame-r-inset);
}

body:has([data-rx-join~="snug"]) .rx-footer {
  --rx-join-x: var(--rx-frame-x-snug);
  --rx-join-r: var(--rx-frame-r-snug);
}

body:has([data-rx-join]) .rx-footer {
  width: calc(100% - var(--rx-join-x) * 2);
  /* -1px closes a REAL hairline, not a rounding superstition. The band's
     height is calc(--rx-ctaband-u * 1051) and u is fractional at almost every
     viewport, so its bottom edge lands mid-pixel; the clip antialiases against
     it while the footer's box starts at the next whole pixel, and the sliver
     between them shows page ground. Measured at a 1969 viewport it was one row
     at 25% cream running the full width of the card — faint, but a light line
     straight across a dark surface is exactly the kind of thing that reads as
     a seam rather than as a surface. Overlapping by a pixel costs nothing:
     both sides of the join are the same colour. */
  margin-block-start: -1px;
  margin-inline: auto;
  border-end-start-radius: var(--rx-join-r);
  border-end-end-radius: var(--rx-join-r);
}

/* --- The panel's width ---------------------------------------------------
   WIDTH 2 — the snug state of the width spec. A frame closed to --rx-frame-x-
   snug gives up one gutter a side, so that is what the panel takes, and the
   --rx-footer-w alias is here so the panel and the legal row below it can
   only ever be the same width as each other.

   It used to carry a literal 1337px, measured off 3078:3781 on the blog post
   frame. That number is real but it belongs to nothing: it is not a state of
   the width system, it does not track --rx-measure, and on any viewport other
   than 1512 it drifted away from every other block on the page — which is
   what made it look arbitrary.

   ONE THING TO WATCH. Unlike every other width in the theme, this one has no
   cap. --rx-frame-x-snug is a fixed gutter, not a share of the viewport, so
   the panel is 1464 at 1512 and 2512 at 2560 — it is a full-bleed block's
   clip state, and a full-bleed block is supposed to grow. A CARD is not. If
   this reads as too wide on a large display, the bounded neighbour is
   --rx-container-wide (1440 at 1512, capped with --rx-measure), which lands
   within 24px of width 2 at the design size and stays a card everywhere else.
   Swap --rx-footer-w and nothing else moves.

   No min() wrapping it, unlike .rx-container. Width 2 IS the gutter state, so
   the narrow-viewport fallback and the width are the same expression, and
   writing min(a, a) would only look like it was doing something. */
.rx-footer__panel {
  width: var(--rx-footer-w);
  margin-inline: auto;
  padding: var(--rx-space-8);
  border-radius: var(--rx-radius-lg);
  background-color: var(--rx-gold-t4);

  /* The logo sits ABOVE the link columns, not beside them — Figma puts the
     logo at x=167 and the first column at x=181, i.e. the same edge
     (3078:3783 / 3078:3798).

     THE CARD SITS ON THE LINKS ROW ONLY, not across both. Spanning the brand
     row put its top edge level with the logo, a whole brand row above the
     nav it is supposed to match — and its height then had to include the
     logo and the 48px row gap to get there. On the links row its top IS the
     top of the link columns, and its height is the link block's height,
     which is the relationship worth holding. */
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 351px);
  grid-template-areas:
    "brand   ."
    "columns newsletter";
  align-items: start;
  gap: var(--rx-space-7) var(--rx-space-7);
}

.rx-footer__brand { grid-area: brand; }
.rx-footer__brand img { width: 168px; height: auto; }

.rx-footer__columns {
  grid-area: columns;
  display: grid;
  /* Four, not three: Solutions carries Industries and Use Cases as their own
     columns in the design (3078:3809 / 3078:3814). */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--rx-space-6);
}

.rx-footer__column-heading {
  margin: 0 0 var(--rx-space-3);
  padding-bottom: var(--rx-space-3);
  border-bottom: 1px solid var(--rx-rule);
  line-height: var(--rx-leading-snug);
  font-size: var(--rx-text-sm);
  font-weight: 600;
  color: var(--rx-heading);
}

/* Figma draws #8f722e here, which is not a codified colour — resolved to
   Gold/Base, the nearest ramp value. */
.rx-footer__subheading {
  margin: 0 0 var(--rx-space-2);
  font-size: var(--rx-text-xs);
  font-weight: 600;
  color: var(--rx-accent);
}

.rx-footer__links { display: flex; flex-direction: column; gap: 11px; }

.rx-footer__link {
  font-size: var(--rx-text-sm);
  text-decoration: none;
  color: var(--rx-heading);
}

.rx-footer__link:hover { color: var(--rx-accent); }

/* No heading, so no rule — the neighbouring column's rule runs over this one
   in the design (3078:3833 spans 384px, across two columns). It still has to
   occupy a full line box, or the sub-heading below it rides up and stops
   lining up with the one beside it: an empty <p> has no line box at all, and
   a min-height does not give it one. The nbsp does. */
.rx-footer__column-heading--empty { border-bottom-color: transparent; }
.rx-footer__column-heading--empty::before { content: "\00a0"; }

/* --- Newsletter ----------------------------------------------------------- */

.rx-footer__newsletter {
  grid-area: newsletter;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* No flex gap: the two gaps in this card are different and independent —
     the heading-to-form one is the composition (auto on desktop, a fixed
     2rem stacked) and the form-to-note one is small print sitting under its
     button. A single gap value plus a margin on top of it is two numbers
     making one distance, which is how the 2rem below would silently become
     3rem. Each child carries its own. */
  gap: 0;
  /* Drawn 3001:498 — a 350.89 x 290 card. Insets measured off its own origin:
     heading and field both start 33.97 in, and the button's floor sits 29.88
     up from the bottom.

     Top is 41.12, not the drawn 44.66: that number is to the heading's CAP,
     and a 20px/1.08 line box overshoots its cap by (1.08 - 1.21)/2 + .242
     = .177em = 3.54. Transcribing the cap figure straight into padding is
     what makes a card read as too airy at the top.

     THE BUTTON IS DRAWN 4.07 LEFT OF THE HEADING AND FIELD (29.90 against
     33.97). Treated as loose drawing and aligned with them — a single element
     4px out of a column of three reads as a mistake, not as a decision. */
  padding: 41.12px 33.97px 29.88px;
  background-color: var(--rx-gold-t2);
  border-radius: 5px; /* measured */
  /* THE CARD SPANS BOTH ROWS of the panel grid, so stretching is already the
     right height: brand + link columns. That is the design's own relationship
     — the card is as tall as the nav beside it — and it needs no number.

     It used to carry a min-height, first the drawn 290 and then 290 + an
     allowance for the attribution line. Both were the same mistake: a literal
     standing in for "as tall as the thing next to it", which is only correct
     while the nav has exactly the link count it has today. Add a column or a
     link and a fixed card stops matching the block it is supposed to match.

     A CAP IS STILL NEEDED because the content is anchored top and bottom: all
     growth goes into the middle gap, and past a point that reads as an empty
     box rather than as composition. The drawn air is 99 on a 290 card; this
     allows it to roughly double before stopping. */
  /* .rx-footer__panel sets align-items: start, so grid items do NOT stretch
     by default and the card sized itself to its contents — the span across
     both rows bought nothing. This is the line that makes "as tall as the nav
     beside it" actually true; without it the max-block-size below caps a
     height nothing was ever growing towards. */
  align-self: stretch;
  max-block-size: var(--rx-footer-newsletter-max, 480px);
}

/* THE FLOOR-ANCHOR HOOK HAS TO COVER BOTH BRANCHES. It used to live only on
   .rx-footer__field, which is the PLACEHOLDER markup — so the moment a real
   HubSpot form was selected the element vanished, nothing pushed, and the
   card packed heading and field together at the top with the drawn 99px of
   air stranded at the bottom as dead space. Measured 2026-09-02: 40px between
   heading and field against the drawn 99, and 42px of nothing under the
   button. The min-height was doing its job the whole time; there was simply
   no longer anything to push against.

   The attribution note sits after the form and is carried down with it, so
   the button ends up one note-height above the drawn floor. That is the cost
   of the reCAPTCHA line, which this frame does not draw at all. */
/* THE FLEX ITEM IS NOT THE FORM. The HubSpot form tag on the portal wraps its
   output in span.hs_cos_wrapper.hs_cos_wrapper_widget plus an inner div, so
   the card's direct child is the WRAPPER and the form is two levels down.
   Neither wrapper exists in preview/fixtures/*, which is captured through the
   JS embed — so locally .hs-form IS the flex item and every rule aimed at it
   worked. Measured on the portal 2026-09-02:

     card content        283.1
     hs_cos_wrapper      187.0   <- flex item, shrink-to-fit
     form / field / input 187.0   <- 100% of the wrong box

   The input was already at width: 100% !important and still came out short,
   because 100% of 187 is 187. Chasing the input was chasing the wrong element.

   Two consequences, both fixed by addressing the direct child instead:
   the width, and margin-block-start: auto — an auto margin on a NON-item does
   nothing, so the floor-anchor was silently dead on the portal and the card
   only looked right because the heading carries the 2rem floor.

   Both selectors are listed because the two renderers disagree about the DOM,
   and > keeps it to whichever one is actually the item. The demo panel needs
   none of this: it is display: block, so its wrapper fills on its own. */
.rx-footer__newsletter.rx-hsform > .hs_cos_wrapper,
.rx-footer__newsletter.rx-hsform > .hs-form {
  width: 100%;
  margin-block-start: auto;
}

/* Three classes to clear HubSpot's own stylesheet, which ships inside the form
   markup and so lands later in the document than the theme's, winning every
   tie at equal specificity. */
.rx-footer__newsletter.rx-hsform .hs-form {
  width: 100%;
  /* Drawn 15.68 from the rule to the button's top edge (3001:502 -> 3001:505);
     --rx-space-4 is 16, and a third of a pixel is not worth a literal. */
  gap: var(--rx-space-4);
}

/* The card's gap is 0, so the small print carries its own distance from the
   button it belongs to. Three classes to clear the shared rule's
   .rx-hsform:has(.hs-recaptcha) .rx-hsform__recaptcha-note, which is (0,3,0)
   and sets margin: 0. */
.rx-footer__newsletter.rx-hsform .rx-hsform__recaptcha-note {
  margin-block-start: var(--rx-space-4);
}

.rx-footer__newsletter-heading {
  /* THE 2REM IS A FLOOR, NOT THE GAP. It is a real margin, so it always
     exists; the form's margin-top: auto then absorbs whatever is left over on
     top of it. Gap = 2rem + free space, which is >= 2rem at every width and
     needs no breakpoint to say so. Written as auto ALONE it collapsed to
     4.4px on desktop the moment the card's contents nearly filled it — an
     auto margin guarantees nothing, it only distributes what is spare. */
  margin: 0 0 var(--rx-space-6);
  /* Drawn 220.90 at 20px = 11.045em. It was 16ch, and ch is Inter's ZERO
     width (~.55em) — so the box resolved to ~176 and the heading broke a word
     early, as "Get updates about / the future of hiring" against the drawn
     "Get updates about the / future of hiring". Same em-not-ch trap the
     Industry Solution page records. */
  max-width: 11.045em;
  font-size: var(--rx-text-card-title);
  font-weight: 500;
  line-height: 1.08;
  color: var(--rx-heading);
}

.rx-footer__field { width: 100%; margin-top: auto; }

.rx-footer__input {
  width: 100%;
  padding: 0 0 var(--rx-space-3);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rx-gold-base);
  font: inherit;
  font-size: var(--rx-text-sm);
  color: var(--rx-heading);
}

.rx-footer__submit {
  /* 14px (measured 3078:3831) — a step below the standard button label. */
  font-size: var(--rx-text-sm);
  background-color: var(--rx-gold-base);
  border-color: var(--rx-gold-base);
  color: var(--rx-white);
  box-shadow: var(--rx-shadow-gold);
}

/* --- Legal ---------------------------------------------------------------- */

/* On the dark band, below the panel and right-aligned (measured 3078:3797:
   38px under the panel, 11px in Dark Brown tint 3). */
/* Same width as the panel, so the legal row's right edge lands on the panel's
   — which is the only reason it is a width at all rather than a container. */
.rx-footer__legal {
  width: var(--rx-footer-w);
  margin: var(--rx-space-6) auto 0;
  display: flex;
  justify-content: flex-end;
  gap: var(--rx-space-4);
  font-size: 11px; /* measured */
}

.rx-footer__legal-link { color: var(--rx-brown-t3); text-decoration: none; }
/* The cookie-settings entry is a <button>: it opens HubSpot's banner and
   navigates nowhere. Reset it to read as the links beside it. */
button.rx-footer__legal-link {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
}
.rx-footer__legal-link:hover { color: var(--rx-white); }

@media (max-width: 1024px) {
  .rx-footer__panel {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas:
      "brand      ."
      "columns    newsletter";
    padding: var(--rx-space-7);
  }
}

@media (max-width: 767px) {
  .rx-footer__panel {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "brand" "columns" "newsletter";
    padding: var(--rx-space-6);
  }
  .rx-footer__columns { grid-template-columns: minmax(0, 1fr); }
  .rx-footer__legal { justify-content: flex-start; }

  /* STACKED, THE CARD NO LONGER SITS BESIDE ANYTHING. The panel is one column
     here, so the card is its own row and there is no nav to match — the whole
     reason for stretching, and for capping the stretch, is gone. Both come
     off, and the card is simply as tall as its contents.

     Which also takes the floor-anchor with it: margin-top: auto only does
     something when there is free space to absorb, and a content-height card
     has none. The heading-to-field distance is a plain 2rem instead of the
     composition it is at full width. */
  .rx-footer__newsletter {
    max-block-size: none;
  }

  /* No heading-to-form override here any more: the heading's own 2rem is the
     floor at every width, and with no free space to absorb the form's auto
     margin resolves to 0 on its own. This block used to restate the 2rem,
     which meant the same distance was declared in two places and only agreed
     by luck. */
}

/* --- Stacked: the footer matches the band, which is full-bleed here --------
   The band and the footer are drawn as ONE dark rectangle, so they have to
   agree on width or the dark run steps in or out at the seam. Below 1024 the
   band loses its frame overshoot and sits flush to both edges; measured at a
   500px viewport, the band was 500 wide and the footer 436, a 32px step each
   side right at the join. The footer goes flush too. */
