/* --- Image frames --------------------------------------------------------- */
/* Photographs are pulled toward the paper: desaturated, then warmed back to
   the ivory the mark is printed on. */

.frame {
  position: relative;
  overflow: hidden;
  background: var(--paper-2);
  isolation: isolate;
}
.frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--img-filter);
}
.frame::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, var(--img-veil), var(--img-tint));
}

/* Choosing which half survives the crop.
   Both photograph slots are a portrait 4:5 frame filled with `cover`, so a
   LANDSCAPE source loses the outer half of its width. people2.webp is
   1537x1023 — centred, the crop cuts the man on the left through the face.
   30% slides the window left until both faces and the floor plans between them
   are whole, and only a hand of the third figure is left at the right edge.
   Matched on the file rather than on a beat, because the same photograph is in
   the markup twice: once in the desktop sticky plate, once in the beat's own
   figure. Replace the file and this line is what you retune. */
img[src$="people2.webp"] { object-position: 30% 50%; }
.frame.is-missing::before,
.plate.is-missing::before {
  content: "Image not loaded";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: var(--paper-2);
  z-index: 1;
}

/* --- Film clips ----------------------------------------------------------- */
/* Three of the story's five slots hold a film instead of a photograph. Unlike
   the reel and the entrance these are PLAYED, not scrubbed: the beat that is
   live decides which one runs, and only that one is ever loaded.
   A `.clip` fills whatever frame it is dropped into — the sticky `.plate` on
   desktop, the beat's own `.beat__figure` on mobile. Both are position:relative
   or absolute already, so it needs no box of its own. */

.clip { position: absolute; inset: 0; }

.clip__video {
  display: block;
  width: 100%;
  height: 100%;
  /* The slot they drop into is the photographs' portrait 4:5, and none of the
     three films matches it: beats 01 and 05 are landscape ~16:9, so `cover`
     keeps roughly the middle half of their width; beat 03 is portrait 9:16, so
     `cover` keeps roughly the middle two thirds of its height instead. Either
     way it is a real crop, and that is the right default — the frame stays
     exactly the size it is for the two photograph beats, so the sticky column
     does not resize as you scroll past. If a subject sits off-centre in one of
     them, move that film's crop rather than the frame:
       .clip[data-clip="4"] .clip__video { object-position: 30% 50%; } */
  object-fit: cover;
  /* NOT --img-filter. The stills beside these are grayscale + sepia; a film
     put through that is a dead film. See --clip-filter in 01-tokens.css. */
  filter: var(--clip-filter);
  background: var(--paper-2);
  /* Fallback for a coarse pointer, and for anything that never gets as far as
     the custom pointer below. */
  cursor: pointer;
}

/* The pointer over a film ---------------------------------------------------
   The real cursor is hidden and #clipCursor takes its place, so the pointer
   itself says what a click will do. Only ever on a fine pointer, and only once
   the script has claimed the element — otherwise `cursor: pointer` stands. */
@media (hover: hover) and (pointer: fine) {
  .has-clip-cursor .clip__video { cursor: none; }
}

.clip-cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 90;
  width: 5.5rem;
  height: 5.5rem;
  margin: -2.75rem 0 0 -2.75rem;   /* centred on the hotspot */
  border-radius: 50%;              /* the one round thing on the site: it is a
                                      cursor, not a piece of the page */
  display: grid;
  place-items: center;
  pointer-events: none;
  background: var(--clip-chip);
  border: 1px solid var(--clip-chip-rule);
  color: var(--clip-chip-ink);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  opacity: 0;
  /* `translate` is set per pointermove and is deliberately NOT in the
     transition — a transitioned position lags the hand and reads as broken.
     Only the fade and the scale are eased. */
  translate: var(--cx, 50vw) var(--cy, 50vh);
  scale: 0.72;
  transition: opacity 0.28s var(--ease), scale 0.45s var(--ease);
}
.clip-cursor.is-on { opacity: 1; scale: 1; }
.clip-cursor__label {
  font-family: 'Jost', ui-sans-serif, system-ui, sans-serif;
  font-size: 0.625rem;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}

/* Controls ------------------------------------------------------------------
   Two words in the corner, in the same wide-tracked caps the buttons use. The
   chip under them is dark in both themes because it sits on footage, not on
   paper. z-index 3 clears the frame's own tint overlay, which is 2. */
.clip__bar {
  position: absolute;
  z-index: 3;
  right: clamp(0.6rem, 1.4vw, 1.1rem);
  bottom: clamp(0.6rem, 1.4vw, 1.1rem);
  display: flex;
  gap: 0.4rem;
}
.clip__btn {
  font-family: 'Jost', ui-sans-serif, system-ui, sans-serif;
  font-size: 0.625rem;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--clip-chip-ink);
  background: var(--clip-chip);
  border: 1px solid var(--clip-chip-rule);
  padding: 0.55rem 0.8rem;
  cursor: pointer;
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  transition: background 0.4s var(--ease), border-color 0.4s var(--ease);
}
.clip__btn:hover,
.clip__btn:focus-visible { background: var(--clip-chip-over); border-color: var(--clip-chip-ink); }
.clip__btn:focus-visible { outline: 2px solid var(--clip-chip-ink); outline-offset: 2px; }

/* Same honest failure state the photographs get, in the films' own words. */
.plate--clip.is-missing::before,
.beat__figure--clip.is-missing::before { content: "Film not loaded"; }
