/* ===== Tokens ===== */
:root {
  --bg: #0a0e14;
  --bg-raised: #0e141c;
  --bg-panel: #101720;
  --line: #1d2833;
  --line-strong: #2c3c4c;
  --ink: #dbe5ee;
  --ink-mid: #acbbc9; /* exact midpoint of --ink and --ink-dim */
  --ink-dim: #7d90a3;
  --ink-faint: #3f4d5c;
  --accent: #45d8ff;
  --accent-soft: rgba(69, 216, 255, 0.1);
  --accent-line: rgba(69, 216, 255, 0.4);
  --amber: #ffb454;
  --font-sans: "IBM Plex Sans", "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", "Consolas", monospace;
  --frame-inset: 14px;
  --topbar-h: 56px;
}

* { box-sizing: border-box; }
/* Belt-and-suspenders alongside the <meta name="color-scheme"> tag in
   index.html — tells the browser this page IS dark, so mobile/OS "force
   dark" converters don't try to re-invert an already-dark design. */
html { scroll-behavior: smooth; color-scheme: dark; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.55;
}
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 64px 64px;
  opacity: 0.12;
  mask-image: radial-gradient(ellipse 70% 60% at 50% 0%, black 30%, transparent 85%);
}
a { color: inherit; text-decoration: none; }
img { max-width: 100%; }

/* ===== Drawing-sheet frame ===== */
.sheet-frame {
  position: fixed;
  inset: var(--frame-inset);
  border: 1px solid var(--line-strong);
  pointer-events: none;
  z-index: -1;
  display: none;
}
.sheet-frame .zone {
  position: absolute;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
}
.sheet-frame .zone-top { top: -18px; }
.sheet-frame .zone-left { left: -20px; writing-mode: vertical-rl; }
@media (min-width: 900px) {
  .sheet-frame { display: block; }
}

/* ===== Top bar ===== */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  min-height: var(--topbar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 max(var(--frame-inset), 16px);
  background: rgba(10, 14, 20, 0.82);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
  transition: transform 0.25s ease;
}
/* Slid up out of view on scroll-down, restored on scroll-up — see
   initTopbarAutoHide in main.js. Transform, not display/visibility, so the
   sticky positioning underneath keeps working and the slide animates. */
.topbar.topbar--hidden { transform: translateY(-100%); }
.topbar .id {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--ink-dim);
  display: flex;
  gap: 10px;
  align-items: baseline;
  white-space: nowrap;
  overflow: hidden;
  flex: none;
}
.topbar .id b { color: var(--ink); font-weight: 600; }
.topbar .id span { overflow: hidden; text-overflow: ellipsis; display: none; }
@media (min-width: 900px) { .topbar .id span { display: inline; } }
.topbar nav {
  display: flex;
  gap: 2px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  overflow-x: auto;
  scrollbar-width: none;
  flex: 1 1 auto;
  justify-content: flex-start;
  min-width: 0;
}
@media (min-width: 900px) { .topbar nav { justify-content: flex-end; } }
.topbar nav::-webkit-scrollbar { display: none; }
.topbar nav a {
  padding: 6px 8px;
  border: 1px solid transparent;
  color: var(--ink-dim);
  white-space: nowrap;
  flex: none;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
@media (min-width: 900px) {
  .topbar nav { gap: 4px; font-size: 12px; }
  .topbar nav a { padding: 6px 12px; }
}
.topbar nav a:hover, .topbar nav a:focus-visible {
  color: var(--accent);
  border-color: var(--accent-line);
  background: var(--accent-soft);
  outline: none;
}
/* ===== Layout shell ===== */
main { position: relative; }
.wrap {
  max-width: 1040px;
  margin: 0 auto;
  padding: 0 max(var(--frame-inset), 20px);
}
section { padding: 72px 0; border-bottom: 1px solid var(--line); }
section:last-of-type { border-bottom: none; }

/* Section eyebrow = detail callout */
.callout {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 28px;
}
.callout .mark {
  width: 26px; height: 26px;
  border: 1px solid var(--accent-line);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--accent);
  flex: none;
}
.callout .leader {
  height: 1px;
  width: 28px;
  background: var(--line-strong);
  flex: none;
}
.callout h2 {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin: 0;
  font-weight: 500;
}

