/* ==========================================================================
   Adam Spicer — portfolio

   Three layouts, matched to the mockups:
     phone    < 600px    hamburger menu, single column, no filter row
     tablet   600–1023   horizontal filter row under the search bar
     desktop  >= 1024    filter rail down the left side

   Everything sits inside one shared container so the nav, the filter rail
   and the project grid all line up on a common set of edges. The mockups
   are hand-drawn and don't align perfectly; this rationalises them.
   ========================================================================== */

/* WOFF2 first, TTF second. The browser takes the first format it
   understands — WOFF2 everywhere current (~71% smaller: 476KB -> 140KB),
   TTF as a fallback for anything ancient. Both files are kept. */
@font-face {
  font-family: "Libre Baskerville";
  src: url("../fonts/LibreBaskerville-Regular.woff2") format("woff2"),
       url("../fonts/LibreBaskerville-Regular.ttf") format("truetype");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Libre Baskerville";
  src: url("../fonts/LibreBaskerville-Italic.woff2") format("woff2"),
       url("../fonts/LibreBaskerville-Italic.ttf") format("truetype");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Libre Baskerville";
  src: url("../fonts/LibreBaskerville-Bold.woff2") format("woff2"),
       url("../fonts/LibreBaskerville-Bold.ttf") format("truetype");
  font-weight: 700; font-style: normal; font-display: swap;
}

:root {
  --bg: #fff;
  --ink: #111;
  --ink-soft: #3a3a3a;
  --ink-muted: #9a9a9a;
  --rule: #c4c4c4;
  --tile-placeholder: #d7d3cc;
  /* Marker stroke behind Adam's own credits. Warm and low-contrast, so it
     reads as a highlight without putting a colour on a colourless page. */
  --highlight: #f2ead6;
  --accent: #4a5df9;

  --font: "Libre Baskerville", Georgia, "Times New Roman", serif;

  /* The signup form runs in sans, which reads as an interface rather than as
     part of the page's prose. Costs nothing to load: every face here is
     already on the device, so no extra font file is fetched. */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;

  /* Container + grid.
     Taken from the 1366px mockup: rail at x=255, tiles at x=420 (208 wide,
     110 gap), content ending at x=946. That's a 691px content block with a
     165px rail-and-gap in front of it. */
  --page-max: 731px;   /* 691 content + 2 x 20 padding */
  --rail-width: 110px;
  --rail-gap: 55px;
  --grid-col-gap: 110px;
  --grid-row-gap: 40px;

  /* How far a playing tile grows past its column on each side, and how
     long it takes. The gutter is 110px, so 34 a side has room to spare. */
  --tile-grow: 34px;
  --tile-grow-time: 720ms;

  /* Gutter between project stills. Used for BOTH axes so the vertical gaps
     match the horizontal ones. */
  --stills-gap: 10px;

  /* Search bar. Narrower than the content column; grows on hover/focus
     but stays inside --search-max. */
  --search-width: 300px;
  --search-max: 420px;

  --dim-opacity: 0.28;

  /* --ease decelerates (good for things arriving). --ease-inout ramps up
     and down at both ends (good for the twirl and the reveal that follows
     it, which should feel like one gesture). */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-inout: cubic-bezier(0.65, 0, 0.35, 1);

  --twirl-duration: 1000ms;
  --reveal-duration: 900ms;
  --scroll-duration: 1000ms;   /* read by site.js */
  --page-fade: 220ms;
}

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

/* Note: deliberately NOT using `scroll-behavior: smooth` here. Where the
   smooth-scroll animation isn't honoured, the scroll gets dropped
   entirely rather than falling back to a jump — so returning to the top
   silently stops working. A plain jump always lands. */
html {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.45;

}

/* Page transition — the CONTENT fades, the header does not. The nav tabs
   and wordmark are identical on every page, so leaving them out of the
   fade makes them appear to stay put across a navigation rather than
   blinking out and back.

   A fade-in inherently starts at opacity 0, so a stalled animation would
   leave the content blank — and omitting animation-fill-mode isn't enough,
   because the 0% keyframe still applies while the animation is *running*.
   site.js adds .page-shown shortly after load to cancel the animation and
   drop back to opacity 1; a no-op normally, a rescue if it ever stalls. */
main {
  animation: page-fade-in var(--page-fade) var(--ease);
}

body.page-shown main { animation: none; }

@keyframes page-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Applied by site.js just before navigating away. */
body.is-leaving main {
  opacity: 0;
  transition: opacity var(--page-fade) var(--ease);
}

body.menu-open { overflow: hidden; }

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* The one container every page shares. */
.shell {
  width: 100%;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 20px;
}


/* ==========================================================================
   Header
   ========================================================================== */

.site-header {
  padding-top: 38px;
  margin-bottom: 44px;
}

/* The project nav link carries two labels; only "Projects" shows by
   default. The "/ Back to projects" label is revealed on tablet project
   pages once scrolled (see the tablet block). */
.nav-lbl-back { display: none; }

.header-inner {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 30px;
}

.site-nav { display: flex; gap: 40px; }

.site-nav a {
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  padding-bottom: 2px;
  border-bottom: 1px solid transparent;
  transition: border-color 180ms ease;
}
.site-nav a:hover { border-bottom-color: var(--rule); }
.site-nav a[aria-current="page"] { border-bottom-color: var(--ink); }

/* Identity block: the name, plus the actions that drop out from under it. */
.identity { text-align: right; }

.wordmark {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  font-size: 0.88rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  display: inline-block;
  transform-style: preserve-3d;
}

/* One full revolution on the Y axis over 1.5s, easing in and out at each
   end so it starts and finishes gently rather than snapping. */
