/* ============================================================================
   EDITOR CHROME — jackburch.dev design system
   ============================================================================

   The page is built from the visual grammar of the tools Jack lives in —
   node canvases, typed pins, property panels, splitter hairlines — rendered
   as a beautifully lit editor rather than a screenshot of one.

   Core ideas, so future-me can dissect this file:

   1. TWO FONTS, TWO VOICES. JetBrains Mono is the editor's furniture (nav,
      eyebrows, labels, chips, buttons, footer — "chrome text"). Archivo is
      the human speaking inside the editor (headings, prose). If a piece of
      text is UI, it's mono; if it's Jack talking, it's sans.

   2. TWO PIN TYPES, ONE GRAPH. Programming is exec-wire cyan (#56C2D6) on a
      dot-grid canvas; Tech Art is normal-map lavender (#9A8CFF) on a faint
      UV checker. Same node anatomy everywhere — only an accent variable,
      a texture, and chip PRESENCE change (status chips are Tech-Art-only,
      part of the honesty story). Each discipline section sets a local
      --pin / --pin-rgb custom property and every accent inside it (eyebrow,
      pins, chips, hover borders) inherits it. One line per section keeps
      the accents from drifting.

   3. THE WIRE. One continuous typed wire runs down a left rail (desktop
      only): neutral out of the hero, resolving to cyan at Programming,
      blending cyan→lavender (going dashed — "still being authored") into
      Tech Art, then relaxing back to neutral through About and terminating
      at the Contact header pin. Each section owns a .wire div; the divs butt
      against each other because they span each section's full padded height.
      Draw-on-scroll: each .wire has .reveal, and wire-specific rules below
      repurpose .show to scaleY the rail open. main.js can't observe a wire
      directly (collapsed to scaleY(0) it is a zero-area box that never
      crosses an intersection threshold), so it observes the wire's parent
      SECTION and forwards .show to the wire — see the reveal block there.

   4. EVERYTHING IS A TOKEN. All colors/spacing that repeat live in :root.
      Contrast pairs were verified at WCAG AA or better (most are AAA) —
      see the palette comments.
   ========================================================================== */

:root{
  /* ---- Surfaces -------------------------------------------------------- */
  --canvas:   #131721;  /* page background — deep blue-charcoal node canvas   */
  --panel:    #1A212C;  /* cards/nodes, header bar, footer — one step lifted  */
  --recess:   #10141B;  /* form inputs & inset wells — editors recess inputs  */
  --splitter: #2A3342;  /* DECORATIVE 1px hairlines only (cards/header/footer)*/

  /* Form-input boundary. --splitter is fine for decorative hairlines but is
     only ~1.3:1 against panel/recess — invisible to low-vision users, and an
     unfocused input's border is its ONLY boundary indicator (WCAG 1.4.11
     wants ≥3:1 for component boundaries). This step is 3.1:1 vs panel and
     3.6:1 vs the input's own recess fill. */
  --input-border: #5F6E88;

  /* ---- Ink ------------------------------------------------------------- */
  --text:  #E8ECF4;     /* primary text/headings — 15.1:1 on canvas (AAA)     */
  --muted: #A9B4C8;     /* secondary/body text — 8.6:1 on canvas (AAA)        */

  /* ---- The two pin types ---------------------------------------------- */
  --wire-cyan: #56C2D6;         /* Programming: exec-wire cyan (8.6:1 canvas) */
  --wire-cyan-rgb: 86,194,214;  /* raw triplet for rgba() mixing              */
  --normal-lav: #9A8CFF;        /* Tech Art: normal-map lavender (6.5:1, AA)  */
  --normal-lav-rgb: 154,140,255;

  --error: #FF7A85;             /* form validation only — 6.4:1 on panel (AA) */
  --ink-on-accent: #0F141C;     /* dark label on cyan-filled buttons (8.8:1)  */

  /* The wire's untyped/neutral color — muted at half alpha. Used for the
     hero stub, the About/Contact segments, and blend endpoints. */
  --neutral-wire: rgba(169,180,200,.5);

  /* ---- Type ------------------------------------------------------------ */
  --font-sans: 'Archivo', system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', Consolas, 'SF Mono', monospace;
  --h2-size: clamp(1.7rem, 3.5vw, 2.3rem); /* shared so the rail-pin math below can track it */

  /* ---- Layout ---------------------------------------------------------- */
  --sect-pad: clamp(80px, 12vw, 140px);  /* vertical rhythm of every section  */

  /* Horizontal position of the wire rail, measured from the section's left
     edge. Sections are full-bleed but the rail must align with the centered
     .container, so we recompute the container's left offset:
       (100% - containerWidth) / 2  puts us at the container's left edge,
       + 24px in — the rail line itself is 2px wide, so its AXIS is at +25px. */
  --rail-left: calc((100% - min(1100px, 92%)) / 2 + 24px);
}

