/* SYBNOC — shared multilevel navigation
   ---------------------------------------------------------------------
   One sheet for every page, homepage included, so the menu cannot drift
   between them.

   Design contract: each top-level item is a REAL LINK to a hub page, and
   the dropdown opens on :hover / :focus-within in pure CSS. JavaScript is
   only used for the mobile panel. So with scripting off, or before nav.js
   runs, every destination is still reachable — the menu degrades to plain
   links rather than becoming a dead button. */

#brandnav {
  position: sticky; top: 0; z-index: 90;
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 14px 4vw;
  background: rgba(7,10,6,.86); backdrop-filter: blur(14px);
  border-bottom: 1px solid rgba(239,243,233,.07);
}
/* the homepage nav is sticky from the first screen (the film runs beneath it) */
#brandnav.gated { opacity: 0; pointer-events: none; transform: translateY(-8px);
                  transition: opacity .4s ease, transform .4s ease; }
#brandnav.gated.on { opacity: 1; pointer-events: auto; transform: none; }

#brandnav .mark { display: flex; align-items: center; gap: 11px; color: var(--text); flex: none; }
#brandnav .logo-mark { display: block; height: 1em; width: auto; font-size: 30px; }
#brandnav .logo-word { display: block; height: 1em; width: auto; font-size: 13px; }

.nav-links { display: flex; align-items: center; gap: 6px; }
.nav-item { position: relative; }
/* nav.js wraps each section's link (and, under the burger, its toggle) in
   this row. display:contents keeps the desktop bar identical to before. */
.nav-row { display: contents; }
.nav-toggle { display: none; }
.nav-sub a.nav-hub-added { display: none; }   /* burger-only; beats .nav-sub a */
.nav-top {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 10px 13px; border-radius: 8px;
  color: var(--text-dim); text-decoration: none;
  font-family: 'IBM Plex Mono', monospace; font-size: 11px; letter-spacing: .18em;
  text-transform: uppercase; white-space: nowrap;
  transition: color .25s var(--ease), background-color .25s var(--ease);
}
.nav-top:hover, .nav-item:focus-within > .nav-top { color: var(--text); background: rgba(239,243,233,.05); }
.nav-top.on { color: var(--lime); }
.nav-top .cv { width: 12px; height: 12px; opacity: .6; transition: transform .3s var(--ease); }
.nav-item:hover .nav-top .cv, .nav-item:focus-within .nav-top .cv { transform: rotate(180deg); }

/* ── the mega panel ──────────────────────────────────────────────────
   The panel spans the whole bar rather than sitting in a small box under
   one item. .nav-item is therefore position:static, so the panel's
   containing block is #brandnav itself (sticky counts as positioned) and
   left:0/right:0 resolve to the full width of the bar. */
.nav-item { position: static; }
.nav-sub {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 5;
  background: rgba(9,13,8,.97); backdrop-filter: blur(18px);
  border-bottom: 1px solid rgba(239,243,233,.12);
  box-shadow: 0 26px 60px rgba(0,0,0,.55);
  opacity: 0; visibility: hidden; transform: translateY(-10px);
  transition: opacity .24s var(--ease), transform .3s var(--ease), visibility 0s linear .32s;
}
/* a lime hairline along the top edge ties the open panel to the bar */
.nav-sub::after { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
                  background: linear-gradient(90deg, transparent, rgba(179,210,48,.5), transparent); }
/* an invisible bridge over the bar's bottom border, or the cursor leaves
   the item on the way down and the panel closes before it is reachable */
.nav-sub::before { content: ""; position: absolute; left: 0; right: 0; top: -12px; height: 12px; }
/* open state: nav.js owns it through .is-open (hover, focus, pointer leaving
   the bar or the window, blur, scroll, Escape all handled there). The pure
   :hover / :focus-within rules apply only while the script has not run. */
.nav-item.is-open > .nav-sub,
html:not(.js-nav) .nav-item:hover > .nav-sub,
html:not(.js-nav) .nav-item:focus-within > .nav-sub {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity .24s var(--ease), transform .3s var(--ease), visibility 0s;
}
/* the opening panel always stacks above the one still fading out, whatever the DOM order */
.nav-item.is-open > .nav-sub { z-index: 6; }
.nav-item:not(.is-open) > .nav-sub { pointer-events: none; }
.nav-item.is-open > .nav-top { color: var(--text); background: rgba(239,243,233,.05); }
.nav-item.is-open .nav-top .cv { transform: rotate(180deg); }
.nav-panel { display: grid; grid-template-columns: minmax(0,.9fr) minmax(0,2.1fr);
             gap: 5vw; max-width: 1500px; margin: 0 auto; padding: 40px 4vw 44px; }
.nav-panel-lead h3, .nav-panel-lead .nav-h3 { font-family: 'Space Grotesk', sans-serif; font-size: 21px;
                     font-weight: 600; letter-spacing: -.02em; color: var(--text);
                     margin-bottom: 12px; }
.nav-panel-lead p { font-family: 'Inter', system-ui, sans-serif; font-size: 13.5px;
                    line-height: 1.7; color: var(--text-dim); text-transform: none;
                    letter-spacing: 0; max-width: 34ch; }
