/* ==========================================================================
   main.css — layout & components (v2 · lab file)
   All color, spacing and type values come from tokens.css.
   ========================================================================== */

/* ---------- Base ---------- */
html {
  scrollbar-gutter: stable;
  /* Remove the gray/blue tap flash on mobile browsers. */
  -webkit-tap-highlight-color: transparent;
  /* Stop the iOS rubber-band overscroll from revealing the sticky
     footer / masthead gaps when the page bounces past its edge. */
  overscroll-behavior-y: none;
}

html.no-scroll {
  overflow: hidden;
}

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

body {
  font-family: var(--font-mono);
  font-size: var(--type-body);
  line-height: var(--leading-base);
  color: var(--color-fg-invert);
  background: var(--color-paper);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}

a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* Header/footer links: quiet by default, underlined on hover. */
.masthead__links a,
.footer__links a {
  text-decoration: none;
}

.masthead__links a:hover,
.masthead__links a:focus-visible,
.footer__links a:hover,
.footer__links a:focus-visible {
  text-decoration: underline;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

:focus-visible {
  outline: var(--hairline) solid currentColor;
  outline-offset: 2px;
}

img,
svg {
  display: block;
  max-width: 100%;
}

/* ---------- Page frame ----------
   The width limiter. Dashed rules are fixed to the viewport but offset to
   line up with the (centered, max-width) content column's edges. */
.frame {
  position: relative;
  width: 100%;
  max-width: var(--content-max-width);
  margin-inline: auto;
}

/* Vertical dashed rules — commented out.
.frame::before,
.frame::after {
  content: "";
  position: fixed;
  top: 0;
  bottom: 0;
  width: var(--hairline);
  z-index: 2;
  background-image: repeating-linear-gradient(
    to bottom,
    var(--color-ruler) 0 var(--dash-length),
    transparent var(--dash-length) calc(var(--dash-length) * 2)
  );
}

.frame::before { left: max(0px, calc((100vw - var(--content-max-width)) / 2)); }
.frame::after { right: max(0px, calc((100vw - var(--content-max-width)) / 2)); }
*/

/* Shared 8-column grid track */
.grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--grid-gutter);
}

/* Mono meta text — the default voice of the interface */
.meta {
  font-family: var(--font-mono);
  font-size: var(--type-body);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
}

/* ---------- Masthead ----------
   Sticky and layered behind the scrolling content (z-index 0, with the
   file cards at z-index 1). Not negative — a negative z-index puts it
   behind its own parent, which blocks the masthead links from clicks. */
.masthead-sticky {
  position: sticky;
  top: var(--masthead-margin-top);
  z-index: 0;
  margin-block: var(--masthead-margin-top) var(--masthead-margin-bottom);
}

.masthead {
  padding-block: var(--space-md) var(--space-lg);
  padding-inline: var(--space-lg);
}

.masthead__worker {
  grid-column: 1 / -1;
}

.masthead__contact {
  grid-column: 1 / -1;
}

.masthead__worker,
.masthead__contact {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

.masthead__contact {
  display: none;
}

.masthead__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-xs);
}

.masthead__links {
  display: none;
  gap: var(--space-md);
}

.rule {
  height: var(--hairline);
  background-image: repeating-linear-gradient(
    to right,
    var(--color-ruler) 0 var(--dash-length),
    transparent var(--dash-length) calc(var(--dash-length) * 2)
  );
}

/* ---------- File tabs ----------
   Each tab carries the color of its own file card, like a folder tab. */
.tabs {
  padding-top: var(--space-lg);
}

.tabs__list {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  align-items: flex-end;
}

.tabs__tab {
  font-family: var(--font-mono);
  font-size: var(--type-title);
  letter-spacing: var(--tracking);
  font-weight: 400;
  text-transform: uppercase;
  color: var(--color-fg-invert);
  padding: var(--tab-padding-y) calc(var(--tab-padding-x) + 4px) var(--tab-padding-y) var(--tab-padding-x);
  text-align: left;
  transition: transform 0.25s ease;
  /* Overlap the file card by 5px so no gap shows while the tab and file
     slide up during the hover animation. */
  margin-bottom: -5px;
  position: relative;
  /* Folder-tab shape: rounded top-left corner and a sharp slanted edge.
     The horizontal offset of the slant is derived from --tab-slant-angle
     by main.js (offset = height / tan(angle)); 40px is the no-JS fallback. */
  border-radius: var(--tab-radius) 0 0 0;
  clip-path: polygon(0 0, calc(100% - var(--tab-slant-offset, 40px)) 0, 100% 100%, 0 100%);
}

