/* ============================================================
   WORK PAGES (assets/work/*.html) — project detail layout
   Top bar with close, hero media CAROUSEL, intro block, data
   sheet, bottom prev/next bar.

   This file now includes the carousel rules that used to live in
   hero-carousel.css: the generated pages only link style.css and
   this one, so a separate file was never loaded.

   The page background and BOTH bars share one fixed ramp:
   with background-attachment: fixed the positioning area is the
   viewport, so each bar shows exactly the slice of gradient that
   would sit behind it — they read as a single continuous surface.
   Flip a whole page with <body class="work-page ramp flip-h">;
   the bars inherit the anchors and follow along.
   ============================================================ */

:root { --work-bar-h: 4.5rem; }

body.work-page {
  /* pages scroll normally: no fullscreen sections here */
  padding-top: var(--work-bar-h);
  padding-bottom: var(--work-bar-h);
}

/* NOTHING on this page is pinned to the viewport any more.

   A fixed background — whether through background-attachment: fixed or
   a position: fixed layer — stays still while the page scrolls over it.
   Everywhere else that reads as a nice effect, but around a composited
   iframe Chrome paints the two at different moments, and the stationary
   gradient shows through as a sheet sliding behind the piece.

   With the gradient scrolling along with the content there is no
   relative motion left to perceive. The bars keep theirs anchored,
   which is right: they are fixed elements and never scroll. */
.work-topbar,
.work-nav {
  background-attachment: fixed;
}

body.work-page.ramp,
body.work-page {
  background-attachment: scroll;
}
body.work-page::before { content: none; }

/* ---------- top bar ---------- */
.work-topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 30;
  min-height: var(--work-bar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem var(--pad);
  background-color: var(--ramp-cb);
  background-image:
    radial-gradient(at var(--rx-a) var(--ry-a), var(--ramp-c1) 0%, transparent 65%),
    radial-gradient(at var(--rx-b) var(--ry-a), var(--ramp-c2) 0%, transparent 65%),
    radial-gradient(at var(--rx-a) var(--ry-b), var(--ramp-c3) 0%, transparent 65%),
    radial-gradient(at var(--rx-b) var(--ry-b), var(--ramp-c4) 0%, transparent 65%);
}
.work-topbar .logo {
  font-size: 1.05rem;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--text);
  text-decoration: none;
}
.work-topbar .logo .light { font-weight: 250; }
.work-topbar .logo strong { font-weight: 400; }

.work-topbar .close {
  display: block;
  width: 1.75rem; height: 1.75rem;
  color: var(--text);
  opacity: 0.85;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.work-topbar .close:hover { opacity: 1; transform: rotate(90deg); }
.work-topbar .close svg {
  width: 100%; height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}

/* ============================================================
   HERO — a carousel of .hero-item stacked on top of each other.
   Only the one with .is-active is visible; swipe-nav.js cycles
   them with mouse or touch and builds the dots.
   ============================================================ */
.work-hero {
  position: relative;
  margin: 0;
  height: 62svh;
  min-height: 320px;
  overflow: hidden;
  touch-action: pan-y;          /* horizontal gesture is ours, vertical scrolls */
}
.work-hero.has-carousel { cursor: grab; }
.work-hero.has-carousel:active { cursor: grabbing; }

/* Every item fills the hero. Without a carousel only the active one
   is shown (plain fade). With a carousel the hero becomes a TRACK:
   JS writes each item's transform so the strip follows the pointer,
   and the neighbours must stay visible while they slide in and out —
   that is what makes the gesture feel physical instead of mechanical. */
.hero-item {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.45s ease, visibility 0s linear 0.45s;
}
.hero-item.is-active {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.45s ease, visibility 0s;
}

/* ---------- track mode (set by swipe-nav.js when there is more than
   one item): position comes from the inline transform, and anything
   adjacent stays painted so it can be seen entering ---------- */
.is-track .hero-item {
  opacity: 1;
  visibility: hidden;
  transition: none;                 /* JS decides when to animate */
  will-change: transform;
  backface-visibility: hidden;
}
.is-track .hero-item.is-near { visibility: visible; }
.is-track.is-dragging { cursor: grabbing; }
.is-track.is-dragging .hero-item { user-select: none; }

/* the media must not be dragged away as an image while swiping */
.is-track .hero-item img,
.is-track .hero-item video { -webkit-user-drag: none; user-select: none; }

/* someone who asked for less motion gets a plain cut */
@media (prefers-reduced-motion: reduce) {
  .hero-item { transition: opacity 0.2s ease, visibility 0s linear 0.2s; }
}

/* media inside an item, plus any extra media further down the page.
   Each item carries its own framing vars, so a vertical clip and a
   wide one can live in the same carousel. */
.hero-item .still,
.hero-item video,
.work-media .still,
.work-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: transparent;
  object-position: var(--v-pos, 50% 50%);
  transform: scale(var(--v-scale, 1));
}
.hero-item video,
.work-media video { opacity: 0; transition: opacity 0.6s ease; }
.hero-item video.ready,
.work-media video.ready { opacity: 1; }

