/* ============================================================
   DESIGN TOKENS — "mission control" theme
   ============================================================ */
:root {
  --void:       #070B14;   /* base background, blue-black, not neutral grey */
  --panel:      #0E1826;   /* raised panel surface */
  --panel-hi:   #142234;   /* slightly brighter panel (hover / active) */
  --line:       #1F3040;   /* hairline borders, grid lines */
  --ink:        #DCE6EC;   /* primary text, cool off-white */
  --ink-dim:    #7C90A3;   /* secondary text */
  --ink-faint:  #45566A;   /* tertiary / meta text */
  --signal:     #4CF2C8;   /* single accent — console teal, not acid-green */
  --signal-dim: #2E9C81;

  --font-display: 'Sora', sans-serif;
  --font-body: 'IBM Plex Sans', sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, monospace;

  --measure: 40rem;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);   /* smooth deceleration, used everywhere */
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

body {
  margin: 0;
  background: var(--void);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  position: relative;
}

/* faint drifting grid, the one ambient signature of the page */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 56px 56px;
  opacity: 0.16;
  animation: drift 90s linear infinite;
}

@keyframes drift {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 0 -1120px, -1120px 0; }
}

@media (prefers-reduced-motion: reduce) {
  body::before { animation: none; }
}

a {
  color: var(--signal);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  transition: color 0.25s var(--ease-soft);
}
a:hover { color: var(--ink); }

a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible {
  outline: 1px solid var(--signal);
  outline-offset: 3px;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0;
}

/* ============================================================
   LAYOUT SHELL
   ============================================================ */
.wrap {
  max-width: 64rem;
  margin: 0 auto;
  padding: 0 1.5rem;
  position: relative;
  z-index: 1;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: rgba(7, 11, 20, 0.86);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 0.9rem;
  padding-bottom: 0.9rem;
  flex-wrap: wrap;
}

.header-left {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--signal);
  box-shadow: 0 0 0 3px rgba(76, 242, 200, 0.15);
  animation: pulse 2.4s ease-in-out infinite;
  flex-shrink: 0;
}

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

.site-mark {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.02rem;
  color: var(--ink);
  text-decoration: none;
}

.header-right {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

nav.tabs {
  display: flex;
  gap: 1.4rem;
  position: relative;
  /* still scrollable on narrow screens so the last item stays reachable —
     just without the bar sitting under the nav in the header */
  overflow-x: auto;
  scrollbar-width: none;      /* Firefox */
  -ms-overflow-style: none;   /* legacy Edge */
}
nav.tabs::-webkit-scrollbar { display: none; }   /* Chrome, Safari, Edge */

nav.tabs a {
  font-family: var(--font-body);
  font-size: 0.92rem;
  background: none;
  border: none;
  color: var(--ink-dim);
  text-decoration: none;
  padding: 0.3rem 0;
  cursor: pointer;
  white-space: nowrap;
  position: relative;
  transition: color 0.25s var(--ease-soft);
}

nav.tabs a:hover { color: var(--ink); }

nav.tabs a[aria-current="true"] {
  color: var(--ink);
}

/* single indicator that slides between sections instead of an instant
   underline swap — positioned by JS as you scroll */
.tab-indicator {
  position: absolute;
  bottom: -1px;
  height: 1px;
  background: var(--signal);
  box-shadow: 0 0 6px var(--signal);
  transition: left 0.4s var(--ease), width 0.4s var(--ease);
}

/* Not a tab — it opens the blog drawer rather than swapping panels, so it is
   deliberately set in mono to read as a different kind of control. */
.nav-blogs {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  background: none;
  border: none;
  color: var(--ink-faint);
  padding: 0.3rem 0;
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.25s var(--ease-soft);
}
.nav-blogs:hover { color: var(--signal); }

/* ---------- menu toggle (phones only) ---------- */
.nav-toggle {
  display: none;   /* switched on in the phone breakpoint below */
  background: none;
  border: 1px solid var(--line);
  padding: 0.55rem 0.6rem;
  line-height: 0;
  cursor: pointer;
  flex-shrink: 0;
}

.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  display: block;
  width: 20px;
  height: 2px;
  background: var(--signal);
  transition: transform 0.3s var(--ease), background 0.2s linear;
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  content: "";
  position: absolute;
  left: 0;
}
.nav-toggle-bars::before { top: -6px; }
.nav-toggle-bars::after  { top: 6px; }