/* The slant clips the default focus outline — use an underline instead. */
.tabs__tab:focus-visible {
  outline: none;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* The active tab stays attached on top of its file; the inactive tab is
   tucked behind the file card so its extended 5px sits underneath. */
.tabs__tab[aria-selected="true"] {
  z-index: 2;
}

.tabs__tab[aria-selected="false"] {
  z-index: 0;
}

@media (hover: hover) and (pointer: fine) {
  .tabs__tab[aria-selected="false"]:hover,
  .case-open .tabs__tab:hover {
    transform: translateY(-10px);
  }
}

.tabs__tab[data-file-tab="index"] {
  grid-column: 1 / 3;
  background: var(--color-sheet);
  color: var(--color-fg);
}

.tabs__tab[data-file-tab="worker"] {
  grid-column: 3 / 5;
  background: var(--color-accent);
  color: var(--color-fg-invert);
}

/* ---------- File cards ---------- */
main {
  position: relative;
}

.file {
  transition: transform 0.25s ease;
}

/* Active file sits in normal flow and defines the card height. */
.file.is-active {
  position: relative;
  z-index: 1;
}

/* Inactive file is layered behind the active one so it can peek up on hover. */
.file:not(.is-active) {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 0;
  max-height: 100%;
  overflow: hidden;
}

.file.is-peeking {
  transform: translateY(-10px);
}

/* -- Project index: white sheet with black text -- */
.file--index {
  background: var(--color-fg-invert);
  color: var(--color-fg);
  padding: var(--space-lg);
}

/* Sheet-colored band wrapping the archive label + date, with fluid
   40px-equivalent top and 24px-equivalent bottom spacing. It bleeds out
   to the archive card's edges; the text inside stays on the card's
   padding grid thanks to matching inline padding. */
.index__bar-wrap {
  background: var(--color-sheet);
  margin-top: calc(-1 * var(--space-lg));
  margin-inline: calc(-1 * var(--space-lg));
  /* Gap between the band's bottom edge and the horizontal rule. */
  margin-bottom: var(--space-lg);
  padding-inline: var(--space-lg);
  padding-top: var(--index-bar-top);
  padding-bottom: var(--index-bar-bottom);
}

.index__bar {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  font-size: var(--type-index);
  color: var(--color-fg);
}

.index__divider {
  height: var(--hairline);
  background: var(--color-fg);
  margin-bottom: var(--space-xl);
}

.index__views {
  display: flex;
  gap: var(--space-md);
}

.index__view[aria-pressed="false"] {
  opacity: 0.7;
}

.projects {
  list-style: none;
  display: flex;
  flex-direction: column;
}

/* The whole project card opens its case study — show a click cursor. */
.project {
  cursor: pointer;
}

/* Divider between projects, mirroring the index bar divider's spacing:
   --space-lg above the line, --space-xl below it. */
.project + .project {
  border-top: var(--hairline) solid var(--color-fg);
  margin-top: var(--space-lg);
  padding-top: var(--space-xl);
}

.project__header {
  padding-bottom: var(--space-md);
  row-gap: var(--space-xs);
}

.project__index {
  grid-column: 1 / 3;
  grid-row: 1;
  font-family: var(--font-mono);
  font-size: var(--type-index);
  letter-spacing: var(--tracking);
}

.project__title {
  grid-column: 1 / -1;
  grid-row: 2;
  align-self: end;
  font-family: var(--font-mono);
  font-size: var(--type-title);
  font-weight: 400;
  line-height: var(--leading-tight);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.project__title a {
  text-decoration: none;
}

/* The meta pair sits on row 1: the client on the left, the year on the
   right. It spans the full card width. */
.project__meta {
  grid-column: 1 / -1;
  grid-row: 1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}

.project__year {
  font-family: var(--font-mono);
  font-size: var(--type-index);
  letter-spacing: var(--tracking);
}

.project__client {
  font-family: var(--font-mono);
  font-size: var(--type-index);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  white-space: nowrap;
}

.project__media {
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border: var(--hairline) solid var(--color-border);
  background: var(--color-sheet);
}

.project__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* -- Worker file: light sheet with black text -- */
.file--worker {
  background: var(--color-fg-invert);
  color: var(--color-fg);
  padding: var(--space-lg);
  align-content: start;

}

/* Red banner stretched across the full card width, with a square portrait
   inside it sized to match the grid columns */
.worker__banner {
  grid-column: 1 / -1;
  margin-top: calc(-1 * var(--space-lg));
  margin-inline: calc(-1 * var(--space-lg));
  background: var(--color-accent);
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--grid-gutter);
  padding: 0;
}

.worker__portrait {
  grid-column: 1 / -1;
  aspect-ratio: 1 / 1;
  background: var(--color-accent);
  position: relative;
  overflow: hidden;
}

.worker__portrait img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  /* transform: scaleX(-1); */
}

