/* ============================================================
   00-header — announcement bar + sticky nav + mobile menu
   ============================================================ */

/* ---------- Announcement bar ----------
   Committed colour, not a wash: a near-black ink bar carrying a
   full-strength brand gradient in the 1px edge and a solid violet tag.
   Blending --grad-brand into the field at low opacity only muddies it. */
.announce {
  --tap: calc(var(--s-10) + var(--s-1)); /* 44px touch target */
  /* The global ring's inner halo is var(--bg) sand — invisible here. */
  --focus-ring: 0 0 0 2px var(--ink-950), 0 0 0 4px var(--violet-300);
  position: relative;
  z-index: var(--z-sticky);
  background: var(--grad-ink);
  color: var(--text-on-dark);
}

.announce::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  background: var(--grad-brand);
}

.announce__inner {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--s-2);
}

/* The whole announcement is one control — a 44px row, not a bare inline
   run of text with a small link tacked on the end. The row never wraps:
   the tag must not be able to orphan itself onto a line above the sentence
   it labels, so the sentence is the only thing allowed to reflow. */
.announce__link {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--s-1) var(--s-2);
  min-height: var(--tap);
  padding-block: var(--s-2);
  padding-inline: var(--s-3);
  margin-inline-start: calc(var(--s-3) * -1);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
  letter-spacing: var(--tracking-normal);
  color: color-mix(in srgb, var(--text-on-dark) 88%, transparent);
  transition: background-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}

.announce__link:hover {
  background: color-mix(in srgb, var(--text-on-dark) 10%, transparent);
  color: var(--text-on-dark);
}

/* The only shrinkable item in the row — at 320px it takes two lines while
   the tag and the arrow hold their place beside it. */
.announce__copy {
  flex: 1 1 auto;
  min-width: 0;
}

.announce__tag {
  flex: none;
  /* Uppercase tracking adds 0.13em after the final glyph; shave half of it
     off the trailing padding so the label sits optically centred. */
  padding: var(--s-1) calc(var(--s-2) - 0.065em) var(--s-1) var(--s-2);
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  line-height: var(--lh-normal);
}

.announce__cue {
  flex: none;
  font-weight: var(--fw-semibold);
  color: var(--text-on-dark);
}

.announce__arrow {
  flex: none;
  color: var(--violet-300);
  transition: transform var(--dur) var(--ease-out);
}

.announce__link:hover .announce__arrow { transform: translate(2px, -2px); }

.announce__dismiss {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  margin-right: calc(var(--s-3) * -1);
  border-radius: var(--r-pill);
  color: var(--text-on-dark-muted);
  transition: color var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out),
    transform var(--dur) var(--ease-out);
}

.announce__dismiss:hover {
  color: var(--text-on-dark);
  background: color-mix(in srgb, var(--text-on-dark) 12%, transparent);
}

.announce__dismiss:active { transform: scale(0.92); }

/* Phantom left column mirrors the dismiss button so the row centres truly. */
@media (min-width: 720px) {
  .announce__inner { grid-template-columns: var(--tap) 1fr var(--tap); }
  .announce__link {
    grid-column: 2;
    justify-self: center;
    margin-inline-start: 0;
  }
  .announce__copy { flex: 0 1 auto; }
  .announce__dismiss { grid-column: 3; }
}

/* Below 560px the row itself is the target, so the cue label is redundant —
   drop it and let the arrow carry the affordance. */
@media (max-width: 560px) {
  .announce__cue { display: none; }
}

/* ---------- Sticky header ----------
   The header reserves a constant height in flow, so nothing below it ever
   moves. The "shrink on scroll" is done with transform only: the painted
   chrome slides up by --hdr-lift (its hairline edge becomes the new bottom)
   and the bar contents slide up by half that, which reads exactly like
   reduced vertical padding. The chrome box clips its own overshoot, so the
   header itself keeps overflow visible (fixed menu panel, focus rings). */
.site-header {
  --tap: calc(var(--s-10) + var(--s-1));
  --hdr-h: calc(var(--tap) + var(--s-5) * 2);
  --hdr-lift: var(--s-4);
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  height: var(--hdr-h);
}

.site-header__chrome {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

/* The filter is declared at rest so it animates instead of snapping, and so
   the compositing layer exists from first paint (no one-frame flicker).
   The wash stays opaque enough to carry 4.5:1 text on its own: a header
   cannot assume what scrolls beneath it. */
.site-header__chrome::before {
  content: "";
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  -webkit-backdrop-filter: blur(0) saturate(100%);
  backdrop-filter: blur(0) saturate(100%);
  transition: transform var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out),
    box-shadow var(--dur) var(--ease-out),
    -webkit-backdrop-filter var(--dur) var(--ease-out),
    backdrop-filter var(--dur) var(--ease-out);
}

