/* ===========================================================================
   Diandra Lasrado — site styles
   Design system + homepage. Fonts are loaded separately from css/fonts.css.
   Palette and type scale come from the approved homepage mockup.
   =========================================================================== */

/* ---- Design tokens (change these to re-theme the whole site) -------------- */
:root {
  --purple-bright: #6827a9;            /* DOMINANT background violet (the plateau) */
  --purple:      #2e0f52;              /* deep purple — dark text on gold + the gradient's dark end */
  --purple-deep: #1c0733;              /* darkest — the mobile nav dropdown panel */
  --gold:        #e9b44c;              /* accent: pill, button, band, links    */
  --gold-hover:  #f0c46e;
  --cream:       #f3eefa;              /* off-white — primary light text       */
  --cream-92:    rgba(243, 238, 250, 0.92);
  --cream-85:    rgba(243, 238, 250, 0.85);
  --cream-60:    rgba(243, 238, 250, 0.60);

  --font-display: 'Archivo', system-ui, sans-serif;      /* headings, logo     */
  --font-body:    'Hanken Grotesk', system-ui, sans-serif;/* body, nav, buttons */
  --font-mono:    'Space Mono', ui-monospace, monospace;  /* mono accent        */

  --page-max: 1200px;                  /* content width cap                    */
  --page-pad: 40px;                    /* left/right gutter on desktop         */
  --rail-w:   250px;                   /* gutter the game side-rail reserves   */
  --radius:   2px;                     /* the design uses crisp, near-square edges */
}

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

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

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  color: var(--cream);
  background-color: var(--purple-bright);
  /* Elliptical "pool of light" backdrop (desk-lamp look). A big OVAL of flat
     lighter purple fills the left; its right edge is a curved demarcation that
     bulges to ~82% of the width about two-thirds down, then a ~10%-wide steep
     blend drops into deep purple in the top-right / right "shadow". The ellipse
     centre sits off-screen to the lower-left. Fitted to the approved ColorMap
     with tools/gradient-fit.mjs — edit there and re-fit before changing this. */
  background-image:
    radial-gradient(ellipse 147% 134% at -50% 67%,
      var(--purple-bright) 0%, var(--purple-bright) 86.5%, var(--purple) 93.5%);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }

a { color: inherit; text-decoration: none; }

/* Keyboard focus visibility across the site */
:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 3px;
  border-radius: var(--radius);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--gold);
  color: var(--purple);
  padding: 10px 16px;
  font-weight: 700;
  z-index: 100;
}
.skip-link:focus { left: 12px; top: 12px; }

/* ---- Header / navigation -------------------------------------------------- */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 30px var(--page-pad);
  position: relative;
  z-index: 20;
}

.brand {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 19px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--cream);
  white-space: nowrap;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 34px;
}
.site-nav a {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 14px;
  color: var(--cream-85);
  transition: color 0.15s ease;
}
.site-nav a:hover { color: var(--gold); }

/* Hamburger — hidden on desktop, shown on mobile */
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}
.nav-toggle-bar {
  display: block;
  width: 26px;
  height: 2px;
  background: var(--cream);
  transition: transform 0.2s ease, opacity 0.2s ease;
}

/* ---- Hero ----------------------------------------------------------------- */
.hero {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 40px var(--page-pad) 90px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 60px;
}

.hero-text { max-width: 560px; }

.eyebrow {
  display: inline-block;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--purple);
  background: var(--gold);
  padding: 8px 16px;
  border-radius: var(--radius);
}

.hero-name {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(52px, 8vw, 82px);
  line-height: 0.92;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--cream);
  margin: 26px 0 30px;
  text-shadow: 0 8px 26px rgba(20, 5, 40, 0.45);
}

.lede {
  font-size: 20px;
  line-height: 1.6;
  color: var(--cream-92);
  max-width: 460px;
  margin: 0 0 40px;
}

.btn {
  display: inline-block;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 15px;
  color: var(--purple);
  background: var(--gold);
  padding: 16px 34px;
  border-radius: var(--radius);
  transition: background 0.15s ease, transform 0.15s ease;
}
.btn:hover { background: var(--gold-hover); transform: translateY(-1px); }