/* bars fold into an X while the menu is open */
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after  { transform: translateY(-6px) rotate(-45deg); }

main { padding: 3.5rem 0 6rem; }

/* Every section is on the page at once; the nav scrolls to them rather than
   swapping them in. scroll-margin-top keeps a target clear of the sticky
   header when you land on it. */
.panel-section {
  display: block;
  scroll-margin-top: 6rem;
}

.panel-section + .panel-section {
  margin-top: 5rem;
  padding-top: 4rem;
  border-top: 1px solid var(--line);
}

/* Sections below the fold settle in as they come into view — the same motion
   the tabs used to play on switch, now tied to scroll position. The hero keeps
   its own boot sequence and is never given .reveal. */
.panel-section.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  will-change: opacity, transform;
}
.panel-section.reveal.is-visible {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .panel-section.reveal { opacity: 1; transform: none; transition: none; }
}

/* ============================================================
   HERO — reticle-framed readout panel
   ============================================================ */
.hero {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
  margin-bottom: 4rem;
  position: relative;
  overflow: hidden;
}

/* one-time boot-sequence sweep across the hero on load */
.hero::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--signal), transparent);
  opacity: 0.8;
  animation: sweep 1.1s var(--ease) 0.05s both;
  pointer-events: none;
}

@keyframes sweep {
  from { top: 0; opacity: 0.9; }
  to   { top: 100%; opacity: 0; }
}

@media (min-width: 800px) {
  .hero {
    grid-template-columns: 1.15fr 1fr;
    /* The name block is a good deal shorter than the photo panel, so pinning
       both to the top left the text stranded against the top edge of the
       image. Centring sits it against the middle of the portrait instead. */
    align-items: center;
    gap: 3rem;
  }

  /* Gives the portrait a squarer frame to fill. At the content-driven height
     the panel was noticeably wider than it was tall, which cropped a portrait
     photo hard through the middle. Set on the dl rather than the panel so the
     hover scrim grows with it and still covers the full frame. */
  .readout dl { min-height: 24rem; }
}

.hero-name {
  font-size: clamp(2.3rem, 5.5vw, 3.6rem);
  line-height: 1.05;
}

.hero-name .char {
  display: inline-block;
}

.hero .role {
  font-family: var(--font-mono);
  color: var(--signal);
  font-size: 1rem;
  margin-top: 0.7rem;
  opacity: 0;
  animation: rowIn 0.5s var(--ease) 0.5s forwards;
}

.hero .tagline {
  max-width: var(--measure);
  color: var(--ink-dim);
  font-size: 1.12rem;
  margin-top: 1.1rem;
  opacity: 0;
  animation: rowIn 0.5s var(--ease) 0.62s forwards;
}

/* the telemetry panel */
.readout {
  position: relative;
  border: 1px solid var(--line);
  background: var(--panel);
  padding: 1.4rem 1.5rem;
}

.readout::before,
.readout::after {
  content: "";
  position: absolute;
  width: 14px;
  height: 14px;
  border-color: var(--signal);
  border-style: solid;
  opacity: 0;
  transform: scale(0.5);
  transform-origin: center;
  animation: corner-in 0.7s var(--ease) forwards;
}

.readout::before { top: -1px; left: -1px; border-width: 2px 0 0 2px; transform-origin: top left; animation-delay: 0.15s; }
.readout::after  { bottom: -1px; right: -1px; border-width: 0 2px 2px 0; transform-origin: bottom right; animation-delay: 0.32s; }

