/* ---------------------------------------------------------------
   Fonts
   --------------------------------------------------------------- */

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

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

/* ---------------------------------------------------------------
   Tokens
   --------------------------------------------------------------- */

:root {
  color-scheme: light;

  --bg: #FFFFFF;
  --ink: #0F1419;
  --muted: #536471;
  --color-line: rgba(0, 0, 0, 0.1);
  --color-accent: #007AFF;

  /* Frosted glass: the fill is the page colour at partial opacity, so it
     nearly vanishes over empty page and blooms over a poster. */
  --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);

  /* 6.667vw resolves to 96px at 1440, floors at 24px on small screens */
  --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);

  /* 1440 − (96 × 2) = 1248 content, minus 2 × 24px gap = 3 × 400 columns */
  --content-width: 1248px;
  --gap-col: 24px;
  /* Scales with viewport so stacked cards don't drift apart on mobile */
  --gap-row: clamp(32px, 4.444vw, 64px);

  /* Card internals are type-relative, so they stay fixed */
  --gap-poster: 8px;
  --gap-details: 16px;
  --gap-text: 8px;
  --details-pad: 16px;

  /* Dock panel

     Every value below is its 1440px measurement divided by 280, so the
     whole panel — type included — scales as one object. Changing
     --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 */

  /* The menu is the budget's remainder, not a fixed value: whatever the
     square has left once the fixed parts and gaps are paid for, divided
     across three lines. The panel is therefore exactly --dock-size at
     every width, and stays so if any other value here 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
      ) / 3
  );

  --dock-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  --dock-progress-height: 5px;
  --dock-progress-gap: 10px;
  /* Page padding below the panel, counting the progress bar above it. */
  --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 the values that differ; --glass-border resolves against
   --color-line at use, so it needs no restating here. */
[data-theme="dark"] {
  color-scheme: dark;

  --bg: #000000;
  --ink: #E7E9EA;
  --muted: #71767B;
  --color-line: rgba(255, 255, 255, 0.1);

  --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, so locking scroll behind the overlay cannot
   remove the scrollbar and shift the centred layout sideways. */
html { scrollbar-gutter: stable; }

body {
  margin: 0;
  background-color: var(--bg);
  color: var(--ink);
  font-family: "Helvetica Now Display", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
  -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);
}

/* The accent rather than the ink: a focus ring is the system speaking, not
   the page, and it 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));
}

/* ---------------------------------------------------------------
   Header
   --------------------------------------------------------------- */

/* Height is derived from the logo rather than guessed, so the Details
   overlay can clear it to the pixel. Not sticky — the logo hands off to
   the dock on scroll rather than following the page down. */
.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;
}

/* Only one mark shows at a time: whichever the scroll position calls for.
   Hiding the link rather than just fading it keeps an invisible target
   out of the tab order and off the cursor. */
.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, 1fr);
  gap: var(--gap-row) var(--gap-col);

  max-width: calc(var(--content-width) + var(--page-x) * 2);
  margin: 0 auto;
  padding: 0 var(--page-x);
  list-style: none;
}

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

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

/* ---------------------------------------------------------------
   Card
   --------------------------------------------------------------- */

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

/* Height follows the file's own proportions — never cropped, never
   forced to a ratio. Column width is the only thing driving size. */
.card__poster {
  width: 100%;
  height: auto;
}

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

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

.card__title {
  margin: 0;
  font-size: 22px;
  line-height: 26px;
  letter-spacing: -0.2px;
  font-weight: 700;
}

.card__credits,
.card__buy {
  font-size: 14px;
  line-height: 18px;
  letter-spacing: -0.2px;
  font-weight: 500;
  color: var(--muted);
}

.card__credits { margin: 0; }

.card__buy {
  display: block;
  transition: color var(--ui-ms) var(--ui-ease);
}

.card__buy:hover { color: var(--ink); }

/* ---------------------------------------------------------------
   Dock
   --------------------------------------------------------------- */

/* A fixed rail matching the grid's own box, so the panel lands on the
   third column's right edge at every width. Measured off the layout
   rather than the viewport: 100vw counts the scrollbar and would push
   the panel inward by its width on desktop. */
.dock-rail {
  position: fixed;
  right: 0;
  bottom: var(--dock-bottom);
  left: 0;
  z-index: 200;

  display: flex;
  justify-content: flex-end;
  max-width: calc(var(--content-width) + var(--page-x) * 2);
  margin: 0 auto;
  padding: 0 var(--page-x);

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

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

/* Nothing may animate on this element or the rail above it. Any animation
   with a fill mode leaves a stacking context behind, which Chrome treats
   as a backdrop root — the panel would then blur the inside of its own
   parent and render as a flat fill. Safari does not enforce this, so the
   fault appears only in Chrome. The entrance therefore runs on the panel
   and the bar themselves, which are the last elements in the chain. */
.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;
}