/* Per-section pin typing — the one-line mechanism that keeps accents honest.
   Everything accent-colored inside a section reads var(--pin)/(--pin-rgb),
   so a section's whole identity changes by changing these two lines. */
.section{ --pin: var(--muted); --pin-rgb: 169,180,200; }        /* untyped default */
.section--prog{ --pin: var(--wire-cyan);  --pin-rgb: var(--wire-cyan-rgb); }
.section--tech{ --pin: var(--normal-lav); --pin-rgb: var(--normal-lav-rgb); }

/* ============================================================================
   BASE
   ========================================================================== */

*{ box-sizing:border-box }
html{
  scroll-behavior:smooth;
  overflow-x:hidden;
  /* Anchored sections shouldn't hide under the fixed header when navigated to. */
  scroll-padding-top: 72px;
}

body{
  margin:0;
  font-family: var(--font-sans);
  color: var(--text);
  /* Solid Canvas — the old black radial + amber washes are gone. The "lighting"
     now lives only in .hero__bg, where it reads as a deliberate key light. */
  background: var(--canvas);
  min-height:100vh;
  overflow-x:hidden;
}

::selection{ background: rgba(86,194,214,.28); }

/* Visible keyboard focus everywhere. Individual widgets refine this below
   (inputs use a border+ring, the filled button flips to a light outline). */
:focus-visible{
  outline: 2px solid var(--wire-cyan);
  outline-offset: 2px;
}

.container{
  width:min(1100px, 92%);
  margin-inline:auto;
  position:relative;
  z-index:1; /* content rides above the section-level .wire rail */
}

/* ============================================================================
   SKIP LINK — first focusable element on the page (WCAG 2.4.1). Parked
   off-screen; snaps into the top-left corner on keyboard focus. The global
   :focus-visible outline above provides the focus ring.
   ========================================================================== */

.skip-link{
  position:absolute;
  left:-9999px;
  z-index:101; /* above the fixed header (z-index:100) */
  padding:12px 18px;
  background:var(--panel);
  color:var(--text);
  font-family:var(--font-mono);
}
.skip-link:focus{ left:16px; top:16px; }

/* ============================================================================
   HEADER — a docked toolbar: translucent panel over the canvas, split from
   the page by a hairline. No glow, no drama; it's furniture.
   ========================================================================== */

.site-header{
  position:fixed; top:0; left:0; right:0; z-index:100;
  display:flex; align-items:center; justify-content:space-between;
  gap:16px;
  padding:18px 4%;
  background: rgba(19,23,33,.72);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-bottom:1px solid var(--splitter);
  transition: background .3s ease, padding .3s ease;
}
/* main.js toggles .scrolled past 50px — the bar just gets more opaque/compact. */
.site-header.scrolled{
  padding:12px 4%;
  background: rgba(19,23,33,.92);
}

.brand{
  color:var(--text);
  text-decoration:none;
  font-family: var(--font-sans);
  font-weight:700;
  font-size:1.05rem;
  letter-spacing:.01em;
  transition: color .3s ease;
}
.brand:hover{ color: var(--wire-cyan); }

.site-nav{ display:flex; gap:22px; flex-wrap:wrap; }
.site-nav a{
  /* Chrome text: mono, lowercase — the way panel tabs read in an editor. */
  font-family: var(--font-mono);
  font-weight:400;
  font-size:.85rem;
  text-transform: lowercase;
  color:var(--muted);
  text-decoration:none;
  transition: color .25s ease;
}
.site-nav a:hover{
  color:var(--text);
  /* Underline replaces the old animated ::after bar — quieter, same signal. */
  text-decoration: underline;
  text-decoration-color: var(--wire-cyan);
  text-decoration-thickness: 2px;
  text-underline-offset: 6px;
}
/* Nav-as-legend: the two discipline links carry their section's pin color as
   a 6px dot, teaching the cyan/lavender type system before the first scroll. */
.site-nav a[href="#programming"]::before,
.site-nav a[href="#tech-art"]::before{
  content:'';
  display:inline-block;
  width:6px; height:6px;
  border-radius:50%;
  margin-right:7px;
  vertical-align:1px;
}
.site-nav a[href="#programming"]::before{ background: var(--wire-cyan); }
.site-nav a[href="#tech-art"]::before{ background: var(--normal-lav); }

/* ============================================================================
   SECTIONS & CANVAS TEXTURES
   ========================================================================== */

