/* ════════════════════════════════════════════════════════════════════════
   style.css — Portfolio stylesheet
   ────────────────────────────────────────────────────────────────────────
   All CSS custom properties are defined on :root here and are fully
   accessible to JavaScript via:
     document.documentElement.style.setProperty('--accent', '#ff0000')
   This works identically whether the property is defined in an external
   stylesheet or in a <style> block — the cascade is the same.

   Load order in index.html:
     1. Google Fonts (preconnect + stylesheet link)
     2. Tailwind CDN + config <script>
     3. <link rel="stylesheet" href="style.css">   ← this file
     4. Vue CDN <script>
   ════════════════════════════════════════════════════════════════════════ */


/* ══════════════════════════════════════════════════════════════════════
   1. DESIGN TOKENS
   ──────────────────────────────────────────────────────────────────────
   All colour, timing, and spacing values flow from these custom
   properties. The Spotify integration overwrites --accent, --accent-2,
   --accent-3, and all --bg-* values in real time via applyTheme() in
   the JS. Everything else stays fixed.
   ══════════════════════════════════════════════════════════════════════ */
   :root {
    /* ── Base palette — warm dark editorial ───────────────────────── */
    --bg:     #0f0f11;   /* page background                          */
    --bg-1:   #141417;   /* slightly elevated surfaces (nav, footer) */
    --bg-2:   #1a1a1e;   /* cards, inset panels                      */
    --bg-3:   #202024;   /* deepest surface (code blocks, inputs)    */
  
    /* ── Typography ────────────────────────────────────────────────── */
    --text:   #ddd8cf;   /* primary — warm off-white                 */
    --text-2: #9d9d9f;   /* secondary / muted body                   */
    --text-3: #525257;   /* very muted — labels, placeholders        */
  
    /* ── Typography Scale (Accessible Minimums) ────────────────────── */
    --fs-xs: 0.875rem; /* 14px - Minimum for metadata, tags, and labels */
    --fs-sm: 1rem;     /* 16px - Base body text and descriptions */
    --fs-md: 1.125rem; /* 18px - Prominent body text / smaller headings */
    --fs-lg: 1.25rem;  /* 20px - Standard headings */
    --fs-xl: 2rem;     /* 32px - Stat values / medium headings */
    --fs-display-sm: clamp(1.8rem, 3vw, 2.5rem); /* Section Titles */
    --fs-display-lg: clamp(3rem, 6vw, 5.5rem);   /* Hero Headings */
  
    /* ── Borders & rules ───────────────────────────────────────────── */
    --rule:   rgba(221, 216, 207, 0.08);   /* hairline dividers      */
    --rule-2: rgba(221, 216, 207, 0.14);   /* slightly visible rules */
  
    /* ── Accent colours ────────────────────────────────────────────── */
    /* These three are overwritten by Spotify album-art extraction.    */
    /* Default palette: muted periwinkle + warm bronze + slate blue.   */
    --accent:   #747474;   /* primary accent — dominant album colour  */
    --accent-2: #949494;   /* secondary accent — contrast album colour*/
    --accent-3: #b5b5b5;   /* tertiary mid-tone — third album colour  */
  
    /* ── BPM animation timing ──────────────────────────────────────── */
    /* Updated by Spotify audio-features tempo. Default ≈ 100 BPM.    */
    --bpm:      600ms;
    --bpm-half: 300ms;
  
    /* ── Global theme transition ───────────────────────────────────── */
    /* Applied to all colour-bearing properties so Spotify theme       */
    /* swaps feel smooth rather than jarring.                          */
    --t: 700ms ease;
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     2. RESETS
     ══════════════════════════════════════════════════════════════════════ */
  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
  
  html {
    scroll-behavior: smooth;
  }
  
  body {
    background: var(--bg);
    color: var(--text);
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--fs-sm);
    line-height: 1.6;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
    transition: background var(--t), color var(--t);
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     3. SCROLLBAR
     ══════════════════════════════════════════════════════════════════════ */
  ::-webkit-scrollbar         { width: 3px; }
  ::-webkit-scrollbar-track   { background: var(--bg); }
  ::-webkit-scrollbar-thumb   {
    background: var(--rule-2);
    border-radius: 2px;
    transition: background var(--t);
  }
  ::-webkit-scrollbar-thumb:hover { background: var(--accent-2); }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     4. SELECTION
     ══════════════════════════════════════════════════════════════════════ */
  ::selection {
    background: var(--accent);
    color: var(--bg);
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     5. NOISE TEXTURE OVERLAY
     ──────────────────────────────────────────────────────────────────────
     A very faint SVG fractal-noise grain applied as a fixed pseudo-element
     over the entire viewport. Adds material depth without visual loudness.
     pointer-events: none so it never intercepts clicks.
     ══════════════════════════════════════════════════════════════════════ */
  body::after {
    content: '';
    position: fixed;
    inset: 0;
    z-index: 9000;
    pointer-events: none;
    opacity: 0.025;
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
    background-repeat: repeat;
    background-size: 128px 128px;
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     6. TYPOGRAPHY UTILITIES
     ══════════════════════════════════════════════════════════════════════ */
  
  /* Fraunces serif display — used for headings */
  .display {
    font-family: 'Fraunces', Georgia, serif;
    font-optical-sizing: auto;
    letter-spacing: -0.02em;
    line-height: 1.05;
  }
  
  .display-italic {
    font-family: 'Fraunces', Georgia, serif;
    font-style: italic;
    font-weight: 300;
    font-optical-sizing: auto;
    letter-spacing: -0.01em;
  }
  
  /* IBM Plex Mono label — small-caps style metadata text */
  .label {
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--fs-xs);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-2);
    transition: color var(--t);
  }
  
  /* Same as .label but coloured with the primary accent */
  .label-accent {
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--fs-xs);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent);
    transition: color var(--t);
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     7. LAYOUT PRIMITIVES
     ══════════════════════════════════════════════════════════════════════ */
  .container {
    max-width: 1100px;
    margin: 0 auto;
    padding: 0 2rem;
  }
  
  /* Horizontal hairline divider */
  .rule {
    width: 100%;
    height: 1px;
    background: var(--rule);
    transition: background var(--t);
  }
  
  /* Vertical hairline divider */
  .rule-v {
    width: 1px;
    background: var(--rule);
    transition: background var(--t);
  }
  
  /* Small accent ruled line — decorative, placed under section headings */
  .accent-line {
    display: block;
    width: 32px;
    height: 1px;
    background: var(--accent);
    transition: background var(--t);
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     8. CARD COMPONENT
     ══════════════════════════════════════════════════════════════════════ */
  .card {
    background: var(--bg-1);
    border: 1px solid var(--rule);
    transition:
      border-color 250ms ease,
      transform 250ms ease,
      background var(--t);
  }
  
  .card:hover {
    border-color: var(--rule-2);
    transform: translateY(-2px);
  }
  
  /* Top accent bar — uses --accent-2 (secondary album colour) */
  .card-accent {
    border-top: 1px solid var(--accent-2);
    transition: border-color var(--t);
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     9. BPM ANIMATIONS
     ──────────────────────────────────────────────────────────────────────
     All animations use --bpm or --bpm-half as their duration, so they
     automatically sync to the current track's tempo when Spotify is
     connected. The animations themselves are deliberately subtle —
     opacity and scale only, never colour or layout shifts.
     ══════════════════════════════════════════════════════════════════════ */
  
  /* Gentle opacity breathing — used on the Spotify widget */
  .bpm-breathe {
    animation: breathe var(--bpm) ease-in-out infinite;
  }
  
  @keyframes breathe {
    0%, 100% { filter: brightness(1); }
    50%       { filter: brightness(1.2);    }
  }
  
  /* Pulsing dot in the nav and footer — confirms Spotify is live */
  .bpm-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    flex-shrink: 0;
    animation: breathe var(--bpm) ease-in-out infinite;
    transition: background var(--t);
  }
  
  /* Cursor blink for typewriter — separate keyframe from breathe */
  @keyframes cursorBlink {
    0%, 100% { opacity: 1; }
    50%       { opacity: 0; }
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     10. NAVIGATION
     ══════════════════════════════════════════════════════════════════════ */
  header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 200;
    background: rgba(15, 15, 17, 0.9);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-bottom: 1px solid var(--rule);
    transition: border-color var(--t);
  }
  
  .nav-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 2rem;
    max-width: 1100px;
    margin: 0 auto;
  }
  
  /* Logo uses a gradient spanning --text → --accent-2 for subtle warmth */
  .nav-logo {
    font-family: 'Fraunces', serif;
    font-size: var(--fs-sm);
    font-weight: 600;
    letter-spacing: -0.02em;
    background: linear-gradient(135deg, var(--text) 40%, var(--accent-2));
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    /* Gradient can't CSS-transition cleanly, so we skip transition here */
  }
  
  .nav-links {
    display: flex;
    align-items: center;
    gap: 2rem;
  }
  
  .nav-link {
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--fs-xs);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-2);
    text-decoration: none;
    transition: color 200ms ease;
  }
  
  .nav-link:hover {
    color: var(--text);
  }
  
  .nav-status {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     11. HERO SECTION
     ══════════════════════════════════════════════════════════════════════ */
  .hero {
    min-height: 100vh;
    display: flex;
    align-items: center;
    padding: 8rem 2rem 6rem;
    border-bottom: 1px solid var(--rule);
    transition: border-color var(--t);
  }
  
  .hero-grid {
    max-width: 1100px;
    margin: 0 auto;
    width: 100%;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6rem;
    align-items: center;
  }
  
  .hero-heading {
    font-family: 'Fraunces', Georgia, serif;
    font-weight: 400;
    font-optical-sizing: auto;
    font-size: var(--fs-display-lg);
    line-height: 1.0;
    letter-spacing: -0.03em;
    color: var(--text);
  }
  
  /* The italic part of the heading picks up --accent-3 (mid-tone album colour) */
  .hero-heading em {
    font-style: italic;
    font-weight: 300;
    color: var(--accent-3);
    transition: color var(--t);
  }
  
  .hero-sub {
    font-size: var(--fs-sm);
    color: var(--text-2);
    line-height: 1.8;
    max-width: 400px;
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     12. TYPEWRITER CURSOR
     ══════════════════════════════════════════════════════════════════════ */
  .tw-cursor {
    display: inline-block;
    width: 1px;
    height: 1em;
    background: var(--accent);
    margin-left: 1px;
    vertical-align: text-bottom;
    animation: cursorBlink 1.1s step-end infinite;
    transition: background var(--t);
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     13. SPOTIFY WIDGET
     ══════════════════════════════════════════════════════════════════════ */
  .spotify-widget {
    background: var(--bg-2);
    border: 1px solid var(--rule);
    padding: 1.25rem;
    transition: background var(--t), border-color var(--t);
  }
  
  /* Album artwork thumbnail */
  .album-art {
    width: 52px;
    height: 52px;
    flex-shrink: 0;
    object-fit: cover;
    box-shadow: 0 0 0 1px var(--rule);
    transition: box-shadow var(--t);
  }
  
  /* Equaliser bar container */
  .eq-bars {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 14px;
  }
  
  /* Each bar animates at --bpm-half (half the beat interval) with
     staggered delays so they don't all move in unison. */
  .eq-bar {
    width: 2px;
    border-radius: 1px;
    background: var(--accent);
    transform-origin: bottom;
    animation: eqBounce var(--bpm-half) ease-in-out infinite alternate;
    transition: background var(--t);
  }
  
  .eq-bar:nth-child(1) { height: 6px;  animation-delay: 0ms;   }
  .eq-bar:nth-child(2) { height: 10px; animation-delay: 80ms;  }
  .eq-bar:nth-child(3) { height: 14px; animation-delay: 40ms;  }
  .eq-bar:nth-child(4) { height: 8px;  animation-delay: 120ms; }
  .eq-bar:nth-child(5) { height: 12px; animation-delay: 60ms;  }
  
  @keyframes eqBounce {
    from { transform: scaleY(0.25); }
    to   { transform: scaleY(1);    }
  }
  
  /* When the track is paused, freeze bars at minimum height */
  .eq-bar.paused {
    animation: none;
    transform: scaleY(0.25);
  }
  
  /* Thin progress line at the bottom of the widget */
  .track-progress {
    height: 1px;
    background: var(--rule-2);
    position: relative;
    overflow: hidden;
  }
  
  .track-progress-fill {
    height: 100%;
    background: var(--accent);
    transition: width 1s linear, background var(--t);
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     14. SECTION LAYOUT
     ══════════════════════════════════════════════════════════════════════ */
  section {
    border-bottom: 1px solid var(--rule);
    padding: 6rem 2rem;
    transition: border-color var(--t), background var(--t);
  }
  
  .section-inner {
    max-width: 1100px;
    margin: 0 auto;
  }
  
  .section-header {
    display: flex;
    align-items: baseline;
    gap: 1.5rem;
    margin-bottom: 4rem;
  }
  
  .section-title {
    font-family: 'Fraunces', Georgia, serif;
    font-size: var(--fs-display-sm);
    font-weight: 400;
    letter-spacing: -0.02em;
    color: var(--text);
    line-height: 1;
  }
  
  /* Section number uses --accent-2 so it picks up the secondary album colour */
  .section-num {
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--fs-xs);
    color: var(--accent-2);
    letter-spacing: 0.1em;
    padding-bottom: 0.2em; /* align to serif baseline */
    transition: color var(--t);
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     15. PROJECT CARDS
     ══════════════════════════════════════════════════════════════════════ */
  
  /* Grid uses 1px gap with the gap background set to --rule colour,
     which makes cards appear to share a single hairline border. */
  .project-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 1px;
    background: var(--rule);
    border: 1px solid var(--rule);
    transition: background var(--t), border-color var(--t);
  }
  
  .project-card {
    background: var(--bg);
    padding: 2rem;
    display: flex;
    flex-direction: column;
    transition: background 200ms ease;
  }
  
  .project-card:hover {
    background: var(--bg-1);
  }
  
  .project-index {
    font-size: var(--fs-xs);
    letter-spacing: 0.15em;
    color: var(--text-3);
    margin-bottom: 1.25rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    transition: color var(--t);
  }
  
  .project-name {
    font-family: 'Fraunces', Georgia, serif;
    font-size: var(--fs-md);
    font-weight: 400;
    letter-spacing: -0.01em;
    color: var(--text);
    margin-bottom: 0.75rem;
    line-height: 1.3;
  }
  
  .project-desc {
    font-size: var(--fs-sm);
    color: var(--text-2);
    line-height: 1.75;
    flex: 1;
    margin-bottom: 1.5rem;
  }
  
  /* Tech tag pill */
  .tag {
    display: inline-block;
    font-size: var(--fs-xs);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-3);
    border: 1px solid var(--rule);
    padding: 2px 8px;
    margin: 2px;
    transition: color 200ms, border-color 200ms;
  }
  
  /* Tags use --accent-3 on hover (third extracted album colour) */
  .tag:hover {
    color: var(--accent-3);
    border-color: var(--accent-3);
  }
  
  .project-links {
    display: flex;
    gap: 1.5rem;
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--rule);
    transition: border-color var(--t);
  }
  
  .project-link {
    font-size: var(--fs-xs);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-3);
    text-decoration: none;
    transition: color 200ms;
    display: flex;
    align-items: center;
    gap: 6px;
  }
  
  .project-link:hover {
    color: var(--accent);
  }
  
  /* Small dot indicating a project is currently live/deployed */
  .active-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent-2);
    opacity: 0.8;
    transition: background var(--t);
    flex-shrink: 0;
  }
  
  
  /* ══════════════════════════════════════════════════════════════════
      SKILLS SECTION
      ══════════════════════════════════════════════════════════════════ */
      .skill-row {
      display: flex;
      align-items: center;
      gap: 1.5rem;
      padding: 1rem 0;
      border-bottom: 1px solid var(--rule);
      transition: border-color var(--t);
    }
  
    .skill-row:last-child { border-bottom: none; }
  
    .skill-name {
      font-size: var(--fs-sm);
      color: var(--text-2);
      width: 200px;
      flex-shrink: 0;
    }
  
    .skill-bar-track {
      flex: 1;
      height: 1px;
      background: var(--rule-2);
      position: relative;
      transition: background var(--t);
    }
  
    .skill-bar-fill {
      height: 1px;
      background: var(--accent);
      position: absolute;
      top: 0; left: 0;
      transition: width 1.4s cubic-bezier(0.16, 1, 0.3, 1), background var(--t);
    }
  
    /* BPM-driven subtle glow on the fill end point */
    .skill-bar-fill::after {
      content: '';
      position: absolute;
      right: -1px; top: -2px;
      width: 3px; height: 5px;
      background: var(--accent);
      border-radius: 1px;
      animation: breathe var(--bpm) ease-in-out infinite;
      transition: background var(--t);
    }
  
    .skill-pct {
      font-size: var(--fs-xs);
      color: var(--text-3);
      width: 36px;
      text-align: right;
      flex-shrink: 0;
      transition: color var(--t);
    }
  
    /* ══════════════════════════════════════════════════════════════════════
     16. SKILL TREE
     ──────────────────────────────────────────────────────────────────────
     A three-level top-down tree: root → branches → leaves.
     Each branch column uses ::before and ::after to draw its segment of
     the shared horizontal bar, so the bar spans exactly from the centre
     of the first column to the centre of the last.
   
     --bc is a per-branch CSS custom property set via inline style in the
     Vue template. It references --accent, --accent-2, or --accent-3 so
     each branch automatically inherits the Spotify-extracted palette.
     ══════════════════════════════════════════════════════════════════════ */
   
  /* ── Tree container ───────────────────────────────────────────────── */
  .st-tree {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 1rem 0 2rem;
  }
   
  /* ── Root row ─────────────────────────────────────────────────────── */
  .st-root-row {
    display: flex;
    justify-content: center;
    margin-bottom: 0;
  }
   
  /* ── Shared node base ─────────────────────────────────────────────── */
  .st-node {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    text-align: center;
    position: relative;
    transition: border-color var(--t), background var(--t);
  }
   
  /* Primary label inside a node */
  .st-primary {
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--fs-xs);
    letter-spacing: 0.08em;
    color: var(--text);
    text-transform: uppercase;
    transition: color var(--t);
  }
   
  /* Sub-label (secondary technology) */
  .st-secondary {
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--fs-xs);
    letter-spacing: 0.1em;
    color: var(--text-3);
    text-transform: uppercase;
    transition: color var(--t);
  }
   
  /* ── ROOT NODE ────────────────────────────────────────────────────── */
  .st-node--root {
    border: 1px solid var(--accent);
    background: var(--bg-2);
    padding: 1.1rem 2rem;
    transition: border-color var(--t), background var(--t), opacity 0ms;
  }
   
  /* Corner bracket decorations using ::before and ::after */
  .st-node--root::before,
  .st-node--root::after {
    content: '';
    position: absolute;
    width: 8px;
    height: 8px;
    border-color: var(--accent-2);
    border-style: solid;
    transition: border-color var(--t);
  }
   
  .st-node--root::before {
    top: -3px; left: -3px;
    border-width: 1px 0 0 1px;
  }
   
  .st-node--root::after {
    bottom: -3px; right: -3px;
    border-width: 0 1px 1px 0;
  }
   
  .st-node--root .st-primary {
    font-family: 'Fraunces', Georgia, serif;
    font-size: var(--fs-lg);
    font-weight: 400;
    letter-spacing: -0.01em;
    text-transform: none;
    color: var(--text);
  }
   
  .st-node--root .st-secondary {
    font-size: var(--fs-xs);
    color: var(--accent);
    letter-spacing: 0.15em;
    text-transform: uppercase;
    transition: color var(--t);
  }
   
  /* ── TRUNK: root → branch bar ─────────────────────────────────────── */
  .st-trunk {
    width: 1px;
    height: 3rem;
    background: var(--accent);
    position: relative;
    overflow: hidden;
    transition: background var(--t);
  }
   
  /* ── BRANCH ROW ───────────────────────────────────────────────────── */
  .st-branch-row {
    display: flex;
    justify-content: center;
    width: 100%;
    max-width: 860px;
  }
   
  /* Each branch column */
  .st-branch-col {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    position: relative;
    /* Space above for the horizontal bar + vdrop */
    padding-top: 0;
  }
   
  /* ── Horizontal bar across branches (::before = left half, ::after = right half) */
  .st-branch-col::before,
  .st-branch-col::after {
    content: '';
    position: absolute;
    top: 0;
    height: 1px;
    background: var(--bc, var(--accent));
    transition: background var(--t);
  }
   
  .st-branch-col::before { left: 0;   right: 50%; }
  .st-branch-col::after  { left: 50%; right: 0;   }
   
  /* First column: no left overhang (bar starts at its own centre) */
  .st-branch-col:first-child::before { left: 50%; }
  /* Last column: no right overhang (bar ends at its own centre) */
  .st-branch-col:last-child::after   { right: 50%; }
   
  /* ── Vertical drop: horizontal bar → branch node ─────────────────── */
  .st-vdrop {
    width: 1px;
    height: 2.5rem;
    background: var(--bc, var(--accent));
    position: relative;
    overflow: hidden;
    transition: background var(--t);
    flex-shrink: 0;
  }
   
  /* ── BRANCH NODE (category label) ────────────────────────────────── */
  .st-node--branch {
    border: 1px solid var(--bc, var(--accent));
    background: var(--bg-1);
    padding: 0.5rem 1.25rem;
    flex-shrink: 0;
  }
   
  .st-node--branch .st-primary,
  .st-node--branch {
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--fs-xs);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--bc, var(--accent));
    transition: color var(--t);
  }
   
  /* ── Trunk: branch node → leaf bar ──────────────────────────────── */
  .st-leaf-trunk {
    width: 1px;
    height: 2rem;
    background: var(--bc, var(--accent));
    position: relative;
    overflow: hidden;
    transition: background var(--t);
    flex-shrink: 0;
  }
   
  /* ── LEAF ROW ─────────────────────────────────────────────────────── */
  .st-leaf-row {
    display: flex;
    justify-content: center;
    width: 100%;
  }
   
  /* Each leaf column — same bar technique as branch columns */
  .st-leaf-col {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    position: relative;
  }
   
  .st-leaf-col::before,
  .st-leaf-col::after {
    content: '';
    position: absolute;
    top: 0;
    height: 1px;
    background: var(--bc, var(--accent));
    opacity: 0.5;
    transition: background var(--t);
  }
   
  .st-leaf-col::before { left: 0;   right: 50%; }
  .st-leaf-col::after  { left: 50%; right: 0;   }
   
  .st-leaf-col:first-child::before { left: 50%; }
  .st-leaf-col:last-child::after   { right: 50%; }
   
  /* ── Vertical drop: leaf bar → leaf node ─────────────────────────── */
  .st-leaf-vdrop {
    width: 1px;
    height: 1.75rem;
    background: var(--bc, var(--accent));
    opacity: 0.5;
    position: relative;
    overflow: hidden;
    transition: background var(--t);
    flex-shrink: 0;
  }
   
  /* ── LEAF NODE ────────────────────────────────────────────────────── */
  .st-node--leaf {
    border: 1px solid var(--bc, var(--accent));
    background: var(--bg);
    margin-right: 15px;
    padding: 0.65rem 0.9rem;
    min-width: 140px;
    /* Override breathe: leaf nodes use opacity range 0.6–1.0 */
    animation: leafBreathe var(--bpm) ease-in-out infinite;
    transition:
      border-color var(--t),
      background var(--t),
      opacity 0ms,
      transform 200ms ease;
  }
   
  .st-node--leaf:hover {
    opacity: 1;
    transform: translateY(-2px);
    background: var(--bg-2);
  }
   
  .st-node--leaf .st-primary {
    font-size: var(--fs-sm);
    letter-spacing: 0.08em;
    color: var(--bc, var(--accent));
    transition: color var(--t);
    white-space: nowrap;
  }
   
  .st-node--leaf .st-secondary {
    font-size: var(--fs-xs);
  }
   
  /* Leaf breathe: slightly more present than the global breathe keyframe */
  @keyframes leafBreathe {
    0%, 100% { filter: brightness(1); }
    50%       { filter: brightness(1.1);    }
  }
   
  /* ── SIGNAL TRACE ANIMATION ───────────────────────────────────────── */
  /* An animated gradient that travels down vertical connector lines,    */
  /* simulating a signal pulse travelling through the circuit.           */
  /* The .st-trace element is placed inside each connector div and       */
  /* fills it fully, then slides its gradient from top to bottom.        */
  .st-trace--v {
    position: absolute;
    inset: 0;
    background: linear-gradient(
      180deg,
      transparent    0%,
      var(--bc, var(--accent)) 50%,
      transparent    100%
    );
    animation: traceV 2.4s ease-in-out infinite;
    pointer-events: none;
  }
   
  @keyframes traceV {
    0%   { transform: translateY(-100%); opacity: 0;   }
    20%  { opacity: 1; }
    80%  { opacity: 1; }
    100% { transform: translateY(100%);  opacity: 0;   }
  }
  
  /* ══════════════════════════════════════════════════════════════════════
     SKILL TREE — RESPONSIVE
     ──────────────────────────────────────────────────────────────────────
     Three tiers of layout:
  
     > 860px  — Full horizontal tree (default, no rules needed here)
     560–860px — Compressed horizontal tree: smaller nodes and tighter
                 spacing so the tree fits a tablet without scrolling.
     < 560px  — Vertical stacked tree: each branch group becomes its own
                 centred mini-tree stacked top-to-bottom. Leaves stay
                 horizontal within their branch. A continuous vertical
                 spine connects the groups.
     ══════════════════════════════════════════════════════════════════════ */
  
  /* ── TIER 2: Tablet — compressed horizontal ───────────────────────── */
  @media (max-width: 860px) {
  
    /* Tighten the outer tree container */
    .st-tree {
      padding: 0.5rem 0 1.5rem;
    }
  
    /* Root node — slightly smaller */
    .st-node--root {
      padding: 0.85rem 1.4rem;
    }
  
    .st-node--root .st-primary {
      font-size: var(--fs-sm);
    }
  
    /* Shorten the trunk line */
    .st-trunk    { height: 2rem; }
    .st-vdrop    { height: 1.75rem; }
    .st-leaf-trunk  { height: 1.5rem; }
    .st-leaf-vdrop  { height: 1.25rem; }
  
    /* Branch node — smaller label */
    .st-node--branch {
      padding: 0.4rem 0.9rem;
    }
  
    .st-node--branch .st-primary,
    .st-node--branch {
      font-size: var(--fs-xs);
      letter-spacing: 0.1em;
    }
  
    /* Leaf node — tighter */
    .st-node--leaf {
      padding: 0.5rem 0.6rem;
    }
  
    .st-node--leaf .st-primary  { font-size: var(--fs-xs); }
    .st-node--leaf .st-secondary { font-size: var(--fs-xs); }
  
    /* Limit tree width so it doesn't grow too wide */
    .st-branch-row { max-width: 700px; }
  }
  
  
  /* ── TIER 3: Mobile — vertical stacked tree ───────────────────────── */
  @media (max-width: 560px) {
  
    /* Scroll container — allows the inner tree to be its natural size.
       The fade-right gradient hints that the content is interactive.   */
    .st-scroll-outer {
      width: 100%;
      position: relative;
    }
  
    /* ── Transform the branch row from horizontal to vertical ─────── */
    .st-branch-row {
      flex-direction: column;
      align-items: center;
      width: 100%;
      max-width: 100%;
      /* Relative positioning lets us draw the spine line behind the groups */
      position: relative;
    }
  
    /* ── VERTICAL SPINE ─────────────────────────────────────────────── */
    /* A continuous vertical line drawn behind all three stacked groups. */
    /* It runs from the top of the first group to the top of the last.  */
    .st-branch-row::before {
      content: '';
      position: absolute;
      top: 0;
      bottom: 2.5rem; /* stop at roughly the centre of the last branch node */
      left: 50%;
      width: 1px;
      background: var(--accent);
      transform: translateX(-50%);
      transition: background var(--t);
      z-index: 0;
    }
  
    /* ── Branch columns: centred column layout ──────────────────────── */
    .st-branch-col {
      width: 100%;
      max-width: 320px;
      align-items: center;
      /* Remove the horizontal bar pseudo-elements — not needed in column mode */
    }
  
    /* Hide the cross-branch horizontal bar */
    .st-branch-col::before,
    .st-branch-col::after {
      display: none;
    }
  
    /* The vdrop on each branch-col now acts as the spine-to-node drop.
       It connects from the central spine down to the branch node.      */
    .st-vdrop {
      height: 2.5rem;
      z-index: 1;
    }
  
    /* Space between stacked branch groups (between leaf row and next vdrop) */
    .st-branch-col + .st-branch-col {
      margin-top: 0; /* gap is provided by the spine + vdrop height */
    }
  
    /* ── Leaf rows: stay horizontal, centred within each group ─────── */
    .st-leaf-row {
      flex-direction: row;         /* keep leaves side by side */
      flex-wrap: wrap;             /* wrap if three don't fit  */
      justify-content: center;
      gap: 1px;
      background: var(--rule);
      border: 1px solid var(--rule);
      transition: background var(--t), border-color var(--t);
      width: 100%;
      max-width: 320px;
    }
  
    /* Hide the leaf-level horizontal bar — the border on st-leaf-row handles it */
    .st-leaf-col::before,
    .st-leaf-col::after {
      display: none;
    }
  
    /* Each leaf col fills evenly but has a min width */
    .st-leaf-col {
      flex: 1 1 90px;
      min-width: 90px;
    }
  
    /* Hide the leaf vdrop — leaves connect via the border grid, not tree lines */
    .st-leaf-vdrop {
      display: none;
    }
  
    /* Leaf nodes fill their col completely */
    .st-node--leaf {
      margin-right: 0;
      width: 100%;
      border: none; /* outer border is on .st-leaf-row */
      padding: 0.75rem 0.5rem;
    }
  
    /* Restore individual leaf border for hover distinction */
    .st-node--leaf:hover {
      background: var(--bg-2);
      transform: none; /* no lift in grid mode */
    }
  
    /* Leaf trunk (branch-node → leaf grid) — short connector */
    .st-leaf-trunk {
      height: 1.25rem;
      z-index: 1;
    }
  
    /* Branch node — full width of its container */
    .st-node--branch {
      width: auto;
      min-width: 140px;
      z-index: 1;
      background: var(--bg-2);
    }
  
    /* Root node — slightly narrower on phone */
    .st-node--root {
      padding: 0.9rem 1.75rem;
    }
  
    /* Trunk from root: stays as-is, already centred */
    .st-trunk { z-index: 1; }
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     17. ABOUT SECTION
     ══════════════════════════════════════════════════════════════════════ */
  .about-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6rem;
    align-items: start;
  }
  
  .about-bio {
    font-size: var(--fs-sm);
    line-height: 1.9;
    color: var(--text-2);
    font-family: 'IBM Plex Mono', monospace;
  }
  
  /* 2×2 stats grid with shared hairline borders */
  .stat-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0;
    border: 1px solid var(--rule);
    overflow: hidden;
    transition: border-color var(--t);
  }
  
  .stat-cell {
    padding: 1.5rem;
    border-bottom: 1px solid var(--rule);
    border-right: 1px solid var(--rule);
    transition: border-color var(--t);
  }
  
  .stat-cell:nth-child(even)        { border-right: none; }
  .stat-cell:nth-last-child(-n + 2) { border-bottom: none; }
  
  .stat-value {
    font-family: 'Fraunces', Georgia, serif;
    font-size: var(--fs-xl);
    font-weight: 300;
    color: var(--text);
    letter-spacing: -0.03em;
    line-height: 1;
    margin-bottom: 0.25rem;
  }
  
  .stat-label {
    font-size: var(--fs-xs);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-3);
    transition: color var(--t);
  }
  
  /* Tech stack tag row */
  .stack-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     18. CONTACT SECTION
     ══════════════════════════════════════════════════════════════════════ */
  .contact-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6rem;
  }
  
  .field-label {
    display: block;
    font-size: var(--fs-xs);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-3);
    margin-bottom: 0.5rem;
    transition: color var(--t);
  }
  
  .field-input {
    width: 100%;
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--rule-2);
    color: var(--text);
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--fs-sm);
    padding: 0.8rem 0;
    outline: none;
    transition: border-color 200ms, color var(--t);
  }
  
  .field-input:focus       { border-bottom-color: var(--accent); }
  .field-input::placeholder { color: var(--text-3); }
  .field-input.textarea    { resize: none; display: block; }
  
  .submit-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    background: transparent;
    border: 1px solid var(--rule-2);
    color: var(--text-2);
    font-family: 'IBM Plex Mono', monospace;
    font-size: var(--fs-sm);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    padding: 0.75rem 1.5rem;
    cursor: pointer;
    transition: border-color 200ms, color 200ms;
  }
  
  .submit-btn:hover {
    border-color: var(--accent);
    color: var(--accent);
  }
  
  /* Social link rows */
  .social-link {
    display: flex;
    gap: 2rem;
    align-items: center;
    justify-content: space-between;
    padding: 1.25rem 0;
    border-bottom: 1px solid var(--rule);
    text-decoration: none;
    transition: border-color var(--t);
  }
  
  .social-link:first-child { border-top: 1px solid var(--rule); }
  
  .social-platform {
    font-size: var(--fs-xs);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-3);
    width: 80px;
    flex-shrink: 0;
    transition: color var(--t);
  }
  
  .social-handle {
    font-size: var(--fs-sm);
    color: var(--text-2);
    flex: 1;
    transition: color 200ms;
  }
  
  .social-arrow {
    font-size: var(--fs-xs);
    color: var(--text-3);
    transition: color 200ms, transform 200ms;
  }
  
  .social-link:hover .social-handle { color: var(--text); }
  .social-link:hover .social-arrow  { color: var(--accent); transform: translateX(3px); }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     19. FOOTER
     ══════════════════════════════════════════════════════════════════════ */
  footer {
    padding: 2rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    max-width: 1100px;
    margin: 0 auto;
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     20. SCROLL REVEAL
     ──────────────────────────────────────────────────────────────────────
     Elements start invisible and translated down. The IntersectionObserver
     in setupReveal() adds .in-view when they enter the viewport, which
     triggers the transition to full opacity and natural position.
     ══════════════════════════════════════════════════════════════════════ */
  .reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 700ms ease, transform 700ms ease;
  }
  
  .reveal.in-view {
    opacity: 1;
    transform: translateY(0);
  }
  
  /* Staggered delays so groups of elements cascade in */
  .reveal-delay-1 { transition-delay: 100ms; }
  .reveal-delay-2 { transition-delay: 200ms; }
  .reveal-delay-3 { transition-delay: 300ms; }
  .reveal-delay-4 { transition-delay: 400ms; }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     22. HIDDEN CANVAS (Spotify colour extraction)
     ──────────────────────────────────────────────────────────────────────
     The canvas element is rendered off-screen. Album art is drawn here
     so we can call getImageData() without it appearing in the page.
     ══════════════════════════════════════════════════════════════════════ */
  #color-canvas {
    display: none;
    position: absolute;
    left: -9999px;
  }
  
  
  /* ══════════════════════════════════════════════════════════════════════
     23. RESPONSIVE — MOBILE
     ══════════════════════════════════════════════════════════════════════ */
  @media (max-width: 768px) {
    /* Collapse two-column grids to single column */
    .hero-grid    { grid-template-columns: 1fr; gap: 3rem; }
    .about-grid   { grid-template-columns: 1fr; gap: 3rem; }
    .contact-grid { grid-template-columns: 1fr; gap: 3rem; }
  
    /* Hide nav links — consider adding a hamburger menu if needed */
    .nav-links { display: none; }
  
    /* Shorten skill name column to fit narrower screens */
    .skill-name { width: 140px; }
  
    /* Reduce section padding on mobile */
    .hero    { padding: 7rem 1.5rem 4rem; }
    section  { padding: 4rem 1.5rem; }
  }
  