/* ===========================================================================
   harrison gill — hg.works

   One page. Everything below is either persistent chrome (visible on every
   page), one of the eight pages, or a shared component (blue frames, players).

   Reading order:
     1. Tokens          — colors, type, spacing, depth
     2. Base            — reset, typography
     3. Depth layers    — what sits above what
     4. Nav bars        — the two white rectangles
     5. Chrome          — name, logo, socials, copyright, about, contact, …
     6. Blue frames     — the randomized quadrangles
     7. Players         — play/stop/tracker/line media controls
     8. Pages           — home, about, picture, music, art, grids, item
     9. Mobile          — every mobile override, in one block
   =========================================================================== */


/* 1. TOKENS ============================================================== */

:root {
  /* The only six colors on the site. */
  --white: #ffffff;
  --light-grey: #cdcdcd;
  --jet-green: #262820;
  --rad-orange: #ff7155;
  --sky-blue: #5687e8;
  --light-lemon: #fffdbb;

  /* Hairline stroke, used by blue frames, the player and the no-video line. */
  --hairline: 0.5pt;

  /* Nav bars are 11/80ths of the window height on desktop, 11/128ths on
     mobile. */
  --nav-h: calc(100dvh * 11 / 80);

  /* Distance from the window edge to the chrome. */
  --gutter: 4.5vw;

  /* Two sizes carry the whole site, and only two: every word set in
     Montserrat is one size, every word set in Playfair Display is the other. */
  --type-text: clamp(11px, 1.02vw, 21px);
  --type-display: clamp(26px, 2.8vw, 60px);

  /* Playfair sits a shade tight at these sizes; a hair of tracking opens it. */
  --display-tracking: 0.03em;

  --line-height-body: 1.4;

  /* Montserrat bold, used for return, about, and the two page messages. Full
     700 sits heavier than the references, so it stops short of it. */
  --weight-bold: 550;

  /* Spacing steps, all relative to the window so the layout keeps its
     proportions as the window changes size. */
  --space-xs: 0.6vw;
  --space-s: 1.2vw;
  --space-m: 2.4vw;
  --space-l: 4vw;

  /* Depth. Every z-index on the site comes from this scale.

     Page content is left at its natural depth rather than given a z-index,
     so that these values all compare against each other directly. A default
     fill layer goes negative to sit under that content, which is what "the
     lowest visible depth layer" asks for. */
  --z-fill: -1;         /* a blue frame's fill layer, by default the lowest */
  --z-fill-raised: 20;  /* a fill layer lifted above neighbouring content */
  --z-raised: 25;       /* the element that fill layer belongs to */
  --z-player: 30;       /* an item page's player, above its blue frame */
  --z-nav: 80;          /* the two nav bars */
  --z-stroke: 90;       /* every blue frame's stroke layer */
  --z-chrome: 100;      /* name, logo, socials, copyright, about, contact, … */
  --z-loading: 200;
}

@font-face {
  font-family: 'Playfair Display';
  src: url('fonts/playfair-display.woff2') format('woff2');
  /* Google served a static 700 instance here, not a variable font, so the face
     declares exactly the weight it contains. Declaring a range the file cannot
     back is what stops it loading in stricter browsers. */
  font-weight: 700;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: 'Montserrat';
  src: url('fonts/montserrat-italic.woff2') format('woff2');
  font-weight: 100 900;
  font-style: italic;
  font-display: block;
}


/* 2. BASE ================================================================ */

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

/* The cursor stays the default arrow everywhere: no pointing hand over links
   and buttons, no I-beam over text. */
* { cursor: default; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--white);
  /* Set on the root, not just on body, so nothing anywhere can fall back to
     the browser's default black. */
  color: var(--jet-green);
  overflow: hidden;               /* only the grid and art pages scroll */
}

body {
  font-family: 'Montserrat', system-ui, sans-serif;
  font-style: italic;
  font-weight: 400;
  font-size: var(--type-text);
  color: var(--jet-green);
  -webkit-font-smoothing: antialiased;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
}

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

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