.nav-panel-lead .nav-head { padding: 0 0 10px; }
.nav-panel-links { display: grid; grid-template-columns: repeat(2, minmax(0,1fr));
                   gap: 4px 24px; align-content: start; }
/* a link hugs its own text: a two-line label in a row must not stretch its neighbours' hover boxes */
.nav-panel-links > a { align-self: start; }
.nav-panel-links.three { grid-template-columns: repeat(3, minmax(0,1fr)); }
.nav-sub a {
  display: flex; align-items: baseline; gap: 10px;
  padding: 11px 14px; border-radius: 9px;
  color: rgba(239,243,233,.74); text-decoration: none;
  font-family: 'Inter', system-ui, sans-serif; font-size: 14px; letter-spacing: 0;
  text-transform: none;
  transition: color .2s var(--ease), background-color .2s var(--ease);
}
.nav-sub a small { font-family: 'IBM Plex Mono', monospace; font-size: 9.5px;
                   letter-spacing: .16em; text-transform: uppercase;
                   color: var(--text-dim); }   /* was .34 alpha (2.8:1); --text-dim clears 4.5:1 */
.nav-sub a:hover, .nav-sub a:focus-visible { color: var(--text); background: rgba(179,210,48,.10); }
.nav-sub a:hover small { color: var(--lime); }
.nav-sub a.on { color: var(--lime); }
.nav-head { font-family: 'IBM Plex Mono', monospace; font-size: 9.5px;
            letter-spacing: .22em; text-transform: uppercase;
            color: var(--text-dim); }   /* was .38 alpha (3.3:1); --text-dim clears 4.5:1 */

#brandnav .navcta {
  flex: none; border: 1px solid var(--lime); border-radius: 100px;
  padding: 9px 22px; color: var(--lime); text-decoration: none;
  font-family: 'IBM Plex Mono', monospace; font-size: 11px; letter-spacing: .18em;
  text-transform: uppercase; white-space: nowrap;
  transition: background-color .25s var(--ease);
}
#brandnav .navcta:hover { background: rgba(179,210,48,.12); }

/* ── mobile ── */
.nav-burger {
  display: none; width: 48px; height: 48px; padding: 0;
  background: none; border: 1px solid rgba(239,243,233,.16); border-radius: 9px;
  color: var(--text); cursor: pointer;
}
.nav-burger svg { width: 18px; height: 18px; display: block; margin: 0 auto; }
.nav-burger .x { display: none; }
.nav-burger[aria-expanded="true"] .x { display: block; }
.nav-burger[aria-expanded="true"] .b { display: none; }

/* Six top-level items need about 1283px at the default 4vw padding, but the
   bar only collapsed at 1080 - so 1081-1282 rendered a nav that ran off the
   right edge. Tightening the side padding on mid-size screens buys ~41px,
   and the burger now takes over just below what actually fits, which closes
   the gap entirely rather than narrowing it. */
@media (max-width: 1400px) and (min-width: 1241px) {
  #brandnav { padding: 14px 2.4vw; }
}

