/* ============================================================
   Sanjiv Sirpal — scrub-timeline portfolio
   Two tokens drive the entire interface: --paper and --ink.
   Everything else references them, so the black/white swap
   inverts the whole thing as one.
   ============================================================ */

:root {
  /* The ground is only ever pure white or pure black — no off-whites,
     no near-blacks, nothing in between. */
  --paper: #ffffff;
  --ink:   #000000;

  /* The accent marks the active rail position and project. The theme-specific
     overrides retain the original bright yellow on black and deepen it on
     white so the ticks remain visible. */
  --accent:     #D9CC00;
  --accent-ink: #000000;

  /* One typeface throughout — identity, metadata, and the timeline. */
  --font-disp: "Helvetica Neue", Helvetica, Inter, Arial, sans-serif;
  --identity-name-size: clamp(26px, 2.9vw, 46px);
  --project-label-size: var(--identity-name-size);
  --dev-mobile-timeline-edge-padding: 0px;

  /* Ground transition — DOM and WebGL clear colour share this duration. */
  --swap-ms: 600ms;
  --swap-ease: cubic-bezier(0.4, 0.0, 0.2, 1);

  /* Narrow strip — the heading now owns the top-left corner. */
  --rail-w: clamp(42px, 3.6vw, 60px);
  --gutter: clamp(16px, 3.2vw, 56px);
  /* Where the time rail starts and stops, clearing the heading above and the
     project caption below. */
  --head-clear: clamp(88px, 11vh, 132px);
  --foot-clear: clamp(84px, 10vh, 120px);

  /* Phones only: the strip kept clear at the bottom for browser chrome.
     100dvh already tracks Safari's collapsing toolbar, but the explicit
     inset keeps the rail off it while the toolbar is expanded. */
  --safe-bottom: calc(env(safe-area-inset-bottom, 0px) + 28px);
}

:root[data-theme="dark"] {
  --paper: #000000;
  --ink:   #ffffff;
  --accent: #F2EA2C;
}

:root[data-theme="light"] {
  --accent: #D9CC00;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-disp);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  transition: background-color var(--swap-ms) var(--swap-ease),
              color var(--swap-ms) var(--swap-ease);
}

#app {
  position: relative;
  width: 100vw;
  height: 100dvh; /* dvh so mobile browser chrome never clips the rails */
  overflow: hidden;
}

/* ---------- Media stage ---------- */

#stage, #plate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
}

#plate {
  background-size: cover;
  background-position: center;
  transition: opacity var(--swap-ms) var(--swap-ease);
}

#plate[hidden] { display: none; }

/* Desktop media lives in the same protected vertical band as the timeline.
   This makes containment deterministic for unusually tall or wide work: the
   renderer can no longer place pixels beneath the identity or footer copy. */
:root[data-ui-mode="desktop"] #stage,
:root[data-ui-mode="desktop"] #plate {
  --stage-top: var(--head-clear);
  --stage-bottom: var(--foot-clear);
  top: var(--stage-top);
  bottom: auto;
  height: calc(100dvh - var(--stage-top) - var(--stage-bottom));
}

/* ---------- Identity ---------- */

/* Desktop: name and role read as a single line, top-left. */
#identity {
  position: absolute;
  z-index: 3;
  top: var(--gutter);
  left: var(--gutter);
  display: flex;
  align-items: baseline;
  justify-content: flex-start;
  gap: 0.42em;
  white-space: nowrap;
  text-align: left;
  pointer-events: auto;
}

#identity h1 {
  margin: 0;
  font-size: var(--identity-name-size);
  font-weight: 700;
  letter-spacing: -0.02em;
  /* Room for the descenders in "Sanjiv Sirpal" — line-height 1 clips them. */
  line-height: 1.2;
}

#identity .identity__sep {
  font-size: clamp(15px, 1.5vw, 24px);
  font-weight: 200;
  line-height: 1;
  opacity: 0.35;
}

#identity .role {
  margin: 0;
  font-size: clamp(13px, 1.25vw, 21px);
  font-weight: 300;
  letter-spacing: -0.01em;
  /* The 'g' in "Designer" sits below the baseline; with line-height 1 and the
     overflow:hidden that guards the mobile ellipsis, its tail was cut off. */
  line-height: 1.35;
  opacity: 0.92;
}

