@font-face {
  font-family: "Helvetica Now Display";
  src: url("/HelveticaNowDisplay/HelveticaNowDisplayMedium.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
}

@font-face {
  font-family: "Helvetica Now Display";
  src: url("/HelveticaNowDisplay/HelveticaNowDisplayBold.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

@font-face {
  font-family: "Helvetica Now Text";
  src: url("/HelveticaNowText/HelveticaNowText.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Helvetica Now Text";
  src: url("/HelveticaNowText/HelveticaNowTextMedium.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
}

/* Tokens */

:root {
  color-scheme: light;

  --bg: #FFFFFF;
  --ink: #000000;
  --muted: #ABABAB;
  --color-line: rgba(0, 0, 0, 0.1);
  --color-divider: #000000;

  /* Shows under a poster while it loads. */
  --surface: #F7F7F7;

  /* #C5C5C5 and #F0F0F0 over white; alpha so they hold over either theme. */
  --scroll-thumb: rgba(0, 0, 0, 0.23);
  --scroll-track: rgba(0, 0, 0, 0.06);
  --scroll-height: var(--dock-progress-height);

  --font-display: "Helvetica Now Display", -apple-system, BlinkMacSystemFont,
      Helvetica, Arial, sans-serif;

  --font-text: "Helvetica Now Text", -apple-system, BlinkMacSystemFont,
      Helvetica, Arial, sans-serif;
  --color-accent: #007AFF;
  --source-size: 16px;

  /* Frosted glass */
  --glass-bg: rgba(255, 255, 255, 0.6);
  --glass-backdrop: blur(20px) saturate(190%) brightness(1.05);
  --glass-border: var(--color-line);
  --glass-rim:
      inset 0 1px 0 rgba(255, 255, 255, 0.7),
      inset 0 -1px 1px rgba(255, 255, 255, 0.18);
  --glass-shadow: var(--glass-rim), 0 8px 24px rgba(0, 0, 0, 0.06);

  /* 96px at 1440, floors at 24px */
  --page-x: clamp(24px, 6.667vw, 96px);
  --page-y: clamp(20px, 2.222vw, 32px);

  --logo-width: 200px;

  --logo-height: calc(var(--logo-width) * 57 / 200);
  --header-height: calc(var(--logo-height) + var(--page-y) * 2);

  /* 1392 = 640 poster + 24 gap + 728 text. Also 3 × 448 cards + 2 × 24 gaps.
     640 is the ceiling: poster files are 1280px, so a wider column resamples
     and goes soft at 2x. Re-export the posters before widening past this. */
  --content-width: 1392px;

  /* One 8-point ladder, tightest to widest. Spacing between cards is twice
     the widest gap inside one, which is what keeps a title reading as part
     of its own poster. */
  --gap-card: 8px;
  --gap-poster: 16px;
  --gap-col: 24px;
  --gap-row: 32px;
  /* The poster page's rhythm: 24px at 1440, easing to 16px by 960. */
  --gap-detail: clamp(16px, 1.667vw, 24px);
  /* The CTA opts out of that easing. Its type never scales, so padding that
     shrank would change the button's proportion rather than its size. */
  --cta-pad: 24px;

  /* Two sizes carry the whole site. Legibility is absolute, so neither
     scales with the viewport — the layout reflows around them instead. */
  --type-body: 14px;
  --line-body: 18px;
  --type-heading: 20px;
  --line-heading: 24px;
  --line-prose: 26px;
  --prose-width: 728px;

  --avatar-size: 40px;
  --cast-avatar-size: 60px;
  /* Matches the line it sits on, so an inline badge can't grow the line box. */
  --rating-size: var(--line-body);
  /* Padding twice plus one line of type. Known, so the dock CTA can
     collapse to nothing on a transition. */
  --dock-buy-height: calc(var(--gap-poster) * 2 + var(--gap-card) + var(--line-body));

  /* Dock panel. Every value below is its 1440px measurement over 280, so the
     whole panel scales as one object. --dock-size is the only edit needed. */
  --dock-size: clamp(200px, 19.444vw, 280px);
  --dock-gap: calc(var(--dock-size) * 0.0571);          /* 16 */
  --dock-pad-y: var(--dock-gap);                        /* 16 */
  --dock-radius: calc(var(--dock-size) * 0.1);          /* 28 */
  --dock-logo-width: calc(var(--dock-size) * 0.6571);   /* 184 */
  --dock-logo-height: calc(var(--dock-logo-width) * 57 / 200);
  --dock-menu-size: calc(var(--dock-size) * 0.1143);    /* 32 */
  --dock-foot-size: calc(var(--dock-size) * 0.05);      /* 14 */
  --dock-foot-line: calc(var(--dock-size) * 0.0643);    /* 18 */
  --dock-toggle-gap: calc(var(--dock-size) * 0.0286);   /* 8 */
  --dock-icon-size: max(18px, var(--dock-foot-line));   /* 18, floored */

  /* Change this alone to add or remove a menu item. */
  --dock-menu-count: 3;

  /* The remainder of the height budget, so the panel is exactly --dock-size
     at every width whatever else changes. */
  --dock-menu-line: calc(
      (var(--dock-size)
        - var(--dock-pad-y) * 2
        - var(--dock-logo-height)
        - var(--dock-icon-size)
        - var(--dock-foot-line)
        - var(--dock-gap) * 3
      ) / var(--dock-menu-count)
  );

  --dock-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  --dock-progress-height: 5px;
  --dock-progress-gap: 10px;
  /* Clears the panel and its progress bar. */
  --dock-clearance: calc(
      var(--dock-size) +
      var(--dock-progress-height) +
      var(--dock-progress-gap) +
      var(--dock-bottom) +
      24px
  );

  --ui-ms: 200ms;
  --ui-ease: ease;
  --chrome-ms: 600ms;
  --enter-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Only what differs. --glass-border resolves against --color-line at use. */
[data-theme="dark"] {
  color-scheme: dark;

  --bg: #000000;
  --ink: #E7E9EA;
  --muted: #71767B;
  --color-line: rgba(255, 255, 255, 0.1);
  --color-divider: #FFFFFF;
  --surface: #0D0D0D;
  --scroll-thumb: rgba(255, 255, 255, 0.23);
  --scroll-track: rgba(255, 255, 255, 0.06);

  --glass-bg: rgba(0, 0, 0, 0.55);
  --glass-backdrop: blur(20px) saturate(190%) brightness(1.1);
  --glass-rim:
      inset 0 1px 0 rgba(255, 255, 255, 0.14),
      inset 0 -1px 1px rgba(255, 255, 255, 0.05);
  --glass-shadow: var(--glass-rim), 0 8px 24px rgba(0, 0, 0, 0.35);
}

/* Base */

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

/* Reserved permanently: locking scroll must not shift the centred layout. */
html { scrollbar-gutter: stable; }

/* Both elements — iOS ignores the lock on body alone. */
.is-locked,
.is-locked body { overflow: hidden; }

body {
  margin: 0;
  background-color: var(--bg);
  color: var(--ink);
  font-family: var(--font-display);
  letter-spacing: -0.2px;
  -webkit-font-smoothing: antialiased;
  transition: color var(--ui-ms) var(--ui-ease), background-color var(--ui-ms) var(--ui-ease);
}

img { display: block; }

a { text-decoration: none; }

::selection {
  background-color: var(--ink);
  color: var(--bg);
}

/* Accent, not ink: must stay legible against text of either colour. */
:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Frame */

.frame {
  padding-top: var(--page-y);
  padding-bottom: calc(var(--page-y) + var(--dock-clearance));
}

/* One box, four users. Navigating between the grid and a poster must not move
   the margins, so they share the measurement rather than repeating it. */
.grid,
.main,
.dock-rail,
.details__content {
  max-width: calc(var(--content-width) + var(--page-x) * 2);
  margin-inline: auto;
  padding-inline: var(--page-x);
}

/* Header */

/* Derived from the logo so Details can clear it to the pixel. Not sticky —
   the logo hands off to the dock instead. */
.header {
  height: var(--header-height);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--page-x);
}

.header__logo {
  width: var(--logo-width);
  height: var(--logo-height);
  object-fit: contain;
}

/* Hidden, not just faded — an invisible link would still be tabbable. */
.header a {
  transition:
      opacity var(--ui-ms) var(--ui-ease),
      visibility var(--ui-ms) var(--ui-ease);
}

.header.is-handed-off a {
  opacity: 0;
  visibility: hidden;
}

/* Theme pairing */

[data-theme="light"] .is-dark-only,
[data-theme="dark"] .is-light-only { display: none; }

/* Grid */

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

/* Hover */

/* Every text and button CTA fades to muted. Icon controls carry their own
   state and are not in here. */
.card__title a:hover,
.card__action:hover,
.cta a:hover,
.dock-buy:hover,
.dock-menu a:hover,
.dock-menu button:hover,
.details__close:hover,
.source__cta:hover { color: var(--muted); }

@media (max-width: 1200px) {
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* row-gap is halved: the visible gap is this plus the card's own 16px bottom
   padding, so 32 rather than 48. One column reads as flow, and 48 breaks the
   scroll. */
@media (max-width: 600px) {
  .grid {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--gap-poster);
  }
}

/* Card */

.card {
  display: flex;
  flex-direction: column;
}

.card__title a {
  color: inherit;
  transition: color var(--ui-ms) var(--ui-ease);
}

/* Block, or an inline image sits on a baseline and the line box's descender
   space appears under the poster. Only bites where the poster is wrapped in
   an anchor — a bare img is a flex item and already blockified. Never
   cropped: column width is the only thing driving size. The fill shows only
   while the image loads; the poster covers it exactly. */
.card__poster {
  display: block;
  width: 100%;
  height: auto;
  background-color: var(--surface);
}

.card__details {
  display: flex;
  flex-direction: column;
  gap: var(--gap-card);
  padding-block: var(--gap-poster);
}

.card__title {
  margin: 0;
  font-size: var(--type-heading);
  line-height: var(--line-heading);
  font-weight: 700;
}

.card__name,
.card__meta,
.card__action {
  font-size: var(--type-body);
  line-height: var(--line-body);
}

.card__director {
  display: flex;
  align-items: center;
  gap: var(--gap-card);
  padding-block: var(--gap-card);
}

/* Flex items refuse to shrink below their content by default, which lets a
   long meta line push past the card. Stacking needs no rule — two block
   paragraphs with no margin already sit flush. */
.card__text { min-width: 0; }

.card__name {
  margin: 0;
  font-weight: 700;
}

.card__avatar {
  flex: none;
  width: var(--avatar-size);
  height: var(--avatar-size);
  border-radius: 50%;
  object-fit: cover;
}

/* Flex so the badge can drop to its own line flush left: column-gap applies
   only between items on a line, so a wrapped badge carries no indent. A
   margin would survive the break and indent it. */
.card__meta {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--gap-card);
  color: var(--ink);
}

/* The text face at Regular, for data and specification copy. Classed
   paragraphs in the panel — the lead and the headings — stay on the display
   face, so adding a class to any of the others drops it out of this rule. */
.card__meta,
.meta__value,
.cast__role,
.detail__description,
.notfound__body,
.dock-copyright,
.details__content p:not([class]),
.details__content li {
  font-family: var(--font-text);
  font-weight: 400;
  letter-spacing: -0.3px;
}

/* Never shrinks: the text wraps first, then the badge drops below it. */
.card__rating {
  flex: none;
  width: var(--rating-size);
  height: var(--rating-size);
}

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

.card__action {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-card);
  font-weight: 700;
  color: var(--ink);
  transition: color var(--ui-ms) var(--ui-ease);
}

/* Pure white or black, not --color-line: that token is 10% and belongs to
   borders. Changing one must not drag the other. */
.card__divider {
  height: 1px;
  margin: 0;
  border: 0;
  background-color: var(--color-divider);
}

/* Dock */

/* Matches the grid's box so the panel lands on the last column's edge.
   Not 100vw — that counts the scrollbar and pushes the panel inward. */
.dock-rail {
  position: fixed;
  right: 0;
  bottom: var(--dock-bottom);
  left: 0;
  z-index: 200;

  display: flex;
  justify-content: flex-end;

  /* Rail spans the page; only the panel is clickable. */
  pointer-events: none;
}

.dock {
  width: var(--dock-size);
  display: flex;
  flex-direction: column;
  gap: var(--dock-progress-gap);
  pointer-events: auto;
}

/* Mobile only: on desktop the page CTA is always in view. Collapses on the
   same height-and-fade as the logo slot, so the dock has one behaviour for
   things entering and leaving it. Hidden at the foot of the page, where the
   real Buy link is already on screen. */
.dock-buy {
  display: none;
  height: var(--dock-buy-height);
  overflow: hidden;

  align-items: center;
  justify-content: center;
  border-radius: 12px;

  font-size: var(--type-body);
  line-height: var(--line-body);
  font-weight: 700;
  color: var(--ink);

  background-color: var(--glass-bg);
  backdrop-filter: var(--glass-backdrop);
  -webkit-backdrop-filter: var(--glass-backdrop);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);

  transition:
      height var(--chrome-ms) var(--enter-ease),
      margin-bottom var(--chrome-ms) var(--enter-ease),
      opacity var(--ui-ms) var(--ui-ease),
      visibility var(--chrome-ms) var(--enter-ease);
}

@media (max-width: 600px) {
  .dock-buy { display: flex; }
}

.dock-buy.is-collapsed {
  height: 0;
  margin-bottom: calc(var(--dock-progress-gap) * -1);
  opacity: 0;
  visibility: hidden;
  border-width: 0;
}

/* Never animate .dock or .dock-rail. An animation with a fill mode leaves a
   stacking context, which Chrome treats as a backdrop root — the panel then
   blurs the inside of its parent and renders flat. Safari doesn't enforce it,
   so the fault shows in Chrome only. */
.dock-panel,
.dock-progress {
  animation: dock-enter var(--chrome-ms) var(--enter-ease) both;
}

@keyframes dock-enter {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.dock-progress {
  height: var(--dock-progress-height);
  border-radius: 999px;
  background-color: var(--color-line);
  overflow: hidden;
}

/* Faster than --ui-ms: at 200ms this visibly trails the scroll. */
.dock-progress-fill {
  width: 100%;
  height: 100%;
  background-color: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.15s ease-out;
}

/* Height follows its contents so the logo can collapse it. The parts sum to
   exactly --dock-size — change one and pay for it elsewhere in the budget. */
.dock-panel {
  padding-block: var(--dock-pad-y);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--dock-gap);
  border-radius: var(--dock-radius);
  text-align: center;

  background-color: var(--glass-bg);
  backdrop-filter: var(--glass-backdrop);
  -webkit-backdrop-filter: var(--glass-backdrop);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
}

/* Plain height, not the grid 0fr technique — that's unreliable in Safari.
   The negative margin retracts the panel's gap at the same rate. */
.dock-logo-slot {
  height: var(--dock-logo-height);
  overflow: hidden;
  transition:
      height var(--chrome-ms) var(--enter-ease),
      margin-bottom var(--chrome-ms) var(--enter-ease),
      opacity var(--ui-ms) var(--ui-ease),
      visibility var(--chrome-ms) var(--enter-ease);
}

/* Hidden, not just flat — the link inside must leave the tab order. */
.dock.is-collapsed .dock-logo-slot {
  height: 0;
  margin-bottom: calc(var(--dock-gap) * -1);
  opacity: 0;
  visibility: hidden;
}

/* Boxed to 200 × 57 so the panel's height is known before the file lands.
   contain means odd proportions letterbox rather than stretch. */
.dock-logo {
  width: var(--dock-logo-width);
  height: var(--dock-logo-height);
  object-fit: contain;
}

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

/* The panel has none, so a long menu word would reach the glass edge. */
.dock-menu a,
.dock-menu button {
  display: block;
  width: 100%;
  border: 0;
  padding: 0 var(--dock-gap);
  appearance: none;
  background: transparent;
  cursor: pointer;

  font-family: inherit;
  font-size: var(--dock-menu-size);
  line-height: var(--dock-menu-line);
  font-weight: 700;
  color: var(--ink);
  transition: color var(--ui-ms) var(--ui-ease);
}

.dock-controls {
  display: flex;
  align-items: center;
  gap: var(--dock-toggle-gap);
}

.dock-control {
  position: relative;
  display: flex;
  border: 0;
  padding: 0;
  appearance: none;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: color var(--ui-ms) var(--ui-ease);
}

/* Widens the tap area without adding height, which would break the square.
   Half the gap horizontally, so neighbours can't overlap. */
.dock-control::after {
  content: "";
  position: absolute;
  inset: -12px calc(var(--dock-toggle-gap) / -2);
}

/* Both stay legible; the one in use is brighter. */
.dock-control[aria-pressed="false"] { color: var(--muted); }

.dock-control svg {
  width: var(--dock-icon-size);
  height: var(--dock-icon-size);
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.dock-copyright {
  margin: 0;
  font-size: var(--dock-foot-size);
  line-height: var(--dock-foot-line);
}

/* Poster page */

/* The fr numbers are the column widths at full frame: 640 poster, 728 text.
   Proportional, so below full frame both shrink together instead of the poster
   pinning and the text collapsing. Keep them in step with --content-width and
   --prose-width; fr cannot read a custom property, so this is the one place
   those numbers are repeated. Stacks before the text column gets too narrow
   for the meta row. */
.card-detail {
  display: grid;
  grid-template-columns: minmax(0, 640fr) minmax(0, 728fr);
  gap: var(--gap-detail);
  align-items: start;
}

@media (max-width: 900px) {
  .card-detail { grid-template-columns: minmax(0, 1fr); }
}

.card-detail__poster {
  width: 100%;
  height: auto;
}

/* Flex items refuse to shrink below their content, which lets the cast
   rail push the whole column past the viewport. */
.detail {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--gap-detail);
}

.detail__title,
.notfound__title {
  margin: 0;
  font-size: var(--type-heading);
  line-height: var(--line-heading);
  font-weight: 700;
}

/* Labels, rule, values — one grid, so both rows share column edges. Columns
   size to the wider of the pair; they can shrink, or a long value overflows
   the page on a narrow phone. The trailing track holds no content — it exists
   so the rule can span past the last column to the container edge. */
.meta {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, max-content)) minmax(0, 1fr);
  column-gap: var(--gap-detail);
  /* Figma, deliberately off the 8-point ladder. */
  row-gap: 10px;
}