@keyframes corner-in {
  from { opacity: 0; transform: scale(0.4); }
  to   { opacity: 1; transform: scale(1); }
}

/* readout rows settle in just after the corners land */
.readout dt, .readout dd { opacity: 0; animation: rowIn 0.5s var(--ease) forwards; }
.readout dt:nth-of-type(1), .readout dd:nth-of-type(1) { animation-delay: 0.45s; }
.readout dt:nth-of-type(2), .readout dd:nth-of-type(2) { animation-delay: 0.55s; }
.readout dt:nth-of-type(3), .readout dd:nth-of-type(3) { animation-delay: 0.65s; }
.readout dt:nth-of-type(4), .readout dd:nth-of-type(4) { animation-delay: 0.75s; }

@keyframes rowIn {
  from { opacity: 0; transform: translateX(6px); }
  to   { opacity: 1; transform: translateX(0); }
}

.readout dt {
  font-family: var(--font-mono);
  /* keys read as the accent rather than as the faintest text on the page —
     --ink-faint was tuned for a flat panel and disappears over a photo */
  font-size: 0.7rem;
  color: var(--signal);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  opacity: 0.85;
  margin-top: 1.05rem;
}
.readout dt:first-child { margin-top: 0; }
.readout dd {
  margin: 0.2rem 0 0;
  color: var(--ink);
  font-size: 0.94rem;
}
.readout ul { list-style: none; margin: 0.2rem 0 0; padding: 0; }
.readout li { margin-top: 0.25rem; }

/* The portrait sits over the telemetry panel and blurs back on hover rather
   than disappearing, so the photo stays present behind the data instead of
   being swapped out for it. The readout is the only place the email and
   links appear, so they stay reachable without a hover. */
.readout {
  isolation: isolate;
  /* the photo scales up slightly on hover so its blurred edges never wash out
     against the panel — clip the overflow so it stays inside the frame */
  overflow: hidden;
}

.readout-portrait {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* shown exactly as shot — the only treatment is the hover blur below */
  transition:
    filter 0.5s var(--ease),
    transform 0.5s var(--ease);
}

/* keep the reticle corners on top of the photo — they frame both states.
   Pulled flush to the edge rather than straddling it, so overflow:hidden
   above can't shave a pixel off their 2px stroke. */
.readout::before, .readout::after { z-index: 2; }
.readout::before { top: 0; left: 0; }
.readout::after  { bottom: 0; right: 0; }

/* The data sits above the photo but stays hidden until you reach for it.
   The negative margin cancels its own padding, so the scrim below fills the
   whole panel while the panel's height stays exactly what it was. */
.readout dl {
  position: relative;
  z-index: 3;
  margin: -1.4rem -1.5rem;
  padding: 1.4rem 1.5rem;
  opacity: 0;
  /* a scrim does the legibility work that a heavy text outline was doing —
     one flat, even ground under the type instead of a halo per glyph */
  background: rgba(7, 11, 20, 0.62);
  transition: opacity 0.5s var(--ease);
}

.readout:hover .readout-portrait,
.readout:focus-within .readout-portrait,
.readout:focus .readout-portrait {
  /* full colour at rest, black and white once you're reading over it — the
     photo drops back to a neutral ground instead of competing with the type */
  filter: grayscale(1) blur(9px) brightness(0.85);
  transform: scale(1.06);
}

.readout:hover dl,
.readout:focus-within dl,
.readout:focus dl {
  opacity: 1;
}

/* With the scrim carrying the contrast, this is just a touch of separation —
   a heavier halo here is what made the labels look smudged rather than clean. */
.readout dt,
.readout dd,
.readout a {
  text-shadow: 0 1px 3px rgba(7, 11, 20, 0.9);
}

