/* ═══════════════════════════════════════════════════════════════════════════
   rzv.studio — menu.css  (THE HEADER — the burger + the panel, EVERY width)

   Haseeb: "Header on mobile change to just modern hamburger top right only.
   Beautiful mobile menu." Then, later: "Header on desktop and mobile.: pearl
   top left. Logo middle. Menu hamburger right." — the collapse this file used
   to gate at ≤760px (base.css:651, where --header-h drops to 62px and the
   glass goes opaque) now runs unconditionally: .nav-center, its sliding
   .nav-pill, .nav-meta and the motion switch are gone at every width, and the
   burger + panel are the one nav control everywhere. Only the panel's OWN
   layout rules stay behind the @media pair further down — the collapse itself
   does not.

   ── THE NO-JS CONTRACT ────────────────────────────────────────────────────
   Everything that collapses the header is gated on `html.menu-ready`, which
   menu.js adds at PARSE TIME and removes again if its own init throws. So
   with JS off, 404'd, or broken:
     · .burger stays display:none — a dead control never appears
     · .nav-center and .nav-meta stay exactly as they are today, at any width
     · .mnav is a closed disclosure nobody can open, and nothing links to it
   i.e. every page keeps the cramped-but-complete five-link header it has now.
   Nothing here is hidden in CSS waiting for JS to reveal it — the panel's
   hidden state is a closed disclosure's resting state, opened by the READER,
   not by a script running on load.

   TRAP 1 (base.css:11): no backdrop-filter anywhere in this file. .site-header
   carries its own progressive blur at every width now (base.css's own
   ::before, not this file's problem); the panel here is a genuinely opaque
   --bg ground with a painted wash and holds no mix-blend-mode child, so the
   iOS black box cannot happen here regardless.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── SCROLL LOCK — THE CLASS STAYS ON BODY, THE OVERFLOW MOVES TO THE ROOT ─
   THE SAME CLASS as services.css, deliberately — there must be exactly one
   scroll lock on this site, and it is re-declared here because services.css
   only ships on index.html while the menu ships on all 27 pages. menu.js takes
   the lock only if nothing else holds it and releases only what it took, so
   the services modal and the menu can never both own it.

   WHAT CHANGED, AND WHY IT IS THE SAME CHANGE base.css MADE TO body.loading:
   `body.svc-lock{overflow:hidden}` was both harmful and inert.

   · HARMFUL — an overflow on one axis computes the other to `auto`, so body
     became a scroll container and every position:sticky descendant started
     sticking to BODY's scrollport instead of the viewport's. Open the menu
     over #constellation and the venn unpins behind the panel for as long as
     it is open. Same mechanism as the body overflow-x bug (base.css's note
     above html's rule), same fix.
   · INERT — a body overflow is propagated to the viewport ONLY when the root's
     own overflow is `visible` (CSS Overflow 3 §3.3), and html carries
     overflow-x:hidden here, so it never was. The page went on scrolling
     behind an open menu. The sticky breakage is the proof: a propagated value
     leaves the source element `visible`, so body could only have become a
     scroll container by NOT being propagated.

   On the root both reverse: the viewport genuinely stops scrolling, and the
   root is treated as `visible` rather than becoming a scroll container, so
   sticky descendants keep resolving against the viewport and stay pinned.
   (A reading of the spec — no browser was available to this pass.)

   `:has()` rather than a class on html so the class stays exactly where
   menu.js, services.js and their two `contains('svc-lock')` guards already
   write and read it. One flag, no second state to desync.

   services.css's svc-lock and work.css's wm-lock were moved to the root the
   same way in the same pass (Fable, after Lane B's handoff), so the
   neutralising `body.svc-lock{overflow:visible}` that briefly lived under this
   rule is gone and nothing here depends on stylesheet order any more.

   THE GUTTER IS STILL PAID BACK IN JS, and only now is that payback correct:
   menu.js and services.js measure `innerWidth - documentElement.clientWidth`
   and write it as a body padding-right. With the lock inert the scrollbar
   never went away, so that padding was a ~15px squeeze with nothing to
   compensate for; with the lock working, it is exactly the gutter the
   retiring scrollbar gives up. Nothing in the JS changes. ── */
html:has(body.svc-lock){overflow:hidden}