/* ===== Hero ===== */
.hero {
  padding-top: 96px;
  padding-bottom: 72px;
  min-height: calc(100vh - var(--topbar-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.hero .kicker {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.2em;
  color: var(--accent);
  text-transform: uppercase;
  margin: 0 0 18px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.hero .kicker::before {
  content: "";
  width: 8px; height: 8px;
  background: var(--accent);
  box-shadow: 0 0 10px 2px var(--accent-line);
  border-radius: 1px;
}
.hero h1 {
  font-size: clamp(2.4rem, 6vw, 4.2rem);
  line-height: 1.05;
  margin: 0 0 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
.hero h1 .accent { color: var(--accent); }
.hero p.tagline {
  max-width: 60ch;
  font-size: clamp(1rem, 2vw, 1.2rem);
  color: var(--ink-dim);
  margin: 0 0 34px;
}
.hero .meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 14px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--ink-dim);
}
.hero .meta-row span { display: flex; align-items: center; gap: 8px; }
.hero .meta-row .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--amber); box-shadow: 0 0 8px 1px rgba(255,180,84,0.5); }
.actions { display: flex; gap: 12px; margin-top: 36px; flex-wrap: wrap; }

/* Hero highlights — 2x2 quick-facts grid */
.highlights-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 40px;
  max-width: 560px;
}
.highlight-tile {
  position: relative;
  display: block;
  border: 1px solid var(--line);
  background: var(--bg-panel);
  padding: 14px 16px;
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.highlight-tile:hover, .highlight-tile:focus-visible {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  outline: none;
}
.highlight-eyebrow {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--accent);
  margin-bottom: 6px;
}
.highlight-title { color: var(--ink); font-size: 0.86rem; font-weight: 600; line-height: 1.3; }
.highlight-sub { color: var(--ink-dim); font-size: 0.78rem; margin-top: 3px; }
.highlight-when { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-faint); margin-top: 8px; }

/* The hero tile's spine tab and status badge are the exact same .type-tab /
   .status-badge components used everywhere else (sizing comes from the
   .highlight-tile ancestor selector next to .type-tab below), so a tile
   reads as one consistent tag with the full cards rather than a lighter,
   separate treatment. */

