/* ==========================================================================
   ROOT VARIABLES: Project Archon palette (dark grey / red)
   ========================================================================== */
:root {
  --color-bg: #1a1a1a;
  --color-panel: #242427;
  --color-panel-alt: #1f1f22;
  --color-accent: #dc2626;
  --color-accent-dim: #991b1b;
  --color-accent-hover: #ef4444;
  --color-on-accent: #ffffff;
  --color-text-secondary: #b4b4b8;
  --color-text-body: #e5e5e7;
  --color-heading: #ffffff;
  --color-border: rgba(180, 180, 184, 0.14);
  --color-nav-bg: rgba(26, 26, 26, 0.85);
  --color-nav-bg-scrolled: rgba(26, 26, 26, 0.97);
  --color-hero-mid: #202023;
  --color-section-alt: rgba(23, 26, 31, 0.78);

  --font-display: 'Rajdhani', sans-serif;
  --font-body: 'Inter', sans-serif;
  --font-mono: 'Consolas', 'SFMono-Regular', Menlo, Monaco, 'Courier New', monospace;

  --nav-height: 64px;
  --max-width: 1180px;

  --shadow-card: 0 8px 24px rgba(0, 0, 0, 0.35);
  --transition: 0.25s ease;
}

/* ==========================================================================
   LIGHT THEME: toggled via data-theme on <html>, see js/script.js.
   Accent red is intentionally unchanged between themes.
   ========================================================================== */
html[data-theme="light"] {
  --color-bg: #f4f4f5;
  --color-panel: #ffffff;
  --color-panel-alt: #ececee;
  --color-text-secondary: #52525b;
  --color-text-body: #27272a;
  --color-heading: #18181b;
  --color-border: rgba(39, 39, 42, 0.12);
  --color-nav-bg: rgba(255, 255, 255, 0.88);
  --color-nav-bg-scrolled: rgba(255, 255, 255, 0.97);
  --color-hero-mid: #e4e4e7;
  --color-section-alt: #ececee;
  --shadow-card: 0 8px 24px rgba(39, 39, 42, 0.1);
}

/* Light theme keeps the same map/nodes/traffic, just re-toned for a light
   backdrop instead of the dark theme's near-black one -- muted land and
   grid instead of charcoal, a much softer region glow, dark-on-light
   ticks. Node/connection/packet reds are left as-is; they already read
   fine against a light background. */
html[data-theme="light"] .network-bg {
  background: linear-gradient(160deg, #f6f6f7 0%, #f2f2f3 55%, #eeeeef 100%);
}

html[data-theme="light"] .network-bg__grid {
  stroke: rgba(100, 110, 120, 0.12);
}

html[data-theme="light"] .network-bg__land {
  fill: rgba(150, 158, 168, 0.11);
  stroke: rgba(100, 110, 122, 0.12);
}

html[data-theme="light"] .network-bg__ticks {
  stroke: rgba(80, 85, 92, 0.35);
  fill: rgba(80, 85, 92, 0.45);
}

html[data-theme="light"] .network-bg__glows ellipse {
  opacity: 0.3;
}

html[data-theme="light"] #networkRegionGlow stop:first-child {
  stop-opacity: 0.22;
}

/* Transparent, same as dark theme and for the same reason: body's own
   background would otherwise paint over the fixed, negative-z-index
   .network-bg layer and hide it entirely. html's own background-color
   (theme-aware, see the base html rule) is what actually shows behind the
   map wherever it doesn't cover -- functionally identical to an opaque
   body in every case except this one. */
html[data-theme="light"] body {
  background-color: transparent;
}

/* The hero scrim and per-section wash exist to protect text against the
   dark theme's much bolder map -- light theme's map is muted enough
   (see the .network-bg light-theme overrides above) that it doesn't need
   the same protection, and a dark tint here would just sit on top of the
   light background looking like a smudge. Higher specificity here beats
   both the desktop and mobile .hero background rules regardless of
   source order. */
html[data-theme="light"] .hero,
html[data-theme="light"] .stats,
html[data-theme="light"] .section:not(.section--alt) {
  background: none;
}

/* Same reasoning as above, scoped to the credential path's own vignette --
   it exists only to keep labels readable over the dark map, which doesn't
   exist in light theme either. */
html[data-theme="light"] .cert-path {
  background: none;
}

/* ==========================================================================
   RESET & BASE
   ========================================================================== */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* The top bar is fixed at every width now, so anchor-jump targets always
     need this much clearance to land below it rather than under it. */
  scroll-padding-top: calc(var(--nav-height) + env(safe-area-inset-top, 0px));
  /* Real background color (theme-aware via the variable), not just a
     backdrop for body -- this is what mobile browsers reveal during
     overscroll/rubber-band bounce at the top and bottom of the page, and
     what's visible for the brief moment before the network-bg partial's
     fetch resolves (see partials/network-bg.html). Without this, both of
     those show the browser's default white instead of the site's theme. */
  background-color: var(--color-bg);
}

body {
  /* Transparent so the fixed .network-bg layer (z-index: -1, behind all
     normal content) is the page's true backdrop over html's own
     background-color above -- individual cards/panels keep their own
     opaque backgrounds for readability; this is only the open space
     between them. */
  background-color: transparent;
  color: var(--color-text-body);
  font-family: var(--font-body);
  line-height: 1.6;
  overflow-x: hidden;
  transition: background-color 0.3s ease, color 0.3s ease;
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

ul {
  list-style: none;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--color-heading);
}

/* ==========================================================================
   GLOBAL NETWORK BACKGROUND
   Fixed, site-wide layer sitting behind every section (negative z-index —
   body and .section--alt are translucent specifically so this shows
   through, see their rules). Continents/nodes are static, always-visible
   markup in index.html; js/script.js's "NETWORK BACKGROUND" block only
   adds the animated arcs and traveling packets on top, into
   .network-bg__connections. Hidden entirely in light theme — see the
   html[data-theme="light"] rule near the top of this file.
   ========================================================================== */
.network-bg {
  position: fixed;
  top: 0;
  bottom: 0;
  /* Wider than the viewport and shifted so it can bleed off the left/right
     edges rather than sitting neatly contained — see js/script.js's
     region-glow reactivity for the only other place these dimensions are
     assumed (none; glow positions live in the SVG's own coordinate space
     so they scale with this regardless of the exact percentages here). */
  left: -12.5%;
  width: 125%;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background: linear-gradient(160deg, #12161b 0%, #0d1014 55%, #090b0e 100%);
}

.network-bg__svg {
  width: 100%;
  height: 100%;
  display: block;
  /* Extremely subtle parallax, see js/script.js — moves at a fraction of
     scroll speed via this custom property rather than scroll-linked JS
     writing transform directly every frame. */
  transform: translateY(var(--parallax-y, 0px));
  will-change: transform;
}

.network-bg__grid {
  stroke: rgba(140, 150, 160, 0.08);
  stroke-width: 0.5;
}

.network-bg__land {
  /* Slightly translucent rather than a flat opaque fill so the lat/long
     grid lines behind the map still show faintly through land — an
     "extremely subtle internal texture" for free, no separate pattern. */
  fill: rgba(17, 22, 28, 0.92);
  stroke: rgba(100, 110, 120, 0.16);
  stroke-width: 0.6;
}

.network-bg__ticks {
  stroke: rgba(180, 180, 184, 0.3);
  stroke-width: 1;
  fill: rgba(180, 180, 184, 0.4);
  font-family: var(--font-mono);
  font-size: 6px;
  letter-spacing: 0.03em;
}

.network-bg__node {
  fill: #991b1b;
  opacity: 0.7;
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 0.6s ease, fill 0.6s ease, transform 0.5s ease;
}

.network-bg__node--major {
  opacity: 0.8;
}

.network-bg__node.is-glow {
  fill: #dc2626;
  opacity: 1;
  transform: scale(1.35);
}

.network-bg__node.is-near {
  fill: #ef4444;
  opacity: 1;
  transform: scale(1.5);
}

.network-bg__glows ellipse {
  opacity: 0.55;
  transition: opacity 1.2s ease;
}

.network-bg__connection {
  fill: none;
  stroke: #7f1d1d;
  stroke-width: 1;
  stroke-linecap: round;
  opacity: 0;
  transition: opacity 0.6s ease;
}

.network-bg__connection.is-active {
  stroke: #991b1b;
  opacity: 0.6;
}

.network-bg__packet {
  fill: url(#networkPacketCore);
  opacity: 0;
  filter: drop-shadow(0 0 3px rgba(239, 68, 68, 0.85));
  transition: opacity 0.3s ease;
}

.network-bg__packet.is-active {
  opacity: 1;
}

@keyframes networkNodePulse {
  from {
    opacity: 0.85;
    r: var(--pulse-r-from, 2px);
  }
  to {
    opacity: 0;
    r: var(--pulse-r-to, 10px);
  }
}

.network-bg__pulse {
  fill: none;
  stroke: #dc2626;
  stroke-width: 1.25;
  pointer-events: none;
  animation: networkNodePulse 0.9s ease-out forwards;
}

/* Reduced motion: keep the static map/nodes, drop all animated traffic —
   js/script.js also checks prefersReducedMotion before spawning anything,
   this is the CSS-only backstop. */
@media (prefers-reduced-motion: reduce) {
  .network-bg__connection,
  .network-bg__packet,
  .network-bg__pulse {
    display: none;
  }

  .network-bg__svg {
    transform: none;
  }
}

/* ==========================================================================
   BUTTONS
   ========================================================================== */
.btn {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.85rem;
  padding: 0.85rem 1.75rem;
  border-radius: 4px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform var(--transition), background-color var(--transition), border-color var(--transition), box-shadow var(--transition);
}

.btn--primary {
  background-color: var(--color-accent);
  color: var(--color-on-accent);
  box-shadow: 0 4px 16px rgba(220, 38, 38, 0.25);
}

.btn--primary:hover {
  background-color: var(--color-accent-hover);
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(220, 38, 38, 0.4);
}

.btn--outline {
  background-color: transparent;
  color: var(--color-text-body);
  border-color: var(--color-border);
}

.btn--outline:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
  transform: translateY(-2px);
}

/* ==========================================================================
   SIDEBAR NAV (name kept for history -- it's a top bar at every width now)
   Compact fixed top bar with a hamburger-triggered slide-out panel by
   default (every width up to and including small laptops); at >=1100px
   (see the "WIDE DESKTOP NAV" block further down) the panel's links show
   inline in the bar itself instead, and the hamburger hides.
   ========================================================================== */
.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 1000;
  display: flex;
  flex-direction: row;
  align-items: center;
  /* Safe-area inset falls back to 0px on devices without a notch/dynamic
     island, so this is identical to plain var(--nav-height) there. */
  height: calc(var(--nav-height) + env(safe-area-inset-top, 0px));
  padding: env(safe-area-inset-top, 0px) max(1.5rem, env(safe-area-inset-right, 0px)) 0 max(1.5rem, env(safe-area-inset-left, 0px));
  background-color: var(--color-nav-bg);
  border-bottom: 1px solid var(--color-border);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  transition: height var(--transition), background-color var(--transition), box-shadow var(--transition);
}