/* ═══ THE BURGER ════════════════════════════════════════════════════════
   Two lines that MORPH into a close mark — they converge to the centre and
   then rotate, and on the way back they un-rotate and then separate. No
   cross-fade, no second icon: the same two bars are the menu mark and the
   close mark. Pure CSS, so it answers the tap on a page where GSAP never
   loaded.

   The asymmetry (top 20px, bottom 13px, both left-aligned) is what stops it
   reading as a generic three-line glyph; the width closes to 20px as the bars
   converge, so the X is symmetric.

   TIMING — the two halves are sequenced by transition-delay, not by JS:
     opening   lines converge 0.24s var(--ease), THEN bars rotate 0.38s
               var(--ease-spring) at +0.18s — the overshoot lands the X
     closing   bars un-rotate 0.26s var(--ease) immediately, THEN the lines
               separate 0.30s var(--ease) at +0.18s
   ═══ */
.burger{
  display:none;                      /* ← the no-JS default. See the contract. */
  position:relative;flex:none;
  width:42px;height:42px;padding:0;
  align-items:center;justify-content:center;
  border-radius:50%;
  background:var(--surface);border:1px solid var(--border);
  color:var(--text);cursor:pointer;-webkit-tap-highlight-color:transparent;
  transition:background .3s var(--ease),border-color .3s var(--ease);
}
.burger:hover{background:var(--surface-2);border-color:var(--border-hi)}
.burger:focus-visible{outline:2px solid var(--a2);outline-offset:3px}
.burger-box{position:relative;display:block;width:20px;height:20px}
.burger-line{
  position:absolute;left:0;top:50%;height:1.5px;margin-top:-0.75px;
  transition:transform .30s var(--ease) .18s,width .30s var(--ease) .18s;
}
.burger-line.b1{width:20px;transform:translateY(-4px)}
.burger-line.b2{width:13px;transform:translateY(4px)}
.burger-bar{
  display:block;width:100%;height:100%;border-radius:2px;
  background:currentColor;transform-origin:50% 50%;
  transition:transform .26s var(--ease);
}
html.mnav-open .burger{background:var(--surface-hi);border-color:var(--border-hi)}
html.mnav-open .burger-line{
  transform:translateY(0);width:20px;
  transition:transform .24s var(--ease),width .24s var(--ease);
}
html.mnav-open .burger-line.b1 .burger-bar{transform:rotate(45deg)}
html.mnav-open .burger-line.b2 .burger-bar{transform:rotate(-45deg)}
html.mnav-open .burger-bar{transition:transform .38s var(--ease-spring) .18s}

/* ═══ THE PANEL ═════════════════════════════════════════════════════════
   Full screen, on the site's own dark ground, UNDER the header (z 480 against
   the header's 500) so the brand and the burger stay put and visible while it
   is open — the menu opens beneath a header that never moves, rather than
   replacing it.

   The ground's fade is owned by CSS (the .open class); the CONTENT cascade is
   owned by menu.js/GSAP. That split is what makes the no-GSAP path complete:
   drop the script's half and the panel still opens, it simply arrives all at
   once. ═══ */
.mnav{
  position:fixed;top:0;left:0;right:0;height:calc(var(--vh,1vh) * 100);z-index:480;   /* --vh, not inset:0: immune to the toolbar */
  display:flex;flex-direction:column;
  overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
  /* THE GROUND IS NOT HERE ANY MORE — it is .mnav-ground, so the circular
     reveal can clip it without clipping the words standing on it.
     --mnav-x/--mnav-y are the burger click's viewport coordinates, written by
     menu.js; --mnav-r is the circle's radius, tweened by menu.js from 0 to the
     far-corner distance. All three have defaults that mean "no reveal", so a
     panel opened with no GSAP is simply a full ground. */
  --mnav-x:50%; --mnav-y:0%; --mnav-r:150%;
  /* The pearl marker's footprint, and the distance the rows step right to make
     room for it. One pair of numbers, read by both. */
  --mnav-pearl:clamp(16px,4.6vw,22px);
  --mnav-shift:calc(var(--mnav-pearl) + clamp(14px,3.4vw,20px));
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .30s var(--ease),visibility 0s linear .30s;
}
.mnav.open{opacity:1;visibility:visible;pointer-events:auto;transition-delay:0s,0s}
.mnav:focus{outline:none}

/* ── THE GROUND, AND THE CIRCLE IT ARRIVES IN ──────────────────────────────
   position:fixed, not absolute: .mnav is a scroll container, so an inset:0
   absolute child would only cover the scrollport at scrollTop 0 and the ground
   would run out from under a scrolled list. .mnav carries no transform, so
   fixed resolves against the viewport and the ground is always the viewport.

   clip-path circle() from the click point. The defaults on .mnav make it
   150% — i.e. no clip at all — so with GSAP absent, reduced motion on, or the
   panel opened from the keyboard the ground is simply there, whole, on the
   frame .open lands. Only menu.js's tween ever makes it smaller. */
