/* ============================================================
   11-footer — site footer
   Namespace: .site-footer

   Structural idea: the logo's ripple doesn't stop at the logo. A field of
   concentric hairlines is centred on the wordmark's dot and dissipates
   across the whole section, so the brand mark and the footer are the same
   drawing at two scales. Visible at every width, not a corner ornament.
   ============================================================ */

.site-footer {
  /* 44px minimum hit area, composed from the space scale */
  --fx-hit: calc(var(--s-10) + var(--s-1));
  /* 24px rendered logo box — the same width/height the header gives its SVG */
  --fx-mark: var(--s-6);
  /* one number for the newsletter card, used by the cap and the grid track */
  --fx-form: calc(var(--s-40) * 2 + var(--s-24));   /* 416px */
  /* the section's own vertical inset — one definition, used by the padding
     and by the ripple origin so the two can never drift apart */
  --fx-pad: calc(var(--section-y) - var(--s-6));

  /* ---- ripple field ---- */
  --fx-rip-gap: calc(var(--s-20) + var(--s-2));   /* 88px between rings */
  --fx-rip-fade: calc(var(--s-40) * 6);           /* rings dissolve by 960px */
  --fx-rip-line: color-mix(in srgb, var(--accent) 15%, transparent);
  /* Origin = the logo dot, derived from the real layout, not eyeballed.
     x: container edge + gutter − the mark's optical pull + the SVG's cx (5.5/28).
     y: the inner's top padding + half the mark — exact because the lockup is
     block-level flex (no half-leading) and the mark is its tallest item. */
  --fx-rip-x: calc(
    max(0px, (100% - var(--maxw-wide)) / 2) + var(--gutter)
    - var(--s-1) * 0.7 + var(--fx-mark) * 0.196
  );
  --fx-rip-y: calc(var(--fx-pad) + var(--fx-mark) / 2);

  /* rebase the shared focus ring onto this section's background */
  --focus-ring: 0 0 0 2px var(--bg-alt), 0 0 0 4px var(--violet-600);

  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--bg-alt);
  color: var(--text);
}

/* Two layers: the rings, then a wash of the page background painted over them
   that thickens outward, so the ripple dies away instead of tiling forever. */
.site-footer__ripple {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    radial-gradient(
      circle at var(--fx-rip-x) var(--fx-rip-y),
      transparent 0,
      color-mix(in srgb, var(--bg-alt) 58%, transparent) calc(var(--fx-rip-fade) * 0.55),
      var(--bg-alt) var(--fx-rip-fade)
    ),
    repeating-radial-gradient(
      circle at var(--fx-rip-x) var(--fx-rip-y),
      transparent 0 calc(var(--fx-rip-gap) - 1px),
      var(--fx-rip-line) calc(var(--fx-rip-gap) - 1px) var(--fx-rip-gap)
    );
}

.site-footer__inner {
  padding-block: var(--fx-pad) var(--s-10);
}

/* ------------------------------------------------------------
   Top band — brand lockup + email capture
   ------------------------------------------------------------ */
.site-footer__top {
  display: grid;
  gap: var(--s-12);
  padding-bottom: var(--s-14);
  border-bottom: 1px solid var(--border);
}

/* Same basis as the capture card rather than a ch measure, so the two blocks
   stay the same width through the font swap instead of only after it. */
.site-footer__brand {
  max-width: var(--fx-form);
}

/* Block-level flex, not inline-flex: an inline lockup inherits the parent's
   1.65 half-leading, which would push the mark's centre off the ripple origin
   by a font-metric-dependent amount. The box is the mark's own 24px; the 44px
   target is a pseudo overlay so the wordmark isn't floating in 10px of slack. */
.site-footer__lockup {
  position: relative;
  display: flex;
  width: fit-content;
  align-items: center;
  gap: var(--s-2);
  padding-right: var(--s-2);
  color: var(--text);
  border-radius: var(--r-sm);
}
.site-footer__lockup::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 50%;
  height: var(--fx-hit);
  transform: translateY(-50%);
}

/* Negative side bearing copied from .site-header__mark (00-header.css): the
   mark's leftmost ink is the dot at cx=5.5 in a 28-unit box, so without the
   pull the lockup measures flush with the column headings below it but reads
   3px indented. */
.site-footer__mark {
  display: inline-flex;
  margin-left: calc(var(--s-1) * -0.7);
  color: var(--accent);
  transition: transform var(--dur-slow) var(--ease-expo);
}

.site-footer__arc,
.site-footer__dot {
  transform-origin: 5.5px 14px;
  transition: transform var(--dur-slow) var(--ease-expo), opacity var(--dur-slow) var(--ease-expo);
}
.site-footer__arc--1 { opacity: 0.95; }
.site-footer__arc--2 { opacity: 0.62; }
.site-footer__arc--3 { opacity: 0.3; }