.site-header.is-scrolled .site-header__chrome::before {
  transform: translateY(calc(var(--hdr-lift) * -1));
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  -webkit-backdrop-filter: blur(var(--s-3)) saturate(180%);
  backdrop-filter: blur(var(--s-3)) saturate(180%);
  box-shadow: inset 0 -1px 0 var(--border), var(--sh-sm);
}

/* ---------- Dark-section inversion ----------
   #workflow and #cta are near-black and over a thousand pixels tall. A sand
   bar floating over them reads as a foreign slab and drags muted nav text
   toward 3.5:1 no matter how the wash is tuned. So the header watches what
   sits under its own band and re-skins to ink for the length of it — one
   crossfade in, one out, and every colour in the bar is picked for the
   field it is actually standing on. */
.site-header.is-over-dark {
  /* The global ring haloes in sand, which vanishes on ink. */
  --focus-ring: 0 0 0 2px var(--ink-950), 0 0 0 4px var(--violet-300);
}

.site-header.is-over-dark .site-header__chrome::before {
  background: color-mix(in srgb, var(--bg-deep) 88%, transparent);
  box-shadow: inset 0 -1px 0 var(--border-on-dark);
}

.site-header.is-over-dark.is-scrolled .site-header__chrome::before {
  background: color-mix(in srgb, var(--bg-deep) 92%, transparent);
  box-shadow: inset 0 -1px 0 var(--border-on-dark), var(--sh-md);
}

.site-header.is-over-dark .site-header__logo { color: var(--text-on-dark); }
.site-header.is-over-dark .site-header__mark { color: var(--violet-400); }
.site-header.is-over-dark .site-header__burger { color: var(--text-on-dark); }

.site-header.is-over-dark .site-header__link,
.site-header.is-over-dark .site-header__login { color: var(--text-on-dark-muted); }

.site-header.is-over-dark .site-header__link:hover,
.site-header.is-over-dark .site-header__link[aria-current="location"],
.site-header.is-over-dark .site-header__login:hover { color: var(--text-on-dark); }

.site-header.is-over-dark .site-header__burger:hover {
  background: color-mix(in srgb, var(--text-on-dark) 10%, transparent);
}

/* Safari < 18 ships only the prefixed property; the unprefixed test alone
   would strip the blur on every iOS device where it works best. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header__chrome::before { background: var(--bg); }
  .site-header.is-over-dark .site-header__chrome::before { background: var(--bg-deep); }
}

/* Equal 1fr flanks put the nav on the true centre of the bar, not on the
   centre of a track squeezed between a short logo and a long CTA group. */
.site-header__bar {
  position: relative;
  height: var(--hdr-h);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--s-4);
  transition: transform var(--dur) var(--ease-out);
}

.site-header.is-scrolled .site-header__bar {
  transform: translateY(calc(var(--hdr-lift) / -2));
}

/* ---------- Logo lockup ---------- */
.site-header__logo {
  display: inline-flex;
  justify-self: start;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding-right: var(--s-2);
  color: var(--text);
  transition: color var(--dur) var(--ease-out);
}

/* The mark's leftmost ink is the dot at cx=5.5 in a 28-unit box, so its
   glyph starts ~2px inside its own bounds while the CTA pill on the far
   side ends hard. Pull the side bearing out so the lockup optically lines
   up with the actions instead of merely measuring the same. */
.site-header__mark {
  display: inline-flex;
  margin-left: calc(var(--s-1) * -0.7);
  color: var(--accent);
  transition: transform var(--dur-slow) var(--ease-expo), color var(--dur) var(--ease-out);
}

.site-header__arc,
.site-header__dot {
  transform-origin: 5.5px 14px;
  transition: transform var(--dur-slow) var(--ease-expo), opacity var(--dur-slow) var(--ease-expo);
}

.site-header__arc--1 { opacity: 0.95; }
.site-header__arc--2 { opacity: 0.62; }
.site-header__arc--3 { opacity: 0.3; }

.site-header__logo:hover .site-header__mark { transform: translateX(-1px); }
.site-header__logo:hover .site-header__arc--1 { transform: translateX(0.6px); opacity: 1; }
.site-header__logo:hover .site-header__arc--2 { transform: translateX(1.4px); opacity: 0.82; }
.site-header__logo:hover .site-header__arc--3 { transform: translateX(2.2px); opacity: 0.5; }