.mnav-ground{
  position:fixed;inset:0;z-index:0;pointer-events:none;
  background:var(--bg);
  /* The page's own aura, brought inside — two very soft ellipses off the corners,
     painted (no blur filter, no blend group). Aurora tokens only. 9 Sep: the .au
     element, the dark wash and the pulse are OUT — "revert menu to how it was
     before - this is making it crash and tweak out". */
  background-image:
    radial-gradient(ellipse 620px 420px at 88% 6%, rgba(79,70,229,0.14), transparent 62%),
    radial-gradient(ellipse 560px 420px at 4% 96%, rgba(167,139,250,0.10), transparent 64%);
  clip-path:circle(var(--mnav-r) at var(--mnav-x) var(--mnav-y));
}
.mnav-inner{
  position:relative;z-index:1;                /* above .mnav-ground, and the pearl's offsetParent */
  flex:1 0 auto;display:flex;flex-direction:column;
  width:100%;max-width:var(--wrap);margin:0 auto;
  padding:calc(var(--header-h) + 34px) var(--gutter)
          calc(26px + env(safe-area-inset-bottom,0px));
}

.mnav-list{list-style:none;margin:0;padding:0}
.mnav-item{position:relative}
/* The mask the line rides out from behind. The negative margin pays back the
   descender room the padding buys, so the rows keep their true rhythm and
   "Journey" still has a tail. */
.mnav-mask{display:block;overflow:hidden;padding-bottom:0.10em;margin-bottom:-0.10em}
.mnav-link{
  display:block;padding:14px 0;text-decoration:none;color:var(--text);
  -webkit-tap-highlight-color:transparent;
  transform:translateX(0);
  transition:transform .55s var(--ease);
}
/* ── THE STEP RIGHT ───────────────────────────────────────────────────────
   Deliberately on .mnav-link and not .mnav-line: the open cascade tweens
   yPercent on .mnav-line, and two owners on one transform is the one way this
   breaks. The link is otherwise untouched by JS, so the two compose.

   The current page sits stepped at rest — that is what "active" means here and
   it is the row the pearl is parked on. The moment the pointer (or the
   keyboard) designates a different row, the current row steps back: exactly
   one row is ever forward, because there is exactly one marker.

   Hover is gated on a fine pointer. On a phone there is no hover to reveal
   anything, so the current row keeps its step, the pearl keeps its seat, and
   the press feedback stays the opacity dip below. */
.mnav-link.is-current{transform:translateX(var(--mnav-shift))}
@media(hover:hover) and (pointer:fine){
  .mnav-link:hover{transform:translateX(var(--mnav-shift))}
  .mnav-list:hover .mnav-link.is-current:not(:hover){transform:translateX(0)}
}
.mnav-link:focus-visible{transform:translateX(var(--mnav-shift))}
.mnav-list:has(.mnav-link:focus-visible) .mnav-link.is-current:not(:focus-visible){transform:translateX(0)}
/* Full --text at rest, NOT --muted: this is a touch surface with no hover
   state to reveal anything, so a dim resting colour would simply be five dim
   links. The press feedback is an opacity dip instead. */
.mnav-line{
  display:flex;align-items:baseline;gap:16px;
  font-size:clamp(34px,11.5vw,54px);font-weight:300;letter-spacing:-0.04em;
  line-height:1.02;color:var(--text);
  transition:opacity .35s var(--ease);
}
.mnav-link:active .mnav-line{opacity:0.55;transition-duration:.08s}
.mnav-link:focus-visible{outline:2px solid var(--a2);outline-offset:6px;border-radius:6px}
.mnav-rule{display:block;height:1px;background:var(--border);transform-origin:0 50%}

/* ── THE PEARL MARKER ─────────────────────────────────────────────────────
   ONE element for the whole list, not one per row — it is a marker, and there
   is one of it. It travels between rows on hover/focus and parks on the
   current page at rest; with no row to name it scales to nothing.

   The MATERIAL is base.css's .pearl, composed and not copied — the markup is
   class="mnav-pearl pearl" and everything below is layout: the size the .pearl
   is parameterised by, the corner radius that makes it a rounded square rather
   than the house disc, and where it sits. Nothing about the iridescence,
   the bloom or the drift is restated here.

   Position is menu.js's: it writes y (the row's centre, measured against
   .mnav-inner) and scale. No transform transition on purpose — GSAP owns this
   transform frame by frame, and .pearl keeps transform out of its own
   transition list for the same reason (base.css trap 2). With no GSAP it snaps
   to the current row, which is the no-GSAP contract everywhere else here. */
