/* ============================================================================
   max bassetts website — style.css
   White ground, Helvetica Neue 65 Medium, no borders, no shadows, no gradients.
   The page is locked to the viewport: nothing scrolls. Over-long text
   scrolls inside its own column instead.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   1. TOKENS
   ------------------------------------------------------------------------- */
:root {
  --ink:    #000000;
  --paper:  #FFFFFF;
  --muted:  #8A8A8A;

  /* Helvetica Neue 65 Medium. The "65" is the weight — 500 selects the real
     Medium face where Helvetica Neue is installed; the explicit "Medium" family
     names are a fallback for engines that won't pick it by weight alone. */
  --font: "Helvetica Neue Medium", "HelveticaNeue-Medium", "Helvetica Neue",
          Helvetica, Arial, "Liberation Sans", system-ui, sans-serif;
  --fw: 500;   /* body weight — Helvetica Neue 65 Medium */

  --fs:      11px;   /* body — everything that isn't a title */
  --fs-ttl:  13px;   /* item title + subtitle, under the media */
  /* wordmark: 30px, but shrinks so it never outgrows a narrow phone (it is
     about 10.6 times its font size wide) */
  --fs-mark: clamp(20px, calc((100vw - 2rem) / 10.6), 30px);
  --lh: 1.45;

  /* The about column (photo + text) is this wide — the width of the photo. */
  --about-w: clamp(96px, 9vw, 150px);

  --pad: clamp(0.75rem, 1.6vw, 1.4rem);
  --gap: clamp(0.75rem, 2vw, 2rem);

  /* Fixed height of the media area. Every item's media is scaled to fit inside
     this band (never cropped), so all items are the same height and the
     title / subtitle / nav below never shift. Tweak this one number. */
  --media-h: 44svh;

  /* How far the media + right-hand column sit below the top of the grid, so
     the media sits at the vertical centre of the page. */
  --media-drop: 15svh;

  /* Pulls the right-hand description in toward the centre. A fixed amount per
     viewport (so the text still lands in the same place for every item), and
     only where the layout is wide enough to have gap to spare — see §5. */
  --desc-shift: clamp(0px, 30vw - 21.5rem, 7rem);

  --dur: 200ms;
  --ease: cubic-bezier(0.2, 0, 0, 1);
}

/* ---------------------------------------------------------------------------
   2. RESET + PAGE LOCK
   ------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

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

html, body {
  height: 100%;
  /* The page itself never scrolls — this is the whole layout premise. */
  overflow: hidden;
  overscroll-behavior: none;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs);
  font-weight: var(--fw);
  line-height: var(--lh);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

h1, h2, p, ul, li, figure {
  margin: 0;
  padding: 0;
  font-size: inherit;
  font-weight: inherit;
}
ul { list-style: none; }

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

button {
  appearance: none; -webkit-appearance: none;
  margin: 0; padding: 0; border: 0;
  background: none; color: inherit; font: inherit;
  text-align: inherit; cursor: pointer;
  touch-action: manipulation;
}

.link { color: inherit; text-decoration: underline; text-underline-offset: 0.18em; }

:focus { outline: none; }
:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }

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

@media (hover: hover) {
  .wordmark:hover, .step:hover, .link:hover { opacity: 0.45; }
}
.wordmark:active, .step:active { opacity: 0.45; }
.wordmark, .step, .link { transition: opacity var(--dur) var(--ease); }

/* ---------------------------------------------------------------------------
   3. APP FRAME
   Exactly one viewport tall, split into header + grid.
   ------------------------------------------------------------------------- */
.app {
  height: 100svh;
  display: flex;
  flex-direction: column;
  padding:
    calc(var(--pad) + env(safe-area-inset-top))
    calc(var(--pad) + env(safe-area-inset-right))
    calc(var(--pad) + env(safe-area-inset-bottom))
    calc(var(--pad) + env(safe-area-inset-left));
}
@supports not (height: 100svh) { .app { height: 100vh; } }

.header { flex: 0 0 auto; }

.wordmark {
  font-size: var(--fs-mark);
  font-weight: 700;
  letter-spacing: -0.015em;   /* bold Helvetica opens up; pull it back in */
  white-space: nowrap;
  padding: 0.15em 0;
  margin: -0.15em 0;
}