.wordmark.is-twirling { animation: twirl var(--twirl-duration) var(--ease-inout); }

@keyframes twirl {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(360deg); }
}

/* The reveal is paced to match the twirl. */
.identity-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 13px;
  margin-top: 11px;

  opacity: 0;
  visibility: hidden;
  transform: translateY(-12px);
  transition: opacity var(--reveal-duration) var(--ease-inout),
              transform var(--reveal-duration) var(--ease-inout),
              visibility 0s linear var(--reveal-duration);
}

.identity-actions.is-visible {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity var(--reveal-duration) var(--ease-inout),
              transform var(--reveal-duration) var(--ease-inout),
              visibility 0s;
}

.action-share {
  display: inline-flex;
  width: 15px;
  height: 15px;
  cursor: pointer;
  background: none;
  border: 0;
  padding: 0;
  transition: opacity 180ms ease;
}
.action-share img { width: 100%; height: 100%; object-fit: contain; }

.action-icon {
  display: inline-flex;
  width: 15px;
  height: 15px;
  transition: opacity 180ms ease;
}
.action-icon img { width: 100%; height: 100%; object-fit: contain; }

.action-share:hover,
.action-icon:hover { opacity: 0.55; }


/* --------------------------------------------------------------------------
   Phone header — hamburger + page title. No wordmark.
   -------------------------------------------------------------------------- */

.mobile-bar { display: none; }

.menu-toggle {
  appearance: none;
  background: none;
  border: 0;
  padding: 6px 4px;
  cursor: pointer;
  width: 34px;
  height: 30px;
  position: relative;
  z-index: 60;
}

/* Three bars that morph into an X. Same bars, repositioned. */
.menu-toggle .bar {
  display: block;
  width: 26px;
  height: 2px;
  background: var(--ink);
  border-radius: 1px;
  transition: transform 340ms var(--ease), opacity 200ms ease;
}
.menu-toggle .bar + .bar { margin-top: 6px; }

.menu-toggle[aria-expanded="true"] .bar:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] .bar:nth-child(2) { opacity: 0; transform: scaleX(0.4); }
.menu-toggle[aria-expanded="true"] .bar:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

.page-title {
  font-size: 1.32rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  margin: 0;
  font-weight: 400;
}

/* Full-screen menu overlay.

   The tint is deliberately light and the blur radius moderate: a heavy
   blur over a heavy white tint just averages out to a white screen, which
   is the thing this is meant to avoid. Keeping the blur softer means the
   shapes behind stay recognisable, which is what reads as "translucent"
   rather than "covered". */
.menu-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.18);
  backdrop-filter: blur(15px) saturate(1.25);
  -webkit-backdrop-filter: blur(15px) saturate(1.25);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 42px;

  opacity: 0;
  visibility: hidden;
  transition: opacity 300ms var(--ease), visibility 0s linear 300ms;
}

.menu-overlay.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 300ms var(--ease), visibility 0s;
}

.menu-overlay a {
  font-size: 1.5rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0;
  transform: translateY(12px);
}

.menu-overlay.is-open a {
  animation: menu-item-in 420ms var(--ease) forwards;
}
.menu-overlay.is-open a:nth-child(1) { animation-delay:  40ms; }
.menu-overlay.is-open a:nth-child(2) { animation-delay: 100ms; }
.menu-overlay.is-open a:nth-child(3) { animation-delay: 160ms; }
.menu-overlay.is-open a:nth-child(4) { animation-delay: 220ms; }

@keyframes menu-item-in {
  to { opacity: 1; transform: none; }
}


/* ==========================================================================
   Projects — search + filters
   ========================================================================== */

/* Sits at the right-hand edge of the content column and grows leftward
   on hover or focus. Width is animated on the row, not the input, so the
   rule underneath grows with it. */
.search-row {
  display: flex;
  align-items: center;
  gap: 10px;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 6px;
  margin-bottom: 16px;

  width: var(--search-width);
  max-width: 100%;
  margin-left: auto;
  transition: width 380ms var(--ease-inout), border-color 260ms ease;
}

/* On desktop/tablet the wrapper is transparent to layout — its children
   act as direct flex children of .search-row, so every rule above still
   applies to them unchanged. Phone re-activates it as the real bar. */
.search-bar { display: contents; }

.search-row:hover,
.search-row.is-active {
  width: var(--search-max);
  border-color: var(--ink-soft);
}

.search-input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  padding: 4px 2px;
  font: inherit;
  font-size: 0.82rem;
  color: var(--ink);
}
.search-input:focus { outline: none; }
.search-input::placeholder { color: var(--ink-muted); }

/* Suppress the browser's own clear button — it only shows on hover/focus
   and can't be styled. Replaced by .search-clear below. */
.search-input::-webkit-search-cancel-button,
.search-input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }

/* Our clear button. Shown whenever there's text, regardless of hover. */
/* Sized against the magnifier (17px): a little smaller here, matched
   exactly on phone — see the phone block. */
.search-clear {
  display: none;
  flex: none;
  width: 14px;
  height: 14px;
  margin-right: 10px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  opacity: 0.45;
  transition: opacity 180ms ease;
}
.search-clear img { width: 100%; height: 100%; object-fit: contain; }
.search-clear:hover { opacity: 0.85; }

.search-row.has-text .search-clear { display: block; }

/* Clicking the icon focuses the field, so it needs to look clickable.
   Indented from the right-hand edge rather than sitting flush against it. */
.search-icon {
  width: 17px;
  height: 17px;
  flex: none;
  margin-right: 8px;
  opacity: 0.75;
  cursor: pointer;
  transition: opacity 180ms ease;
}
.search-icon:hover { opacity: 1; }