.site-footer__lockup:hover .site-footer__arc--1 { opacity: 1; }
.site-footer__lockup:hover .site-footer__arc--2 { opacity: 0.82; }
.site-footer__lockup:hover .site-footer__arc--3 { opacity: 0.5; }

/* Three values copied verbatim from .site-header__word (00-header.css) —
   optical size, tracking and the quarter-step lift that answers the two
   descenders in "Ripple". Header and footer must be the same object. */
.site-footer__wordmark {
  font-family: var(--font-display);
  font-size: calc(var(--fs-h4) * 1.06);
  font-weight: var(--fw-bold);
  letter-spacing: calc(var(--tracking-tight) * 1.6);
  line-height: 1;
  transform: translateY(calc(var(--s-1) * -0.25));
}

.site-footer__positioning {
  margin-top: var(--s-4);
  font-size: var(--fs-sm);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
  text-wrap: pretty;
}

/* ---------- Email capture ----------
   A real surface, so the drop sits on the ripple rather than in it. */
.site-footer__form {
  /* the card is white, so the ring's inner halo has to be too */
  --focus-ring: 0 0 0 2px var(--surface), 0 0 0 4px var(--violet-600);

  max-width: var(--fx-form);
  padding: var(--s-6);
  background: var(--surface);
  border-radius: var(--r-xl);
  box-shadow: inset 0 0 0 1px var(--border), var(--sh-sm);
}

.site-footer__form-title {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
  line-height: 1;
  margin-bottom: var(--s-4);
}

.site-footer__field {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--s-2);
}

/* 14rem basis: wide enough that the address stays readable, and it forces the
   button onto its own row before the field is squeezed under ~380px. */
.site-footer__input {
  flex: 1 1 14rem;
  min-width: 0;
  min-height: var(--fx-hit);
  padding: var(--s-3) var(--s-5);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text);
  background: var(--bg-alt);
  border: 0;
  border-radius: var(--r-pill);
  box-shadow: inset 0 0 0 1px var(--border-strong);
  transition: box-shadow var(--dur) var(--ease-out);
}
.site-footer__input::placeholder { color: var(--text-muted); }
.site-footer__input:not(:read-only):hover { box-shadow: inset 0 0 0 1px var(--ink-300); }
/* Spent, not faded. The JS keeps both controls focusable (readOnly +
   aria-disabled, never disabled) so the next Tab continues into the footer —
   which means dimming the row would take the focus ring and the button label
   below contrast on a control that is still in the tab order, and the 1.4.3
   exemption for inactive controls wouldn't cover it. The success pill carries
   the state; the field just goes quiet at full contrast. */
.site-footer__input:read-only {
  color: var(--text-muted);
  box-shadow: inset 0 0 0 1px var(--border);
  cursor: default;
}
.site-footer__input:focus-visible {
  box-shadow: var(--focus-ring);
  border-radius: var(--r-pill);
}
.site-footer__field.is-invalid .site-footer__input {
  box-shadow: inset 0 0 0 1px var(--coral-500);
}
/* Compose, never replace — the JS focuses the field as it marks it invalid,
   so the red border has to survive the ring. */
.site-footer__field.is-invalid .site-footer__input:focus-visible {
  box-shadow: inset 0 0 0 1px var(--coral-500), var(--focus-ring);
}

/* Once wrapped it owns the row; beside the field it stays its own width. */
.site-footer__submit { flex: 1 1 auto; }

.site-footer__msg {
  margin-top: var(--s-3);
  /* two lines reserved: idle hint, error and success pill all fit, so no state
     change ever moves the content below it */
  min-height: calc(var(--fs-xs) * var(--lh-normal) * 2);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
  color: var(--text-muted);
  transition: color var(--dur) var(--ease-out);
}
.site-footer__msg.is-error { color: color-mix(in srgb, var(--coral-600) 76%, var(--ink-900)); }
.site-footer__msg.is-success {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-pill);
  font-weight: var(--fw-semibold);
  color: color-mix(in srgb, var(--mint-500) 55%, var(--ink-900));
  background: var(--mint-100);
}
.site-footer__msg.is-success::before {
  content: "";
  flex: none;
  width: var(--s-4);
  height: var(--s-4);
  background: currentColor;
  /* check glyph, self-contained illustration */
  clip-path: polygon(41% 71%, 84% 22%, 93% 31%, 42% 88%, 8% 55%, 17% 46%);
}

/* ------------------------------------------------------------
   Link groups
   ------------------------------------------------------------ */
.site-footer__nav {
  padding-block: var(--s-14);
  border-bottom: 1px solid var(--border);
}

/* One column on the narrowest phones: two 130px columns wrap the longest
   labels and put the two stacks out of rhythm with each other. */
.site-footer__groups {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-8);
}

.site-footer__group-title {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  line-height: 1;
  color: var(--text);
  margin-bottom: var(--s-3);
}

.site-footer__links {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-2);
}

/* Box shrink-wraps the text so the focus ring is crisp; the 44px target is a
   transparent overlay that lives inside the row gap. */