.highlight-preview {
  position: absolute;
  z-index: 70;
  top: calc(100% + 10px);
  left: 0;
  width: 340px;
  max-width: min(90vw, 340px);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
  /* Purely a glance-preview, not interactive — pointer-events:none even while
     open means moving the mouse down into it doesn't extend the tile's hover
     state, so it can't hold itself open and block reaching a tile in the row
     below (the 2x2 grid stacks previews right over the next row). */
  pointer-events: none;
}
.highlight-tile:nth-child(2n) .highlight-preview { left: auto; right: 0; }
.highlight-tile:hover .highlight-preview,
.highlight-tile:focus-visible .highlight-preview,
.highlight-tile:focus-within .highlight-preview {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.highlight-preview .card,
.highlight-preview .degree { box-shadow: 0 20px 45px -15px rgba(0,0,0,0.55); }

@media (max-width: 640px) {
  .highlights-grid { max-width: 100%; grid-template-columns: 1fr; }
  .highlight-preview { display: none; }
}

/* Buttons — drafting-tool style: bracketed corners, not brutalist */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 20px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  letter-spacing: 0.06em;
  color: var(--ink);
  border: 1px solid var(--line-strong);
  background: var(--bg-raised);
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.btn:hover, .btn:focus-visible { border-color: var(--accent-line); color: var(--accent); background: var(--accent-soft); outline: none; }
.btn.primary { border-color: var(--accent-line); color: var(--accent); }
.btn.primary:hover { background: var(--accent-soft); }

/* ===== Cards (experience / projects) ===== */
.card-grid { display: flex; flex-direction: column; gap: 18px; }
.card {
  position: relative;
  min-width: 0;
  border: 1px solid var(--line);
  background: var(--bg-panel);
  padding: 22px 24px;
  scroll-margin-top: calc(var(--topbar-h) + 24px);
}
.card::before, .card::after {
  content: "";
  position: absolute;
  width: 10px; height: 10px;
  border-color: var(--accent-line);
  border-style: solid;
  opacity: 0.8;
}
.card::before { top: -1px; left: -1px; border-width: 1px 0 0 1px; }
.card::after { bottom: -1px; right: -1px; border-width: 0 1px 1px 0; }
.card .row-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}
.card h3 { margin: 0; font-size: 1.15rem; font-weight: 600; }
.card .org { color: var(--ink-dim); font-size: 0.92rem; margin: 2px 0 12px; }
.card .when {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-faint);
  white-space: nowrap;
}
.card ul { margin: 0; padding-left: 18px; }
.card li { margin: 5px 0; color: var(--ink); font-size: 0.95rem; }
.card li::marker { color: var(--accent); }
.card .tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 14px; }
.tag {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.05em;
  padding: 3px 8px;
  border: 1px solid var(--line-strong);
  color: var(--ink-dim);
}
.card .summary { color: var(--ink-dim); font-size: 0.93rem; margin: 0 0 12px; }

/* Project video embed — same collapse mechanic (toggleDegreeDetail) as the
   education transcript toggle, reused via a generic button+detail pair. */
.project-video-detail {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.35s ease;
}
.project-video-inner {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px dashed var(--line);
}
.project-video-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--bg-raised);
  border: 1px solid var(--line);
}
.project-video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.project-video-link { display: block; margin-top: 10px; color: var(--accent); font-family: var(--font-mono); font-size: 11.5px; }

/* Additional / compact list */
.compact-list { margin-top: 22px; border-top: 1px dashed var(--line); padding-top: 18px; }
.compact-list .label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  text-transform: uppercase;
  margin-bottom: 10px;
}
.compact-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.9rem;
  flex-wrap: wrap;
}
.compact-row:last-child { border-bottom: none; }
.compact-row .who { color: var(--ink); }
.compact-row .org { color: var(--ink-dim); }
.compact-row .when { font-family: var(--font-mono); font-size: 11px; color: var(--ink-faint); }

/* Info popup — hovering/tapping anywhere on a tag, chip, or certification
   row that carries .has-info reveals a snippet. The dot is a visual cue
   only; the whole host element is the hover/tap target. */
.has-info { position: relative; cursor: pointer; }
.info-dot {
  display: inline-flex;
  width: 5px; height: 5px;
  margin-left: 6px;
  vertical-align: middle;
  background: var(--accent);
  border-radius: 50%;
  opacity: 0.8;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.has-info:hover .info-dot, .has-info.open .info-dot { opacity: 1; transform: scale(1.5); }
.info-popup {
  display: none;
  position: absolute;
  z-index: 40; /* above page content, but below .topbar's 50 — never covers the nav */
  top: calc(100% + 8px);
  left: 0;
  min-width: 320px;
  max-width: min(420px, 90vw);
  /* Reset the pointer cursor inherited from .has-info — most of a popup's
     content (labels, notes) isn't clickable, only its actual links are. */
  cursor: default;
  background: var(--bg-raised);
  border: 1px solid var(--accent-line);
  padding: 12px 14px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-dim);
  text-align: left;
  box-shadow: 0 14px 30px -10px rgba(0,0,0,0.55);
}
/* display:none (not visibility:hidden) so a closed popup — there's one per
   chip/tag/cert row — never contributes 220px+ of phantom scrollable width
   to its container while sitting invisibly off to the side. */