.search-empty {
  font-size: 0.76rem;
  font-style: italic;
  color: var(--ink-muted);
  margin: 26px 0 0;
}
.search-empty[hidden] { display: none; }

/* --- Filters ------------------------------------------------------------- */

.filter-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.filter {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  /* Block, not the inline-block a button defaults to — otherwise it sits
     on the line box's baseline and starts ~6px below its own <li>. */
  display: block;
  font-family: inherit;
  font-weight: 400;
  color: var(--ink-muted);
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
  line-height: 1.5;

  /* Animating font-size is what makes the neighbours get pushed away
     smoothly — they're in normal flow, so they reflow every frame. */
  font-size: 0.68rem;
  transition: font-size 420ms var(--ease), color 300ms ease;
}

/* Hovering grows it slightly — a smaller version of what selecting does,
   so the affordance previews the result. */
.filter:hover {
  color: var(--ink-soft);
  font-size: 0.78rem;
}

/* Selected: darkens and grows further, so it reads as a heading.
   Wins over :hover because it comes later at equal specificity. */
.filter[aria-pressed="true"],
.filter[aria-pressed="true"]:hover {
  color: var(--ink);
  font-size: 0.92rem;
}

.filter-reset {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 0.75rem;
  font-style: italic;
  color: var(--ink-muted);
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
}
.filter-reset:hover { color: var(--ink); }

/* The reset lives in a wrapper that collapses to nothing when unused. */
.reset-wrap {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows 380ms var(--ease),
              opacity 300ms ease,
              margin-top 380ms var(--ease);
  margin-top: 0;
}
.reset-wrap > .reset-inner { overflow: hidden; min-height: 0; }

.filters.has-selection .reset-wrap {
  grid-template-rows: 1fr;
  opacity: 1;
  margin-top: 10px;
}

.reset-rule {
  width: 72px;
  height: 1px;
  background: var(--rule);
  border: 0;
  margin: 0 0 7px;
}


/* ==========================================================================
   Project grid
   ========================================================================== */

.project-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--grid-col-gap);
  row-gap: var(--grid-row-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.tile {
  position: relative;
  display: block;
  transition: opacity 320ms ease;
}

.tile[hidden] { display: none; }

.projects.is-hovering .tile:not(.is-active) { opacity: var(--dim-opacity); }

.tile-media {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  margin: 0 0 9px;
  background-color: var(--tile-placeholder);
  background-size: cover;
  background-position: center;
  transition: aspect-ratio var(--tile-grow-time) var(--ease-inout),
              margin var(--tile-grow-time) var(--ease-inout);
}

.tile-video {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  /* The box is animated into the clip's own shape, so cover crops nothing
     once it arrives — and during the animation it fills rather than
     leaving a white margin that appears and disappears again. */
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
}

/* No fade. The clip is swapped in at full opacity on the same frame the
   growth begins, so what you see is one movement — the tile opening out
   into the film — rather than a dissolve and a resize happening at once. */
.tile-video.is-playing { opacity: 1; }

/* --- The tile grows while its clip plays ---------------------------------
   A real layout change, not a transform: the row gets taller, so the tiles
   and captions below are pushed down rather than being overlapped.

   It opens out on all four sides at once, which is why this is done with
   negative margins and NOT with `width`. At rest the box has no width of
   its own — it's `auto`, filling the column — and `auto` is not a value
   that can be interpolated. Animating to an explicit width makes the box
   snap to full size on the first frame and then slide sideways as the
   margins catch up, and the jump also drops the height before the aspect
   ratio has caught up, so a wide clip appears to shrink on its way out.
   Margins are plain lengths, so all four sides move together.

   Mouse only. There's no hovering on a touch screen, so a phone would have
   no way to trigger this and no way to undo it. */
@media (hover: hover) and (pointer: fine) {
  .tile.is-playing .tile-media {
    aspect-ratio: var(--clip-ar, 16 / 9);
    margin-left: calc(var(--tile-grow) * -1);
    margin-right: calc(var(--tile-grow) * -1);
    /* Upward too, so it doesn't only unfold downwards. 9/16 of the
       sideways growth keeps the opening roughly square to the frame. */
    margin-top: calc(var(--tile-grow) * -0.5625);
  }

  /* It now spills past its own column. */
  .tile.is-playing { z-index: 2; }
}

@media (prefers-reduced-motion: reduce) {
  .tile-media { transition: none; }
}

.tile-caption { display: block; }

.tile-title {
  display: block;
  font-size: 0.72rem;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  line-height: 1.5;
}
.tile-subtitle {
  display: block;
  font-size: 0.7rem;
  font-style: italic;
  line-height: 1.5;
}
.tile-meta {
  display: block;
  font-size: 0.7rem;
  line-height: 1.5;
}

/* --- More projects ------------------------------------------------------- */

.more-projects { margin-top: 74px; }
.more-projects[hidden] { display: none; }

.more-heading {
  font-size: 0.76rem;
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin: 0 0 7px;
  padding-left: 34px;
}

.section-rule {
  height: 1px;
  background: var(--rule);
  border: 0;
  margin: 0 0 30px;
}


/* ==========================================================================
   Press
   ========================================================================== */

.section-heading {
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  margin: 0 0 9px;
  padding-left: 34px;
}
.section-heading--spaced { margin-top: 76px; }

.press-entry { margin-bottom: 24px; }

.press-title {
  margin: 0 0 3px;
  font-size: 0.74rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.press-line {
  margin: 0 0 2px;
  padding-left: 22px;
  font-size: 0.72rem;
  line-height: 1.55;
}
.press-link { text-decoration: underline; text-underline-offset: 2px; }


/* ==========================================================================
   Contact
   ========================================================================== */

/* max-content lets the contact column take exactly the width its longest
   line needs, so a single long address can't wrap and leave its two
   centred halves looking misaligned against the lines around it. The bio
   absorbs whatever width is left. minmax(0, …) keeps the column able to
   shrink if the viewport genuinely can't fit both, rather than overflowing. */
.contact-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, max-content);
  column-gap: 70px;
  align-items: start;
}