/* Fixed, not absolute — it escapes #identity's box to sit bottom-right,
   on the same baseline as the footer description. */
#identity .contact {
  position: fixed;
  right: var(--gutter);
  bottom: var(--gutter);
  margin: 0;
  font-size: clamp(12px, 1.05vw, 17px);
  font-weight: 300;
  line-height: 1.35;
  opacity: 0.5;
}

#identity .contact a {
  color: inherit;
  text-decoration: none;
}

#identity .contact a:hover,
#identity .contact a:focus-visible { text-decoration: underline; }

#identity .contact span { margin: 0 0.4em; }

/* ---------- Top-right controls ----------
   Geometry lifted from ttos: 34px circles, 18px from the top, 24px from the
   right edge, 14px apart. */

#controls {
  position: absolute;
  z-index: 6;
  top: 18px;
  right: 24px;
  display: flex;
  align-items: center;
  gap: 14px;
}

#controls button {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink);
  opacity: 0.6;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: opacity 200ms var(--swap-ease),
              color var(--swap-ms) var(--swap-ease);
}

#controls button svg {
  width: 20px;
  height: 20px;
  display: block;
}

#dev-tool-toggle[hidden] { display: none; }

/* The filled half swings round to read as the inverse ground. */
#theme-toggle svg { transition: transform 420ms var(--swap-ease); }
:root[data-theme="light"] #theme-toggle svg { transform: rotate(180deg); }

#controls button:hover { opacity: 1; }

#controls button:focus-visible {
  opacity: 1;
  outline: 1px solid color-mix(in srgb, var(--ink) 55%, transparent);
  outline-offset: 3px;
}

/* ---------- Development settings ---------- */

#dev-panel {
  position: fixed;
  z-index: 30;
  top: 68px;
  right: 24px;
  width: min(300px, calc(100vw - 28px));
  border: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
  border-radius: 12px;
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  color: var(--ink);
  box-shadow: 0 18px 60px color-mix(in srgb, #000 22%, transparent);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
  overflow: hidden;
  max-height: calc(100dvh - 88px);
}

#dev-panel[hidden] { display: none; }

.dev-panel__head {
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 8px 0 16px;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
}

.dev-panel__head p {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.dev-panel__head-actions {
  display: flex;
  align-items: center;
  gap: 4px;
}

#dev-panel-close {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: inherit;
  cursor: pointer;
  opacity: 0.6;
}

#dev-panel-close:hover { opacity: 1; }
#dev-panel-close svg { width: 16px; height: 16px; }
#dev-panel-close line { stroke: currentColor; stroke-width: 1.4; }

.dev-panel__body {
  display: grid;
  gap: 18px;
  padding: 16px;
  max-height: calc(100dvh - 134px);
  overflow-y: auto;
}

.dev-mobile-controls,
.dev-style-group {
  display: grid;
  gap: 18px;
}

.dev-mobile-controls[hidden],
.dev-style-group[hidden] { display: none; }

.dev-setting {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  display: grid;
  gap: 8px;
  font-family: var(--font-disp);
  font-size: 11px;
  font-weight: 400;
}

.dev-setting__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px;
}

.dev-setting output {
  opacity: 0.5;
  font-variant-numeric: tabular-nums;
}

.dev-reset {
  min-height: 24px;
  padding: 0 7px;
  border: 0;
  border-radius: 5px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  color: inherit;
  font: inherit;
  font-size: 10px;
  cursor: pointer;
}

.dev-reset:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ink) 14%, transparent);
}

.dev-reset:disabled {
  opacity: 0.3;
  cursor: default;
}

.dev-segmented {
  display: grid;
  grid-template-columns: 1fr 1fr;
  padding: 2px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}

.dev-segmented--three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.dev-segmented--three button {
  padding-inline: 5px;
  font-size: 9px;
}

