/* ─────────────────────────────────────────────────────────────
   enhance.css — shared motion + polish layer (loaded on every page)
   Nothing here is required for content to be readable: if JS is off,
   every reveal falls back to fully visible.
   ───────────────────────────────────────────────────────────── */

:root{
  --ease-out: cubic-bezier(.22,.8,.28,1);
  --ease-soft: cubic-bezier(.4,0,.2,1);
  --accent-soft: rgba(224,92,42,.14);
  --cyan-soft: rgba(0,212,255,.12);
  --dim: rgba(200,220,232,.62);   /* replaces the too-faint .3/.45 greys */
  --dimmer: rgba(200,220,232,.44);
}

/* ── 1. PAGE ENTRANCE (CSS only — paints immediately, no JS dependency) ── */
/* transform-only: if the animation clock never ticks (bfcache, throttled or
   embedded frames), a stuck frame is a 8px offset — never invisible content */
@keyframes pageIn{from{transform:translateY(8px)}to{transform:none}}
body{animation:pageIn .4s var(--ease-out)}

/* Leaving the page: enhance.js adds .leaving for a matched fade */
body.leaving{opacity:0;transition:opacity .18s ease}

/* ── 2. SCROLL REVEALS ── */
/* Hidden only when JS is confirmed present (html.js set by an inline head script) */
html.js [data-rv]:not(.rv-in){
  opacity:0 !important;
  will-change:opacity,transform;
}
html.js [data-rv]:not(.rv-in){transform:translateY(34px) !important}
html.js [data-rv="left"]:not(.rv-in){transform:translateX(-34px) !important}
html.js [data-rv="right"]:not(.rv-in){transform:translateX(34px) !important}
html.js [data-rv="scale"]:not(.rv-in){transform:scale(.94) !important}
html.js [data-rv="row"]:not(.rv-in){transform:translateY(14px) !important}
html.js [data-rv].rv-in{
  opacity:1 !important;
  transform:none !important;
  transition:opacity .7s var(--ease-out), transform .78s var(--ease-out) !important;
  transition-delay:var(--rv-d,0ms) !important;
  will-change:auto;
}
html.js [data-rv="row"].rv-in{
  transition:opacity .42s var(--ease-out), transform .42s var(--ease-out) !important;
  transition-delay:var(--rv-d,0ms) !important;
}

/* ── 3. SCROLL PROGRESS ── */
#rv-progress{
  position:fixed;top:0;left:0;height:2px;width:100%;
  transform:scaleX(0);transform-origin:0 50%;
  background:linear-gradient(90deg,var(--accent,#e05c2a),var(--cyan,#00d4ff));
  z-index:999;pointer-events:none;
}

/* ── 4. NAV: condense on scroll, mark on the brand ── */
nav{transition:height .25s var(--ease-soft), background .25s ease, box-shadow .25s ease}
nav.nav-condensed{height:50px;box-shadow:0 8px 30px rgba(0,0,0,.35)}
/* nav is display:flex, so whitespace text nodes between the brand and the
   link list are dropped — the separation has to be a real gap, not source
   whitespace (making .nav-brand a flex item is what exposed this). */
nav{gap:clamp(18px,3vw,40px)}
/* The desktop nav holds 7 nowrap links + MAIN + RESUME + two injected tools.
   It genuinely cannot fit under ~1060px, and .nav-links can't shrink (its
   anchors are nowrap), so the excess used to spill past the viewport and get
   silently clipped by body{overflow-x:hidden}. Collapse to the hamburger
   before the row runs out of room — the mobile menu carries its own search
   and theme controls, so nothing becomes unreachable. */
@media (max-width:1060px){
  .nav-links{display:none !important}
  .hamburger,#ham{display:flex !important}
  .nav-tools:not(.is-floating),.nav-tools-li{display:none !important}
  nav{gap:16px}
}
.nav-brand{display:inline-flex;align-items:center;gap:9px;margin-right:auto}
.nav-brand,.nav-links{min-width:0}
.nav-mark{width:24px;height:24px;flex:none;display:block;line-height:0}
.nav-mark img{width:100%;height:100%;display:block}

/* ── 5. MICRO-INTERACTIONS ── */
.wid-card,.ep-card,.cert-card,.proj-card,.card,.lnk{
  transition:transform .28s var(--ease-out), background .25s ease,
             border-color .25s ease, box-shadow .28s var(--ease-out);
}
.wid-card:hover,.ep-card:hover{transform:translateY(-4px);box-shadow:0 14px 34px rgba(0,0,0,.34)}
.btn-primary,.btn-secondary,.btn-ghost,.nav-dl{
  transition:transform .2s var(--ease-out), background .2s ease,
             border-color .2s ease, color .2s ease, box-shadow .2s ease;
}
.btn-primary:hover{box-shadow:0 8px 24px rgba(224,92,42,.3)}
.btn-secondary:hover{box-shadow:0 8px 24px rgba(0,212,255,.16)}
.soc{transition:transform .22s var(--ease-out), opacity .2s ease}
.soc:hover{transform:translateY(-3px) scale(1.06)}
.lnk:hover{transform:translateY(-2px);box-shadow:0 10px 26px rgba(0,0,0,.3)}

/* Counting stats shouldn't reflow their row as digits change */
.hs-num,.ss-num,.ph-stat-num{font-variant-numeric:tabular-nums}

/* Continuous glow on every stat was distracting — glow once as it lands */
@keyframes statLand{
  0%{text-shadow:0 0 0 rgba(224,92,42,0)}
  40%{text-shadow:0 0 26px rgba(224,92,42,.55)}
  100%{text-shadow:0 0 10px rgba(224,92,42,.18)}
}
.hs-num,.ss-num{animation:none !important}
.hs-num.rv-lit,.ss-num.rv-lit{animation:statLand 1.6s var(--ease-out) forwards !important}
.ph-stats{gap:clamp(16px,4vw,44px)}

/* ── 6. LEGIBILITY ── */
.hs-label,.ss-label,.ep-num,.wid-desc,.ep-desc{color:var(--dim)}
a:focus-visible,button:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--cyan,#00d4ff);outline-offset:3px;border-radius:2px;
}