.section{
  padding: var(--sect-pad) 0;
  position:relative; /* anchors the absolutely-positioned .wire rail */
}

/* Programming: node-canvas dot grid, barely-there cyan. Pure decoration —
   the alpha is far below any contrast threshold, and all text sits on
   Canvas or Panel for the AA math.

   TEXTURE BLENDING: the texture used to hard-cut at the section's edges
   (dots stopping dead where the hero / UV checker begins read as a seam).
   Two extra background layers fix it — opaque-Canvas→transparent fades
   painted OVER the texture at the top and bottom. Background layers stack
   first-declared-on-top, so the fades sit above the tile pattern; the fade
   distance is var(--sect-pad), meaning the texture is at full strength
   exactly where content lives and dissolves through the section's own
   padding into the neighbor's plain canvas. Both discipline sections use
   the same recipe, so every boundary crossfades through neutral Canvas. */
.section--prog{
  background-color: var(--canvas);
  background-image:
    linear-gradient(180deg, var(--canvas) 0, transparent var(--sect-pad)),
    linear-gradient(0deg,   var(--canvas) 0, transparent var(--sect-pad)),
    radial-gradient(circle, rgba(86,194,214,0.055) 1px, transparent 1.6px);
  background-repeat: no-repeat, no-repeat, repeat;
  background-size: 100% 100%, 100% 100%, 24px 24px;
}

/* Tech Art: faint UV checker — the texture every tech artist knows on sight.
   A conic gradient paints two opposite quarters of each 28px tile. Same
   edge-fade layers as Programming (see the blending comment above). */
.section--tech{
  background-color: var(--canvas);
  background-image:
    linear-gradient(180deg, var(--canvas) 0, transparent var(--sect-pad)),
    linear-gradient(0deg,   var(--canvas) 0, transparent var(--sect-pad)),
    conic-gradient(
      from 0deg,
      rgba(154,140,255,0.05) 0deg 90deg,
      transparent 90deg 180deg,
      rgba(154,140,255,0.05) 180deg 270deg,
      transparent 270deg 360deg
    );
  background-repeat: no-repeat, no-repeat, repeat;
  background-size: 100% 100%, 100% 100%, 28px 28px;
}
/* About and Contact stay untextured Canvas on purpose — the two discipline
   canvases only read as deliberate if somewhere else is plain. */

/* ============================================================================
   HERO — the origin node, softly lit.
   ========================================================================== */

.hero{
  min-height:100vh;
  display:grid;
  place-items:center;
  text-align:center;
  overflow:hidden;
  isolation:isolate; /* keeps .hero__bg's negative z-index inside the hero */
}
.hero__bg{
  position:absolute;
  inset:0;
  z-index:-1;
  /* "Beautifully lit editor": a cool soft key light from above, and a whisper
     of cyan rising from below — where the wire begins. */
  background:
    radial-gradient(1100px 600px at 50% -10%, rgba(122,152,200,0.14), transparent 62%),
    radial-gradient(700px 400px at 50% 105%, rgba(86,194,214,0.06), transparent 70%);
}
.hero__content{ position:relative; z-index:1; }

/* SVG headline — kept as SVG text so it scales exactly as drawn. */
.hero-title{
  width:100%;
  max-width:900px;
  height:auto;
  margin:0 auto;
  display:block;
  pointer-events:none;
}
.hero-text{
  font-family: var(--font-sans);  /* Archivo — human voice, not chrome */
  font-size:80px;                 /* in viewBox units; scales with the SVG */
  font-weight:700;
  fill: var(--text);
  letter-spacing:-0.02em;
}
/* Zero-footprint h1 wrapper around the hero SVG: it exists purely so the
   document outline has a top-level heading (the SVG's role=img + aria-label
   gives it its accessible name). font-size/line-height are zeroed so the
   wrapper contributes no layout of its own. */
.hero-heading{ margin:0; font-size:0; line-height:0; }

/* "Jack" carries the cyan→lavender pin gradient — both pin types belong to
   one name, declared before either section appears. The gradient fill itself
   is a presentation attribute on the tspan (fragment url(#...) from external
   CSS is unreliable in older browsers).
   The restrained glow lives on the aria-hidden GLOW LAYER <text> (see the
   hero SVG markup): `filter` is ignored on a <tspan> — it isn't an SVG
   graphics element — so putting it on .hero-text-accent never rendered.
   The glow copy is fully transparent except its gradient "Jack" tspan
   (presentation-attribute fill beats the inherited transparent), so only
   "Jack" casts the glow. */
.hero-text--glow{
  fill: transparent;
  filter: drop-shadow(0 0 24px rgba(86,194,214,.25)); /* no amber anywhere */
}