.hero-photo {
  width: 440px;
  max-width: 42vw;
  aspect-ratio: 438 / 548;      /* ratio lives on the frame, not the <img> */
  transform: rotate(3.5deg);
  justify-self: end;
}
.hero-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 20%;
  border-radius: var(--radius);
  box-shadow: 0 34px 64px -22px rgba(0, 0, 0, 0.6);
}

/* ---- Studio logo band ----------------------------------------------------- */
.logo-band {
  background: var(--gold);
  color: var(--purple);
}
.logo-band-list {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 22px var(--page-pad);
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 18px 40px;
}
.logo-band-list li {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 18px;
  letter-spacing: 0.01em;
  display: flex;
  align-items: center;
}
/* diamond separator between studios (not after the last) */
.logo-band-list li:not(:last-child)::after {
  content: "✦";
  margin-left: 40px;
  font-size: 13px;
  color: rgba(46, 15, 82, 0.5);
}

/* ---- Closing / contact ---------------------------------------------------- */
.closing {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 110px var(--page-pad) 90px;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: 40px;
}
.closing-line {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(40px, 6vw, 58px);
  line-height: 1.0;
  letter-spacing: -0.025em;
  color: var(--cream);
  max-width: 640px;
  margin: 0;
}
.contact-links {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  text-align: right;
  white-space: nowrap;
}
.contact-email {
  font-weight: 600;
  font-size: 16px;
  color: var(--gold);
}
.contact-email:hover { color: var(--gold-hover); }
.contact-social {
  font-size: 16px;
  color: var(--cream-85);
}
.contact-social:hover { color: var(--cream); }

/* ---- Footer --------------------------------------------------------------- */
.site-footer {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 20px var(--page-pad) 40px;
}
.site-footer p {
  margin: 0;
  font-size: 13px;
  color: var(--cream-60);
}

/* ===========================================================================
   Responsive — the mockup is desktop-only; below 900px we stack and swap the
   nav for a menu button so nothing overflows.
   =========================================================================== */
@media (max-width: 900px) {
  :root { --page-pad: 24px; }

  /* Phones/tablets: keep the field mostly flat lighter purple, with just a soft
     deep-purple darkening in the top-right corner. The full oval is desktop drama. */
  body {
    background-image:
      radial-gradient(95% 55% at 112% -6%, var(--purple) 0%, rgba(46, 15, 82, 0) 58%);
  }

  .nav-toggle { display: flex; }

  /* Nav collapses into a dropdown panel toggled by the hamburger */
  .site-nav {
    position: absolute;
    top: 100%;
    right: var(--page-pad);
    left: var(--page-pad);
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    background: var(--purple-deep);
    border: 1px solid rgba(243, 238, 250, 0.12);
    border-radius: 6px;
    padding: 10px 6px;
    box-shadow: 0 24px 48px -18px rgba(0, 0, 0, 0.7);
    display: none;
  }
  .site-nav.is-open { display: flex; }
  .site-nav a {
    width: 100%;
    padding: 12px 14px;
    font-size: 16px;
    border-radius: 4px;
  }
  .site-nav a:hover { background: rgba(243, 238, 250, 0.06); }

  /* Hamburger → X when open */
  .nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(2) { opacity: 0; }
  .nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  /* Hero stacks: name/bio first, photo below */
  .hero {
    grid-template-columns: 1fr;
    gap: 40px;
    padding-top: 20px;
    padding-bottom: 64px;
  }
  .hero-photo {
    width: min(360px, 78vw);
    max-width: 78vw;
    transform: rotate(2.5deg);
    justify-self: center;
  }

  .lede { font-size: 18px; }

  /* Studio band stacks vertically */
  .logo-band-list {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
  }
  .logo-band-list li:not(:last-child)::after {
    margin-left: 16px;
  }

  /* Closing stacks, contact moves below the line */
  .closing {
    grid-template-columns: 1fr;
    align-items: start;
    gap: 28px;
    padding: 72px var(--page-pad) 64px;
  }
  .contact-links {
    align-items: flex-start;
    text-align: left;
  }
}

