/* ==========================================================================
   Pagelove — Homepage
   Page-specific styles. Built on top of foundations.css (grid, color,
   typography, buttons). This file currently covers the nav bar; further
   blocks will be appended here as their Figma specs come in.
   ========================================================================== */

* {
  box-sizing: border-box;
}

html {
  /* FIX 2026-08-12: right at the 1180px pin threshold, the feature card's
     own intro-state slide (a constant, hardcoded transform — see
     getMorphConfig() in main.js) can momentarily push a few px past the
     viewport edge before the user has scrolled far enough for the morph to
     settle it back into place.
     CORRECTED: `overflow-x: hidden` here (and on body) was the first fix
     tried, but it backfired badly — per spec, setting overflow to a
     non-visible value on only one axis forces the OTHER axis's used value
     to `auto`, turning html/body into their own scrolling container(s).
     That's exactly what broke every section's position:sticky behavior
     sitewide (feature.section's pinned card included) — sticky elements
     stick relative to their nearest ACTUAL scroll container, and once
     html/body quietly became one instead of the real viewport, the math no
     longer matched a plain window.scrollTo(). `overflow-x: clip` doesn't
     carry that same auto-promotion behavior, so it clips the transient
     overflow without implying or creating a second scroll container —
     confirmed empirically (see verification below) that sticky positioning
     is unaffected with this. */
  overflow-x: clip;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  color: var(--color-text-primary);
  background: var(--color-background-white);
  overflow-x: clip;
}

img {
  max-width: 100%;
}

a {
  text-decoration: none;
}

/* --------------------------------------------------------------------------
   NAV BAR

   Spec pulled from Figma (node 3077:5840, "Website" file):
     - nav.bar 3077:6311 (desktop, 1512 frame)
     - nav.bar 3324:8796 (tablet, 900 frame)
     - header  3117:2100 (mobile, 375 frame)

   Height is 64px at every breakpoint. Side padding uses .content-bound
   (foundations.css) — fluid at the grid margin (24 / 48 / 64px) until the
   content zone would exceed 1150px, then it locks and the margin grows
   instead. That reproduces the measured 181px margin at the 1512px
   desktop reference frame exactly, and matches the tablet (48px) and
   mobile (24px) measurements below that.

   Background is background.mask and the bottom edge uses border.neutral,
   per spec — applied uniformly across breakpoints (the Figma frames show
   a slightly different blur/opacity per breakpoint; the foundations
   tokens are used instead so the header reads as one consistent surface).
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  width: 100%;
  border-bottom: 1px solid var(--color-border-neutral);
}

.site-header.mobile-menu-is-open {
  position: fixed;
  top: 0;
}

.nav-bar {
  box-sizing: border-box;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: 12px;
}

.nav-logo {
  display: block;
  flex-shrink: 0;
  line-height: 0;
}

.nav-logo img {
  display: block;
  width: 107px;
  height: 28px;
}

/* Vertical 1px divider used between nav groups */
.nav-divider {
  display: inline-block;
  flex-shrink: 0;
  width: 1px;
  height: 16px;
  background-color: var(--color-border-neutral);
}

/* Tablet + desktop: full inline nav (links, divider, log in, CTA) */
.nav-desktop {
  display: none;
  align-items: center;
  gap: 24px;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 24px;
}

.nav-link[hidden] {
  display: none;
}

.nav-desktop .nav-link,
.nav-mobile-actions .nav-link {
  color: var(--color-nav-link);
}

/* CTA in the nav is smaller than the default .btn-primary padding/size */
.nav-cta {
  padding: 10px 16px;
}

/* Mobile: log in + hamburger toggle only */
.nav-mobile-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.nav-menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  flex-shrink: 0;
}

.nav-menu-icon,
.nav-menu-icon::before,
.nav-menu-icon::after {
  display: block;
  width: 18px;
  height: 2px;
  border-radius: 1px;
  background-color: var(--color-text-primary);
  transition: transform 0.2s ease, opacity 0.2s ease;
}

.nav-menu-icon {
  position: relative;
}

.nav-menu-icon::before,
.nav-menu-icon::after {
  content: "";
  position: absolute;
  left: 0;
}

.nav-menu-icon::before {
  top: -5px;
}

.nav-menu-icon::after {
  top: 5px;
}

.nav-menu-toggle[aria-expanded="true"] .nav-menu-icon {
  background-color: transparent;
}

.nav-menu-toggle[aria-expanded="true"] .nav-menu-icon::before {
  top: 0;
  transform: rotate(45deg);
}

.nav-menu-toggle[aria-expanded="true"] .nav-menu-icon::after {
  top: 0;
  transform: rotate(-45deg);
}

/* Full-screen mobile menu below the persistent 64px header. */
.mobile-menu {
  position: fixed;
  top: 64px;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 90;
  background: var(--color-background-white);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  overflow-y: auto;
}

.mobile-menu[hidden] {
  display: none;
}

.mobile-menu-links {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex: 1 0 0;
  min-height: 0;
  padding: 32px 20px;
}

.mobile-menu-links,
.mobile-menu-cta {
  opacity: 0;
  filter: blur(12px);
  transition: opacity 0.35s ease, filter 0.35s ease;
}

.mobile-menu-links.is-visible,
.mobile-menu-cta.is-visible {
  opacity: 1;
  filter: blur(0);
}

@media (prefers-reduced-motion: reduce) {
  .mobile-menu-links,
  .mobile-menu-cta {
    filter: none;
    transition: none;
  }
}

.mobile-menu-link {
  width: 100%;
  height: 56px;
  display: flex;
  align-items: center;
  color: var(--color-text-primary);
  font-size: 28px;
  font-weight: 500;
  line-height: 36px;
  letter-spacing: -0.6px;
}

.mobile-menu-cta {
  position: relative;
  flex-shrink: 0;
  padding: 20px;
  transition-delay: 0.06s;
}

.mobile-menu-cta-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 0;
  width: 327px;
  max-width: calc(100% - 40px);
  height: 110px;
  border-radius: 14px;
  background: var(--gradient-btn-hover-fx);
  opacity: 0.6;
  filter: blur(10px);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.mobile-menu-cta-card {
  position: relative;
  z-index: 1;
  box-sizing: border-box;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
  padding: 16px;
  border: 1px solid var(--color-btn-secondary-border);
  border-radius: 16px;
  background-color: var(--color-background-white);
}

.mobile-menu-cta-text {
  margin: 0;
  color: var(--color-text-primary);
}