/* Keeps each label paired with its value in the markup while both sit
   directly on the grid. */
.meta__field { display: contents; }

.meta__label { grid-row: 1; }

.meta__divider {
  grid-row: 2;
  grid-column: 1 / -1;
  height: 1px;
  margin: 0;
  border: 0;
  background-color: var(--color-divider);
}

.meta__value,
.meta__rating { grid-row: 3; }

.meta__rating {
  width: var(--rating-size);
  height: var(--rating-size);
}

/* One size across the page; only the leading differs by block. Each property
   is set once — nothing below overrides anything above. */
.meta__label,
.meta__value,
.cast__label,
.cast__name,
.cast__role,
.detail__description,
.notfound__body,
.cta a { font-size: var(--type-body); }

.meta__label,
.meta__value,
.cast__label,
.cta a { line-height: var(--line-body); }

.meta__label,
.cast__label,
.cast__name { font-weight: 700; }

.detail__description,
.notfound__body {
  margin: 0;
  max-width: var(--prose-width);
  line-height: var(--line-prose);
}

/* Description and its credit sit closer than the column's own rhythm. */
.detail__prose {
  display: flex;
  flex-direction: column;
  gap: var(--gap-poster);
  min-width: 0;
}

/* Full width, ruled top and bottom. Label left, link right. */
.source {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-card);
  padding: 8px;
  border: 1px solid var(--color-divider);

  line-height: var(--line-body);
}

