/* ═══════════════════════════════════════════════════════════════════
   base.css — everything the portfolio and its six world pages need,
   and nothing that belongs to the landing page.

   The portfolio is sent to clients on its own; the landing page is a
   separate, unfinished thing. They used to share style.css, so an edit
   meant for one could break the other and every world page carried
   ~1300 lines of landing-page CSS it never used. This file is the
   shared part only: brand tokens, the reset, base element styles,
   .skip-link, .mono, the media viewer and the showcase type system.

   index.html keeps style.css. Nothing else loads it.
   ═══════════════════════════════════════════════════════════════════ */
/* ============ DESIGN TOKENS ============ */
:root{
  /* ── BRAND PALETTE ──────────────────────────────────────────
     #111111 primary dark / text   #ff9124 accent orange
     #242e3d dark slate            #2f4560 navy
     #e5e5e5 light grey            #f4f4f4 off-white
     Three neutrals (--ink-500, --ink-300, and the light-section
     --ink-300 override below) are DERIVED in the slate family to
     fill tonal steps the 6-colour set doesn't carry. No single
     muted grey can clear WCAG AA on BOTH #111 and #f4f4f4, so the
     dark tone lives here and light sections override it. Everything
     else is exact. */

  /* color/core — dark → light */
  --ink-000:#111111; --ink-900:#111111; --ink-800:#242e3d; --ink-700:#2f4560;
  --ink-500:#3b4a5e; --ink-300:#949eac; --ink-100:#e5e5e5;
  --paper-000:#f4f4f4; --paper-100:#e5e5e5; --white:#f4f4f4;

  /* color/accent */
  --accent:#ff9124; --accent-2:#2f4560;
  --accent-rgb:255,145,36; --accent2-rgb:47,69,96;

  /* space (8px system) */
  --s1:8px; --s2:16px; --s3:24px; --s4:32px; --s6:48px; --s8:64px;
  --s12:96px; --s16:128px; --s20:160px; --s30:240px;

  /* fluid rhythm */
  --gutter:clamp(20px, 5.5vw, 80px);
  --section-y:clamp(88px, 11vw, 160px);
  --maxw:1440px;

  /* radius */
  --r-sm:8px; --r-md:16px; --r-lg:24px; --r-xl:32px; --r-pill:999px;

  /* type */
  --font-display:'Space Grotesk', system-ui, sans-serif;
  --font-body:'Inter', system-ui, sans-serif;
  --font-mono:'JetBrains Mono', ui-monospace, monospace;

  /* motion */
  --ease:cubic-bezier(.22,1,.36,1);
  --ease-io:cubic-bezier(.65,0,.35,1);
  --dur:.7s;
}

/* Light sections carry a darker muted tone: the dark-surface --ink-300
   (#949eac) drops to ~2.4:1 on off-white. This override is the only ink-300
   consumer in each of these sections (the .mono kickers), and clears AA on
   both #f4f4f4 and #e5e5e5. */

/* ============ RESET / BASE ============ */
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box;}

html{
  scroll-behavior:smooth;
  scroll-padding-top:clamp(90px,12vh,140px);
  -webkit-text-size-adjust:100%;
}
/* Smooth scrolling is still motion. Page-level reduced-motion blocks scope
   to their own body class and never reach <html>, so the exception has to
   live here, beside the declaration it disarms. */
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto;}
}

body{
  background:var(--ink-000);
  color:var(--white);
  font-family:var(--font-body);
  font-size:16px;
  line-height:1.6;
  overflow-x:clip;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}
body.is-locked{overflow:hidden;}

h1,h2,h3,h4{font-family:var(--font-display);font-weight:500;text-wrap:balance;}
p{text-wrap:pretty;}
a{text-decoration:none;color:inherit;}
li{list-style:none;}
img,svg,video{display:block;max-width:100%;}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;}
input{font:inherit;}
address{font-style:normal;}

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

:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:3px;
  border-radius:4px;
}

.sr-only{
  position:absolute;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
}

.skip-link{
  position:fixed;top:8px;left:8px;z-index:9999;
  transform:translateY(-150%);
  background:var(--accent);color:var(--ink-000);
  padding:12px 20px;border-radius:var(--r-pill);
  font-family:var(--font-display);font-weight:500;
  transition:transform .3s var(--ease);
}
.skip-link:focus{transform:none;}

