/* ============================================================================
   SWORT navigation disclosure
   ---------------------------------------------------------------------------
   Adds a dropdown to each of the six primary categories on desktop and an
   accordion inside the existing hamburger panel on mobile. It deliberately
   changes NOTHING about the header itself: the bar height, the wordmark, the
   Start a Project CTA, the type, the colours and the spacing are all untouched.
   Everything here is either inside the panel or is the small caret beside a
   label, and the caret is sized so the row height cannot grow.

   Three navigation families exist (.nav-links, .p-nav-links, .l-nav-links) and
   they are styled differently by design. The disclosure is written once against
   the shared .nav-item / .nav-menu classes so the three keep their own look and
   gain the same behaviour, rather than each growing its own copy.
   ========================================================================= */

/* ── Desktop ─────────────────────────────────────────────────────────────── */

.nav-item { position: relative; }

/* inline-flex, not the default inline layout.

   The generated markup puts the link and the caret on separate lines, and in an
   inline formatting context that newline is a rendered space. Measured, it cost
   about 10px per category on top of the caret's own width: the bar grew 189px
   when the carets themselves only account for 96px. A flex container has no
   text nodes between its items, so the whitespace stops counting. */
.nav-has-menu {
  display: inline-flex;
  align-items: center;
}

/* The caret is a separate control from the link so a keyboard user can open the
   panel without following the link. It is removed from the tab order because
   the link beside it already carries aria-expanded and opens on focus; two tab
   stops per category would double the length of the bar for no gain.

   Kept deliberately small: the brief asks for the header's spacing to be
   preserved as closely as possible, and every pixel here widens the bar six
   times over. */
/* The colour is stated, not inherited.

   `color: currentColor` on a <button> resolves against its PARENT, which is the
   <li>, not the sibling link. On the five legal pages that parent computes to
   #181818, so the caret rendered near-black on a near-black bar at 1.15:1 and
   was effectively invisible. Caught by the rendered button-state gate, which is
   exactly the kind of thing it exists for. An explicit neutral clears AA on all
   three bars, which are all dark. */