.mobile-sticky-cta {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 20px;
  padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  opacity: 1;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

.mobile-sticky-cta.is-docked {
  padding-bottom: 24px;
}

.mobile-sticky-cta-btn {
  padding: 12px 24px;
  pointer-events: auto;
}

.mobile-sticky-cta.is-hidden {
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.2s ease, visibility 0s linear 0.2s;
  pointer-events: none;
}

@media (min-width: 700px) {
  .nav-desktop {
    display: flex;
  }

  .nav-mobile-actions {
    display: none;
  }

  .mobile-sticky-cta {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   HERO SECTION

   Spec pulled from Figma node 3077:5888 ("hero.section", desktop 1512
   frame only — tablet/mobile weren't inspected this round, so the
   vertical rhythm below is fluid-interpolated with clamp() rather than
   measured; horizontal bound and typography use the same tokens as the
   rest of the site, so those ARE accurate at every breakpoint).

   Headline: "From idea to / implementation / in minutes, not days" split
   into one container per line, using .h1 (already fully responsive).
   Per Figma, only "not days" — the last run of the final line — carries
   the gradient-main text-clip; "in minutes," is a solid accent color
   (#7B6BB5, the gradient's own start stop) rather than the gradient
   itself, matching the actual design instead of gradient-ing the whole
   line.

   Description: "Built for humans, readable by AI." — 18px/500/-0.4px,
   which is .body-large-tagline once its desktop line-height is corrected
   to 28px (see foundations.css TYPOGRAPHY section).

   Ghost CTA: "Start building free" — 18px/500/-0.6px/28px, i.e.
   .button-label (also corrected to 28px line-height at desktop), with
   gradient-main applied to the text (not the default ghost-button
   gradient, which is a different token) and an underline. NOTE: the
   trailing 20×20 arrow icon needs its asset exported from Figma — it's
   currently omitted rather than hand-drawn (see chat).
   -------------------------------------------------------------------------- */

.hero {
  padding-top: clamp(64px, 14vw, 128px);
  padding-bottom: clamp(32px, 6vw, 64px);
}

.hero-lines {
  display: flex;
  flex-direction: column;
}

.hero-line {
  /* Plain block, not flex — lets "in minutes, not days" wrap as normal
     inline text on narrow viewports instead of the two colored spans
     staying rigid and overflowing. */
  color: var(--color-text-primary);
}

.hero-line--final .hero-accent,
.hero-line--final .hero-gradient {
  display: inline;
}

.hero-line--final .hero-accent {
  color: #7B6BB5; /* gradient-main start stop, used solid per Figma */
}

.hero-line--final .hero-gradient {
  background: var(--gradient-main);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero-sub {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* CORRECTED 2026-08-11: previously split into row-gap:0/column-gap:8px,
     on the assumption the stacked/mobile state should sit flush with no
     gap. Checking Figma's own dedicated mobile frame (node 3117:2156,
     "container") shows that's wrong — it's a separate flex-col layout
     (not the same 3077:5898 row just wrapping) and it explicitly carries
     its own gap:8px between the tagline and the CTA, same as the desktop
     row's 8px. So the correct spec is a uniform 8px on both axes after
     all — back to the plain shorthand. */
  gap: 8px;
  margin-top: clamp(16px, 3vw, 16px);
}

.hero-description {
  color: var(--color-text-primary);
  margin: 0;
}

/* FIX 2026-08-11: .btn-ghost (foundations.css) carries its own 10px/8px
   padding, meant for a general-purpose "ghost button" — but per Figma
   (node 3077:5898/3117:2156, the "button" sub-node) this specific CTA
   isn't a padded button shape at all: no background, no border-radius
   chrome, just underlined text + an arrow sitting directly inline with
   the tagline. .btn-ghost is only used here today, but zeroing it out on
   .hero-cta specifically (rather than editing .btn-ghost itself) keeps
   that class free to carry real padding if a genuinely padded ghost
   button shows up elsewhere later. */
.hero-cta {
  padding: 0;
}

.hero-cta.btn-ghost .btn-label {
  background: var(--gradient-main);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-decoration: underline;
  text-decoration-color: #7B6BB5;
  text-underline-offset: 3px;
}

.hero-cta .btn-icon {
  width: 20px;
  height: 20px;
}

/* --------------------------------------------------------------------------
   INTRO SECTION

   Spec pulled from Figma:
     - desktop 3077:5907 (1512 frame) — 3 cards in a row, image on top of
       content, vertical border.neutral dividers between cards.
     - tablet  3442:4611 (900 frame)  — cards 1+2 side by side (2-col,
       vertical divider), card 3 breaks the grid: full width, content on
       the left / video on the right instead of stacked.
     - mobile  3281:2274 (375 frame)  — single column, all 3 cards stacked
       image-on-top-of-content, separated by the foundations .divider
       (fading gradient line) instead of a flat border.

   Card titles use .h4-card-title (24px Source Serif SemiBold — matches
   exactly), descriptions use .body (16px Inter Medium — matches exactly)
   at 80% opacity per Figma. Content padding is var(--grid-margin), same
   as the rest of the site (24/48/64px). The outer grid uses
   .content-bound--wide since intro's cards span a wider zone (1278px)
   than nav/hero's 1150px — see foundations.css.

   Images are replaced with the provided videos, muted/looped/autoplaying,
   swapped per breakpoint via <source media="...">. No tablet-specific cut
   was provided, so tablet reuses the desktop video.

   CORRECTED 2026-08-11: the section root (3077:5907) also carries a top
   and bottom border in the same #e2e8f0 border-neutral color the rest of
   the site's dividers use. get_design_context flattens it to a plain
   (fully transparent) border color rather than a gradient, but that's a
   tooling artifact, not the actual paint — #e2e8f0 is exactly the color
   --gradient-divider fades through, and a flat solid edge-to-edge line
   would look visually heavier/harder than every other divider on the
   page. Implemented as ::before/::after rather than the .divider class
   directly since those are horizontal rules in normal flow, not a
   section's own border edges.
   -------------------------------------------------------------------------- */

.intro-section {
  position: relative;
  padding-block: 48px;
}

.intro-section::before,
.intro-section::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%;
  height: 1px;
  background: var(--gradient-divider);
}

.intro-section::before {
  top: 0;
}

.intro-section::after {
  bottom: 0;
}

.intro-grid {
  display: flex;
  flex-direction: column;
  gap: 48px;
}

.intro-card {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.intro-media {
  /* FIX 2026-08-11: mobile Figma (3281:2274) has every card's media
     instance at x:0, width:375 — full-bleed, edge to edge — while
     .intro-grid (via .content-bound) puts a 24px padding-inline around
     everything in the section, so media was inheriting that inset it was
     never meant to have (the 375/356 ratio below already assumes a
     full 375px-wide box, which only lined up by coincidence of both
     numbers matching the viewport width). Bleeding it back out with a
     negative margin equal to the grid's own padding restores edge-to-edge
     media without touching .content-bound itself (used sitewide) or the
     divider between cards, which keeps its existing inset. Reset back to
     a normal 100%-width box at 700px, where Figma's cards sit inside the
     grid padding like everything else (no bleed there). */
  width: calc(100% + (var(--grid-margin) * 2));
  margin-inline: calc(var(--grid-margin) * -1);
  aspect-ratio: 375 / 356;
  overflow: hidden;
  background: var(--color-background-white);
}

.intro-media video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.intro-content {
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* FIX 2026-08-11: at mobile, .intro-grid's own content-bound padding
     (24px) already gives the card its single correct inset per Figma
     (3281:2274: Title/Description sit at x:24 directly, no second inset
     layered on top) — this rule's own padding-inline was doubling that to
     48px. Zeroed here and restored at 700px, where Figma's tablet numbers
     (3324:10170) DO want both: the grid's own 48px margin placing the
     card, plus this rule's separate 48px inset for the text inside it. */
  padding-inline: 0;
  /* Fades the content block in from transparent at the top so it blends
     with the media above, per Figma. */
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, var(--color-background-white) 20.192%);
}

.intro-title {
  margin: 0;
}

.intro-description {
  margin: 0;
  opacity: 0.8;
}

/* Mobile: fading divider (foundations .divider) between stacked cards */
.intro-divider {
  margin: 0;
}

@media (min-width: 700px) {
  /* Tablet: 2-up grid, card 3 breaks out to full width + reversed layout */
  .intro-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0;
  }

  .intro-divider {
    display: none;
  }

  /* Reset the mobile-only full-bleed treatment above: at 700px+, Figma has
     cards sitting inside the grid's own padding like everything else on
     the page, and the doubled 48px text inset (grid margin + this rule's
     own padding) is the real spec here, not a bug — see the FIX comments
     on the base .intro-media / .intro-content rules. */
  .intro-media {
    width: 100%;
    margin-inline: 0;
  }

  .intro-content {
    padding-inline: var(--grid-margin);
  }

  .intro-media {
    aspect-ratio: 402 / 356;
  }

  /* FIX 2026-08-11: cards 1 and 2 were relying on .intro-card's mobile-
     default 16px flex-gap to space content below media, and .intro-content
     had no top/bottom padding of its own — so card 1 ("Familiar tools",
     3324:10168) and card 2 ("Easy to review...", 3324:10173) each landed at
     a different total height (title position matched, since both start
     right after the same fixed-height media, but the shorter card's own
     box came up short of the row's stretched height by a different amount
     than Figma intends). Figma's own resolved metadata for both cards'
     content frames (3324:10170 / 3324:10175) is identical: a fixed
     24px-top / 40px-bottom padding baked into the content frame itself,
     with ZERO gap between media and content (content starts at y=356,
     exactly where the 356px-tall media ends). Card 2's 4-line description
     fills that padding exactly (24+30+4+96+40=194, its full content
     height); card 1's shorter 3-line description leaves the same 24px of
     slack at the bottom that Figma's own frame does (170 of 194). Card 3
     keeps its own centered layout below and is explicitly reset off this. */
  .intro-card {
    gap: 0;
  }

  .intro-content {
    padding-top: 24px;
    padding-bottom: 40px;
  }

  /* Vertical divider is a pseudo-element rather than a plain border-left
     so it can extend past the card's own box into .intro-section's 48px
     padding-block and touch the top gradient-divider line added above —
     a border-left would stop at the card's own edge and leave a visible
     gap. Only card--2 needs this: it's the top-left of the 2-up row, so
     its vertical divider is the one touching that top line. */
  .intro-card--2 {
    position: relative;
  }

  .intro-card--2::before {
    content: "";
    position: absolute;
    top: -48px;
    bottom: 0;
    left: 0;
    width: 1px;
    background: var(--color-border-neutral);
  }

  .intro-card--3 {
    position: relative;
    grid-column: 1 / -1;
    /* row-reverse (not row): the DOM keeps media-then-content so desktop
       (image-top/content-bottom) doesn't need reordering, but Figma's
       tablet row puts content on the LEFT and media on the RIGHT. */
    flex-direction: row-reverse;
    align-items: stretch;
    gap: 0;
    /* Explicit height (matches the other cards' image height at this
       breakpoint) rather than deriving it from the video's own intrinsic
       size — that's indeterminate until the video loads. */
    height: 356px;
  }

  /* FIX 2026-08-11: this row is its own distinct layout (2-up row above,
     full-width row below — not a plain continuation of the desktop
     side-by-side or mobile stacked layouts), so it gets its own divider
     treatment instead of inheriting card--2's vertical-line approach. The
     old left-edge border-color line is removed; a horizontal top divider
     using the same --gradient-divider token as every other section
     divider on the page (see .feature-block-divider/.divider) marks the
     boundary between the 2-up row and this full-width row instead. */
  .intro-card--3::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--gradient-divider);
  }

  .intro-card--3 .intro-media {
    /* Explicit 50% rather than flex:1 1 0 — an unloaded <video> carries a
       default 300×150 intrinsic size that skews equal-grow distribution
       even with min-width:0, so pin the split directly instead. */
    flex: 0 0 50%;
    min-width: 0;
    aspect-ratio: auto;
  }

  .intro-card--3 .intro-content {
    flex: 0 0 50%;
    min-width: 0;
    justify-content: center;
    /* CORRECTED 2026-08-11: Figma's dev-mode code exports a px-48 on both
       the row's own wrapper AND this description frame (a doubled 96px
       read literally), but that's a nested-auto-layout export quirk, not
       the real resolved layout — Figma's own computed metadata for this
       node (3324:9574) places the description text at a single 48px
       inset, with the text itself measuring 306px wide inside this
       402px-wide flex-basis column (402 - 2×48 = 306, exactly matching).
       The doubled padding was making the text column ~96px narrower than
       spec, wrapping the description to 5 lines here instead of Figma's
       3 — this single grid-margin brings it back in line. */
    padding-inline: var(--grid-margin);
    /* Reset off the card1/2 pt-24/pb-40 added above — card 3 is a distinct
       row-reversed 50/50 layout with its own vertically-centered content
       (justify-content: center just above), not the top-packed layout the
       new padding-block values are for. */
    padding-block: 0;
    background: none;
  }
}