/* Applied by JS once the user scrolls past the hero (or, on pages without
   one, a fixed fallback distance), see js/script.js. */
.sidebar.is-scrolled {
  height: calc(56px + env(safe-area-inset-top, 0px));
  background-color: var(--color-nav-bg-scrolled);
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}

.sidebar.is-scrolled .navbar__badge {
  width: 32px;
  height: 32px;
  font-size: 0.88rem;
}

.sidebar__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex: 1 1 auto;
}

.sidebar__brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
  margin-right: auto;
}

.navbar__badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  background-color: var(--color-accent);
  color: var(--color-on-accent);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1rem;
  border-radius: 6px;
  letter-spacing: 0.02em;
}

.sidebar__wordmark {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--color-heading);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Slide-out drawer by default, toggled via the hamburger and .is-open (see
   js/script.js) -- becomes a plain inline row at >=1100px instead, see the
   "WIDE DESKTOP NAV" block. */
.sidebar__nav {
  position: fixed;
  top: calc(var(--nav-height) + env(safe-area-inset-top, 0px));
  right: 0;
  left: auto;
  width: 100%;
  max-width: 320px;
  height: calc(100vh - var(--nav-height) - env(safe-area-inset-top, 0px));
  z-index: 998;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: stretch;
  gap: 0;
  padding: 1.5rem;
  background-color: var(--color-nav-bg-scrolled);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-left: 1px solid var(--color-border);
  transform: translateX(100%);
  transition: transform var(--transition);
  overflow-y: auto;
}

.sidebar__nav.is-open {
  transform: translateX(0);
}

/* Command-list style links: monospace like the hero terminal, not the
   uppercase Rajdhani tabs a traditional horizontal nav would use. */
.sidebar__link {
  display: flex;
  align-items: center;
  width: 100%;
  font-family: var(--font-mono);
  font-size: 0.95rem;
  padding: 1rem 0.75rem;
  border-radius: 0;
  color: var(--color-text-secondary);
  border-bottom: 1px solid var(--color-border);
  transition: color var(--transition), background-color var(--transition), border-color var(--transition);
}

.sidebar__prompt {
  color: var(--color-accent-dim);
  font-weight: 600;
  transition: color var(--transition);
}

/* Groups nav links into identity/background, work output, and contact --
   see partials/nav.html. */
.sidebar__divider {
  height: 1px;
  margin: 0.75rem 0.75rem;
  background-color: var(--color-border);
}

/* Resume download -- an action, not a page, so it reads differently from
   the plain nav links above it: accent-colored, its own outline, an icon
   instead of the "~/" prompt. Persistent across every page (see
   partials/nav.html) so it's never more than one click away. */
.sidebar__link.sidebar__resume {
  gap: 0.6rem;
  color: var(--color-accent);
  font-weight: 600;
  border: 1px solid rgba(220, 38, 38, 0.35);
  border-left: 1px solid rgba(220, 38, 38, 0.35);
}

.sidebar__link.sidebar__resume:hover {
  color: var(--color-on-accent);
  background-color: var(--color-accent);
  border-color: var(--color-accent);
}

.sidebar__resume-icon {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

.sidebar__link:hover {
  color: var(--color-text-body);
  background-color: rgba(220, 38, 38, 0.06);
}

/* Marked by js/include.js to match the current page's filename */
.sidebar__link.is-active {
  color: var(--color-accent);
  background-color: rgba(220, 38, 38, 0.1);
  border-bottom-color: var(--color-accent);
}

.sidebar__link.is-active .sidebar__prompt {
  color: var(--color-accent);
}

/* Dims the page behind the slide-out panel -- see js/script.js for the
   show/hide logic tied to the hamburger toggle. At >=1100px the hamburger
   is hidden and this is simply never un-hidden (see "WIDE DESKTOP NAV"). */
.sidebar-scrim {
  position: fixed;
  inset: 0;
  z-index: 999;
  background-color: rgba(0, 0, 0, 0.5);
}

.sidebar-scrim[hidden] {
  display: none;
}

/* ==========================================================================
   SCROLL PROGRESS BAR
   Lives outside the sidebar now (see index.html) so it can stay pinned to
   the very top of the viewport in both the desktop sidebar layout and the
   mobile top-bar layout, rather than tracking either one's box. Fill width
   is set directly in JS as a 1:1 reflection of scroll position, no CSS
   transition, so it stays glued to the scrollbar instead of lagging.
   ========================================================================== */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  z-index: 1100;
  background-color: transparent;
  pointer-events: none;
}

.scroll-progress__bar {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--color-accent), var(--color-accent-dim));
}

/* ==========================================================================
   THEME TOGGLE
   ========================================================================== */
.theme-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: none;
  color: var(--color-text-secondary);
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color var(--transition), color var(--transition), transform var(--transition);
}

.theme-toggle:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
  transform: rotate(12deg);
}

.theme-toggle__icon {
  width: 18px;
  height: 18px;
}

.theme-toggle__icon--moon {
  display: none;
}

/* Sun shows in dark mode (click to go light), moon shows in light mode (click to go dark) */
html[data-theme="light"] .theme-toggle__icon--sun {
  display: none;
}

html[data-theme="light"] .theme-toggle__icon--moon {
  display: block;
}

.navbar__toggle {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: 26px;
  height: 18px;
  background: none;
  border: none;
  cursor: pointer;
  z-index: 1100;
}

.navbar__toggle span {
  display: block;
  height: 2px;
  width: 100%;
  background-color: var(--color-text-body);
  border-radius: 2px;
  transition: transform var(--transition), opacity var(--transition), background-color var(--transition);
}

.navbar__toggle[aria-expanded="true"] span:nth-child(1) {
  transform: translateY(8px) rotate(45deg);
  background-color: var(--color-accent);
}

.navbar__toggle[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
}

.navbar__toggle[aria-expanded="true"] span:nth-child(3) {
  transform: translateY(-8px) rotate(-45deg);
  background-color: var(--color-accent);
}

/* ==========================================================================
   HERO
   ========================================================================== */
.hero {
  position: relative;
  /* Content-sized rather than forced to 100vh -- a full-viewport-height
     hero on a tall/wide monitor left a large empty gap between the (fixed-
     height) content and the Stats section below it, since align-items:
     center was splitting that leftover space above and below. Generous
     padding here keeps it feeling like a proper hero without that gap. */
  display: flex;
  align-items: center;
  overflow: hidden;
  /* Top padding clears the always-fixed top bar (plus a little breathing
     room); bottom stays generous so content doesn't crowd Stats below it. */
  padding: calc(var(--nav-height) + env(safe-area-inset-top, 0px) + 2rem) 1.5rem 7rem;
  /* The hero's own atmosphere now comes entirely from the shared, site-wide
     .network-bg layer behind it (see body > .network-bg). This is just a
     readability scrim: darker over the text/portrait column, easing off
     toward the outer edges so more of the map shows there, per the same
     "content areas stay readable, margins show more map" rule every
     section below uses. */
  background: radial-gradient(ellipse 75% 85% at 45% 50%, rgba(8, 10, 13, 0.9), rgba(8, 10, 13, 0.6) 60%, transparent 100%);
}