@media (max-width: 1240px) {
  .nav-burger { display: block; }
  #brandnav { gap: 10px; padding: 12px 5vw; }
  #brandnav .navcta { display: none; }
  /* The bar carries backdrop-filter, which makes it the containing block for
     fixed-position descendants — so `inset: 62px 0 0` sized this panel against
     the 73px bar instead of the screen and it collapsed to its own padding.
     Anchoring the top to the bar's real height and giving an explicit height
     is correct whether the containing block resolves to the bar or the
     viewport, so the menu fills the screen below the bar in either case.
     dvh keeps it right while mobile browser chrome slides in and out. */
  .nav-links {
    position: fixed; top: var(--navh, 73px); left: 0; right: 0; bottom: auto; z-index: 80;
    height: calc(100vh - var(--navh, 73px));
    height: calc(100dvh - var(--navh, 73px));
    display: block; overflow-y: auto; -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain; padding: 18px 6vw 60px;
    /* fully opaque: at .98 the hero headline behind still ghosted through a
       full-height panel, and a blur costs GPU work for a surface nothing is
       visible beneath */
    background: var(--bg, #070A06);
    /* closed by default; opened by nav.js. Nothing but navigation lives
       in here, so a script failure costs the menu, never page content —
       and every destination is still linked from the footer. */
    opacity: 0; visibility: hidden; transform: translateY(-10px);
    transition: opacity .28s var(--ease), transform .3s var(--ease), visibility 0s linear .32s;
  }
  .nav-links.open { opacity: 1; visibility: visible; transform: none;
                    transition: opacity .28s var(--ease), transform .3s var(--ease), visibility 0s; }
  .nav-item { position: static; }
  .nav-top { display: flex; justify-content: space-between; align-items: center;
             width: 100%; min-height: 48px;
             padding: 15px 4px; font-size: 12px; border-radius: 0;
             border-bottom: 1px solid rgba(239,243,233,.09); }
  .nav-top .cv { display: none; }
  /* A section with sub-options: its name is the toggle (the whole row), and
     the hub page is the first link inside. The plain link stays in the
     markup for no-JS and for the desktop bar; under the burger it is hidden
     once nav.js has built the toggle (.has-sub). */
  .nav-row { display: flex; align-items: stretch; }
  .nav-row > .nav-top { flex: 1 1 auto; min-width: 0; }
  .nav-row.has-sub > .nav-top { display: none; }
  .nav-toggle { display: flex; justify-content: space-between; align-items: center; gap: 12px;
                flex: 1 1 auto; width: 100%; min-height: 48px; padding: 15px 4px;
                background: none; border: 0; border-radius: 0;
                border-bottom: 1px solid rgba(239,243,233,.09);
                font-family: 'IBM Plex Mono', monospace; font-size: 12px; letter-spacing: .18em;
                text-transform: uppercase; text-align: left;
                color: var(--text-dim); cursor: pointer; transition: color .25s var(--ease); }
  .nav-toggle.on { color: var(--lime); }                         /* the section you are in */
  .nav-item.acc-open .nav-toggle:not(.on) { color: var(--text); }
  .nav-toggle svg { flex: none; width: 14px; height: 14px; transition: transform .25s var(--ease); }
  .nav-item.acc-open .nav-toggle svg { color: var(--lime); transform: rotate(180deg); }
  /* the hub page leads its section */
  .nav-panel-links > .nav-hub { order: -1; }
  .nav-sub .nav-panel-links > a.nav-hub-added { display: flex; }
  /* collapsed only once nav.js has added .acc — without it the panels stay
     open exactly as the markup ships them */
  .nav-links.acc .nav-sub { display: none; }
  .nav-links.acc .nav-item.acc-open > .nav-sub { display: block; }
  /* submenus are simply open on mobile — an accordion would add a second
     thing that can fail for no benefit on a menu this shallow */
  .nav-sub {
    position: static; opacity: 1; visibility: visible; transform: none;
    padding: 0; margin: 0; background: none; border: 0;
    box-shadow: none; transition: none; backdrop-filter: none;
  }
  /* the panels are always open on a phone, so the desktop rule that parks
     taps on a closed dropdown has to be undone at matching specificity —
     otherwise every sub-menu link is visible but dead */
  .nav-item:not(.is-open) > .nav-sub { pointer-events: auto; }
  /* …but only while the menu is open. The rule above sets the sub-menus
     visibility:visible, which overrides the closed panel's hidden state, so
     the open section's links sat transparent over the page and caught taps
     (UX test C1). Hidden with the same .32s delay as the panel's own fade. */
  .nav-links:not(.open) .nav-sub { visibility: hidden; pointer-events: none;
                                   transition: visibility 0s linear .32s; }
  .nav-sub::before, .nav-sub::after { display: none; }
  .nav-panel { grid-template-columns: 1fr; gap: 0; padding: 6px 0 16px; max-width: none; }
  /* the descriptive column is desktop chrome; on a phone it pushes the
     actual destinations below the fold */
  .nav-panel-lead { display: none; }
  .nav-panel-links, .nav-panel-links.three { grid-template-columns: 1fr; gap: 0; }
  .nav-sub a { padding: 11px 14px; min-height: 48px; align-items: center; font-size: 14.5px; }
  .nav-mobile-cta { display: flex; align-items: center; justify-content: center;
                    min-height: 48px; margin-top: 22px; text-align: center;
                    background: var(--lime); color: #0d1600; border-radius: 100px;
                    padding: 15px 0; font-family: 'IBM Plex Mono', monospace;
                    font-size: 11.5px; letter-spacing: .18em; text-transform: uppercase;
                    text-decoration: none; }
}
@media (min-width: 1241px) { .nav-mobile-cta { display: none; } }

/* Touch pointers get 48px tap targets even in the desktop bar layout — a
   touch tablet wider than the burger breakpoint still needs finger-sized
   hits. Scoped to (pointer: coarse) so mouse-primary devices (including
   touchscreen laptops, which report a fine primary pointer) keep the
   compact bar. */
@media (pointer: coarse) {
  .nav-top { min-height: 48px; }
  .nav-sub a { min-height: 48px; align-items: center; }
}
/* the pill only exists in the desktop bar (it is display:none under the
   burger breakpoint) — enlarge it for touch only where it is shown, or the
   display override would resurrect it on phones and overflow the bar */
@media (pointer: coarse) and (min-width: 1241px) {
  #brandnav .navcta { display: inline-flex; align-items: center; min-height: 48px; }
}

@media (prefers-reduced-motion: reduce) {
  #brandnav, .nav-sub, .nav-links, .nav-top, .nav-top .cv { transition: none; }
}

/* A menu entry that leaves for the homepage is marked, so a visitor on a
   subpage knows before clicking. Cyan rather than lime keeps it distinct
   from the "you are here" state. */
.nav-sub a.home small { color: rgba(46,171,198,.9); }
.nav-sub a.home:hover small { color: var(--cyan); }

/* ── page transition ──────────────────────────────────────────────────────
   A short switching panel between pages, so a click no longer drops the
   visitor onto a bare flash - and, on the homepage, no longer onto the full
   preloader.

   The REVEAL is pure CSS, deliberately. nav.js loads at the end of body, far
   too late to hide a first paint, so the panel is painted by a class set in
   <head> and lifted by a CSS animation that runs whether or not any script
   follows. A JS failure therefore cannot trap a visitor behind it. Only the
   EXIT (covering up on a click) needs JS, and if that never runs the link
   simply navigates the ordinary way.

   Direction is continuous: leaving pushes the panel up from the bottom,
   arriving carries the same panel on up and off the top, so a navigation
   reads as one movement rather than two unrelated fades.

   Two layers, moving together:
     ::after   the panel body - a plain vertical gradient and the lit edge
     ::before  a dot field with the mark beating at its centre

   The dots are a repeating radial-gradient, masked so they dissolve toward
   the edges instead of tiling flatly to the corners. The mask sits on this
   layer only - the opaque base is on ::after, so nothing ever shows through
   to the page underneath. Where mask-image is unsupported the dots simply
   cover the full panel, which is a fair fallback rather than a broken one. */

html.pt::after {
  content: "";
  position: fixed; inset: 0; z-index: 9998;
  pointer-events: none;
  will-change: transform;
  background: linear-gradient(180deg, #0C130A 0%, #070A06 58%, #040603 100%);
  /* the lit leading edge - top edge serves both directions: it leads going
     up on arrival, and it is the edge that sweeps the page on the way out */
  box-shadow: 0 -1px 0 rgba(179,210,48,.5), 0 -20px 62px rgba(179,210,48,.09);
  animation: ptPanelUp .6s cubic-bezier(.65,0,.35,1) forwards;
}
@keyframes ptPanelUp {
  0%   { transform: translateY(0); }
  100% { transform: translateY(-100%); }
}

/* Smoothness (2026-09-24). The mark's heartbeat used to animate
   background-size and the dot drift background-position - neither can run
   on the compositor, so every frame restyled and repainted a full-screen
   layer on the main thread, the same thread that is busy loading the next
   page (or, on the homepage, decoding the film) exactly while this panel is
   up. Result: dropped frames, and a slow drift that snapped pixel to pixel.
   Now every moving part is transform-only and runs on the GPU:
     travel  -> the `translate` property   (same curve and timing as the panel)
     beat    -> the `scale` property        (mark on its own layer)
     drift   -> `transform`                 (a gentle sway of the dot field)
   Individual transform properties compose with `transform`, so travel and
   sway/beat never fight over one property. Each lifted layer also ends
   visibility:hidden, so a browser without `translate` still clears the page. */

/* dot field - oversized by one dot cell on every side so the sway never
   shows an edge; the travel offsets that extra size so it keeps pace with
   the panel exactly */
html.pt::before {
  content: "";
  position: fixed; inset: calc(var(--pt-dot) * -1); z-index: 9999;
  pointer-events: none;
  will-change: transform, translate;
  --pt-dot: 26px;
  background: radial-gradient(circle, rgba(179,210,48,.34) 0 1.4px, rgba(179,210,48,0) 1.5px)
                0 0 / var(--pt-dot) var(--pt-dot);
  -webkit-mask-image: radial-gradient(closest-side circle at 50% 46%,
                        #000 34%, rgba(0,0,0,0) 82%);
          mask-image: radial-gradient(closest-side circle at 50% 46%,
                        #000 34%, rgba(0,0,0,0) 82%);
  /* the fade is drawn at screen size, centred, so the oversizing does not
     widen the dissolve circle */
  -webkit-mask-size: calc(100% - var(--pt-dot) * 2) calc(100% - var(--pt-dot) * 2);
          mask-size: calc(100% - var(--pt-dot) * 2) calc(100% - var(--pt-dot) * 2);
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  animation: ptDotsUp .6s cubic-bezier(.65,0,.35,1) forwards,
             ptSway 5.5s ease-in-out infinite alternate;
}
@keyframes ptDotsUp {
  0%   { translate: 0 0; }
  100% { translate: 0 calc(var(--pt-dot) * 2 - 100%); visibility: hidden; }
}
/* a slow sway rather than a looping drift: a loop would jump the masked
   centre back by a whole cell every cycle; alternating never jumps */
@keyframes ptSway {
  0%   { transform: translate3d(calc(var(--pt-dot) * -.35), calc(var(--pt-dot) * -.35), 0); }
  100% { transform: translate3d(calc(var(--pt-dot) * .35),  calc(var(--pt-dot) * .35), 0); }
}

/* the mark - its own full-screen layer, placed exactly as before
   (centre / 46%), scaled about its own centre for the beat. The SVG is
   358.9 x 268.9, so its drawn height is --pt-mark x .7493. */
html.pt > body::after {
  content: "";
  position: fixed; inset: 0; z-index: 10000;
  pointer-events: none;
  will-change: translate, scale;
  --pt-mark: clamp(104px, 13vw, 156px);
  --pt-mark-h: calc(var(--pt-mark) * .7493);
  background: url("img/sybnoc-mark.svg") center 46% / var(--pt-mark) no-repeat;
  transform-origin: 50% calc((100% - var(--pt-mark-h)) * .46 + var(--pt-mark-h) / 2);
  animation: ptMarkUp .6s cubic-bezier(.65,0,.35,1) forwards,
             ptBeat 2.9s ease-in-out infinite;
}
@keyframes ptMarkUp {
  0%   { translate: 0 0; }
  100% { translate: 0 -100%; visibility: hidden; }
}

/* A heartbeat, not a throb: a hard thump, a partial fall, a softer second
   thump, then rest. At 2.9s the two thumps land inside the first second and
   the rest is pause, which is what makes it read as a heartbeat. */
@keyframes ptBeat {
  0%, 34%, 100% { scale: 1; }
  7%            { scale: 1.075; }
  15%           { scale: 1.005; }
  23%           { scale: 1.045; }
}

/* ── holding ──────────────────────────────────────────────────────────
   On any load after the first, the intro screen is skipped and this panel
   covers the wait instead. pt-hold freezes the travel at fully-covering
   while the beat and the sway keep running, so it reads as a live loading
   state rather than a stalled animation. The play-state lists are
   positional and must match each layer's animation list above. */
html.pt-hold::before        { animation-play-state: paused, running; }
html.pt-hold::after         { animation-play-state: paused; }
html.pt-hold > body::after  { animation-play-state: paused, running; }

/* Covering up on the way out. Separate names so they restart cleanly after
   the reveal set has already run to completion on this document.
   .4s matches LEAVE_MS in nav.js - change one, change both. */
html.pt-leave::after {
  pointer-events: all;   /* swallow a second click while the panel closes */
  animation: ptPanelDown .4s cubic-bezier(.65,0,.35,1) forwards;
}
@keyframes ptPanelDown {
  0%   { transform: translateY(100%); }
  100% { transform: translateY(0); }
}
html.pt-leave::before {
  animation: ptDotsDown .4s cubic-bezier(.65,0,.35,1) forwards,
             ptSway 5.5s ease-in-out infinite alternate;
}
@keyframes ptDotsDown {
  0%   { translate: 0 calc(100% - var(--pt-dot) * 2); }
  100% { translate: 0 0; }
}
html.pt-leave > body::after {
  animation: ptMarkDown .4s cubic-bezier(.65,0,.35,1) forwards,
             ptBeat 2.9s ease-in-out infinite;
}
@keyframes ptMarkDown {
  0%   { translate: 0 100%; }
  100% { translate: 0 0; }
}

/* Motion-sensitive visitors get the same panel as a plain crossfade: no
   travel, no beat, no sway. */
@media (prefers-reduced-motion: reduce) {
  html.pt::before, html.pt::after, html.pt > body::after {
    animation: ptFade .3s linear forwards;
  }
  html.pt-leave::before, html.pt-leave::after, html.pt-leave > body::after {
    animation: ptFadeIn .22s linear forwards;
  }
  @keyframes ptFade   { to { opacity: 0; visibility: hidden; } }
  @keyframes ptFadeIn { from { opacity: 0; } to { opacity: 1; } }
}

/* ── Careers ──────────────────────────────────────────────────────────
   The only top-level item that is an invitation rather than a description
   of the work, so it is marked out as a soft pill.

   Mint, not lime and not cyan. Not lime, because the Contact button beside
   it is lime and two lime pills in the same corner blunt the CTA. Not cyan,
   because inside THIS stylesheet cyan already means "Back to home" (see
   .nav-sub a.home small) - an auxiliary, backwards signal, which is the
   opposite of what a careers link is for. Mint is the midpoint of the
   logo's own lime-to-cyan gradient, so it is unarguably brand colour and
   carries no conflicting meaning. --mint is defined in the :root of both
   index.html and sub.css; nav.css inherits it, as it does --lime.

   The colour rules below are prefixed with #brandnav on purpose:
   sub.css sets `#brandnav a { color: var(--text-dim) }` at (1,0,1)
   and loads AFTER this file, so a bare .nav-top.is-careers at
   (0,2,0) lost the colour on all nineteen subpages while the
   border and background - which sub.css does not set - still won.

   The "hiring" dot is positioned OUTSIDE the box on purpose: six top-level
   items already leave only about 27px of spare nav width at 1280, so the
   whole treatment had to cost roughly two pixels. A leading inline dot
   would have cost thirteen and pushed the bar back over the edge. */
#brandnav .nav-top.is-careers {
  position: relative;
  color: var(--mint);
  border: 1px solid rgba(98,186,139,.42);   /* fallback ring, see @supports below */
  background-color: rgba(98,186,139,.085);
  padding: 9px 13px;              /* absorbs the border: same height as its siblings */
  transition: color .3s var(--ease), background-color .3s var(--ease),
              border-color .3s var(--ease);
}

/* The ring carries the logo's own gradient - lime through mint to cyan -
   which is the same range the rest and hover states already sit at either
   end of. Done with a masked pseudo-element rather than border-image,
   because border-image ignores border-radius and would square off the pill.

   The fill and the text stay SOLID colours on purpose: gradients cannot be
   transitioned, so putting the hover shift on them would make it snap. This
   way the gradient is always present and the hover still animates. */
@supports ((-webkit-mask-composite: xor) or (mask-composite: exclude)) {
  #brandnav .nav-top.is-careers { border-color: transparent; }
  #brandnav .nav-top.is-careers::before {
    content: ""; position: absolute; inset: 0;
    border-radius: inherit; padding: 1px; pointer-events: none;
    background: linear-gradient(105deg, var(--lime), var(--mint) 52%, var(--cyan));
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
            mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    opacity: .5;
    transition: opacity .3s var(--ease);
  }
  #brandnav .nav-top.is-careers:hover::before,
  #brandnav .nav-top.is-careers:focus-visible::before { opacity: 1; }
}
.nav-top.is-careers::after {
  content: ""; position: absolute; top: -3px; right: -3px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--lime); box-shadow: 0 0 0 2px rgba(7,10,6,.92);
  animation: navHire 2.4s ease-in-out infinite;
}
@keyframes navHire {
  0%, 100% { transform: scale(.72); opacity: .55; }
  50%      { transform: scale(1);   opacity: 1; }
}
/* At rest it is mint, the green from the logo gradient. On hover it moves
   to the brand cyan - the other end of that same gradient - so the whole
   interaction stays inside the logo's own two colours instead of
   introducing a third. focus-visible matches, or the state would exist for
   a mouse and not for a keyboard. */
#brandnav .nav-top.is-careers:hover,
#brandnav .nav-top.is-careers:focus-visible {
  color: var(--cyan); border-color: rgba(46,171,198,.78);
  background-color: rgba(46,171,198,.15);
}
/* on the careers page itself it turns lime like any other current item, but
   keeps the pill so it still reads as the odd one out */