/* Inside this panel the teal is the labels' colour, so links take the same
   white as the values they sit among — the underline is what marks them as
   links, and the accent comes back on hover. */
.readout a {
  color: var(--ink);
  text-decoration-color: var(--ink-faint);
}
.readout a:hover,
.readout a:focus-visible {
  color: var(--signal);
  text-decoration-color: var(--signal-dim);
}

.readout:focus { outline: 1px solid var(--signal-dim); outline-offset: 3px; }

/* On touch there is no hover to reveal with, so show the panel and let the
   portrait sit above it in the flow instead of covering it. */
@media (hover: none) {
  .readout-portrait {
    position: relative;
    inset: auto;
    height: auto;
    aspect-ratio: 3 / 2;
    margin: -1.4rem -1.5rem 1.4rem;
    width: calc(100% + 3rem);
    filter: none;
    transform: none;
  }
  /* nothing sits behind the data here, so drop the scrim and the separation.
     min-height goes too — the photo is in the flow above, not filling a frame. */
  .readout dl {
    opacity: 1;
    margin: 0;
    padding: 0;
    min-height: 0;
    background: none;
  }
  .readout dt, .readout dd, .readout a { text-shadow: none; }
  /* some touch browsers still fire :hover on tap — make sure that can't
     blur a photo that is now sitting in the flow above the data */
  .readout:hover .readout-portrait,
  .readout:focus-within .readout-portrait,
  .readout:focus .readout-portrait {
    filter: none;
    transform: none;
  }
}

.origin {
  max-width: var(--measure);
  margin-top: 2.5rem;
}

/* under a heading the heading's own spacing is enough — stacking both
   leaves the text floating away from the thing that names it */
.section-heading + .origin { margin-top: 0; }

/* The closing line. An accent rule down the left and a fade that dies out
   before the text ends — enough to lift it off the paragraphs above without
   turning it into a callout box shouting at the reader. */
.credo {
  margin-top: 2.4rem;
  padding: 1.35rem 1.5rem 1.35rem 1.4rem;
  border-left: 2px solid var(--signal);
  background: linear-gradient(90deg, rgba(76, 242, 200, 0.06), transparent 75%);
}

.origin .credo-text {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.1rem;
  line-height: 1.65;
  color: var(--ink);
}

/* emphasis by colour and weight, not italics — the line is a statement,
   not an aside */
.credo-hl {
  font-style: normal;
  font-weight: 600;
  color: var(--signal);
}

@media (max-width: 720px) {
  .credo { padding: 1.1rem 0 1.1rem 1.1rem; }
  .origin .credo-text { font-size: 1rem; }
}
.origin p { margin: 0 0 1.1rem; color: var(--ink); }
.origin p:last-child { margin-bottom: 0; }

/* ============================================================
   SECTION HEADINGS — no eyebrow labels, just a signal tick
   ============================================================ */
.section-heading {
  font-size: 1.4rem;
  margin-bottom: 1.75rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.section-heading::before {
  content: "";
  width: 8px;
  height: 8px;
  background: var(--signal);
  flex-shrink: 0;
}

/* ============================================================
   SKILLS — angled-corner tags, grouped
   ============================================================ */
.skills-grid {
  display: grid;
  gap: 1.9rem;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  margin-bottom: 3.75rem;
}

.skill-group h3 {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--ink-dim);
  margin-bottom: 0.7rem;
}

.chip-row { display: flex; flex-wrap: wrap; gap: 0.45rem; }

.chip {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--ink);
  padding: 0.25rem 0.6rem;
  background: var(--panel);
  border: 1px solid var(--line);
  clip-path: polygon(6px 0, 100% 0, 100% calc(100% - 6px), calc(100% - 6px) 100%, 0 100%, 0 6px);
  transition: transform 0.25s var(--ease-soft), border-color 0.25s var(--ease-soft), background 0.25s var(--ease-soft);
}