/* ── 7. FIT ANY SCREEN ── */
img,svg,video,canvas{max-width:100%}
/* These two grids paint their own background and use a 2px gap for the
   dividers, so a half-empty final row shows up as a large blank panel.
   auto-fit can't guarantee a full last row, so the column counts are
   pinned to values that divide the card count exactly:
   .wid-grid has 4 cards -> 1 / 2 / 4 columns
   .explore-grid has 6 cards -> 1 / 2 / 3 columns */
.wid-grid{grid-template-columns:repeat(2,1fr)}
.explore-grid{grid-template-columns:repeat(2,1fr)}
@media(min-width:760px){
  .explore-grid{grid-template-columns:repeat(3,1fr)}
}
@media(min-width:1180px){
  .wid-grid{grid-template-columns:repeat(4,1fr)}
}
@media(max-width:520px){
  .wid-grid,.explore-grid{grid-template-columns:1fr}
}
.stats-strip{gap:clamp(14px,3vw,32px)}
.stats-strip .ss-item{flex:1 1 140px}
body{overflow-x:hidden}
/* long strings (emails, URLs, hashes) never push a phone layout sideways */
p,li,td,.tb-item,.ep-count,.footer-center{overflow-wrap:anywhere}

/* display headlines living in a narrow column must scale to the column,
   not the full viewport (the contact hero clipped "Connect." at ~900px) */
.lp-title{font-size:clamp(30px,3.8vw,68px);overflow-wrap:normal}
.hero-name,.ph-title,.form-title{overflow-wrap:normal;word-break:keep-all}
/* the hero's text column is 1fr against a fixed 420px photo, so viewport-sized
   display type overruns it in the 900–1300px band — size it to the column */
@media (min-width:901px) and (max-width:1300px){
  /* #hero is display:flex; the actual two-column grid is .hero-grid.
     Its 1.1fr/1fr tracks are minmax(auto,…), so the HUD panel's
     min-content width floors the right track and the pair overflows
     a narrow viewport (clipped by #hero{overflow:hidden}).
     minmax(0,…) lets both tracks actually shrink. */
  .hero-grid{grid-template-columns:minmax(0,1.1fr) minmax(0,1fr)}
  /* home uses .hero-name, tryhackme uses .hero-title: size BOTH against
     the column, not the viewport, or the headline spills the track */
  .hero-name,.hero-grid .hero-title{font-size:clamp(40px,5.2vw,76px)}
  /* two stat cells in a ~420px panel leave 113px of content box, which
     a 6-character number like "78,310" cannot fit */
  .hud-stats{grid-template-columns:1fr}
}
/* the same two rules have to hold at EVERY two-column width, not just the
   900–1300 band: with auto tracks the HUD panel's min-content width floors
   the right column and "GLOBAL." overruns the left one (the L landed inside
   the Operator Stats panel at ~1440px) */