.hero__inner {
  position: relative;
  z-index: 1;
  max-width: var(--max-width);
  margin: 0 auto;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 3rem;
}

.hero__content {
  flex: 1 1 480px;
  animation: heroFadeUp 0.9s ease both;
}

.hero__eyebrow {
  font-family: var(--font-display);
  color: var(--color-accent);
  text-transform: uppercase;
  letter-spacing: 0.25em;
  font-size: 0.85rem;
  margin-bottom: 1rem;
}

.hero__name {
  font-size: clamp(2.5rem, 6vw, 4.5rem);
  line-height: 1.05;
  margin-bottom: 0.75rem;
}

.hero__title {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--color-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: clamp(1rem, 2.2vw, 1.4rem);
  margin-bottom: 1.5rem;
}

/* Typed out by JS on load, see js/script.js. The blinking bar echoes a
   terminal cursor, a small nod to the security/tech theme. */
html.js-enabled .hero__title-text::after {
  content: '';
  display: inline-block;
  width: 2px;
  height: 0.85em;
  margin-left: 3px;
  vertical-align: -0.1em;
  background-color: var(--color-accent);
  animation: caretBlink 0.9s step-end infinite;
}

@keyframes caretBlink {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}

.hero__tagline {
  max-width: 640px;
  color: var(--color-text-body);
  font-size: 1.05rem;
  margin-bottom: 2.25rem;
}

.hero__focus {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 2.25rem;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--color-accent);
}

.hero__focus li:not(:last-child)::after {
  content: '\00b7';
  margin-left: 0.6rem;
  color: var(--color-text-secondary);
}

.hero__actions {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

/* Profile photo with an animated red glow ring */
.hero__media {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
  animation: heroFadeUp 0.9s ease 0.15s both;
}

.hero__photo-ring {
  width: 320px;
  height: 320px;
  border-radius: 50%;
  padding: 6px;
  background: linear-gradient(135deg, var(--color-accent), rgba(220, 38, 38, 0.15));
  animation: photoGlow 4s ease-in-out infinite;
}

.hero__photo {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  border: 4px solid var(--color-bg);
}

@keyframes photoGlow {
  0%, 100% {
    box-shadow: 0 0 0 1px rgba(220, 38, 38, 0.25), 0 8px 40px rgba(220, 38, 38, 0.25);
  }
  50% {
    box-shadow: 0 0 0 1px rgba(220, 38, 38, 0.45), 0 8px 55px rgba(220, 38, 38, 0.45);
  }
}

@keyframes heroFadeUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ==========================================================================
   HERO TERMINAL
   Typed by JS on load, see js/script.js. The HTML already holds the full
   transcript, so a failed script just leaves it fully readable, unanimated.
   ========================================================================== */
.terminal {
  margin-top: 2rem;
  max-width: 420px;
  border-radius: 10px;
  overflow: hidden;
  background-color: var(--color-panel);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-card);
}

.terminal__bar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.6rem 0.85rem;
  background-color: rgba(0, 0, 0, 0.2);
  border-bottom: 1px solid var(--color-border);
}

.terminal__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
}

.terminal__dot--red {
  background-color: #ff5f56;
}

.terminal__dot--yellow {
  background-color: #ffbd2e;
}

.terminal__dot--green {
  background-color: #27c93f;
}

.terminal__bar-title {
  margin-left: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--color-text-secondary);
}

.terminal__body {
  margin: 0;
  padding: 1rem 1.1rem 1.2rem;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  line-height: 1.7;
  color: var(--color-text-secondary);
  white-space: pre-wrap;
  word-break: break-word;
  min-height: 5.9em;
}

.terminal__prompt {
  color: var(--color-accent);
  font-weight: 700;
}

/* Blinking cursor appended after the last typed character, JS controlled */
html.js-enabled .terminal__body.is-typing::after,
html.js-enabled .terminal__body.is-done::after {
  content: '';
  display: inline-block;
  width: 7px;
  height: 1em;
  margin-left: 2px;
  vertical-align: -0.15em;
  background-color: var(--color-accent);
  animation: caretBlink 0.9s step-end infinite;
}

/* ==========================================================================
   STATS BAR
   ========================================================================== */
.stats {
  padding: 0 1.5rem 5rem;
  margin-top: -3.5rem;
  /* Matches .section's wash below — without it, this sits between the
     hero's own (much stronger, fading) scrim and .about's wash with no
     wash of its own, showing up as a visible seam at the hero/stats
     boundary. */
  background-color: rgba(9, 11, 14, 0.55);
}

.stats__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
}

.stat-card {
  background-color: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 1.75rem 1.25rem;
  text-align: center;
  box-shadow: var(--shadow-card);
  transition: transform var(--transition), border-color var(--transition);
}

.stat-card:hover {
  transform: translateY(-4px);
  border-color: rgba(220, 38, 38, 0.4);
}

.stat-card__number {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2rem, 4vw, 2.75rem);
  color: var(--color-accent);
  line-height: 1;
  margin-bottom: 0.5rem;
}

.stat-card__label {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.8rem;
  color: var(--color-text-secondary);
}

/* ==========================================================================
   SECTION LAYOUT (shared)
   ========================================================================== */
.section {
  padding: 6rem 1.5rem;
  /* Every section gets some wash over the shared .network-bg map so body
     text stays comfortably readable; .section--alt below layers on a
     second, slightly different tint for the existing alternating rhythm
     between sections. */
  background-color: rgba(9, 11, 14, 0.55);
}

.section--alt {
  background-color: var(--color-section-alt);
  transition: background-color var(--transition);
}

.section__inner {
  max-width: var(--max-width);
  margin: 0 auto;
}

.section__inner--narrow {
  max-width: 820px;
}

.section__inner--center {
  text-align: center;
}

.section__eyebrow {
  font-family: var(--font-display);
  color: var(--color-accent);
  text-transform: uppercase;
  letter-spacing: 0.25em;
  font-size: 0.8rem;
  margin-bottom: 0.5rem;
}

.section__title {
  position: relative;
  font-size: clamp(1.8rem, 3.5vw, 2.5rem);
  margin-bottom: 2.5rem;
  padding-bottom: 0.85rem;
}

/* Optional one-line intro directly under a .section__title — pulls up
   into the gap the title's own margin-bottom already leaves, rather than
   stacking an extra gap on top of it. */
.section__lead {
  max-width: 640px;
  margin: -1.5rem 0 2.5rem;
  color: var(--color-text-secondary);
}

/* Animated underline draws in left to right once the title scrolls into
   view. Reuses the existing .reveal/.is-visible classes already applied
   to .section__title, no extra JS observation needed. */
.section__title::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  width: 0;
  background: linear-gradient(90deg, var(--color-accent), var(--color-accent-dim));
  border-radius: 2px;
  transition: width 0.7s ease 0.2s;
}

.section__title.is-visible::after {
  width: 56px;
}

.section__inner--center .section__title::after {
  left: 50%;
  transform: translateX(-50%);
}

/* ==========================================================================
   SECTION DIVIDER
   Small reusable seam between major sections: a gradient line fading from
   transparent to accent and back, with a centered hexagon glyph echoing
   the Archon logo mark. Purely decorative.
   ========================================================================== */
.section-divider {
  position: relative;
  height: 1px;
  /* Cancels the section's own top padding so the line sits right at the
     seam with the previous section, rather than floating inside the
     padded area. Mirrored in the tablet/mobile media queries below,
     where .section padding shrinks. */
  margin: -6rem 0 4rem;
  background: linear-gradient(90deg, transparent, rgba(220, 38, 38, 0.45), transparent);
}

.section-divider__hex {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 11px;
  height: 11px;
  transform: translate(-50%, -50%);
  background-color: var(--color-bg);
  border: 1.5px solid var(--color-accent);
  clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
}

/* Matches the alt section tint so the glyph reads as a notch cut into
   whichever background it sits on, not a filled dot */
.section--alt .section-divider__hex {
  background-color: var(--color-section-alt);
}

/* ==========================================================================
   ABOUT
   ========================================================================== */
.about__bio {
  font-size: 1.1rem;
  color: var(--color-text-body);
}

/* ==========================================================================
   PULL-QUOTE
   ========================================================================== */
.pull-quote {
  position: relative;
  margin: 2.5rem 0;
  padding: 0.25rem 0 0.25rem 1.75rem;
  border-left: 3px solid var(--color-accent);
}

.pull-quote p {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.15rem, 2.4vw, 1.5rem);
  line-height: 1.5;
  color: var(--color-heading);
}

/* ==========================================================================
   CORE SKILLS
   ========================================================================== */
.skills-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2rem;
}