.worker__column {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

.worker__column--left {
  grid-column: 1 / -1;
}

.worker__column--right {
  grid-column: 1 / -1;
}

/* Top block: Name / Role / Location on the left, Expertise on the right. */
.worker__intro {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--grid-gutter);
}

.worker__intro--left,
.worker__intro--right {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

/* Rule between the two lower sections: on mobile they stack, so the rule
   is a straight horizontal line between them. Desktop turns it vertical
   in the media query below. */
.worker__rule {
  grid-column: 1 / -1;
  height: var(--hairline);
  background: var(--color-border);
}

/* Straight horizontal rule between the intro block and the lower columns. */
.worker__rule--top {
  grid-column: 1 / -1;
  height: var(--hairline);
  background: var(--color-border);
}

/* Wrapper grid for the lower columns + vertical rule, so the rule can
   never push the right column onto a later grid row. */
.worker__lower {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--grid-gutter);
}

.field__label {
  font-family: var(--font-mono);
  font-size: var(--type-index);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
  margin-bottom: var(--space-sm);
  font-weight: 400;
  /* Chip: sheet-colored text on a solid fg (black) background, with a
     2px-equivalent padding on all sides (--space-sm scales 5px → 6px with
     the viewport). The hatching is commented out for now. */
  display: inline-block;
  padding: var(--space-sm);
  color: var(--color-fg);
  background-color: var(--color-fg-invert);
  /* background-image:
    repeating-linear-gradient(135deg, var(--color-fg-gray) 0 1px, transparent 2px 4px); */
  border: var(--hairline) solid var(--color-border);
}

.field__value {
  font-family: var(--font-mono);
  font-size: var(--type-title);
  line-height: var(--leading-base);
}

.field__rows {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-xs) var(--space-xl);
  font-family: var(--font-mono);
  font-size: var(--type-title);
}

.field__rows dt {
  font-variant-numeric: tabular-nums;
}

/* Standalone dd entries (no dt) span the full row so their text wraps —
   otherwise the max-content column sizes to the longest unbroken line and
   blows the card past the viewport, zooming the page out on mobile. */
.field__rows dd {
  grid-column: 1 / -1;
}

/* ---------- Crop marks (plus crosses, inset deep into the image) ---------- */
.crop {
  --crop-color: var(--color-fg-invert);
  position: absolute;
  width: var(--crop-size);
  height: var(--crop-size);
  pointer-events: none;
}

.crop::before,
.crop::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--crop-color);
}

.crop::before {
  width: 100%;
  height: var(--hairline);
}

.crop::after {
  width: var(--hairline);
  height: 100%;
}

.crop--tl { top: var(--crop-inset); left: var(--crop-inset); }
.crop--tr { top: var(--crop-inset); right: var(--crop-inset); }
.crop--bl { bottom: var(--crop-inset); left: var(--crop-inset); }
.crop--br { bottom: var(--crop-inset); right: var(--crop-inset); }

.project__media--light .crop {
  --crop-color: var(--color-fg);
}

/* ---------- Footer ---------- */
.footer__mark {
  grid-column: 1 / -1;
  display: flex;
  justify-content: center;
  padding-block: var(--space-md);
}

.footer__mark svg,
.footer__mark img {
  width: clamp(90px, 9.4vw, 180px);
  height: auto;
}