/* ============ UTILITIES ============ */
.container{
  width:100%;max-width:var(--maxw);
  margin-inline:auto;
  padding-inline:var(--gutter);
}

.mono{
  font-family:var(--font-mono);
  font-size:clamp(10px,.78vw,12px);
  letter-spacing:.08em;text-transform:uppercase;font-weight:500;
}


/* ══════════════ SHARED MEDIA VIEWER ══════════════
   Every showcase world opens its work at full size through the same dialog
   (js/media-viewer.js injects the markup). Dark, so photography and film read
   the way they would on a lightbox. */
.mv{
  position:fixed;inset:0;z-index:1000;
  display:grid;
  grid-template-columns:auto 1fr auto;
  grid-template-rows:auto minmax(0, 1fr);
  /* deliberately NOT align-items:center — that stops the stage row stretching,
     so its height:100% resolves against an auto row and the media overflows.
     The stage centres its own content instead. */
  align-items:stretch;
  background:rgba(10,10,10,.96);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  color:var(--paper-000);
  padding:clamp(12px,2vw,28px);
  opacity:0;transition:opacity .3s var(--ease);
}
.mv[hidden]{display:none;}
.mv.is-open{opacity:1;}

.mv-bar{
  grid-column:1 / -1;
  display:flex;align-items:center;gap:var(--s3);
  padding-bottom:clamp(8px,1.4vw,16px);
}
.mv-title{
  font-size:clamp(18px,2.2vw,28px);
  text-transform:uppercase;letter-spacing:.02em;line-height:1;flex:1;
  margin:0;
}
.mv-count{
  font-family:var(--font-mono);
  font-size:clamp(10px,.9vw,12px);letter-spacing:.14em;
  color:rgba(244,244,244,.55);margin:0;
}
.mv-x{
  background:none;border:0;cursor:pointer;padding:0 4px;
  font-size:34px;line-height:1;color:var(--paper-000);
  transition:color .25s var(--ease);
}
.mv-x:hover{color:var(--accent);}

/* Absolute media + object-fit:contain, rather than max-height on a grid item:
   percentage max-heights resolve unreliably inside grid tracks, which let a
   tall portrait frame run off the bottom of the screen. This always fits. */
.mv-stage{
  grid-column:2;
  position:relative;
  width:100%;height:100%;min-height:0;overflow:hidden;
}
.mv-stage img,
.mv-stage video{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:contain;
  border-radius:4px;display:block;
}

.mv-nav{
  align-self:center;
  background:none;border:0;cursor:pointer;
  font-size:clamp(32px,4vw,52px);line-height:1;
  color:rgba(244,244,244,.55);
  padding:0 clamp(6px,1.4vw,20px);
  transition:color .25s var(--ease);
}
.mv-nav:hover{color:var(--paper-000);}
.mv-nav[disabled]{opacity:.18;cursor:default;}
.mv-prev{grid-column:1;}
.mv-next{grid-column:3;}
/* a single-frame album (or one film) has nothing to page through */
.mv.is-single .mv-nav{visibility:hidden;}

body.mv-locked{overflow:hidden;}


/* ══════════════ SHOWCASE TYPE SYSTEM ══════════════
   Portfolio and the six category worlds share one type stack. It lives here,
   once, rather than being redeclared in seven scoped files — that is what
   stops them drifting apart again.

   The set is portfolio.css's, which was the reference: Big Shoulders Display
   for display, Cormorant Garamond for serif, Geist Mono for readouts. Space
   Grotesk carries body prose; portfolio already loaded it as a display
   fallback, so this adds no new family to the page weight.

   index.html deliberately keeps the :root stack above — it is the homepage,
   not a showcase, and its 3D hero is tuned to that type. */
.pf, .fx, .bx, .hs, .av, .pc, .ms, .re{
  --font-display:'Big Shoulders Display','Big Shoulders','Space Grotesk',system-ui,sans-serif;
  /* Retired from headings — every section title now uses --font-display, so the
     pages no longer download Cormorant. Re-add it to the <link> before using it. */
  --font-serif:'Cormorant Garamond',Georgia,serif;
  --font-body:'Space Grotesk',system-ui,sans-serif;
  --font-mono:'Geist Mono',ui-monospace,'JetBrains Mono',monospace;
}
