/* ==========================================================================
   main.css — design tokens, reset, base typography, layout primitives
   Oscar Kilgore — portfolio
   --------------------------------------------------------------------------
   Load order: main.css → components.css → responsive.css
   Everything below is mobile-first. Breakpoint work lives in responsive.css.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* --- Colour ------------------------------------------------------------
     Warm off-white paper, warm near-black ink, one accent (vermilion).
     Every value below is contrast-checked against its intended background;
     --ink-3 is the lightest text tone permitted on --paper (4.8:1).        */
  --paper:        #f4f1ea;
  --paper-sunk:   #ece8de;  /* recessed bands, code wells */
  --paper-raised: #fbf9f5;  /* preview surfaces */
  --ink:          #16150f;  /* primary type                      16.3:1 */
  --ink-2:        #4a473d;  /* secondary type                     8.4:1 */
  --ink-3:        #676355;  /* metadata, mono labels              4.8:1 */
  --rule:         #d8d3c6;  /* hairlines */
  --rule-strong:  #b9b3a3;
  --accent:       #b03a1e;  /* interaction, labels, links         5.4:1 */
  --accent-wash:  rgba(176, 58, 30, 0.08);

  /* --- Derived surfaces ---------------------------------------------------
     ONE formula, every palette. Because --ink is always the contrast colour
     of its theme, mixing toward it darkens light palettes and lightens dark
     ones automatically — no per-theme pattern values anywhere.

     97% lands at roughly 2.5 L* on light themes and 3.0 L* on dark: read as
     texture, not as a grid. The value is capped by contrast, not taste —
     every text tone still clears AA when it lands directly on a pattern
     stroke. See the contrast note in README.

     --card-bg exists so Opening Hand cards have a guaranteed OPAQUE surface.
     The pattern must never show through a card.                            */
  /* Dark palettes need a bigger step to read as texture at all, so the
     strength is a token rather than a constant. Each palette sets it. */
  --pattern-mix:       97%;
  --pattern:           transparent;   /* fallback: no pattern at all */
  --pattern-sunk:      transparent;
  --card-bg:           var(--paper-raised);
  --card-bg-personal:  var(--paper-sunk);

  /* Spotlight position, written by atmosphere.js. */
  --glow-x: 50%;
  --glow-y: 30%;

  /* --- Type --------------------------------------------------------------
     Two families only. Archivo is a variable font (wght 100–900,
     wdth 62–125) — the weight axis drives the hero interaction, and the
     width axis gives display type range without loading a third family.  */
  --font-sans: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Consolas, monospace;

  /* Fluid type scale. Ratio ~1.22 mobile, ~1.30 desktop. */
  --fs-micro:   0.6875rem;                                    /* 11px  mono ticks */
  --fs-mono:    0.75rem;                                      /* 12px  labels     */
  --fs-body:    clamp(1rem, 0.97rem + 0.15vw, 1.0625rem);     /* 16–17 */
  --fs-lead:    clamp(1.125rem, 1.04rem + 0.42vw, 1.375rem);  /* 18–22 */
  --fs-3:       clamp(1.25rem, 1.14rem + 0.55vw, 1.5rem);     /* 20–24 */
  --fs-2:       clamp(1.75rem, 1.25rem + 2.5vw, 3rem);        /* 28–48 */
  --fs-1:       clamp(2.25rem, 1.4rem + 4.2vw, 4rem);         /* 36–64 */
  --fs-display: clamp(2.5rem, 1.1rem + 6.2vw, 5.5rem);        /* 40–88 */
  --fs-index:   clamp(1.5rem, 0.95rem + 2.6vw, 2.75rem);      /* project titles */
  --fs-numeral: clamp(2.5rem, 1.5rem + 4.5vw, 5rem);          /* oversize numerals */

  --lh-tight:  1.02;
  --lh-snug:   1.14;
  --lh-normal: 1.5;
  --lh-loose:  1.62;

  --tracking-mono: 0.09em;
  --tracking-display: -0.025em;

  --measure: 60ch;       /* comfortable reading width */
  --measure-narrow: 44ch;

  /* --- Space -------------------------------------------------------------
     One scale, used everywhere. No arbitrary pixel values in components.  */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4rem;
  --sp-9: 6rem;
  --sp-10: 8rem;

  /* Section rhythm and page gutters scale with viewport, not breakpoints. */
  --section-y: clamp(4rem, 9vw, 8.5rem);
  --page-x:    clamp(1.25rem, 5vw, 5rem);
  --gutter:    clamp(1rem, 2vw, 2rem);
  --grid-cols: 12;

  /* --- Motion ------------------------------------------------------------
     Fast = state feedback. Medium = element transitions.
     Slow = reveals and large transforms. Spring is for pointer-driven
     movement only (magnetic, preview inertia).                            */
  --dur-fast:   160ms;
  --dur-med:    320ms;
  --dur-slow:   640ms;
  --ease-out:    cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-spring: cubic-bezier(0.34, 1.4, 0.5, 1);

  --hairline: 1px;
  --focus-ring: 2px;
}

