/* ==========================================================================
   Creek Studio V4 — components
   Work gallery preview (spec 6.7), Info drawer (6.8), video containers (6.6).
   Tokens come from the :root block in creek.css; loaded after creek.css and
   motion.css. The only literal durations are the two the spec gives without a
   token, and each is declared once here and read by its script.
   ========================================================================== */

/* ---------- 1. Work gallery: hover preview and row dimming ----------
   gallery.js builds one figure on fine pointers and moves it with a transform.
   This file owns how it opens, closes and swaps its picture, and how the rows
   around the hovered one dim. Coarse pointers never get the figure: creek.css
   draws an inline thumbnail in each row instead. */
.gallery-preview {
  /* the close runs on --t-out (6.7: 360ms); gallery.js reads the resolved value
     from this element, so the fallback is only for a page without that token */
  --gallery-close: var(--t-out, 360ms);
  position: fixed;
  left: 0;
  top: 0;
  z-index: 40;                       /* under the fixed header (50) and the drawer */
  width: var(--preview-w);
  margin: 0;
  pointer-events: none;
  will-change: transform, clip-path;
  clip-path: inset(0 0 100% 0);
  transition: clip-path var(--gallery-close) var(--ease-in);
}
.gallery-preview.is-open {
  clip-path: inset(0 0 0 0);
  transition: clip-path var(--t-4) var(--ease);
}
.gallery-preview .media-frame {
  position: relative;
  aspect-ratio: 4 / 5;               /* gallery.js sets the row's own ratio inline */
  overflow: hidden;
  background: var(--mist);
}
.gallery-preview .media-frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  scale: none;                       /* not a page image: no drift, no hover scale */
  translate: none;
  transition: opacity var(--t-2) var(--ease);
}
.gallery-preview .media-frame img.is-front { opacity: 1; z-index: 1; }
/* the picture being replaced stays opaque underneath while the new one fades in,
   so the crossfade never dips to the mist between two photographs */
.gallery-preview .media-frame img.is-under { opacity: 1; transition: none; }

/* creek.css already transitions .index-row opacity over --t-2; this only sets the level */
[data-gallery].is-hovering .index-row:not(.is-active) { opacity: var(--gallery-dim, 0.35); }

/* ---------- 2. Info drawer ----------
   Fixed at the right, columns 7–12 plus the margin, its own scroll. The page
   beneath steps one column left and fades; the scrim is transparent (that fade
   is the dimming) and only exists to catch the click. The close runs on
   --t-drawer-out (6.8: 480ms, the DRAWER.closeMs fallback repeats it) and the
   page fades to --drawer-fade, which info.js sets on <html> from DRAWER. */
.info {
  --info-pad-top: calc(26px + env(safe-area-inset-top, 0px));   /* the header's own top padding */
  position: fixed;
  top: 0;
  right: 0;
  z-index: 56;                       /* above the header (50) and scrim (55), under the cursor */
  width: var(--drawer-w);
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding:
    var(--info-pad-top)
    max(var(--m), env(safe-area-inset-right, 0px))
    calc(var(--sp-6) + env(safe-area-inset-bottom, 0px))
    var(--g);
  background: var(--ground);
  color: var(--ink);
  border-left: 1px solid var(--rule);
  translate: 100% 0;
  visibility: hidden;
  transition:
    translate var(--t-drawer-out, 480ms) var(--ease-in),
    visibility 0s linear var(--t-drawer-out, 480ms);
}
.info.is-open {
  translate: 0 0;
  visibility: visible;
  transition: translate var(--t-5) var(--ease), visibility 0s;
}
/* focus lands on the dialog itself first; the ring belongs to its links */
.info:focus-visible { outline: none; }
/* a fragment inside the drawer (#info-services) lands below the drawer's own top padding,
   not under the page's header clearance that creek.css gives every [id] */
.info [id] { scroll-margin-top: var(--info-pad-top); }

/* contents rise in behind the slide, staggered by --d-step and capped at three steps */
.info-inner > * { --i: 3; }
.info-inner > :nth-child(1) { --i: 0; }
.info-inner > :nth-child(2) { --i: 1; }
.info-inner > :nth-child(3) { --i: 2; }
.info.is-open .info-inner > * {
  animation: info-rise var(--t-3) var(--ease) calc(var(--i) * var(--d-step)) both;
}
@keyframes info-rise {
  from { opacity: 0; translate: 0 var(--rise); }
  to { opacity: 1; translate: 0 0; }
}

/* main and footer sit inside .page (creek.css); body and html are never transformed,
   so the fixed header keeps its viewport and moves by the same pitch in its own rule,
   with its scroll hide folded in so the two translates never fight */