.site-footer__link {
  position: relative;
  display: inline-block;
  padding-block: var(--s-2);
  padding-right: var(--s-1);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-muted);
  border-radius: var(--r-xs);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: transparent;
  text-underline-offset: 0.24em;
  transition: color var(--dur-fast) var(--ease-out),
              text-decoration-color var(--dur-fast) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
.site-footer__link::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 50%;
  height: var(--fx-hit);
  transform: translateY(-50%);
}
.site-footer__link:hover {
  color: var(--text);
  text-decoration-color: color-mix(in srgb, currentColor 42%, transparent);
}
.site-footer__link:focus-visible { color: var(--text); }

/* ------------------------------------------------------------
   Bottom bar
   ------------------------------------------------------------ */
.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5) var(--s-8);
  padding-top: var(--s-8);
}

.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-5);
}

.site-footer__copy {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  white-space: nowrap;
}

.site-footer__legal-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-5);
}

.site-footer__link--legal { font-size: var(--fs-xs); }

.site-footer__end {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-5);
}

.site-footer__social {
  display: flex;
  align-items: center;
  gap: var(--s-1);
}

.site-footer__social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--fx-hit);
  height: var(--fx-hit);
  border-radius: var(--r-md);
  color: var(--text-muted);
  transition: color var(--dur-fast) var(--ease-out),
              background-color var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out),
              box-shadow var(--dur) var(--ease-out);
}
/* 24px in a 24 viewBox — glyph strokes land at their authored 1.6px, matching
   the ripple mark's hairlines instead of shrinking to 1.33px */
.site-footer__social-link svg {
  width: var(--s-6);
  height: var(--s-6);
}
.site-footer__social-link:hover {
  color: var(--accent);
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--border), var(--sh-sm);
}

.site-footer__totop {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--fx-hit);
  padding-inline: var(--s-5);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  background: var(--surface);
  border-radius: var(--r-pill);
  box-shadow: inset 0 0 0 1px var(--border), var(--sh-xs);
  transition: color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
.site-footer__totop svg {
  width: var(--s-4);
  height: var(--s-4);
  transition: transform var(--dur) var(--ease-out);
}
.site-footer__totop:hover {
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border-strong), var(--sh-sm);
}

/* ------------------------------------------------------------
   Focus
   Elements that carry their own box-shadow — including one set only on
   :hover, which out-specifies the global (0,1,0) :focus-visible — would
   otherwise swallow the shared ring. Restate it, and keep each control's
   own radius while focused.
   ------------------------------------------------------------ */
.site-footer__submit:focus-visible,
.site-footer__totop:focus-visible {
  box-shadow: var(--focus-ring);
  border-radius: var(--r-pill);
}
.site-footer__social-link:focus-visible {
  box-shadow: var(--focus-ring);
  border-radius: var(--r-md);
}
.site-footer__lockup:focus-visible { border-radius: var(--r-sm); }

/* ------------------------------------------------------------
   Motion — every decorative offset lives here, so reduced motion gets it
   by omission rather than by out-specifying itself later.
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: no-preference) {
  .site-footer__lockup:hover .site-footer__mark { transform: translateX(-1px); }
  .site-footer__lockup:hover .site-footer__arc--1 { transform: translateX(0.6px); }
  .site-footer__lockup:hover .site-footer__arc--2 { transform: translateX(1.4px); }
  .site-footer__lockup:hover .site-footer__arc--3 { transform: translateX(2.2px); }
  .site-footer__link:hover { transform: translateX(2px); }
  .site-footer__social-link:hover { transform: translateY(-2px); }
  .site-footer__totop:hover svg { transform: translateY(-2px); }
  .site-footer__totop:active { transform: scale(0.985); }
}

/* ------------------------------------------------------------
   Breakpoints
   ------------------------------------------------------------ */

/* Large phones — two columns. 26rem is the first width where the longest
   label ("Repurposing guide", 135px with its hit padding) clears a half
   column with room to spare, so the swap-in font can't force a wrap. */
@media (min-width: 26rem) {
  .site-footer__groups {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-10) var(--s-8);
  }
}

/* Tablet up — the whole footer goes wide at one breakpoint: four columns and
   the brand beside the capture card, so the top band can't sit half-empty
   above a full-width row. */
@media (min-width: 48rem) {
  .site-footer__inner { padding-block: var(--fx-pad) var(--s-12); }
  .site-footer__top {
    grid-template-columns: minmax(0, 1fr) minmax(0, var(--fx-form));
    align-items: start;
    gap: var(--s-10);
    /* matches the nav's stepped-up padding so the hairline sits centred */
    padding-bottom: var(--s-16);
  }
  .site-footer__form { justify-self: end; width: 100%; }
  .site-footer__nav { padding-block: var(--s-16); }
  .site-footer__groups {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--s-10) var(--s-8);
  }
}

/* Desktop — room to open the gutter between brand and card */
@media (min-width: 60rem) {
  .site-footer__top { gap: var(--s-16); }
}
