/* ============================================================================
   BASE — theme-agnostic reset + skip link + universal reduced-motion.

   This file has NO colors, NO typography, NO layout rhythm. Everything
   visual lives in themes/<id>/theme.css and is scoped under
   [data-theme="<id>"]. Theme CSS is loaded per-page based on the
   active theme (see the bootloader in <head> of index.html).

   Rules kept here are only those that:
     (a) must apply before any theme paints, and
     (b) have no theme-specific interpretation.
   ========================================================================= */

/* ============================================================================
   SHARED TOKENS — values every theme uses verbatim. Themes can still
   override an individual step under their own scope, but most won't
   need to. Hoisted here so each theme.css is leaner and there's a
   single source of truth for the spacing scale.
   ========================================================================= */

:root {
  --space-1:   4px;
  --space-2:   8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  24px;
  --space-6:  32px;
  --space-7:  48px;
  --space-8:  64px;
  --space-9:  96px;
  --space-10: 128px;

  /* Layout container — every theme uses 1440px max-inline-size for
     the central column. Hoisted so theme.css doesn't have to repeat
     it. Themes can still override under their own scope if a future
     theme wants a different rhythm. */
  --grid-max:    1440px;
  --grid-gutter: var(--space-5);

  /* Shared mono stack — used for nav meta, KPIs, monospace
     accents across every theme. Terminal overrides this with a
     longer fallback chain that includes Consolas (more reliable
     on Windows machines without JetBrains Mono installed). */
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
}

*, *::before, *::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

html, body {
  margin: 0;
  padding: 0;
}

body {
  /* Horizontal-overflow safety net — each theme can style body further,
     but none should ever trigger a horizontal scroll. */
  overflow-x: clip;
}

img, svg {
  display: block;
  max-inline-size: 100%;
}

a       { color: inherit; text-decoration: none; }
button  { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }

/* ============================================================================
   CUSTOM CURSOR — base hide rule
   ----------------------------------------------------------------------------
   `body.has-custom-cursor` is added by themes/_motion-base.js' customCursor()
   helper only after it has successfully wired up the mouse listeners.
   Hiding the native cursor here (instead of inside each [data-theme="<id>"]
   scope) means:
     1. A single source of truth — no 8 copies of the same rule.
     2. The rule never depends on CSS Nesting de-sugaring inside an
        attribute-scoped block, which has bitten us before.
     3. If customCursor() fails for any reason (cache miss, JS error,
        touch device, reduced motion), body.has-custom-cursor is never
        set and the native cursor stays visible — so the page never
        ends up with NO cursor at all.
   Each theme.css still owns the `.cursor` visual styling.
   ========================================================================= */

body.has-custom-cursor,
body.has-custom-cursor a,
body.has-custom-cursor button,
body.has-custom-cursor [role="button"],
body.has-custom-cursor summary,
body.has-custom-cursor .idx-toggle { cursor: none; }

/* Hide the custom .cursor div on touch devices regardless of which
   theme is active. Each theme's cursor visual styling is otherwise
   identical here (display:none on hover:none / pointer:coarse), so
   hoisting the rule deduplicates 7 copies across themes. */
@media (hover: none), (pointer: coarse) {
  .cursor { display: none !important; }
}

/* Skip-to-content link — neutral chrome so it works legibly across all
   themes regardless of palette. */
.skip {
  position: absolute;
  inset-block-start: -40px;
  inset-inline-start: 16px;
  padding: 8px 16px;
  background: #0A0A0A;
  color: #FAFAF9;
  font: 500 11px/1 ui-monospace, "SF Mono", Menlo, monospace;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  z-index: 1000;
  transition: inset-block-start 150ms cubic-bezier(.2, .7, .2, 1);
}
.skip:focus { inset-block-start: 8px; }

/* §01 index rows — keyboard focus ring. Lives here (not per-theme)
   because every theme restyles .idx-toggle but none draws its own
   focus indicator, leaving keyboard users with no visible cue. The
   accent var resolves per-theme; Swiss red is the fallback for any
   theme that doesn't define it. :focus-visible keeps mouse/touch
   activation clean. The !important wins over Terminal's scoped
   `.idx-toggle:focus-visible { outline: none }` — same trick the
   drawer rules below use to beat [data-theme=…] specificity. */