@media (max-width: 420px) {
  .hero-name { font-size: clamp(40px, 13vw, 52px); }
}

/* ===========================================================================
   Interior pages — shared shell for About, Work, Press & Talks, Résumé,
   Contact, and the individual game pages. Same header/footer/palette as home.
   =========================================================================== */
.site-nav a[aria-current="page"] { color: var(--gold); }

.page-hero {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 30px var(--page-pad) 10px;
}
.back-link {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.06em;
  color: var(--gold);
  margin-bottom: 22px;
}
.back-link:hover { color: var(--gold-hover); }

.page-title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(44px, 7vw, 72px);
  line-height: 0.95;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--cream);
  margin: 0 0 18px;
  text-shadow: 0 8px 26px rgba(20, 5, 40, 0.4);
}
.page-intro {
  font-size: 20px;
  line-height: 1.6;
  color: var(--cream-92);
  max-width: 620px;
  margin: 0;
}

.page-body {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 40px var(--page-pad) 100px;
}
.placeholder-note {
  font-family: var(--font-mono);
  font-size: 14px;
  letter-spacing: 0.04em;
  color: var(--cream-60);
}

/* "COMING SOON" on a game page that has no content yet (Brian, 2026-08-05).
   Emitted as the LAST thing inside <main> by build-stubs.mjs, so it sits below
   everything else on the page and above the footer's copyright line.

   Sized clearly larger than body copy but deliberately SMALLER than
   .page-title — at the title's size it competes with the game's own name for
   the same eye, and the game name has to win.

   Left-aligned like the rest of the site. A centred version was tried first and
   read as an error page rather than a deliberate placeholder.

   Uppercased in CSS rather than typed in caps in the HTML, so a screen reader
   announces "coming soon" instead of spelling out C-O-M-I-N-G. */
.coming-soon {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(32px, 5vw, 48px);
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0;
}

/* Free prose on an interior page — a game write-up, a case-study paragraph.
   Deliberately identical in size, rhythm and measure to the About bio, so body
   copy reads the same everywhere on the site. Sits above the video sections. */
.page-prose {
  max-width: 62ch;
  margin: 0 0 48px;
}
.page-prose p {
  font-size: 17px;
  line-height: 1.75;
  color: var(--cream-92);
  margin: 0 0 18px;
}
.page-prose p:last-child { margin-bottom: 0; }

/* Work — game grid */
.game-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 18px;
}
/* Title pinned to the TOP, the role/note block pinned to the BOTTOM.
   This used to be justify-content: flex-end (everything hugging the bottom),
   which worked only while subtitles were one short line. A real role line — the
   "Role · Studio · Year" format, around 50 characters — is wider than the
   card and wraps to two or three lines, and a card that wraps more pushes its
   title higher than its neighbours'. Widening the grid does not fix it: even at
   three columns the longest roles still wrap. Pinning the two ends means a card
   can wrap any number of times and every title still sits on the same line. */
.game-card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 10px;
  min-height: 150px;
  /* The grid item is the <li>; this <a> is inside it. The li stretches to the
     row height on its own, the <a> does not — so without height:100% a card
     with a short role ends higher than its neighbours and the row's bottom
     edges come out ragged. Only visible once the roles had different lengths. */
  height: 100%;
  padding: 22px;
  background: rgba(243, 238, 250, 0.06);
  border: 1px solid rgba(243, 238, 250, 0.12);
  border-radius: 6px;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.game-card:hover {
  background: rgba(243, 238, 250, 0.10);
  border-color: var(--gold);
  transform: translateY(-2px);
}
.game-card-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 22px;
  color: var(--cream);
}
/* The meta block sits at the bottom of the card (see .game-card above). Both rows
   are always rendered, even when empty, so a card with no role still reserves the
   space and the grid keeps an even rhythm.

   .game-card-role — "Role · Studio · Year", the credential. Gold: it's the point.
   .game-card-note — an optional second line, quieter so it reads as supplementary.
                     Only World of Warcraft uses it (its expansion list), a
                     deliberate exception; see the GAMES comment in build-stubs.mjs. */