@media (min-width: 901px) {
  /* Desktop: back to a plain 3-up row, card 3 un-reverses */
  .intro-grid {
    grid-template-columns: repeat(3, 1fr);
  }

  .intro-media {
    aspect-ratio: 426 / 356;
  }

  /* FIX 2026-08-11: the tablet block above sets .intro-card to gap:0 and
     .intro-content to padding-top:24/padding-bottom:40 (Figma's tablet-only
     numbers for the 2-up row). Both rules use a min-width:700px query, so
     without an explicit reset here they silently carried into desktop too —
     collapsing cards 1/2's media→content gap to 0 while adding 24px of
     padding-top instead, an 8px net drop that knocked their titles out of
     alignment with card 3's (which resets its own content padding-block to
     0 in its tablet rule, so it was unaffected and stayed at the old
     gap:16 position). Restoring the pre-existing desktop numbers here. */
  .intro-card {
    gap: 16px;
  }

  .intro-content {
    padding-top: 0;
    padding-bottom: 0;
  }

  .intro-card--3 {
    grid-column: auto;
    flex-direction: column;
    align-items: stretch;
    height: auto;
    /* FIX 2026-08-11: same leftover-override bug as the media aspect-ratio
       below — the tablet rule sets gap:0 for its row-reversed 50/50 split,
       and nothing restored it once card 3 un-reverses to a column layout
       here, so its content sat flush against its media instead of the
       16px gap cards 1/2 get from the base .intro-card rule. */
    gap: 16px;
  }

  /* Single row at this breakpoint: card 2's divider now borders the row
     on its top AND bottom, so extend the pseudo-element both ways instead
     of the tablet's one-sided extension. */
  .intro-card--2::before {
    top: -48px;
    bottom: -48px;
  }

  /* FIX 2026-08-11: card 3's tablet rule swaps ::before to a horizontal
     top divider (gradient bar) because tablet gives card 3 its own
     full-width row below the 2-up grid — but that rule uses a
     min-width:700px query, so without a reset here it also carried into
     this 901px+ desktop layout, where card 3 sits back in a plain 3-up
     row alongside cards 1/2. In that row it needs the SAME vertical
     divider treatment as card 2 (a border between adjacent columns), not
     the tablet's horizontal one — so every property the tablet rule set
     (top/left/right/height/background) is explicitly reinstated here to
     match card--2's vertical-line pseudo-element exactly. */
  .intro-card--3::before {
    top: -48px;
    right: auto;
    left: 0;
    width: 1px;
    height: auto;
    bottom: -48px;
    background: var(--color-border-neutral);
  }

  .intro-card--3 .intro-media {
    flex: initial;
    /* FIX 2026-08-11: the tablet rule above sets aspect-ratio:auto (needed
       there so the row-reversed 50/50 split isn't fighting a fixed ratio)
       but nothing reset it back once card 3 un-reverses to a column layout
       here — so at desktop this media box was sized by the video's own
       intrinsic dimensions instead of matching cards 1/2's height, which
       is what pulled its headline up out of alignment with the other two
       cards' headlines. Explicitly matching the base .intro-media rule
       (same breakpoint, same ratio) puts all three cards back on equal
       footing. */
    aspect-ratio: 426 / 356;
  }

  .intro-card--3 .intro-content {
    flex: initial;
    justify-content: flex-start;
    padding-inline: var(--grid-margin);
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, var(--color-background-white) 20.192%);
  }
}

/* --------------------------------------------------------------------------
   QUOTE SECTION

   Spec pulled from Figma:
     - desktop 3136:308  (1512 frame)
     - tablet  3324:8406 (900 frame)
     - mobile  3284:1531 (375 frame)

   padding-top (128px) and the text↔mockup gap (16px) are identical at
   every breakpoint — confirmed by measuring all three frames' own child
   y-offsets, not just eyeballed.

   Mobile stacks: avatar+name row, then the quote, then the "Watch demo"
   button alone on its own row. Tablet/desktop instead put the quote
   first, full width, with the button and avatar+name sharing ONE row
   underneath (button left, avatar/name right) — a real structural
   change, not just a reflow, so .quote-text switches from a flex column
   to a 2-row CSS grid at 700px to relocate the pieces without duplicating
   markup (grid-template-areas below).

   Quote text.container padding is 20px at mobile specifically — NOT the
   sitewide 24px grid margin (measured directly on node 3284:1532/2046;
   tablet's 48px and desktop's 181px margins DO match the standard
   content-bound/grid-margin system, so only the mobile value is
   special-cased here).

   The kanban-demo mockup + floating "Claude" panel + edge shadow fades
   are all one exported image per breakpoint (assets/images/kanban-demo.
   {mobile,tablet,desktop}.svg) rather than hand-built — per the user's
   instruction to use that asset directly. Those exports also happen to
   bake in the "Built with Pagelove and Claude" caption for desktop/
   tablet (confirmed by rendering them), so the live .quote-built-with
   text below is shown at mobile only, where the export stops short of
   the caption (350px tall vs. the caption sitting just below it).
   -------------------------------------------------------------------------- */