.chip:hover {
  border-color: var(--signal-dim);
  background: var(--panel-hi);
  transform: translateY(-1px);
}

/* ============================================================
   PROJECTS — vertical log with a connecting trace
   ============================================================ */
#projectsList { position: relative; }

#projectsList::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 8px;
  bottom: 8px;
  width: 1px;
  background: var(--line);
}

.project {
  position: relative;
  padding: 0 0 2.75rem 2.25rem;
}

.project::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  width: 11px;
  height: 11px;
  border: 1px solid var(--signal);
  background: var(--void);
  transition: box-shadow 0.3s var(--ease-soft), background 0.3s var(--ease-soft);
}

.project:hover::before {
  background: var(--signal);
  box-shadow: 0 0 10px var(--signal);
}

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

.project-meta {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--ink-faint);
  margin-bottom: 0.4rem;
}

.project h3 { font-size: 1.22rem; margin-bottom: 0.55rem; }

/* employer and seat — mono like the date above it, but a step brighter, so
   the pair reads as one block of context around the title */
.project-org {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  color: var(--signal-dim);
  margin-bottom: 0.7rem;
}

.project .summary {
  max-width: var(--measure);
  color: var(--ink-dim);
  margin-bottom: 0.9rem;
}

/* Responsibilities. Hairline markers rather than bullet glyphs — the page
   already draws structure with thin teal lines, and round dots would compete
   with the timeline marker to the left. */
.project-highlights {
  list-style: none;
  max-width: var(--measure);
  margin: 0 0 1.1rem;
  padding: 0;
}

.project-highlights li {
  position: relative;
  padding-left: 1.1rem;
  margin-bottom: 0.5rem;
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--ink-dim);
}
.project-highlights li:last-child { margin-bottom: 0; }

.project-highlights li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 6px;
  height: 1px;
  background: var(--signal-dim);
}

.project .chip-row { margin-bottom: 0.9rem; }

.project-links {
  display: flex;
  gap: 1.25rem;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  margin-bottom: 1rem;
}

/* media embeds, framed like a viewport */
.media-frame {
  position: relative;
  border: 1px solid var(--line);
  background: var(--panel);
  max-width: var(--measure);
}

.media-frame::before,
.media-frame::after {
  content: "";
  position: absolute;
  width: 10px;
  height: 10px;
  border-color: var(--signal);
  border-style: solid;
  opacity: 0;
  transform: scale(0.5);
  animation: corner-in 0.6s var(--ease) forwards;
  animation-delay: 0.1s;
}
.media-frame::before { top: -1px; left: -1px; border-width: 1.5px 0 0 1.5px; transform-origin: top left; }
.media-frame::after  { bottom: -1px; right: -1px; border-width: 0 1.5px 1.5px 0; transform-origin: bottom right; animation-delay: 0.2s; }

.media-frame iframe,
.media-frame video,
.media-frame img {
  display: block;
  width: 100%;
  height: 460px;
  border: none;
}
.media-frame img { height: auto; }
.media-frame video { height: auto; background: #000; }

.media-fallback {
  padding: 1.25rem 1.5rem;
  font-family: var(--font-mono);
  font-size: 0.83rem;
  color: var(--ink-faint);
}

/* ============================================================
   HOBBIES
   ============================================================ */
/* ============================================================
   AWARDS & RECOGNITION
   ============================================================ */
/* Panels rather than the timeline treatment: these are discrete honours, not
   a sequence, and nothing about them is chronological. */
.award {
  max-width: var(--measure);
  padding: 1.25rem 1.4rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 2px solid var(--signal-dim);
  margin-bottom: 1.1rem;
}
.award:last-child { margin-bottom: 0; }

.award-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.35rem;
}

.award-head h3 {
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 500;
  color: var(--signal);
  margin: 0;
}

svg.award-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  fill: none;
  stroke: var(--signal);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* who gave it — the citation below carries what it was for */