#brandnav .nav-top.is-careers.on {
  color: var(--lime); border-color: rgba(179,210,48,.55);
  background-color: rgba(179,210,48,.10);
}
.nav-top.is-careers.on::after { animation: none; opacity: 1; transform: scale(1); }
@media (prefers-reduced-motion: reduce) { .nav-top.is-careers::after { animation: none; } }

/* In the burger panel every item is a full-width row, so the pill is dropped
   and the dot rides to the right edge on the existing space-between. */
@media (max-width: 1240px) {
  .nav-top.is-careers {
    border: 0; background: none; padding: 15px 4px;
    border-bottom: 1px solid rgba(239,243,233,.09);
  }
  .nav-top.is-careers::after { position: static; align-self: center; }
}


/* -- particle field background ----------------------------------------
   The same green particle field as the page-transition panel, carried
   behind every page. Identical geometry by construction: the same dot
   grid, the same 1.4px lime dot, the same slow diagonal drift. What
   changes is only what a permanent layer needs - the alpha comes down,
   because .34 is right for half a second over a covering panel and reads
   as wallpaper when it sits under real copy all day.

   Drift rides transform, not background-position. The panel animates
   background-position because it lives for half a second; a layer that
   never leaves would repaint the full viewport every frame that way.
   A transform on an over-scanned layer is composited instead, so this
   costs nothing per frame and needs no JavaScript at all.

   The overscan is what makes it seamless: the layer hangs past every
   edge, and travel is exactly one cell, so the field arrives back on
   itself with no seam and no gap swept in at the leading corner.

   z-index -1 puts it under all body content but still above the <html>
   background, which is why the ground colour moves from body to html on
   every page - a painted body would simply cover it. */