.source__label {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  font-size: var(--type-body);
}

.source__key { font-weight: 700; }

.source__name {
  font-family: var(--font-text);
  font-weight: 400;
}

.source__cta {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
  padding: 6px 8px;
  border: 1px solid var(--color-divider);

  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  transition: color var(--ui-ms) var(--ui-ease);
}

/* Sized to the line, not to the file: other sites ship favicons at whatever
   dimensions they like. */
.source img {
  width: var(--source-size);
  height: var(--source-size);
  object-fit: cover;
}

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

/* Rail and its bar. The bar is drawn rather than native: iOS never shows a
   persistent scrollbar for an overflow container, and macOS hides it until you
   scroll. Neither is stylable to a flat rectangle on every device. */
.cast__rail {
  display: flex;
  flex-direction: column;
  gap: var(--gap-card);
  min-width: 0;
}

.cast__list {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--gap-detail);
  margin: 0;
  padding: 0;
  list-style: none;

  overflow-x: auto;
  scroll-snap-type: x proximity;
  overscroll-behavior-x: contain;

  /* Both hide it, so Chrome preferring the standard property over the
     pseudo-element costs nothing here. */
  scrollbar-width: none;
}

.cast__list::-webkit-scrollbar { display: none; }

/* app.js adds the class and sizes the thumb. No overflow, no bar. */
.cast__scrollbar {
  display: none;
  height: var(--scroll-height);
  background-color: var(--scroll-track);
}