/* Tagline: "UNREAL ENGINE" as a sub-headline, then the two roles beneath it.
   The engine line is set in the DISPLAY face (Archivo caps, bright, flanked
   by hairline rules) while the roles are mono links — a different voice,
   size, and weight, so it reads as its own distinct element rather than a
   text line stacked on the roles. That distinctness matters: when the two
   lines read as a typographic pair, the eye expects the roles' "|" to align
   under the engine's center, and it can't — "Technical artist" is longer
   than "Programmer", so either the pipe sits off-center (row mass-centered)
   or the row's ink hangs asymmetrically (pipe force-centered). Breaking the
   pair by hierarchy dissolves the expectation instead of fighting it.
   Each role is an <a> into its own section, set directly in that section's
   pin color (cyan 8.6:1 / lavender 6.5:1 on Canvas — both AA at this size). */
.hero__tagline{
  margin:18px 0 0;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:14px;
  font-family: var(--font-mono);
  font-weight:400;
  font-size:.85rem;
  letter-spacing:.18em;
  text-transform:uppercase;
  color:var(--muted);
}
/* The engine sub-headline: human voice (Archivo), bright, with symmetric
   hairline rules either side — a section divider holding a name, in the
   editor-chrome sense. Symmetric flanks keep it exactly centered. */
.hero__engine{
  display:inline-flex;
  align-items:center;
  gap:16px;
  font-family: var(--font-sans);
  font-weight:700;
  font-size:1.15rem;
  letter-spacing:.22em;
  color:var(--text);
}
.hero__engine::before,
.hero__engine::after{
  content:'';
  width:36px;
  height:1px;
  background: var(--splitter);
}
/* letter-spacing adds a trailing space after the final glyph, so the gap to
   the right hairline would render one tracking-unit wider than the left —
   pull the right rule back in by the same amount to keep the flanks even. */
.hero__engine::after{ margin-left:-.22em; }
.hero__roles{ display:inline-flex; align-items:center; gap:14px; }
/* The aria-hidden "|" between the two roles. */
.hero__tagline .sep{ color: rgba(169,180,200,.45); }

/* Each role link declares its accent once; color and hover read it. */
.role-link--prog{ --role-accent: var(--wire-cyan); }
.role-link--tech{ --role-accent: var(--normal-lav); }
.role-link{
  color: var(--role-accent);
  text-decoration:none;
}
/* Hover/focus: same underline treatment as the nav. */
.role-link:hover,
.role-link:focus-visible{
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 6px;
}

.hero .lead{
  color:var(--muted);
  max-width:62ch;
  margin:18px auto 32px;
  font-size: clamp(1.05rem, 1.8vw, 1.2rem);
  line-height:1.7;
}
.lead__line{ display:block; }

/* --- Scroll cues = pins ON the wire ------------------------------------- */
/* A 44px pin: hairline circle on the canvas. One lives at the bottom of every
   section except Contact (the terminal node): the hero's is the wire's ORIGIN
   pin, and the rest are pass-through "reroute" pins the wire threads straight
   through — click one and you follow the wire to the next section. Positioned
   absolutely at section level. Mobile: classic centered cue. Desktop (≥900px):
   they move onto the rail axis at the left. translateX(-50%) centers the
   circle on whatever x the breakpoint sets, so the layouts share one
   mechanism.

   Hover accent = the wire's color at that point, which is also the TARGET
   section's type: cyan by default (hero→Programming, and the neutral-target
   cues, since cyan is the site's global interactive color), overridden to
   lavender for Programming→Tech art below. The rgb twin exists because the
   glow shadow needs an rgba() mix. */
.scroll-cue{
  --cue-accent: var(--wire-cyan);
  --cue-accent-rgb: var(--wire-cyan-rgb);
  position:absolute;
  left:50%;
  bottom:32px;
  transform:translateX(-50%);
  z-index:2;
  display:grid;
  place-items:center;
  width:44px; height:44px;
  border:2px solid var(--splitter);
  border-radius:50%;
  background: var(--canvas); /* opaque: the wire must visually stop AT the pin */
  color:var(--muted);
  transition: border-color .3s ease, box-shadow .3s ease;
}
.scroll-cue:hover,
.scroll-cue:focus-visible{
  border-color: var(--cue-accent);
  box-shadow: 0 0 12px rgba(var(--cue-accent-rgb), .35);
}
/* Programming's cue jumps to Tech art — and sits where the wire has already
   blended to lavender, so the hover accent agrees with the wire under it. */