.prose--spaced { margin-top: 58px; }

.prose p {
  margin: 0 0 15px;
  font-size: 0.76rem;
  line-height: 1.62;
}

.portrait-frame {
  width: 100%;
  aspect-ratio: 3 / 2;
  margin-top: 22px;
  background-color: var(--tile-placeholder);
  background-size: cover;
  background-position: center;
}

.contact-column { text-align: center; }

.contact-block { margin-bottom: 24px; }

.contact-heading {
  margin: 0 0 3px;
  font-size: 0.74rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.contact-line {
  margin: 0 0 2px;
  font-size: 0.74rem;
  line-height: 1.55;
}

.agency-logo {
  margin: 26px auto 40px;
  width: 62%;
}
.logo-frame {
  width: 100%;
  aspect-ratio: 5 / 2;
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
}

/* On Contact the actions live at the foot of the page rather than under the
   wordmark, at every width. `[data-default-visible]` is only ever set on
   Contact, so the wordmark twirl — and the actions it reveals — still work
   as before on Projects, Press and Gramercy. */
.contact-actions-mobile { display: none; }

.identity-actions[data-default-visible] { display: none; }

.contact-actions-mobile {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 26px;
  margin-top: 52px;
}
.contact-actions-mobile .action-share,
.contact-actions-mobile .action-icon { width: 26px; height: 26px; }


/* ==========================================================================
   Gramercy
   ========================================================================== */

/* The mockup gives the form ~62% of the content column — noticeably wider
   than a default form. Held as a ratio so it scales with the page. */
/* One rounded box with the rules drawn inside it, rather than a stack of
   separately bordered inputs. The outer border carries the shape; the
   inputs only draw the lines that divide them. */
.signup {
  display: flex;
  width: 62%;
  min-width: 340px;
  max-width: 460px;
  margin: 26px auto 60px;
  align-items: stretch;
  border: 1.5px solid var(--ink);
  border-radius: 10px;
  overflow: hidden;                /* clips the inputs and button to the curve */
  transition: box-shadow 220ms ease;
  /* Inputs and the button both use `font: inherit`, so setting it once here
     carries to all of them. */
  font-family: var(--font-sans);
}
.signup:focus-within { box-shadow: 0 0 0 3px rgba(17, 17, 17, 0.10); }

/* A grid so two fields can share a row. The labels are visually-hidden and
   therefore absolutely positioned, which takes them out of the grid
   entirely — so the cells count inputs only, and nothing is thrown off. */
/* minmax(0, 1fr), not 1fr. A bare `1fr` is minmax(auto, 1fr), and an <input>
   carries an intrinsic width of about twenty characters that `auto` refuses
   to shrink below — so the two columns held themselves at ~174px each inside
   a 288px box, overflowed it, and got clipped. The zero minimum lets them
   divide the space they actually have, evenly. */
.signup-fields {
  flex: 1;
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  border-right: 1.5px solid var(--ink);
}

/* Dividers are drawn as inset shadows rather than borders so that every
   field is exactly the same height — a border would make the rows that
   have one 1.5px taller than the row that doesn't. */
.signup-input {
  grid-column: 1 / -1;                             /* full width by default */
  min-width: 0;
  border: 0;
  box-shadow: inset 0 -1.5px 0 var(--ink);
  padding: 9px 12px;
  font: inherit;
  font-size: 0.74rem;
  text-align: center;
  color: var(--ink);
  background: #fff;
  transition: background 200ms ease;
}

.signup-input--half { grid-column: span 1; }       /* two to a row */
/* The left-hand one of a pair also carries the divider between them. */
.signup-input--half:nth-of-type(1) {
  box-shadow: inset 0 -1.5px 0 var(--ink), inset -1.5px 0 0 var(--ink);
}
.signup-input:nth-last-of-type(1) { box-shadow: none; }
.signup-input::placeholder { color: #a8a8a8; transition: color 160ms ease; }

/* The prompt has done its job once you're typing in the box. */
.signup-input:focus::placeholder { color: transparent; }

.signup-input:focus {
  outline: none;
  background: #fafafa;
  /* Fades the caret in and out rather than the browser's hard blink. */
  animation: caret-fade 1.15s ease-in-out infinite;
}

@keyframes caret-fade {
  0%, 100% { caret-color: var(--ink); }
  50%      { caret-color: transparent; }
}

/* A caret that fades is a nicety; a caret that vanishes is a bug. Anyone
   who has asked for less motion keeps the browser's own. */
@media (prefers-reduced-motion: reduce) {
  .signup-input:focus { animation: none; }
}

.signup-button {
  border: 0;
  padding: 8px 20px;
  font: inherit;
  font-size: 0.84rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #fff;
  background: var(--accent);
  cursor: pointer;
  white-space: nowrap;
  min-width: 138px;
  transition: background 180ms ease;
}
.signup-button:hover { background: #3a4de9; }
.signup-button:disabled { opacity: 0.6; cursor: default; }

/* The label moves, not the button. Shifting the button itself would slide
   it out from under the form's clipped corner and show a sliver of the
   background at the top edge. */
.signup-button span {
  display: block;
  transition: transform 130ms var(--ease);
}
.signup-button:hover span  { transform: translateY(1px); }
.signup-button:active span { transform: translateY(2px); }
.signup-button:disabled:hover span { transform: none; }

.signup-status {
  max-width: 460px;
  margin: -46px auto 54px;
  font-size: 0.72rem;
  text-align: center;
  color: var(--ink-muted);
  min-height: 1.4em;
}
.signup-status.is-error { color: #b3261e; }

.patron-list {
  list-style: none;
  margin: 6px 0 0;
  padding-left: 22px;
}
.patron-list li { font-size: 0.74rem; line-height: 1.62; }


/* ==========================================================================
   Project pages

   A project page is just a stack of blocks. Mix and match per project —
   that's what makes the format flexible. See docs/PROJECTS.md.
   ========================================================================== */

.project-page { padding-bottom: 130px; }

/* Title left (name + strapline), meta pinned right — as in the mockup. */
.project-header {
  margin-bottom: 30px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
}

.project-name {
  font-size: 1.02rem;
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin: 0 0 4px;
}
.project-strap {
  font-size: 0.82rem;
  font-style: italic;
  margin: 0;
}
/* Year on top, duration/format beneath it. Two short lines read apart at a
   glance where "Short Film 33’, 2026" ran together as one. */
.project-meta {
  font-size: 0.76rem;
  margin: 0;
  white-space: nowrap;
  text-align: right;
}
.project-meta span { display: block; }
.project-format { color: var(--ink-soft); }

/* --- block: caption line under the video -------------------------------- */
.block-caption {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 6px 30px;
  margin: 10px 0 34px;
  font-size: 0.7rem;
  color: var(--ink-soft);
}
.block-caption span { white-space: nowrap; }

/* --- block: video (Vimeo) ------------------------------------------------ */
.block-video {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--tile-placeholder);
  margin-bottom: 26px;
}
.block-video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* --- block: hero (silent teaser loop) ------------------------------------
   Takes its aspect-ratio inline from the clip's own dimensions, so the
   column holds the right shape before a single byte has downloaded and
   the page never jumps when it arrives. */
.block-hero {
  position: relative;
  aspect-ratio: 16 / 9;   /* replaced inline per project */
  background: var(--tile-placeholder);
  margin-bottom: 26px;
  overflow: hidden;
}
.block-hero .hero-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;      /* the box is already the clip's shape */
  display: block;
}

/* A fallback, not a fixture. The clip starts on its own when it scrolls
   into view, so the button is hidden until something refuses to autoplay
   — iOS low-power mode, say, or reduced-motion. */
.hero-play {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: none;
  align-items: center;
  justify-content: center;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  opacity: 1;
  transition: opacity 300ms var(--ease);
}
.block-hero.needs-press .hero-play { display: flex; }
.block-hero.is-playing .hero-play { opacity: 0; pointer-events: none; }

.hero-play-mark {
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.86);
  position: relative;
  transition: transform 260ms var(--ease), background 260ms ease;
}
.hero-play:hover .hero-play-mark {
  transform: scale(1.08);
  background: rgba(255, 255, 255, 0.96);
}
/* The triangle, drawn rather than shipped as another icon file. */
.hero-play-mark::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-42%, -50%);
  border-style: solid;
  border-width: 8px 0 8px 13px;
  border-color: transparent transparent transparent var(--ink);
}