.cast__scrollbar.is-scrollable { display: block; }

.cast__scrollbar-thumb {
  height: 100%;
  background-color: var(--scroll-thumb);
  cursor: grab;

  /* Or dragging the thumb pans the page on a touchscreen. */
  touch-action: none;
}

.cast__member {
  flex: none;
  scroll-snap-align: start;

  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gap-card);
  text-align: center;
}

.cast__avatar {
  width: var(--cast-avatar-size);
  height: var(--cast-avatar-size);
  border-radius: 50%;
  object-fit: cover;
}

.cast__name-block {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* nowrap sets the member's width — it hugs its longest line. The leading
   cannot go below the glyph height: overflow-x on the rail makes both axes
   scroll containers, so anything outside the line box is clipped. */
.cast__name,
.cast__role {
  line-height: 16px;
  white-space: nowrap;
}

/* Equal halves, separated rather than joined. The border belongs to each half,
   not the pair: a shared rule would sit against one edge and hand the whole gap
   to the other half, which is what made the stacked pair look uneven. */
.cta {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gap-card);
}

.cta a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-card);
  min-width: 0;
  padding: var(--cta-pad);
  border: 1px solid var(--color-divider);
  font-weight: 700;
  color: var(--ink);
  transition: color var(--ui-ms) var(--ui-ease);
}