.game-card-meta {
  display: block;
}
/* Both rows MUST be display: block. They are <span>s, and an inline span ignores
   min-height and vertical margin — so without this the role and the note render
   run-together on ONE line, and the reserved empty-row height silently does
   nothing. Only surfaced once real role text existed (2026-08-04); with every
   role empty it looked correct. */
.game-card-role {
  display: block;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: 0.05em;
  color: var(--gold);
  margin-top: 6px;
  min-height: 18px;
}
.game-card-note {
  display: block;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: 0.05em;
  color: var(--cream-60);
  margin-top: 3px;
  min-height: 18px;
}

/* Press & Talks — item list */
.stack-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 14px;
  max-width: 760px;
}
.stack-list li {
  padding: 20px 22px;
  background: rgba(243, 238, 250, 0.06);
  border: 1px solid rgba(243, 238, 250, 0.12);
  border-radius: 6px;
}
.stack-list .item-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 18px;
  color: var(--cream);
}
.stack-list .item-sub {
  font-size: 14px;
  line-height: 1.55;
  color: var(--cream-85);
  margin-top: 5px;
}
.stack-list .item-note {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--cream-60);
  margin-top: 4px;
}
/* A whole stack-list card can be a link. The negative margin cancels the li's
   padding so the entire box is the click target, not just the words — a small
   card with three lines of text is a frustrating thing to have to aim at. */
.stack-list a.item-link {
  display: block;
  margin: -20px -22px;
  padding: 20px 22px;
  border-radius: 6px;
  color: inherit;
  text-decoration: none;
  transition: background 0.15s ease;
}
.stack-list a.item-link:hover { background: rgba(243, 238, 250, 0.05); }
.stack-list a.item-link:hover .item-title { color: var(--gold); }

/* Sub-heading inside the Talks & Podcasts section, under the .section-label. */
.stack-heading {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 18px;
  color: var(--cream);
  margin: 40px 0 14px;
}

/* Press & Talks — a talk with an embedded video */
.talk {
  max-width: 760px;
  margin: 0 0 30px;
}
.talk-eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 6px;
}
.talk-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 22px;
  color: var(--cream);
  margin: 0 0 16px;
}
/* A talk that isn't Diandra's alone lists its speakers. The co-speakers sit back
   a step and HER name reads at full strength (.speaker-self) — the panel is
   credited honestly, but a visitor scanning the page still sees whose portfolio
   this is. Brian's call, 2026-08-05. */
.talk-byline {
  font-size: 15px;
  color: var(--cream-60);
  margin: -8px 0 14px;
}
.talk-byline .speaker-self { color: var(--cream); }

/* Press coverage OF a talk, listed under its link — not the talk itself. */
.talk-coverage {
  font-size: 14px;
  line-height: 1.6;
  color: var(--cream-60);
  max-width: 62ch;
  margin: 16px 0 0;
}
/* Underlined, not just gold: colour alone doesn't mark a link for someone who
   can't distinguish the two. Same rule as the résumé's publication links. */
.talk-coverage a {
  color: var(--gold);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.talk-coverage a:hover { color: var(--gold-hover); }
/* A line about the talk, for one that has no embedded video to speak for it.
   66ch rather than the site's usual 62ch measure so the GDC Vault sentence sits
   on ONE line at desktop instead of orphaning its last word; `pretty` stops a
   lone word ending any longer note that does wrap. */
.talk-note {
  font-size: 16px;
  line-height: 1.6;
  color: var(--cream-92);
  max-width: 66ch;
  text-wrap: pretty;
  margin: 0 0 22px;
}
.talk-links {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}

/* Lazy YouTube facade — a still image until clicked, then the real player
   (see js/video-embed.js). Keeps the page fast and contacts YouTube only on click. */
.video-facade {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid rgba(243, 238, 250, 0.12);
  border-radius: 6px;
  background: #000;
}
.video-facade img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.video-facade__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 76px;
  height: 76px;
  border-radius: 50%;
  background: var(--gold);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 10px 30px rgba(28, 7, 51, 0.55);
  transition: transform 0.2s, background 0.2s;
}
.video-facade__play::after {   /* the play triangle */
  content: "";
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 13px 0 13px 22px;
  border-color: transparent transparent transparent var(--purple);
  margin-left: 5px;
}
.video-facade:hover .video-facade__play,
.video-facade:focus-visible .video-facade__play {
  transform: translate(-50%, -50%) scale(1.08);
  background: var(--gold-hover);
}
.video-facade:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 3px;
}
/* the real player that replaces the facade on click (plays in-page) */
.video-embed__iframe {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid rgba(243, 238, 250, 0.12);
  border-radius: 6px;
  background: #000;
}
@media (prefers-reduced-motion: reduce) {
  .video-facade__play { transition: none; }
  .video-facade:hover .video-facade__play,
  .video-facade:focus-visible .video-facade__play { transform: translate(-50%, -50%); }
}