.idx-toggle:focus-visible {
  outline: 2px solid var(--color-accent, #E3342F) !important;
  outline-offset: 2px;
}

/* Universal reduced-motion kill-switch — themes layer their own
   per-selector overrides on top. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* prefers-reduced-data — visitors on metered or capped connections.
   Disable the heavier theme decorations so they don't pay for them:
     - Cinematic film-grain SVG noise overlay
     - Pastel hero gradient washes
     - Marquee animations (movement only; the strip itself stays)
   The pages still look like their themes; just less glamour. */
@media (prefers-reduced-data: reduce) {
  [data-theme="cinematic"] body::before { display: none !important; }
  [data-theme="pastel"] .hero { background-image: none !important; }
  .marquee-track { animation: none !important; }
}

/* ============================================================================
   MOBILE NAV DRAWER — hamburger toggle that lives outside any theme
   scope so all 8 themes inherit the same drawer behaviour without
   each having to wire it up. The button is hidden on >640px, visible
   on phones. When body.nav-open is set by main.js, the existing
   .nav-list slides in from the right as a fixed-position panel.

   We pick up the active theme's --color-bg / --color-ink / --color-rule
   so the drawer matches whatever theme is loaded.
   ========================================================================= */

.nav-burger {
  display: none;
  inline-size: 44px;
  block-size: 44px;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: currentColor;
}
.nav-burger svg line {
  transition: transform 220ms cubic-bezier(.2,.7,.2,1),
              opacity   220ms cubic-bezier(.2,.7,.2,1);
  transform-origin: center;
}
/* When the drawer is open, morph the burger into an X */
body.nav-open .nav-burger .nav-burger-l1 { transform: translateY(4px) rotate(45deg); }
body.nav-open .nav-burger .nav-burger-l2 { opacity: 0; }
body.nav-open .nav-burger .nav-burger-l3 { transform: translateY(-4px) rotate(-45deg); }

/* Hide the drawer-only "cool projects" <li> on desktop. The matching
   .nav-cool pill on the right of the topbar serves the same role
   there, so showing both would duplicate the entry. The mobile rules
   below override this when body.nav-open. */
.nav-list-cool { display: none; }

@media (max-width: 640px) {
  .nav-burger { display: inline-flex; }

  /* Force every theme's mobile nav-inner into a clean 2-column
     layout so the burger never wraps onto a second row. Some
     themes set grid-template-columns: 1fr (causing stacking) or
     auto auto (mark + nav-avail, burger overflow into row 2);
     the !important here wins over any [data-theme=…] override. */
  .nav-inner {
    grid-template-columns: 1fr auto !important;
  }
  .nav-avail { display: none !important; }
  .nav-burger { justify-self: end; }

  /* Lock body scroll while the drawer is open */
  body.nav-open { overflow: hidden; }

  /* Every theme makes .nav-top sticky with z-index: 50, which means
     .nav-top creates its own stacking context. The drawer .nav-list
     lives INSIDE .nav-top, so its z-index: 100 below was capped to
     that local stacking context — the body.nav-open::before scrim
     (z-index 99 in body's stacking context) rendered on top of the
     drawer, greying it out and intercepting every click that should
     have hit a menu link. Lifting .nav-top above the scrim (and
     keeping the burger clickable too) fixes both bugs at once. */
  body.nav-open .nav-top {
    z-index: 101 !important;
  }

  /* Scrim — full-viewport dimmer behind the drawer panel.
     The dim alone (rgba 0.45 over the page) is enough to signal
     "drawer is in front"; we deliberately don't blur the page
     because backdrop-filter rendered as an aggressive smear on
     dark themes (Cinematic, Terminal) and made the page look
     broken when the drawer opened. */
  body.nav-open::before {
    content: "";
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    z-index: 99;
    cursor: pointer;
    animation: nav-scrim-in 220ms cubic-bezier(.2,.7,.2,1) both;
  }

  /* The existing .nav-list becomes the drawer panel.
     Each theme already hides .nav-list on <=640px (display:none);
     the !important here is the explicit override for drawer mode. */
  body.nav-open .nav-list {
    display: flex !important;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    position: fixed;
    inset-block: 0;
    inset-inline-end: 0;
    inline-size: min(82vw, 320px);
    block-size: 100dvh;
    padding-block-start: max(72px, calc(56px + env(safe-area-inset-top, 0px)));
    padding-block-end: max(32px, env(safe-area-inset-bottom, 0px));
    padding-inline: 32px;
    background: var(--color-bg, #FAFAF9);
    color: var(--color-ink, #0A0A0A);
    border-inline-start: 0.5px solid var(--color-rule, #D4D4D4);
    box-shadow: -16px 0 48px rgba(0, 0, 0, 0.18);
    z-index: 100;
    overflow-y: auto;
    animation: nav-drawer-in 260ms cubic-bezier(.2,.7,.2,1) both;
  }
  body.nav-open .nav-list li { inline-size: 100%; }
  /* Reveal the drawer-only cool-projects entry. Specificity matches
     the global hide rule above; the !important on display covers any
     theme that styles .nav-list li with `display: list-item` etc. */
  body.nav-open .nav-list-cool { display: block !important; }
  /* The drawer renders cool-projects as the visual finale — accent
     it so the visitor sees it as the "destination" item, not just
     another nav link. */
  body.nav-open .nav-list-cool a {
    color: var(--color-accent, #E3342F) !important;
    font-weight: 500;
  }
  body.nav-open .nav-list a {
    display: block;
    padding-block: 14px;
    font-size: 22px;
    color: var(--color-ink, #0A0A0A);
    border-block-end: 0.5px solid var(--color-rule, #D4D4D4);
    text-decoration: none;
    /* override any theme tracking/transform that doesn't read well at
       drawer scale */
    text-transform: none;
    letter-spacing: -0.01em;
    transform: none;
  }
  body.nav-open .nav-list a:hover,
  body.nav-open .nav-list a:focus-visible {
    color: var(--color-accent, #E3342F);
    outline: none;
  }
}

@keyframes nav-drawer-in {
  from { transform: translateX(100%); }
  to   { transform: translateX(0); }
}
@keyframes nav-scrim-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  body.nav-open .nav-list,
  body.nav-open::before { animation: none !important; }
}

/* ============================================================================
   SAFE-AREA-INSET — Dynamic Island / notch / home-indicator awareness.

   The viewport-fit=cover meta tag (set in <head>) lets backgrounds
   reach the device edges; in return we owe the user content insets
   so the notch and home indicator never overlap text or controls.

   These rules deliberately live OUTSIDE any theme scope and use the
   property names theme CSS never targets:
     .nav-top   — themes set background, border, position; not padding
                  (padding lives on .nav-inner). Adding padding here
                  composes cleanly with the theme rules above.
     body       — adding padding-block-end pushes the document a few
                  pixels taller so the footer ends ABOVE the home
                  indicator on Pro-class iPhones.

   Wrapped in @supports so we never write `padding: env(...)` on
   browsers that don't understand env() (Edge < 88, etc.) and would
   otherwise treat the value as invalid.
   ========================================================================= */

@supports (padding: env(safe-area-inset-top)) {
  body {
    padding-block-end: env(safe-area-inset-bottom);
  }
  .nav-top {
    padding-block-start: env(safe-area-inset-top);
  }
}

/* ============================================================================
   PRINT — flatten the active theme to a clean black-on-white résumé.

   Triggered when the user prints (Cmd/Ctrl+P) or saves a PDF. The
   active theme's CSS still loads, but these print rules trump any
   theme-specific colour, background, motion, or chrome. The output
   is intentionally austere: ink on paper, no decorations, no
   navigation, no marquee, no expandable index.
   ========================================================================= */

@media print {
  /* Reset every element's surface to white-on-black for ink savings */
  *, *::before, *::after {
    background: transparent !important;
    color: #000 !important;
    box-shadow: none !important;
    text-shadow: none !important;
    filter: none !important;
    animation: none !important;
    transition: none !important;
  }

  html, body {
    background: #fff !important;
    font-size: 11pt;
    line-height: 1.45;
  }

  /* Screen-only copy ("tap any row…") makes no sense on paper. */
  .no-print { display: none !important; }

  /* Hide chrome that doesn't belong on paper */
  .nav-top,
  .footer .marquee,
  .marquee,
  #theme-switcher,
  .cursor,
  .skip,
  .hero hr.hair-draw,
  .hair-draw {
    display: none !important;
  }

  /* Expand every collapsed §01 row so the entire role list prints */
  .idx-detail {
    block-size: auto !important;
    overflow: visible !important;
    display: block !important;
  }
  .idx-detail[hidden] { display: block !important; }

  /* Strip rotations / transforms — Zine + Brutalist tilts look bad
     on paper */
  .wordmark, .idx-row, .case, .proj, .skill-group, .contact-table,
  .nav-mark, .hero-cap-meta, .kpi {
    transform: none !important;
  }

  /* Compact the hero on the printable page */
  .hero {
    padding: 0 0 16pt;
    border-bottom: 1pt solid #000;
    overflow: visible;
  }
  .wordmark {
    font-size: 32pt !important;
    line-height: 1 !important;
    margin-bottom: 8pt;
  }
  .wordmark .line { display: block; }

  /* Sections — use page-break hints */
  section.chapter {
    page-break-inside: avoid;
    padding: 16pt 0;
    border-top: 0.5pt solid #000;
  }
  h2, h3, .h2, .h3 {
    page-break-after: avoid;
  }

  /* Show URLs after every link so printed text retains references */
  a[href^="http"]::after,
  a[href^="mailto:"]::after,
  a[href^="tel:"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    color: #555 !important;
  }

  /* Avoid excessive ink for terminal / cinematic dark themes — the
     wildcard reset above handles most of it but proj-visual blocks
     (which use solid fills) need a forceful override. */
  .proj-visual,
  .hero::before,
  .hero::after {
    display: none !important;
  }
}