body.info-open .page,
body.info-open .site-header {
  translate: calc(-1 * var(--pitch)) 0;
  opacity: var(--drawer-fade, 0.4);
  transition: translate var(--t-5) var(--ease), opacity var(--t-5) var(--ease);
}
body.info-open .site-header.is-hidden { translate: calc(-1 * var(--pitch)) -100%; }
/* info.js holds this class for the length of the close so the page returns on the
   exit curve without the transition living on .page for the rest of the session */
body.info-closing .page,
body.info-closing .site-header {
  transition:
    translate var(--t-drawer-out, 480ms) var(--ease-in),
    opacity var(--t-drawer-out, 480ms) var(--ease-in);
}
/* a drawer the page arrived with (#info in the URL) is already open: no slide, no
   rise, no page shift; info.js drops the class on close so that close still animates */
body.info-instant .info,
body.info-instant .info .info-inner > *,
body.info-instant .page,
body.info-instant .site-header { transition: none; animation: none; }

.scrim {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 55;
  background: transparent;
}
.scrim.is-on { display: block; }
html.has-cursor .scrim.is-on { cursor: none; }

/* drawer head: INFO at the left edge, CLOSE on the nav's right edge, 44px tall target */
.info-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--g);
  margin-bottom: var(--sp-8);
}
.info-close {
  position: relative;
  display: inline-block;
  min-width: 44px;
  padding: 14px 0;
  margin: -14px 0;
  text-align: right;
}
.info-close::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 14px;
  height: 1px;
  background: currentColor;
  scale: 0 1;
  transform-origin: left;
  transition: scale var(--t-2) var(--ease);
}
.info-close:hover::after,
.info-close:focus-visible::after { scale: 1 1; }

/* drawer typography: label above each section, sections a service apart */
.info-section + .info-section { margin-top: var(--sp-7); }
.info-label { margin-bottom: var(--sp-2); }
.info .h3 {
  font-size: min(40px, var(--fs-h3));   /* 7.5: h3 40 inside the drawer, never wider than the ladder */
  line-height: 0.98;
  letter-spacing: -0.015em;
}
.info-step + .info-step,
.info-service + .info-service { margin-top: var(--sp-6); }
.info-step .h3 + .body,
.info-service .h3 + .body { margin-top: var(--sp-3); }
.info-service .lines {
  margin-top: var(--sp-3);
  columns: 2;
  column-gap: var(--g);
  max-width: 34em;
}
.info-service .lines li { break-inside: avoid; padding-block: 2px; }
.info .email {
  display: inline-block;
  margin-top: var(--sp-2);
  padding-bottom: 6px;
  font-size: min(40px, var(--fs-h3));
  font-weight: 600;
  line-height: 0.98;
  letter-spacing: -0.015em;
  text-transform: uppercase;
  overflow-wrap: anywhere;
  white-space: normal;
  background: linear-gradient(currentColor, currentColor) 0 100% / 0% 2px no-repeat;
  transition: background-size var(--t-2) var(--ease);
}
.info .email:hover,
.info .email:focus-visible { background-size: 100% 2px; }
.info-contact-lines { margin-top: var(--sp-3); }
.info-contact-lines a { display: inline-block; padding-block: 5px; margin-block: -5px; }
.info-colophon {
  margin-top: var(--sp-7);
  max-width: 62ch;
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: 0.02em;
  text-transform: none;
}

@media (max-width: 899px) {
  .info {
    --info-pad-top: calc(16px + env(safe-area-inset-top, 0px));
    border-left: 0;                  /* full width: the page edge is the rule */
    padding-inline:
      max(var(--m), env(safe-area-inset-left, 0px))
      max(var(--m), env(safe-area-inset-right, 0px));
  }
}

/* Without JS the aside is a plain section at the end of the document, reached by
   the #info anchor; there is nothing to close and no scrim to click. */
html:not(.js) .info {
  position: static;
  width: auto;
  height: auto;
  overflow: visible;
  translate: none;
  visibility: visible;
  border-left: 0;
  padding:
    var(--sp-10)
    max(var(--m), env(safe-area-inset-right, 0px))
    var(--sp-10)
    max(var(--m), env(safe-area-inset-left, 0px));
  background: linear-gradient(var(--rule), var(--rule)) var(--m) 0 / calc(100% - 2 * var(--m)) 1px no-repeat;
}
html:not(.js) .info-close,
html:not(.js) .scrim { display: none; }

/* the keyboard's control: a 44px corner target, visible only when focused */
[data-video] .video-toggle {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  opacity: 0;
}
[data-video] .video-toggle:focus-visible { opacity: 1; outline-offset: -4px; }