/* Game / work page — captioned video sections (World of Warcraft, etc.) */
.work-section {
  margin: 0 0 56px;
}
.work-section:last-of-type {
  margin-bottom: 8px;
}
.work-section__label {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 24px;
  color: var(--cream);
  margin: 0 0 4px;
}
.work-section__note {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.05em;
  color: var(--gold);
  text-transform: uppercase;
  margin: 0 0 22px;
}
.video-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 30px 26px;
}
.video-grid--single {          /* a section with one video: don't stretch it full-width */
  grid-template-columns: minmax(0, 620px);
}
.video {                        /* <figure> wrapping a facade + caption */
  margin: 0;
}
/* The caption block under a video: a title, and OPTIONALLY a sentence of
   description beneath it. Both live inside the one <figcaption> because they
   are one caption — a <figcaption> has to be the first or last child of its
   <figure>, so a description cannot be a sibling of it. */
.video__caption {
  margin-top: 12px;
}
.video__title {
  display: block;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  color: var(--cream-85);
}
/* Quieter than the title above it, so the eye still lands on the title first
   and the description reads as a note about it. Videos without one are
   unaffected — the element simply isn't there. */
.video__desc {
  display: block;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.65;
  color: var(--cream-60);
  margin-top: 6px;
}
/* Diandra's commentary on a video — what she was going for, in her own words.
   This is the most valuable writing on the site, so it is READABLE body text,
   not a footnote: larger and brighter than .video__desc, which stays a quiet
   one-line note about what you're watching. The gold rule marks it as her voice
   rather than more description, and gives the eye a place to start in what is
   otherwise a column of similar-looking text.
   Optional per video — omit it and nothing renders, with no empty space left. */
.video__commentary {
  display: block;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.75;
  color: var(--cream-85);
  margin-top: 16px;
  padding-left: 16px;
  border-left: 2px solid rgba(233, 180, 76, 0.45);
}
@media (max-width: 720px) {
  .video-grid { grid-template-columns: 1fr; gap: 26px; }
  .video__commentary { padding-left: 13px; }
}

/* Press & Talks — featured book */
.book-feature {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 44px;
  align-items: center;
  max-width: 840px;
  margin: 0 0 64px;
  /* Arriving from the Résumé page's press.html#book link, the browser scrolls
     this to the very top of the window. Without the offset the cover sits flush
     against the viewport edge with the page title cropped off above it, and it
     is not obvious you've landed on Press & Talks at all. */
  scroll-margin-top: 32px;
}
.book-cover img {
  width: 100%;
  height: auto;
  border-radius: 4px;
  box-shadow: 0 30px 60px -24px rgba(0, 0, 0, 0.7);
}
.book-eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 10px;
}
.book-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(26px, 3.4vw, 36px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--cream);
  margin: 0 0 12px;
}
.book-byline {
  font-size: 16px;
  color: var(--cream-85);
  margin: 0 0 16px;
}
.book-desc {
  font-size: 17px;
  line-height: 1.6;
  color: var(--cream-92);
  max-width: 52ch;
  margin: 0 0 26px;
}
.book-links {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}
.btn-outline {
  background: transparent;
  color: var(--gold);
  border: 2px solid var(--gold);
  padding: 14px 32px;   /* 2px less than .btn so borders don't change the height */
}
.btn-outline:hover {
  background: var(--gold);
  color: var(--purple);
}
.section-label {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 22px;
  color: var(--cream);
  margin: 0 0 18px;
}