#waveBg {
  --wave-dot: 26px;
  position: fixed;
  inset: calc(var(--wave-dot) * -2);
  z-index: -1;
  pointer-events: none;
  display: block;
  background: radial-gradient(circle,
                rgba(179,210,48,.15) 0 1.4px,
                rgba(179,210,48,0)  1.5px)
              0 0 / var(--wave-dot) var(--wave-dot);
  will-change: transform;
  animation: waveDrift 5.5s linear infinite;
}

/* exactly one dot-cell of travel per cycle, so the field loops seamlessly -
   the same figure the panel's ptDrift uses, moved onto transform */
@keyframes waveDrift {
  0%   { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(var(--wave-dot), var(--wave-dot), 0); }
}

/* a still field is still the field: the texture stays, the drift stops */
@media (prefers-reduced-motion: reduce) {
  #waveBg { animation: none; }
}
/* phones: dense body copy sits directly over the field, so soften the dots
   (the review flagged the grid as slightly busy behind text on mobile) */
@media (max-width: 700px) {
  #waveBg { opacity: .66; }
}

/* -- fixed-menu chat bubble --------------------------------------------
   Built by chat.js, styled here because every page already loads this
   file. Inherits the launcher the WhatsApp FAB used - brand gradient,
   pulsing ring - so the control is recognisably the same one, and only
   what it opens has changed.

   Deliberately not WhatsApp green anywhere except the final button. The
   bubble is a SYBNOC menu that can end in WhatsApp, not a WhatsApp
   widget, and the colour should not claim otherwise. */