.quote-section {
  padding-top: 128px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.quote-text {
  display: flex;
  flex-direction: column;
  gap: 32px;
  width: 100%;
  padding-inline: 20px; /* mobile-only override, see header note above */
}

.quote-person-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.quote-avatar {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 6px;
  overflow: hidden;
  background-color: rgba(255, 255, 255, 0.2);
  border: 1px solid var(--color-border-neutral);
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
}

.quote-avatar img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.quote-person {
  display: flex;
  flex-direction: column;
}

.quote-person-name {
  margin: 0;
  background: var(--gradient-main);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.quote-person-title {
  margin: 0;
  color: var(--color-text-primary);
}

.quote-copy {
  margin: 0;
  color: var(--color-text-primary);
}

.quote-copy-muted {
  color: var(--color-text-secondary);
}

.quote-cta-row {
  display: flex;
}

.quote-mockup-wrap {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
}

.quote-mockup-link {
  display: block;
  border-radius: 8px;
  transition: opacity 0.2s ease;
}

.quote-mockup-link:hover {
  opacity: 0.92;
}

.quote-mockup-link:focus-visible {
  outline: 2px solid var(--color-btn-secondary-border);
  outline-offset: 4px;
}

.quote-mockup {
  display: block;
}

.quote-mockup img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 375 / 350;
}

.quote-built-with {
  margin: 0;
  padding-inline: 20px; /* matches .quote-text's mobile-only 20px */
  color: var(--color-text-muted);
}

@media (min-width: 700px) {
  .quote-text {
    padding-inline: var(--grid-margin);
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "quote quote"
      "cta   person";
    row-gap: 32px;
    column-gap: 24px;
    align-items: center;
  }

  .quote-copy {
    grid-area: quote;
  }

  .quote-cta-row {
    grid-area: cta;
    justify-self: start;
  }

  .quote-person-row {
    grid-area: person;
    justify-self: end;
  }

  .quote-mockup img {
    aspect-ratio: 900 / 550;
  }

  /* The tablet/desktop kanban-demo export already bakes this caption in. */
  .quote-built-with {
    display: none;
  }
}

@media (min-width: 901px) {
  /* CORRECTED 2026-08-10: the quote paragraph's 1032px width isn't an
     arbitrary measurement — it's exactly 9 of the content zone's 10 true
     grid columns (93.33px column + 24px gutter each, the same column
     unit the 1150px content-bound cap itself is built from: 10 columns +
     9 gutters = 1149.3 ≈ 1150). A hardcoded max-width wouldn't shrink
     with the rest of the grid between 901–1278px, so it's replaced with
     an actual 10-column grid-column span instead — this stays properly
     grid-aligned at every desktop width, not just the 1512px reference. */
  .quote-text {
    grid-template-columns: repeat(10, 1fr);
    grid-template-areas:
      "quote quote quote quote quote quote quote quote quote ."
      "cta   cta   cta   .     .     .     .     person person person";
  }

  .quote-copy {
    max-width: none;
  }

  /* BUG FIX 2026-08-11: .quote-mockup-wrap had no width cap at all, so past
     the 1512px reference frame the mockup image (aspect-ratio 1512/550,
     width:100%) just kept stretching edge-to-edge with the viewport
     instead of holding its designed size — the wrapper needs the same
     "fixed size, centered, margin absorbs the rest" treatment as every
     other section. 1512px (not one of the content-bound tokens) because
     this image is a full-bleed export of the entire reference frame, with
     no inset of its own — same literal value already used by its own
     aspect-ratio just below. */
  .quote-mockup-wrap {
    max-width: 1512px;
    margin-inline: auto;
  }

  .quote-mockup img {
    aspect-ratio: 1512 / 550;
  }
}

/* --------------------------------------------------------------------------
   FEATURE SECTION (feature.section.intro + feature.section.scroll)

   Spec pulled from Figma:
     - desktop intro  3077:6025 (1512 frame) / scroll 3077:6048
     - tablet  intro  3324:10491 (900 frame) / scroll 3327:11107
     - mobile: no pin/scroll interaction (confirmed with you) — plain
       stacked fallback only, built without a dedicated mobile Figma pass.

   ARCHITECTURE (wide desktop only, >=1180px):
   One persistent element ("body card") plays both roles instead of two
   separate cards crossfading — matches the designer's own annotation on
   feature.section.intro's "effect" layer: "the 'Pagelove replaces...'
   section should transition to the left, overlapping the 'Traditional...'
   section. Once it reaches its final position, it should remain sticky
   while the rest of the page continues to scroll."

   .feature-stage is the CSS Grid's left column (standard sticky-sidebar
   recipe: align-self:start + a taller sibling column gives native,
   JS-free pinning for the whole back half of the section). At rest it
   carries a translateX offset (set as a CSS custom property, animated by
   JS) that visually parks it next to .feature-description — which is
   itself absolutely positioned (not part of the grid at all) so it can
   sit at the standard 1150px content-bound position independent of the
   grid's own flatter margin. As scroll progress (0->1) advances, the
   translateX relaxes to 0 (settling into the grid's natural flat-margin
   column, which is where feature.section.scroll's real "body" sidebar
   sits — 64px margin at desktop, not content-bound's 181px), the card
   grows from its intro height to its sidebar height, its inner content
   crossfades (code snippet -> TOC list), Description fades out, and
   .feature-scroll-area (the 4 stacked blocks, permanently in normal
   flow to the right — this is what gives the grid its extra height for
   the sticky trick) fades in starting at 80% progress. All driven by
   feature.js.

   FIX 2026-08-12: below 1180px, this two-column pin/sticky/morph grid ran
   out of room to keep both the body card and .feature-scroll-area's real
   content on screen at a readable size at once — no combination of column
   floors avoids SOME squeeze in that band. Per explicit request, under
   1180px the section now drops the pin entirely and falls back to the
   exact same plain stacked layout mobile already uses (description, then
   the body card, then the scroll blocks, all in normal flow, no sticky/
   translateX/crossfade) — so this is now the DEFAULT (mobile-first) rule
   below, with the two-column grid/sticky/morph treatment layered back on
   top only inside the >=1180px block further down. Card/description
   SIZING (394px tablet, 680px desktop) is untouched either way — see the
   bounded tablet-asset and >=901px blocks below, which apply purely on
   viewport width and don't care whether the pin is engaged. -------------------------------------------------------------------------- */

.feature-pin {
  padding-top: 128px;
}

.feature-pin-grid {
  box-sizing: border-box;
  width: 100%;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 48px;
  padding-inline: var(--grid-margin);
}

.feature-stage {
  position: relative;
  min-width: 0;
}

/* Stacked (<1180px, see FIX 2026-08-12 above): shown in normal flow,
   right above the body card, same treatment as mobile always used — no
   pin/sticky, no fade. Only inside the >=1180px block further down does
   this switch to the sticky, absolutely-positioned, JS-faded panel the
   comment below (kept for that context) describes.

   Once the pin IS active (>=1180px): shares .feature-stage's own grid
   cell (both explicitly grid-column:1/grid-row:1, see below) rather than
   being taken out of the grid with position:absolute — that was the bug:
   an out-of-flow absolute element has no sticky behavior at all, so it
   just scrolled away with the rest of the document like ordinary content
   ("going up" as the page scrolled down) instead of holding its place in
   the viewport the way the body card does. Sticky too, at the exact same
   flat, constant top offset as .feature-stage (160px) so the two stay
   levelled with each other and hold still together — neither one's top
   ever changes, so there's no vertical motion at all, only the
   horizontal slide. It's still visually inset further right than the
   card's own column start (via margin-left, mirroring .content-bound's
   centering), and it still gets covered rather than just fading:
   .feature-stage comes later in DOM order, so once its leftward slide
   lands on top, it paints over Description (which also fades out and
   sits behind the card's own opaque backing — see .feature-body-card —
   so nothing shows through either way). */
.feature-description {
  display: block;
  position: static;
  width: auto;
  margin-bottom: 24px;
}

/* FIX 2026-08-12: per explicit request, the stacked layout's Description
   text (700-1179px — i.e. whenever the pin isn't engaged, see the
   ARCHITECTURE/FIX comment above .feature-pin) shouldn't run the full
   width of the section like the base rule's width:auto gives it; it reads
   as a 6-column measure instead. Computed off the page's own live grid
   variables (--grid-columns/--grid-gutter, foundations.css) rather than a
   hardcoded pixel width so it comes out correctly proportioned whether the
   current breakpoint has 8 columns (tablet, 700-900px) or 12 (901-1179px)
   — "6 columns" means something different in each, and this stays right
   either way without a separate rule per range. Stays flush left, same as
   every other element here — no margin:auto added. */
@media (min-width: 700px) and (max-width: 1179px) {
  .feature-description {
    width: calc(
      (6 / var(--grid-columns)) *
      (100% - ((var(--grid-columns) - 1) * var(--grid-gutter))) +
      (5 * var(--grid-gutter))
    );
  }
}

.feature-eyebrow {
  margin: 0 0 8px;
  background: var(--gradient-main);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.feature-description-headline {
  margin: 0;
  color: var(--color-text-primary);
}

/* The body card's chrome (rounded corner, translucent blur, border,
   header dots) and its headline are baked into the exported
   feature-body-intro.svg artwork — see .feature-body-header-visual,
   which crops to just that top portion and never changes for the rest
   of the interaction (no crossfade, no movement — avoids the ghosting
   double-exposure a full-image crossfade caused). Only the code area
   below it crossfades, and only with the real, live .feature-toc nav —
   see .feature-body-crossfade. No box-shadow: the artwork's own
   translucent surface already reads as elevated without an extra drop
   shadow muddying the page background.

   background-color is an opaque backing behind the artwork: the exported
   SVG's own card surface is a deliberately glassy fill (white at 90%
   opacity, by design), so anything painted behind it in the DOM — namely
   .feature-description, mid-fade — reads through it. That's a fine, soft
   effect against the plain page background it was designed for, but not
   against readable text sitting directly underneath. A solid backing the
   same color as the page means the 10% see-through shows white-on-white
   (invisible) instead of foreign content, regardless of any fade timing. */
.feature-body-card {
  box-sizing: border-box;
  width: 100%;
  position: relative;
  overflow: hidden;
  border-radius: 12px 0 0 0;
  display: flex;
  flex-direction: column;
  background-color: var(--color-background-white);
}

/* FIX 2026-08-11: per Figma node 3077:6069 ("shadows"), the card has two
   overlay fades that gently dissolve its own content into the page
   background right before the card's own edge — a right-side fade (so the
   code/TOC content doesn't feel like it's cut off mid-line) and a
   bottom-side fade (so the divider/footer content below doesn't feel like
   it collides with the card). Figma exports these as flat PNGs, but
   they're just a plain two-stop fade to the card's own background color,
   so a CSS gradient reproduces them exactly without needing the
   (unreachable, localhost-only) exported asset. z-index:1 is needed since
   .feature-body-header-visual/.feature-body-crossfade are position:relative
   and paint above a default z-index:auto ::before/::after otherwise. */
.feature-body-card::before,
.feature-body-card::after {
  content: "";
  position: absolute;
  z-index: 1;
  pointer-events: none;
}

.feature-body-card::before {
  top: 0;
  right: 0;
  bottom: 0;
  width: 31.2%; /* 211/676, per node 3077:6070 */
  background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, var(--color-background-white) 100%);
}

.feature-body-card::after {
  left: 0;
  right: 0;
  bottom: 0;
  height: 96px; /* per node 3077:6071 */
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, var(--color-background-white) 100%);
}