/* ---------- "show it whole" mode ----------
   object-fit: contain fits the entire piece inside the frame instead
   of filling and cropping it. The gradient of the section shows in the
   bands, so a vertical clip on a wide screen reads as intentional.
   Framing and zoom are meaningless here: nothing is being cropped. */
.hero-item.is-contain .still,
.hero-item.is-contain video {
  object-fit: contain;
  object-position: center;
  transform: none;
}

@media (orientation: portrait) {
  .hero-item:not(.is-contain) .still,
  .hero-item:not(.is-contain) video,
  .work-media .still,
  .work-media video {
    object-position: var(--v-pos-p, var(--v-pos, 50% 50%));
    transform: scale(var(--v-scale-p, var(--v-scale, 1)));
  }
}

/* ---------- YouTube / Vimeo facade ----------
   The iframe is inserted only when the play button is clicked, so an
   embed costs nothing (no vendor script, no cookies) until asked for. */
.hero-item.is-embed .hero-play {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 3;
  width: 4.5rem; height: 4.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  color: #fff;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}
.hero-item.is-embed .hero-play:hover {
  background: rgba(0, 0, 0, 0.55);
  transform: translate(-50%, -50%) scale(1.06);
}
.hero-item.is-embed .hero-play svg { width: 45%; height: 45%; fill: currentColor; }

.hero-item iframe {
  position: absolute;
  inset: 0;
  z-index: 4;
  width: 100%;
  height: 100%;
  border: 0;
  background: #000;
}
.hero-item.is-playing .hero-play { display: none; }

/* ---------- dot indicators (built by swipe-nav.js) ---------- */
.hero-dots {
  position: absolute;
  left: 0; right: 0;
  bottom: 1.25rem;
  z-index: 5;
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  pointer-events: auto;         /* the dots are clickable */
}
.hero-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.35);
  cursor: pointer;
  transition: background 0.25s ease, transform 0.25s ease;
}
.hero-dot:hover { background: rgba(255, 255, 255, 0.7); }
.hero-dot.is-active {
  background: #fff;
  transform: scale(1.35);
}

/* extra media further down the page */
.work-media {
  position: relative;
  margin: 3rem 0 0;
  height: 56svh;
  min-height: 280px;
  overflow: hidden;
}

/* ---------- body ---------- */
.work-body {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 3.5rem var(--pad) 4rem;
}

.label {
  margin: 0 0 0.5rem;
  font-size: 0.8rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* intro: titles left, description right */
.work-intro {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(1.5rem, 5vw, 4rem);
  align-items: start;
}
.work-titles h1 {
  margin: 0 0 0.6rem;
  font-size: clamp(1.9rem, 3.4vw, 2.6rem);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: 0.005em;
}
.work-titles .kind {
  margin: 0;
  font-size: 1rem;
  font-weight: 250;
  color: var(--text-dim);
}
.work-text p {
  margin: 0 0 0.4rem;
  max-width: 60ch;
  font-weight: 250;
  font-size: 1.05rem;
  line-height: 1.55;
}

/* ---------- data sheet: 3 columns with vertical rules ---------- */
.work-specs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  margin: 3.5rem 0 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}
.work-specs > div { padding: 0 clamp(1rem, 3vw, 2.5rem); }
.work-specs > div:first-child { padding-left: 0; }
.work-specs > div + div { border-left: 1px solid var(--line); }