@media (max-width: 600px) {
  .cta { grid-template-columns: minmax(0, 1fr); }
}

/* Not found */

/* Borrows the poster page's column and its CTA wholesale; the cap is what
   stops two buttons stretching across 1248px. */
.notfound {
  display: flex;
  flex-direction: column;
  gap: var(--gap-detail);
  max-width: var(--prose-width);
  padding-block-start: var(--gap-row);
}

/* Details */

/* Opens below the header so the logo stays visible. Solid, not glass —
   a blur behind body copy costs more than it buys. */
.details {
  position: fixed;
  top: calc(var(--page-y) + var(--header-height));
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 150;

  overflow-y: auto;
  overscroll-behavior: contain;
  background-color: var(--bg);

  /* A second scrollbar reads as a nested window. */
  scrollbar-width: none;

  opacity: 0;
  visibility: hidden;
  transition:
      opacity var(--ui-ms) var(--ui-ease),
      visibility var(--ui-ms) var(--ui-ease);
}

.details::-webkit-scrollbar { display: none; }

.details.is-open {
  opacity: 1;
  visibility: visible;
}

/* The grid's box exactly, so the text lands on the posters' edges. */
.details__content {
  padding-block-end: calc(var(--page-y) + var(--dock-clearance));

  font-size: var(--type-body);
  line-height: var(--line-body);
}