.section--prog .scroll-cue{
  --cue-accent: var(--normal-lav);
  --cue-accent-rgb: var(--normal-lav-rgb);
}
.scroll-cue svg{
  width:22px; height:22px;
  animation: bounce 2s ease-in-out infinite; /* killed by reduced-motion below */
}
@keyframes bounce{
  0%,100%{ transform: translateY(0); }
  50%{ transform: translateY(6px); }
}

/* ============================================================================
   NODE HEADERS — eyebrow (chrome) + h2 (human) + rail input pin.
   ========================================================================== */

.node-header{
  position:relative; /* anchor for the ::before rail pin (desktop) */
  margin:0 0 14px;
}

.eyebrow{
  margin:0 0 10px;
  font-family: var(--font-mono);
  font-weight:600;
  font-size:.75rem;
  line-height:1.4;
  letter-spacing:.14em;
  text-transform:uppercase;
  color: var(--pin); /* inherits the section's type color automatically */
}

.node-header h2{
  margin:0;
  font-family: var(--font-sans);
  font-weight:700;
  font-size: var(--h2-size);
  line-height:1.15;
  letter-spacing:-0.02em;
  color:var(--text);
  /* No decorative underline — on desktop the rail pin IS the marker. */
}

.section-intro{
  max-width:62ch;
  margin:0 0 clamp(28px, 4vw, 44px);
  color:var(--muted);
  line-height:1.75;
}

/* ============================================================================
   NODE CARDS — tight editor panels, not soft web cards.
   Used for the About card, the Contact card, and all discipline cards.
   ========================================================================== */

.node-grid{
  display:grid;
  gap:20px;
  grid-template-columns:1fr;
}
@media (min-width: 700px){
  .node-grid{ grid-template-columns:1fr 1fr; }
}
/* Tech Art gets two WIDER node-cards: full container width instead of the
   2×2 Programming grid. Specificity 0,2,0 beats the 0,1,0 media rule above,
   so this holds at every breakpoint. */
.section--tech .node-grid{ grid-template-columns:1fr; }

.node-card{
  background: var(--panel);
  border:1px solid var(--splitter);
  border-radius:8px; /* editor panels are tight — no 20px pillows */
  padding: clamp(20px, 3vw, 32px);
  box-shadow: 0 12px 32px rgba(0,0,0,.35);
  transition: transform .3s ease, border-color .3s ease;
}
.node-card:hover{
  transform: translateY(-2px);
  /* Border tints toward the section's pin type — no colored glow shadows. */
  border-color: rgba(var(--pin-rgb), .5);
}

.node-card p{
  margin:0;
  color:var(--muted);
  font-size:1rem;
  line-height:1.75;
}
.node-card p + p{ margin-top:14px; } /* multi-paragraph cards (About) */

/* --- Card title row: pin dot + mono title + chip ------------------------ */
.node-card__title{
  display:flex;
  align-items:center;
  gap:10px;
  flex-wrap:wrap; /* lets the chip drop below the title on narrow cards */
  margin:0 0 14px;
}
.pin-dot{
  flex:none;
  width:8px; height:8px;
  border-radius:50%;
  background: var(--pin); /* filled — this card's output is a resolved type */
}
.node-card__title h3{
  margin:0;
  font-family: var(--font-mono);
  font-weight:600;
  font-size:.8rem;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--text);
}

/* Status chip — mono badge in the section's pin color. */
.chip{
  margin-left:auto; /* push to the row's right edge, like a node's corner tag */
  font-family: var(--font-mono);
  font-weight:600;
  font-size:.7rem;
  line-height:1;
  letter-spacing:.06em;
  text-transform:uppercase;
  white-space:nowrap;
  color: var(--pin);
  border:1px solid rgba(var(--pin-rgb), .4);
  border-radius:4px;
  padding:3px 8px;
}

/* NOTE: Tech Art cards deliberately carry NO extra finish (no sheen, no
   checkerboard wash). Card CSS is identical across the two disciplines —
   only the accent variable, the section texture, and chip presence change.
   That enforced sameness is the design's discipline rule: the wire is the
   one theatrical element, and boldness is spent there only. */

/* ============================================================================
   CONTACT FORM — property-panel rows: recessed inputs under mono labels.
   (Class names and structure are load-bearing for main.js — do not rename.)
   ========================================================================== */

/* Needs to out-rank `.node-card p { margin:0 }` (0,1,1) — hence the compound
   selector rather than !important. */
.node-card p.contact-intro{ margin:0 0 22px; }

.grid{
  display:grid; gap:16px; grid-template-columns:1fr;
}
@media (min-width: 700px){
  .grid{ grid-template-columns:1fr 1fr; }
}

.field{ display:flex; flex-direction:column; gap:8px; margin-top:16px; }
.grid .field{ margin-top:0; } /* the grid's gap already spaces the first row */