/* Sticky to the bottom, content scrolls over it (z-index 0; the file cards
   are at 1, so they still cover it — but the footer links stay clickable
   when the footer is exposed). */
.footer-sticky {
  position: sticky;
  bottom: var(--footer-margin-bottom);
  z-index: 0;
  margin-block: var(--footer-margin-top) var(--footer-margin-bottom);
}

.footer__bar {
  padding-block: var(--space-md) var(--space-lg);
  padding-inline: var(--space-lg);
  /* The two lines sit tight on mobile (stacked) — no gap between them. */
  row-gap: 0;
}

/* Mirrors the masthead: a left text column and a right links column.
   Within each pair the lines sit tight like one body of text (gap 0). */
.footer__column {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.footer__links {
  display: flex;
  gap: var(--space-md);
  justify-content: flex-start;
}

/* Mobile-only: the masthead hides its social links on phones, so they
   reappear here at the bottom of the footer instead. */
.footer__socials {
  display: none;
}

@media (max-width: 719px) {
  .footer__socials {
    display: flex;
    flex-wrap: wrap;
    padding-top: var(--space-md);
    margin-bottom: 1.45em; /* one extra line of space below the socials */
  }

  /* One extra line of space before the manifesto, which stacks below the
     record column on phones. */
  .footer__column--right {
    margin-top: 1.45em;
  }
}

/* ---------- Load-in ----------
   The site holds the masthead and foreground content hidden until the page
   is fully loaded (scripts, fonts, images), then the masthead decrypts and
   the foreground glides up as one group. */
html.is-booting .masthead,
html.is-booting .tabs,
html.is-booting main,
html.is-booting .footer-sticky {
  visibility: hidden;
}

/* Foreground slide-in is disabled for now.
html:not(.is-booting) .tabs,
html:not(.is-booting) main,
html:not(.is-booting) .footer-sticky {
  animation: rise-in 0.8s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}
*/

@keyframes rise-in {
  from { transform: translateY(100px); }
  to   { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  html:not(.is-booting) .tabs,
  html:not(.is-booting) main,
  html:not(.is-booting) .footer-sticky {
    animation: none;
  }
}

/* ---------- Case study sheet ----------
   Fixed to the viewport (aligned to the frame column) and slides in from
   the bottom until its top edge meets the bottom edge of the tabs. */
/* While a case study is open, the page behind (masthead, tabs and files)
   is frozen in place and the case study scrolls over it with the page. */
html.case-open {
  overscroll-behavior-y: none;
  /* Keep the reserved scrollbar gutter (stable from the root rule) so the
     frozen page behind never reflows when the study opens. The control bar
     is anchored to measured frame/tabs coordinates, not 100vw math. */
  /* Clicking outside the study closes it — show a click cursor there. */
  cursor: pointer;
}

html.case-open body {
  overscroll-behavior-y: none;
}

html.case-open .case__sheet {
  cursor: auto;
}

.case-open .page {
  position: fixed;
  top: 0;
  left: max(0px, calc((100vw - var(--content-max-width)) / 2));
  right: max(0px, calc((100vw - var(--content-max-width)) / 2));
}

/* The study is the last content: take the sticky footer out of flow so the
   page can't scroll past the bottom of the sheet. */
html.case-open .footer-sticky {
  display: none;
}

.case {
  position: fixed;
  top: 0;
  left: max(0px, calc((100vw - var(--content-max-width)) / 2));
  right: max(0px, calc((100vw - var(--content-max-width)) / 2));
  z-index: 3;
  pointer-events: none;
  visibility: hidden;
  transform: translateY(100vh);
  transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

.case.is-open {
  position: static;
  padding-top: var(--case-top, 0px);
  visibility: visible;
  transform: translateY(0);
}

.case__sheet {
  background: var(--color-fg-invert);
  color: var(--color-fg);
  pointer-events: auto;
}

/* The sheet is focused programmatically on open — no outline wanted. */
.case__sheet:focus,
.case__sheet:focus-visible {
  outline: none;
}

/* Sideways study-to-study transitions (control bar arrows). The sheet is
   translated horizontally; the fixed .case shell stays put. The old study
   leaves through one side while the new one enters from the opposite side,
   both travelling in the same direction. */
.case--out-left .case__sheet {
  animation: case-sheet-out-left 0.38s cubic-bezier(0.4, 0, 1, 1) forwards;
}

.case--out-right .case__sheet {
  animation: case-sheet-out-right 0.38s cubic-bezier(0.4, 0, 1, 1) forwards;
}

.case--in-left .case__sheet {
  animation: case-sheet-in-left 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

.case--in-right .case__sheet {
  animation: case-sheet-in-right 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

/* While a study steps sideways, the translated sheet would overflow the
   viewport horizontally and flash a horizontal scrollbar — clip it for
   the duration of the transition (class toggled by stepCase in JS). */
html.case-stepping {
  overflow-x: hidden;
}

@keyframes case-sheet-out-left {
  from { transform: translateX(0); }
  to   { transform: translateX(-100%); }
}

@keyframes case-sheet-out-right {
  from { transform: translateX(0); }
  to   { transform: translateX(100%); }
}

@keyframes case-sheet-in-left {
  from { transform: translateX(-100%); }
  to   { transform: translateX(0); }
}

@keyframes case-sheet-in-right {
  from { transform: translateX(100%); }
  to   { transform: translateX(0); }
}

@media (prefers-reduced-motion: reduce) {
  .case--out-left .case__sheet,
  .case--out-right .case__sheet,
  .case--in-left .case__sheet,
  .case--in-right .case__sheet {
    animation: none;
  }
}

.case__body {
  padding: var(--space-lg);
}

/* Sheet-colored bumper below the study content — touch devices only
   (phones and iPads, where Safari's floating toolbar can reveal area past
   the document end; the bumper makes that area look like the sheet). */
.case__bumper {
  display: none;
}

@media (max-width: 719px), (pointer: coarse) and (min-width: 720px) {
  .case__bumper {
    display: block;
    height: 96px;
    /* Toolbar delta (large - dynamic viewport) plus safe-area slack. */
    height: calc(100lvh - 100dvh + env(safe-area-inset-bottom, 0px) + 56px);
    background: var(--color-sheet);
  }
}

/* -- Intro: title + context on the left, project info on the right -- */
.case__intro {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--grid-gutter);
  row-gap: var(--space-xl);
}

.case__lead {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
}

/* A margin under every label + value pairing in the study intro. */
.case__intro .field {
  margin-bottom: var(--space-xl);
}

.case__display-title {
  font-family: var(--font-serif);
  font-size: var(--type-case-display);
  font-weight: 400;
  line-height: var(--leading-super-tight);
  letter-spacing: 0;
  margin-top: var(--space-lg);
  margin-bottom: var(--space-md);
}

.case__context {
  font-family: var(--font-mono);
  font-size: var(--type-title);
  line-height: var(--leading-base);
}

.case__info {
  grid-column: 1 / -1;
  align-self: start;
  display: flex;
  flex-direction: column;
}

/* Vertical rule between the intro's left (client + context) and right
   (info) halves — desktop only. It sits in the grid gutter, mirroring the
   worker file's vertical rule. */
.case__rule {
  display: none;
}

.case__divider {
  height: var(--hairline);
  margin-top: var(--space-xl);
  margin-bottom: var(--space-xl);
  background: var(--color-border);
}

/* Divider under the study title: straight solid line, tighter margins. */
.case__divider--title {
  margin-top: 0;
  margin-bottom: var(--space-xl);
}

/* -- Figures -- */
.case__figures {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--space-lg);
}

.case__figure {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin: 0;
}

.case__figure-media {
  position: relative;
  width: 100%;
  aspect-ratio: var(--media-ratio, 3 / 4);
  overflow: hidden;
  border: var(--hairline) solid var(--color-border);
  background: var(--color-paper);
  cursor: zoom-in;
}

.case__figure-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.case__figure-label {
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: var(--type-index);
  letter-spacing: var(--tracking);
  text-transform: uppercase;
}

/* ---------- Image lightbox ----------
   Enlarges a case-study image, centered over a dimmed and slightly
   blurred page. Clicking the empty space around the image closes it. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  background: rgba(10, 10, 10, 0.85);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  cursor: pointer;
}

.lightbox.is-open {
  display: flex;
  animation: lightbox-fade-in 0.25s ease;
}

/* The image glides into the center when the lightbox opens and whenever
   the shown image changes. */
.lightbox__stage--glide {
  animation: lightbox-glide-in 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}

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

@keyframes lightbox-glide-in {
  from {
    opacity: 0;
    transform: translateY(4%) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .lightbox.is-open,
  .lightbox__stage--glide {
    animation: none;
  }
}

.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  will-change: transform;
  cursor: inherit;
  /* Never let the browser start a native image drag (ghost + block cursor). */
  -webkit-user-drag: none;
  user-select: none;
}

/* The stage fills the overlay and hosts all drag / swipe / pinch input.
   Only the image inside it moves — never the page behind. Padding reserves
   room so the resting image never sits under the arrows or the close. */
.lightbox__stage {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Side arrows (--control-pad + offset) and the top close cross stay
     clear of the resting image. */
  padding: calc(var(--space-lg) + var(--control-pad)) calc(var(--space-lg) + var(--control-pad));
  cursor: zoom-in;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

@media (max-width: 719px) {
  /* Mobile: controls sit in a bottom bar, so reserve space there instead. */
  .lightbox__stage {
    padding: var(--space-lg);
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--space-lg) + var(--control-pad));
  }
}

.lightbox.is-zoomed .lightbox__stage {
  cursor: zoom-out;
}

.lightbox__stage.is-dragging {
  cursor: grabbing;
}

/* Prev / next arrows and the close cross — absolutely positioned so they
   don't disturb the centered image. They share the case-control bar's
   tokens (--control-pad hit area, --control-icon icons) but stay
   transparent. */
.lightbox__nav,
.lightbox__close {
  position: absolute;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--control-pad);
  height: var(--control-pad);
  font-family: var(--font-mono);
  font-size: var(--type-case-title);
  color: var(--color-fg-invert);
  opacity: 0.75;
  transition: opacity 0.2s ease;
}

.lightbox__nav:hover,
.lightbox__nav:focus-visible,
.lightbox__close:hover,
.lightbox__close:focus-visible {
  opacity: 1;
}

.lightbox__nav {
  top: 50%;
  transform: translateY(-50%);
}

.lightbox__nav svg,
.lightbox__close svg {
  width: var(--control-icon);
  height: var(--control-icon);
}

.lightbox__nav--prev {
  left: var(--space-md);
}

.lightbox__nav--next {
  right: var(--space-md);
}

.lightbox__close {
  top: var(--space-md);
  right: var(--space-md);
}

/* Mobile: control bar at the bottom — prev arrow left, close in the
   middle, next arrow right. */
@media (max-width: 719px) {
  .lightbox__nav,
  .lightbox__close {
    top: auto;
    bottom: calc(env(safe-area-inset-bottom, 0px) + var(--space-md));
  }

  .lightbox__nav {
    transform: none;
  }

  .lightbox__nav--prev {
    left: var(--space-md);
  }

  .lightbox__nav--next {
    right: var(--space-md);
  }

  .lightbox__close {
    left: 0;
    right: 0;
    margin-inline: auto;
    transform: none;
  }
}

/* Only one figure in the study — nothing to browse, hide the arrows. */
.lightbox--single .lightbox__nav {
  display: none;
}

/* Phones: swap in the mobile control sizing — the derived inset and gap
   recompute on their own against the swapped values. */
@media (max-width: 719px) {
  :root {
    --control-pad: var(--control-pad-mobile);
    --control-icon: var(--control-icon-mobile);
  }
}

/* ---------- Case study controls ----------
   Viewport-fixed bar: close + next / previous study. The bar and the
   image-lightbox controls share one sizing source (--control-pad /
   --control-icon / --control-inset / --control-gap in tokens.css, with
   the *-mobile pair active on phones); the bar adds a black chip with a
   sheet-colored stroke, the lightbox stays transparent. Default dock:
   bottom center (previous left, close middle, next right). On viewports
   whose outside gutter can physically host it — gutter = (100vw -
   1440px) / 2, bar = 2 x --control-pad - --control-icon + 2 x hairline
   = 70px at the default tokens — it hangs on the study's top-right
   corner, outside the sheet: top edge flush with the sheet's top edge,
   left edge flush with the sheet's right edge. It appears out of thin
   air — an ease-in deblur — instead of gliding with the sheet. */
.case-controls {
  position: fixed;
  z-index: 4;
  display: flex;
  flex-direction: row;
  /* Wall padding and button gap derive from the shared control tokens. */
  gap: var(--control-gap);
  padding: var(--control-inset);
  background: var(--color-paper);
  border: var(--hairline) solid var(--color-sheet);
  opacity: 0;
  pointer-events: none;
  left: 50%;
  bottom: var(--space-lg);
  transform: translateX(-50%);
}

.case-controls__btn[data-case-control="prev"] {
  order: 1;
}

.case-controls__btn[data-case-control="close"] {
  order: 2;
}

.case-controls__btn[data-case-control="next"] {
  order: 3;
}

/* Gutter >= bar width (bar = 2 x --control-pad - --control-icon + 2 x
   hairline = 70px at the default tokens; crossover is 1440px + 2 x bar +
   scrollbar ≈ 1595px — 1600px keeps slack for classic scrollbars): dock
   the bar on the study's top-right corner. The horizontal anchor comes
   from JS (--case-controls-left = the frame's real right edge) because
   100vw-based calcs drift by the classic scrollbar width against the
   shrunken containing block. */
@media (min-width: 1600px) {
  .case-controls {
    flex-direction: column;
    top: var(--case-controls-top, 0px);
    left: var(--case-controls-left, calc((100vw + var(--content-max-width)) / 2));
    right: auto;
    bottom: auto;
    transform: none;
  }

  .case-controls__btn[data-case-control="prev"],
  .case-controls__btn[data-case-control="close"],
  .case-controls__btn[data-case-control="next"] {
    order: 0;
  }
}

.case-controls.is-visible {
  pointer-events: auto;
  animation: case-controls-in 0.45s ease-in both;
}

@keyframes case-controls-in {
  from {
    opacity: 0;
    filter: blur(14px);
  }
  to {
    opacity: 1;
    filter: blur(0);
  }
}

/* Reverse of the appearance: the bar blurs ease-out while the study
   closes. */
.case-controls.is-leaving {
  pointer-events: none;
  animation: case-controls-out 0.45s ease-out both;
}

@keyframes case-controls-out {
  from {
    opacity: 1;
    filter: blur(0);
  }
  to {
    opacity: 0;
    filter: blur(14px);
  }
}

.case-controls__btn {
  width: var(--control-pad);
  height: var(--control-pad);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-sheet);
  opacity: 0.85;
  transition: opacity 0.2s ease;
}