/* Changes on the site are instant by default. The only timed changes are the
   ones the instructions ask for: the unsp cross-fade and the copied! message. */
:focus-visible { outline: 2px solid var(--sky-blue); outline-offset: 3px; }


/* No text on the site can be highlighted — not by dragging across it, not by
   double-clicking, not by a finger resting on it. The contact address still
   reaches the clipboard: app.js copies it when it is clicked. (Nothing can be
   dragged away either; app.js handles that, since Firefox ignores the CSS for
   it.) */
* {
  -webkit-user-select: none;
  user-select: none;
}

/* On a touch screen the browser adds behaviours this design does not want: a
   grey flash on tap, a press-and-hold menu over images, and zoom on double
   tap. Pinch zoom is deliberately left alone — it is the one a visitor may
   genuinely need. */
@media (hover: none) {
  * {
    -webkit-tap-highlight-color: transparent;
    -webkit-touch-callout: none;
  }

  /* manipulation keeps scrolling and pinch zoom, and drops double-tap zoom. */
  html { touch-action: manipulation; }
}


/* 3. DEPTH LAYERS ======================================================== */

/* Absolute, not fixed, and with no z-index: both of those would make the page
   a stacking context, which would trap the depth values of everything inside
   it and stop page content from comparing against the frames and nav bars. */
.page {
  position: absolute;
  inset: 0;
  display: none;
}

.page.is-active { display: block; }

.chrome { position: fixed; z-index: var(--z-chrome); }
.chrome[hidden] { display: none !important; }


/* 4. NAV BARS ============================================================ */

.nav-bar {
  position: fixed;
  left: 0;
  width: 100%;
  height: var(--nav-h);
  background: var(--white);
  z-index: var(--z-nav);
  pointer-events: none;
}

.nav-bar--top { top: 0; }
.nav-bar--bottom { bottom: 0; }


/* 5. CHROME ============================================================== */

/* Each chrome element is only as large as its own content and is centred on
   the nav bar by where it sits, rather than by filling the bar's height. That
   keeps hover and click confined to the words themselves. */
.chrome--top {
  top: calc(var(--nav-h) / 2);
  transform: translateY(-50%);
}

.chrome--bottom {
  bottom: calc(var(--nav-h) / 2);
  transform: translateY(50%);
}

/* The approach feature: as the cursor gets closer, app.js interpolates this
   custom property from light grey toward jet green. Hovering overrides it. */
.approachable {
  color: var(--approach-color, var(--light-grey));
}

#name { left: var(--gutter); }
#copyright { left: var(--gutter); color: var(--light-grey); }

/* Hover belongs to cursors. Unscoped, a touch device keeps the hover colour
   painted after the finger lifts, because there is nothing to move away. */
@media (hover: hover) {
  #name:hover,
  #contact:hover,
  .social:hover,
  .unsp-links a:hover { color: var(--rad-orange); }
}

#aboutBtn,
#returnBtn { font-weight: var(--weight-bold); }

/* The about button sits in the middle of the top bar; the return element and
   the Hg logo share the middle of the bottom bar, and never show at once. */
#aboutBtn { left: 50%; transform: translate(-50%, -50%); }

#returnBtn,
#hgLogo { left: 50%; transform: translate(-50%, 50%); }

@media (hover: hover) {
  #aboutBtn:hover,
  #returnBtn:hover,
  #hgLogo:hover { color: var(--rad-orange); }
}

/* The logo is a button, drawn like the icons below: its file is the mark's
   shape, and the button's own color shows through it. The file is named here
   so the shared mask rule can pick it up. */
#hgLogo {
  --icon: url('assets/icons/hg-logo.svg');
  width: clamp(30px, 3.9vw, 70px);
  height: clamp(30px, 3.9vw, 70px);
  color: var(--jet-green);
}

/* Briefly set while the loading screen still covers the page (see app.js),
   so the orange logo has been drawn once before anyone can tap it. */