/* Honeypot field — parked off-screen rather than display:none, because some
   naive bots skip fields they can see are display:none but still fill
   off-screen ones. Humans never see it; the backends drop filled submissions. */
.field--hp{ position:absolute; left:-9999px; top:auto; width:1px; height:1px; overflow:hidden; }

.field label{
  font-family: var(--font-mono);
  font-weight:400;
  font-size:.8rem;
  color:var(--muted); /* 7.7:1 on panel — AAA even at this size */
}

.field input,
.field textarea{
  font-family: var(--font-sans);
  font-size:1rem;
  background: var(--recess); /* editors recess inputs below panel level */
  /* --input-border, not --splitter: an unfocused input's border is its only
     visible boundary, so it must clear the 3:1 non-text-contrast bar
     (see the token comment in :root). Decorative hairlines stay --splitter. */
  border:1px solid var(--input-border);
  color:var(--text);
  border-radius:6px;
  padding:13px 14px;
  outline:none; /* focus is signaled by the border + ring below */
  transition: border-color .2s ease, box-shadow .2s ease;
}
/* Placeholders are still rendered text: .7 alpha composites to ~4.9:1 on the
   recess fill (AA); the old .5 alpha computed ~3.1:1 and failed. */
.field input::placeholder,
.field textarea::placeholder{ color: rgba(169,180,200,.7); }
.field input:focus,
.field textarea:focus{
  border-color: var(--wire-cyan);
  box-shadow: 0 0 0 3px rgba(86,194,214,.22);
}
.field textarea{ resize:vertical; }

.error{
  font-family: var(--font-mono);
  font-size:.78rem;
  color: var(--error);
  min-height:1em; /* reserves the line so the form doesn't jump on error */
}

.actions{ display:flex; align-items:center; gap:16px; margin-top:20px; flex-wrap:wrap; }
#form-status{
  font-family: var(--font-mono);
  font-size:.8rem;
  color:var(--muted);
}

/* --- Primary button: a solid cyan tool button, dark ink ----------------- */
.btn{
  display:inline-flex; align-items:center; justify-content:center;
  padding:13px 26px;
  border-radius:6px;
  border:1px solid var(--wire-cyan);
  background: var(--wire-cyan);
  color: var(--ink-on-accent); /* 8.8:1 — AAA */
  font-family: var(--font-mono);
  font-weight:600;
  font-size:.85rem;
  letter-spacing:.02em;
  cursor:pointer;
  transition: background .25s ease, transform .25s ease;
}
.btn:hover{
  background:#6FD3E4;
  transform: translateY(-1px);
}
.btn:active{ transform: translateY(0); }
/* On a cyan fill, a cyan focus ring would vanish — flip to light ink. */
.btn:focus-visible{
  outline:2px solid var(--text);
  outline-offset:2px;
}
/* main.js marks the in-flight state with aria-disabled (NOT disabled): a
   truly disabled button ejects keyboard/screen-reader focus to <body> while
   the fetch runs. The [aria-disabled] selector keeps the visual parity. */
.btn:disabled,
.btn[aria-disabled="true"]{ opacity:.7; cursor:default; transform:none; }

/* ============================================================================
   SOCIALS — quiet tool buttons.
   ========================================================================== */

.socials{ display:flex; align-items:center; justify-content:center; gap:14px; flex-wrap:wrap; }
.social{
  display:inline-grid; place-items:center;
  width:44px; height:44px;
  border-radius:8px;
  background: var(--panel);
  border:1px solid var(--splitter);
  color:var(--muted);
  text-decoration:none;
  transition: border-color .25s ease, color .25s ease, transform .25s ease;
}
.social svg{ width:20px; height:20px; }
.social:hover{
  border-color: var(--wire-cyan);
  color:var(--text);
  transform: translateY(-2px);
}

/* ============================================================================
   BACK TO TOP — created by main.js; a quiet tool button, not a FAB.
   ========================================================================== */

.back-to-top{
  position:fixed;
  bottom:28px; right:28px;
  width:48px; height:48px;
  border-radius:8px; /* rounded square — tool button, not a floating circle */
  background: var(--panel);
  border:1px solid var(--splitter);
  color: var(--wire-cyan);
  display:grid;
  place-items:center;
  cursor:pointer;
  z-index:99;
  opacity:0;
  visibility:hidden;
  transform: translateY(12px);
  transition: opacity .3s ease, transform .3s ease, visibility .3s, border-color .25s ease;
}
.back-to-top.visible{
  opacity:1;
  visibility:visible;
  transform: translateY(0);
}
.back-to-top:hover{ border-color: var(--wire-cyan); }
.back-to-top svg{ width:22px; height:22px; }