.feature-body-header-visual {
  position: relative;
  width: 100%;
  aspect-ratio: 684 / 235;
  overflow: hidden;
  flex-shrink: 0;
}

.feature-body-img-static {
  display: block;
  width: 100%;
  height: auto;
}

/* Fixed-size box the code snippet and the real TOC nav share, crossfading
   in place via opacity only — nothing here ever changes size, so the
   card's total height stays constant through the whole interaction.

   FIX 2026-08-11: the card's left/top border is baked into the static
   feature-body-intro.svg artwork (see .feature-body-header-visual's own
   comment) — which only covers the TOP box. Below it, this box had no
   border of its own, so the frame read as cropped/cut short right where
   the TOC list appears instead of enclosing the whole card. Continuing
   the same left border down here (matching Figma's own container, which
   per node 3077:6052 is a single border-l frame around the full card) is
   what actually stitches the two boxes into one visually continuous
   frame — no top border needed here since the header box above already
   draws that once, at the very top of the card.

   FIX 2026-08-11: that border-left painted flush at x=0, but the header
   artwork's own baked-in border sits ~4-5px inset from the card's true
   edge (measured directly off the rendered artwork — matches Figma node
   3327:11746's own -4px "shadow" frame offset). A flush border here made
   the line visibly jump/glitch sideways right at the seam between the two
   boxes. Using an inset pseudo-element instead of a true border lines the
   two segments up into one continuous line. z-index is needed because the
   code image and .feature-toc are both position:absolute siblings that
   would otherwise paint over a default-stacked ::before. */
.feature-body-crossfade {
  position: relative;
  width: 100%;
  aspect-ratio: 684 / 337;
  overflow: hidden;
  flex-shrink: 0;
}

.feature-body-crossfade::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 5px;
  width: 1px;
  background: var(--color-border-neutral);
  z-index: 2;
  pointer-events: none;
}

.feature-body-img-code {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: auto;
  /* Shifts the (full 572px-tall) image up so only its code region — the
     bottom 337px, starting at y=235 — shows inside this box. 235/572. */
  transform: translateY(-41.084%);
  opacity: var(--feature-code-opacity, 1);
  /* REDEFINED 2026-08-11: the code snippet and the TOC nav used to be a
     plain linear opacity crossfade (one ramping down exactly as the other
     ramps up), so for a stretch in the middle both were partway visible
     at once — the "overlap" that read as strange. main.js now sequences
     them (code fully gone before the TOC starts appearing) and blurs each
     one as it exits/enters — filter, like opacity, is driven straight off
     scroll position every frame, so it stays exactly as responsive as the
     opacity fade already was, just softer at the edges instead of a hard
     dissolve. */
  filter: blur(var(--feature-code-blur, 0px));
}

/* Stacked range (<1180px — mobile AND the 700-1179px tablet/desktop-
   stacked band, see ARCHITECTURE/FIX comment above .feature-pin): the
   body card's title and code are LIVE text here, not the raster artwork
   (.feature-body-header-visual/.feature-body-crossfade, both hidden
   below), per explicit request that they be fluid — reflowing to fit the
   column rather than ever being cropped, the way the fixed-pixel-asset
   approach this replaced was. Content/structure/colors match Figma node
   3483:7858 ("feature.section.intro") exactly: same two-line headline,
   same 20-line code sample with the same syntax colors, same "03" active
   line. .feature-toc's own desktop TOC crossfade isn't relevant here
   either way (display:none below 1180px already).

   .feature-body-live itself keeps a flat 550px height (matching Figma's
   own literal h-[550px] on this node) so the card's total height still
   never changes regardless of width — same promise as before, just via a
   fixed height instead of a fixed-and-cropped width. Width is 100%
   (fluid) since nothing inside needs to be pinned to a pixel size
   anymore. Any code content that wraps past the available height at the
   very narrow end is clipped by this element's own overflow:hidden
   rather than pushing the card taller — the same graceful-crop fallback
   used elsewhere, just vertical instead of horizontal now. */
.feature-body-live {
  display: none;
}

@media (max-width: 1179px) {
  .feature-body-header-visual,
  .feature-body-crossfade {
    display: none;
  }

  .feature-body-live {
    box-sizing: border-box;
    display: block;
    width: 100%;
    height: 550px;
    padding: 4px;
    overflow: hidden;
    flex-shrink: 0;
  }

  .feature-body-live-surface {
    box-sizing: border-box;
    position: relative;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    gap: 32px;
    overflow: hidden;
    border-radius: 12px 0 0 0;
    border-left: 1px solid var(--color-border-neutral);
    border-top: 1px solid var(--color-border-neutral);
    background-color: rgba(255, 255, 255, 0.9);
    backdrop-filter: blur(50px);
    -webkit-backdrop-filter: blur(50px);
  }

  /* Stand-in for the page.header "..." dots baked into the old artwork —
     there's no live equivalent to reuse, so three plain dots reproduce
     the same window-chrome read. */
  .feature-body-live-dots {
    position: absolute;
    top: 13px;
    left: 20px;
    display: flex;
    align-items: center;
    gap: 4px;
  }

  .feature-body-live-dots span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: #CBD5E1;
  }

  /* "1 column ahead" per explicit request: the same live-grid-variable
     technique as .feature-scroll-area's own inset above (one column's
     own fluid width plus one gutter — the offset that lands exactly at
     the start of the next column) rather than Figma's literal 133px, so
     it stays correctly proportioned whether this breakpoint has 8
     columns (tablet) or 12 (desktop-stacked/mobile's 4). */
  /* Per explicit request: the headline always measures 3 grid columns
     wide (tablet and mobile alike), computed the same way as
     .feature-description's own 6-column width above — a live K-column
     span off the page's own grid variables rather than a fixed pixel
     wrap width. Reflows within that measure instead of ever being
     cropped.

     BUG FIX: width and margin-left both need to resolve against the SAME
     100% reference (.feature-body-live-surface's own content box) — set
     on this one element rather than split between it (margin-left) and
     its child <p> (width), since a percentage width on the child would
     otherwise resolve against ITS parent's box, i.e. THIS element's
     already-margined width, silently subtracting the column-ahead offset
     a second time and rendering the "3 columns" narrower than the grid's
     real 3 columns. */
  .feature-body-live-headline {
    box-sizing: border-box;
    flex-shrink: 0;
    padding-top: 52px;
    margin-left: calc(
      ((100% - ((var(--grid-columns) - 1) * var(--grid-gutter))) / var(--grid-columns)) +
      var(--grid-gutter)
    );
    width: calc(
      (3 / var(--grid-columns)) *
      (100% - ((var(--grid-columns) - 1) * var(--grid-gutter))) +
      (2 * var(--grid-gutter))
    );
  }

  .feature-body-live-headline p {
    margin: 0;
    color: var(--color-text-warm);
  }

  /* BUG FIX: originally two separate flex columns (all 20 line numbers in
     one, all 20 code lines in the other) — the moment any code line wrapped
     onto a 2nd/3rd line (inevitable once "fluid, not cropped" lets long
     lines like the viewport meta tag reflow), the two columns fell out of
     sync, since the numbers column had no matching extra rows to grow by.
     A single CSS Grid with each number+line as a pair of same-row items
     (auto-placed: number in column 1, its code line in column 2, next pair
     wraps to the next row automatically) keeps every number level with its
     own line regardless of how tall that line's wrapped text gets — the
     row's height is just whichever cell is taller, same as an HTML table. */
  .feature-body-live-code {
    box-sizing: border-box;
    flex: 1 0 0;
    min-height: 0;
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: 24px;
    align-items: start;
    align-content: start;
    padding-right: 24px;
    margin-left: calc(
      ((100% - ((var(--grid-columns) - 1) * var(--grid-gutter))) / var(--grid-columns)) +
      var(--grid-gutter)
    );
    overflow: hidden;
  }

  .feature-body-live-code-num {
    color: var(--color-text-secondary);
  }

  .feature-body-live-code-num.is-active {
    color: #00A933;
  }

  /* white-space:pre-wrap (not nowrap or the old crop) plus min-width:0 is
     what makes the code "fluid to not be cropped" per explicit request —
     long lines wrap onto extra lines within their own grid row instead of
     running off the edge or being cut by an overflow:hidden ancestor. */
  .feature-body-live-code-line {
    margin: 0;
    min-width: 0;
    color: #26334D;
    white-space: pre-wrap;
    word-break: break-word;
  }

  /* Full-row highlight (matching Figma's "03" active line): both cells in
     the row get the tint, and the number cell carries the left accent, so
     together they read as one continuous highlighted row the way the
     original absolutely-positioned bar did — but robust to row height
     changing as lines wrap, since it's just per-cell background now
     instead of a fixed-position overlay. */
  .feature-body-live-code-num.is-active,
  .feature-body-live-code-line.is-active {
    background-color: #E8F3ED;
  }

  .feature-body-live-code-num.is-active {
    border-left: 2px solid #00A933;
    padding-left: 2px;
    margin-left: -4px;
  }

  .feature-code-tag {
    color: #FF66BF;
  }

  .feature-code-attr {
    color: #59A6FF;
  }

  .feature-code-string {
    color: #4DD959;
  }
}