/* Scaled by app.js via transform. Quicker and flatter than the UI easing:
   this tracks the scroll, and at 200ms it would visibly trail the page. */
.dock-progress-fill {
  width: 100%;
  height: 100%;
  background-color: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left;
  will-change: transform;
  transition: transform 0.15s ease-out;
}

/* Height follows its contents rather than being fixed, so collapsing the
   logo slot shrinks the panel in step with it. Expanded, the parts sum to
   exactly 280 — the square is a consequence of the contents, not a value
   imposed on them, so any change to the type or gaps must be paid for
   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);
}

/* A 0fr row collapses to nothing while still transitioning, which a
   height of auto cannot do. The negative margin retracts the panel's own
   gap at the same rate, so the space closes completely. */
.dock-logo-slot {
  display: grid;
  grid-template-rows: 1fr;
  transition:
      grid-template-rows 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);
}

.dock-logo-slot > * { min-height: 0; overflow: hidden; }

/* Hidden rather than merely flat, so the home link inside leaves the tab
   order while the header is carrying the mark. */
.dock.is-collapsed .dock-logo-slot {
  grid-template-rows: 0fr;
  margin-bottom: calc(var(--dock-gap) * -1);
  opacity: 0;
  visibility: hidden;
}

/* Boxed to the declared 200 × 57 ratio so the panel's height is known
   before the file loads. object-fit means a file of different proportions
   fits inside the box rather than stretching to it — the mark stays true
   and the square stays exact either way. */
.dock-logo {
  width: var(--dock-logo-width);
  height: var(--dock-logo-height);
  object-fit: contain;
}

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

.dock-menu a,
.dock-menu button {
  display: block;
  width: 100%;
  border: 0;
  padding: 0;
  appearance: none;
  background: transparent;
  cursor: pointer;

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

.dock-menu a:hover,
.dock-menu button:hover { color: var(--muted); }

.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);
}

/* An 18px glyph is a poor target on a phone. This widens the tap area
   without adding height, which would break the square. Horizontal inset
   stops at half the gap so neighbouring targets can never overlap. */
.dock-control::after {
  content: "";
  position: absolute;
  inset: -12px calc(var(--dock-toggle-gap) / -2);
}

/* Both themes stay legible; the one in use is simply 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;   /* SF-regular weight, as the portfolio dock */
  stroke-linecap: round;
  stroke-linejoin: round;
}

.dock-copyright {
  margin: 0;
  font-size: var(--dock-foot-size);
  line-height: var(--dock-foot-line);
  letter-spacing: -0.2px;
  font-weight: 500;
  color: var(--ink);
}

/* ---------------------------------------------------------------
   Details
   --------------------------------------------------------------- */

/* Opens below the header, so the logo stays visible while reading. Solid
   rather than glass: a blur behind body copy costs more legibility than
   it buys. Sits under the dock, which stays reachable to close it. */
.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);

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

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

/* The grid's box exactly, so the text sits on the same edges as the
   posters. Bottom padding clears the dock, which overlaps the last of it. */
.details__content {
  max-width: calc(var(--content-width) + var(--page-x) * 2);
  margin: 0 auto;
  padding: var(--page-y) var(--page-x) var(--dock-clearance);

  font-size: 14px;
  line-height: 18px;
  letter-spacing: -0.2px;
  font-weight: 500;
  color: var(--muted);
}

.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 two scales can be compared directly — the diagrams share
   a scale, which is only legible when they sit together. Basis without
   grow: the blocks hold 160px where there is room and shrink together
   where there is not, rather than stretching across the full measure. */
.details__sizes {
  display: flex;
  gap: var(--gap-col);
  margin-bottom: 18px;
}

/* A column so the diagram can be pushed to the bottom: the lists differ
   in length, and the outlines are bottom-anchored in their viewBox, so
   they only line up if the two boxes end level. */
.details__size {
  flex: 0 1 160px;
  display: flex;
  flex-direction: column;
}

/* Outlines take the muted grey and labels the ink, so both follow the
   theme without a second copy of the artwork. */
.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;
  letter-spacing: -0.2px;
}

.details__lead,
.details__heading { color: var(--ink); }

.details__lead {
  font-size: 22px;
  line-height: 26px;
  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) * 2 + var(--header-height));
  right: var(--page-x);

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

.details__close 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;
}

/* ---------------------------------------------------------------
   Fallback
   --------------------------------------------------------------- */

.fallback {
  padding: 0 var(--page-x);
  font-size: 14px;
  line-height: 18px;
  letter-spacing: -0.2px;
  font-weight: 500;
  color: var(--muted);
}

.fallback a { color: var(--ink); }

/* ---------------------------------------------------------------
   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; }
}

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