/* ============================================================================
   FOOTER — the graph terminates.
   ========================================================================== */

.site-footer{
  padding:40px 0;
  border-top:1px solid var(--splitter);
  text-align:center;
}
.site-footer p{
  margin:0;
  font-family: var(--font-mono);
  font-size:.8rem;
  color:var(--muted);
}
/* The wire's 6px filled end-pin, centered above the copyright line. Neutral
   ink — by the footer the wire has long since resolved and discharged. */
.site-footer .container::before{
  content:'';
  display:block;
  width:6px; height:6px;
  border-radius:50%;
  background: var(--muted);
  margin:0 auto 14px;
}

/* ============================================================================
   REVEAL MACHINERY — main.js's IntersectionObserver adds .show once per
   element. Wires and rail pins piggyback on the same class (see below).
   ========================================================================== */

.reveal{
  opacity:0;
  transform: translateY(30px) scale(.97);
  transition: opacity .8s cubic-bezier(.16,1,.3,1), transform .8s cubic-bezier(.16,1,.3,1);
}
.reveal.show{
  opacity:1;
  transform:none;
}
/* Small stagger inside card grids: siblings cascade in ~70ms apart. The IO
   fires them all at once when the grid enters the viewport; the delays turn
   that into a left-to-right, top-to-bottom deal. */
.node-grid > .reveal:nth-child(2){ transition-delay:.07s; }
.node-grid > .reveal:nth-child(3){ transition-delay:.14s; }
.node-grid > .reveal:nth-child(4){ transition-delay:.21s; }

/* ============================================================================
   THE WIRE (desktop ≥900px) — the page's single theatrical element.
   ============================================================================
   Anatomy per section (all positions relative to the SECTION, aligned to the
   container via --rail-left):

     hero          neutral stub from the origin pin to the section's bottom edge
     programming   neutral→cyan resolve, solid cyan, then cyan→lavender blend
                   with the final third DASHED (the type change is "still being
                   authored" as it enters Tech Art)
     tech-art      solid lavender, relaxing back to neutral at the bottom
     about         solid neutral
     contact       neutral, terminating AT the header's input pin

   Draw-on-scroll: the .reveal class marks each wire for main.js, which
   observes the wire's PARENT SECTION (a scaleY(0) wire is a zero-area box
   that no IntersectionObserver threshold catches reliably) and forwards
   .show to the wire when the section enters the viewport.
   The rules below OVERRIDE the generic .reveal styles for wires (same
   specificity, declared later → they win): instead of fade+lift, a wire
   starts scaleY(0) from its top and unrolls to scaleY(1) on .show.
   ========================================================================== */

.wire{ display:none; } /* mobile: no rail — pins move inline into eyebrows */