.dev-segmented button {
  min-height: 30px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.dev-segmented button[aria-pressed="true"] {
  background: var(--ink);
  color: var(--paper);
}

.dev-setting input[type="range"] {
  width: 100%;
  margin: 0;
  accent-color: var(--accent);
  cursor: ew-resize;
}

/* ---------- Index drawer ---------- */

#drawer-scrim {
  position: fixed;
  inset: 0;
  z-index: 7;
  background: color-mix(in srgb, var(--ink) 38%, transparent);
  border: 0;
}

#drawer-scrim[hidden] { display: none; }

#drawer {
  position: fixed;
  z-index: 8;
  top: 0;
  right: 0;
  width: min(420px, 86vw);
  height: 100dvh;
  display: flex;
  flex-direction: column;
  /* Always the inverse of the ground, so the index reads as a separate
     surface laid over the work rather than part of it. */
  background: var(--ink);
  color: var(--paper);
  transform: translateX(100%);
  transition: transform 420ms cubic-bezier(0.32, 0.72, 0, 1);
  will-change: transform;
}

#drawer.is-open { transform: translateX(0); }

.drawer__head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 12px 10px 22px;
}

.drawer__title {
  margin: 0;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.55;
}

.drawer__close {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: inherit;
  opacity: 0.7;
  cursor: pointer;
}

.drawer__close svg { width: 20px; height: 20px; display: block; }
.drawer__close:hover { opacity: 1; }

.drawer__close:focus-visible {
  opacity: 1;
  outline: 1px solid color-mix(in srgb, var(--paper) 55%, transparent);
  outline-offset: 2px;
}

.drawer__list {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 6px 12px 30px;
  -webkit-overflow-scrolling: touch;
}

.drawer__group {
  margin: 22px 0 8px;
  padding-left: 10px;
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.4;
}

.drawer__group:first-child { margin-top: 4px; }

.drawer__item {
  width: 100%;
  min-height: 35px;
  display: grid;
  grid-template-columns: 22px 1fr 64px;
  align-items: center;
  gap: 10px;
  padding: 0 10px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: color-mix(in srgb, var(--paper) 76%, transparent);
  font-family: var(--font-disp);
  font-size: 13.5px;
  letter-spacing: -0.135px;
  text-align: left;
  cursor: pointer;
  transition: background-color 160ms ease, color 160ms ease;
}

.drawer__num {
  font-size: 9.5px;
  letter-spacing: 0.08em;
  opacity: 0.55;
  font-variant-numeric: tabular-nums;
}

.drawer__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.drawer__meta {
  font-size: 9.5px;
  letter-spacing: 0.08em;
  opacity: 0.4;
  font-variant-numeric: tabular-nums;
  text-align: left;
}

.drawer__item:hover {
  color: var(--paper);
  background: color-mix(in srgb, var(--paper) 6%, transparent);
}

.drawer__item.is-active {
  color: var(--paper);
  background: color-mix(in srgb, var(--paper) 10%, transparent);
}

.drawer__item:focus-visible {
  outline: 1px solid color-mix(in srgb, var(--paper) 55%, transparent);
  outline-offset: -1px;
}

.drawer__foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 22px 20px;
}

.drawer__foot p {
  margin: 0;
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.3;
}

#drawer-tool-toggle {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: inherit;
  opacity: 0.35;
  cursor: pointer;
}

#drawer-tool-toggle svg { width: 20px; height: 20px; display: block; }

#drawer-tool-toggle:hover,
#drawer-tool-toggle.is-enabled { opacity: 1; }

#drawer-tool-toggle.is-enabled {
  background: color-mix(in srgb, var(--paper) 10%, transparent);
}

#drawer-tool-toggle:focus-visible {
  opacity: 1;
  outline: 1px solid color-mix(in srgb, var(--paper) 55%, transparent);
  outline-offset: 3px;
}

/* ---------- Footer meta ---------- */

/* Desktop: title and description share one baseline, as in the frames. */
#meta {
  position: absolute;
  z-index: 3;
  left: calc(var(--rail-w) + var(--gutter));
  bottom: var(--gutter);
  right: calc(var(--gutter) + 26ch);   /* clear of the contact line */
  display: flex;
  align-items: baseline;
  gap: clamp(12px, 1.8vw, 34px);
  min-width: 0;
}