/* --- block: crew one-liners --------------------------------------------- */
.block-crew {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 44px;
  margin-bottom: 30px;
}
.block-crew p { margin: 0; font-size: 0.72rem; line-height: 1.5; }

/* --- block: body copy ----------------------------------------------------
   Full column width, so the description squares off against the video
   above it rather than stopping short of it. */
.block-text { margin-bottom: 40px; }
.block-text p { margin: 0 0 13px; font-size: 0.76rem; line-height: 1.62; }

/* --- block: stills -------------------------------------------------------
   One gap value drives both axes, so the vertical space between rows is
   exactly the horizontal space between columns. */
.block-stills {
  margin-bottom: var(--stills-gap);
  display: grid;
  gap: var(--stills-gap);
}
.block-stills--full { grid-template-columns: minmax(0, 1fr); }
.block-stills--two  { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.block-stills--three{ grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* An <img>, so it can be lazy-loaded — object-fit does the cropping that
   background-size: cover used to. The background colour still shows while
   the picture is on its way. */
.still {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: center;
  background-color: var(--tile-placeholder);
  cursor: pointer;
}
.still--tall { aspect-ratio: 2 / 3; }
.still--square { aspect-ratio: 1 / 1; }
.still--wide { aspect-ratio: 16 / 9; }

/* Click-to-enlarge: the promoted still takes the full row and a wide crop;
   siblings reflow around it. The FLIP animation lives in site.js — the grid
   itself changes instantly, then tiles are played from their old boxes. */
.block-stills .still.is-big {
  grid-column: 1 / -1;
  aspect-ratio: 16 / 9;
}
.block-stills--three .still.is-big { aspect-ratio: 2 / 1; }

/* --- block: pull quote --------------------------------------------------- */
.block-quote {
  margin: 0 0 44px;
  max-width: 44ch;
  font-size: 0.94rem;
  font-style: italic;
  line-height: 1.5;
}
.block-quote footer {
  margin-top: 8px;
  font-size: 0.7rem;
  font-style: normal;
  color: var(--ink-muted);
}

/* --- block: credits -------------------------------------------------------
   Two columns of role -> name rows, grouped with gaps, centred on the page
   as in the mockup. Each row is itself a two-column grid: the role sits
   right-aligned against the gutter, the name left-aligned beside it. */
/* One column by default. Roles now sit on single lines, and a two-up split
   needs about 670px to hold both groups without them colliding — more than
   the tablet's 620px content column has. Desktop widens it back to two. */
.block-credits {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: 30px;
  margin: 60px auto 0;
}

/* The group — not each row — is the grid, so the role and name columns are
   sized once against the longest entry and every row lines up on the same
   two edges. `display: contents` lets each .credit contribute its two
   children directly. Columns are content-sized, so nothing wraps: a role
   like "Wardrobe, Hair and Makeup" stays on one line. */
.credit-group {
  display: grid;
  grid-template-columns: max-content max-content;
  justify-content: center;
  column-gap: 14px;
  row-gap: 4px;
  margin-bottom: 20px;
}
.credit-group:last-child { margin-bottom: 0; }

.credit { display: contents; }

/* One person, several jobs: the roles stack and the name is printed once,
   centred against the whole stack so it plainly belongs to all of them. */
.credit-roles { text-align: right; align-self: center; }
.credit-role {
  display: block;
  text-align: right;
  font-size: 0.66rem;
  color: var(--ink-soft);
  line-height: 1.45;
  white-space: nowrap;
}
.credit-name {
  text-align: left;
  align-self: center;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  line-height: 1.45;
  white-space: nowrap;
}

/* Adam's own credits, picked out from the rest of the crew with a marker
   stroke rather than a change of weight.

   width:max-content makes each line hug its own text — without it these are
   grid items filling the whole column, and the highlight would run as a
   band across the empty space too. The negative margins let the colour
   extend a few pixels past the glyphs without moving the text itself. */
.credit--self .credit-role,
.credit--self .credit-name {
  background: var(--highlight);
  width: max-content;
  padding: 0 3px;
  margin: 0 -3px;
}
.credit--self .credit-role {
  color: var(--ink);
  margin-left: auto;   /* stays right-aligned against the gutter */
}

/* --------------------------------------------------------------------------
   Project-page navigation: "/ Back to projects" and "Back to top".

   Two homes for the same two controls:
     .proj-rail  — desktop only, a sticky left rail (see desktop block).
     .proj-foot  — tablet/phone, static links after the credits.
   Base styling here is shared; visibility is switched per breakpoint.
   -------------------------------------------------------------------------- */

.proj-rail { display: none; }          /* becomes the sticky rail on desktop */

.proj-foot {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: center;
  margin-top: 54px;
}

.rail-back {
  font-size: 0.74rem;
  align-self: flex-start;
}
.proj-foot .rail-back { align-self: center; }
.rail-back:hover { text-decoration: underline; text-underline-offset: 3px; }

.rail-top {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.72rem;
  color: var(--ink-muted);
  transition: color 200ms ease;
}
.rail-top img {
  width: 12px;
  height: 12px;
  object-fit: contain;
  opacity: 0.5;
  transition: opacity 200ms ease, transform 260ms var(--ease);
}
.rail-top:hover { color: var(--ink-soft); }
.rail-top:hover img { opacity: 0.78; transform: translateY(-2px); }


/* ==========================================================================
   Return to top
   ========================================================================== */

.to-top {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;

  width: max-content;
  margin: 60px auto 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  font-family: inherit;

  /* Hidden until the page bottom is reached, then it drifts up in. */
  opacity: 0;
  transform: translateY(14px);
  pointer-events: none;
  transition: opacity 520ms var(--ease), transform 520ms var(--ease);
}

.to-top.is-visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

/* The arrow is a solid-black PNG, so its colour is set by how far it's
   faded against the white page. Black at 40% reads as #999 — matching
   --ink-muted on the label below it. 22% short of black gives #3a3a3a,
   matching --ink-soft, so the two stay in step on hover too. */
.to-top img {
  width: 16px;
  height: 16px;
  object-fit: contain;
  opacity: 0.4;
  transition: opacity 200ms ease, transform 260ms var(--ease);
}

.to-top:hover img { opacity: 0.78; transform: translateY(-3px); }
.to-top:hover .to-top-label { color: var(--ink-soft); }

/* The label only appears on hover. It's laid out at all times so its
   arrival doesn't shift the arrow above it. */
.to-top-label {
  font-size: 0.64rem;
  color: var(--ink-muted);
  white-space: nowrap;
  opacity: 0;
  transform: translateY(-3px);
  transition: opacity 240ms var(--ease), transform 240ms var(--ease),
              color 200ms ease;
}

.to-top:hover .to-top-label,
.to-top:focus-visible .to-top-label {
  opacity: 1;
  transform: none;
}

@media (max-width: 599px) {
  .block-stills--three { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .block-crew { gap: 4px 24px; }

  /* The project name already shows in the mobile header bar, so drop the
     repeat at the top of the body. Strap + meta stay as the lead-in. */
  .project-header .project-name { display: none; }
}


/* Credits below desktop: one column, and — importantly — ONE grid.
   The build splits the crew into two halves so they can sit side by side on
   a wide screen. Once those halves stack, each was still sizing its own
   columns off its own longest role and longest name, so the two ran on
   slightly different gutters and the list looked broken in the middle.
   Flattening both halves into the same grid puts every row on one gutter. */
@media (max-width: 1023px) {
  .block-credits {
    display: grid;
    grid-template-columns: max-content max-content;
    justify-content: center;
    column-gap: 14px;
    row-gap: 4px;
    max-width: 100%;
  }
  .credits-col,
  .credit-group { display: contents; }
}


/* ==========================================================================
   Layout: DESKTOP (>= 1024px) — filter rail on the left
   ========================================================================== */

@media (min-width: 1024px) {
  .layout {
    display: grid;
    grid-template-columns: var(--rail-width) minmax(0, 1fr);
    column-gap: var(--rail-gap);
    align-items: start;
    padding-bottom: 150px;
  }

  /* Press / Contact / Donate have no rail, and the mockups show them using
     the full page width rather than the narrower projects column. */
  .layout--single { grid-template-columns: minmax(0, 1fr); }

  /* Explicit placement. The rail sits in row 2 alongside the projects
     rather than spanning from row 1, so the top of "Narrative" lines up
     with the top of the first tile. */
  /* -5px lifts the cap-height of "Narrative" up to the tile's top edge.
     Box-aligning the two leaves the word looking low, because the line
     box carries half-leading and ascender space above the glyph. */
  .filters {
    grid-column: 1;
    grid-row: 2;
    position: sticky;
    top: 40px;
    margin-top: -5px;
  }
  .search-row { grid-column: 2; grid-row: 1; }
  .projects   { grid-column: 2; grid-row: 2; }

  /* Nudged up by roughly its own height. `position: relative` rather than
     a negative margin, so the tiles below don't move with it. */
  .search-row {
    position: relative;
    top: calc(0px - var(--search-h, 33px));
  }

  /* On Projects the arrow is a direct child of a two-column grid, so
     without this it lands in the rail column. Scoped to that layout only:
     on the single-column pages the rows are auto-placed, and forcing a row
     here drags the button up into the middle of the content. */
  .layout:not(.layout--single) > .to-top {
    grid-column: 1 / -1;
    grid-row: 3;
  }

  /* Project pages are IDENTICAL to the tablet treatment at this width:
     one centred column, no sticky header, no rail, footer links at the end.
     Everything project-specific is deliberately neutralised here so the
     shared (tablet) rules are what actually apply. */
  .layout--single.project-page { grid-template-columns: minmax(0, 1fr); }
  .project-page > .project-header,
  .project-page > .block-credits,
  .project-page > .proj-scroll { grid-column: 1; }

  .project-page > .proj-scroll {
    display: block;
    margin-bottom: 0;
  }

  /* Back to two columns: the 691px content column has room for both
     groups now that no role wraps. */
  .block-credits { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .proj-rail { display: none; }   /* footer links carry the navigation */
  .proj-foot { display: flex; }
  .nav-top   { display: none; }

  .filter-list li + li { margin-top: 11px; }
}


/* ==========================================================================
   Layout: TABLET (600–1023px) — filters in a horizontal row
   ========================================================================== */

@media (min-width: 600px) and (max-width: 1023px) {
  :root {
    --page-max: 660px;
    --grid-col-gap: 88px;
    --grid-row-gap: 44px;
  }

  .layout { padding-bottom: 120px; }

  .site-header { padding-top: 30px; margin-bottom: 34px; }
  .site-nav { gap: 34px; }

  /* Search and filters sit in a narrower centred column than the tiles,
     as drawn in the tablet mockup. The search is narrower again, and
     grows within that column on hover/focus. */
  .search-row {
    width: 260px;
    max-width: 100%;
    margin: 0 auto 12px;
  }

  .search-row:hover,
  .search-row.is-active { width: 400px; }

  .filters {
    max-width: 400px;
    margin: 0 auto 36px;
  }

  .filter-list {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
  }

  /* Selected item grows and pushes its neighbours sideways. */
  .filter[aria-pressed="true"] { font-size: 1rem; }

  .reset-wrap { text-align: left; }

  /* Tablet keeps the plain static header and the footer links — the
     sticky Projects -> Back morph is desktop-only (see desktop block). */
  .nav-top { display: none; }
}


/* ==========================================================================
   Layout: PHONE (< 600px)
   ========================================================================== */

@media (max-width: 599px) {
  :root {
    --page-max: 100%;
    --grid-row-gap: 30px;
  }

  .shell { padding: 0 22px; }

  /* Swap the desktop header for the hamburger bar. */
  .header-inner { display: none; }

  .mobile-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
  }

  /* The header stays put as you scroll, with the search bar and page
     title travelling with it. Content fades out underneath rather than
     colliding with it — see .site-header::after below. */
  .site-header {
    position: sticky;
    top: 0;
    z-index: 40;
    padding-top: 22px;
    margin-bottom: 0;
    background: #fff;
  }

  /* The fade below the sticky zone. Purely decorative — it must never eat
     clicks on the tiles beneath it. */
  .site-header::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    height: 34px;
    pointer-events: none;
    background: linear-gradient(to bottom, #fff 0%, rgba(255, 255, 255, 0) 100%);
  }

  /* Per the mockups, the phone projects page is search-only. */
  .filters { display: none; }

  /* The search bar scrolls away with the content — it is NOT sticky. The
     masking strip and fade it used to need while pinned are gone with it;
     the header keeps its own fade above (see .site-header::after). */
  .search-row {
    display: block;
    position: static;
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 22px 0 0;       /* margin from the PROJECTS title above */
    border: 0;
    background: none;
  }

  /* The visible bar: re-activated from display:contents, centred, 72% wide,
     carrying the line. */
  .search-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 72%;
    max-width: 100%;
    margin: 0 auto;
    padding-bottom: 5px;
    border-bottom: 1px solid var(--rule);
    transition: width 380ms var(--ease-inout),
                border-color 260ms ease,
                padding-bottom 260ms var(--ease-inout);
  }
  /* The bar widens, the row does not. Without this the base rule wins on
     specificity (.search-row.is-active beats .search-row) and pins the row
     to --search-max — 420px, which is wider than the phone it's on. */
  .search-row:hover,
  .search-row.is-active { width: 100%; max-width: 100%; }

  .search-row.is-active .search-bar { width: 100%; border-color: var(--ink-soft); }

  /* Note: the bar no longer condenses on scroll — that existed only to
     shrink the frozen header, and the bar isn't frozen any more. */

  /* Matched to the magnifier at this size — easier to hit with a thumb. */
  .search-clear { width: 17px; height: 17px; }

  /* Breathing room between the search line and the first tile. */
  .projects { margin-top: 34px; }

  .layout { padding-top: 26px; }

  /* With the menu open the header must stop being an opaque white strip
     sitting on top of the blur — but the toggle still has to be clickable
     above the overlay. So the header rises above it and everything in it
     except the toggle gets out of the way. */
  body.menu-open .site-header {
    z-index: 60;
    background: transparent;
  }
  body.menu-open .site-header::after { opacity: 0; }
  body.menu-open .page-title,
  body.menu-open .search-row { opacity: 0; pointer-events: none; }

  .page-title,
  .search-row { transition: opacity 220ms var(--ease), width 380ms var(--ease-inout); }

  .project-grid { grid-template-columns: minmax(0, 1fr); }

  .more-heading,
  .section-heading { padding-left: 0; }

  .layout { padding-bottom: 90px; }

  /* Contact stacks, and reorders: agency contacts (US, then UK/EU) come
     first, then the portrait, then the bio. Done with flex `order` rather
     than by moving the markup, so desktop keeps its two-column reading
     order. Nothing in these blocks is focusable, so visual order and tab
     order can't disagree here. */
  .contact-layout {
    display: flex;
    flex-direction: column;

    /* The desktop rule sets align-items: start. In a flex column that
       shrink-wraps each child to its content width and pins it left, so
       the centred text ends up off-centre on the page. Stretch makes the
       blocks full width, and their text-align: center then reads as
       centred against the page. */
    align-items: stretch;

    /* All vertical spacing here is done with explicit margins instead, so
       the two gaps around the agency logos can be matched exactly. */
    row-gap: 0;

    /* Room between the CONTACT header and the first block. */
    margin-top: 30px;
  }

  .contact-layout > .contact-column { order: 1; }

  /* The bio + portrait wrapper. Flipped internally so the photo leads. */
  .contact-layout > :not(.contact-column) {
    order: 2;
    display: flex;
    flex-direction: column;
  }
  .contact-layout > :not(.contact-column) > .portrait-frame { order: 1; }
  .contact-layout > :not(.contact-column) > .prose { order: 2; }

  /* 74% left a lot of dead space between the contact blocks while the
     logo files are still missing, and reads oversized once they land.

     The margins here set the two gaps that bracket each logo — the one
     between US Narrative and UK/EU Commercial, and the one between
     UK/EU Narrative and the portrait. 26px above comes from the preceding
     .contact-block's own margin-bottom, so 23px below gives 26 + 63 + 23
     = 112px, matched on both. */
  .agency-logo {
    width: 50%;
    margin: 0 auto 23px;
  }

  /* Centred, with room before the bio that now follows it. */
  .portrait-frame {
    width: 62%;
    margin: 0 auto 34px;
  }

  .signup { max-width: 100%; }
  .signup-status { max-width: 100%; }
}


/* ==========================================================================
   Reduced motion
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  main { animation: none; }
  body.is-leaving main { opacity: 1; transition: none; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}


/* ==========================================================================
   Added for the content-driven build
   ========================================================================== */

/* Title + strapline group in the project header (meta sits opposite it). */
.project-header-main { min-width: 0; }

/* Line under the credits, e.g. a sponsor or grant acknowledgement. */
.project-footnote {
  max-width: 640px;
  margin: 26px auto 0;
  text-align: center;
  font-size: 0.66rem;
  font-style: italic;
  color: var(--ink-soft);
}

/* Placeholder shown when a section has no entries yet (e.g. Press). */
.empty-note {
  margin: 0;
  font-size: 0.76rem;
  font-style: italic;
  color: var(--ink-muted);
}


/* ==========================================================================
   Layout: WIDE DESKTOP (≥1100px) — project-page navigation moves to a rail
   ==========================================================================
   "/ Back to projects" and "Back to top" move out of the page foot and into
   the left margin, right-justified so they align against the content column.

   The block is pulled left by exactly the rail's own width plus its gap, so
   the rail lands in the margin beside the page and the content column keeps
   its full width and its original position — the hero and stills don't move.

   1150px rather than 1024 because that pull needs somewhere to go: it wants
   176px of margin, and a 731px page doesn't clear that until about 1085.
   Below this width the footer links carry the navigation as before. */

@media (min-width: 1150px) {
  :root {
    --proj-rail-w: 110px;
    --proj-rail-gap: 66px;
  }

  .project-page > .proj-scroll {
    display: grid;
    grid-template-columns: var(--proj-rail-w) minmax(0, 1fr);
    column-gap: var(--proj-rail-gap);
    align-items: start;
    margin-left: calc(-1 * (var(--proj-rail-w) + var(--proj-rail-gap)));
  }

  .proj-rail {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 10px;
    text-align: right;
    position: sticky;
    top: 140px;
  }

  /* Beats the shared `align-self: flex-start` used by the foot layout. */
  .proj-rail .rail-back { align-self: flex-end; }

  /* The rail is sticky and always in reach, so repeating the pair at the
     end of the page would just be the same two controls twice. */
  .proj-foot { display: none; }
}