/* Default behaviour ("auto"): follow the operating system. */
@media (prefers-color-scheme: dark) {
  :root {
    --paper:        #14130f;
    --paper-sunk:   #1b1a15;
    --paper-raised: #201e18;
    --ink:          #efebe0;
    --ink-2:        #b4ae9e;
    --ink-3:        #948f7f;
    --rule:         #2e2c25;
    --rule-strong:  #45423a;
    --accent:       #e56a47;
    --accent-wash:  rgba(229, 106, 71, 0.11);
    --pattern-mix:  94%;
  }
}

/* --------------------------------------------------------------------------
   1b. Palettes
   --------------------------------------------------------------------------
   Every theme is the same eleven custom properties with different values —
   no component rule anywhere in the site knows a theme exists. `[data-theme]`
   is set on <html> before first paint (see the inline script in index.html)
   and outranks the media query above by specificity, so an explicit choice
   always wins over the system preference.

   `auto` deliberately declares nothing: it falls through to :root plus the
   media query.

   Contrast is checked per palette. --ink-3 is the lightest tone permitted on
   --paper in each and clears 4.5:1; every --accent clears 4.5:1 as well.
   -------------------------------------------------------------------------- */

/* Paper — warm off-white, vermilion. The default look, pinned. */
:root[data-theme="paper"] {
  --paper:        #f4f1ea;
  --paper-sunk:   #ece8de;
  --paper-raised: #fbf9f5;
  --ink:          #16150f;
  --ink-2:        #4a473d;
  --ink-3:        #676355;
  --rule:         #d8d3c6;
  --rule-strong:  #b9b3a3;
  --accent:       #b03a1e;
  --accent-wash:  rgba(176, 58, 30, 0.08);
  --pattern-mix:  97%;
}

/* Blueprint — cool grey paper, drafting-ink blue. */
:root[data-theme="blueprint"] {
  --paper:        #eef1f5;
  --paper-sunk:   #e3e8ef;
  --paper-raised: #f7f9fb;
  --ink:          #10151c;
  --ink-2:        #3c4654;
  --ink-3:        #576475;
  --rule:         #cdd5df;
  --rule-strong:  #a6b1c0;
  --accent:       #2b4a9b;
  --accent-wash:  rgba(43, 74, 155, 0.08);
  --pattern-mix:  97%;
}

/* Moss — warm sand paper, deep green. */
:root[data-theme="moss"] {
  --paper:        #f2f0e6;
  --paper-sunk:   #e9e6d9;
  --paper-raised: #f9f8f2;
  --ink:          #14170f;
  --ink-2:        #454a3a;
  --ink-3:        #5f6451;
  --rule:         #d7d3c2;
  --rule-strong:  #b4b09c;
  --accent:       #3f6b3a;
  --accent-wash:  rgba(63, 107, 58, 0.09);
  --pattern-mix:  97%;
}

/* Ink — warm dark, ember. */
:root[data-theme="ink"] {
  --paper:        #14130f;
  --paper-sunk:   #1b1a15;
  --paper-raised: #201e18;
  --ink:          #efebe0;
  --ink-2:        #b4ae9e;
  --ink-3:        #948f7f;
  --rule:         #2e2c25;
  --rule-strong:  #45423a;
  --accent:       #e56a47;
  --accent-wash:  rgba(229, 106, 71, 0.11);
  --pattern-mix:  94%;
}

/* Carbon — cool near-black, signal amber. */
:root[data-theme="carbon"] {
  --paper:        #0f1216;
  --paper-sunk:   #151920;
  --paper-raised: #1b2028;
  --ink:          #e6ebf2;
  --ink-2:        #a3adbb;
  --ink-3:        #858f9b;
  --rule:         #262c35;
  --rule-strong:  #3a424e;
  --accent:       #e0a020;
  --accent-wash:  rgba(224, 160, 32, 0.11);
  --pattern-mix:  94%;
}

/* --------------------------------------------------------------------------
   2. Reset — only what is actually needed
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 6rem; /* clears the sticky masthead on anchor jumps */
}

body,
h1, h2, h3, h4, p, figure, blockquote, dl, dd, ol, ul {
  margin: 0;
}

ol[class],
ul[class] {
  list-style: none;
  padding: 0;
}