#hgLogo.is-warming { color: var(--rad-orange); }

#socials {
  display: flex;
  align-items: center;
  right: var(--gutter);
  gap: clamp(6px, 0.8vw, 14px);
}

.social {
  width: clamp(15px, 1.9vw, 34px);
  height: clamp(15px, 1.9vw, 34px);
}

#contact { right: var(--gutter); }

.contact-copied {
  display: none;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  color: var(--rad-orange);
  white-space: nowrap;
}

/* While the address is on the clipboard, the address is swapped for the
   message. The element keeps its size so nothing around it moves. */
#contact.is-copied .contact-address { visibility: hidden; }
#contact.is-copied .contact-copied { display: block; }

/* Icons drawn by masking their SVG file, so CSS can colour them.

   Written as longhand properties rather than the mask shorthand: the
   shorthand's "position / size" syntax is not parsed reliably everywhere, and
   when the size is the part that gets dropped the mask falls back to the
   file's own intrinsic size — these SVGs have viewBoxes in the thousands of
   units — so a 19px icon box shows an empty corner and the icon disappears. */
.social,
.item-nav,
#hgLogo {
  background-color: currentColor;
  -webkit-mask-image: var(--icon);
          mask-image: var(--icon);
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
}


/* 6. BLUE FRAMES ========================================================= */

/* app.js creates one of these per layer, sizes and places it to fit the shape,
   and writes the four vertices into the polygon. Fixed to the window by
   default; a frame placed inside a grid scrolls with the grid instead (see the
   grids, below). Overflow stays visible so the outline's outer half, which
   falls just outside the box, is not cut off. */
.frame-layer {
  position: fixed;
  left: 0;
  top: 0;
  pointer-events: none;
  overflow: visible;
}

.frame-layer--stroke { z-index: var(--z-stroke); }
.frame-layer--fill { z-index: var(--z-fill); }

.frame-layer--stroke polygon {
  fill: none;
  stroke: var(--sky-blue);
  stroke-width: var(--hairline);
}

.frame-layer--fill polygon {
  fill: var(--white);
  stroke: none;
}

/* A fill layer lets clicks through to whatever is beneath it. The one behind an
   item page's player must not: its shape covers the lower part of the picture,
   and a click landing on the white would otherwise reach the picture and start
   or stop the video.

   Only the painted shape catches the click. The layer's own box stays
   transparent, so a click in a corner outside the shape still reaches the
   picture. The stroke layer above it stays click-through, and so does the
   player, which sits above both. */
.frame-layer--fill.stops-clicks polygon { pointer-events: auto; }

/* 7. PLAYERS ============================================================= */

/* A player is a play/stop button, a line, and a tracker. Its width is set per
   instance by app.js; everything else is derived from it, so the proportions
   hold at any size.

   width = button + (10% of line) + line  →  line = width / 1.137 */
.player {
  --line-w: calc(var(--player-w) / 1.137);
  --unit: calc(var(--line-w) * 0.037);   /* tracker diameter = player height */

  position: relative;
  display: flex;
  align-items: center;
  width: var(--player-w);
  height: var(--unit);
}

.player-button {
  width: var(--unit);
  height: var(--unit);
  flex: none;
  overflow: visible;        /* or the stroke is clipped at the viewBox edge */
}

.player-button polygon,
.player-button rect,
.player-tracker circle {
  stroke-width: var(--hairline);
  /* Draw the stroke at its stated width rather than scaled up by the viewBox. */
  vector-effect: non-scaling-stroke;
}

/* Play shows only while paused, stop only while playing. */
.player .player-play { display: block; fill: var(--jet-green); stroke: none; }
.player .player-stop { display: none; fill: var(--sky-blue); stroke: none; }
.player.is-playing .player-play { display: none; }
.player.is-playing .player-stop { display: block; }

/* Pressed on a touch screen (.is-pressed is set by app.js). */
.player-button.is-pressed .player-play {
  fill: var(--light-lemon);
  stroke: var(--jet-green);
}