.award-org {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
  margin-bottom: 0.75rem;
}

.award p {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.65;
  color: var(--ink-dim);
}

/* The certificate is portrait and tall, so it's capped by height rather than
   width — at full width it would push the next award off the screen. */
.award-media {
  display: block;
  margin-top: 1.1rem;
  max-width: 22rem;
}

.award-media .media-frame {
  max-width: 100%;
  transition: border-color 0.25s var(--ease-soft);
}
.award-media:hover .media-frame { border-color: var(--signal-dim); }

.award-media-hint {
  display: block;
  margin-top: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--ink-faint);
}

/* ---------- certifications ----------
   Flatter than the award cards deliberately: awards carry a citation worth
   reading, certifications carry a name. Two columns on desktop so six of them
   read as a short list rather than a long scroll. */
.certs-grid {
  display: grid;
  gap: 0.85rem;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  max-width: var(--measure);
}

.cert {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  padding: 0.85rem 1rem;
  background: var(--panel);
  border: 1px solid var(--line);
}

.cert-icon {
  color: var(--signal);
  flex-shrink: 0;
}

svg.cert-icon {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
  width: 1.3rem;
  height: 1.3rem;
}

/* Devicon glyphs are font characters, so they size by font-size, not by box. */
i.cert-icon {
  font-size: 1.35rem;
  line-height: 1;
  font-style: normal;
}

.cert-body { min-width: 0; }

.cert-body h3 {
  font-size: 0.94rem;
  font-weight: 500;
  margin: 0 0 0.15rem;
  color: var(--ink);
}

.cert-issuer {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* ============================================================
   COLLAPSIBLE REGIONS
   ============================================================ */

/* The 0fr → 1fr grid trick: the row animates to the content's real height, so
   nothing needs a guessed max-height that a long section would overflow. */
.collapse {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 0.32s var(--ease-soft);
}

.collapse.is-collapsed { grid-template-rows: 0fr; }

/* the clipping happens here, not on the grid row itself */
.collapse-inner { overflow: hidden; }

/* the one control left: the chevron on each project card header */
svg.collapse-chevron {
  width: 1.1rem;
  height: 1.1rem;
  flex-shrink: 0;
  fill: none;
  stroke: var(--signal);
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.32s var(--ease-soft);
}

/* points down when open, right when closed — the arrow shows where the
   content went, not where clicking will take you */
[aria-expanded="false"] svg.collapse-chevron { transform: rotate(-90deg); }

/* ---------- project cards ----------
   The header stays put when the body collapses: period, title and org are
   what you scan by, so they are never the thing that gets hidden. */
.project-head {
  position: relative;
  padding-right: 2.25rem;
}

.project-toggle {
  position: absolute;
  top: -2px;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  background: none;
  border: 1px solid transparent;
  color: inherit;
  cursor: pointer;
  transition: border-color 0.2s var(--ease-soft);
}

.project-toggle:hover { border-color: var(--line); }

.project-toggle:focus-visible {
  outline: 1px solid var(--signal);
  outline-offset: 2px;
}

/* A closed card is down to two lines: the project, and who it was for. The
   period and the seat are display:none rather than removed, so opening one
   costs no DOM work and the header doesn't rebuild itself mid-animation. */
.project:not(.is-open) .project-meta,
.project:not(.is-open) .project-seat { display: none; }

/* with the date gone above it, the title carries the top of the card */
.project:not(.is-open) h3 { margin-bottom: 0.35rem; }

.project:not(.is-open) .project-org { margin-bottom: 0; }

/* ---------- card hit target ----------
   The whole card toggles, open or closed, so the pointer is honest in both
   states. Text inside an open card still selects: the click handler bails on a
   selection rather than the cursor having to warn anyone off. */
.project { cursor: pointer; }

/* A closed card would otherwise keep the full 2.75rem of timeline gutter meant
   to separate a card's body from the next card's marker. */
.project:not(.is-open) { padding-bottom: 1.4rem; }

.project:hover h3 { color: var(--signal); }

/* the timeline marker already lights on hover; keep the chevron in step */
.project:hover svg.collapse-chevron { stroke: var(--signal); }

/* Someone who has asked for less motion gets the state change without the
   slide — the region still opens and closes, it just does it at once. */
@media (prefers-reduced-motion: reduce) {
  .collapse,
  svg.collapse-chevron { transition: none; }
}

.hobbies-grid {
  display: grid;
  gap: 1.9rem;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}

.hobby h3 {
  font-size: 1.02rem;
  margin-bottom: 0.4rem;
  font-family: var(--font-mono);
  font-weight: 500;
  color: var(--signal);
}

.hobby p { color: var(--ink-dim); margin: 0; }

/* ============================================================
   FOOTER
   ============================================================ */

footer {
  border-top: 1px solid var(--line);
  padding: 1.5rem 0;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--ink-faint);
  position: relative;
  z-index: 1;
}