.has-info:hover .info-popup,
.has-info.open .info-popup {
  display: block;
}
/* Click-opened only: bridges the gap back up to the trigger so that moving
   the mouse down into the popup can't graze a different .has-info row
   sitting right underneath (dense lists like certifications) and trigger
   initSnippetHoverClose() on it, closing this popup before the pointer
   ever reaches it. Not added for hover-only display — there, closing on a
   stray graze is normal/expected hover behavior. */
.has-info.open .info-popup::before {
  content: "";
  position: absolute;
  top: -8px;
  left: 0;
  right: 0;
  height: 8px;
}
.info-popup .info-title {
  margin: 0 0 8px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}
.info-popup p { margin: 0 0 8px; }
.info-popup p:last-child { margin-bottom: 0; }
.info-popup .info-line {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin: 0 0 6px;
  font-family: var(--font-mono);
  font-size: 11px;
}
.info-popup .info-line:last-child { margin-bottom: 0; }
.info-popup .info-line .k { color: var(--ink-dim); font-family: var(--font-sans); letter-spacing: 0; }
.info-popup .info-line .v { color: var(--accent); flex: none; }
.info-popup .info-line a.v { text-decoration: underline; }
.info-popup .info-line-link { display: block; color: var(--accent); font-size: 11.5px; margin-top: 2px; }
.info-popup a { cursor: pointer; }

/* ===== Skills ===== */
.skills-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}
.skill-card {
  position: relative;
  min-width: 0;
  border: 1px solid var(--line);
  background: var(--bg-panel);
  padding: 20px;
}
.skill-card h3 {
  font-family: var(--font-mono);
  font-size: 12.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 8px;
  font-weight: 500;
}
.skill-card p { color: var(--ink-dim); font-size: 0.88rem; margin: 0 0 14px; }
.chip-row { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  font-size: 12px;
  padding: 4px 10px;
  border: 1px solid var(--line-strong);
  color: var(--ink);
}

/* ===== Education timeline ===== */
.degree-list { display: grid; grid-template-columns: 1fr; gap: 14px; margin-bottom: 40px; }
/* min-width:0 is load-bearing: a grid item's automatic minimum size is based
   on its content's min-content width unless overridden, and that calculation
   reaches straight through descendants even when they're visually collapsed
   via max-height:0 (max-height never affects intrinsic width). Without this,
   the collapsed transcript's course-row content forces the shared 1fr column
   to ~665px on narrow screens regardless of the actual viewport width. */
.degree { grid-column: 1 / -1; min-width: 0; }
@media (min-width: 640px) {
  .degree-list { grid-template-columns: 1fr 1fr; }
  .degree--compact { grid-column: span 1; }
}

/* "Show more" wrapper for the compact degrees — collapsed behind a button
   on mobile only; desktop always shows them in their 2-column layout. */
.degree-more { grid-column: 1 / -1; }
/* Compound selector, not .degree-more-toggle alone: the base .expand-toggle
   rule below also sets display, at equal specificity but later in the file,
   so it would otherwise always win and this button would never actually hide. */