@media (min-width:901px){
  .hero-grid{grid-template-columns:minmax(0,1.1fr) minmax(0,1fr)}
  .hero-name,.hero-grid .hero-title{font-size:clamp(40px,5.2vw,86px)}
  .hero-grid .hero-title{padding-right:.12em}
}
/* the HUD's own contents must be allowed to shrink with their track */
.hero-grid > *{min-width:0}
.hud-panel,.hud-stats{min-width:0}
.hud-stats > *{min-width:0}
/* stat figures scale with the panel instead of overflowing their cell */
.hud-stats .hs-num{font-size:clamp(20px,2.4vw,34px);white-space:nowrap}
.footer-center{white-space:normal}

@media (max-width:520px){
  .hero-stats{display:grid;grid-template-columns:1fr 1fr;gap:18px 12px}
  #rv-progress{height:3px}
}
/* tall-narrow and short-wide ratios */
@media (max-height:520px) and (orientation:landscape){
  nav{position:relative}
  body{animation:none}
}

/* ── 8. REDUCED MOTION / PRINT ── */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;scroll-behavior:auto !important;
  }
  html.js [data-rv]{opacity:1 !important;transform:none !important}
}
@media print{
  html.js [data-rv]{opacity:1 !important;transform:none !important}
  #rv-progress,#ambientFx{display:none !important}
}

/* ═══════════════════════════════════════════════════
   CROSS-DEVICE SAFETY NET
   (compatibility across screen ratios, notches,
   foldables, and older browsers — shared by every page)
   ═══════════════════════════════════════════════════ */

@supports not (height: 100svh) {
  [style*="100svh"], .min-h-svh { min-height: 100vh; }
}

body {
  padding-left: max(env(safe-area-inset-left), 0px);
  padding-right: max(env(safe-area-inset-right), 0px);
}

@media (max-width: 359px) {
  body { font-size: 14px; }
  h1 { font-size: clamp(24px, 8vw, 32px) !important; }
}

@media (max-height: 480px) and (orientation: landscape) {
  #hero, #page-hero { min-height: auto !important; padding-top: 24px !important; padding-bottom: 24px !important; }
  .scroll-hint { display: none !important; }
}

/* ── WIDE-SCREEN FIT ──
   The old rule capped every page at 1600px from 1800px up, which left wide
   gutters and made body copy look undersized on a large display. Scale the
   reading sizes with the viewport and let the container follow the screen. */
@media (min-width:1450px){
  body{font-size:17px}
  p,li,.bio-text p,.gc-desc,.exp-list li,.tl-detail,.wid-desc,.ep-desc,
  .wu-desc,.wu-card-desc,.fs-sub,.nl-desc,.ph-sub,.fq-sub,.faq-a-inner p,
  .hero-desc,.lp-desc,.ft-tag{font-size:17px}
}
@media (min-width:1800px){
  body{font-size:18px}
  p,li,.bio-text p,.gc-desc,.exp-list li,.tl-detail,.wid-desc,.ep-desc,
  .wu-desc,.wu-card-desc,.fs-sub,.nl-desc,.ph-sub,.fq-sub,.faq-a-inner p,
  .hero-desc,.lp-desc,.ft-tag{font-size:18px}
  #hero, .section, #page-hero, .contact-wrap, #projects-wrap, #filter-bar {
    max-width: 1800px; margin-left: auto; margin-right: auto;
  }
  .site-footer{max-width:1800px}
}
@media (min-width:2300px){
  body{font-size:19px}
  #hero, .section, #page-hero, .contact-wrap, #projects-wrap, #filter-bar,
  .site-footer { max-width: 2080px; }
}

@media (min-width: 540px) and (max-width: 720px) {
  .wid-grid, .exp-grid, .feat-grid { grid-template-columns: repeat(2, 1fr) !important; }
}

/* Scrollbar theming is intentionally NOT centralized here: each page tints
   its scrollbar to match its own accent color, and enhance.css loads after
   every page's inline <style> — a shared rule here would win the cascade
   and flatten every page to the same color. Left as each page's own block. */


/* a third experience panel spans the 2-column grid rather than
   leaving a half-empty row */
.exp-panel.wide{grid-column:1 / -1}