.nav-caret {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  width: 12px; height: 14px; margin-left: 4px; padding: 0;
  background: none; border: 0; cursor: pointer;
  color: #9a9a9a;
}
@media (hover: hover) and (pointer: fine) {
  .nav-caret:hover { color: #ffffff; }
}
.nav-caret:focus-visible {
  outline: 2px solid var(--focus-ring, #ffffff);
  outline-offset: 1px;
  color: #ffffff;
}
.nav-caret svg { width: 8px; height: 5px; display: block; }

/* The caret rotates rather than the whole control moving, so nothing reflows. */
.nav-has-menu .nav-caret svg {
  transition: transform 200ms cubic-bezier(.2, .8, .2, 1);
}
.nav-has-menu[data-open="true"] .nav-caret svg { transform: rotate(180deg); }
.nav-has-menu[data-open="true"] .nav-caret { opacity: 1; }

/* ── The mega panel ───────────────────────────────────────────────────────
   Full width, flush under the header, no gap and no floating box.

   POSITIONING. The panel is fixed rather than absolute inside its <li>. An
   absolutely-positioned child of a list item is bounded by the bar's padding
   and by whatever the ancestor chain does with overflow, so it can only ever be
   a small box under one label. Fixed with left:0/right:0 spans the viewport
   regardless of where in the bar the item sits.

   --nav-h is written once by nav-menu.js from the header's measured height, and
   again on resize. The three navigation families have different bar heights
   (85, 77 and 72px measured), so a hardcoded offset would leave a seam on two
   of them. */
.nav-menu {
  position: fixed;
  top: var(--nav-h, 77px);
  left: 0;
  right: 0;
  z-index: 120;
  background: #060606;
  border-top: 1px solid #1e1e1e;
  border-bottom: 1px solid #1e1e1e;
  /* Reveal downward and fade, 200ms on one curve. No scale, no shadow, no
     height animation: animating height would reflow the panel's own contents
     while the reader is trying to aim at them. */
  opacity: 0;
  transform: translateY(-14px);
  transition: opacity 200ms cubic-bezier(.2, .8, .2, 1),
              transform 200ms cubic-bezier(.2, .8, .2, 1);
  pointer-events: none;
}
.nav-has-menu[data-open="true"] > .nav-menu {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* The scrim. One element per page, created by the script, so the dimming does
   not multiply with the number of categories. */
/* BELOW the header, deliberately, and this is the whole trick.

   The panel declares z-index 120, but the header that contains it declares 100
   and so opens a stacking context: the panel's 120 only orders it against its
   siblings INSIDE the header, not against the page. A scrim appended to <body>
   at 110 therefore sat above the panel at the root level. It painted over the
   panel, dimming the surface that is supposed to be lit, and it took every
   click: elementsFromPoint inside an open panel returned .nav-scrim first, so
   choosing a destination hit the scrim and closed the menu instead.

   Nothing in the computed-style assertions could see this, because computed
   styles and synthetic pointer events both skip hit testing.

   Sitting at 99 puts the scrim under the header and everything the header
   contains, so the header and its panel are the only lit surface and the page
   behind dims. Consent surfaces live at 999 and above and are unaffected. */
.nav-scrim {
  position: fixed;
  inset: var(--nav-h, 77px) 0 0 0;
  z-index: 99;
  background: rgba(0, 0, 0, 0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms cubic-bezier(.2, .8, .2, 1);
}
.nav-scrim[data-on="true"] { opacity: 1; pointer-events: auto; }

/* While the layer is up the header adopts the panel's surface.

   Measured, the four header treatments are rgba(0,0,0,0) on the homepage,
   rgba(5,5,5,0.94) on page and legal routes and rgba(0,0,0,0.95) on careers,
   none of them the panel's #060606. The panel already meets the header exactly,
   but a seam was still visible because the two were different colours, and on
   the homepage the starfield showed through the header while the panel below it
   was solid. Matching them for the duration makes the whole thing read as one
   surface, which is the point of a full-bleed menu.

   Selector weight is deliberate: (0,2,0) clears .p-nav and .l-nav, and
   body > nav at (0,1,1) clears the nav {} rule inside careers.html. */
@media (min-width: 901px) {
  :root[data-nav-open="true"] #main-nav,
  :root[data-nav-open="true"] .p-nav,
  :root[data-nav-open="true"] .l-nav,
  :root[data-nav-open="true"] body > nav {
    background: #060606;
    backdrop-filter: none;
    transition: background 200ms cubic-bezier(.2, .8, .2, 1);
  }
}

.nav-menu-inner {
  /* Tracks the header rather than the page measure. The header is full-bleed
     with 40px gutters, so a centred 1180px inner column started the panel's
     first word 130px inside the wordmark above it: measured, the logo sat at
     40px and COMPANY at 170px. Nothing in a full-bleed menu should be indented
     from the bar that opened it. Losing the cap also returns enough width for
     three link columns, which the narrower measure could not hold. */
  max-width: none;
  margin: 0;
  /* Measured 49px top / 55px bottom before this, against a 28-40px brief. The
     panel is out of flow and content-driven, so its own padding is the only
     thing that makes it taller than it needs to be. */
  padding: clamp(1.75rem, 2.2vw, 2.25rem) 40px clamp(1.9rem, 2.4vw, 2.5rem);
  display: grid;
  /* Left column carries the category and its description; the right area holds
     the destination columns. */
  grid-template-columns: minmax(0, 30fr) minmax(0, 70fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}

/* ── Left column ── */
.nav-lede-title {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(30px, 3vw, 40px);
  line-height: 1.02;
  letter-spacing: 0.03em;
  color: #f4f4f4;
  margin: 0 0 14px;
  text-transform: uppercase;
}
.nav-lede-desc {
  font-family: 'DM Mono', ui-monospace, monospace;
  font-size: 12px;
  line-height: 1.85;
  color: #8f8f8f;
  margin: 0 0 20px;
  max-inline-size: 34ch;
}
/* SELECTOR STRENGTH IS LOAD-BEARING HERE, not stylistic.

   .nav-links a sets colour at (0,2,0) and .nav-lede-link is (0,1,0), so the
   panel's own declaration lost and the Overview link rendered in the bar's
   muted link colour instead: measured rgb(141,146,151), which is a COOL grey
   with a ten-point channel spread, inside a surface specified as true neutral.
   The three navigation families each set that colour differently, so the panel
   would also have looked different on legal pages than on the homepage.

   Qualifying with the element takes it to (0,2,1), which beats all three
   families without touching any of them, so the panel controls its own
   appearance wherever it is mounted. */
.nav-menu a.nav-lede-link,
.nav-lede-link {
  display: inline-block;
  font-family: 'DM Mono', ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #ededed;
  padding-bottom: 4px;
  border-bottom: 1px solid #3a3a3a;
  transition: color 180ms cubic-bezier(.2, .8, .2, 1),
              border-color 180ms cubic-bezier(.2, .8, .2, 1);
}
@media (hover: hover) and (pointer: fine) {
  .nav-menu a.nav-lede-link:hover { color: #ffffff; border-bottom-color: #ffffff; }
}
.nav-lede-link:focus-visible { outline: 2px solid var(--focus-ring, #fff); outline-offset: 3px; }

/* ── Right area ── */
.nav-cols {
  display: grid;
  /* Capped tracks, packed from the left. With 1fr tracks a two-group category
     stretched its columns to 338px while a three-group one sat at 216px, so the
     panel visibly changed shape as the reader moved along the bar. A maximum
     keeps every category on the same measure and lets the surplus fall as
     trailing space, which is what makes the set read as one system. */
  grid-template-columns: repeat(auto-fit, minmax(180px, 232px));
  justify-content: start;
  gap: clamp(1.5rem, 2.6vw, 2.75rem);
}
.nav-col { min-width: 0; }
.nav-eyebrow {
  font-family: 'DM Mono', ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: #858585;
  margin: 0 0 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid #1e1e1e;
}
.nav-sub { list-style: none; margin: 0; padding: 0; }
.nav-sub li + li { margin-top: 2px; }

/* Editorial text links, not buttons: no fill, no border, no radius. */
.nav-menu a.nav-sub-link,
.nav-sub-link {
  display: block;
  padding: 10px 0;
  transition: color 180ms cubic-bezier(.2, .8, .2, 1);
}
.nav-sub-name {
  display: block;
  font-family: 'DM Mono', ui-monospace, monospace;
  font-size: 14px;
  letter-spacing: 0.02em;
  line-height: 1.4;
  color: #ededed;
  transition: color 180ms cubic-bezier(.2, .8, .2, 1);
}
.nav-sub-note {
  display: block;
  font-family: 'DM Mono', ui-monospace, monospace;
  font-size: 10.5px;
  line-height: 1.6;
  color: #8a8a8a;
  margin-top: 4px;
  max-inline-size: 34ch;
}
@media (hover: hover) and (pointer: fine) {
  .nav-sub-link:hover .nav-sub-name { color: #ffffff; }
  .nav-sub-link:hover .nav-sub-note { color: #9a9a9a; }
}
.nav-sub-link:focus-visible {
  outline: 2px solid var(--focus-ring, #ffffff);
  outline-offset: 2px;
}
.nav-sub-link:active .nav-sub-name { color: #c9c9c9; }

/* The open category is marked with a restrained underline rather than a colour
   change, so the six labels keep one weight. */
.nav-has-menu > .nav-top { position: relative; }
.nav-has-menu > .nav-top::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: -6px; height: 1px;
  /* Explicit, not currentColor. currentColor on .nav-top resolves against the
     bar's own link colour, which is rgb(141,146,151) on the homepage: a cool
     grey, and this rule marks the open category on a surface specified as true
     neutral. It also ought to read brighter than the labels it distinguishes,
     which inheriting the muted colour cannot do. */
  background: #f0f0f0;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 200ms cubic-bezier(.2, .8, .2, 1);
}
.nav-has-menu[data-open="true"] > .nav-top::after { transform: scaleX(1); }
/* Qualified with the element to clear .nav-links a at (0,2,0). */
.nav-has-menu[data-open="true"] > a.nav-top { color: #ffffff; }

/* Below the desktop breakpoint the bar is hidden by each page's own rules, so
   the panel and the scrim must not be reachable either. */
@media (max-width: 900px) {
  .nav-menu, .nav-caret, .nav-scrim { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .nav-menu, .nav-scrim { transition: opacity 1ms linear; }
  .nav-menu { transform: none; }
  .nav-has-menu[data-open="true"] > .nav-menu { transform: none; }
  .nav-has-menu .nav-caret svg,
  .nav-has-menu > .nav-top::after { transition: none; }
}

/* ── Mobile accordion, inside the existing hamburger panel ───────────────── */

/* The row keeps the link and the toggle side by side so the label still
   navigates and the toggle only discloses. Nothing about the panel, the close
   control, the scroll lock or focus restoration is touched. */
.mnav-row { display: flex; align-items: stretch; gap: 0; }
.mnav-row .mnav-link { flex: 1 1 auto; min-width: 0; }

.mnav-toggle {
  flex: 0 0 auto;
  width: 56px;
  min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; cursor: pointer;
  color: #8a8a8a;
  transition: color 180ms cubic-bezier(.2, .8, .2, 1);
}
.mnav-toggle svg {
  width: 11px; height: 7px; display: block;
  transition: transform 200ms cubic-bezier(.2, .8, .2, 1);
}
.mnav-toggle[aria-expanded="true"] { color: #ffffff; }
.mnav-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
.mnav-toggle:focus-visible {
  outline: 2px solid var(--focus-ring, #ffffff);
  outline-offset: -2px;
}

.mnav-sub {
  list-style: none;
  margin: 0 0 6px;
  padding: 2px 0 6px 0;
}
.mnav-sub-link {
  display: block;
  padding: 11px 0 11px 34px;
  font-family: 'DM Mono', ui-monospace, monospace;
  font-size: 12px;
  letter-spacing: 0.06em;
  color: #8f8f8f;
  /* Wraps rather than overflowing: a long label must never widen the panel and
     introduce horizontal scrolling inside the menu. */
  white-space: normal;
  overflow-wrap: anywhere;
  transition: color 180ms cubic-bezier(.2, .8, .2, 1);
}
.mnav-sub-link:active { color: #ffffff; }
.mnav-sub-link:focus-visible {
  outline: 2px solid var(--focus-ring, #ffffff);
  outline-offset: -2px;
  color: #ffffff;
}
@media (hover: hover) and (pointer: fine) {
  .mnav-sub-link:hover { color: #ffffff; }
}

@media (prefers-reduced-motion: reduce) {
  .mnav-toggle svg { transition: none; }
}

/* The caret is a control and should acknowledge a press like every other one.
   It was the last labelled interactive element with no pressed state. */
.nav-caret:active { color: #ffffff; }


/* ══ Reveal sequence ══════════════════════════════════════════════════════
   Information initialising, not a typewriter performance. The panel fades and
   drops 14px; the eyebrow arrives first, the category description types, and
   the destinations follow on a short stagger. The whole thing lands inside
   ~440ms.

   Two rules govern the implementation:

   Links are never gated on the animation. They animate opacity and transform
   only, they keep pointer-events throughout, and their text is real DOM text at
   all times, so a reader who knows where they are going can click during the
   reveal and hit the destination.

   Nothing here runs on a loop. Each element carries a delay derived from its
   index and the browser runs one animation per element, once. There is no
   rAF loop and no timer per frame.
   ═══════════════════════════════════════════════════════════════════════ */

@keyframes nav-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Resting state while the panel is closed, so the sequence has somewhere to
   come from and no element flashes at full opacity on the first frame. */
.nav-menu .nav-eyebrow,
.nav-menu .nav-sub > li,
.nav-menu .nav-lede-link { opacity: 0; }

.nav-has-menu[data-open="true"] .nav-eyebrow {
  animation: nav-rise 200ms cubic-bezier(.2, .8, .2, 1) 60ms both;
}
/* Columns start together; entries inside a column stagger. --i is written by
   the generator, so the cascade does the sequencing and JS does not have to
   touch each node. */
.nav-has-menu[data-open="true"] .nav-sub > li {
  animation: nav-rise 190ms cubic-bezier(.2, .8, .2, 1) both;
  animation-delay: calc(150ms + var(--i, 0) * 45ms);
}
.nav-has-menu[data-open="true"] .nav-lede-link {
  animation: nav-rise 200ms cubic-bezier(.2, .8, .2, 1) 260ms both;
}

/* The typing cursor exists only while characters are being written. It is a
   pseudo-element on the description, so it cannot end up in the text content
   or be read out. */
.nav-lede-desc[data-typing="true"]::after {
  content: '';
  display: inline-block;
  width: 7px;
  height: 1em;
  margin-left: 2px;
  background: #8f8f8f;
  vertical-align: -0.15em;
  animation: nav-caret-blink 620ms steps(1, end) infinite;
}
@keyframes nav-caret-blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* The description holds its full height from the start so that typing into it
   cannot reflow the columns beside it mid-sequence. */
.nav-lede-desc { min-height: calc(4 * 1.85em); }

@media (prefers-reduced-motion: reduce) {
  /* Everything present immediately: no stagger, no typing, no cursor. The JS
     reads the same query and writes the full string in one assignment. */
  .nav-menu .nav-eyebrow,
  .nav-menu .nav-sub > li,
  .nav-menu .nav-lede-link { opacity: 1; }
  .nav-has-menu[data-open="true"] .nav-eyebrow,
  .nav-has-menu[data-open="true"] .nav-sub > li,
  .nav-has-menu[data-open="true"] .nav-lede-link { animation: none; opacity: 1; }
  .nav-lede-desc[data-typing="true"]::after { display: none; animation: none; }
}