.skills-grid > .skills-group:last-child:nth-child(odd) {
  grid-column: 1 / -1;
}

.skills-group__title {
  font-size: 1.1rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-accent);
  margin-bottom: 1rem;
}

.pill-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.pill {
  position: relative;
  background-color: var(--color-panel);
  border: 1px solid var(--color-border);
  color: var(--color-text-secondary);
  font-size: 0.85rem;
  padding: 0.5rem 1rem;
  border-radius: 999px;
  transition: border-color var(--transition), color var(--transition), box-shadow var(--transition),
    opacity 0.4s ease, transform 0.35s ease;
}

.pill:hover {
  border-color: var(--color-accent);
  color: var(--color-text-body);
  transform: translateY(-2px) scale(1.05);
  box-shadow: 0 6px 18px rgba(220, 38, 38, 0.22);
}

/* Cascade the tags in one group after another once their parent reveals */
html.js-enabled .pill {
  opacity: 0;
  transform: translateY(10px);
}

html.js-enabled .skills-group.is-visible .pill,
html.js-enabled .growth.is-visible .pill {
  opacity: 1;
  transform: translateY(0);
}

html.js-enabled .skills-group.is-visible .pill:nth-child(1),
html.js-enabled .growth.is-visible .pill:nth-child(1) { transition-delay: 0.03s; }
html.js-enabled .skills-group.is-visible .pill:nth-child(2),
html.js-enabled .growth.is-visible .pill:nth-child(2) { transition-delay: 0.08s; }
html.js-enabled .skills-group.is-visible .pill:nth-child(3),
html.js-enabled .growth.is-visible .pill:nth-child(3) { transition-delay: 0.13s; }
html.js-enabled .skills-group.is-visible .pill:nth-child(4),
html.js-enabled .growth.is-visible .pill:nth-child(4) { transition-delay: 0.18s; }
html.js-enabled .skills-group.is-visible .pill:nth-child(5),
html.js-enabled .growth.is-visible .pill:nth-child(5) { transition-delay: 0.23s; }
html.js-enabled .skills-group.is-visible .pill:nth-child(6),
html.js-enabled .growth.is-visible .pill:nth-child(6) { transition-delay: 0.28s; }
html.js-enabled .skills-group.is-visible .pill:nth-child(7),
html.js-enabled .growth.is-visible .pill:nth-child(7) { transition-delay: 0.33s; }

/* Thin proficiency bar under Core Skills tags, driven by the data-level
   attribute in index.html (high/mid/low). Tags without the attribute,
   like the Currently Expanding Into pills, are unaffected and keep their
   normal compact padding. The fill grows in once the group reveals. */
.pill[data-level] {
  padding-bottom: 1.1rem;
}

.pill[data-level]::after {
  content: '';
  position: absolute;
  left: 1rem;
  bottom: 0.55rem;
  width: calc(100% - 2rem);
  height: 2px;
  background-color: rgba(220, 38, 38, 0.18);
  border-radius: 2px;
}

.pill[data-level]::before {
  content: '';
  position: absolute;
  left: 1rem;
  bottom: 0.55rem;
  width: 0;
  height: 2px;
  border-radius: 2px;
  background-color: var(--color-accent);
  z-index: 1;
  transition: width 0.8s ease 0.3s;
}

.skills-group.is-visible .pill[data-level="low"]::before {
  width: calc((100% - 2rem) * 0.3);
}

.skills-group.is-visible .pill[data-level="mid"]::before {
  width: calc((100% - 2rem) * 0.62);
}

.skills-group.is-visible .pill[data-level="high"]::before {
  width: calc((100% - 2rem) * 0.92);
}

/* ==========================================================================
   CONTINUOUS GROWTH (within About)
   ========================================================================== */
.growth {
  margin-top: 3rem;
  padding-top: 2.5rem;
  border-top: 1px solid var(--color-border);
}

.growth__heading {
  font-size: 1.3rem;
  margin-bottom: 0.75rem;
}

.growth__line {
  color: var(--color-text-body);
  max-width: 640px;
  margin-bottom: 2rem;
}

.growth__line strong {
  color: var(--color-accent);
  font-weight: 600;
}

/* ==========================================================================
   CREDENTIAL PATH ("Always Leveling Up")
   Data-driven from js/cert-path-data.js, rendered by js/script.js's
   "CREDENTIAL PATH RENDER" block. .cert-path__main is the completed
   chronology (A+ through GSEC) laid out as a wide serpentine grid on
   desktop (JS assigns each node's grid-column/grid-row and a data-connector
   direction, see layoutCertPath()) and a plain single-column vertical flow
   on mobile (CSS below, inline grid placement is simply inert there since
   the container isn't display:grid at that width). .cert-path__branches is
   what follows GSEC: two tracks in progress at once (Academic, Offensive
   Security), branching rather than continuing the same line so the diagram
   doesn't imply one must finish before the other starts.
   ========================================================================== */
.cert-path {
  position: relative;
  margin-top: 0.5rem;
  padding: 1.75rem 0.5rem 0.5rem;
  /* Soft vignette, not a card -- just enough to keep labels readable over
     the site-wide network map without boxing the whole diagram in. */
  background: radial-gradient(ellipse 90% 100% at 50% 35%, rgba(10, 12, 15, 0.4), rgba(10, 12, 15, 0.12) 70%, transparent 100%);
  border-radius: 16px;
}

.cert-path__main {
  display: grid;
  grid-template-columns: repeat(var(--cert-cols, 5), minmax(100px, 1fr));
  grid-auto-rows: 92px;
  list-style: none;
}

/* Cascade in left-to-right/top-to-bottom as .growth scrolls into view, same
   pattern as the badge/pill cascades elsewhere on the page. */
html.js-enabled .cert-path__node {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.45s ease, transform 0.45s ease;
}

html.js-enabled .growth.is-visible .cert-path__node,
html.js-enabled .growth.is-visible .cert-path__branch-nodes .cert-path__node {
  opacity: 1;
  transform: translateY(0);
}