.work-specs dt {
  margin-bottom: 0.75rem;
  font-size: 0.8rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.work-specs dd { margin: 0; }
.work-specs ul {
  list-style: none;
  margin: 0; padding: 0;
  font-weight: 250;
  line-height: 1.6;
}
.work-specs .year { color: var(--text-dim); }

/* ---------- bottom nav bar ---------- */
.work-nav {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  z-index: 30;
  height: var(--work-bar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--pad);
  background-color: var(--ramp-cb);
  background-image:
    radial-gradient(at var(--rx-a) var(--ry-a), var(--ramp-c1) 0%, transparent 65%),
    radial-gradient(at var(--rx-b) var(--ry-a), var(--ramp-c2) 0%, transparent 65%),
    radial-gradient(at var(--rx-a) var(--ry-b), var(--ramp-c3) 0%, transparent 65%),
    radial-gradient(at var(--rx-b) var(--ry-b), var(--ramp-c4) 0%, transparent 65%);
}
.work-nav a {
  color: var(--text);
  text-decoration: none;
  font-size: 0.9rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.85;
  transition: opacity 0.15s ease;
}
.work-nav a:hover { opacity: 1; }
.work-nav .prev { margin-right: auto; }
.work-nav .next { margin-left: auto; }

/* ============================================================
   NARROW SCREENS — stack everything, no vertical rules
   ============================================================ */
@media (max-width: 800px) {
  :root { --work-bar-h: 3.75rem; }

  .work-hero { height: 45svh; }
  .work-body { padding-top: 2.5rem; }

  .work-intro {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }

  /* one column, separated by horizontal rules */
  .work-specs {
    grid-template-columns: 1fr;
    margin-top: 2.5rem;
  }
  .work-specs > div { padding: 1.5rem 0 2.5rem; }
  /* same selector shape as the base rule, so it actually wins:
     media queries add zero specificity */
  .work-specs > div + div {
    border-left: none;
    border-top: 1px solid var(--line);
  }

  .work-topbar .logo { font-size: 0.95rem; }
  .work-nav a { font-size: 0.78rem; }
}

/* ============================================================
   INTERACTIVE PIECE
   A page of this same site embedded below the data sheet, in its
   own full-width band: it needs the mouse drag for itself, which
   is why it is not one more item in the hero carousel.
   Linkable on its own with #interactive at the end of the URL.
   ============================================================ */
.work-interactive {
  margin: clamp(3rem, 8vh, 5rem) 0 0;
  padding-top: 2rem;
  border-top: 1px solid var(--line);
  /* arriving from a #interactive link would leave the heading tucked
     under the fixed bar: this keeps it clear of it */
  scroll-margin-top: calc(var(--work-bar-h) + 1rem);
}
.work-interactive-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.2rem;
}
.work-interactive-head .label { margin: 0; }

.iv-actions {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  white-space: nowrap;
}
.iv-link,
.iv-open {
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-decoration: none;
  transition: color 0.15s ease;
}
.iv-link:hover,
.iv-open:hover { color: var(--text); }

/* a moment of emphasis for whoever arrives through the link, so it is
   clear which part of the page the address was pointing at */
.work-interactive:target .label { color: var(--text); }
.work-interactive:target .iv-frame { border-color: var(--text-dim); }

.iv-frame {
  position: relative;
  width: 100%;
  height: var(--iv-h, 80svh);
  min-height: 320px;
  border: 1px solid var(--line);
  /* No border-radius and no overflow: a rounded clip around a
     composited iframe puts Chrome on a path where the mask updates a
     frame behind the content, which reads as the layer sliding. */
  transition: border-color 0.3s ease;

  /* its own compositing boundary, so Chrome commits to one stacking
     order for this box instead of re-resolving it against the fixed
     bars elsewhere on the page every frame during scroll */
  isolation: isolate;

  /* Transparent once the piece is live: a background painted here
     sits in the SAME stacking context as the iframe's own compositor
     layer, and if that background is ever painted after the iframe's
     layer on a given frame instead of before it, it shows as a solid
     block over the content — a close match for the blue, then white,
     block that kept appearing here regardless of what the iframe
     itself was doing. Only while there is nothing to look at yet does
     the box need a fill at all, so the button reads. */
  background: transparent;
}
.iv-frame:not(.is-live) { background: var(--ramp-cb); }
.iv-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* shown until the piece loads, and the way in when the browser has no
   IntersectionObserver */
.iv-play {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  padding: 0.8rem 1.7rem;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: rgba(0, 0, 0, 0.35);
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.iv-play:hover { border-color: var(--text); background: rgba(0, 0, 0, 0.5); }
.iv-frame.is-live .iv-play { display: none; }

@media (max-width: 800px) {
  .iv-frame { height: 60svh; }
  .work-interactive-head { flex-direction: column; align-items: flex-start; gap: 0.6rem; }
}