.player-button.is-pressed .player-stop { fill: var(--rad-orange); }

@media (hover: hover) {
  .player-button:hover .player-play {
    fill: var(--light-lemon);
    stroke: var(--jet-green);
  }
  .player-button:hover .player-stop { fill: var(--rad-orange); }
}

/* The line starts a 10%-of-its-own-width gap to the right of the button. */
.player-track {
  /* The page must not scroll while the tracker is being dragged, so touches
     starting on the line are handled here rather than by the scroller. */
  touch-action: none;
  position: relative;
  container-type: inline-size;   /* lets the tracker measure the line: cqw */
  margin-left: calc(var(--line-w) * 0.1);
  width: var(--line-w);
  height: var(--unit);
}

/* The line is an SVG stroke across the middle of the track, not a CSS border.
   Browsers round a border to whole screen pixels, which can leave a 0.5pt
   border a single pixel thick — and on iPhone, Firefox was losing the art
   page's second line. A stroke keeps its true width, smoothed across pixels,
   the same way the play button and tracker are drawn. */
.player-line {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.player-line line {
  stroke: var(--light-grey);
  stroke-width: var(--hairline);
}

/* The tracker's centre stays on the line and inside its width. It starts at
   the line's left end and slides right by --progress (set by app.js, 0 to 1)
   times the line's width — 100cqw, since the track is a size container. A
   translate moves it smoothly between pixels without laying the page out
   again, and will-change lets the browser move it on its own layer. */
.player-tracker {
  position: absolute;
  top: 50%;
  left: 0;
  width: var(--unit);
  height: var(--unit);
  transform: translate(-50%, -50%);
  translate: calc(var(--progress, 0) * 100cqw) 0;
  will-change: translate;
  overflow: visible;        /* same clipping problem as the buttons */
}

.player-tracker circle {
  fill: var(--jet-green);
  stroke: none;
}

/* The tracker's hover look also holds for a whole scrub — from the moment the
   line or the tracker is pressed until it is let go, wherever the pointer
   goes meanwhile. app.js marks the player .is-scrubbing for that time. On a
   touch screen this is the tracker's pressed look. */
.player.is-scrubbing .player-tracker circle {
  fill: var(--light-lemon);
  stroke: var(--jet-green);
}

@media (hover: hover) {
  .player-tracker:hover circle {
    fill: var(--light-lemon);
    stroke: var(--jet-green);
  }
}

.player.is-playing .player-tracker circle { fill: var(--rad-orange); }

.player.is-playing.is-scrubbing .player-tracker circle {
  fill: var(--sky-blue);
  stroke: none;
}

@media (hover: hover) {
  .player.is-playing .player-tracker:hover circle {
    fill: var(--sky-blue);
    stroke: none;
  }
}


/* 8. PAGES =============================================================== */

/* --- Home and picture: evenly spaced words ----------------------------- */

/* The words sit on the window's own even divisions — three at 25/50/75%, two
   at 33/67% — with each word centred on its division regardless of how long
   it is. Spacing them apart by flow would shift them, since the words differ
   in width, so each is placed directly. */
.page--choices.is-active { display: block; }

.choice {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
}

[data-page="home"] .choice:nth-child(1) { left: 25%; }
[data-page="home"] .choice:nth-child(2) { left: 50%; }
[data-page="home"] .choice:nth-child(3) { left: 75%; }

[data-page="picture"] .choice:nth-child(1) { left: 33.333%; }
[data-page="picture"] .choice:nth-child(2) { left: 66.667%; }

.choice {
  font-family: 'Playfair Display', Georgia, serif;
  font-style: normal;
  font-weight: 700;
  font-size: var(--type-display);
  line-height: 1;
  letter-spacing: var(--display-tracking);
  color: var(--jet-green);
}

/* --- About ------------------------------------------------------------- */

/* The about and art pages share a layout: a narrower media column on the left,
   words on the right, both set further in than the chrome. */
.page--about,
.page--art {
  padding: var(--nav-h) 13.5vw var(--nav-h) 14.75vw;
}

.page--about.is-active,
.page--art.is-active {
  display: grid;
  grid-template-columns: 32.25vw 1fr;
  align-items: center;
  gap: 6vw;
}

#portrait { width: 100%; height: auto; }

.about-text {
  margin: 0;
  /* Sized to hold the longest of the four lines this copy is meant to break
     into, and no more. Measured in the real face: the longest line is 24.51em
     and the next word would join it at 25.12em, so the midpoint of that window
     is the most forgiving setting. In em rather than ch or px, the ratio holds
     as the type scales, so the breaks stay put at any window size — and being
     a max-width, it still gives way on a window too narrow to hold it.

     Re-measure this if the copy changes. */
  max-width: 24.8em;
  font-size: var(--type-text);
  line-height: var(--line-height-body);
  color: var(--jet-green);
}

/* --- Music ------------------------------------------------------------- */

.page--music { padding: var(--nav-h) var(--gutter); }

.construction-text {
  position: absolute;
  left: 29%;
  top: 34%;
  margin: 0;
  font-size: var(--type-text);
  font-weight: var(--weight-bold);
  color: var(--jet-green);
}

.construction-video {
  /* The file is opaque, drawn on white. Multiplying it against the white page
     gives the same result transparency would, and every browser handles it —
     which the alpha codecs did not. Only valid while the backdrop is white. */
  mix-blend-mode: multiply;
  position: absolute;
  left: 43%;
  top: 38%;
  width: 30%;
  height: auto;
}

/* --- Art --------------------------------------------------------------- */

.art-media { display: flex; flex-direction: column; }

.players {
  display: flex;
  flex-direction: column;
  gap: calc(var(--space-s) * 0.8);
  margin-bottom: var(--space-m);
}

/* The four images cross-fade in place, so they stack in one box. */
.unsp {
  position: relative;
  width: 100%;
  aspect-ratio: 1413 / 1080;
}

.unsp img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1s linear;
}