@media (max-width: 720px) {
  .book-feature { grid-template-columns: 1fr; gap: 22px; justify-items: start; }
  .book-cover { max-width: 220px; }
}

/* ---- Résumé --------------------------------------------------------------
   A career timeline: one block per job, newest first. The page shows each job's
   summary paragraph only — the bullets live in the downloadable PDF, because
   all eleven jobs' bullets together run to ~80 lines and nobody reads that on a
   web page. Both come from tools/resume-data.mjs, so they cannot disagree.

   Layout: the company name and the dates sit on one row (dates right-aligned on
   desktop, stacked underneath on a phone), with a gold rule under each job's
   head so the timeline scans as a list of distinct positions. */
.resume-actions {
  margin: 0 0 56px;
}
/* Capped at the same 860px as the Publications and Education blocks below.
   Without this the list runs the full 1200px page width while the summary
   paragraphs are only ~62ch (~530px) — so the right-aligned dates end up
   stranded hundreds of pixels away from the job they belong to, and the whole
   timeline reads as two unrelated columns. */
.resume-list {
  list-style: none;
  margin: 0 0 64px;
  padding: 0;
  max-width: 860px;
}
.resume-job {
  padding: 0 0 30px;
  margin: 0 0 30px;
  border-bottom: 1px solid rgba(243, 238, 250, 0.12);
}
.resume-job:last-child {
  border-bottom: 0;
  margin-bottom: 0;
}
.resume-job__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px 20px;
  margin: 0 0 4px;
}
.resume-job__company {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 24px;
  color: var(--cream);
  margin: 0;
}
.resume-job__dates {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.05em;
  color: var(--cream-60);
  margin: 0;
  white-space: nowrap;
}
/* The role is the credential — gold, same treatment as the Work-card role line
   and the section notes on a game page, so "this is what she did" reads the
   same everywhere on the site. */
.resume-job__role {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 2px;
}
.resume-job__titles {
  font-size: 15px;
  font-weight: 600;
  color: var(--cream-85);
  margin: 0 0 12px;
}
.resume-job__summary {
  font-size: 17px;
  line-height: 1.75;
  color: var(--cream-92);
  max-width: 62ch;      /* same measure as the About bio and game write-ups */
  margin: 0;
}
/* Extra credit lines — only the Blizzard publishing job uses these (the book
   titles she edited and authored). Quieter than the summary; it's a list. */
.resume-job__credit {
  font-size: 15px;
  line-height: 1.65;
  color: var(--cream-85);
  max-width: 62ch;
  margin: 14px 0 0;
}

.resume-section {
  margin: 0 0 56px;
}
.resume-section:last-child {
  margin-bottom: 0;
}
.resume-section__intro {
  font-size: 17px;
  line-height: 1.75;
  color: var(--cream-92);
  max-width: 62ch;
  margin: 0 0 24px;
}
.resume-pubs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 28px;
  max-width: 860px;
}
.resume-pub__label {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 10px;
}
.resume-pub__list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.resume-pub__list li {
  font-size: 16px;
  line-height: 1.6;
  color: var(--cream-92);
  margin: 0 0 8px;
}
/* A publication that has somewhere to point becomes a link (right now only the
   book, which goes to its entry on Press & Talks). The global rule strips link
   styling site-wide, so it has to be put back deliberately here — and it needs
   to be visible as a link, since the other items in the same list are not one.
   Underlined rather than only recoloured: colour alone is not enough to mark a
   link for anyone who can't distinguish gold from cream. */
.resume-pub__list a {
  color: var(--gold);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(233, 180, 76, 0.5);
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}
.resume-pub__list a:hover {
  color: var(--gold-hover);
  text-decoration-color: var(--gold-hover);
}
.resume-edu-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 24px;
  max-width: 860px;
}
.resume-edu__school {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 18px;
  color: var(--cream);
  margin: 0 0 2px;
}
.resume-edu__program {
  font-size: 16px;
  color: var(--cream-92);
  margin: 0 0 4px;
}
.resume-edu__dates {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.05em;
  color: var(--cream-60);
  margin: 0;
}