.details__content p { margin: 0 0 18px; }

.details__content ul {
  margin: 0 0 18px;
  padding: 0;
  list-style: none;
}

.details__content li::before { content: "• "; }

/* Paired so the shared scale can be read across both diagrams. */
.details__sizes {
  display: flex;
  gap: var(--gap-col);
  margin-bottom: 18px;
}

/* Column so the diagram sits at the bottom: the lists differ in length and
   the outlines are bottom-anchored, so the boxes must end level. */
.details__size {
  flex: 0 1 160px;
  display: flex;
  flex-direction: column;
}

/* currentColor, so one copy of the artwork serves both themes. */
.details__diagram {
  width: 100%;
  height: auto;
  display: block;
  margin-top: auto;
  color: var(--muted);
}

.details__diagram rect {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
}

.details__diagram text {
  fill: var(--ink);
  font-size: 16px;
  font-weight: 500;
}

.details__lead {
  font-size: var(--type-heading);
  line-height: var(--line-heading);
  font-weight: 700;
}

.details__heading {
  margin-bottom: 0;
  font-weight: 700;
}

.details__content a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.details__close {
  position: fixed;
  top: calc(var(--page-y) + var(--header-height));
  right: var(--page-x);

  display: flex;
  border: 0;
  padding: 0;
  appearance: none;
  background: transparent;
  color: var(--ink);
  cursor: pointer;

  /* Buttons don't inherit the face. Matched to the card action arrows. */
  font-family: inherit;
  font-size: var(--type-body);
  line-height: var(--line-body);
  font-weight: 700;
}

/* Preferences */

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

/* Translucency is hard for some people to read. */
@media (prefers-reduced-transparency: reduce) {
  :root { --glass-bg: #F1F1F1; --glass-backdrop: none; }
  [data-theme="dark"] { --glass-bg: #0A0A0A; }
}

/* No blur available, so the fill goes near-opaque. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root { --glass-bg: #F1F1F1; }
  [data-theme="dark"] { --glass-bg: #0A0A0A; }
}