.unsp img.is-showing { opacity: 1; }

.islands-header {
  margin: 0 0 var(--space-m);
  font-family: 'Playfair Display', Georgia, serif;
  font-style: normal;
  font-weight: 700;
  font-size: var(--type-display);
  line-height: 1;
  letter-spacing: var(--display-tracking);
  color: var(--jet-green);
}

.islands-text p {
  margin: 0 0 1.2em;
  max-width: 52ch;
  font-size: var(--type-text);
  line-height: var(--line-height-body);
  color: var(--jet-green);
}

.unsp-links {
  display: flex;
  flex-direction: column;
  font-size: var(--type-text);
  line-height: var(--line-height-body);
}

.unsp-links a { align-self: flex-start; }

/* --- Advertisement and entertainment grids ----------------------------- */

.page--grid { overflow-y: auto; overflow-x: hidden; }

/* Positioned (without a z-index, so depths still compare site-wide) so that a
   thumbnail's blue frame can be placed inside it and scroll along with it. */
.grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.2vw;
  padding: var(--nav-h) 1.4vw;
}

.grid > .frame-layer { position: absolute; }

.thumb {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1728 / 1080;
}

.thumb img { width: 100%; height: 100%; object-fit: cover; }

/* A hovered thumbnail rises above its neighbours — and above its own blue
   frame's fill layer — but stays below the nav bars. */
.thumb.is-hovered { z-index: var(--z-raised); }

/* --- Item pages -------------------------------------------------------- */

/* The media fills as much of the wire frame area as it can without leaving it
   or changing proportions. Expressing that as one width — the smaller of the
   frame's width and the width its height allows — means the media box, and
   everything measured against it, is settled in CSS alone. */
.page--item {
  /* A safety net: on unusual window shapes the media, title and credits can
     together outgrow the space between the nav bars. */
  overflow-y: auto;
  --item-media-w: min(70.2vw, calc(53.3dvh * 1728 / 1080));
  padding: var(--nav-h) 0;
}