@media (max-width: 640px) {
  /* Stack the dates under the company name rather than letting a long range
     get squeezed against it. */
  .resume-job__head { flex-direction: column; gap: 2px; }
  .resume-job__dates { white-space: normal; }
}

/* About — photo + text */
.about-body {
  display: grid;
  grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
  gap: 48px;
  align-items: start;
}
.about-photo {
  width: 360px;
  max-width: 100%;
}
/* Bio prose. Measure is capped in ch so the line length stays readable no matter
   how wide the window gets — long lines are the main thing that makes body copy
   tiring to read. */
.about-text p {
  font-size: 17px;
  line-height: 1.75;
  color: var(--cream-92);
  max-width: 62ch;
  margin: 0 0 18px;
}
.about-text p:last-child { margin-bottom: 0; }

.about-photo img {
  width: 100%;
  height: auto;               /* show the full portrait, no crop */
  border-radius: 4px;
  box-shadow: 0 28px 56px -22px rgba(0, 0, 0, 0.6);
}

/* Contact — big links */
.contact-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  align-items: flex-start;
}
.contact-body .contact-email {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(24px, 4vw, 40px);
  color: var(--gold);
}
.contact-body .contact-email:hover { color: var(--gold-hover); }
/* Matched to .contact-email above — same face, weight and size (Brian,
   2026-08-05: the LinkedIn line was 18px next to a 40px email and read as an
   afterthought). Colour is still the distinction: email gold, LinkedIn cream,
   so email stays the primary route without being the only visible one. */
.contact-body .contact-social {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(24px, 4vw, 40px);
  color: var(--cream-85);
}
.contact-body .contact-social:hover { color: var(--cream); }
/* The ↗ would be a 40px glyph at full size and swamps the word next to it. */
.contact-body .contact-social span { font-size: 0.6em; }

@media (max-width: 900px) {
  .about-body { grid-template-columns: 1fr; gap: 28px; }
  .about-photo { width: 320px; }
}

/* 404 — reuses .page-hero / .page-title / .eyebrow / .btn; only spacing is new */
.error-hero .eyebrow { margin-bottom: 26px; }
.error-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}

/* ===========================================================================
   MOTION — the page entrance.

   Tag an element `rise` and it fades up 20px into place as the page loads.
   Add `rise--1` … `rise--5` to stagger it: the numbers are the reading order,
   so a hero reads eyebrow → name → line → button rather than arriving all at
   once. Ported from briankindregan.com, delays and duration matched exactly —
   this is what makes a page feel like it assembles itself instead of just
   appearing. The side-rail below shares the same keyframe.

   TO ANIMATE SOMETHING NEW: add `class="rise rise--2"` in the HTML. No CSS
   needed. To take motion off a page, delete the classes.

   `backwards` rather than `forwards`, and NO `opacity: 0` resting state — the
   element's normal state is fully visible and the animation only borrows the
   faded state during its delay. Get this wrong and any browser that skips the
   animation shows a permanently blank page. On a portfolio that is the worst
   possible failure, so it is worth the one-word difference.
   =========================================================================== */
@keyframes rise {
  from { opacity: 0; transform: translateY(20px); }
}
.rise    { animation: rise 0.9s ease backwards; }

/* HOLD THE ENTRANCE UNTIL THE PAGE IS ACTUALLY ON SCREEN.
   CSS animations run on the document's own clock whether or not anyone is
   looking. A page opened in a background tab — or in a browser window that is
   still being raised because the link was clicked from another app — finishes
   its whole entrance within about 1.5s, so by the time it is switched to, it is
   already settled and the visitor sees no motion at all. That is not a broken
   animation; it is an animation that played to an empty room.
   The class is added by the small script in each page's <head> (see
   build-stubs.mjs `entranceHold`) and removed the moment the page is seen.
   It FAILS OPEN: no JavaScript, no class, and the entrance behaves as before. */