html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(1) { transition-delay: 0.03s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(2) { transition-delay: 0.06s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(3) { transition-delay: 0.09s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(4) { transition-delay: 0.12s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(5) { transition-delay: 0.15s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(6) { transition-delay: 0.18s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(7) { transition-delay: 0.21s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(8) { transition-delay: 0.24s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(9) { transition-delay: 0.27s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(10) { transition-delay: 0.3s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(11) { transition-delay: 0.33s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(12) { transition-delay: 0.36s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(13) { transition-delay: 0.39s; }
html.js-enabled .growth.is-visible .cert-path__main .cert-path__node:nth-child(14) { transition-delay: 0.42s; }
html.js-enabled .growth.is-visible .cert-path__branch-nodes .cert-path__node { transition-delay: 0.46s; }

.cert-path__node {
  position: relative;
  height: 92px;
}

.cert-path__marker {
  position: absolute;
  top: 26px;
  left: 50%;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background-color: #2a2a2e;
  border: 1.5px solid rgba(220, 38, 38, 0.4);
  z-index: 2;
  transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition), background-color var(--transition);
}

/* Small inset checkmark on completed nodes -- the illuminated path already
   communicates "done", this is just a quiet extra confirmation on hover/focus. */
.cert-path__marker::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background-color: var(--color-text-secondary);
  opacity: 0;
  transform: scale(0.4);
  transition: opacity var(--transition), transform var(--transition);
}

.cert-path__label {
  position: absolute;
  top: 40px;
  left: 50%;
  width: 100%;
  max-width: 130px;
  transform: translateX(-50%);
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.76rem;
  line-height: 1.25;
  color: var(--color-text-secondary);
  transition: color var(--transition);
}

.cert-path__status {
  position: absolute;
  top: 65px;
  left: 50%;
  width: 100%;
  transform: translateX(-50%);
  text-align: center;
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.6rem;
  color: var(--color-accent);
  white-space: nowrap;
}

/* Node states -------------------------------------------------------------- */
.cert-path__node[data-status="active"] .cert-path__marker {
  width: 15px;
  height: 15px;
  background-color: var(--color-accent);
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15);
  animation: pulseCurrent 3s ease-in-out infinite;
}

.cert-path__node[data-status="active"] .cert-path__label {
  color: var(--color-heading);
  font-weight: 600;
}

.cert-path__node[data-status="planned"] .cert-path__marker {
  background-color: transparent;
  border: 1.5px solid rgba(180, 180, 184, 0.4);
}

.cert-path__node[data-status="planned"] .cert-path__label {
  color: var(--color-text-secondary);
  opacity: 0.65;
}

.cert-path__node[data-status="planned"] .cert-path__status {
  color: var(--color-text-secondary);
}

/* Hover / focus -------------------------------------------------------------
   :has() lets a completed node also brighten the connector segment drawn by
   the PREVIOUS node (each node only draws its own outgoing connector, see
   layoutCertPath()) -- browsers without :has() simply keep the node's own
   outgoing connector highlight, which is the more important half anyway. */
.cert-path__node:hover .cert-path__marker,
.cert-path__node:focus-visible .cert-path__marker {
  transform: translate(-50%, -50%) scale(1.25);
}

.cert-path__node[data-status="done"]:hover .cert-path__marker,
.cert-path__node[data-status="done"]:focus-visible .cert-path__marker {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.15);
}

.cert-path__node[data-status="done"]:hover .cert-path__marker::after,
.cert-path__node[data-status="done"]:focus-visible .cert-path__marker::after {
  opacity: 1;
  transform: scale(0.4);
}

.cert-path__node:hover .cert-path__label,
.cert-path__node:focus-visible .cert-path__label {
  color: var(--color-heading);
}

.cert-path__node[data-status="active"]:hover .cert-path__marker,
.cert-path__node[data-status="active"]:focus-visible .cert-path__marker {
  box-shadow: 0 0 0 5px rgba(220, 38, 38, 0.25);
}

.cert-path__node[data-status="planned"]:hover .cert-path__marker,
.cert-path__node[data-status="planned"]:focus-visible .cert-path__marker {
  border-color: rgba(220, 38, 38, 0.6);
}

.cert-path__node:hover::after,
.cert-path__node:focus-visible::after {
  background-color: rgba(220, 38, 38, 0.6) !important;
}

.cert-path__node:has(+ .cert-path__node:hover)::after,
.cert-path__node:has(+ .cert-path__node:focus-visible)::after {
  background-color: rgba(220, 38, 38, 0.6) !important;
}

/* Connectors -----------------------------------------------------------
   Desktop only -- see layoutCertPath() in js/script.js, which sets
   data-connector to the direction of the line THIS node draws toward the
   NEXT node chronologically ("right"/"left" within a row, "down" at a row
   turn). Each node only draws its own outgoing segment so a gap is never
   double-drawn; the last node (GSEC) has no data-connector and instead
   flows straight into .cert-path__fork below. Mobile ignores this entirely
   and uses a single uniform vertical rail instead (see the mobile media
   query further down). */
@media (min-width: 769px) {
  .cert-path__node[data-connector]::after {
    content: '';
    position: absolute;
    top: 26px;
    background-color: rgba(220, 38, 38, 0.32);
    z-index: 1;
    transition: background-color var(--transition);
  }

  .cert-path__node[data-connector="right"]::after {
    left: 50%;
    width: 100%;
    height: 2px;
    transform: translateY(-50%);
  }

  .cert-path__node[data-connector="left"]::after {
    right: 50%;
    width: 100%;
    height: 2px;
    transform: translateY(-50%);
  }

  .cert-path__node[data-connector="down"]::after {
    left: 50%;
    width: 2px;
    height: 92px;
    transform: translateX(-50%);
  }
}

/* Packet: a small traveling dot, positioned via left/top set from JS
   (see runCertPacketOnce() in js/script.js), reusing the same
   white-hot-center-on-crimson language as the sitewide network background's
   .network-bg__packet. */
.cert-path__packet {
  position: absolute;
  top: 0;
  left: 0;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background-color: #fff5f5;
  box-shadow: 0 0 4px 1px rgba(239, 68, 68, 0.85);
  opacity: 0;
  transform: translate(-50%, -50%);
  transition: left 0.14s linear, top 0.14s linear, opacity 0.2s ease;
  pointer-events: none;
  z-index: 3;
}

.cert-path__packet.is-active {
  opacity: 1;
}

@keyframes certPacketHit {
  0% {
    box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.5);
  }
  100% {
    box-shadow: 0 0 0 10px rgba(220, 38, 38, 0);
  }
}

@keyframes certPacketHitSoft {
  0% {
    box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.5);
  }
  100% {
    box-shadow: 0 0 0 6px rgba(220, 38, 38, 0);
  }
}

.cert-path__node.is-packet-hit .cert-path__marker {
  animation: certPacketHit 0.7s ease-out;
}

/* Current progression cluster --------------------------------------------
   Follows GSEC: a short stem drops from the main grid, spreads into a bar,
   and each branch's own lead node picks the line back up -- reads as one
   point forking into two concurrent tracks rather than a continuation of
   the same line. */
.cert-path__fork {
  position: relative;
  height: 34px;
  max-width: 420px;
  margin: 0 auto;
}

.cert-path__fork-stem {
  position: absolute;
  top: 0;
  left: 50%;
  width: 2px;
  height: 16px;
  transform: translateX(-50%);
  background-color: rgba(220, 38, 38, 0.32);
}

.cert-path__fork-bar {
  position: absolute;
  top: 16px;
  left: 15%;
  width: 70%;
  height: 2px;
  background-color: rgba(220, 38, 38, 0.32);
}

.cert-path__current-label {
  text-align: center;
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  font-size: 0.68rem;
  color: var(--color-text-secondary);
  margin-top: 0.75rem;
}

.cert-path__branches {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem 1rem;
  max-width: 460px;
  margin: 0.25rem auto 0;
}

.cert-path__branch {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 1.25rem;
}

.cert-path__branch-label {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.68rem;
  color: var(--color-text-secondary);
  margin-bottom: 0.75rem;
}

.cert-path__branch-nodes {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  list-style: none;
}

.cert-path__branch-nodes .cert-path__node {
  position: static;
  height: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0;
}

.cert-path__branch-nodes .cert-path__node .cert-path__marker,
.cert-path__branch-nodes .cert-path__node .cert-path__label,
.cert-path__branch-nodes .cert-path__node .cert-path__status {
  position: static;
  transform: none;
  max-width: none;
  width: auto;
}

.cert-path__branch-nodes .cert-path__node:hover .cert-path__marker,
.cert-path__branch-nodes .cert-path__node:focus-visible .cert-path__marker {
  transform: scale(1.25);
}

/* Short stem connecting the branch's own lead node up to .cert-path__fork-bar */
.cert-path__branch-nodes .cert-path__node:first-child::before {
  content: '';
  position: absolute;
  top: -1.25rem;
  left: 50%;
  width: 2px;
  height: 1.25rem;
  transform: translateX(-50%);
  background-color: rgba(220, 38, 38, 0.32);
}

/* Dashed connector into a planned node (PNPT), solid into an active one */
.cert-path__branch-nodes .cert-path__node + .cert-path__node::before {
  content: '';
  display: block;
  width: 2px;
  height: 1.1rem;
  margin: 0 auto;
  background-color: rgba(220, 38, 38, 0.32);
}

.cert-path__branch-nodes .cert-path__node[data-status="planned"] + .cert-path__node::before,
.cert-path__branch-nodes .cert-path__node + .cert-path__node[data-status="planned"]::before {
  background: repeating-linear-gradient(to bottom, rgba(180, 180, 184, 0.45) 0 4px, transparent 4px 8px);
}

@keyframes pulseCurrent {
  0%, 100% {
    box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15);
  }
  50% {
    box-shadow: 0 0 0 7px rgba(220, 38, 38, 0.25);
  }
}

.growth__next {
  margin-top: 2.25rem;
  padding-top: 2rem;
  border-top: 1px dashed var(--color-border);
}

.growth__next-heading {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.9rem;
  color: var(--color-text-secondary);
  margin-bottom: 1rem;
}

.growth__next-line {
  color: var(--color-text-body);
  max-width: 640px;
  margin-top: 1rem;
}

.growth__cta {
  margin-top: 1.75rem;
  padding: 1.25rem 1.5rem;
  max-width: 640px;
  border-left: 3px solid var(--color-accent);
  background-color: rgba(220, 38, 38, 0.06);
  border-radius: 0 8px 8px 0;
  transition: background-color var(--transition), border-color var(--transition);
}

.growth__cta:hover {
  background-color: rgba(220, 38, 38, 0.1);
}

.growth__cta-link {
  display: inline;
  font-style: italic;
  font-weight: 500;
  color: var(--color-accent);
  font-size: 1.02rem;
  line-height: 1.6;
}

.growth__cta-link::after {
  content: '\2192';
  display: inline-block;
  margin-left: 0.4rem;
  font-style: normal;
  transition: transform var(--transition);
}

.growth__cta-link:hover::after {
  transform: translateX(3px);
}

/* ==========================================================================
   CURRENTLY READING
   EDIT: swap the img src/title/author in index.html to update the shelf.
   ========================================================================== */
.reading {
  margin-top: 3rem;
  padding-top: 2.5rem;
  border-top: 1px solid var(--color-border);
}

.reading__heading {
  font-size: 1.3rem;
  margin-bottom: 1.5rem;
}

.book-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 1.5rem;
}

.book-card {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  transition: transform var(--transition);
}

.book-card:hover {
  transform: translateY(-4px);
}

.book-card__cover {
  width: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-card);
  transition: box-shadow var(--transition), border-color var(--transition);
}

.book-card:hover .book-card__cover {
  border-color: var(--color-accent);
  box-shadow: 0 10px 24px rgba(220, 38, 38, 0.2);
}

.book-card__title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.88rem;
  color: var(--color-text-body);
  line-height: 1.3;
}

.book-card__author {
  font-size: 0.78rem;
  color: var(--color-text-secondary);
}

/* ==========================================================================
   PROJECTS
   Rendered entirely from js/projects-data.js by js/script.js — see that
   file to add, remove, or reorder a project. .project-card--featured spans
   the full grid width for the rich write-up treatment (logo, long
   description, highlights list); .project-card--compact is the smaller
   title+description+tags+link card everything else uses.
   ========================================================================== */
.projects-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 2rem;
}

.project-card {
  background: linear-gradient(155deg, var(--color-panel) 0%, var(--color-panel-alt) 100%);
  border: 1px solid var(--color-border);
  border-radius: 14px;
  box-shadow: var(--shadow-card);
  transition: transform var(--transition), border-color var(--transition), box-shadow var(--transition);
}

.project-card:hover {
  transform: translateY(-4px);
  border-color: var(--color-accent);
  box-shadow: 0 16px 40px rgba(220, 38, 38, 0.22), var(--shadow-card);
}

.project-card--featured {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 2.5rem;
  padding: 2.5rem;
  align-items: center;
}

.project-card--compact {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1.75rem;
}

.project-card__logo {
  display: flex;
  align-items: center;
  justify-content: center;
}

.project-card__logo img {
  width: 100%;
  max-width: 220px;
  border-radius: 10px;
  border: 1px solid var(--color-border);
  filter: drop-shadow(0 8px 20px rgba(220, 38, 38, 0.2));
}

.project-card__eyebrow {
  font-family: var(--font-display);
  color: var(--color-accent);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: 0.75rem;
  margin-bottom: 0.5rem;
}

.project-card__name {
  font-size: 1.3rem;
  margin-bottom: 0.35rem;
}

.project-card--featured .project-card__name {
  font-size: 1.6rem;
}

.project-card__title {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--color-text-secondary);
  margin-bottom: 1rem;
}