/* height:auto matters: the images carry width/height attributes to reserve
   layout space, and without it max-width:100% would squash the aspect ratio. */
img,
svg {
  display: block;
  max-width: 100%;
  height: auto;
}

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

/* --------------------------------------------------------------------------
   3. Base
   -------------------------------------------------------------------------- */

body {
  background-color: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  font-variation-settings: "wght" 400, "wdth" 100;
  line-height: var(--lh-loose);
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Deliberately NOT `overflow-x: hidden` on <body>. It makes the body a
   scroll container, which breaks programmatic scrolling and position:
   sticky. The pointer-following layers are position:fixed and clamped to
   the viewport in JS, so there is nothing to clip. */

h1, h2, h3, h4 {
  font-weight: 400; /* weight comes from font-variation-settings, not synthesis */
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
}

p { text-wrap: pretty; }

::selection {
  background-color: var(--accent);
  color: var(--paper);
}

/* Links inherit colour by default; components opt into accent explicitly. */
a {
  color: inherit;
  text-decoration: none;
}

/* --- Focus -------------------------------------------------------------
   Visible, high-contrast, and never removed. :focus-visible keeps it off
   mouse clicks without punishing keyboard users.                         */
:focus-visible {
  outline: var(--focus-ring) solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
}

/* --------------------------------------------------------------------------
   4. Layout primitives
   -------------------------------------------------------------------------- */

/* The page rail. Sections opt out of it deliberately (see .bleed). */
.shell {
  padding-inline: var(--page-x);
  margin-inline: auto;
  max-width: 96rem;
}

/* 12-column grid. Children position themselves with .col-* utilities so
   layouts stay declarative in the HTML rather than hidden in ad-hoc CSS. */
.grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  column-gap: var(--gutter);
}

.section {
  padding-block: var(--section-y);
  position: relative;
}

.section--sunk { background-color: var(--paper-sunk); }

/* The section directly after the hero. The hero already supplies the space
   above it, so it does not need the full section rhythm on top. */
.section--after-hero { padding-top: var(--sp-6); }

/* Full-bleed hairline that sits flush with the page rail. */
.rule {
  border: 0;
  border-top: var(--hairline) solid var(--rule);
  margin: 0;
}

/* --------------------------------------------------------------------------
   5. Type utilities
   -------------------------------------------------------------------------- */

/* Monospace metadata: project numbers, categories, stack lists, status.
   Deliberately limited to small, uppercase, non-prose text. */
.mono {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: 500;
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--ink-3);
}

.mono--micro { font-size: var(--fs-micro); }
.mono--accent { color: var(--accent); }
.mono--ink { color: var(--ink); }

.lead {
  font-size: var(--fs-lead);
  line-height: var(--lh-normal);
  color: var(--ink-2);
  max-width: var(--measure);
  font-variation-settings: "wght" 380;
}

.prose {
  max-width: var(--measure);
  color: var(--ink-2);
}

.prose p + p { margin-top: var(--sp-4); }

.measure-narrow { max-width: var(--measure-narrow); }

/* The only spacing utilities on the site. Anything beyond a single-step
   nudge belongs in a component rule, not in the markup. */
.mb-5 { margin-bottom: var(--sp-5); }
.mt-2 { margin-top: var(--sp-2); }

/* Screen-reader-only, still focusable. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Skip link — visible the moment it receives focus. */
.skip-link {
  position: absolute;
  top: var(--sp-3);
  left: var(--sp-3);
  z-index: 200;
  padding: var(--sp-3) var(--sp-4);
  background-color: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  transform: translateY(-160%);
  transition: transform var(--dur-fast) var(--ease-out);
}

.skip-link:focus-visible { transform: translateY(0); }

/* Marks unfinished content honestly rather than faking it. */
.pending {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  color: var(--accent);
  vertical-align: super;
  margin-left: 0.35em;
}

/* --------------------------------------------------------------------------
   6. Reduced motion
   --------------------------------------------------------------------------
   JS checks the same query and skips registering pointer-driven effects
   entirely (see utils.js). This block covers everything CSS-driven.
   Content is never hidden by a reveal that cannot run.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }

  /* Force every reveal to its resting state. */
  [data-reveal] > *,
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
  }
}

/* --------------------------------------------------------------------------
   Pattern colour — enabled only where color-mix() is available, so older
   browsers simply get no pattern rather than an unverified one.
   -------------------------------------------------------------------------- */

@supports (color: color-mix(in srgb, red 50%, blue)) {
  :root {
    --pattern:      color-mix(in srgb, var(--paper) var(--pattern-mix), var(--ink));
    --pattern-sunk: color-mix(in srgb, var(--paper-sunk) var(--pattern-mix), var(--ink));
  }
}