.meta__title {
  margin: 0;
  flex: 0 0 auto;
  font-size: var(--project-label-size);
  font-weight: 300;
  letter-spacing: -0.01em;
  line-height: 1.2;
  transition: opacity 260ms var(--swap-ease);
}

.meta__desc {
  margin: 0;
  /* Long disciplines shrink and ellipsize rather than push the row. */
  min-width: 0;
  flex: 0 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: clamp(12px, 1.05vw, 17px);
  font-weight: 300;
  opacity: 0.5;
  line-height: 1.35;
  transition: opacity 260ms var(--swap-ease);
}

#meta[data-pending="1"] .meta__title,
#meta[data-pending="1"] .meta__desc { opacity: 0; }

/* ---------- Rails ---------- */

.rail {
  position: absolute;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  width: var(--rail-w);
  touch-action: none;            /* we own the drag; never page-scroll */
  -webkit-tap-highlight-color: transparent;
  cursor: crosshair;
  outline: none;
}

.rail__svg {
  flex: 1 1 auto;
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.rail:focus-visible {
  outline: 1px solid color-mix(in srgb, var(--ink) 55%, transparent);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Ticks are inset from the screen edge — they read as an instrument sitting
   in the frame, not as something bleeding off it. The inset used to leave room
   for the marker dot beyond the ticks; with the dot gone the ticks take that
   space back and sit closer to the edge. */
.rail { --rail-inset: clamp(4px, 0.72vw, 13px); }

/* Starts below the heading and stops above the caption. Its labels and pill
   overhang to the right, so ending level with the metadata would run them
   straight through the title and description. */
.rail--time {
  left: 0;
  top: var(--head-clear);
  bottom: var(--foot-clear);
  height: auto;
  padding: 0 0 0 var(--rail-inset);
}

/* SVG primitives written per frame by rails.js */
.tick {
  stroke: var(--ink);
  shape-rendering: crispEdges;
  transition: stroke var(--swap-ms) var(--swap-ease);
}

.tick--decade { stroke-width: 1.5; }

/* Set beside its tick when the rail runs vertically, above it when the rail
   runs flat. `rails.js` writes the baseline per orientation. */
.rail__label {
  font-family: var(--font-disp);
  font-weight: 400;
  fill: var(--ink);
  transition: fill var(--swap-ms) var(--swap-ease);
}

/* ---------- Accent marker ----------
   A connector and a pill naming the position. Sits on the active frame's
   tick and overhangs the rail, so it must never eat pointer events meant
   for scrubbing.

   Plain HTML, not SVG — positioned in rails.js as a sibling of the rail's
   svg, sized to overlay that svg's own measured box exactly. WebKit has
   been seen to clip SVG content sitting outside the element's viewBox
   while it's mid-transition, even with overflow:visible set; the pill does
   exactly that on every touch, growing past the rail's own box and settling
   back. An HTML overlay carries no viewBox to clip against. */

.rail__marker {
  position: absolute;
  pointer-events: none;
}
.rail__marker[hidden] { display: none; }

.marker__line,
.marker__pill {
  position: absolute;
  left: 0;
  top: 0;
}

.marker__line { background: var(--accent); }

.marker__pill {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
}

.marker__text {
  color: var(--accent-ink);
  font-family: var(--font-disp);
  font-weight: 500;
  letter-spacing: 0;
  white-space: nowrap;
  line-height: 1;
}

/* Ticks belonging to the project you are currently inside. */
.tick.is-project { stroke: var(--accent); }

/* ---------- Scrim ----------
   In cover mode the media runs under the chrome, so the identity block and
   footer need ground behind them or the type dissolves into photography.
   Each scrim is a pseudo-element inside the chrome's own stacking context,
   so it sits above the stage but below its own text. It reads as the ground
   fading out, and it flips with the theme because it is built from --paper. */

:root[data-fit="cover"] #identity::before,
:root[data-fit="cover"] #meta::before {
  content: '';
  position: absolute;
  left: -50vw;
  right: -50vw;
  z-index: -1;
  pointer-events: none;
}

/* The fade tail extends well past the last line of type, so no text ever
   sits in the transparent part of its own scrim. */
:root[data-fit="cover"] #identity::before {
  top: -60vh;
  bottom: -5.5rem;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--paper) 95%, transparent) 0%,
    color-mix(in srgb, var(--paper) 90%, transparent) 72%,
    transparent 100%
  );
}