/* REVERTED 2026-08-12: tablet (700-900px) briefly had its own 4-column
   title width here — per explicit follow-up request, tablet instead
   follows the exact same rule as desktop-stacked (901-1179px): the base
   rule above (3-column width, "1 column ahead" inset), no tablet-only
   override. Both sub-ranges of the <1180px stacked band now share one
   set of numbers; only mobile (<700px, below) still differs. */

/* Match the tablet headline measure after the global grid changes from
   eight to twelve columns, while the feature section remains stacked. */
@media (min-width: 901px) and (max-width: 1179px) {
  .feature-body-live-headline {
    width: calc(
      (5 / var(--grid-columns)) *
      (100% - ((var(--grid-columns) - 1) * var(--grid-gutter))) +
      (4 * var(--grid-gutter))
    );
  }
}

/* Mobile only (<700px), per explicit request: title and code no longer
   start at the "1 column ahead" offset the base rule above gives every
   width in the <1180px range — instead a flat 32px inset, and the title
   now stretches all the way to the end of the grid's 4th (last mobile)
   column instead of being held to a live column-span width. Since
   .feature-body-live-code already has no width of its own (it fills the
   remaining cross-axis space of its flex column via default stretch),
   only its margin-left needs the same 32px override — it already reaches
   the same right edge the title now does. */
@media (max-width: 699px) {
  .feature-body-live-headline,
  .feature-body-live-code {
    margin-left: 32px;
  }

  .feature-body-live-headline {
    width: calc(100% - 32px);
  }
}

.feature-toc {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  /* FIX 2026-08-11: was inset:0 (flush left at x=0), but the container's
     true visible left edge is the 5px-inset border line drawn by
     .feature-body-crossfade::before, not the box's literal x=0. That left
     the active row's full-bleed highlight starting 5px further left than
     the border itself, so the highlight briefly crossed/overlapped the
     border line instead of stopping flush against it. Starting the whole
     nav 5px in makes every item's edge-to-edge highlight land exactly on
     the border, matching Figma's own selector (which snaps to the frame's
     true edge, not past it). */
  left: 5px;
  /* Hidden by default (<1180px, plain stacked layout — see the
     ARCHITECTURE comment above .feature-pin) since the interactive TOC nav
     only ever appears once the pin/crossfade is actually engaged; the
     >=1180px block below switches this back to flex. */
  display: none;
  flex-direction: column;
  /* REDEFINED 2026-08-11: per Figma (node 3077:6052), the headline and the
     TOC list are the top and bottom of ONE auto-layout frame with an
     "Auto" (space-between-style) gap — whatever room is left between them
     goes to the gap, not to the list. Our headline lives in a separate,
     never-changing static-image box above this one (see
     .feature-body-header-visual's own comment), so the equivalent here is
     anchoring the list to the BOTTOM of its own box instead of the top —
     same visual result, larger gap above the list, list sitting low in
     the frame — without merging the two boxes and reintroducing the
     ghosting a shared crossfade caused (see .feature-body-card's comment). */
  justify-content: flex-end;
  /* Only vertical padding lives here now — bottom space below the last
     item, same "Auto gap" reasoning as above. Horizontal inset moved onto
     each .feature-toc-item below, so the active-row highlight can reach
     this container's true left/right edges (see .feature-toc-item's own
     comment). */
  padding: 0 0 24px;
  opacity: 0; /* fades in once morphed into the sidebar */
  filter: blur(var(--feature-sidebar-blur, 0px));
}

/* FIX 2026-08-11: per Figma node 3077:6065, the active row's highlight is
   a decorative layer that escapes its own item's padding (there: literally
   `left:-113px; right:0` to cancel out a `pl-113` on the item) so the
   highlight reaches the container's true left/right edges instead of
   stopping at the text's own inset. Rather than replicate that
   negative-offset escape hatch, the horizontal inset that used to live on
   `.feature-toc` (padding-left: 17.5%, padding-right) now lives on each
   `.feature-toc-item` instead, and the highlight is a position:absolute,
   inset:0 pseudo-element on the item. Per spec, an absolutely-positioned
   box with inset:0 sizes itself to its containing block's *padding box* —
   i.e. it naturally spans the item's full padding, ignoring the item's own
   content edges — so it reaches all the way to the item's true left/right
   edges (flush with the card) without any negative-offset math needed. */
.feature-toc-item {
  position: relative;
  z-index: 0; /* contains ::before's z-index:-1 to this item only */
  display: flex;
  align-items: center;
  gap: 24px;
  padding-block: 8px;
  padding-left: 17.5%;
  padding-right: 24px;
  color: var(--color-text-secondary);
  transition: color 0.2s ease;
}

/* The index/label spans use .code-inline (foundations.css) for family/
   weight/letter-spacing/line-height, but pin font-size here explicitly at
   14px rather than relying on .code-inline's own value happening to match
   — keeps the TOC links' size an intentional, dedicated choice instead of
   an incidental side effect of a shared utility class. */
.feature-toc-index,
.feature-toc-label {
  font-size: 12px;
}

.feature-toc-item::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-left: 2px solid transparent;
  background-color: transparent;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.feature-toc-item.is-active {
  color: var(--color-text-warm);
}