.project-card__desc {
  color: var(--color-text-body);
  margin-bottom: 0;
}

.project-card--featured .project-card__desc {
  margin-bottom: 1.25rem;
}

.project-card__list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-bottom: 1.75rem;
}

.project-card__list li {
  position: relative;
  padding-left: 1.3rem;
  color: var(--color-text-secondary);
  font-size: 0.95rem;
}

.project-card__list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 6px;
  height: 6px;
  background-color: var(--color-accent);
  border-radius: 1px;
  transform: rotate(45deg);
}

/* Engagement-report style Objective/Approach/Outcome breakdown, an
   alternative to .project-card__list — see the reportFields block in
   js/script.js's renderFeaturedCard. */
.project-card__report {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  margin-bottom: 1.75rem;
  padding: 1.1rem 1.4rem;
  background-color: rgba(255, 255, 255, 0.03);
  border-left: 3px solid var(--color-accent);
  border-radius: 0 8px 8px 0;
}

.project-card__report dt {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  /* Matches the wider tracking used by .section__eyebrow/.project-card__eyebrow
     elsewhere -- at this small a size, Rajdhani's own character doesn't
     read clearly through tight letter-spacing, so it looked like a generic
     sans instead of matching the rest of the site's technical labels. */
  letter-spacing: 0.18em;
  font-size: 0.76rem;
  color: var(--color-accent);
  margin-bottom: 0.35rem;
}

.project-card__report dd {
  color: var(--color-text-body);
  font-size: 0.95rem;
  margin: 0;
}

.project-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.25rem 0 1.25rem;
}

/* .pill is reused here for styling only — its opacity/transform cascade is
   scoped to .skills-group/.growth reveal-on-scroll (see PILL LIST rules
   above), which project tags never sit inside, so it's cancelled here to
   keep them visible immediately rather than permanently opacity: 0. */
html.js-enabled .project-tags .pill {
  opacity: 1;
  transform: none;
}

.project-card--compact .project-card__link {
  align-self: flex-start;
  margin-top: 0.4rem;
}

/* ==========================================================================
   PROJECT DIAGRAM
   Optional deep-dive block a project entry can supply (see the `diagram`
   field in js/projects-data.js) — only Project Archon uses one today.
   Component chips are a hand-built legend next to the diagram rather than a
   hotspot overlay on the image itself, so tooltips stay correctly placed no
   matter how the image is scaled. Tooltip shown via CSS on hover/focus only,
   no JS needed.
   ========================================================================== */
.project-diagram {
  grid-column: 1 / -1;
  margin-top: -0.5rem;
  padding: 2.5rem;
  background: linear-gradient(155deg, var(--color-panel) 0%, var(--color-panel-alt) 100%);
  border: 1px solid var(--color-border);
  border-radius: 14px;
  box-shadow: var(--shadow-card);
}

.project-diagram__title {
  font-size: 1.3rem;
  margin-bottom: 1.5rem;
}

.project-diagram__frame {
  display: block;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-card);
  transition: border-color var(--transition), box-shadow var(--transition), transform var(--transition);
}

.project-diagram__frame:hover {
  border-color: var(--color-accent);
  box-shadow: 0 12px 32px rgba(220, 38, 38, 0.18), var(--shadow-card);
  transform: translateY(-3px);
}

.project-diagram__frame img {
  width: 100%;
  background-color: #ffffff;
}

.project-diagram__components {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

.project-diagram__component {
  position: relative;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--color-text-body);
  background-color: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: 0.55rem 1.1rem;
  cursor: help;
  transition: border-color var(--transition), color var(--transition), transform var(--transition);
}

.project-diagram__component:hover,
.project-diagram__component:focus-visible {
  border-color: var(--color-accent);
  color: var(--color-accent);
  transform: translateY(-2px);
}

.project-diagram__component::after {
  content: attr(data-tooltip);
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  transform: translateX(-50%) translateY(4px);
  width: 220px;
  padding: 0.65rem 0.85rem;
  background-color: var(--color-heading);
  color: var(--color-bg);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.78rem;
  line-height: 1.4;
  text-align: left;
  border-radius: 8px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition), transform var(--transition);
  z-index: 20;
}

.project-diagram__component:hover::after,
.project-diagram__component:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.project-diagram__download {
  margin-top: 1.75rem;
}

/* ==========================================================================
   CVE RESEARCH
   Rendered from js/cve-data.js by js/script.js -- the array starts empty,
   so .cve-empty (styled below) is what actually shows today. .cve-card
   reuses the old write-ups card's left-accent-border shape, since that
   content is expected to fold in here once there's something to show.
   ========================================================================== */
.cve-list {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.cve-card {
  background-color: var(--color-panel);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  border-radius: 0 10px 10px 0;
  padding: 1.5rem 1.75rem;
  box-shadow: var(--shadow-card);
  transition: border-color var(--transition), transform var(--transition);
}

.cve-card:hover {
  transform: translateX(4px);
}

.cve-card__top {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}

.cve-card__id {
  font-family: var(--font-mono);
  color: var(--color-accent);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.cve-card__severity {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.68rem;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  background-color: var(--color-panel-alt);
  color: var(--color-text-secondary);
  border: 1px solid var(--color-border);
}

.cve-card__severity--critical,
.cve-card__severity--high {
  background-color: rgba(220, 38, 38, 0.12);
  color: var(--color-accent);
  border-color: rgba(220, 38, 38, 0.35);
}

.cve-card__title {
  font-size: 1.15rem;
  margin-bottom: 0.6rem;
}

.cve-card__meta {
  color: var(--color-text-secondary);
  font-size: 0.88rem;
  margin-bottom: 1rem;
}

.cve-card__link {
  margin-top: 0.25rem;
}

/* Empty state shown until js/cve-data.js has at least one entry -- a
   dashed border reads as "not filled in yet" without a big empty card. */
.cve-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.6rem;
  padding: 3rem 1.5rem;
  border: 2px dashed var(--color-border);
  border-radius: 14px;
  color: var(--color-text-secondary);
}

.cve-empty__title {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--color-heading);
  font-size: 1.05rem;
}

.cve-empty__desc {
  max-width: 420px;
  font-size: 0.92rem;
}

/* ==========================================================================
   EXPERIENCE TIMELINE
   ========================================================================== */
.timeline {
  position: relative;
  padding-left: 2rem;
  border-left: 2px solid var(--color-border);
}

.timeline__item {
  position: relative;
  padding-bottom: 3rem;
}

.timeline__item:last-child {
  padding-bottom: 0;
}

.timeline__marker {
  position: absolute;
  left: -2.45rem;
  top: 0.3rem;
  width: 14px;
  height: 14px;
  background-color: var(--color-bg);
  border: 3px solid var(--color-accent);
  border-radius: 50%;
  box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.12);
  transition: box-shadow var(--transition), transform var(--transition);
}

.timeline__content {
  background-color: var(--color-panel);
  border: 1px solid var(--color-border);
  border-left: 3px solid rgba(220, 38, 38, 0.2);
  border-radius: 10px;
  padding: 1.75rem;
  box-shadow: var(--shadow-card);
  transition: transform var(--transition), border-left-color var(--transition);
}