/* ---------------------------------------------------------------------------
   4. GRID — description | media | about
   `min-height: 0` on the grid and its children is what lets the media column
   shrink instead of pushing the page taller than the viewport.
   ------------------------------------------------------------------------- */
.grid {
  flex: 1 1 auto;
  min-height: 0;

  display: grid;
  grid-template-columns:
    minmax(0, 0.85fr)     /* about       */
    minmax(0, 2.6fr)      /* media       */
    minmax(0, 0.85fr);    /* description */
  gap: var(--gap);
  align-items: stretch;
  padding-top: var(--gap);
}

.col { min-height: 0; min-width: 0; display: flex; flex-direction: column; }

/* Any column whose text outruns the viewport scrolls within itself,
   so the page as a whole still never moves. */
.scroll {
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

/* ---------------------------------------------------------------------------
   5. RIGHT — description
   ------------------------------------------------------------------------- */
/* The description block is bottom-aligned to the media: its last line always
   lands on the media's bottom edge (a fixed place per viewport — longer copy
   just grows upward), and it's nudged in from the screen edge but a word's
   width clear of the media. */
.col--desc {
  justify-content: flex-start;
  align-items: flex-start;
  text-align: left;
}
.col--desc .scroll {
  margin-top: calc(var(--media-drop) + var(--media-h));   /* start at media bottom */
  transform: translateY(-100%);                           /* hang above that line */
}
@media (min-width: 1100px) {
  .col--desc .scroll {
    transform: translateY(-100%)
               translateX(calc(2rem - var(--desc-shift)));   /* +a word to the right */
  }
}

.desc { text-wrap: pretty; }
.desc:empty { display: none; }

.desc__list { margin-top: 0.8em; display: flex; flex-direction: column; gap: 0.1em; }
.desc__list:empty { display: none; }

.desc__links { margin-top: 0.8em; display: flex; flex-direction: column; gap: 0.25em; }
.desc__links:empty { display: none; }
.desc__link { display: inline-block; }

/* ---------------------------------------------------------------------------
   6. CENTRE — media, then title / subtitle / nav
   ------------------------------------------------------------------------- */
/* The media sits in a fixed-height band (--media-h). Top-aligned, so its top
   edge lands on the same line as the first line of text in both side columns,
   and because the band never changes height the title / subtitle / nav below
   it never move — every item lines up. */
.col--media {
  align-items: center;
  justify-content: flex-start;
  padding-top: var(--media-drop);
  position: relative;           /* anchors the "click video to unmute" hint */
}

.mediabox {
  /* Fixed height is what pins the caption in place. Still allowed to shrink
     (flex-shrink 1 + min-height 0) on viewports too short to hold the band. */
  flex: 0 1 auto;
  height: var(--media-h);
  min-height: 0;
  max-height: 100%;
  width: 100%;
  position: relative;          /* anchors the "loading media.." label */
  display: flex;
  align-items: flex-start;
  justify-content: center;
}

/* "loading media.." — flashes over the box while the media arrives. Same blink
   as the unmute hint. Never catches a tap meant for the media beneath it. */
.loading {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  pointer-events: none;
  animation: hint-flash 0.85s step-end infinite;
}
.loading[hidden] { display: none; }
.loading--failed { animation: none; }   /* an error is stated, not blinked */

/* One rule fits every media type inside the band — scaled to fit, never
   cropped, never overflowing. */
.media__el {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* Two or more stills side by side, each fitted to the band. */
.media__multi {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(0.4rem, 1.5vw, 1rem);
  max-width: 100%;
  min-height: 0;
  height: 100%;
}
.media__multi__img {
  min-width: 0;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* Photos on top, an audio player fitted underneath. */
.media__gallery {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6em;
  max-width: 100%;
  height: 100%;
}
.media__gallery .media__multi { flex: 1 1 auto; height: auto; }
.media__gallery .media__el--audio { flex: 0 0 auto; }

/* Video: aspect-ratio is set inline per item so the box is reserved before
   loading. Height-driven so it fills the fixed band; max-width (from .media__el)
   reins in wide clips. Paper background so any pillar/letterbox bars blend into
   the page instead of showing black. Pointer cursor — a tap toggles the sound. */
.media__el--video {
  height: 100%;
  width: auto;
  background: var(--paper);
  cursor: pointer;
}
/* No native control bar — the piece plays itself and can't be scrubbed. */
.media__el--video::-webkit-media-controls { display: none !important; }
.media__el--video::-webkit-media-controls-enclosure { display: none !important; }

/* Audio items: poster above a native player, the pair fitted to the band. */
.media__audiowrap {
  max-width: 100%;
  max-height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6em;
}
.media__poster {
  min-height: 0;
  max-width: 100%;
  max-height: calc(100% - 46px);   /* leave room for the player */
  object-fit: contain;
}
.media__el--audio {
  flex: 0 0 auto;
  width: min(100%, 340px);
  height: 36px;
}

/* --- under the media: title, subtitle, prev/next ----------------------- */
.under {
  flex: 0 0 auto;
  padding-top: calc(var(--gap) * 0.75);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2em;
}

.ttl { font-size: var(--fs-ttl); }
.sub { font-size: var(--fs-ttl); color: var(--muted); }
.sub:empty { display: none; }

.nav {
  display: flex;
  justify-content: center;
  gap: clamp(1.5rem, 5vw, 3rem);
  margin-top: 0.85em;
}
.step {
  padding: 0.5em 0.35em;
  margin: -0.5em -0.35em;
  white-space: nowrap;
}
.step[aria-disabled="true"] { color: var(--muted); cursor: default; opacity: 1; }

/* "click video to unmute!" — shown only on a muted opening slide, flashing until
   the first click dismisses it. It floats over the empty space above the media,
   right-aligned to the picture's top-right corner with a small gap. app.js
   (placeHint) sets its exact top / right from the picture's real edges; the
   values here are only the fallback until then. It takes no layout space, so
   nothing shifts, and it never catches a tap meant for the video. */
.hint {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 1;
  white-space: nowrap;
  text-align: right;
  color: var(--ink);
  pointer-events: none;
  animation: hint-flash 0.85s step-end infinite;
}
.hint[hidden] { display: none; }
@keyframes hint-flash { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }

/* ---------------------------------------------------------------------------
   7. LEFT — photo, contact, bio and the toggle row, collapsed behind "about me".

   Three-column layout: this column starts on the same line as the top of the
   media, and runs down to the bottom of the page. Closed, only the toggle row
   sits (at the bottom). Open, the four blocks — photo, contact links, bio, and
   the "hide" row — are spread from top to bottom: the photo level with the top
   of the media, "hide" at the bottom, and whatever height is spare becomes equal
   gaps between them. Line spacing never changes; only the gaps do.

   Nothing here is measured against the photo. Load a photo of any shape and the
   photo simply takes its own height; the gaps take up the rest. If the screen is
   too short to hold everything, the gaps shrink to their minimum (`gap`) and the
   column scrolls, with the "hide" row kept in view.
   ------------------------------------------------------------------------- */
.col--about {
  justify-content: flex-end;                /* closed: the toggle row alone, at the bottom */
  align-items: flex-start;
  text-align: left;
  padding-top: var(--media-drop);           /* top of the block = top of the media */
  gap: 0.9em;                               /* the least space between blocks */
  overflow-y: auto;
  scrollbar-width: none;
  /* The buttons' invisible tap padding hangs 0.5em below the toggle row. Room for
     it in the column (and taken back with the margin) stops it counting as
     overflow, so the column only scrolls when it truly has to. */
  padding-bottom: 0.5em;
  margin-bottom: -0.5em;
}
.col--about::-webkit-scrollbar { display: none; }
.col--about.is-open { justify-content: space-between; }

/* The wrapper adds no box of its own: its children (photo, contact, bio) join the
   column directly, so the spare space is shared between them and the toggle row. */
.about__body { display: contents; }
.about__body[hidden] { display: none; }

/* Each block is as wide as the photo column (--about-w). */
.portrait,
.about__contact,
.about__bio,
.about__bar { width: var(--about-w); flex: 0 0 auto; }

/* Toggle row: "about me" / "hide" on the left; while open, "download CV" flashes on
   the right, lined up with the right edge of the text. Sticky, so it stays in
   view if the column has to scroll. */
.about__bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  position: sticky;
  bottom: 0;
  background: var(--paper);
}
.about__toggle { flex: 0 0 auto; }

/* "download CV" — shown only while the panel is open, and flashing like the other
   prompts. Clicking it downloads the PDF. */
.about__cv {
  flex: 0 0 auto;
  color: var(--ink);
  text-decoration: none;          /* a prompt like the buttons, not an underlined link */
  animation: hint-flash 0.85s step-end infinite;
}
.about__cv[hidden] { display: none; }

.portrait { height: auto; }      /* any shape of photo: the width is fixed, the height follows */
.portrait[hidden] { display: none; }

.about__contact { color: var(--muted); }
.about__contact .link { color: var(--muted); overflow-wrap: anywhere; }

/* Justified — flush on both edges. Hyphenation stops the word gaps opening
   into rivers in this narrow column. Last line stays flush left. */
.about__bio {
  text-align: justify;
  text-align-last: left;
  -webkit-hyphens: auto;
  hyphens: auto;
}

/* ---------------------------------------------------------------------------
   8. STACKED LAYOUT — phones, and any tall / narrow window
   Chosen by the shape of the window, not the device: any portrait window
   (taller than wide), or a narrow one with room to stack. So a phone held
   upright and a desktop browser dragged tall behave identically.

   Three columns can't fit, so everything stacks full-width, in reading order:
   media (with title + nav), the description, then "about me" pinned to the
   bottom of the screen. The frame stays locked to the viewport, like desktop:
   the toggle never moves, and its text opens upward as a panel above it.

   Wide-but-short windows (a phone on its side) keep the three-column layout
   above, with the media band trimmed to fit.
   ------------------------------------------------------------------------- */
@media (max-aspect-ratio: 1/1), (max-width: 900px) and (min-height: 560px) {
  :root { --gap: 0.85rem; --media-h: 38svh; --media-drop: 0px; }

  .grid {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
  }

  .col--media { flex: 0 0 auto; order: 1; padding-top: 1.6rem; }   /* room for the unmute hint */

  /* Fills whatever is left between the media and the toggle; if the copy is
     longer than that, it scrolls in its own box. */
  .col--desc {
    flex: 1 1 auto;
    order: 2;
    min-height: 0;
    padding-top: 0;
    align-items: stretch;
    text-align: left;
  }
  .col--desc .scroll { margin-top: 0; transform: none; }

  /* Justified, like the about-me text: a squared-off block, flush both edges.
     Hyphenation keeps the word gaps from opening up; last line stays left. */
  .desc {
    text-align: justify;
    text-align-last: left;
    -webkit-hyphens: auto;
    hyphens: auto;
  }

  /* "about me": the toggle row is the only thing in the column, so it sits at the
     bottom-left and never moves. Its text is a panel that opens upward from it,
     over the free space, full width — photo at the left, text around it. */
  .col--about,
  .col--about.is-open {
    flex: 0 0 auto;
    order: 3;
    position: relative;
    align-items: flex-start;
    justify-content: flex-end;
    text-align: left;
    padding: 0;
    margin-bottom: 0;
    gap: 0;
    overflow: visible;
  }

  .about__body {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(100% + 0.5em);     /* clear of the toggle's tap padding (0.5em) … */
    z-index: 2;
    width: auto;
    max-height: 45svh;
    padding-bottom: 0.4em;          /* … so the text sits the same 0.9em above the toggle */
    background: var(--paper);
  }

  .about__bar { position: static; width: 100%; background: none; }

  .portrait { float: left; width: 104px; margin: 0 0.9em 0.4em 0; }
  .about__contact { width: auto; margin-bottom: 0.5em; }
  .about__bio { width: auto; }
}

/* Wide but short (a phone on its side): trim the media band so the title and
   nav still fit beneath it in the three-column layout. */
@media (min-aspect-ratio: 1/1) and (max-height: 559px) {
  :root { --media-h: 46svh; --media-drop: 3svh; }
}

/* ---------------------------------------------------------------------------
   9. MOTION + PRINT
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 1ms !important;
    animation-duration: 1ms !important;
  }
  /* These two are meant to flash, whatever the setting — keep their blink. */
  .hint, .loading, .about__cv { animation-duration: 0.85s !important; }
}

@media print {
  html, body { height: auto; overflow: visible; }
  .nav { display: none; }
  .grid { display: block; }
}
