/*
  Griffy — portfolio
  A dark screening room. The left wall holds the name and how to reach Griffy;
  the right is a reel that lights one cover at a time, and the room takes on that cover's colours (data/works.json):
  its accent lights the room, its shade tints the night, and a bright cover lights the room more.
  Phones and tablets held upright, and other tall screens, stack everything (see "Tall screens" below).
  Rules under html.js only apply when the script runs, so the page reads fine without it.
*/
/* The initial values are the room's defaults (--default-* in :root); @property cannot take them from a var(). */
@property --glow { syntax: "<color>"; inherits: true; initial-value: #7d8aa3; }
@property --shade { syntax: "<color>"; inherits: true; initial-value: #141414; }
@property --brightness { syntax: "<number>"; inherits: true; initial-value: .5; }
/* Runs 0 → 1 as the room turns from one cover's colours to the next (site.js sets the two ends). */
@property --tint { syntax: "<number>"; inherits: false; initial-value: 1; }

/* Fonts are served from assets/fonts (SIL OFL, licences beside them), split like Google Fonts:
   the latin file covers the page as it is, latin-ext only loads if a name needs it.
   GRIFFY is drawn from Anton's outlines (tools/build-lockup.py), so the page never loads Anton itself. */
@font-face {
  font-family: "Instrument Sans"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("../fonts/instrument-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Sans"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("../fonts/instrument-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Instrument Serif"; font-style: italic; font-weight: 400; font-display: swap;
  src: url("../fonts/instrument-serif-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Serif"; font-style: italic; font-weight: 400; font-display: swap;
  src: url("../fonts/instrument-serif-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --night: #09090a; /* tools/build-tones.py and build-works.py read it from here */
  --ink: #f2f0ea;
  --dim: #75736d;

  /* The focused cover's colours. The room turns through OKLCH, so going from one hue to another it passes through the
     colours between them, not through grey. */
  /* A cover without colours of its own lights the room with these; site.js reads them from here too. */
  --default-glow: #7d8aa3; --default-shade: #141414; --default-brightness: .5;
  --glow-from: var(--default-glow); --glow-to: var(--default-glow);
  --shade-from: var(--default-shade); --shade-to: var(--default-shade);
  --brightness-from: var(--default-brightness); --brightness-to: var(--default-brightness);
  --tint: 1;
  --glow: color-mix(in oklch, var(--glow-from), var(--glow-to) calc(var(--tint) * 100%));
  --shade: color-mix(in oklch, var(--shade-from), var(--shade-to) calc(var(--tint) * 100%));
  --brightness: calc(var(--brightness-from) + (var(--brightness-to) - var(--brightness-from)) * var(--tint));
  /* The light the room catches is quieter than the cover that casts it: the accent at half its chroma, same hue and
     lightness (mixed with the grey of its own lightness). The type stays white throughout; colour is only light.
     The focused cover's shadow takes its own accent the same way (.work below).
     The grey's lightness is the accent's, ACCENT_L in tools/build-tones.py, which stops if the two differ. */
  --accent-grey: oklch(.74 0 0);
  --room-glow: color-mix(in oklab, var(--glow) 50%, var(--accent-grey));
  /* The night, tinted: deepest at the wall, most coloured at the reel (see @supports below). site.js reads the wall's
     colour back for the browser's own bar. */
  --room-wall: var(--night);
  --room-reel: var(--night);
  --room-way: to right;
  /* Where the wall's darkest stretch ends and where the reel's colour is fullest, along --room-way. */
  --room-wall-end: 20%;
  --room-reel-end: 100%;

  --serif: "Instrument Serif", Georgia, "Times New Roman", serif;
  --sans: "Instrument Sans", "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;

  /* Everything is a share of something on screen, with no floor or cap in px but the focus ring's 1px, the captions'
     11px (.c-artist) and the 12px footer, which only shows without the script. Tall screens set a few of these tokens once (see "Tall screens" below).
     --unit is the layout's shared scale: 1px on a 1920-wide page, growing and shrinking with the page's width. */
  --unit: calc(var(--page-w, 100vw) / 1920);
  /* Focus rings: 2px / 4px at 1920 × 1080; the ring never gets thinner than 1px, so focus stays visible. */
  --ring-w: max(1px, calc(var(--unit) * 2));
  --ring-o: calc(var(--unit) * 4);
  /* The margin is 2.6% of the page's width. */
  --pad: 2.6vw;
  /* Without the script, the contact details are 3.486% of the page's width; the arrow after each link has no floor.
     They were 3.4% for itsmegriffy@gmail.com. With hello@itsmegriffy.com the mail line, from "Get" to its arrow, is
     1.0252 times shorter (15.401em, was 15.789em), so the type is set that much larger and the line ends where it did. */
  --reach-type: calc(var(--page-w, 100vw) * .0348566);
  --arrow-min: 0px;
  /* The 200px noise tile at 1920 × 1080, as a share of the screen's short side. */
  --grain: calc(min(100vw, 100vh) * 200 / 1080);
  /* The OPEN cursor's type is 1/32 of the full-size cover (--cover-size, set by site.js); its ring is eight times that. */
  --cursor-type: calc(var(--cover-size, var(--cover)) / 32);
  --row: calc(min(100cqw, var(--cover)) * .2);
  /* The wall and the reel split at the golden ratio; the room light sits over the reel. */
  --wall: 1.618fr;
  --reel: 1fr;
  --cover: 48vh;
  --light-x: 79vw;
  --light-y: 50vh;
  /* The room light is 160% of the screen's width, so it runs on behind the whole wall, as far as GRIFFY's G. */
  --light-w: 160vw;
  --ease: cubic-bezier(.2, .7, .1, 1);
  color-scheme: dark;
  /* The cover lights first; the room follows a moment later, as eyes adjust. */
  transition: --tint 1.6s ease .3s;
}

/* Only where color-mix works: without it a var() holding one is invalid and the page would lose its night altogether. */
@supports (color: color-mix(in oklab, red, blue)) {
  :root {
    --room-wall: color-mix(in oklab, var(--night) 80%, var(--shade));
    --room-reel: color-mix(in oklab, var(--night) 35%, var(--shade));
  }
}

/* Whole pixels: the wall's margin and GRIFFY's width are rounded to whole pixels, so GRIFFY is painted where the layout
   says whatever the browser does to a box that falls between two pixels, and the type set beside it can be placed
   against it to the pixel (site.js, refine). Browsers without round() keep the fractional sizes. */
@supports (width: round(nearest, 1.5px, 1px)) {
  :root { --pad: round(nearest, 2.6vw, 1px); }
  .lockup { width: round(down, 100%, 1px); }
}

html { background: var(--room-wall); scroll-behavior: auto; }
html.js[data-loop="on"] { scrollbar-width: none; }
html.js[data-loop="on"]::-webkit-scrollbar { display: none; }
body {
  margin: 0;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
p, h1, h2 { margin: 0; }
a { color: inherit; text-decoration: none; }
a:focus-visible, button:focus-visible { outline: var(--ring-w) solid var(--ink); outline-offset: var(--ring-o); }
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- Room light ---------- */
/* The room light is fixed and very wide, so no edge or centre can be seen sliding against the covers.
   Under it the night runs from the wall into the cover's shade at the reel. */
.ambient {
  position: fixed; inset: 0; pointer-events: none; z-index: 0; overflow: hidden;
  background: linear-gradient(var(--room-way), var(--room-wall) var(--room-wall-end), var(--room-reel) var(--room-reel-end));
}
.ambient::before, .ambient::after { content: ""; position: absolute; border-radius: 50%; }
.ambient::before {
  width: var(--light-w); height: 170vh; left: calc(var(--light-x) - var(--light-w) / 2); top: calc(var(--light-y) - 85vh);
  background: radial-gradient(closest-side,
    color-mix(in oklab, var(--room-glow) 42%, transparent),
    color-mix(in oklab, var(--room-glow) 20%, transparent) 50%,
    color-mix(in oklab, var(--room-glow) 6%, transparent) 80%,
    transparent);
  /* At 1920×1080, the light's shorter side is 1836px and its blur is 140px. */
  filter: blur(calc(min(var(--light-w), 170vh) * 140 / 1836));
  /* .6 for a cover of middling brightness; a bright cover lights the room more, a dark one less. */
  opacity: calc(.6 + (var(--brightness) - .5) * .55);
}
.ambient::after {
  --light-width: 46vw;
  width: var(--light-width); height: 50vh; left: -10vw; bottom: -18vh;
  background: radial-gradient(closest-side, color-mix(in oklab, var(--room-glow) 45%, transparent), transparent);
  /* Use the actual width, including the wide-screen page cap; baseline short side is 540px. */
  filter: blur(calc(min(var(--light-width), 50vh) * 90 / 540));
  /* .35 for a cover of middling brightness, as above. */
  opacity: calc(.35 + (var(--brightness) - .5) * .37);
}
.grain {
  position: fixed; inset: -50%; z-index: 30; pointer-events: none; opacity: .07;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200' viewBox='0 0 200 200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  /* The viewBox lets the noise itself scale with the tile, not just the tile's size. */
  background-size: var(--grain);
  animation: grain 1s steps(6) infinite;
}

.page { position: relative; z-index: 1; display: grid; grid-template-columns: minmax(0, var(--wall)) minmax(0, var(--reel)); }

/* ---------- Left wall ---------- */
/* The wall is the height of the screen with the browser's bars showing (svh), not the height of the moment (dvh): a phone
   held sideways hides and shows its bars as the page scrolls, and a wall that followed them would draw GRIFFY and the
   contact details again mid-scroll, which flickers. With the bars hidden the room shows on a little below the wall. */
.wall {
  position: sticky; top: 0;
  height: 100vh; height: 100svh;
  box-sizing: border-box;
  padding: var(--pad);
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 4vh;
  min-width: 0;
}
.name { min-width: 0; }
.lockup { display: block; width: 100%; height: auto; overflow: visible; }
.lk-greeting, .lk-name, .lk-role { fill: var(--ink); }

/* ---------- Contact ---------- */
/*
  Mail and Instagram, said as one sentence at the foot of the wall: the italic voice of "It's me," leading into the links.
  site.js sets --reach-size so the mail line's ink, from the G of "Get in touch at" to the tip of its arrow, spans --fit
  of the wall's width: it starts on GRIFFY's G and ends on the end of its Y. The underline draws in on hover.
*/
.reach {
  --fit: 1;
  /* The arrow's size, and its stroke in its 10-unit drawing; the underline is drawn with the same pen. */
  --arrow: max(var(--arrow-min), .4em);
  --arrow-stroke: 1.1;
  align-self: end; min-width: 0; font-style: normal; line-height: 1.12;
  margin-bottom: var(--nudge-b, calc(var(--ink-bottom, 0) * -1em));
  font-size: var(--reach-size, var(--reach-type));
}
.r-line { display: block; width: fit-content; margin-left: var(--nudge-x, calc(var(--ink-in, 0) * -1em)); white-space: nowrap; }
.r-say {
  font-family: var(--serif); font-style: italic; color: color-mix(in oklab, var(--dim) 50%, var(--ink));
  transition: color .4s var(--ease);
}
.r-text {
  /* As thick as the arrow's stroke: .044em, 3.13px at the fitted 1920 × 1080 contact font (71.0799px). */
  --underline-thickness: calc(var(--arrow) * var(--arrow-stroke) / 10);
  position: relative;
  white-space: nowrap; letter-spacing: calc(-.035em + var(--track, 0px));
}
.r-text::after {
  content: ''; position: absolute; left: 0; top: var(--underline-top, 94%);
  width: 0; height: var(--underline-thickness); background: currentColor;
  pointer-events: none; transition: width .7s var(--ease);
}
.r-line:hover .r-say, .r-line:focus-visible .r-say { color: var(--ink); }
.r-line:hover .r-text::after, .r-line:focus-visible .r-text::after { width: 100%; }

/* A small arrow after each link shows it can be clicked; it lifts on hover. */
.r-arrow {
  display: inline-block; width: var(--arrow); height: var(--arrow); margin-left: .1em; vertical-align: .3em; overflow: visible;
  fill: none; stroke: currentColor; stroke-width: var(--arrow-stroke); stroke-linecap: round; stroke-linejoin: round;
  opacity: .7; transition: transform .6s var(--ease), opacity .4s var(--ease);
}
.r-line:hover .r-arrow, .r-line:focus-visible .r-arrow { opacity: 1; transform: translate(.08em, -.08em); }

/* ---------- Reel ---------- */
/* List gaps use the reel's available width, capped at the full-size cover width.
   The adjacent-list margin keeps the same gap across the original/clone boundary. */
.works { display: flex; flex-direction: column; container-type: inline-size; padding: 17vh var(--pad) 11vh; min-width: 0; }
.works-list { list-style: none; margin: 0 auto; padding: 0; display: grid; gap: var(--row); width: min(100%, var(--cover)); }
.works-list + .works-list { margin-top: var(--row); }
.works-list[hidden] { display: none; }
/* site.js moves the lists by the fraction of a pixel their scroller cannot take, every frame the reel runs. */
.js .works-list { will-change: transform; }
.work {
  min-width: 0; container-type: inline-size;
  /* The cover's own light, as the room's (--room-glow) once the room has turned to it; build-works.py sets --accent. */
  --cover-glow: color-mix(in oklab, var(--accent, var(--default-glow)) 50%, var(--accent-grey));
}

.cover { display: block; position: relative; aspect-ratio: 1; max-width: 100%; }
.cover img {
  display: block; width: 100%; height: 100%; object-fit: cover; background: #151515;
  transform-origin: center bottom;
  transition: filter 1s var(--ease), transform 1.1s var(--ease), box-shadow 1.1s var(--ease);
}
.js .work .cover img { filter: grayscale(1) brightness(.36) contrast(1.05); transform: scale(.9); }
.js .work.is-focus .cover img {
  filter: none; transform: none;
  /* Keep the 1920×1080 shadow proportions against the full-size 518.4px cover. */
  box-shadow: 0 calc(100cqw * 26 / 518.4) calc(100cqw * 80 / 518.4) calc(100cqw * -34 / 518.4) color-mix(in oklab, var(--cover-glow) 30%, transparent);
}
.js .work .cover:hover img, .js .work .cover:focus-visible img { filter: none; }

/* Each work is captioned with its artist over its title, set left under the cover: the name upright, as GRIFFY and
   the contact links are, and the title in the italic of "It's me,". Artwork and caption scale from their shared
   boundary, keeping the gap and text proportional and both edges aligned throughout the focus transition.
   Gaps keep their 1920×1080 proportions: cover-to-artist 16px / 518.4px, artist-to-title 2px / 518.4px. */
.cap {
  display: grid; gap: calc(100cqw * 2 / 518.4); padding-top: calc(100cqw * 16 / 518.4); min-width: 0;
  transform-origin: center top;
  transition: color .8s var(--ease), opacity .8s, transform 1.1s var(--ease);
}
.js .cap { color: var(--dim); opacity: .55; transform: scale(.9); }
.js .work.is-focus .cap { color: var(--ink); opacity: 1; transform: none; }
.c-artist, .c-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Keep the 1920×1080 proportions: 21.7167px / 518.4px and 35.1384px / 518.4px. The title is the artist's size times
   the golden ratio (1.618), the ratio the wall and the reel split at.
   Each row's width is the square cover's size, including width-constrained and tall layouts.
   The artist is never set below 11px, the smallest type Apple and Material allow, and the title below 11px times the
   golden ratio. Both reach their floor on the same cover (262.6px wide), so they stay in proportion on every screen.
   Below it, on small and landscape phones, the longest titles end in an ellipsis rather than shrink out of reading. */
.c-artist { font-size: max(11px, calc(100cqw * 21.7167 / 518.4)); font-weight: 600; letter-spacing: .02em; }
.c-title {
  font-family: var(--serif); font-style: italic; line-height: 1.1;
  font-size: max(calc(11px * 1.618034), calc(100cqw * 35.1384 / 518.4));
}

/* The footer only shows without the loop. Its type stays at a legible 12px on every screen. */
.site-foot {
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  padding: 0 var(--pad) calc(32px + env(safe-area-inset-bottom, 0px));
  font-size: 12px; color: var(--dim); position: relative; z-index: 1;
}
:root[data-loop="on"] .site-foot { display: none; }

/* Until its fonts are in and it is fitted to them, the room is not shown (site.js, at most 3s; index.html): drawn first
   in the fallback fonts, the contact details, GRIFFY's height and the light would all change once they arrive. Hidden,
   not removed, so it can be measured meanwhile. */
.is-loading .ambient, .is-loading .page, .is-loading .site-foot { visibility: hidden; }

/* The first active cover is fully drawn; later interaction transitions remain. */
:root.is-starting { transition: none; }
.is-starting *, .is-starting *::before,
.is-starting *::after { transition: none !important; }

/* ---------- YouTube cursor ---------- */
/* The cursor is sized against its type (--cursor-type): in the wide layout its ring is a quarter of the full-size cover,
   eight times its type, with an edge of 1/11 and a blur of 8/11 of that type (129.6px, 16.2px type at 1920 × 1080). */
.cursor { position: fixed; left: 0; top: 0; z-index: 25; pointer-events: none; transform: translate3d(-200px, -200px, 0); }
.cursor i {
  position: absolute; left: -4em; top: -4em; width: 8em; height: 8em; border-radius: 50%;
  border: calc(1em / 11) solid rgba(242, 240, 234, .7); color: var(--ink);
  background: rgba(9, 9, 10, .28); -webkit-backdrop-filter: blur(calc(8em / 11)); backdrop-filter: blur(calc(8em / 11));
  display: flex; align-items: center; justify-content: center;
  font-style: normal; font-size: var(--cursor-type); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; white-space: nowrap;
  transform: scale(0); transition: transform .45s var(--ease);
}
.cursor.is-on i { transform: scale(1); }
/* The work's role runs round inside the ring (site.js fills it), half the OPEN type's size and dimmed to a texture
   rather than a line to read. It turns once in 60s at an even pace; with reduced motion it stands still (below). */
.cursor-orbit {
  position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible;
  animation: orbit 60s linear infinite; animation-play-state: paused;
}
.cursor.is-on .cursor-orbit { animation-play-state: running; }
.cursor-orbit path { fill: none; }
.cursor-orbit text { fill: currentColor; fill-opacity: .55; font-size: 6.4px; font-weight: 600; letter-spacing: .16em; }
.has-cursor [data-cursor] { cursor: none; }

@keyframes orbit { to { transform: rotate(1turn); } }
@keyframes grain {
  0% { transform: translate(0, 0); } 20% { transform: translate(-3%, 2%); } 40% { transform: translate(2%, -3%); }
  60% { transform: translate(-2%, -1%); } 80% { transform: translate(3%, 3%); } 100% { transform: translate(0, 0); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---------- Wider than 2.39:1 ---------- */
/*
  Past 2.39:1, the shape of a 21:9 monitor (2560×1080 is 2.37, 3440×1440 is 2.39), the page stops widening: it keeps
  that shape, centred, and the room (light, grain) runs on to the screen's edges. 16:9 and 16:10 screens fill edge to
  edge in a browser window (about 1.75–2.3 once the browser's own bars are taken off), as do 21:9 monitors in full screen.
  A 21:9 monitor's browser window (about 2.6–2.7), 32:9 screens and phones held sideways with Safari's bar showing
  get side margins.
  Sizes that the wide layout takes from the screen's width (vw) are taken from the page's width (--page-w) here.
*/
@media (min-aspect-ratio: 239/100) {
  :root {
    --page-w: calc(100vh * 2.39);
    --edge: calc((100vw - var(--page-w)) / 2);
    --pad: calc(var(--page-w) * .026);
    --light-x: calc(var(--edge) + var(--page-w) * .79);
    --light-w: calc(var(--page-w) * 1.6);
    /* The night runs from wall to reel across the page, not the screen; the gutters carry on its two ends. */
    --room-wall-end: calc(var(--edge) + var(--page-w) * .2);
    --room-reel-end: calc(var(--edge) + var(--page-w));
  }
  @supports (width: round(nearest, 1.5px, 1px)) {
    :root {
      --page-w: round(down, calc(100vh * 2.39), 1px);
      --edge: round(down, calc((100vw - var(--page-w)) / 2), 1px);
      --pad: round(nearest, calc(var(--page-w) * .026), 1px);
    }
  }
  .page { width: var(--page-w); margin-left: var(--edge); }
  .ambient::after { --light-width: calc(var(--page-w) * .46); left: calc(var(--edge) - var(--page-w) * .1); }
}

/* ---------- Tall screens (phones and tablets held upright): the wall sits on top, the reel runs beneath it ---------- */
/*
  Taller than 5:6. Measured on the browser's own viewport, with its bars taken off: phones held upright come to about
  0.45–0.7 (an iPhone SE in Safari is 0.68), iPads held upright to 0.66–0.82 (an iPad Pro 13 in Safari with its tab bar
  showing). An unfolded Galaxy Fold (about 0.88) and near-square windows keep the wide layout.
  Keep this query in step with `stack` in site.js.
  The wide layout turned on its side: the screen splits at the golden ratio again, now across its height, the wall over
  the reel. The page itself stays put; the reel is its own scroller, looping and running up as it does beside the wall.
  Every size here is a share of the screen or of the cover, so a phone and a tablet are drawn alike.
*/
@media (max-aspect-ratio: 5/6) {
  :root {
    --screen-h: 100vh;
    /* The wide layout's tokens, read with the screen's width standing in for the wide wall's: that wall is 61.8% of a
       page, so the shared scale is 1px on a 1920 × .618 (1186.56px) wide screen, and the margin and no-JS contact type
       keep their shares of the wall (2.6% and 3.486% of the page over its 61.8%). The arrow, grain and cursor tokens keep
       their root values: no floor, the screen's short side, and the full-size cover. Focus rings keep their 1px floor. */
    --unit: calc(100vw / (1920 * .618));
    --pad: calc(100vw * .026 / .618);
    --reach-type: calc(100vw * .0348566 / .618);
    /* The golden split on every upright screen; on wider ones GRIFFY keeps its width and is drawn shorter (site.js). */
    --wall-h: calc(var(--screen-h) / 2.618);
    --reel-h: calc(var(--screen-h) - var(--wall-h));
    --cover: 100cqw;
    --light-x: 50vw;
    /* Here the light already reaches the wall above the reel, so it keeps the width it had. */
    --light-w: 120vw;
    --room-way: to bottom;
  }
  @supports (height: 100dvh) { :root { --screen-h: 100dvh; } }
  @supports (width: round(nearest, 1.5px, 1px)) {
    :root { --pad: round(nearest, calc(100vw * .026 / .618), 1px); }
  }
  .page { grid-template-columns: minmax(0, 1fr); }
  .wall {
    position: relative; height: auto; grid-template-rows: auto; gap: calc(var(--wall-h) * .04);
    padding: calc(var(--pad) + env(safe-area-inset-top, 0px)) var(--pad) var(--pad);
  }
  .works { padding: 0 var(--pad) var(--pad); }
  .site-foot { padding-inline: var(--pad); }
  .grain { animation: none; }

  /* With the script, the reel runs as it does in the wide layout. The cover keeps the wide reel's share of its height
     (48%), so the works above and below show by the same amount; the light moves over the reel. */
  html.js { overflow: hidden; overscroll-behavior: none; }
  :root.js { --cover: calc(var(--reel-h) * .48); --light-y: calc(var(--wall-h) + var(--reel-h) / 2); }
  .js .page {
    height: var(--screen-h); overflow: hidden;
    grid-template-rows: var(--wall-h) minmax(0, 1fr);
  }
  .js .wall { height: auto; min-height: 0; grid-template-rows: auto 1fr; }
  .js .works {
    min-height: 0; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
    scrollbar-width: none;
    /* Half the reel on either end, so any work can come to its middle line. */
    padding-block: calc(var(--reel-h) / 2);
    -webkit-mask-image: linear-gradient(to bottom, transparent, #000 calc(var(--reel-h) * .04));
    mask-image: linear-gradient(to bottom, transparent, #000 calc(var(--reel-h) * .04));
  }
  .js .works::-webkit-scrollbar { display: none; }
}