.timeline__item:hover .timeline__content {
  transform: translateX(6px);
  border-left-color: var(--color-accent);
}

.timeline__item:hover .timeline__marker {
  transform: scale(1.15);
  box-shadow: 0 0 0 6px rgba(220, 38, 38, 0.2);
}

/* Present-day role marker pulses gently so it draws the eye first,
   reusing the same pulse keyframe as the in-progress credential path nodes */
.timeline__item--current .timeline__marker {
  background-color: var(--color-accent);
  animation: pulseCurrent 2.4s ease-in-out infinite;
}

.timeline__item--current:hover .timeline__marker {
  animation-play-state: paused;
}

.timeline__dates {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--color-accent);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.8rem;
  margin-bottom: 0.5rem;
}

.timeline__role {
  font-size: 1.2rem;
  margin-bottom: 0.25rem;
}

.timeline__org {
  color: var(--color-text-secondary);
  font-size: 0.9rem;
  margin-bottom: 1rem;
}

.timeline__list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.timeline__list li {
  position: relative;
  padding-left: 1.2rem;
  font-size: 0.92rem;
  color: var(--color-text-body);
}

.timeline__list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 5px;
  height: 5px;
  background-color: var(--color-text-secondary);
  border-radius: 50%;
}

/* ==========================================================================
   EDUCATION & CERTIFICATIONS
   ========================================================================== */
.edu-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.5rem;
}

.edu-card {
  background-color: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 1.75rem;
  box-shadow: var(--shadow-card);
}

.edu-card__title {
  font-size: 1.1rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-accent);
  margin-bottom: 1.25rem;
}

.edu-entry {
  display: flex;
  flex-direction: column;
  margin-bottom: 1.25rem;
}

.edu-entry:last-child {
  margin-bottom: 0;
}

.edu-entry__degree {
  font-weight: 600;
  color: var(--color-text-body);
  font-size: 0.98rem;
}

.edu-entry__school {
  color: var(--color-text-secondary);
  font-size: 0.88rem;
  margin-top: 0.2rem;
}

/* ==========================================================================
   CERTIFICATION BADGES
   ========================================================================== */
.badge-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 1.25rem;
}

.badge-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Sized so 5 fit per row within an edu-card at the two-column desktop
     grid width (~522px content) instead of one orphaning onto its own row
     -- see the Microsoft group, which has exactly 5 badges. */
  width: 80px;
  text-align: center;
  gap: 0.5rem;
  transition: opacity 0.45s ease, transform 0.45s ease;
}

.badge-item__img {
  width: 72px;
  height: 72px;
  object-fit: contain;
  background-color: #f5f5f5;
  border-radius: 9px;
  border: 2px solid var(--color-panel);
  padding: 0.4rem;
  box-shadow: var(--shadow-card);
  transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition);
}

.badge-item:hover .badge-item__img {
  transform: translateY(-4px);
  border-color: var(--color-accent);
  box-shadow: 0 10px 24px rgba(220, 38, 38, 0.25);
}

/* Badges backed by a Credly verification URL are wrapped in this link.
   Badges without one (see EDIT comments in index.html) are left as a
   plain, non-interactive image. The lift/glow above already fires from
   .badge-item:hover regardless of the link, this just layers on the
   small external-link cue so a linked badge reads as clickable. */
.badge-item__link {
  position: relative;
  display: block;
}

.badge-item__link-icon {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 16px;
  height: 16px;
  padding: 2px;
  background-color: var(--color-accent);
  color: var(--color-on-accent);
  border-radius: 4px;
  opacity: 0;
  transform: scale(0.75);
  transition: opacity var(--transition), transform var(--transition);
  pointer-events: none;
}

.badge-item:hover .badge-item__link-icon {
  opacity: 1;
  transform: scale(1);
}

/* Badges cascade in one after another once their card reveals */
html.js-enabled .badge-item {
  opacity: 0;
  transform: translateY(12px);
}

html.js-enabled .edu-card.is-visible .badge-item {
  opacity: 1;
  transform: translateY(0);
}

html.js-enabled .edu-card.is-visible .badge-item:nth-child(1) { transition-delay: 0.04s; }
html.js-enabled .edu-card.is-visible .badge-item:nth-child(2) { transition-delay: 0.09s; }
html.js-enabled .edu-card.is-visible .badge-item:nth-child(3) { transition-delay: 0.14s; }
html.js-enabled .edu-card.is-visible .badge-item:nth-child(4) { transition-delay: 0.19s; }
html.js-enabled .edu-card.is-visible .badge-item:nth-child(5) { transition-delay: 0.24s; }
html.js-enabled .edu-card.is-visible .badge-item:nth-child(6) { transition-delay: 0.29s; }
html.js-enabled .edu-card.is-visible .badge-item:nth-child(7) { transition-delay: 0.34s; }
html.js-enabled .edu-card.is-visible .badge-item:nth-child(8) { transition-delay: 0.39s; }
html.js-enabled .edu-card.is-visible .badge-item:nth-child(9) { transition-delay: 0.44s; }

.badge-item__caption {
  font-size: 0.72rem;
  line-height: 1.3;
  color: var(--color-text-secondary);
}

/* ==========================================================================
   GITHUB ACTIVITY FEED
   Populated client-side, see js/script.js. .gh-feed__status covers both the
   loading and error/fallback states with the same neutral styling.
   ========================================================================== */
.gh-feed {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.gh-feed__item {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  background-color: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 1rem 1.25rem;
  transition: border-color var(--transition), transform var(--transition);
}

.gh-feed__item:hover {
  border-color: var(--color-accent);
  transform: translateX(4px);
}

.gh-feed__desc {
  font-size: 0.92rem;
  color: var(--color-text-body);
}

.gh-feed__repo {
  font-family: var(--font-mono);
  color: var(--color-accent);
}

.gh-feed__time {
  flex-shrink: 0;
  font-size: 0.78rem;
  color: var(--color-text-secondary);
  white-space: nowrap;
}

.gh-feed__status {
  color: var(--color-text-secondary);
  font-size: 0.92rem;
  padding: 1rem 0;
}

.gh-feed__status a {
  color: var(--color-accent);
  text-decoration: underline;
}

/* ==========================================================================
   CONTACT
   ========================================================================== */
.contact__lead {
  color: var(--color-text-secondary);
  max-width: 560px;
  margin: 0 auto 2.5rem;
}

.contact__cta {
  display: inline-block;
  margin-bottom: 2.5rem;
}

.contact__links {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-width: 480px;
  margin: 0 auto 2.5rem;
}

.contact__link {
  background-color: var(--color-panel);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 1rem 1.5rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  transition: border-color var(--transition), transform var(--transition);
}

.contact__link:hover {
  border-color: var(--color-accent);
  transform: translateY(-2px);
}

.contact__link-label {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.75rem;
  color: var(--color-accent);
}

.contact__link-value {
  color: var(--color-text-body);
  font-size: 0.9rem;
}

/* ==========================================================================
   FOOTER
   ========================================================================== */
.footer {
  padding: 3.5rem 1.5rem;
}

/* Footer's own top padding differs from .section, so the divider needs
   its own offset rather than the -6rem/-4rem values used elsewhere */
.footer .section-divider {
  margin: -3.5rem 0 2.5rem;
}

.footer__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.5rem;
}

.footer__brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--color-text-body);
}

.footer__social {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.footer__social-link {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  color: var(--color-text-secondary);
  transition: border-color var(--transition), color var(--transition), transform var(--transition);
}

.footer__social-link svg {
  width: 16px;
  height: 16px;
}

.footer__social-link:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
  transform: translateY(-2px);
}

.footer__copy {
  color: var(--color-text-secondary);
  font-size: 0.85rem;
}

/* ==========================================================================
   SCROLL-REVEAL UTILITY (see js/script.js)
   Gated behind html.js-enabled (set as the first line of script.js) so
   content stays fully visible by default if the script never runs, rather
   than being stuck at opacity: 0 forever.
   ========================================================================== */
html.js-enabled .reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.55s ease, transform 0.55s ease;
}

html.js-enabled .reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ==========================================================================
   RESPONSIVE: TABLET
   Also where the sidebar collapses to a top bar + slide-out panel — a
   sidebar needs real horizontal room, so this is the "sidebar or hamburger"
   decision point, not the 768px mobile breakpoint below.
   ========================================================================== */
@media (max-width: 900px) {
  .stats__inner {
    grid-template-columns: repeat(2, 1fr);
  }

  .skills-grid {
    grid-template-columns: 1fr;
  }

  .edu-grid {
    grid-template-columns: 1fr;
  }

  .project-card--featured {
    grid-template-columns: 1fr;
    text-align: center;
  }

  .project-card__logo img {
    max-width: 160px;
    margin: 0 auto;
  }

  .project-card__list {
    text-align: left;
  }
}