/* Set as a logotype, not as bold body text: one notch of optical size over
   --fs-h4 and tracking pulled well past the shared tight token, which is
   calibrated for the 100px end of the scale. The two descenders in "Ripple"
   drag its ink mass low against a mark centred on its own axis, so the word
   is lifted a quarter step to sit on the same optical line. */
.site-header__word {
  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));
}

/* ---------- Primary nav ----------
   One indicator for the whole nav: it travels between items on hover, focus
   and scroll rather than fading in under each one independently. */
.site-header__nav {
  position: relative;
  justify-self: center;
}

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

.site-header__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding-inline: var(--s-3);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-normal);
  transition: color var(--dur) var(--ease-out);
}

.site-header__link:hover,
.site-header__link[aria-current="location"] { color: var(--text); }

.site-header__ind {
  position: absolute;
  left: 0;
  bottom: var(--s-2);
  width: 100%;
  /* The one deliberate hairline in the bar besides the 1px chrome edges. */
  height: calc(var(--s-1) / 2);
  border-radius: var(--r-pill);
  background: var(--grad-brand);
  transform-origin: 0 50%;
  transform: translateX(var(--ind-x, 0px)) scaleX(var(--ind-s, 0));
  transition: transform var(--dur) var(--ease-expo);
  pointer-events: none;
  will-change: transform;
}

/* ---------- Actions ---------- */
.site-header__actions {
  display: flex;
  justify-self: end;
  align-items: center;
  gap: var(--s-4);
}

.site-header__login {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding-inline: var(--s-3);
  margin-right: calc(var(--s-3) * -1);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  transition: color var(--dur) var(--ease-out);
}

.site-header__login:hover { color: var(--text); }

/* .btn--sm's compact padding is right; its 13px is not — the conversion
   control should not carry the smallest type in the bar. */
.site-header__cta {
  min-height: var(--tap);
  padding-inline: var(--s-5);
  font-size: var(--fs-sm);
}

/* tokens.css sets the ring on :focus-visible, but .btn--primary / .btn--secondary
   re-declare box-shadow at equal specificity and later in source order, so the ring
   has to be restated here alongside each button's own shadow. */
.site-header__cta:focus-visible,
.site-header__panel-btn:focus-visible {
  border-radius: var(--r-pill);
  box-shadow: var(--focus-ring);
}
.site-header__cta:focus-visible,
.site-header__panel-btn.btn--primary:focus-visible {
  box-shadow: var(--focus-ring), var(--sh-accent);
}
.site-header__panel-btn.btn--secondary:focus-visible {
  box-shadow: var(--focus-ring), inset 0 0 0 1px var(--border-strong);
}

/* ---------- Hamburger ---------- */
.site-header__burger {
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  justify-self: end;
  margin-right: calc(var(--s-2) * -1);
  border-radius: var(--r-sm);
  color: var(--text);
  transition: background-color var(--dur) var(--ease-out), transform var(--dur) var(--ease-out),
    color var(--dur) var(--ease-out);
}

.site-header__burger:hover { background: var(--surface-2); }
.site-header__burger:active { transform: scale(0.94); }

/* ---------- Mobile panel ---------- */
.site-header__panel {
  --panel-measure: calc(var(--s-40) * 3);
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  /* The nav is the scroller, not the panel — see .site-header__panel-nav. */
  overflow: hidden;
  overscroll-behavior: contain;
  background: var(--bg);
  opacity: 0;
  transform: translateY(calc(var(--s-2) * -1));
  /* Never swallow clicks while fading out. */
  pointer-events: none;
  transition: opacity var(--dur) var(--ease-out), transform var(--dur-slow) var(--ease-expo);
}

.site-header__panel[hidden] { display: none; }

.site-header__panel::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 50%;
  background: var(--grad-brand-soft);
  opacity: 0.75;
  -webkit-mask-image: linear-gradient(to bottom, var(--text) 0%, transparent 100%);
  mask-image: linear-gradient(to bottom, var(--text) 0%, transparent 100%);
  pointer-events: none;
}

.site-header__panel.is-open {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.site-header__panel-top,
.site-header__panel-nav,
.site-header__panel-actions {
  position: relative;
}

.site-header__panel-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding-block: var(--s-4);
  flex: none;
}

.site-header__logo--static { cursor: default; }