/* ============================================================
   PHONES — collapse the header into a menu, and keep every
   block inside the viewport width
   ============================================================ */
@media (max-width: 720px) {

  /* --- header collapses to a toggle --- */
  .site-header .wrap {
    flex-wrap: nowrap;
    gap: 0.75rem;
  }

  .nav-toggle { display: block; }

  /* The panel drops out of the header bar and becomes a sheet underneath it.
     max-height (not display) so the open/close can actually animate; the
     visibility flip keeps the links out of the tab order while closed. */
  .header-right {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0 1.25rem;
    background: rgba(7, 11, 20, 0.97);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--line);
    max-height: 0;
    overflow: hidden;
    visibility: hidden;
    transition: max-height 0.35s var(--ease), visibility 0s linear 0.35s;
  }

  .site-header.is-open .header-right {
    max-height: 24rem;
    visibility: visible;
    transition: max-height 0.35s var(--ease), visibility 0s;
  }

  /* stacked rows instead of a scrolling strip */
  nav.tabs {
    flex-direction: column;
    gap: 0;
    overflow: visible;
  }

  nav.tabs a {
    padding: 0.85rem 0;
    border-bottom: 1px solid var(--line);
    font-size: 1rem;
  }

  /* the sliding underline only makes sense on a horizontal row */
  .tab-indicator { display: none; }

  .nav-blogs {
    text-align: left;
    padding: 0.85rem 0;
    font-size: 0.9rem;
  }

  /* --- fit to the screen --- */
  .wrap { padding: 0 1.25rem; }

  main { padding: 2.5rem 0 4.5rem; }

  .panel-section + .panel-section {
    margin-top: 3rem;
    padding-top: 2.5rem;
  }

  .hero { gap: 1.75rem; margin-bottom: 2.5rem; }

  /* long addresses and URLs are the usual cause of a sideways scroll */
  .readout dd,
  .readout a,
  .project .summary,
  .origin p {
    overflow-wrap: anywhere;
  }

  /* 460px of PDF viewer is most of a phone screen */
  .media-frame iframe { height: 300px; }

  .skills-grid,
  .hobbies-grid {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }

  .section-heading { font-size: 1.2rem; }

}

/* ============================================================
   SKILL ICONS — Devicon brand marks (<i>) and sprite glyphs (<svg>)
   normalised to one size and one colour
   ============================================================ */

/* The sprite is a definitions block, never rendered. Collapsed rather than
   display:none — a few browsers refuse to resolve <use> into a none'd tree. */
.sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.chip-icon,
.group-icon {
  color: var(--signal);
  flex-shrink: 0;
}

/* sprite glyphs: stroked, scaled off the text they sit beside */
svg.chip-icon,
svg.group-icon {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
  width: 1.05em;
  height: 1.05em;
}