html.entrance-hold .rise,
html.entrance-hold .subrail-list li { animation-play-state: paused; }
.rise--1 { animation-delay: 0.10s; }
.rise--2 { animation-delay: 0.24s; }
.rise--3 { animation-delay: 0.42s; }
.rise--4 { animation-delay: 0.56s; }
.rise--5 { animation-delay: 0.70s; }

/* ===========================================================================
   Game side-rail — a small list of every game pinned down the left of a game
   page, so a visitor deep in one game's videos can jump straight to another
   instead of backing out to Work.

   The links are injected by js/subrail.js, which is GENERATED by
   tools/build-stubs.mjs from the same GAMES list that builds the Work grid —
   so the rail can never drift from the grid. Nothing here needs editing to add
   a game.

   Desktop only. Below 1200px the page simply isn't wide enough to give up a
   250px gutter, so the rail hides and the "← Work" link does the job.
   =========================================================================== */
.subrail { display: none; }
.subrail-list { list-style: none; margin: 0; padding: 0; }
.subrail-item {
  display: block;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.03em;
  line-height: 1.35;
  color: var(--cream-60);
  transition: color 0.15s ease;
}
a.subrail-item:hover,
a.subrail-item:focus-visible { color: var(--gold-hover); }
.subrail-item.is-current { color: var(--gold); }   /* the page you're on */

@media (min-width: 1200px) {
  .subrail {
    display: block;
    position: fixed;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 210px;
    padding-left: 40px;
    z-index: 10;           /* under .site-header (20), above page content */
    /* NO max-height / overflow here, deliberately. An earlier version added
       `max-height: 80vh; overflow-y: auto` as insurance against a very long game
       list, and it caused a visible bug: the entrance animation starts each item
       at translateY(20px), transformed descendants count toward scrollable
       overflow, so a browser scrollbar appeared for the ~1.4s the animation ran
       and then vanished. Brian's rail has ten items and no overflow handling at
       all. If the game list ever grows enough to run off a short window, fix it
       by shrinking the gap or the font — not by making this scrollable. */
  }
  .subrail-list {
    display: flex;
    flex-direction: column;
    gap: 13px;
    border-left: 1px solid rgba(243, 238, 250, 0.18);
    padding-left: 18px;
  }
  /* Shift the WHOLE page right by the gutter — header, main and footer together.
     Shifting only <main> would slide the page title out of line with the logo
     above it, and this design leans on that alignment.
     The class is added by subrail.js only once the rail really exists, so a
     visitor with JavaScript off never gets an empty gutter down the side. */
  body.subrail-ready { padding-left: var(--rail-w); }
}

/* The rail floats in link by link, sharing the `rise` keyframe above so the whole
   page entrance moves as one. --i is the item's index, set by subrail.js, so
   adding a game needs no CSS change (Brian's hardcodes nth-child(1..10), which
   silently stops staggering at an 11th item).

   The 0.06s step is tighter than the hero's, on purpose: seven links stepping at
   the hero's pace would still be arriving long after the page had settled. */
.subrail-list li {
  animation: rise 0.9s ease backwards;
  animation-delay: calc(var(--i, 0) * 0.06s + 0.08s);
}

/* Reduced motion.
   Hover/state TRANSITIONS are switched off for anyone who asks for less motion.

   THE PAGE ENTRANCE IS A DELIBERATE EXCEPTION and still plays — matching
   briankindregan.com, which makes the same call. Brian's Windows has "animation
   effects" switched off, so a build that honoured the setting showed him a site
   with no motion anywhere while his own site animated fine; this is the whole
   reason the entrance appeared broken for two rounds. The effect is a gentle
   0.9s fade with 20px of travel — the mild end of the scale — but it IS a
   deliberate accessibility tradeoff, and Diandra can reverse it by adding
   `.rise, .subrail-list li { animation: none; }` to this block.

   If it is ever re-enabled here, toggle ONLY `animation-name`, never the
   `animation` shorthand — the shorthand resets duration, fill mode AND the
   per-element stagger delays, which silently flattens the whole effect.

   NOTE for anyone testing this: `transition: none` does NOT stop a CSS
   animation. @keyframes needs its own `animation` rule to be suppressed. */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}