.feature-toc-item.is-active::before {
  border-left-color: var(--color-text-secondary);
  background-color: var(--color-background-neutral-100, #F8FAFC);
}

.feature-scroll-area {
  box-sizing: border-box;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 48px;
  /* 0 by default (plain stacked layout, <1180px) — the >=1180px block
     below replaces this with the JS-driven --feature-scrollarea-delay,
     which reserves scroll distance for the morph instead. */
  padding-top: 0;
  opacity: 1;
}

/* Stacked range only (700-1179px, per explicit request): scroll.area and
   everything inside it (each .feature-block — "AI", "One system", "Tokens",
   "Security", their dividers/toggle) sit 1 grid column narrower on each
   side than the rest of the section, rather than flush with the same
   var(--grid-margin) edge Description/the body card use. A single
   padding-inline here insets every child at once, since they're all plain
   full-width flex children of this container.

   Computed off the page's own live grid variables, same technique as
   Description's 6-column width above: one column's own fluid width (no
   internal gutter, since it's a single column) plus one gutter for the
   "column + gutter" inset Figma shows (node 3483:7881 "feature.section.
   scroll") — correct whether the current breakpoint has 8 columns
   (tablet, 700-900px) or 12 (901-1179px), same as Description. */
@media (min-width: 700px) and (max-width: 1179px) {
  .feature-scroll-area {
    padding-inline: calc(
      ((100% - ((var(--grid-columns) - 1) * var(--grid-gutter))) / var(--grid-columns)) +
      var(--grid-gutter)
    );
  }
}

.feature-block {
  display: flex;
  flex-direction: column;
  gap: 16px;
  /* FIX 2026-08-12: .feature-scroll-area (the grid item/flex container this
     sits in) already has min-width:0 so IT can shrink below its content's
     natural size, but that alone doesn't help — each .feature-block here
     is itself a flex item one level down, and flex items default to
     min-width:auto (shrink-to-fit their own content's minimum, e.g. a
     headline's longest word), which was still forcing this block wider
     than the column and pushing it off the right edge at the narrow end
     of the desktop range. min-width:0 here lets it actually shrink to
     match, so headline/description text rewraps and the image rescales
     instead of the block overflowing. */
  min-width: 0;
}

/* FIX 2026-08-11: background-clip:text only paints the gradient within the
   element's own box (the line-height box, here 42px for a 40px font) — so
   any part of a glyph's descender that dips below that box (serif "p"/"g"
   tails, common at this tight a line-height) has no gradient to paint
   through and reads as cropped/invisible, even though overflow is already
   visible and nothing is actually clipping the glyph outline itself.
   overflow:visible is set explicitly for clarity, and padding-bottom
   enlarges the box the gradient paints into (padding adds space after the
   text without moving its baseline) so descenders have room — cheaper and
   more robust than retuning line-height/letter-spacing on the shared
   .h3-section scale just for this gradient-clipped instance. */
.feature-block-headline {
  margin: 0;
  overflow: visible;
  padding-bottom: 0.15em;
  background: var(--gradient-main);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.feature-block-description {
  margin: 0;
  color: var(--color-text-primary);
}

/* Mobile only, per explicit request: tighten the headline→description gap
   from .feature-block's shared 16px flex-gap down to 8px, without
   touching the eyebrow→headline or description→media gaps that share the
   same `gap` value. A negative top margin on the description is the
   least invasive way to shave 8px off just this one gap; bounded to
   max-width so it never reaches the tablet/desktop pin layout. */
@media (max-width: 699px) {
  .feature-block-description {
    margin-top: -8px;
  }
}

.feature-block-media {
  display: block;
  margin-top: 8px;
}

.feature-block-media img {
  display: block;
  width: 100%;
  height: auto;
}

/* BUG FIX 2026-08-11: this <hr> only ever had its margin reset, so it was
   rendering with the plain browser-default hr style (a grey inset/groove
   border) instead of any real design color — never actually styled.
   Checked against Figma (node 3077:6072 "scroll.area", the three divider
   vectors between blocks): they're literally named/tagged with the site's
   own "divider" style, i.e. the same fading --gradient-divider token
   already used by .divider (foundations.css) and intro.section's card
   dividers — reused here the same way rather than a flat border-color. */
.feature-block-divider {
  margin: 0;
  height: 1px;
  border: none;
  background: var(--gradient-divider);
}

.feature-block-media--toggle {
  position: relative;
}

.feature-toggle {
  display: inline-flex;
  /* FIX 2026-08-12: .feature-scroll-area's grid track can now shrink as
     low as needed to guarantee it's never cropped (see .feature-pin-grid),
     but this toggle's two pill buttons don't reflow on their own — at the
     very narrow end of the desktop range they were the one thing still
     overflowing their column. max-width+wrap here, plus min-width:0 on
     each button below, let the pair drop to a second row instead of
     forcing horizontal overflow, without changing the pill's normal
     single-row look at any width that actually has room for it. */
  max-width: 100%;
  flex-wrap: wrap;
  gap: 4px;
  padding: 4px;
  border-radius: 100px;
  background-color: #F8FAFC;
  border: 1px solid var(--color-border-neutral);
  margin-bottom: 16px;
}

.feature-toggle-btn {
  min-width: 0;
  border: none;
  background: none;
  cursor: pointer;
  padding: 6px 14px;
  border-radius: 100px;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: -0.3px;
  color: var(--color-text-secondary);
  transition: background-color 0.2s ease, color 0.2s ease;
}

.feature-toggle-btn.is-active {
  background-color: #FFFFFF;
  color: var(--color-text-primary);
  box-shadow: 0 1px 2px rgba(29, 41, 61, 0.08);
}

.feature-toggle-image {
  display: none;
}

.feature-toggle-image.is-active {
  display: block;
}

/* --------------------------------------------------------------------------
   NOTE 2026-08-12: the fixed-680px card sizing that used to live here,
   unconditional from 901px up, has moved into the >=1180px block below —
   the 700-1179px stacked range now gets its own full-width, adaptive card
   instead (see the "Stacked range" block above). Below 1180px the card is
   never pinned to 680px anymore; only once the two-column pin engages does
   the fixed size apply, since that's the only layout where the grid column
   itself is capped at 680px too.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   >=1180px only: the pin/sticky/morph interaction engages.

   FIX 2026-08-12: this used to start at 700px (tablet), with a second,
   desktop-only sizing/grid step at 901px. Per explicit request, the pin is
   now reserved for a wide-enough viewport that it never has to fight for
   room: below 1180px, .feature-pin-grid's own base rule (mobile-first,
   above) already lays the section out exactly like mobile always has —
   description, then the body card, then the scroll blocks, stacked in
   normal flow, nothing sticky, nothing scroll-driven. main.js's own
   min-width:1180px media query mirrors this exactly, so the JS-driven
   crossfade/translateX/fade-in simply never starts below it either — see
   feature.js's `mq` there. The card's fixed 680px sizing (below) is now
   exclusive to this block too — see the NOTE above the old >=901px sizing
   block, which the stacked range (700-1179px) no longer shares. -------------------------------------------------------------------------- */

@media (min-width: 1180px) {
  /* BUG FIX 2026-08-11: .feature-pin-grid had no width cap, so past its own
     content's natural size (the two columns hit their minmax upper bounds)
     the grid box itself kept stretching to 100vw while the columns stayed
     put — CSS Grid packs tracks at the start by default, so the columns
     (and everything in them) just sat flush against the left padding with
     a growing dead zone of empty space to the right, instead of the whole
     block re-centering the way nav/hero/footer do. --feature-grid-max-width
     is the natural cap already implied by the two columns' own upper
     bounds (680px/563px) + their gap + the section's padding — not a new
     measurement, just the box those numbers already add up to once fixed.
     Set as a custom property (inherited by .feature-description below,
     which is a DOM child of this element) rather than a private local
     constant, so both rules stay in sync if the column widths ever change.

     The columns themselves flex within a floor..680/563 range rather than
     being pinned outright to 680/563: at exactly 1180px there still isn't
     room for both at full size (680+563+24px gap+2×64px margin = 1395px),
     so grid's track-sizing algorithm grows both together from their floors
     in lockstep as the viewport widens, reaching their full 680/563 size
     together once it's wide enough (~1395px+) — same mechanism already
     proven crop-free down to 901px; starting the whole pin at 1180px
     instead just means there's less range where the floor ever applies. */
  .feature-pin-grid {
    display: grid;
    grid-template-columns: minmax(390px, 680px) minmax(340px, 563px);
    column-gap: 24px;
    align-items: start;
    --feature-grid-max-width: calc(680px + 563px + 24px + (2 * var(--grid-margin)));
    max-width: var(--feature-grid-max-width);
    margin-inline: auto;
  }

  /* .feature-stage sticks 64px below the nav plus a constant 96px of
     breathing room (160px total, flat — see main.js) for the entire
     interaction, not just on first arrival. This used to relax down to
     64px once the morph got going (even resolved quickly, that read as
     the card jumping upward right as scrolling began); keeping it fully
     constant means zero vertical motion, ever — only the horizontal slide
     and the crossfades move. */
  .feature-stage {
    grid-column: 1;
    grid-row: 1;
    position: sticky;
    top: 160px;
    align-self: start;
    transform: translateX(var(--feature-shift-x, 0px));
    will-change: transform;
  }

  /* Same grid cell as .feature-stage and the exact same flat sticky top,
     so the two hold level with each other and stay put in the viewport
     together — no more scrolling away with the rest of the document, and
     no vertical motion for either one. Fades out across the same progress
     the horizontal slide uses (main.js), on top of the card's own opaque
     backing (see .feature-body-card) — belt and suspenders against
     anything reading through the card's deliberately glassy artwork. */
  .feature-description {
    grid-column: 1;
    grid-row: 1;
    position: sticky;
    top: 160px;
    align-self: start;
    width: 446px;
    padding-top: 96px; /* aligns the eyebrow with the body card's headline */
    margin-bottom: 0;
    opacity: var(--feature-description-opacity, 1);
    /* Content-bound alignment, as an offset ADDED on top of the grid
       cell's own start. This used to be a plain (100vw - min(100vw,
       1278px))/2 — correct only because the grid itself was always flush
       left at x:0, so a raw viewport-relative offset happened to land in
       the right place. Now that .feature-pin-grid is capped and centered
       above, its own column 1 start moves too, so the offset has to be
       the DIFFERENCE between where this grid centers and where
       .content-bound centers, not an absolute viewport offset — otherwise
       centering both independently double-shifts this element. Algebra
       aside, the effect is unchanged: still lands at exactly 181px total
       from the viewport edge at the 1512px Figma reference frame, same as
       nav/hero's own content-bound position, just robust to the grid's
       position now instead of assuming it's pinned to x:0. */
    margin-left: calc((min(100vw, var(--feature-grid-max-width)) - min(100vw, var(--content-max-width-wide))) / 2);
  }

  /* padding-top reserves exactly as much extra scroll distance as the
     morph takes to reach 80% progress (see SCROLLAREA_DELAY in main.js) —
     without it, block 1 ("AI-native") sits right at the top of this
     column and scrolls up past the sticky viewport band while its own
     opacity is still 0 (the reveal only starts at 80% progress), so it
     was never actually visible before this fix: by the time it faded in,
     the page had already scrolled past it. This keeps block 1 arriving in
     view exactly as its opacity starts turning on. */
  .feature-scroll-area {
    grid-column: 2;
    grid-row: 1;
    padding-top: var(--feature-scrollarea-delay, 0px);
    opacity: var(--feature-scrollarea-opacity, 0);
    pointer-events: var(--feature-scrollarea-pointer, none);
  }

  .feature-toc {
    display: flex;
    opacity: var(--feature-sidebar-opacity, 0);
    padding-bottom: 32px;
  }

  /* MOVED 2026-08-12 from the old unconditional >=901px sizing block (see
     the NOTE left in its place above) — the fixed 680px card only applies
     once the pin actually engages and the grid column itself caps at 680px
     (see minmax above); the 700-1179px stacked range now gets its own
     full-width, adaptive card instead (see the "Stacked range" block
     further up). Reasoning unchanged: pinned to a flat 680px so the
     artwork renders at its one true native size regardless of the column's
     own width — .feature-body-card (overflow:hidden) clips any excess. */
  .feature-body-header-visual,
  .feature-body-crossfade {
    width: 680px;
  }

  .feature-stage {
    max-width: 680px;
  }

  .feature-toc-item {
    padding-right: 32px;
  }
}

/* --------------------------------------------------------------------------
   Mobile only (<700px): the base rule above (mobile-first, and now shared
   with the 700-1179px stacked band too — see the ARCHITECTURE/FIX comments
   above .feature-pin) already covers the plain stacked layout itself
   (flex-column, Description in normal flow, TOC hidden, scroll blocks at
   full opacity). The only things still genuinely mobile-SPECIFIC are the
   tighter vertical/horizontal rhythm below — every breakpoint from 700px
   up uses var(--grid-margin) (48/64px) for the section's own side padding
   instead.
   -------------------------------------------------------------------------- */

@media (max-width: 699px) {
  .feature-pin {
    padding-top: 64px;
  }

  .feature-pin-grid {
    padding-inline: 20px;
  }
}

/* --------------------------------------------------------------------------
   CTA BANNER

   Spec pulled from Figma (node 3256:37297, desktop 1512 frame only —
   tablet/mobile weren't inspected this round, so this reuses the site's
   existing responsive tokens/scale rather than measured breakpoint specs).

   Content column: a centered 680px stack (logo-mark, heading, button) —
   see .content-bound--narrow in foundations.css. Vertical padding follows
   the same flat-value-plus-mobile-override pattern as .feature-pin/
   .quote-section rather than Figma's literal py-128, so it stays in step
   with the rest of the page's rhythm.

   Headline: "Ready to" / "build?" — two separate inline containers per
   spec, reusing .h1 (already fully responsive; Figma's one-off "Headline/
   Desktop/primary" token — 96px/84/-4px at desktop — is close enough to
   .h1's 92px/88px/-4px that forking a near-duplicate scale isn't
   justified). Only "build?" carries gradient-main, via the same
   background-clip:text pattern used by hero/quote/feature.

   Description: Figma's Body/primary token (16px/26px/-0.4px) is an exact
   match for the existing .body-subtitle utility — reused as-is, with a
   445px wrap width per the Figma frame (narrower than the 680px column).

   Button: .btn.btn-primary reused directly — its existing hover glow
   effect (foundations.css) already matches the Figma "Gradients, Hover"
   button variant. Label reuses .body-small-feature, an exact match for
   Figma's Label/secondary token (14px/22px/-0.4px), same pairing already
   used by .nav-cta.

   Animation: per spec, only the primary button's hover/focus state
   triggers anything (see the .cta-logo-mark note above and in main.js) —
   nothing else in this section animates.
   -------------------------------------------------------------------------- */

.cta-banner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  text-align: center;
  padding-block: 64px;
}

.cta-heading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
}

