:root{
  --bg:#08090c;
  --bg-2:#0d0f14;
  --bg-3:#12151d;
  --line:#1c1f28;
  --line-soft:#15181f;
  --fg:#f3f4f7;
  --fg-soft:#a6abb9;
  --dim:#7d8397;
  --faint:#737990;
  --a1:#8b7cff;
  --a2:#2fd4c6;
  --grad:linear-gradient(100deg,var(--a1),var(--a2));
  --mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  --sans:"Inter",system-ui,-apple-system,"Segoe UI",sans-serif;

  --t-label:11px;
  --t-huge:clamp(2.4rem,6.2vw,4.5rem);
  --t-lead:clamp(1.15rem,2.8vw,1.75rem);
  --t-head:clamp(1.45rem,3.2vw,2.1rem);

  --max:1120px;
  --gutter:clamp(1.25rem,4vw,3rem);
  --nav-h:58px;
  --sec-y:clamp(3.25rem,5vw,4.5rem);
  --ease:cubic-bezier(.16,1,.3,1);
}

*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  background:var(--bg);
  color:var(--fg-soft);
  font-family:var(--sans);
  font-size:15px;
  line-height:1.7;
  font-weight:300;
  letter-spacing:.005em;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
}
::selection{background:rgba(139,124,255,.28);color:#fff}

/* No native drag ghosts: dragging a link, icon or the model should do nothing
   rather than start an HTML5 drag. JS covers Firefox, which ignores user-drag. */
a,img,svg,canvas,button,kbd{-webkit-user-drag:none;user-drag:none}

/* Nothing on the page is selectable — set on body so it inherits everywhere. */
body{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}

/* A soft fixed wash so the flat black has some depth behind the content. */
body::before{
  content:'';
  position:fixed;inset:0;z-index:0;pointer-events:none;
  background:
    radial-gradient(70vw 55vw at 88% -8%, rgba(139,124,255,.075), transparent 62%),
    radial-gradient(60vw 50vw at 2% 104%, rgba(47,212,198,.05), transparent 62%);
}

/* ---------- Nav ---------- */
nav{
  position:fixed;top:0;left:0;width:100%;
  height:var(--nav-h);
  z-index:100;
  display:flex;align-items:center;justify-content:center;
  background:rgba(8,9,12,.78);
  backdrop-filter:blur(14px) saturate(140%);
  -webkit-backdrop-filter:blur(14px) saturate(140%);
  border-bottom:1px solid var(--line-soft);
}
.nav-inner{
  width:100%;max-width:var(--max);
  padding:0 var(--gutter);
  display:flex;align-items:center;justify-content:space-between;gap:1rem;
}
.nav-logo{
  font-family:var(--mono);font-size:13px;font-weight:500;
  color:var(--fg);letter-spacing:.02em;text-decoration:none;
  display:inline-flex;align-items:center;gap:.55rem;flex:none;
}
.nav-logo .dot{width:6px;height:6px;border-radius:50%;background:var(--grad);flex:none}
.nav-links{display:flex;gap:1.75rem;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}
.nav-links::-webkit-scrollbar{display:none}
.nav-links a{
  font-family:var(--mono);font-size:var(--t-label);
  text-transform:uppercase;letter-spacing:.13em;
  color:var(--faint);text-decoration:none;white-space:nowrap;
  transition:color .3s var(--ease);
  position:relative;padding:2px 0;
}
.nav-links a:hover{color:var(--fg-soft)}
.nav-links a.active{color:var(--fg)}
.nav-links a.active::after{
  content:'';position:absolute;left:0;right:0;bottom:-2px;height:1px;background:var(--grad);
}
/* Hidden until the links stop fitting — see the 760px block, which is also
   where it is given something to open. Two hairlines rather than the usual
   three, to sit in the same weight as every other rule on the page. */
.nav-toggle{
  display:none;flex:none;
  width:34px;height:34px;align-items:center;justify-content:center;
  padding:0;background:none;cursor:pointer;
  border:1px solid var(--line);border-radius:2px;
  color:var(--fg-soft);
  transition:color .3s var(--ease),border-color .3s var(--ease);
}
.nav-toggle:hover{color:var(--fg);border-color:#2f3441}
.nav-toggle .bars{position:relative;display:block;width:15px;height:9px}
.nav-toggle .bars span{
  position:absolute;left:0;width:100%;height:1px;background:currentColor;
  transition:transform .3s var(--ease);
}
.nav-toggle .bars span:first-child{top:0}
.nav-toggle .bars span:last-child{bottom:0}
/* Open: the two bars meet in the middle and cross, so the button states its
   own action rather than needing a second icon. */
.nav-open .nav-toggle{color:var(--fg)}
.nav-open .nav-toggle .bars span:first-child{transform:translateY(4px) rotate(45deg)}
.nav-open .nav-toggle .bars span:last-child{transform:translateY(-4px) rotate(-45deg)}

#progress{position:fixed;top:0;left:0;height:1px;width:0;background:var(--grad);z-index:101;pointer-events:none}

/* ---------- Layout ---------- */
.wrap{position:relative;z-index:2;max-width:var(--max);margin:0 auto;padding:0 var(--gutter)}
section{padding:var(--sec-y) 0;position:relative}

/* Each section carries its own accent, stepping down the existing violet →
   teal brand gradient as you scroll. No new hues are introduced — the page
   just walks the gradient it already uses, so sections read as distinct
   without any of them looking foreign. --sec is inherited by everything
   accent-coloured inside the section. */
section{--sec:var(--a2)}
#projects  {--sec:#8b7cff}
#experience{--sec:#7a89ff}
#research  {--sec:#5da7f0}
#awards    {--sec:#3fc2dd}
#education {--sec:#2fd4c6}

/* ---------- Reveal ---------- */
.reveal{opacity:0;transform:translateY(20px);transition:opacity .85s var(--ease),transform .85s var(--ease)}
.reveal.in{opacity:1;transform:none}
.d1{transition-delay:.06s}.d2{transition-delay:.12s}.d3{transition-delay:.18s}

/* ---------- Type ---------- */
.label{font-family:var(--mono);font-size:var(--t-label);text-transform:uppercase;letter-spacing:.16em;color:var(--faint)}
h1,h2,h3,h4{font-weight:500;color:var(--fg);letter-spacing:-.022em;line-height:1.14}

/* ---------- Section headers ---------- */
.sec-head{
  display:flex;align-items:baseline;gap:1.25rem;
  padding-bottom:1.25rem;margin-bottom:2.25rem;
  border-bottom:1px solid var(--line);
}
.sec-num{
  font-family:var(--mono);font-size:var(--t-label);
  letter-spacing:.14em;color:var(--sec);flex:none;
}
.sec-head h2{font-size:var(--t-head);flex:1}
.sec-count{
  font-family:var(--mono);font-size:10px;text-transform:uppercase;
  letter-spacing:.14em;color:var(--faint);flex:none;white-space:nowrap;
}

.cat{
  font-family:var(--mono);font-size:var(--t-label);
  text-transform:uppercase;letter-spacing:.16em;color:var(--dim);
  margin:2.75rem 0 1.25rem;
  display:flex;align-items:center;gap:1rem;
}
.cat::after{content:'';flex:1;height:1px;background:var(--line-soft)}
.cat.first{margin-top:0}

/* ---------- Hero ---------- */
.hero{
  /* One value used above and below, with the nav height added on top. The two
     have to match or the content centres in the padded box but sits off-centre
     in the space the visitor actually sees below the fixed nav. The value also
     has to clear the absolutely-positioned scroll cue. */
  --hero-pad:clamp(2.5rem,6vh,3.5rem);
  /* The scroll cue is absolutely positioned, so it takes no part in the
     centring — which left the grid mathematically centred but looking
     top-heavy, because the bottom gap was occupied by the cue while the top
     gap was empty. Reserving the cue's space as extra bottom padding centres
     the grid in the area that is actually empty. */
  --cue-space:3.5rem;
  position:relative;
  min-height:100vh;
  min-height:100svh;
  display:flex;flex-direction:column;justify-content:center;
  padding-top:calc(var(--nav-h) + var(--hero-pad));
  padding-bottom:calc(var(--hero-pad) + var(--cue-space));
}
/* The hero is a single column until the wireframe has actually rendered a
   frame — js/hero-3d.js sets .has-model on <html> at that point. Any failure
   (blocked script, no WebGL, slow connection, narrow screen) therefore leaves
   a clean one-column hero rather than a blank reserved column. */
.hero-grid{
  display:grid;
  grid-template-columns:1fr;
  gap:clamp(2rem,4vw,3.5rem);
  align-items:center;
}
/* The model sits in a framed panel: hairline border, faint inset ground, and
   the caption as a footer bar inside the same frame. */
.hero-model{
  display:none;position:relative;min-width:0;
  border:1px solid var(--line);
  border-radius:3px;
  background:rgba(13,15,20,.45);
  overflow:hidden;
  transition:border-color .4s var(--ease);
}
.hero-model.interactive{border-color:rgba(47,212,198,.4)}
.hero-model.interactive .model-stage{cursor:grab;touch-action:none}
.hero-model.interactive .model-stage:active{cursor:grabbing}

/* Fluid, so just above the 960px breakpoint the model column doesn't squeeze
   the name into a narrow ribbon. */
.has-model .hero-grid{grid-template-columns:minmax(0,1fr) clamp(360px,44vw,680px)}
.has-model .hero-model{display:block}
.model-stage{position:relative;width:100%;aspect-ratio:1/1;max-height:64vh}
.model-stage canvas{
  position:absolute;inset:0;width:100%;height:100%;display:block;
  opacity:0;transition:opacity .9s ease;
}
.model-stage canvas.ready{opacity:1}

/* Shown from first paint so the column never shifts, and faded out once the
   first frame has actually rendered. */
.model-loader{
  position:absolute;inset:0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:.85rem;
  transition:opacity .5s var(--ease);
}
.hero-model.loaded .model-loader{opacity:0;pointer-events:none}
.loader-track{position:relative;width:110px;height:1px;background:var(--line);overflow:hidden}
.loader-track::after{
  content:'';position:absolute;top:0;left:0;height:1px;width:38%;
  background:var(--grad);animation:cue 1.4s var(--ease) infinite;
}
.loader-text{
  font-family:var(--mono);font-size:10px;
  text-transform:uppercase;letter-spacing:.16em;color:var(--faint);
}
.model-cap{
  display:flex;align-items:center;flex-wrap:nowrap;gap:.35rem .6rem;
  margin:0;padding:.7rem .9rem;
  border-top:1px solid var(--line-soft);
  font-family:var(--mono);font-size:10px;
  text-transform:uppercase;letter-spacing:.14em;color:var(--faint);
}
.model-dot{width:5px;height:5px;border-radius:50%;background:var(--a2);flex:none}
/* The caption is one row at every width: the hint keeps its full width and the
   model name is the part that gives, ellipsing rather than pushing the hint on
   to a second line. Only the longest names ever reach the ellipsis, and only
   while interacting — the idle hint is short enough to leave the name whole. */
#robot-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* Interact affordance — sits opposite the model label */
.model-cta{
  margin-left:auto;flex:none;
  display:inline-flex;align-items:center;gap:.4rem;
  font:inherit;letter-spacing:.14em;text-transform:uppercase;
  color:var(--dim);background:none;border:0;padding:0;cursor:pointer;
  transition:color .3s var(--ease);
  white-space:nowrap;
}
.model-cta:hover{color:var(--fg)}
.model-cta kbd{
  font-family:var(--mono);font-size:9px;line-height:1;
  border:1px solid var(--line);border-radius:2px;
  padding:.28rem .34rem;color:var(--fg-soft);
}
/* The interactive hint lists five controls where the idle one lists two, so it
   is set smaller and tracked tighter than the rest of the caption — at the
   caption's own size it cannot share a row with the longer model names
   (AmadorUAVs Payload Drone, FTC Centerstage) and drops to a second line. */
.hero-model.interactive .model-cta{
  color:var(--a2);
  font-size:8.5px;letter-spacing:.06em;gap:.3rem;
}
/* Colour only — the key caps keep the caption's own metrics in both states.
   They are the tallest thing in that row, so resizing them resized the caption,
   and the figure is centred in its grid cell: a caption 2.3px shorter moved the
   model 1.1px down the instant you pressed I. */
.hero-model.interactive .model-cta kbd{border-color:rgba(47,212,198,.45);color:var(--a2)}

.hero-eyebrow{margin-bottom:1.6rem;display:flex;align-items:center;gap:.75rem}
.hero-eyebrow .bar{width:26px;height:1px;background:var(--grad);flex:none}
.hero h1{
  font-size:var(--t-huge);
  font-weight:300;
  letter-spacing:-.04em;
  color:var(--fg);
  margin-bottom:1.1rem;
}
.hero h1 .ln{display:block;overflow:hidden;padding-bottom:.08em}
.hero h1 .ln > span{display:block}
.lead{
  font-size:var(--t-lead);
  font-weight:300;
  line-height:1.28;
  letter-spacing:-.02em;
  margin-bottom:2.5rem;
  max-width:22ch;
  background:var(--grad);
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
}
.hero-actions{display:flex;flex-wrap:wrap;gap:.7rem;align-items:center}

/* Buttons */
.btn{
  display:inline-flex;align-items:center;gap:.6rem;
  font-family:var(--mono);font-size:var(--t-label);
  text-transform:uppercase;letter-spacing:.13em;
  padding:.85rem 1.4rem;border-radius:3px;
  text-decoration:none;border:1px solid var(--line);
  color:var(--fg-soft);background:transparent;
  transition:color .35s var(--ease),border-color .35s var(--ease),background .35s var(--ease);
}
.btn:hover{color:var(--fg);border-color:#2c313e;background:var(--bg-2)}
.btn-primary{border-color:transparent;color:#07080b;background:var(--fg);font-weight:500}
.btn-primary:hover{background:#fff;color:#07080b;border-color:transparent}
.btn .arrow{transition:transform .35s var(--ease)}
.btn:hover .arrow{transform:translateX(3px)}

/* Scroll cue — pinned to the bottom of the hero rather than stacked after the
   grid. As a sibling it both pushed itself below the fold once the model column
   grew, and dragged the grid off centre by taking part in the centring. */
.cue{
  position:absolute;left:0;bottom:clamp(1.25rem,3.5vh,2.25rem);
  display:flex;align-items:center;gap:.85rem;
  font-family:var(--mono);font-size:10px;
  text-transform:uppercase;letter-spacing:.18em;color:var(--faint);
}
.cue .track{position:relative;width:54px;height:1px;background:var(--line);overflow:hidden}
.cue .track::after{
  content:'';position:absolute;top:0;left:0;height:1px;width:40%;
  background:var(--grad);animation:cue 2.6s var(--ease) infinite;
}
@keyframes cue{0%{transform:translateX(-100%)}60%,100%{transform:translateX(250%)}}

/* ---------- Cards ---------- */
.card{
  position:relative;
  /* Transparent: the page's own background wash shows through, so cards read
     as outlined regions rather than panels. Section differentiation comes from
     the accent colours alone. */
  background:transparent;
  border:1px solid var(--line);
  border-radius:3px;
  padding:2rem;
  display:flex;flex-direction:column;
  transition:border-color .5s var(--ease),background .5s var(--ease),transform .5s var(--ease);
}
.card::before{
  content:'';position:absolute;top:-1px;left:0;height:1px;width:0;
  background:var(--grad);transition:width .6s var(--ease);
}
/* Hover stays a faint lift rather than a fill, so the card never becomes a
   panel — just brighter than its surroundings for a moment. */
.card:hover,
.card.spotlight{
  border-color:#2a2f3d;
  background:rgba(255,255,255,.02);
  transform:translateY(-2px);
}
.card:hover::before,
.card.spotlight::before{width:100%}
.card-kicker{
  font-family:var(--mono);font-size:10px;
  text-transform:uppercase;letter-spacing:.15em;
  color:var(--sec);margin-bottom:.8rem;
}
.card h3{font-size:1.15rem;margin-bottom:.8rem;letter-spacing:-.018em}
.card p{color:var(--dim);font-size:.9rem;margin-bottom:1.4rem;flex:1}
.card p:last-child{margin-bottom:0}
/* Feature cards differ by width and title size only — padding stays uniform so
   a full-width card and a half-width one read as the same object. */
.card-meta{
  font-family:var(--mono);font-size:11.5px;letter-spacing:.05em;
  color:var(--sec);margin:.5rem 0 1rem;
}
/* Two text columns inside a card. Wider than the card-grid gap on purpose:
   columns of prose need more separation than adjacent panels do. */
.split{display:grid;grid-template-columns:1fr 1fr;gap:clamp(1.5rem,3vw,2.5rem)}
.card.feature h3{font-size:clamp(1.25rem,2.6vw,1.7rem)}

/* Grids */
.grid-2{display:grid;grid-template-columns:repeat(2,1fr);gap:1.15rem;align-items:start}
/* An odd-numbered final card would otherwise sit half-width with a gap beside
   it. Let it run the full width instead, matching the feature cards. */
.grid-2 > .card:last-child:nth-child(odd){grid-column:1 / -1}
.stack{display:flex;flex-direction:column;gap:1.15rem}

/* Chips */
.chips{display:flex;flex-wrap:wrap;gap:.4rem;margin:0 0 1.3rem}
.chip{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.04em;
  color:var(--dim);border:1px solid var(--line);
  padding:.28rem .6rem;border-radius:2px;
  transition:color .3s var(--ease),border-color .3s var(--ease);
}
.card:hover .chip,
.card.spotlight .chip{color:var(--fg-soft);border-color:#252a35}

/* Explore (E) marks its target: the hover look above, plus a short accent ring
   so it reads as being pointed at rather than merely moused over. */
.spotlight{box-shadow:0 0 0 1px color-mix(in srgb, var(--sec) 45%, transparent)}

/* Bullets */
.bullets{list-style:none;margin-bottom:1.4rem}
.bullets li{position:relative;padding-left:1.15rem;margin-bottom:.55rem;color:var(--dim);font-size:.9rem;line-height:1.65}
.bullets li::before{content:'';position:absolute;left:0;top:.68em;width:5px;height:1px;background:var(--faint)}
.bullets strong{color:var(--fg-soft);font-weight:500}
.bullets.last{margin-bottom:0}
/* A second line under a bullet, for detail that belongs to the entry but would
   crowd it on the same line — set smaller and fainter so the entry still reads
   as one item rather than two. */
.note{display:block;margin-top:.25rem;font-size:.78rem;line-height:1.7;color:var(--faint)}
/* Wide enough that the run reads as separate items rather than one sentence.
   The gap does the separating, so the dot itself stays at the text's own
   weight — the next step down in the palette is the hairline colour, which
   would leave the marks all but invisible. */
.note .sep{padding:0 .7em}

/* Links */
a{color:var(--fg-soft);text-decoration:none}
.lnk{color:var(--fg-soft);border-bottom:1px solid rgba(166,171,185,.25);transition:color .3s var(--ease),border-color .3s var(--ease)}
.lnk:hover{color:var(--a2);border-color:var(--a2)}
.links{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:auto}
.pill{
  display:inline-flex;align-items:center;gap:.45rem;
  font-family:var(--mono);font-size:10.5px;
  text-transform:uppercase;letter-spacing:.11em;
  color:var(--dim);border:1px solid var(--line);
  padding:.42rem .8rem;border-radius:2px;
  transition:color .3s var(--ease),border-color .3s var(--ease),background .3s var(--ease);
}
.pill:hover{color:var(--fg);border-color:#2f3441;background:var(--bg-3)}
.ico{width:1em;height:1em;flex:none;display:block}
.pill .ico{width:.85em;height:.85em}
.bullets .ico,.awards .ico{display:inline-block;vertical-align:-.08em;width:.72em;height:.72em;margin-left:.3em}
.sprite{position:absolute;width:0;height:0;overflow:hidden}

/* ---------- Experience ---------- */
.xp{border-top:1px solid var(--line)}
.xp-row{
  display:grid;grid-template-columns:3.25rem 1fr;gap:0 1.5rem;
  /* Symmetric: the left inset clears the accent rail, and the right side needs
     the same or the right-aligned date runs flush into the edge. */
  padding:2.1rem 1rem;
  border-bottom:1px solid var(--line-soft);
  position:relative;
  transition:background .4s var(--ease);
}
/* Accent rail that grows on hover */
.xp-row::before{
  content:'';position:absolute;left:0;top:0;width:1px;height:0;
  background:var(--grad);transition:height .5s var(--ease);
}
.xp-row:hover,
.xp-row.spotlight{background:rgba(255,255,255,.016)}
.xp-row:hover::before,
.xp-row.spotlight::before{height:100%}
.xp-idx{font-family:var(--mono);font-size:var(--t-label);letter-spacing:.12em;color:var(--faint);padding-top:.35rem;transition:color .4s var(--ease)}
.xp-row:hover .xp-idx,
.xp-row.spotlight .xp-idx{color:var(--sec)}
.xp-top{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:baseline;gap:.3rem 1.5rem;margin-bottom:.3rem}
.xp-org{font-size:1.15rem;color:var(--fg);font-weight:500;letter-spacing:-.018em}
.xp-date{font-family:var(--mono);font-size:11px;letter-spacing:.06em;color:var(--dim);white-space:nowrap}
.xp-role{font-family:var(--mono);font-size:11.5px;letter-spacing:.05em;color:var(--sec);margin-bottom:1rem}
.xp-links{margin-top:1.15rem}

/* ---------- Awards ---------- */
.awards{list-style:none}
.awards li{
  display:grid;grid-template-columns:auto 1fr;gap:1rem;
  padding:.95rem 0;border-bottom:1px solid var(--line-soft);
  font-size:.9rem;color:var(--dim);line-height:1.6;
}
.awards li:last-child{border-bottom:0;padding-bottom:0}
.awards .mk{font-family:var(--mono);font-size:10px;color:var(--faint);padding-top:.35rem;letter-spacing:.1em}
.awards strong{display:block;color:var(--fg);font-weight:500;font-size:.95rem;margin-bottom:.1rem;letter-spacing:-.012em}
.awards a strong{transition:color .3s var(--ease)}
.awards a:hover strong{color:var(--sec)}

/* The certifications are the only links inside a bullet list, and unlike an
   award — whose title owns its own line — theirs sit mid-sentence against the
   issuer and year. What marks them is contrast: the linked title is the full
   foreground against the dimmer text around it, and the arrow carries the
   section accent, which the title joins on hover. */
.bullets a strong{color:var(--fg);transition:color .3s var(--ease)}
.bullets a .ico{color:var(--sec)}
.bullets a:hover strong{color:var(--sec)}

/* ---------- Education ---------- */
.stats{
  display:flex;flex-wrap:wrap;gap:2.5rem;
  padding:1.4rem 0;margin:1.6rem 0;
  border-top:1px solid var(--line);border-bottom:1px solid var(--line);
}
.stat-k{font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.15em;color:var(--faint);margin-bottom:.35rem}
.stat-v{font-family:var(--mono);font-size:1.5rem;color:var(--fg);font-weight:500;letter-spacing:-.01em;line-height:1;font-variant-numeric:tabular-nums}
.stat-v small{font-size:.7rem;color:var(--dim);letter-spacing:.06em;margin-left:.4rem;font-weight:400}
.sub-h{font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.15em;color:var(--sec);margin-bottom:.85rem}
.gap{margin-top:1.9rem}
.cols-2{columns:2;column-gap:2rem}
.cols-2 li{break-inside:avoid}

/* ---------- Footer ---------- */
footer{position:relative;z-index:2;border-top:1px solid var(--line);padding:2.75rem 0 3.5rem}
.foot-inner{
  max-width:var(--max);margin:0 auto;padding:0 var(--gutter);
  display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between;align-items:center;
  font-family:var(--mono);font-size:11px;letter-spacing:.06em;color:var(--faint);
}
.foot-inner a{color:var(--faint);transition:color .3s var(--ease)}
.foot-inner a:hover{color:var(--a2)}
.foot-links{display:flex;gap:1.5rem;flex-wrap:wrap}

/* ---------- Responsive ---------- */
/* Desktop only — js/hero-3d.js checks the same 960px threshold and skips
   booting entirely below it, so nothing is downloaded on phones or tablets. */
@media (max-width:960px){
  .has-model .hero-grid{grid-template-columns:1fr}
  .has-model .hero-model{display:none}
  .lead{max-width:26ch}
}
@media (max-width:760px){
  .grid-2,.split{grid-template-columns:1fr}
  .cols-2{columns:1}
  .xp-row{grid-template-columns:1fr;gap:.45rem}
  .xp-idx{padding-top:0}
  .stats{gap:1.75rem}
  /* Below this width the five links either scroll sideways out of sight or
     crowd the logo, so they move into a panel under the bar and the toggle
     takes their place. The panel is the same element, restyled — nothing is
     duplicated, so the active-link marking keeps working untouched.

     Everything below is scoped to .has-nav-menu, which main.js sets once the
     button is wired up: if the script never runs, the links stay the
     side-scrolling row they have always been rather than collapsing into a
     panel that nothing can open. */
  .nav-links{gap:1.1rem}
  .has-nav-menu .nav-toggle{display:inline-flex}
  .has-nav-menu .nav-links{
    position:fixed;top:var(--nav-h);left:0;right:0;
    flex-direction:column;gap:0;overflow:visible;
    padding:.25rem var(--gutter) .9rem;
    background:rgba(8,9,12,.94);
    backdrop-filter:blur(14px) saturate(140%);
    -webkit-backdrop-filter:blur(14px) saturate(140%);
    border-bottom:1px solid var(--line);
    opacity:0;visibility:hidden;transform:translateY(-6px);
    transition:opacity .25s var(--ease),transform .25s var(--ease),visibility .25s;
  }
  .has-nav-menu.nav-open .nav-links{opacity:1;visibility:visible;transform:none}
  .has-nav-menu .nav-links a{padding:.85rem 0;border-bottom:1px solid var(--line-soft)}
  .has-nav-menu .nav-links a:last-child{border-bottom:0}
  /* The gradient underline marks a link a few characters wide; stretched
     across a full-width row it reads as a divider instead. Colour alone
     carries the active state here. */
  .has-nav-menu .nav-links a.active::after{display:none}
  .sec-count{display:none}
}
@media (max-width:480px){
  .hero-eyebrow .label{font-size:10px;letter-spacing:.1em}
  .hero-actions .btn{white-space:nowrap;padding:.85rem 1.15rem}
}

/* Pre-animation state — only while JS is running; cleared if anime.js
   never arrives so the hero can never be stuck invisible. Declared before the
   reduced-motion block so that block can override it at equal specificity. */
.js [data-hero]{opacity:0}
.js [data-hero-line] > span{transform:translateY(105%)}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{
    animation-duration:.001ms!important;
    animation-iteration-count:1!important;
    transition-duration:.001ms!important;
  }
  .reveal{opacity:1!important;transform:none!important}
  .js [data-hero]{opacity:1}
  .js [data-hero-line] > span{transform:none}
}
