/* The about photo, opened big (photo-zoom.js), Figma keyan-folio-v2 619:32502 and, clipped, 623:32846.
   The page behind blurs by --zoom-blur under a --zoom-wash wash; the print sits in the middle of the window, the
   caption under it. No close button: Escape or a click on the blur closes it. Values in tokens.css as --zoom-*. */

/* The photo on the page is the button that opens it. --zoom-press is its press, before either opens (photo-zoom.js). */
.lede .snap {
  --zoom-press: 1;
  cursor: pointer;
}

.lede .snap:focus-visible {
  outline: 1px solid var(--mute);
  outline-offset: 4px;
}

/* The press, on .bob's own scale, so the bob (transform) and the tilt (.print) still run. */
.lede .snap .bob {
  scale: var(--zoom-press);
}

/* Zoom: the small photo lifts off its shadow while the big one is out, and holds still, so it lands back where it left. */
.lede .snap.zoomed .bob {
  visibility: hidden;
}

.lede .snap.zoomed :is(.bob, .shadow) {
  animation-play-state: paused;
}

/* Over the bar and a case study, under the grain (style.css, body::after), the Tune panel and the cursor. Clipped,
   it stops at the canvas's foot (--zoom-bottom, photo-zoom.js), so the bar stays sharp and live. The print fits the
   window, with room below for the caption, and sits in the window's middle either way. */
.zoom {
  --zoom-w: min(var(--zoom-width), (100vw - var(--zoom-right, 0px)) * var(--zoom-fit-w), (100dvh - var(--zoom-bottom, 0px)) * var(--zoom-fit-h) * 1.5);
  --zoom-ease: cubic-bezier(var(--zoom-fade-curve));
  position: fixed;
  z-index: 9000;
  inset: 0 0 var(--zoom-bottom, 0px);
  display: grid;
  place-items: center;
  overscroll-behavior: contain;
}

.zoom[hidden] {
  display: none;
}

/* Figma: a 16.9px background blur and #f7f7f7 at 8%, faded in and out whole. Bruce Cao's: 24px and black at 85%. */
.zoom-blur {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--zoom-wash) calc(var(--zoom-wash-opacity) * 100%), transparent);
  -webkit-backdrop-filter: blur(var(--zoom-blur));
  backdrop-filter: blur(var(--zoom-blur));
  opacity: 0;
  transition: opacity var(--zoom-fade-out) var(--zoom-ease);
}

/* The print's box, the small one's (138 by 92: 5px of white round a 128 by 82 photo) at --zoom-w. The caption hangs
   under it, and the two are centred in the window together: the box sits in the middle, lifted by --zoom-lift, half the
   caption and its gap (photo-zoom.js measures it). While the Tune panel shows down the right, the middle is that of the
   room left of it (--zoom-right, photo-zoom.js), so the photo looks centred as it is tuned. The flight moves .zoom-item. */
.zoom-figure {
  position: fixed;
  inset: 0 var(--zoom-right, 0px) 0 0;
  width: var(--zoom-w);
  height: calc(var(--zoom-w) * 92 / 138);
  margin: auto;
  translate: 0 calc(-1 * var(--zoom-lift, 0px));
}

/* A drag across the caption's words selects them, not the photo, which would turn grey under --highlight. */
.zoom-item {
  position: absolute;
  inset: 0;
  -webkit-user-select: none;
  user-select: none;
}

/* The plain photo, framed as the small print is, the frame in step with its size, and straight: the zoom turns it out
   of the small one's tip on the way (photo-zoom.js). The shadow falls down and right: light from the top left. */
.zoom-print {
  position: absolute;
  inset: 0;
  padding: calc(var(--zoom-w) * 5 / 138);
  background: #fff;
  box-shadow: 0 0 0 0.5px rgba(71, 67, 60, 0.14), 3px 8px 28px rgba(50, 45, 42, 0.12);
}

.zoom-print img {
  display: block;
  width: 100%;
  height: 100%;
}

/* The same grain as the small photo's, at the same size on screen. */
.zoom-print::before {
  content: '';
  position: absolute;
  inset: calc(var(--zoom-w) * 5 / 138);
  opacity: var(--photo-grain);
  mix-blend-mode: multiply;
  background: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E") 0 0 / var(--photo-grain-size) repeat;
  pointer-events: none;
}