.cta-headline {
  margin: 0;
  color: var(--color-text-primary);
}

.cta-headline-accent {
  background: var(--gradient-main);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.cta-description {
  margin: 0;
  max-width: 445px;
  color: var(--color-text-primary);
  /* Mobile only, per explicit request: 24px of its own padding in addition
     to .content-bound--narrow's 24px on the section itself. Reset to 0 at
     700px so tablet/desktop keep just the section's single inset. */
  padding-inline: 24px;
}

/* logo-mark: a short video (its own first frame doubles as the resting
   pose — see the HTML comment above .cta-banner), played once on hover/
   focus of the primary button by main.js. */
.cta-logo-mark {
  display: block;
  width: 56px;
  height: 32px;
  object-fit: contain;
  pointer-events: none;
}

.cta-mobile-dock {
  display: none;
}

@media (max-width: 699px) {
  #cta-primary-btn {
    display: none;
  }

  .cta-mobile-dock {
    display: block;
    width: 100%;
    height: 96px;
  }
}

@media (min-width: 700px) {
  .cta-banner {
    padding-block: 96px;
  }

  .cta-description {
    padding-inline: 0;
  }
}

@media (min-width: 901px) {
  .cta-banner {
    padding-block: 128px;
  }
}

/* --------------------------------------------------------------------------
   FOOTER

   Spec pulled from Figma (node 3077:6273, "footer", desktop 1512 frame
   only — no separate tablet/mobile pass, so this reuses the site's
   existing responsive tokens/breakpoints rather than measured specs, same
   as .cta-banner).

   Horizontal bound: 181px side margin at the 1512px reference frame is
   exactly (1512-1150)/2 — the standard .content-bound zone already used by
   nav/hero/quote, not a wide or narrow variant.

   Surface: a near-transparent white (1% opacity) with a 12px backdrop
   blur and a top border.neutral divider — distinct from .bg-mask (70%
   opacity / 24px blur, used by the sticky nav), so implemented as its own
   rule rather than reusing that class.

   Links: "Pricing"/"Developers"/"Blog"/"Terms & Privacy" all use Figma's
   Label/secondary token, an exact match for the existing .nav-link +
   .body-small-feature pairing already used in the nav bar — reused as-is.

   Legal text: Caption/secondary token (12px/400/-0.2px/20px) is an exact
   match for the existing .caption-footer utility (named for exactly this
   use) — reused as-is.

   Social icons (LinkedIn, Bluesky): omitted — see the HTML comment above
   .footer-social. .footer-social itself is still styled/sized so the
   layout won't shift once the real icon buttons are dropped in.
   -------------------------------------------------------------------------- */

/* FIX 2026-08-11: .content-bound applies its own max-width + centering
   margin directly to the element it's on — so putting it straight on
   <footer> (which also carries the border/background/blur) capped the
   border-top at the content width instead of running full-width like the
   nav bar's own bottom border does. Same fix as nav: keep the border and
   surface (background/blur) on the full-width outer element, and move
   .content-bound onto a dedicated inner wrapper (.footer-inner) that
   only handles the horizontal bound + the actual content layout — see
   .site-header/.nav-bar for the identical split. */
.site-footer {
  box-sizing: border-box;
  width: 100%;
  background-color: rgba(255, 255, 255, 0.01);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--color-border-neutral);
}

.footer-inner {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding-block: 32px;
}

.footer-header {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
}

.footer-logo {
  display: block;
  flex-shrink: 0;
  line-height: 0;
}

.footer-logo img {
  display: block;
  width: 107px;
  height: 28px;
}

.footer-links {
  display: flex;
  align-items: center;
  gap: 24px;
}

.footer-bottom {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.footer-legal {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.footer-legal p {
  margin: 0;
  color: var(--color-text-primary);
}

.footer-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.footer-social {
  display: flex;
  align-items: center;
  gap: 0;
}

.footer-social .icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 8px;
  overflow: hidden;
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
}

.footer-social .icon-button-glyph {
  display: block;
  width: 20px;
  height: 20px;
  background-color: var(--color-nav-link);
  transition: background-color 0.2s ease;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.footer-social .icon-button:hover .icon-button-glyph,
.footer-social .icon-button:focus-visible .icon-button-glyph {
  background-color: var(--color-nav-link-hover);
}

.footer-social .icon-button-glyph--linkedin {
  -webkit-mask-image: url("/assets/images/linkedin.svg");
  mask-image: url("/assets/images/linkedin.svg");
}

.footer-social .icon-button-glyph--bluesky {
  -webkit-mask-image: url("/assets/images/bluesky.svg");
  mask-image: url("/assets/images/bluesky.svg");
}

@media (min-width: 700px) {
  .footer-header {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

@media (min-width: 901px) {
  .footer-inner {
    padding-block: 64px;
  }

  .footer-bottom {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
  }

  .footer-legal {
    max-width: 563px;
  }

  .footer-meta {
    flex-shrink: 0;
  }
}