:root[data-fit="cover"] #meta::before {
  top: -5.5rem;
  bottom: -60vh;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--paper) 95%, transparent) 0%,
    color-mix(in srgb, var(--paper) 90%, transparent) 72%,
    transparent 100%
  );
}

/* Secondary type is set low-contrast by design; over photography it needs
   a little back. */
:root[data-fit="cover"] #identity .contact { opacity: 0.72; }
:root[data-fit="cover"] .meta__desc { opacity: 0.7; }

/* Rails sit on the media in cover mode too — a soft edge gradient keeps the
   hairline ticks readable without drawing a visible band. */
:root[data-fit="cover"] .rail::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

:root[data-fit="cover"] .rail--time::before {
  background: linear-gradient(
    to right,
    color-mix(in srgb, var(--paper) 78%, transparent),
    transparent
  );
}

/* ---------- Utility ---------- */

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.noscript {
  position: absolute;
  z-index: 10;
  inset: auto 0 40% 0;
  text-align: center;
  font-size: 15px;
  font-weight: 300;
  padding: 0 2rem;
}
.noscript a { color: inherit; }

/* ============================================================
   Responsive — breakpoints mirror ttos.getthinkable.com
   ============================================================ */

@media (max-width: 960px) {
  #meta { max-width: min(40ch, 52vw); }
}

/* Mobile reflow, per the horizontal-timeline frame: identity and contact at
   the top, media through the middle, then the caption and a flat timeline
   running the full width along the bottom. */
:root[data-ui-mode="mobile"] {
    --rail-w: 64px;   /* the rail's depth now, not its width */
    --gutter: 20px;
    --identity-name-size: clamp(13px, 3.9vw, 19px);
}

  /* The media gives up the top and bottom bands rather than running behind
     them — `stage.resize()` sizes the renderer from the canvas's own box, so
     insetting it here is all that is needed to reframe the work.
     The height is stated outright: `height: auto` on an absolutely positioned
     *replaced* element resolves from its intrinsic size and drops `bottom`,
     which for a canvas means its own backing store — the box and the renderer
     would then chase each other down to nothing. */
:root[data-ui-mode="mobile"] #stage,
:root[data-ui-mode="mobile"] #plate {
    --stage-top: clamp(66px, 10vh, 96px);
    --stage-bottom: calc(var(--safe-bottom) + var(--rail-w) + clamp(76px, 14vh, 108px));
    top: var(--stage-top);
    bottom: auto;
    height: calc(100dvh - var(--stage-top) - var(--stage-bottom));
  }

  /* The controls read as small marks up here rather than as buttons — 60%
     of their desktop size. The hit areas shrink with them, so the gap opens
     up to keep the two apart under a thumb. */
:root[data-ui-mode="mobile"] #controls { gap: 10px; }
:root[data-ui-mode="mobile"] #controls button { width: 26px; height: 26px; }
:root[data-ui-mode="mobile"] #controls button svg { width: 12px; height: 12px; }

  /* Name and role on one row, contact wrapping to its own row beneath —
     the whole lockup now lives at the top. */
:root[data-ui-mode="mobile"] #identity {
    flex-wrap: wrap;
    row-gap: 0.25em;
    left: var(--gutter);
    right: calc(24px + 34px + 14px + 34px + 12px);
    top: clamp(14px, 3vh, 30px);
  }

  /* Sized to hold one row inside the space the controls leave, down to 375px. */
:root[data-ui-mode="mobile"] #identity h1 { font-size: var(--identity-name-size); }
:root[data-ui-mode="mobile"] #identity .identity__sep { font-size: clamp(11px, 3vw, 15px); }

:root[data-ui-mode="mobile"] #identity .role {
    font-size: clamp(11px, 3vw, 15px);
    /* Last-resort guard: shrink rather than push into the controls. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Back into the header's flow, on its own row directly under the name. */
:root[data-ui-mode="mobile"] #identity .contact {
    position: static;
    flex: 0 0 100%;
    text-align: left;
    font-size: clamp(10px, 2.9vw, 13px);
    opacity: 0.45;
  }

  /* Title over description, centred, sitting just above the timeline. */