.site-header__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  margin-right: calc(var(--s-2) * -1);
  border-radius: var(--r-pill);
  color: var(--text);
  background: color-mix(in srgb, var(--surface) 70%, transparent);
  box-shadow: inset 0 0 0 1px var(--border);
  transition: background-color var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}

.site-header__close:hover { background: var(--surface); }
.site-header__close:active { transform: scale(0.94); }

/* On a landscape phone the five rows plus both CTAs are taller than the
   viewport. When the whole panel scrolled, "Log in" / "Start free" sat below
   the fold with nothing peeking over the edge to say so. Scrolling the nav
   instead pins the action row on screen at every height, and `safe center`
   keeps the list centred when it fits without stranding the first row above
   the scroll origin when it doesn't. */
.site-header__panel-nav {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  align-items: safe center;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-block: var(--s-6);
}

/* Capped and centred: the wide page container would stretch these rows to
   ~800px on a tablet and the menu would read as an empty table. */
.site-header__panel-list {
  width: 100%;
  max-width: var(--panel-measure);
  margin-inline: auto;
  counter-reset: navnum;
  display: flex;
  flex-direction: column;
}

.site-header__panel-list li + li { border-top: 1px solid var(--border); }

.site-header__panel-link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  min-height: calc(var(--tap) + var(--s-4));
  padding-block: var(--s-3);
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight);
  line-height: var(--lh-snug);
  color: var(--text);
  opacity: 0;
  transform: translateY(var(--s-3));
  transition: opacity var(--dur-slow) var(--ease-expo), transform var(--dur-slow) var(--ease-expo),
    color var(--dur) var(--ease-out);
  transition-delay: calc(var(--dur-fast) / 3 * var(--i, 0));
}

/* Generated content IS part of the accessible name, so the numeral needs the
   `/ ""` alt-text form to stay purely visual — same as .voices__quote p::before.
   Without it every row announces as "Features 02". */
.site-header__panel-link::after {
  counter-increment: navnum;
  content: counter(navnum, decimal-leading-zero) / "";
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-regular);
  letter-spacing: var(--tracking-normal);
  color: var(--text-muted);
}

.site-header__panel.is-open .site-header__panel-link {
  opacity: 1;
  transform: none;
}

.site-header__panel-link:hover { color: var(--accent); }

.site-header__panel-actions {
  flex: none;
  padding-block: var(--s-6) var(--s-8);
  opacity: 0;
  transform: translateY(var(--s-3));
  transition: opacity var(--dur-slow) var(--ease-expo), transform var(--dur-slow) var(--ease-expo);
  transition-delay: calc(var(--dur-fast) / 3 * var(--i, 0));
}

.site-header__panel-btns {
  display: grid;
  gap: var(--s-3);
  max-width: var(--panel-measure);
  margin-inline: auto;
}

.site-header__panel.is-open .site-header__panel-actions {
  opacity: 1;
  transform: none;
}

.site-header__panel-btn {
  width: 100%;
  min-height: var(--tap);
}

.site-header__panel-note {
  margin-top: var(--s-2);
  text-align: center;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* ---------- Breakpoints ----------
   900.02px is the single boundary, written as an exact complement so no
   sub-pixel width (display scaling, browser zoom) can fall between the two
   states and leave a full-screen panel open over a visible desktop nav. */
@media not all and (min-width: 900.02px) {
  .site-header__nav,
  .site-header__actions { display: none; }

  .site-header__bar { grid-template-columns: 1fr auto; }

  .site-header__burger { display: inline-flex; }
}

@media (min-width: 900.02px) {
  .site-header__panel { display: none; }
}

/* Just above the breakpoint the three tracks are tight. Tighten the nav's
   own rhythm there rather than letting a font fallback push the CTA past
   the container edge. */
@media (min-width: 900.02px) and (max-width: 1120px) {
  .site-header__link { padding-inline: var(--s-2); }
  .site-header__actions { gap: var(--s-3); }
}

/* Short viewports (landscape phones): compact the rows so four of the five fit
   before the nav has to scroll. Must sit after the base panel rules — a media
   query adds no specificity, so an earlier block would simply lose. */
@media (max-height: 30rem) {
  .site-header__panel-nav { padding-block: var(--s-3); }
  .site-header__panel-link {
    min-height: var(--tap);
    font-size: var(--fs-h4);
  }
  .site-header__panel-actions { padding-block: var(--s-4) var(--s-5); }
  /* Repeats the hero tagline; the two CTAs are what has to survive here. */
  .site-header__panel-note { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .site-header__panel,
  .site-header__panel-link,
  .site-header__panel-actions {
    opacity: 1;
    transform: none;
  }
}