.expand-toggle.degree-more-toggle { display: none; }
.degree-more-inner {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
@media (max-width: 639px) {
  .expand-toggle.degree-more-toggle { display: inline-flex; margin-bottom: 14px; }
  .degree-more-inner {
    grid-template-columns: 1fr;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.35s ease;
  }
}
.degree { position: relative; border: 1px solid var(--line); background: var(--bg-panel); padding: 18px 22px; scroll-margin-top: calc(var(--topbar-h) + 24px); }
.degree .row-head { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.degree h3 { margin: 0; font-size: 1.05rem; font-weight: 600; }
.degree .when { font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-faint); white-space: nowrap; }
.degree .school { color: var(--ink-dim); font-size: 0.9rem; margin: 4px 0; }
.degree .note { font-family: var(--font-mono); font-size: 11.5px; color: var(--accent); margin-top: 8px; }

/* Generic left-edge type spine — shared by any card (degree, experience,
   project, skill). Color/label come from item.type via badge-types.json
   "types". Width stays the same everywhere a spine appears, including on
   the smaller compact-degree cards, so it reads as one consistent tag. */
.has-type { padding-left: 46px; }
.type-tab {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 28px;
  overflow: hidden;
  border-right: 1px dashed var(--type-color, var(--accent));
  background: color-mix(in srgb, var(--type-color, var(--accent)) 12%, transparent);
}
/* .type-tab-label is a plain, unrotated positioning box — its top/height
   are set in JS (layoutSpineLabels in main.js) each frame to track whatever
   part of the tab is actually on-screen as the page scrolls, rather than
   staying pinned to the middle of the full card. */
.type-tab-label {
  position: absolute;
  left: 0;
  width: 100%;
}
/* The text itself is rotated via transform rather than writing-mode:
   vertical-rl. writing-mode's built-in glyph rotation gets antialiased by
   several rendering backends as though the text were still horizontal,
   leaving the rotated edges visibly jagged/fringed — an explicit rotate()
   on normally-shaped horizontal glyphs is the same technique the browser
   already uses for any other rotated element, and rasterizes cleanly. */
.type-tab-label-text {
  position: absolute;
  top: 50%;
  left: 50%;
  white-space: nowrap;
  transform: translate(-50%, -50%) rotate(90deg);
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--type-color, var(--accent));
}
/* Compact degree cards and hero tiles are too short/narrow for the standard
   size — always smaller here rather than measuring at runtime whether a
   given label happens to wrap; simpler, and one consistent size per context
   beats a size that depends on which label it happens to be showing. */
.degree--compact .type-tab-label-text,
.highlight-tile .type-tab-label-text {
  font-size: 10px;
  letter-spacing: 0.06em;
}

/* Status badge shared by term cards, degree cards, and experience cards —
   color/label come from data (status.in-progress, status.current, ...). */
.status-badge {
  position: absolute;
  top: -4px; /* badge sits ~80% inside its card, ~20% poking above it */
  right: 10px;
  background: var(--bg-raised);
  border: 1px dashed var(--badge-color, var(--amber));
  color: var(--badge-color, var(--amber));
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 1px 6px;
  white-space: nowrap;
}
/* Opt-in: a card can have its own border pick up the status badge's color
   and dashed style (set via applyBlockBadges's borderMatchesStatus option,
   or applied directly on hero tiles) — enabled only for education's
   in-progress cards, matching how term cards already treat in-progress;
   left off for experience's "Currently Employed". */
.status-border-match { border-color: var(--badge-color, var(--amber)); border-style: dashed; }

/* Compact degrees (CMC, Summit HS) — minimal card, smaller type throughout */
.degree--compact { padding: 12px 16px; }
/* Compound selector: .degree--compact's padding shorthand and .has-type's
   padding-left are equal specificity, and this rule comes later in the file,
   so without the compound form it would always win and the spine would sit
   on top of the school name (same trap as the .expand-toggle rule above). */
.degree--compact.has-type { padding-left: 46px; }
.degree--compact .school { font-size: 0.78rem; margin: 0; }
.degree--compact .expand-toggle { margin-top: 8px; font-size: 10px; }