.zoom-print::after {
  content: '';
  position: absolute;
  inset: calc(var(--zoom-w) * 5 / 138);
  box-shadow: inset 2px 4px 10px rgba(50, 45, 42, 0.3);
  mix-blend-mode: multiply;
  pointer-events: none;
}

/* The big pixel art, drawn at --zoom-art-pixel and made bigger nearest; photo-zoom.js sizes and places it on whole
   device pixels, with room round the print for its steps. */
.zoom-art {
  position: absolute;
  image-rendering: pixelated;
}

/* The small pixel art the zoom starts from, made bigger nearest. Its box is the art's (the tip makes it wider than the
   print), centred on the print. Its tip is in its pixels, so it turns back by the tip as the print turns into it. */
.zoom-from {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  rotate: calc(-1 * var(--photo-turn));
  image-rendering: pixelated;
}

.zoom-from[hidden],
.zoom:not(.as-art) .zoom-art,
.zoom.as-art .zoom-print {
  display: none;
}

.zoom-caption {
  position: absolute;
  top: calc(100% + var(--zoom-caption-gap));
  left: 50%;
  width: max-content;
  max-width: var(--zoom-w);
  margin: 0;
  translate: -50% 0;
  color: var(--zoom-caption-ink);
  font-size: var(--zoom-caption-size);
  font-family: var(--zoom-caption-face), 'TWK Lausanne', system-ui, sans-serif;
  font-weight: 400;
  line-height: 1.4;
  text-align: center;
  opacity: 0;
  filter: blur(var(--zoom-caption-blur));
  transition: opacity var(--zoom-fade-out) var(--zoom-ease), filter var(--zoom-fade-out) var(--zoom-ease);
}

/* The layer takes focus as it opens (photo-zoom.js), so Escape closes it from the keyboard. It needs no ring. */
.zoom:focus {
  outline: none;
}

/* In over --zoom-fade, out over --zoom-fade-out: a transition takes the timing of the state it goes to. */
.zoom.open :is(.zoom-blur, .zoom-caption) {
  opacity: 1;
  transition-duration: var(--zoom-fade), var(--motion-fast);
}

/* The caption comes in --zoom-caption-delay after the rest, sharpening as it comes, and goes at once. */
.zoom.open .zoom-caption {
  filter: blur(0);
  transition-duration: var(--zoom-fade);
  transition-delay: var(--zoom-caption-delay);
}

/* The big view's two buttons, top right of the room left of the Tune panel (image-zoom.js): the bar's links (style.css,
   links.js), a size up. The --link-* values the bar's buttons read are set here from --zoom-act-*, so the chip, its lean
   and the icon's all grow with the button. They fade in and out with the blur. */
.zoom-acts {
  --link-size: var(--zoom-act-size);
  --link-icon: var(--zoom-act-icon);
  --link-gap: var(--zoom-act-gap);
  --link-bg-size: var(--zoom-act-bg-size);
  --link-bg-radius: var(--zoom-act-bg-radius);
  --link-pull: var(--zoom-act-pull);
  --link-icon-pull: var(--zoom-act-icon-pull);
  position: fixed;
  z-index: 1;
  top: var(--zoom-act-inset);
  right: calc(var(--zoom-right, 0px) + var(--zoom-act-inset));
  padding: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--zoom-fade-out) var(--zoom-ease);
}

/* In, they fade slower than the blur and a moment after it, --zoom-acts-delay then over --zoom-acts-fade, so they come
   with the chevrons rather than popping in with the blur (Keyan, 2026-10-15: "fade in the overlay buttons too"; they
   took the blur's 220ms, near full in 100ms). */
.zoom.open .zoom-acts {
  opacity: 1;
  pointer-events: auto;
  transition-duration: var(--zoom-acts-fade);
  transition-delay: var(--zoom-acts-delay);
}

.zoom-acts [hidden] {
  display: none;
}

/* Any picture in a case study, and any picture or video in a tidbit, opens big too (image-zoom.js), on this same layer and blur, and so does a click on a tidbit's
   grey plate round it. Each picture is a button. Its ring sits inside its edge, since the box round it clips anything outside. */
.case-main figure img,
.extras figure img,
.extras figure video,
.extras .plate.grey {
  cursor: pointer;
}