/* ---------- 3. Video containers ----------
   The video fills its frame exactly as an image does: cover, the frame's ratio
   class, the same drift and hover scale. The poster shows through until the
   first frame arrives; video.js injects the sources and drives playback. */
[data-video] { cursor: pointer; }
[data-video] .media-frame video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  --s-base: 1;
  scale: var(--s-base);
  translate: 0 calc(var(--drift, 0) * 1%);
  transition: scale var(--t-2) var(--ease);
}
@media (min-width: 900px) {
  [data-drift][data-video] .media-frame video,
  [data-drift] [data-video] .media-frame video { --s-base: 1.08; }
}
@media (hover: hover) {
  [data-video]:hover .media-frame video { scale: calc(var(--s-base) + 0.02); }
}
/* the control video.js adds: a real button, visually hidden until keyboard focus brings
   it into the frame's corner as a meta label */
.video-toggle {
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  background: none;
  color: inherit;
  font: inherit;
}
.video-toggle:focus-visible {
  left: var(--sp-2);
  top: var(--sp-2);
  width: auto;
  height: auto;
  margin: 0;
  padding: 6px 8px 5px;
  clip-path: none;
  background: var(--ink);
  color: var(--ground);
  font-size: var(--fs-meta);
  font-weight: 500;
  line-height: var(--lh-meta, 16px);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  outline-offset: 2px;
}

/* ---------- 4. Reduced motion and print ---------- */
/* creek.css zeroes every duration under reduced motion; this file's own pieces end
   in the same still state without it: no slide, no wipe, no rise, no page shift */
@media (prefers-reduced-motion: reduce) {
  .gallery-preview,
  .gallery-preview.is-open,
  .gallery-preview .media-frame img,
  .info,
  .info.is-open,
  .info.is-open .info-inner > *,
  body.info-open .page,
  body.info-open .site-header,
  body.info-closing .page,
  body.info-closing .site-header {
    --t-out: 0ms;
    --t-drawer-out: 0ms;
    transition-duration: 0s;
    transition-delay: 0s;
    animation-duration: 0s;
    animation-delay: 0s;
  }
}

@media print {
  .gallery-preview,
  .scrim,
  .info-close { display: none !important; }
  .info {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    translate: none;
    visibility: visible;
    border-left: 0;
    padding: 12mm 0 0;
  }
  .info-inner > * { animation: none !important; }
  body.info-open .page,
  body.info-open .site-header { translate: none; opacity: 1; }
}

/* ---------- Marquee (marquee.js) ----------
   A row that travels right to left and loops; items stand on a common baseline and
   swell toward the middle of the row, so the row keeps headroom for the swell.
   Under reduced motion, or without JS, the row scrolls sideways by hand instead. */
.marquee { --marquee-h: 240px; --marquee-gap: 56px; overflow: hidden; padding-top: calc(var(--marquee-h) * 0.2); }
.marquee-track {
  display: flex; align-items: flex-end; gap: var(--marquee-gap);
  width: max-content; margin: 0; padding: 0 var(--marquee-gap) 6px; list-style: none;
}
.marquee.is-live .marquee-track { will-change: transform; }
.marquee-item { flex: none; transform-origin: 50% 100%; }
.marquee.is-live .marquee-item { will-change: transform; }
.marquee-item img { display: block; height: var(--marquee-h); width: auto; }
.reduced .marquee, html:not(.js) .marquee { overflow-x: auto; scrollbar-width: none; }
.reduced .marquee::-webkit-scrollbar, html:not(.js) .marquee::-webkit-scrollbar { display: none; }
@media (max-width: 899px) { .marquee { --marquee-h: 150px; --marquee-gap: 32px; } }

/* ---------- Strip (strip.js) ----------
   Figures side by side that swipe: native scroll with snap points, drag with a mouse,
   previous/next for the keyboard. One item hides the nav. */
.strip-track {
  display: flex; gap: var(--g); margin: 0; padding: 0; list-style: none;
  overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.strip-track::-webkit-scrollbar { display: none; }
.strip-track.is-dragging { cursor: grabbing; scroll-snap-type: none; user-select: none; }
.strip-item { flex: 0 0 100%; min-width: 0; scroll-snap-align: start; }
.strip-item .media { width: 100%; }
.strip-nav { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-2); }
.strip-nav[hidden] { display: none; }
.strip-btn { padding: 0; border: 0; background: none; font: inherit; color: inherit; cursor: pointer; min-height: 24px; }
.strip-btn:focus-visible { outline: 1.5px solid currentColor; outline-offset: 4px; }
.strip-count { color: var(--ink-2); font-variant-numeric: tabular-nums; }
@media (hover: hover) and (pointer: fine) { .strip-track { cursor: grab; } }