.case-controls__btn:hover,
.case-controls__btn:focus-visible {
  opacity: 1;
}

.case-controls__btn svg {
  width: var(--control-icon);
  height: var(--control-icon);
}

/* Where the bar doesn't fit beside the sheet, dock it bottom center:
   previous on the left, close in the middle, next on the right. */
@media (max-width: 1519px) {
  .case-controls {
    top: auto;
    right: auto;
    left: 50%;
    bottom: var(--space-lg);
    transform: translateX(-50%);
    flex-direction: row;
  }

  .case-controls__btn[data-case-control="prev"] {
    order: 1;
  }

  .case-controls__btn[data-case-control="close"] {
    order: 2;
  }

  .case-controls__btn[data-case-control="next"] {
    order: 3;
  }
}

@media (prefers-reduced-motion: reduce) {
  .case-controls.is-visible,
  .case-controls.is-leaving {
    animation: none;
  }
}

/* Freeze the page while the lightbox is open. */
html.lightbox-open {
  overflow: hidden;
  /* Drop the reserved scrollbar gutter while scrolling is locked so the
     overlay's containing block spans the full viewport (keeps the overlay
     flush to the right edge and centers the close cross exactly). */
  scrollbar-gutter: auto;
}

html.lightbox-open body {
  overflow: hidden;
}