/* Devicon glyphs are font characters, so they take a font-size rather than a
   box. The nudge up matches their optical centre to the stroked sprite ones. */
i.chip-icon,
i.group-icon {
  font-size: 1.15em;
  line-height: 1;
  font-style: normal;
}

.skill-group h3 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.group-icon {
  width: 1.15em;
  height: 1.15em;
}

/* the chip's teal only appears on hover elsewhere; keep the icon steady */
.chip:hover .chip-icon { color: var(--signal); }

/* ============================================================
   GITHUB ANALYTICS
   ============================================================ */
.gh-loading,
.gh-fallback {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--ink-faint);
}

/* --- the headline figure ---
   One number, so it's a hero figure rather than a tile in a grid: no box to
   imply a missing row of siblings, sitting directly on top of the graph it
   totals. */
.gh-headline-value {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 3.2rem;
  line-height: 1;
  color: var(--signal);
  font-variant-numeric: tabular-nums;
}

.gh-headline-label {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-dim);
  margin-top: 0.5rem;
}

.gh-block { margin-bottom: 1.5rem; }

/* names what the grid below covers, without competing with the total above */
.gh-heatmap-caption {
  margin: 1.6rem 0 0.9rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--ink-dim);
}

/* --- contribution heatmap ---
   One sequential ramp on the page's own teal: empty days sit at the panel
   surface and four steps climb to full --signal. One hue, light to dark, no
   second colour — shade encodes magnitude and nothing else, and the exact
   count is always in the tooltip so no reader depends on telling two steps
   apart. */
.gh-heatmap-block { position: relative; }

/* 53 columns don't fit a phone; the grid scrolls inside its own box rather
   than widening the page. */
.gh-heatmap-scroll {
  overflow-x: auto;
  padding-bottom: 0.4rem;
}

.gh-heatmap-plot {
  --gh-cell: 11px;
  display: inline-block;
  min-width: 100%;
}

.gh-heatmap-months {
  display: grid;
  gap: 3px;
  margin-bottom: 0.35rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  color: var(--ink-faint);
}

.gh-heatmap {
  display: grid;
  grid-template-rows: repeat(7, var(--gh-cell));
  grid-auto-flow: column;
  grid-auto-columns: var(--gh-cell);
  gap: 3px;                      /* surface gap between cells, never touching */
}

.gh-cell {
  box-sizing: border-box;
  width: var(--gh-cell);
  height: var(--gh-cell);
  border-radius: 2px;
  background: var(--panel);
  border: 1px solid var(--line);
}

.gh-cell-pad { visibility: hidden; }

.gh-cell[data-level="1"] { background: #14483C; border-color: #14483C; }
.gh-cell[data-level="2"] { background: #1E7360; border-color: #1E7360; }
.gh-cell[data-level="3"] { background: var(--signal-dim); border-color: var(--signal-dim); }
.gh-cell[data-level="4"] { background: var(--signal); border-color: var(--signal); }

.gh-heatmap-legend {
  --gh-cell: 11px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 3px;
  margin-top: 0.6rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  color: var(--ink-faint);
}
.gh-heatmap-legend span:first-child { margin-right: 0.3rem; }
.gh-heatmap-legend span:last-child { margin-left: 0.3rem; }

/* follows the pointer across the grid; one node, not 365 */
.gh-tip {
  position: absolute;
  transform: translate(-50%, calc(-100% - 8px));
  padding: 0.35rem 0.55rem;
  background: var(--panel-hi);
  border: 1px solid var(--line);
  border-radius: 3px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--ink);
  white-space: nowrap;
  pointer-events: none;
  z-index: 5;
}

.gh-source {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--ink-faint);
}

@media (max-width: 720px) {
  .gh-headline-value { font-size: 2.5rem; }

  /* smaller cells fit more of the year before the grid has to scroll */
  .gh-heatmap-plot, .gh-heatmap-legend { --gh-cell: 9px; }
}