/* ==========================================================================
   WIDE DESKTOP NAV
   At laptop width and up, the drawer's links show inline in the top bar
   itself instead of behind the hamburger -- same markup (partials/nav.html),
   just re-laid-out: .sidebar__nav goes from an off-canvas vertical panel to
   a plain static row, and each .sidebar__link drops its drawer styling
   (full-width, bottom-border separators) for a compact inline-tab look.
   ========================================================================== */
@media (min-width: 1100px) {
  .navbar__toggle {
    display: none;
  }

  .sidebar__nav {
    position: static;
    width: auto;
    max-width: none;
    height: auto;
    flex-direction: row;
    align-items: center;
    gap: 0.3rem;
    padding: 0;
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border-left: none;
    transform: none;
    overflow: visible;
  }

  /* Even-width button chips instead of ragged text of varying length --
     border is transparent at rest (so hover/active don't shift layout)
     and only becomes visible as a real outline on interaction. */
  .sidebar__link {
    width: auto;
    min-width: 116px;
    justify-content: center;
    padding: 0.55rem 0.9rem;
    border: 1px solid transparent;
    border-radius: 6px;
  }

  .sidebar__link:hover {
    background-color: rgba(220, 38, 38, 0.06);
    border-color: var(--color-border);
  }

  .sidebar__link.is-active {
    background-color: rgba(220, 38, 38, 0.1);
    border-color: rgba(220, 38, 38, 0.35);
  }

  .sidebar__divider {
    width: 1px;
    height: 18px;
    margin: 0 0.5rem;
  }

  .sidebar__link.sidebar__resume {
    width: auto;
    margin-left: 0.5rem;
    padding: 0.5rem 0.9rem;
  }
}

/* ==========================================================================
   RESPONSIVE: MOBILE
   Fine-grained mobile layout tweaks, on top of the compact top bar/drawer
   nav that's the default below the 1100px wide-nav breakpoint above.
   ========================================================================== */
@media (max-width: 768px) {
  .stats__inner {
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
  }

  .stat-card {
    padding: 1.25rem 1rem;
  }

  .section {
    padding: 4rem 1.25rem;
  }

  .section-divider {
    margin: -4rem 0 3rem;
  }

  .hero__inner {
    flex-direction: column;
    text-align: center;
    gap: 2rem;
  }

  /* Content no longer forces a full viewport tall hero on mobile -- the
     stacked column (eyebrow/name/role/portrait/tagline/focus/actions/
     terminal) determines its own height instead, so there's no dead space
     under a short stack on a tall phone. The scrim tightens up around that
     stacked column instead of the wide two-column ellipse used on desktop. */
  .hero {
    min-height: auto;
    background: radial-gradient(ellipse 95% 60% at 50% 35%, rgba(8, 10, 13, 0.85), rgba(8, 10, 13, 0.55) 65%, transparent 100%);
  }

  /* Reflow order: eyebrow -> name -> role -> portrait -> tagline -> focus ->
     actions -> terminal. hero__content becomes display:contents so its own
     children act as direct flex items of hero__inner alongside hero__media,
     letting the portrait slot in mid-stack without touching the HTML. */
  .hero__content {
    display: contents;
  }

  /* display:contents elements don't paint, so hero__content's own fade-in
     animation would silently no-op -- run it on hero__inner instead, which
     still has a real box and now wraps the whole reordered stack. */
  .hero__inner {
    animation: heroFadeUp 0.9s ease both;
  }

  .hero__eyebrow { order: 1; }
  .hero__name { order: 2; }
  .hero__title { order: 3; }
  .hero__media { order: 4; }
  .hero__tagline { order: 5; }
  .hero__focus { order: 6; }
  .hero__actions { order: 7; }
  .terminal { order: 8; }

  .hero__name {
    font-size: clamp(2rem, 8.5vw, 2.6rem);
  }

  .hero__title {
    font-size: clamp(0.75rem, 3.2vw, 0.94rem);
  }

  .hero__tagline {
    font-size: clamp(0.9rem, 3.7vw, 1rem);
  }

  .hero__focus {
    font-size: 0.78rem;
  }

  /* BUGFIX: text-align: center cascades down from .hero__inner into the
     terminal's prompt/output text and window title. Reset it here so the
     terminal reads left-to-right like a real shell regardless of viewport. */
  .terminal {
    margin-left: 0;
    margin-right: 0;
    max-width: 100%;
    text-align: left;
  }

  .terminal__body {
    font-size: 0.78rem;
    padding: 0.85rem 0.9rem 1rem;
  }

  .hero__photo-ring {
    width: 170px;
    height: 170px;
  }

  .hero__actions {
    justify-content: center;
  }

  /* Touch target floor across the whole site, not just the hero -- nav
     links already clear this via their own padding. */
  .btn {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .section__title {
    margin-bottom: 1.75rem;
  }

  /* Mobile keeps the map/nodes/traffic but drops the tiny HUD-style detail
     (coordinate text, corner crosshairs) that only reads at desktop size --
     see also the data-region node hiding below, which trims the cluster of
     nodes that fall outside the mobile map crop (js/script.js's
     applyMapViewBox) down to the ~17 that are still on screen. */
  .network-bg__ticks {
    display: none;
  }

  .network-bg__node[data-region="e-asia"],
  .network-bg__node[data-region="japan"],
  .network-bg__node[data-region="singapore"],
  .network-bg__node[data-region="india"],
  .network-bg__node[data-region="australia"] {
    display: none;
  }

  .footer {
    padding-bottom: calc(3.5rem + env(safe-area-inset-bottom, 0px));
  }

  /* Credential path: desktop's JS-computed grid-column/grid-row placement
     is simply inert once this isn't display:grid, so no JS re-layout is
     needed on this breakpoint switch -- just a left-aligned vertical flow
     with a single uniform rail instead of the desktop grid's per-node
     data-connector direction. */
  .cert-path {
    padding: 1.5rem 0.25rem 0.5rem;
  }

  .cert-path__main {
    display: flex;
    flex-direction: column;
    padding-left: 0.5rem;
  }

  .cert-path__node {
    position: relative;
    height: auto;
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
    padding-bottom: 1.5rem;
  }

  .cert-path__node:last-child {
    padding-bottom: 0;
  }

  .cert-path__marker {
    position: static;
    transform: none;
    flex-shrink: 0;
    margin-top: 0.2rem;
  }

  .cert-path__node:hover .cert-path__marker,
  .cert-path__node:focus-visible .cert-path__marker {
    transform: scale(1.25);
  }

  .cert-path__label,
  .cert-path__status {
    position: static;
    transform: none;
    max-width: none;
    width: auto;
    text-align: left;
  }

  .cert-path__status {
    display: block;
    margin-top: 0.2rem;
  }

  .cert-path__node[data-connector]::after {
    display: none;
  }

  .cert-path__node:not(:last-child)::before {
    content: '';
    position: absolute;
    top: 0.75rem;
    left: 6px;
    width: 2px;
    height: 100%;
    background-color: rgba(220, 38, 38, 0.32);
    z-index: 1;
  }

  .cert-path__branches {
    max-width: 100%;
  }

  /* Smaller glow radius than desktop's packet/hit-pulse -- still visible,
     less blur to composite on a phone GPU. */
  .cert-path__packet {
    box-shadow: 0 0 2px 1px rgba(239, 68, 68, 0.85);
  }

  .cert-path__node.is-packet-hit .cert-path__marker {
    animation-name: certPacketHitSoft;
  }

  .footer__inner {
    flex-direction: column;
    text-align: center;
  }
}

@media (max-width: 480px) {
  .stats__inner {
    grid-template-columns: 1fr;
  }

  .sidebar__wordmark {
    display: none;
  }

  .hero__photo-ring {
    width: 150px;
    height: 150px;
  }

  /* Below ~480px, two side-by-side CTAs get cramped -- stack them full
     width instead. Wider phones keep the row layout from the 768px block. */
  .hero__actions {
    flex-direction: column;
    align-items: stretch;
  }

  .hero__actions .btn {
    width: 100%;
  }

  /* Below ~480px, side-by-side Academic/Offensive Security branches get too
     cramped to read -- stack them instead, per-branch relationship is still
     clear from the labels above each mini-chain. */
  .cert-path__branches {
    grid-template-columns: 1fr;
    gap: 1.75rem;
    max-width: 220px;
  }
}

/* ==========================================================================
   SECURITY SCAN OVERLAY
   Created entirely by JS on first load only, see js/script.js. Never present
   in the static HTML, so a failed script simply means no overlay ever exists
   and the page renders normally underneath where it would have been.
   ========================================================================== */
.security-scan {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background-color: var(--color-bg);
  opacity: 1;
  transition: opacity 0.2s ease;
}

.security-scan.is-hidden {
  opacity: 0;
  pointer-events: none;
}

.security-scan__line {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--color-accent), transparent);
  box-shadow: 0 0 14px rgba(220, 38, 38, 0.45);
  animation: scanSweep 0.65s ease-in-out forwards;
}

@keyframes scanSweep {
  from {
    top: 0;
  }
  to {
    top: 100%;
  }
}

/* ==========================================================================
   ACCESSIBILITY: respect reduced-motion preference
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