/* every goal card now carries an icon, so no baseline compensation is needed */

/* ═══════════════════════════════════════════════════
   SITE FOOTER  (was a restatement of the header; now a
   real directory: pages, profiles, and how to reach him)
   ═══════════════════════════════════════════════════ */
footer.site-footer{
  display:block;padding:0;text-align:left;
  background:var(--ink2,#141821);
  border-top:1px solid var(--border);
}
.site-footer .ft-top{
  display:grid;
  grid-template-columns:minmax(0,1.6fr) repeat(3,minmax(0,1fr));
  gap:clamp(24px,4vw,48px);
  padding:clamp(34px,5vw,58px) clamp(18px,4vw,46px) clamp(26px,3vw,38px);
  max-width:1400px;margin:0 auto;
}
.site-footer .ft-brand{min-width:0}
.site-footer .ft-mark{display:block;margin-bottom:14px;line-height:0}
/* the eagle art is a circular badge: crop to the circle so neither mark
   shows an opaque square against light chrome */
.site-footer .ft-mark img,.nav-mark img{border-radius:50%}
.site-footer .ft-name{
  font-family:"Syne",system-ui,sans-serif;font-weight:700;
  font-size:clamp(17px,2.2vw,21px);letter-spacing:-.4px;
  color:var(--paper);margin-bottom:10px;
}
.site-footer .ft-tag{
  font-family:"Lora",Georgia,serif;font-size:13.5px;line-height:1.7;
  color:var(--dim);max-width:34ch;margin:0 0 16px;
}
.site-footer .ft-open{
  display:inline-flex;align-items:flex-start;gap:8px;
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:9.5px;letter-spacing:1.5px;text-transform:uppercase;
  line-height:1.6;max-width:100%;text-wrap:pretty;
  color:var(--green,#39ff8a);
  border:1px solid rgba(57,255,138,.28);border-radius:2px;padding:6px 10px;
}
.site-footer .ft-dot{margin-top:5px}
.site-footer .ft-dot{
  width:5px;height:5px;border-radius:50%;background:var(--green,#39ff8a);flex:none;
}
/* a footer column must never pick up the page's global nav{} bar rules
   (sticky, 58px tall, blurred background, 46px side padding) */
.site-footer .ft-col,
.site-footer nav.ft-col{
  display:flex;flex-direction:column;align-items:flex-start;min-width:0;
  position:static;height:auto;min-height:0;padding:0;margin:0;
  background:none;backdrop-filter:none;border:0;box-shadow:none;z-index:auto;
}
.site-footer .ft-h{
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:9px;letter-spacing:2px;text-transform:uppercase;
  color:var(--dim,rgba(200,220,232,.62));
  padding-bottom:14px;
}
.site-footer .ft-col a,
.site-footer .ft-here,
.site-footer .ft-loc{
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:11.5px;letter-spacing:.6px;line-height:1;
  text-decoration:none;padding:9px 0;min-height:0;
}
.site-footer .ft-col a{color:var(--dim);transition:color .2s ease}
.site-footer .ft-col a:hover,
.site-footer .ft-col a:focus-visible{color:var(--cyan,#00d4ff)}
.site-footer .ft-here{color:var(--paper);position:relative;padding-left:12px}
.site-footer .ft-here::before{
  content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:4px;height:4px;border-radius:50%;background:var(--accent,#e05c2a);
}
.site-footer .ft-loc{color:var(--dim,rgba(200,220,232,.62));padding-top:14px;line-height:1.6}
.site-footer .ft-bottom{
  display:flex;align-items:center;justify-content:center;
  gap:10px;flex-wrap:wrap;
  padding:16px clamp(18px,4vw,46px);
  border-top:1px solid var(--border);
}
.site-footer .footer-center{
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:10px;letter-spacing:1.4px;text-transform:uppercase;
  color:var(--dim,rgba(200,220,232,.62));text-align:center;
}
/* the phone number is a plain link here: no room for a copy affordance
   in a footer column (features.js also skips it, this is the belt) */
.site-footer .copy-btn{display:none !important}
.site-footer .ft-col a{max-width:100%}
@media (max-width:900px){
  .site-footer .ft-top{grid-template-columns:repeat(2,minmax(0,1fr))}
  .site-footer .ft-brand{grid-column:1 / -1}
}
@media (max-width:520px){
  .site-footer .ft-top{grid-template-columns:1fr;gap:26px}
}
@media print{.site-footer .ft-top{display:none}}