@media (min-width: 900px){

  /* Indent section content so the rail has a lane of its own. The hero is
     exempt: its content stays visually centered, and its rail elements are
     absolutely positioned at section level instead. */
  .section:not(.hero) .container{ padding-left:72px; }

  .wire{
    display:block;
    position:absolute;
    left: var(--rail-left);
    top:0; bottom:0;
    width:2px;
    z-index:0;             /* under .container (z-index:1) */
    pointer-events:none;
  }

  /* Wire reveal overrides: keep opacity at 1 (a fading wire looks broken —
     it should DRAW), collapse via scaleY instead. transform-origin:top makes
     it unroll downward, the direction execution flows. */
  .wire.reveal{
    opacity:1;
    transform: scaleY(0);
    transform-origin: top;
    transition: transform 1.2s cubic-bezier(.16,1,.3,1);
  }
  .wire.reveal.show{ transform: scaleY(1); }

  /* ALL cues ride the rail axis on desktop (--rail-left + 1px = axis;
     translateX(-50%) recenters the 44px circle on it). */
  .scroll-cue{ left: calc(var(--rail-left) + 1px); }

  /* HERO stub: origin pin sits on the rail axis; the stub runs from under it
     to the section's bottom edge, where Programming's segment picks it up.
     The pin is parked 64px up; the stub fills those 64px. */
  .hero .scroll-cue{ bottom:64px; }

  /* Section cues float centered in the bottom padding band — the content is
     above, the texture has faded out around them (see the blending layers),
     and the full-height wire passes visibly through the opaque circle:
     a reroute node dropped on the wire. (--sect-pad/2 − half the 44px pin.) */
  .section:not(.hero) .scroll-cue{ bottom: calc(var(--sect-pad) / 2 - 22px); }
  .hero .wire{
    top:auto; bottom:0;
    height:64px;
    background: var(--neutral-wire);
  }

  /* PROGRAMMING: the wire resolves its type. Neutral at the very top (meeting
     the hero stub), cyan by 16% (roughly where the header pin sits), solid
     cyan through the cards, then blending to lavender across the final third. */
  .section--prog .wire{
    background: linear-gradient(180deg,
      rgba(169,180,200,.5) 0%,
      var(--wire-cyan) 16%,
      var(--wire-cyan) 66%,
      var(--normal-lav) 100%);
  }
  /* Dash the blend zone: an overlay punches Canvas-colored gaps over the
     bottom third, so the cyan→lavender segment reads as a dashed wire —
     the handoff into Tech Art is "still being authored". Zero JS, and it
     scales with the parent's scaleY draw animation for free. */
  .section--prog .wire::after{
    content:'';
    position:absolute;
    left:0; right:0; bottom:0;
    height:34%;
    background: repeating-linear-gradient(180deg,
      transparent 0 12px,
      var(--canvas) 12px 20px);
  }

  /* TECH ART: solid lavender past the header, relaxing back to neutral
     through the bottom padding — past the typed outputs, About is just
     the person. */
  .section--tech .wire{
    background: linear-gradient(180deg,
      var(--normal-lav) 0%,
      var(--normal-lav) 72%,
      rgba(169,180,200,.5) 100%);
  }

  /* ABOUT: untyped. */
  .section--about .wire{ background: var(--neutral-wire); }

  /* CONTACT: the wire terminates AT the header's input pin rather than
     running the full section. Height = section top padding + the eyebrow
     line box (0.75rem × 1.4 = 1.05rem) + its 10px margin + half the h2's
     line (h2 line-height is 1.15, so the midline sits at 0.575 × font-size).
     This tracks the same clamp()s the header itself uses. */
  .section--contact .wire{
    bottom:auto;
    height: calc(var(--sect-pad) + 1.05rem + 10px + var(--h2-size) * 0.575);
    background: var(--neutral-wire);
  }

  /* SECTION INPUT PINS: a 12px circle on the rail axis at the h2's midline.
     Horizontal math: the container is padded 72px, so the header's content
     box starts 72px right of the container edge; the rail axis is at 25px;
     the pin must be centered at -47px from the content box (25 − 72), so a
     12px circle starts at -53px. Vertical math mirrors the contact-wire
     comment above: bottom = (h2 midline) − (half the pin). */
  .node-header::before{
    content:'';
    position:absolute;
    left:-53px;
    bottom: calc(var(--h2-size) * 0.575 - 6px);
    width:12px; height:12px;
    border-radius:50%;
    border:2px solid var(--pin);
    background: var(--canvas); /* hollow until the section reveals */
    box-shadow: 0 0 12px rgba(var(--pin-rgb), .45);
    /* Fill is delayed ~.5s so it lands as the wire draw arrives at the pin. */
    transition: background-color .4s ease .5s;
  }
  /* The pin fills solid when the header's own .reveal fires — the node's
     input is connected. */
  .node-header.reveal.show::before{ background: var(--pin); }
}

/* MOBILE (<900px): no rail, no plumbing — but the type-color system survives.
   Each section pin collapses into a leading 8px dot inside the eyebrow row. */
@media (max-width: 899.98px){
  .eyebrow::before{
    content:'';
    display:inline-block;
    width:8px; height:8px;
    border-radius:50%;
    background: var(--pin);
    margin-right:8px;
  }
  /* Section cues sit lower on mobile than the hero's (which keeps its
     classic 32px): --sect-pad bottoms out at 80px here, and 32px + the 44px
     pin would leave only 4px of air below the last card. 20px keeps a
     16px gap. */
  .section:not(.hero) .scroll-cue{ bottom:20px; }
}

/* ============================================================================
   ACCESSIBILITY — reduced motion
   ========================================================================== */

@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior:auto; }
  *, *::before, *::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
    transition-delay:0s !important; /* also cancels the card stagger */
  }
  /* main.js adds .show to every .reveal immediately under reduced motion, so
     wires render fully drawn and pins pre-filled; these are belt-and-braces
     for the instant before that script runs. */
  .wire.reveal{ transform:none; }
  .back-to-top{ transition: opacity .2s ease; }
}

/* ============================================================================
   SMALL SCREENS
   ========================================================================== */

@media (max-width: 768px){
  .site-header{ padding:14px 4%; }
  .brand{ font-size:1rem; }
  .site-nav{ gap:14px; }
  .site-nav a{ font-size:.8rem; }
  .hero .lead{ font-size:1rem; }
  .back-to-top{
    bottom:20px; right:20px;
    width:44px; height:44px;
  }
  .back-to-top svg{ width:20px; height:20px; }
}