.page--item.is-active {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.item-title {
  margin: 0 0 2vw;
  /* As wide as the media, then shifted left of it — "above and slightly to
     the left". Long titles run on rather than wrapping, as in DT_item2. */
  width: var(--item-media-w);
  transform: translateX(-12%);
  white-space: nowrap;
  font-family: 'Playfair Display', Georgia, serif;
  font-style: normal;
  font-weight: 700;
  font-size: var(--type-display);
  line-height: 1.05;
  letter-spacing: var(--display-tracking);
  color: var(--jet-green);
}

/* The stage is the wire frame itself. */
.item-stage {
  position: relative;
  flex: none;
  width: 70.2vw;
  height: 53.3dvh;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The thumbnail takes as much of the wire frame as its own proportions allow.
   Everything measured "against the thumbnail" — the player width, the title
   and credits — is measured against this box. */
.item-media {
  position: relative;
  width: var(--item-media-w);
  aspect-ratio: 1728 / 1080;
}

.item-thumb {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* The video fills the wire frame on its own terms, by width or by height,
   whichever lets it be larger — its shape need not match the thumbnail's. */
.item-video {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Once the video is playing the thumbnail is gone, not sitting behind it. */
.item-stage.is-playing-video .item-media { display: none; }
.item-stage.is-playing-video .item-video { display: block; }

.item-coming-soon {
  display: none;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: var(--z-raised);
  font-size: var(--type-text);
  font-weight: var(--weight-bold);
  color: var(--jet-green);
  white-space: nowrap;
}

/* Only while the thumbnail is hovered or pressed, above its blue frame's
   white fill. */
.item-stage.show-frame .item-media.is-coming-soon .item-coming-soon { display: block; }

/* On desktop the player's middle sits on the bottom edge of the media, and it
   stays hidden until the media or its blue frame is hovered. */
.item-player {
  position: absolute;
  left: 50%;
  /* Centre of the player on the bottom edge of the thumbnail: the thumbnail is
     centred in the stage, so that edge is half its height below the middle. */
  top: calc(50% + var(--item-media-w) / 1.6 / 2);
  transform: translate(-50%, -50%);
  z-index: var(--z-player);
  display: none;
  --player-w: calc(var(--item-media-w) * 5 / 8);
}

.item-stage.has-video.show-player .item-player { display: flex; }

.item-noVid-line {
  display: none;
  width: calc(var(--item-media-w) * 7 / 8);
  border-top: var(--hairline) solid var(--light-grey);
}

.item-credits {
  margin: 3vw 0 0;
  /* The mirror of the title: media width, shifted right, and aligned to its
     own right edge — "below and slightly to the right". */
  width: var(--item-media-w);
  transform: translateX(12%);
  text-align: right;
  white-space: nowrap;
  font-size: var(--type-text);
  line-height: var(--line-height-body);
  color: var(--jet-green);
}

.item-nav {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  width: 2.8vw;
  height: 1.43vw;
  color: var(--jet-green);
}

.item-nav--left { left: 8vw; --icon: url('assets/icons/itemNav_L1.svg'); }
.item-nav--right { right: 8vw; --icon: url('assets/icons/itemNav_R1.svg'); }

@media (hover: hover) {
  .item-nav:hover { color: var(--rad-orange); }
}

/* At either end of a category the navigator becomes the outline version and
   stops responding. */
@media (hover: hover) {
  .item-nav.is-end:hover { color: var(--jet-green); }
}
.item-nav--left.is-end { --icon: url('assets/icons/itemNav_L2.svg'); }
.item-nav--right.is-end { --icon: url('assets/icons/itemNav_R2.svg'); }


/* --- Loading screen ---------------------------------------------------- */

#loadingScreen {
  position: fixed;
  inset: 0;
  z-index: var(--z-loading);
  background: var(--white);
  display: grid;
  place-items: center;
}

#loadingScreen[hidden] { display: none; }

.loading-word {
  font-weight: var(--weight-bold);
  font-size: var(--type-text);
  color: var(--jet-green);
  /* Above the loading screen's own blue frame, which is layered 1 and 2
     inside this same element. */
  position: relative;
  z-index: 3;
}


/* 9. MOBILE ============================================================== */

/* Portrait or narrow windows get the stacked layout from the MB references. */
@media (orientation: portrait), (max-width: 860px) {
  :root {
    --nav-h: calc(100dvh * 11 / 128);
    --gutter: 8vw;
    --type-text: clamp(10px, 2.85vw, 18px);
    --type-display: clamp(18px, 4.7vw, 36px);
    --space-m: 6vw;
    --space-l: 8vw;
  }

  /* A finger needs more to aim at than a cursor: the tracker is drawn a little
     larger than on desktop, and the line it rides gets a taller invisible strip,
     so the grab area is usable without changing how the player looks. */
  .player {
    --unit: calc(var(--line-w) * 0.05);
    height: auto;
  }

  .player-track { height: max(var(--unit), 34px); }

  #socials { gap: 2.2vw; }
  .social { width: 4.1vw; height: 4.1vw; }
  #hgLogo { width: 11.5vw; height: 11.5vw; }

  /* Home and picture stack vertically, each word centred on the window and
     spaced across the gap between the nav bars, as the MB references show.
     These repeat the desktop selectors exactly so they outrank them — a
     plainer `.choice` rule would lose to `[data-page] .choice:nth-child()`. */
  [data-page="home"] .choice:nth-child(1) { left: 50%; top: calc(var(--nav-h) + (100dvh - 2 * var(--nav-h)) * 0.25); }
  [data-page="home"] .choice:nth-child(2) { left: 50%; top: 50%; }
  [data-page="home"] .choice:nth-child(3) { left: 50%; top: calc(var(--nav-h) + (100dvh - 2 * var(--nav-h)) * 0.75); }

  [data-page="picture"] .choice:nth-child(1) { left: 50%; top: calc(var(--nav-h) + (100dvh - 2 * var(--nav-h)) / 3); }
  [data-page="picture"] .choice:nth-child(2) { left: 50%; top: calc(var(--nav-h) + (100dvh - 2 * var(--nav-h)) * 2 / 3); }

  .page--about, .page--art, .page--music, .page--item { overflow-y: auto; }

  /* --- About: portrait above, words below, both centred (MB_about) ------ */
  .page--about { padding: var(--nav-h) var(--gutter); }

  .page--about.is-active {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 17vw;
  }

  #portrait { width: 63.5vw; max-height: none; }

  /* The same measured cap as desktop. In em it holds the four-line break at
     the smaller mobile type too, and being font-relative it does not need its
     own number. */
  .about-text { width: auto; }

  /* --- Art: the words come first, then the players, then the image. That is
         the order MB_art shows, and the reverse of the desktop column order,
         so the two blocks are re-ordered rather than re-marked-up. -------- */
  /* MB_art starts the header lower than the bar alone would put it. */
  .page--art { padding: calc(var(--nav-h) + 11vw) var(--gutter) var(--nav-h); }

  .page--art.is-active {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 9vw;
  }

  /* As wide as the images below (74vw), so the paragraphs and the links line up
     with the images' left edge at 13vw; the header stays centred. */
  .art-words { order: 1; width: 74vw; }
  .art-media { order: 2; width: 100%; align-items: center; }

  /* 11.5vw leaves the gap MB_art shows between the header and the text:
     12.9vw from the header's baseline to the top of the first line. */
  .islands-header { text-align: center; margin-bottom: 11.5vw; }
  .islands-text p { max-width: none; }

  /* The gap between the players and the image. The white below the image, up
     to the bottom nav bar, is 2.25 times that (20.25vw). */
  .page--art { --art-gap: 9vw; }

  .players { width: 74vw; margin-bottom: var(--art-gap); }
  .unsp { width: 74vw; margin-bottom: calc(var(--art-gap) * 2.25); }

  /* --- Music: animation above, message below, both centred (MB_music) --- */
  .page--music { padding: var(--nav-h) var(--gutter); }

  .page--music.is-active {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 9vw;
  }

  .construction-video { position: static; order: 1; width: 59vw; height: auto; }
  .construction-text { position: static; order: 2; text-align: center; }

  /* --- Grids: one column, centred. Thumbnails are 81.4vw wide — a tenth wider
         than the 74vw MB_adv-ent shows — which leaves 9.3vw either side. ---- */
  .grid {
    grid-template-columns: 1fr;
    gap: 3vw;
    padding: var(--nav-h) 9.3vw;
  }

  /* Every part of an item page sits in a slot of fixed height, so the layout
     is decided by the window and not by the item in it. A title that wraps to
     two lines keeps the same centre line as a one-line title, and an item with
     no video leaves exactly the space a player would have taken — so nothing
     jumps as you walk through the items with the navigators. */
  .page--item.is-active {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0;
  }

  /* The wire frame, as MB_item_wire draws it: nearly the full width of the
     window, and as tall as a thumbnail 84vw wide — or 34dvh on a window too
     short for that. The thumbnail fills the frame's height; a video fills as
     much of the whole frame as its shape allows, so a 16:9 video reaches
     almost to the window's edges. */
  .page--item {
    --item-wire-w: 93.3vw;
    --item-wire-h: min(calc(84vw * 1080 / 1728), 34dvh);
    --item-media-w: calc(var(--item-wire-h) * 1728 / 1080);
  }

  .item-title,
  .item-credits {
    width: 100%;
    padding: 0 var(--gutter);
    transform: none;
    text-align: center;
    white-space: normal;
    margin: 0;
    /* The text centres inside its slot rather than growing the slot. */
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
  }

  .item-title { height: 15dvh; }
  .item-credits { height: 10dvh; }

  /* The stage is the wire frame's width. Media at the top of it, player or
     line centred in the space left below — the same space whether or not the
     item has a video. */
  .item-stage {
    width: var(--item-wire-w);
    height: calc(var(--item-wire-h) + 11dvh);
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 0;
    flex: none;
  }

  .item-media { width: var(--item-media-w); flex: none; }

  .item-stage.has-video .item-player,
  .item-stage:not(.has-video) .item-noVid-line {
    margin-top: auto;
    margin-bottom: auto;
  }

  /* The video fills the whole wire frame and letterboxes inside it. Its box is
     exactly the thumbnail's height, so nothing below jumps the moment a video
     starts. */
  .item-video {
    position: static;
    width: 100%;
    height: var(--item-wire-h);
    object-fit: contain;
  }

  .item-stage.has-video .item-player {
    position: static;
    top: auto;
    transform: none;
    display: flex;
    --player-w: calc(var(--item-media-w) * 7 / 8);
  }

  /* An item with no video shows a plain line where its player would be. */
  .item-stage:not(.has-video) .item-noVid-line { display: block; }

  .item-nav {
    position: static;
    transform: none;
    width: 8.5vw;
    height: 4.35vw;
  }

  .item-navs {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 62vw;
    height: 12dvh;
    flex: none;
  }
}

/* Touch devices never get the approach feature or hover colors — those are
   cursor behaviours. Pressed states stand in for them. */
@media (hover: none) {
  /* .is-pressed is set by app.js while a button is pressed, and kept for an
     eighth of a second after a tap (the press hold), so a tap reads as clearly as a cursor
     resting on something. It is the only thing that shows a pressed look:
     :active is left out, as it can linger on a press that became a scroll. */
  .approachable { color: var(--light-grey); }
  .approachable.is-pressed { color: var(--rad-orange); }

  #aboutBtn.is-pressed, #returnBtn.is-pressed,
  #hgLogo.is-pressed { color: var(--rad-orange); }

  .item-nav.is-pressed { color: var(--rad-orange); }

  .item-nav.is-end.is-pressed { color: var(--jet-green); }
}

@media (prefers-reduced-motion: reduce) {
  .unsp img { transition-duration: 0.01ms; }
}