.bot-fab {
  position: fixed; bottom: 26px; right: 24px; z-index: 95;
  display: flex; flex-direction: column; align-items: flex-end; gap: 12px;
  opacity: 0; transform: translateY(14px); pointer-events: none;
  transition: opacity .35s var(--ease), transform .35s var(--ease);
}
.bot-fab.on { opacity: 1; transform: none; pointer-events: none; }
/* The container is 340px wide because the closed chat panel still takes up
   space above the button, so letting the container take taps blocked a large
   invisible box of the page (UX test C2). Only the button, and the panel once
   it is open (.bot-panel.open), take pointer events. */
.bot-fab.on .bot-btn { pointer-events: auto; }
/* the full-screen mobile menu owns the screen while it is open */
.nav-open .bot-fab, .nav-open .bot-fab.on { opacity: 0; pointer-events: none; }

.bot-btn {
  width: 58px; height: 58px; border-radius: 50%;
  background: linear-gradient(135deg, #B3D230 0%, #62BA8B 50%, #2EABC6 100%);
  display: flex; align-items: center; justify-content: center;
  border: none; cursor: pointer; position: relative; flex-shrink: 0;
  box-shadow: 0 6px 22px rgba(98,186,139,.42), 0 2px 10px rgba(0,0,0,.5);
  transition: transform .3s var(--ease), box-shadow .3s var(--ease);
}
.bot-btn:hover { transform: scale(1.1) translateY(-3px);
  box-shadow: 0 12px 34px rgba(98,186,139,.5), 0 4px 14px rgba(0,0,0,.55); }
.bot-btn svg { width: 28px; height: 28px; fill: #06310f; }
.bot-ring {
  position: absolute; inset: -4px; border-radius: 50%;
  border: 2px solid rgba(179,210,48,.5); animation: botRing 2.4s ease infinite;
}
@keyframes botRing {
  0%        { transform: scale(1);    opacity: 1; }
  80%, 100% { transform: scale(1.55); opacity: 0; }
}

/* -- the panel --
   Order in the DOM is panel-then-button so the launcher stays pinned to
   the corner and the panel grows upward from it. */
.bot-panel {
  order: -1;
  width: 340px; max-width: calc(100vw - 44px);
  background: rgba(10,15,10,.97);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(239,243,233,.14); border-radius: 16px;
  box-shadow: 0 18px 52px rgba(0,0,0,.6);
  display: flex; flex-direction: column; overflow: hidden;
  opacity: 0; transform: translateY(10px) scale(.94); transform-origin: bottom right;
  transition: opacity .32s var(--ease), transform .35s var(--ease);
  pointer-events: none;
}
.bot-panel.open { opacity: 1; transform: none; pointer-events: auto; }
.bot-panel:focus-visible { outline: 2px solid var(--lime); outline-offset: -2px; }

.bot-head {
  display: flex; align-items: center; gap: 11px;
  padding: 14px 16px; border-bottom: 1px solid rgba(239,243,233,.12);
  flex-shrink: 0;
}
.bot-av {
  width: 38px; height: 38px; border-radius: 50%; flex-shrink: 0;
  background: linear-gradient(135deg, #B3D230, #2EABC6);
  display: grid; place-items: center;
}
.bot-av svg { width: 20px; height: 20px; fill: #06310f; }
.bot-who { flex: 1; min-width: 0; }
.bot-who strong {
  display: block; font-family: 'Space Grotesk', sans-serif;
  font-size: 14px; font-weight: 600; color: var(--text); line-height: 1.2;
}
.bot-who span { font-size: 12px; color: var(--mint); font-weight: 500; }
.bot-x {
  background: none; border: none; cursor: pointer; flex-shrink: 0;
  color: rgba(239,243,233,.5); font-size: 15px; line-height: 1;
  padding: 4px 7px; border-radius: 6px; transition: background .18s, color .18s;
}
.bot-x:hover { background: rgba(239,243,233,.08); color: var(--text); }

/* the transcript scrolls, the header and footer do not */
.bot-log {
  padding: 16px; overflow-y: auto; max-height: min(56vh, 420px);
  display: flex; flex-direction: column; align-items: flex-start; gap: 9px;
  scrollbar-width: thin; scrollbar-color: rgba(239,243,233,.2) transparent;
}
.bot-log::-webkit-scrollbar { width: 6px; }
.bot-log::-webkit-scrollbar-thumb {
  background: rgba(239,243,233,.2); border-radius: 3px;
}

.bot-msg {
  font-size: 13.5px; line-height: 1.5; max-width: 88%;
  padding: 9px 13px; border-radius: 14px;
  animation: botIn .3s var(--ease) both;
}
@keyframes botIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.bot-msg.is-bot {
  background: rgba(239,243,233,.06); color: rgba(239,243,233,.86);
  border-bottom-left-radius: 5px;
}
.bot-msg.is-me {
  align-self: flex-end;
  background: linear-gradient(135deg, rgba(179,210,48,.22), rgba(98,186,139,.18));
  border: 1px solid rgba(179,210,48,.3);
  color: var(--text); border-bottom-right-radius: 5px;
}
/* the message that will actually be sent, shown before it is */
.bot-msg.is-quote {
  align-self: stretch; max-width: 100%;
  background: rgba(7,10,6,.6); border: 1px dashed rgba(179,210,48,.34);
  color: rgba(239,243,233,.74); font-style: italic; font-size: 13px;
}

.bot-typing { display: flex; gap: 4px; padding: 12px 13px; }
.bot-typing i {
  width: 6px; height: 6px; border-radius: 50%; background: rgba(239,243,233,.45);
  animation: botDot 1.2s ease-in-out infinite;
}
.bot-typing i:nth-child(2) { animation-delay: .16s; }
.bot-typing i:nth-child(3) { animation-delay: .32s; }
@keyframes botDot {
  0%, 60%, 100% { opacity: .3; transform: translateY(0); }
  30%           { opacity: 1;  transform: translateY(-3px); }
}

.bot-opts {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 6px; width: 100%; margin-top: 2px;
}
.bot-opt {
  font-family: inherit; font-size: 13px; text-align: left; cursor: pointer;
  padding: 9px 14px; border-radius: 20px; width: auto; max-width: 100%;
  background: rgba(179,210,48,.08); color: var(--lime);
  border: 1px solid rgba(179,210,48,.34);
  transition: background .2s var(--ease), border-color .2s var(--ease),
              transform .2s var(--ease), color .2s var(--ease);
}
.bot-opt:hover {
  background: rgba(179,210,48,.18); border-color: var(--lime);
  transform: translateX(3px);
}
/* answered: kept visible for context, but plainly no longer live */
.bot-opts.spent .bot-opt {
  opacity: .32; cursor: default; transform: none;
  background: transparent; border-color: rgba(239,243,233,.14);
  color: var(--text-dim);
}

/* the one green thing, because it is the one thing going to WhatsApp */
.bot-go {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  align-self: stretch; margin-top: 4px;
  background: linear-gradient(135deg, #B3D230, #62BA8B); color: #0d1600;
  font-family: 'Space Grotesk', sans-serif; font-weight: 700; font-size: 13.5px;
  padding: 12px 18px; border-radius: 30px; text-decoration: none;
  box-shadow: 0 4px 14px rgba(179,210,48,.3);
  animation: botIn .3s var(--ease) both;
  transition: transform .22s var(--ease), box-shadow .22s var(--ease);
}
.bot-go:hover { transform: translateY(-2px); box-shadow: 0 8px 22px rgba(98,186,139,.42); }
.bot-go svg { width: 17px; height: 17px; fill: #0d1600; flex-shrink: 0; }

.bot-foot { flex-shrink: 0; }
.bot-restart {
  display: block; width: 100%; background: none; cursor: pointer;
  border: none; border-top: 1px solid rgba(239,243,233,.1);
  font-family: 'IBM Plex Mono', monospace; font-size: 10.5px;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--text-dim); padding: 11px; transition: color .18s, background .18s;
}
.bot-restart:hover { color: var(--text); background: rgba(239,243,233,.05); }
.bot-restart[hidden] { display: none; }

@media (max-width: 560px) {
  .bot-fab { right: 14px; bottom: 14px; }
  /* smaller launcher on phones so it covers less of the content it floats over
     (it was clipping card headings and the footer line at the bottom-right) */
  .bot-btn { width: 50px; height: 50px; }
  .bot-btn svg { width: 24px; height: 24px; }
  .bot-log { max-height: 50vh; }
}

@media (prefers-reduced-motion: reduce) {
  .bot-ring, .bot-typing i { animation: none; }
  .bot-msg, .bot-go { animation: none; }
  .bot-opt:hover { transform: none; }
}

/* ── form text areas ──────────────────────────────────────────────────
   Dragged by its corner a textarea grows without limit: it pushes the
   submit button off the screen and lets someone paste an essay the form
   then has to carry. max-height stops the drag, maxlength stops the
   content, and the counter below makes the limit visible before it is
   reached rather than silently truncating at it.

   A bare element selector on purpose. The two forms style their fields
   through different classes (.field on the homepage, .ap-form on the
   application page) and neither sets these properties, so one low
   specificity rule covers both and any form added later. */
textarea { resize: vertical; max-height: 320px; max-width: 100%; }

small.cap {
  display: block; margin-top: 6px; text-align: right;
  font-family: 'IBM Plex Mono', monospace; font-size: 11px;
  letter-spacing: .08em; font-variant-numeric: tabular-nums;
  color: rgba(239,243,233,.34); transition: color .25s ease;
}
small.cap.is-near { color: var(--lime); }
small.cap.is-full { color: #E4674F; }

/* hero-clear-fab: on phones the chat bubble sits over the bottom of the first screen; give the hero room */
@media (max-width: 600px) { .hero .wrap { padding-bottom: 84px; } }

/* slim capabilities panel: links only, one column on the left, no explanatory copy */
.nav-panel.nav-panel-slim { grid-template-columns: minmax(0,.55fr) minmax(0,1.45fr); }
.nav-panel-slim .nav-panel-links { grid-template-columns: repeat(2, minmax(0,1fr)); }
.nav-panel-slim .nav-panel-links a { padding: 10px 12px; font-size: 14.5px; }
.nav-all { display: inline-block; margin-top: 6px; font-family: 'IBM Plex Mono', monospace; font-size: 11px;
           letter-spacing: .14em; text-transform: uppercase; color: var(--lime); text-decoration: none; }
.nav-all:hover { text-decoration: underline; }
/* The slim variant's two-column grid and its lead column are desktop chrome:
   under the burger they squeezed the destinations into a ~94px gutter while
   the lead sat on top of them. The section row itself links to the hub page,
   so nothing is lost by dropping both. These sit after the .nav-panel-slim
   rules on purpose — same specificity, later wins. */
@media (max-width: 1240px) {
  .nav-panel.nav-panel-slim { grid-template-columns: 1fr; }
  .nav-panel-slim .nav-panel-lead { display: none; }
  .nav-panel-slim .nav-panel-links { grid-template-columns: 1fr; }
  .nav-panel-slim .nav-panel-links a { padding: 11px 14px; font-size: 14.5px; }
}