.mnav-pearl{
  --pearl-size:var(--mnav-pearl);
  position:absolute;left:var(--gutter);top:0;
  margin-top:calc(var(--mnav-pearl) * -0.5);
  /* 9 Sep, Haseeb: "rotate pearl by 45deg less rounded" — a diamond, the shape the
     venn's core takes (constellation.js rotates the core pearl 45° too). The radius
     drops so the corners read; the rotation lives here, and menu.js's y/scale tween
     writes translate/scale only, which composes with it rather than replacing it. */
  border-radius:calc(var(--mnav-pearl) * 0.20);
  rotate:45deg;
  opacity:0;                                      /* menu.js seats it on open */
  pointer-events:none;z-index:2;
}

.mnav-foot{
  margin-top:auto;padding-top:34px;
  display:flex;flex-direction:column;gap:20px;
}
/* 10 Sep: the motion switch is gone from the panel, and the socials are marks
   rather than words — the footer's own icons, at the footer's own size. */
.mnav-socials{display:flex;flex-wrap:wrap;gap:10px;margin-left:calc(var(--mnav-pearl) * -0.6)}
.mnav-social{
  font-family:var(--mono);font-size:var(--fs-label-m);font-weight:700;letter-spacing:0.16em;
  text-transform:uppercase;color:var(--muted);text-decoration:none;
  transition:color .3s var(--ease);
}
.mnav-social:hover,.mnav-social:focus-visible{color:var(--text)}
.mnav-social--ico{width:42px;height:42px;padding:0;display:inline-flex;align-items:center;
  justify-content:center;border-radius:50%;color:var(--muted)}
.mnav-ico{width:17px;height:17px;fill:currentColor;display:block}
.mnav-social--ico:hover,.mnav-social--ico:focus-visible{color:var(--text)}

/* ═══ THE COLLAPSE — every width now, not gated on base.css's breakpoint ═══
   .menu-ready is menu.js saying "I parsed, I am wired". Everything that TAKES
   SOMETHING AWAY from the header hangs off it, so a page without a working
   menu.js loses nothing — see the no-JS contract above. */
html.menu-ready .nav-center,
html.menu-ready .nav-meta{display:none}
html.menu-ready .burger{display:inline-flex}

@media(max-width:760px){
  /* base.css:697 drops the wordmark ≤460px because the nav was crowding it.
     It is not any more — THE BRAND STAYS, per the brief. */
  html.menu-ready .brand-mark{display:inline-block}
}

/* ── DESKTOP: THE LIST SITS IN THE MIDDLE ─────────────────────────────────
   Auto margins on the list, and the foot gives up its own margin-top:auto —
   the free space is then split evenly above and below the words instead of
   being dumped between them and the footer. The phone keeps today's layout
   (list under the header, foot pinned to the bottom), which is right for a
   thumb: the words stay in the top half of a tall screen. */
@media(min-width:761px){
  .mnav-list{margin-block:auto}
  .mnav-foot{margin-top:0}
}

/* Freeze, don't remove (base.css trap 3). The global reduced-motion block
   already collapses every duration here to ~0; this only makes sure the
   burger's sequencing delays go with them, so the mark snaps rather than
   waiting 0.18s to do nothing. */
@media(prefers-reduced-motion:reduce){
  .burger-line,.burger-bar,html.mnav-open .burger-line,html.mnav-open .burger-bar{
    transition-delay:0s!important;
  }
}
html.rzv-no-motion .burger-line,
html.rzv-no-motion .burger-bar,
html.rzv-no-motion.mnav-open .burger-line,
html.rzv-no-motion.mnav-open .burger-bar{transition-delay:0s}

/* ── "MENU =" ── the 2 Sep reference: the control is the word in the house
   overline voice with the two-bar glyph after it as an equals sign. No box,
   no fill, no border — text and a mark. The morph to a close mark is
   unchanged (the bars are the same two elements). */
.burger{width:auto;height:auto;padding:0;border:0;background:none;border-radius:0;
  align-items:center;gap:10px}   /* display stays with html.menu-ready — a dead control never appears */
.burger:hover{background:none;border-color:transparent}
.burger-label{color:var(--text);letter-spacing:0.22em;margin:0;transition:color .3s var(--ease)}   /* solid white, 2 Sep */
.burger:hover .burger-label{color:var(--text)}
.burger-box{width:14px;height:12px}
.burger-line.b1{width:14px;transform:translateY(-3px)}
.burger-line.b2{width:14px;transform:translateY(3px)}