:root[data-ui-mode="mobile"] #meta {
    display: block;
    text-align: center;
    left: var(--gutter);
    right: var(--gutter);
    max-width: none;
    /* Clear of the rail by more than the engaged pill's reach — it rises
       about 17px above the rail's own box, onto the media, not onto type. */
    bottom: calc(var(--safe-bottom) + var(--rail-w) + clamp(30px, 5vh, 44px));
  }

  /* Smaller than they were — the rail below them is now the wide element on
     the screen, and the caption reads as its label. */
:root[data-ui-mode="mobile"] .meta__title { font-size: var(--project-label-size); }
:root[data-ui-mode="mobile"] .meta__desc {
    font-size: clamp(10px, 3vw, 13px);
    margin-top: 0.25em;
    /* One field, wrapping to a second line when it needs it. */
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }

  /* The timeline runs flat and full-width, clear of the browser chrome.
     Its dates and pill overhang upward onto the media, which is what gives
     the fish-eye somewhere to grow. */
:root[data-ui-mode="mobile"] .rail--time {
    left: var(--dev-mobile-timeline-edge-padding);
    right: var(--dev-mobile-timeline-edge-padding);
    top: auto;
    bottom: var(--safe-bottom);
    width: auto;
    height: var(--rail-w);
    padding: 0 0 var(--rail-inset) 0;
  }

:root[data-ui-mode="mobile"][data-fit="cover"] .rail--time::before {
    background: linear-gradient(
      to top,
      color-mix(in srgb, var(--paper) 78%, transparent),
      transparent
    );
}

@media (max-width: 390px) {
  :root[data-ui-mode="mobile"] {
    --rail-w: 40px;
    --gutter: 14px;
    --identity-name-size: 13px;
  }
  /* The identity stays one row here, so the name must not balloon — this
     used to force 34px back when the mobile lockup was stacked. */
  :root[data-ui-mode="mobile"] #identity h1 { font-size: var(--identity-name-size); }
  :root[data-ui-mode="mobile"] #identity .identity__sep,
  :root[data-ui-mode="mobile"] #identity .role { font-size: 11px; }
}

/* ---------- Reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  :root { --swap-ms: 1ms; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
}

/* ---------- Full-screen viewer ----------
   Opened by tapping the stage. Shows the full-resolution original fitted
   inside the viewport, over the current ground. */

#lightbox {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  background: var(--paper);
  padding: clamp(12px, 3vw, 48px);
  animation: lightbox-in 220ms var(--swap-ease);
}

#lightbox[hidden] { display: none; }

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

.lightbox__stage {
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  overflow: hidden;
}

/* Fit, never crop, never upscale past the file's own resolution. */
.lightbox__stage img,
.lightbox__stage video,
.lightbox__stage canvas {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
}

.lightbox__close {
  position: absolute;
  top: 18px;
  right: 24px;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink);
  opacity: 0.6;
  cursor: pointer;
}

.lightbox__close svg { width: 20px; height: 20px; display: block; }
.lightbox__close:hover { opacity: 1; }

.lightbox__close:focus-visible {
  opacity: 1;
  outline: 1px solid color-mix(in srgb, var(--ink) 55%, transparent);
  outline-offset: 3px;
}

.lightbox__caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(12px, 2.5vh, 28px);
  margin: 0;
  text-align: center;
  color: var(--ink);
  font-size: clamp(11px, 1vw, 14px);
  font-weight: 300;
  opacity: 0.5;
  pointer-events: none;
}

/* Nothing on the page scrolls, so the stage takes the whole gesture. Leaving
   pan-y let Safari claim a touch as a pan and cancel the pointer stream, which
   is why a tap never reached the viewer there. The index keeps its own
   scrolling. */
#app { touch-action: none; }
.drawer__list { touch-action: pan-y; }

@media (prefers-reduced-motion: reduce) {
  #lightbox { animation: none; }
}

/* The viewer's media slides with the finger. */
#lightbox .lightbox__stage { will-change: transform; }
#lightbox { touch-action: pan-y; }