/* ==========================================================================
   Responsive breakpoints
   --------------------------------------------------------------------------
   Structural changes only (column spans, stacking). Fluid sizing (type,
   spacing) is handled entirely by the clamp() tokens in tokens.css and
   needs no media queries. Breakpoint values must match --bp-tablet /
   --bp-desktop in tokens.css (media queries can't read custom properties).
   ========================================================================== */

/* ---- Tablet and up (>= 720px): switch to the 8-column grid ---- */
@media (min-width: 720px) {
  :root {
    --grid-columns: 8;
  }

  .masthead__worker {
    grid-column: 1 / 5;
  }

  .masthead__contact {
    grid-column: 5 / 9;
    display: flex;
  }

  .masthead__worker,
  .masthead__contact {
    gap: 0;
  }

  .masthead__links {
    display: flex;
  }

  .masthead__row {
    grid-template-columns: clamp(90px, 7.8vw, 150px) 1fr max-content;
    gap: var(--space-md);
  }

  /* Worker rows have no label anymore: let the text span the full row. */
  .masthead__worker .masthead__row > span {
    grid-column: 1 / -1;
  }

  /* Contact rows have no label: drop the (empty) label column so the links
     can't overflow the column at tablet widths (e.g. iPad portrait). */
  .masthead__contact .masthead__row {
    grid-template-columns: minmax(0, 1fr) max-content;
  }

  .masthead__contact .masthead__row > span:first-child {
    display: none;
  }

  .tabs__tab[data-file-tab="index"] {
    grid-column: 1 / 3;
  }

  .tabs__tab[data-file-tab="worker"] {
    grid-column: 3 / 5;
  }

  .project__title {
    grid-column: 1 / 7;
    grid-row: 2;
  }

  .project__media {
    aspect-ratio: 16 / 10;
  }

  .case__figure-media {
    aspect-ratio: 16 / 10;
  }

  /* Tablet: the meta pair stays together on row 1, spanning the full card
     width (the index numeral sits at the left, the pair hugs the right
     edge) so long pairs like "The LEGO Group — 2025 — 2026" never wrap
     in narrow columns. Desktop (>= 1024px) dissolves the wrapper and
     moves the year and client into column 8. */
  .project__meta {
    grid-column: 1 / -1;
    grid-row: 1;
  }

  .case__lead {
    grid-column: 1 / 5;
    grid-row: 1;
  }

  .case__info {
    grid-column: 5 / 9;
    grid-row: 1;
  }

  .case__rule {
    display: block;
    grid-column: 5 / 6;
    grid-row: 1;
    justify-self: start;
    /* Center the line in the gutter between the two halves. */
    margin-left: calc(-0.5 * var(--grid-gutter));
    width: var(--hairline);
    height: 100%;
    background: var(--color-border);
  }

  .case__figure--pair {
    grid-column: span 4;
  }

  .worker__intro--left {
    grid-column: 1 / 5;
    grid-row: 1;
  }

  .worker__intro--right {
    grid-column: 5 / 9;
    grid-row: 1;
  }

  .worker__column--left {
    grid-column: 1 / 5;
    grid-row: 1;
  }

  .worker__column--right {
    grid-column: 5 / 9;
    grid-row: 1;
  }

  .worker__rule {
    display: block;
    grid-column: 5 / 6;
    grid-row: 1;
    justify-self: start;
    /* Center the line in the gutter between the two 4-column halves. */
    margin-left: calc(-0.5 * var(--grid-gutter));
    width: var(--hairline);
    height: 100%;
    background: var(--color-border);
  }

  .worker__banner {
    padding: 0;
  }

  .worker__portrait {
    grid-column: span 2;
  }

  .footer__mark {
    grid-column: 4 / 6;
    grid-row: 1;
    justify-content: center;
    align-self: stretch; /* lock the gif to the footer text's height */
    padding-block: 0;
    position: relative;
  }

  /* The gif fills (and scales with) the text column height. It is taken
     out of flow so it can't make the row taller than the text itself. */
  .footer__mark img {
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    height: 100%;
    width: auto;
  }

  .footer__column--left {
    grid-column: 1 / 4;
    grid-row: 1;
    align-self: end;
  }

  .footer__column--right {
    grid-column: 6 / 9;
    grid-row: 1;
    align-self: end;
  }

  /* Right-aligned against the content column's right edge, mirroring the
     masthead's link alignment. */
  .footer__column--right .footer__row {
    text-align: right;
  }

  .footer__column {
    gap: 0;
  }

  .footer__links {
    justify-content: flex-end;
  }
}

/* ---- Desktop and up (>= 1024px) ----
   The meta pair now stays as one unit on the left at every size, so the
   old desktop column-8 split is gone. */