.expand-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 14px;
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}
.expand-toggle:hover { color: #7fe6ff; }
.expand-toggle .chevron { display: flex; transition: transform 0.25s ease; }
.expand-toggle[aria-expanded="true"] .chevron { transform: rotate(180deg); }

.degree-detail {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.35s ease;
}
.degree-detail-inner {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 10px;
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px dashed var(--line);
}
/* min-width:0 — a flex/grid item's automatic minimum size otherwise floors
   at its content's min-content width, which can override the fixed width
   set below (82% in the mobile carousel) once a course flips from its short
   code to a longer title, shifting the card's real width and, with it, the
   carousel's total scrollWidth out from under the current scroll position. */
.term-card { position: relative; min-width: 0; border: 1px solid var(--line); background: var(--bg-raised); }
.term-card.has-status { border-color: var(--badge-color, var(--amber)); border-style: dashed; }
@media (max-width: 640px) {
  .degree-detail-inner { grid-template-columns: 1fr; }
  .degree-detail-inner[data-many="true"] {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    padding-bottom: 4px;
  }
  .degree-detail-inner[data-many="true"] .term-card {
    flex: none;
    width: 82%;
    scroll-snap-align: start;
  }
}
.term-card .term-head {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.08em;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  color: var(--ink-dim);
  text-transform: uppercase;
}
.term-card.has-status .term-head { color: var(--badge-color, var(--amber)); }
.term-card .courses { padding: 10px 14px; display: flex; flex-direction: column; gap: 7px; }
.course-row { position: relative; cursor: pointer; font-size: 12.5px; }
/* overflow:hidden, not just relying on opacity — the inactive face is still
   fully laid out and slid 105% to one side, so without a hard clip here its
   painted extent can leak into an ancestor's scrollable area even though
   it's invisible, inflating the mobile carousel's scrollWidth. */
.course-row .face-stack { display: grid; overflow: hidden; }
.course-row .face {
  grid-area: 1 / 1;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  min-width: 0;
  transition: transform 0.35s ease, opacity 0.35s ease;
}
.course-row .face-code { transform: translateX(0); }
.course-row .face-name { transform: translateX(105%); opacity: 0; }
.course-row[data-flipped="true"] .face-code { transform: translateX(-105%); opacity: 0; }
.course-row[data-flipped="true"] .face-name { transform: translateX(0); opacity: 1; }
.course-row .ccode { font-family: var(--font-mono); color: var(--ink-mid); white-space: nowrap; }
.course-row .ctitle { color: var(--ink-mid); line-height: 1.3; }
.course-row .cgrade-tag {
  flex: none;
  width: 24px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--grade-color, var(--ink-faint));
  border: 1px solid var(--grade-color, var(--line-strong));
  padding: 1px 0;
}

/* ===== Contact / footer ===== */
.contact-panel {
  border: 1px solid var(--line);
  background: var(--bg-panel);
  padding: 36px;
  text-align: left;
  position: relative;
}
.contact-panel h2 { font-size: 1.6rem; margin: 0 0 10px; }
.contact-panel p { color: var(--ink-dim); max-width: 50ch; margin: 0 0 24px; }

/* Title block footer */
.title-block {
  border-top: 1px solid var(--line-strong);
  margin-top: 20px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  font-family: var(--font-mono);
  font-size: 11px;
}
@media (min-width: 640px) { .title-block { grid-template-columns: repeat(4, 1fr); } }
.title-block .cell { border-right: 1px solid var(--line-strong); border-bottom: 1px solid var(--line-strong); padding: 10px 12px; }
.title-block .cell:nth-child(2n) { border-right: none; }
@media (min-width: 640px) {
  .title-block .cell:nth-child(2n) { border-right: 1px solid var(--line-strong); }
  .title-block .cell:last-child { border-right: none; }
}
.title-block .cell .k { color: var(--ink-faint); letter-spacing: 0.1em; display: block; margin-bottom: 4px; }
.title-block .cell .v { color: var(--ink-dim); }

footer.site-footer { padding: 28px max(var(--frame-inset), 20px) 60px; text-align: center; font-family: var(--font-mono); font-size: 11px; color: var(--ink-faint); position: relative; }

/* ===== Reveal-in animation ===== */
.reveal { opacity: 0; transform: translateY(10px); transition: opacity 0.5s ease, transform 0.5s ease; }
/* transform:none (not translateY(0)) once settled — any non-none transform
   creates a stacking context, which would trap nested .info-popups below
   the topbar/other high z-index UI regardless of the popup's own z-index. */
.reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
}

/* Loading skeleton */
.skel { color: var(--ink-faint); font-family: var(--font-mono); font-size: 12px; }