.case-main figure img:focus-visible,
.extras figure img:focus-visible,
.extras figure video:focus-visible {
  outline: 1px solid var(--mute);
  outline-offset: -1px;
}

/* The big picture, at its own shape and the size image-zoom.js gives it, in the middle of the room left of the Tune
   panel. It starts as the small one's own picture (its background) while the sharper one loads over it. The shadow
   falls down and right, as the about photo's: light from the top left. --lift (image-zoom.js) grows it in as the
   picture rises and takes it away as it lands, as the small one on the page has none. Its corners are
   --image-zoom-radius once it lands (image-zoom.js runs them there from the small one's), and its hairline ring is
   --zoom-outline-width of --zoom-outline-color at --zoom-outline-opacity (0 width for none). */
.image-zoom-box {
  --lift: 1;
  position: fixed;
  inset: 0 var(--zoom-right, 0px) 0 0;
  margin: auto;
  overflow: clip;
  border-radius: var(--image-zoom-radius);
  /* No white of its own: at the rounded edge it showed as a pale rim inside the grey ring (Keyan, 2026-10-15: one outline). */
  background: transparent center / cover no-repeat;
  box-shadow: 0 0 0 var(--zoom-outline-width) color-mix(in srgb, var(--zoom-outline-color) calc(var(--zoom-outline-opacity) * var(--lift) * 100%), transparent),
    3px 8px 28px rgba(50, 45, 42, calc(0.12 * var(--lift)));
  -webkit-user-select: none;
  user-select: none;
}

.image-zoom-box img,
.image-zoom-box video {
  display: block;
  width: 100%;
  height: 100%;
}

.image-zoom-box video {
  position: relative;
  object-fit: cover;
}

/* The small video's frame at the click, under the big copy until the copy's own frames come (image-zoom.js). */
.image-zoom-still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The big picture's holder, for every picture that opens big: the whole window, clipped to the frames the small one sits
   in while the picture flies out from under the bar and the edge blurs, or back under them (zoom-stage.js). */
.zoom-under {
  position: absolute;
  inset: 0;
}

/* The edge blurs, copied over the layer while a picture flies out from under them or back (zoom-stage.js), cut to the
   same frame by overflow and rounded corners. */
.zoom-lid {
  position: fixed;
  z-index: 9001;
  overflow: clip;
  pointer-events: none;
}

/* A tidbit with data-live (the raccoon pond) shows its live page over the big video once it lands and has loaded,
   fading in over --zoom-fade, and out again as it closes (image-zoom.js). */
.image-zoom-live {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  opacity: 0;
  transition: opacity var(--zoom-fade-out) var(--zoom-ease);
}

.image-zoom-live.on {
  opacity: 1;
  transition-duration: var(--zoom-fade);
}

/* Opened big, a picture has one outline, the grey ring above (--zoom-outline-*), not the 1px light line it has on the
   page as well (Keyan, 2026-10-15: "two outlines, use only one, grey one"). */

/* The big view's chevrons, bottom middle, level with the bar's (image-zoom.js places them): the bar's two chevrons side
   by side, --zoom-steps-gap apart, the right one the left flipped. Their outline and lip take the bar's --step-line, a
   touch darker hovered. On phones too, where the bar's own hide: there they are the only way along. */
.zoom-steps {
  position: fixed;
  display: flex;
  gap: var(--zoom-steps-gap);
  translate: -50% -50%;
}

.zoom-steps[hidden] {
  display: none;
}

.zoom-step {
  width: var(--step-size);
  height: var(--step-size);
  padding: 0;
}

.zoom-step svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  color: var(--step-line);
  transition: color var(--motion-fast) var(--motion-ease-out), scale var(--motion-fast) var(--motion-ease-out);
}

.zoom-step[data-step="1"] svg {
  scale: -1 1;
}

.zoom-step:active svg {
  scale: var(--press-scale);
}

.zoom-step[data-step="1"]:active svg {
  scale: calc(-1 * var(--press-scale)) var(--press-scale);
}

@media (hover: hover) and (pointer: fine) {
  .zoom-step:hover svg {
    color: var(--step-line-on);
  }
}

/* A project's cover opened big (image-zoom.js copies the whole cover in) keeps the big view's one outline, not its own
   1px line. */
.image-zoom-box .cover::after {
  display: none;
}
