/* coverflow — a full-height wall of media lanes with a cylindrical curve:
 * draggable at rest, or flowing as opposing marquees. Gallery-wall is the
 * skeleton (shell, fit ladder, media discipline); logo-wall is the motion
 * precedent (sequence + clones + the beat vocabulary).
 *
 * TWO RENDERS, ONE MARKUP:
 *   flat — the composed state: a plain uniform grid on the shared
 *     .sk-cell-grid engine, every item visible, natural height. This is the
 *     scripts-off / print / pre-JS render: structural degradation (§7),
 *     nothing clipped, nothing trapped.
 *   band — coverflow.js re-parents the cells round-robin into lanes, clones
 *     each lane's sequence for the seamless wrap (logo-wall's clone
 *     discipline), and stamps data-coverflow="on" — ONLY then does anything
 *     below the BAND marker apply. The engine owns every lane's offset
 *     (marquee velocity and drag scrub write the same number — the declared
 *     deviation from logo-wall's CSS keyframe) and writes the per-tile curve
 *     inline on the MEDIA, never the cell: the cell's transform belongs to
 *     the reveal engine (gallery-wall's scatter gotcha).
 *
 * Geometry only — every paint reads the --sk-* indirections. */

/* Flush, like accordion gallery: shed the scaffold's block padding or the
 * band sits in a strip of page ground. Slideshow's (0,2,0) rank. */
.sk-section[data-sk="coverflow"] {
  padding-block: 0;
}

.sk-coverflow {
  /* Lane gutter — fixed --kit-space scale (gutters don't ride Density). */
  --sk-coverflow-gap: var(--kit-space-4);
  /* Marquee pace: seconds per tile pitch (logo-wall's beat vocabulary). */
  --sk-coverflow-beat: 3;
  /* THE CYLINDER HAS ONE SHAPE DIAL: the half-angle (deg) subtended at the
   * band edge. It is the radius in disguise — the engine solves
   * R = half / radians(deg) from the lane's own half-width, then seats every
   * tile ON that circle. Recede is therefore DERIVED (R·(1−cos θ)), not a
   * second dial: a cylinder cannot tilt and recede by independent amounts,
   * and the old `--sk-coverflow-curve-depth` let them disagree, which is why
   * the wall read as tiles each pivoting in place rather than one surface.
   * (The derived numbers land where that dial used to sit — ~100px at 16°,
   * ~184px at 30°, on a 1440 band — so the look is preserved, not restated.)
   *
   * Perspective is the genuinely independent axis: the viewer's distance.
   * It lives on the BAND (below) so every row shares one vanishing point. */
  --sk-coverflow-curve-deg: 16;
  --sk-coverflow-perspective: 1400px;
  /* +1 seats tiles on the inner face (edges recede); −1 flips the drum so
   * edges come forward — same radius, inverted deformation. */
  --sk-coverflow-curve-sign: 1;
  /* A grabbed lane tightens around the same axis: the drum you are pulling
   * reads as a SMALLER one, so the same drag sweeps further around it. 0.68
   * is a third off the radius at full engagement — enough to feel the barrel
   * shrink under the hand. The engine eases there and back continuously. */
  --sk-coverflow-drag-radius: 0.68;
  /* The two halves of the Edge Fade dial, because the ends need both.
   *
   * --edge-fade is the page-edge alpha ramp: a MASK, which fades the region
   * pixel by pixel and softens the boundary tiles cross.
   *
   * --dissolve is how much of the row's half-width a tile takes to fade out
   * as an OBJECT, and it is the one that stops a loop reading as a teleport.
   * A mask cannot do this job: a tile wider than the gradient goes
   * transparent at its leading edge while the rest of it is still solid, so
   * the picture slides under a soft edge and pops back in across the row
   * rather than leaving and arriving. Sized against a tile's own width — a
   * dissolve much shorter than that is a cut with a soft corner.
   *
   * The Edge Fade control moves the MASK only. Removing the mask is a look
   * (a crisp band edge); it must not hand back the teleport, so the dissolve
   * is not on that dial and is always on. */
  --sk-coverflow-edge-fade: 7%;
  --sk-coverflow-dissolve: 0.35;
  /* Parallax portal: how far the picture may slide inside its own frame, as a
   * percentage of the frame. The frame is a WINDOW — the picture sits a little
   * behind it, so turning the tile away shows you round the far side of the
   * opening. The zoom is the material that slide consumes: overhang the image
   * by the excursion on BOTH sides and the crop can never run off the edge. */
  --sk-coverflow-portal: 0;
  --sk-coverflow-portal-zoom: 1;
  /* Media corner rounding — follows the shape's media posture. */
  --sk-coverflow-media-radius: var(--sk-media-radius);
  /* Cross-axis SAFE — room for the cylinder's projected bleed so the section
   * crop never bites a tile. Horizontal spends it as padding-block (above /
   * below); vertical as padding-inline (left / right). Zero when flat. */
  --sk-coverflow-safe: 0;
  /* Base tile ratio (+ its w/h number, for content-height lane math). */
  --sk-coverflow-aspect: 3 / 4;
  --sk-coverflow-ratio: 0.75;
  /* The band is media, never prose, and it is a DRAG SURFACE: a selection
   * swept in from the page outside would paint its highlight across the
   * tiles and leave a stuck selection the next drag fights. Nothing here is
   * text worth selecting, so the whole band opts out. */
  -webkit-user-select: none;
  user-select: none;
}
.sk-coverflow[data-gap="none"] {
  --sk-coverflow-gap: 0px;
}
.sk-coverflow[data-gap="tight"] {
  --sk-coverflow-gap: var(--kit-space-2);
}
.sk-coverflow[data-gap="airy"] {
  --sk-coverflow-gap: var(--kit-space-5);
}
.sk-coverflow[data-speed="slow"] {
  --sk-coverflow-beat: 5;
}
.sk-coverflow[data-speed="fast"] {
  --sk-coverflow-beat: 1.5;
}
.sk-coverflow[data-curve="none"] {
  --sk-coverflow-curve-deg: 0;
}
/* A deeper cylinder also brings the viewer closer — a tighter radius seen
 * from far away flattens back out into the parabola we just left. */
.sk-coverflow[data-curve="deep"] {
  --sk-coverflow-curve-deg: 30;
  --sk-coverflow-perspective: 1000px;
}
.sk-coverflow[data-curve-facing="outward"] {
  --sk-coverflow-curve-sign: -1;
}
.sk-coverflow[data-corners="small"] {
  --sk-coverflow-media-radius: 0.5rem;
}
.sk-coverflow[data-corners="large"] {
  --sk-coverflow-media-radius: 1.25rem;
}
/* Safe inset scales with how hard the cylinder leans. Outward needs more:
 * tiles come toward the eye and grow past the band plane. */
.sk-coverflow[data-coverflow="on"]:not([data-curve="none"]) {
  --sk-coverflow-safe: clamp(1.25rem, 3vh, 2rem);
}
.sk-coverflow[data-coverflow="on"][data-curve="deep"] {
  --sk-coverflow-safe: clamp(1.75rem, 4.5vh, 2.75rem);
}
.sk-coverflow[data-coverflow="on"]:not([data-curve="none"])[data-curve-facing="outward"] {
  --sk-coverflow-safe: clamp(3rem, 10vh, 6rem);
}
.sk-coverflow[data-coverflow="on"][data-curve="deep"][data-curve-facing="outward"] {
  --sk-coverflow-safe: clamp(4rem, 14vh, 8rem);
}
.sk-coverflow[data-parallax="subtle"] {
  --sk-coverflow-portal: 10;
  --sk-coverflow-portal-zoom: 1.2;
}
.sk-coverflow[data-parallax="deep"] {
  --sk-coverflow-portal: 40;
  --sk-coverflow-portal-zoom: 1.8;
}
.sk-coverflow[data-edge-fade="small"] {
  --sk-coverflow-edge-fade: 3%;
}
.sk-coverflow[data-edge-fade="large"] {
  --sk-coverflow-edge-fade: 14%;
}
.sk-coverflow[data-aspect="1:1"] {
  --sk-coverflow-aspect: 1 / 1;
  --sk-coverflow-ratio: 1;
}
.sk-coverflow[data-aspect="3:2"] {
  --sk-coverflow-aspect: 3 / 2;
  --sk-coverflow-ratio: 1.5;
}
.sk-coverflow[data-aspect="2:3"] {
  --sk-coverflow-aspect: 2 / 3;
  --sk-coverflow-ratio: 0.6667;
}
.sk-coverflow[data-aspect="4:3"] {
  --sk-coverflow-aspect: 4 / 3;
  --sk-coverflow-ratio: 1.3333;
}
.sk-coverflow[data-aspect="16:9"] {
  --sk-coverflow-aspect: 16 / 9;
  --sk-coverflow-ratio: 1.7778;
}
/* valign — gallery-wall's vocabulary; the cross-axis seat of a mixed-ratio
 * tile inside its lane (vertical orientation reads it as left/center/right). */
.sk-coverflow[data-valign="top"] {
  --sk-coverflow-valign: flex-start;
}
.sk-coverflow[data-valign="center"] {
  --sk-coverflow-valign: center;
}
.sk-coverflow[data-valign="bottom"] {
  --sk-coverflow-valign: flex-end;
}

/* ── FLAT — the composed fallback grid (gallery-wall's engine) ──
 * The container is a shared .sk-cell-grid: display:grid + the tracks come
 * from base.css; we re-declare only the --sk-columns-fit seam it reads,
 * gallery-wall's ladder verbatim (the dial is a ceiling, never a floor;
 * sanctioned rungs only). The band reads the SAME seam for tile sizing, so
 * flat and band always agree about density. */
.sk-coverflow .sk-coverflow-cells {
  --sk-columns-fit: var(--sk-columns, 4);
  gap: var(--sk-coverflow-gap);
}
@media (max-width: 1200px) {
  .sk-coverflow .sk-coverflow-cells {
    --sk-columns-fit: min(var(--sk-columns, 4), 4);
  }
}
@media (max-width: 900px) {
  .sk-coverflow .sk-coverflow-cells {
    --sk-columns-fit: min(var(--sk-columns, 4), 3);
  }
}
@media (max-width: 560px) {
  .sk-coverflow .sk-coverflow-cells {
    --sk-columns-fit: min(var(--sk-columns, 4), 2);
  }
}

/* The cell's link/focus posture rides base.css's CELL LINK home; only the
 * INSIDE ring is coverflow's own — cells sit flush in a clipped lane, so
 * an outside ring would be cropped. */
.sk-coverflow-cell {
  --sk-cell-ring-offset: -2px;
}

/* The media box: base ratio unless the item declares its own. */
.sk-coverflow-media {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: var(--sk-surface);
  aspect-ratio: var(--sk-coverflow-aspect);
  border-radius: var(--sk-coverflow-media-radius);
}
.sk-coverflow-media .sk-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--sk-focal-x, 50%) var(--sk-focal-y, 50%);
  filter: var(--sk-image-filter, none);
}
/* Per-item ratio from base.css's ASPECT TOKEN TABLE, presence-keyed on the
 * item (the inheritance guard) and EXCLUDING auto — data-aspect="auto"
 * (the default row value) keeps the section's base var, so it must not
 * read the token. :where() keeps the old per-value rank. */
.sk-coverflow-media[data-aspect]:where(:not([data-aspect="auto"], [data-aspect=""])) {
  aspect-ratio: var(--sk-aspect);
}

/* ── BAND — everything below is gated on data-coverflow="on", which only
 * coverflow.js stamps (and only after a successful build), so scripts-off
 * and print never see any of it. ── */

.sk-coverflow[data-coverflow="on"] .sk-coverflow-inner {
  /* The lanes bleed; the soft edge fade is their page edge (logo-wall), and
   * it doubles as the cylinder's falloff. The gradient paints MASK ALPHA,
   * not pixels — --sk-ink is just an opaque stop.
   *
   * Cross-axis SAFE sits inside the clip: projected tiles may paint into the
   * padding without being cropped, and anything past the pad still hits the
   * overflow edge rather than escaping onto the page. */
  padding-inline: 0;
  padding-block: 0;
  overflow: hidden;
  box-sizing: border-box;
  /* ONE gradient, angle-parameterized — the orientation rules below only
   * turn it (and pick the safe-pad axis), so the fade's stops can never
   * drift between the two. The -webkit- twin must stay: Safari still
   * prefixes mask-image. */
  -webkit-mask-image: linear-gradient(
    var(--sk-coverflow-fade-angle, 90deg),
    transparent 0,
    var(--sk-ink) var(--sk-coverflow-edge-fade),
    var(--sk-ink) calc(100% - var(--sk-coverflow-edge-fade)),
    transparent 100%
  );
  mask-image: linear-gradient(
    var(--sk-coverflow-fade-angle, 90deg),
    transparent 0,
    var(--sk-ink) var(--sk-coverflow-edge-fade),
    var(--sk-ink) calc(100% - var(--sk-coverflow-edge-fade)),
    transparent 100%
  );
}
.sk-coverflow[data-coverflow="on"][data-orientation="horizontal"] .sk-coverflow-inner {
  --sk-coverflow-fade-angle: 90deg;
  padding-block: var(--sk-coverflow-safe);
  padding-inline: 0;
}
.sk-coverflow[data-coverflow="on"][data-orientation="vertical"] .sk-coverflow-inner {
  --sk-coverflow-fade-angle: 180deg;
  padding-inline: var(--sk-coverflow-safe);
  padding-block: 0;
}
.sk-coverflow[data-coverflow="on"][data-edge-fade="none"] .sk-coverflow-inner {
  -webkit-mask-image: none;
  mask-image: none;
}
/* The viewport band — explicit author choice only. Vertical uses a band
 * height clamp so the section still stacks; viewport fills the screen. */
.sk-coverflow[data-coverflow="on"][data-height="viewport"] .sk-coverflow-inner {
  height: 100svh;
}
.sk-coverflow[data-coverflow="on"][data-orientation="vertical"]:not([data-height="content"])
  .sk-coverflow-inner {
  height: var(--sk-coverflow-h, clamp(28rem, 55vw, 40rem));
}

/* The cells container becomes the lane stack. It KEEPS .sk-cell-grid, so the
 * fit-ladder seam above still resolves — the band's tile width reads it. */
.sk-coverflow[data-coverflow="on"] .sk-coverflow-cells {
  display: flex;
  gap: var(--sk-coverflow-gap);
  height: 100%;
  --sk-coverflow-cols: var(--sk-columns-fit, var(--sk-columns, 4));
}
.sk-coverflow[data-coverflow="on"][data-orientation="horizontal"] .sk-coverflow-cells {
  flex-direction: column;
  /* Tile pitch: N tiles visible across the bleed, N−1 interior gutters.
   * UNIFORM on the drag axis — the invariant the curve arithmetic rides. */
  --sk-coverflow-tile: calc(
    (100vw - (var(--sk-coverflow-cols) - 1) * var(--sk-coverflow-gap)) / var(--sk-coverflow-cols)
  );
}
.sk-coverflow[data-coverflow="on"][data-orientation="vertical"] .sk-coverflow-cells {
  flex-direction: row;
  --sk-coverflow-tile: calc(
    (100svh - (var(--sk-rows, 2) - 1) * var(--sk-coverflow-gap)) / var(--sk-rows, 2)
  );
}
/* Content height (horizontal only): the lane takes the base-ratio tile
 * height instead of flexing into the viewport band. */
.sk-coverflow[data-coverflow="on"][data-height="content"][data-orientation="horizontal"]
  .sk-coverflow-cells {
  height: auto;
}
.sk-coverflow[data-coverflow="on"][data-height="content"][data-orientation="horizontal"]
  .sk-coverflow-lane {
  flex: none;
  height: calc(var(--sk-coverflow-tile) / var(--sk-coverflow-ratio));
}

/* Lane, track, sequence — built by coverflow.js. touch-action: pan-y is the
 * scroll-trap covenant: a touch swipe down is ALWAYS the page's, in both
 * orientations (vertical drag is fine-pointer only). */
/* THE LANE DOES NOT CLIP — .sk-coverflow-inner does, for all of them.
 *
 * A lane always spans the full inner box across its own drag axis (rows are
 * full-width, columns full-height), so the only thing that ever escapes a lane
 * is the track running out to max-content ALONG that axis — and that leaves
 * the inner box at the same moment, where the existing overflow: hidden (and
 * the edge mask) already cuts it. A per-lane clip was therefore always
 * redundant, and once the wall became one cylinder it turned harmful twice
 * over: it bit off the overhang a receding row makes as it converges on the
 * shared eye (the rows looked cropped top and bottom), and it forced each lane
 * into its own 3D context, where rows that overlap have no shared depth to
 * sort by and the wrong one can paint in front. */
.sk-coverflow-lane {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 0;
  touch-action: pan-y;
  cursor: grab;
}
/* ONE DRUM, ONE VIEWER — the whole wall is a single cylinder seen from a
 * single eye, and it took getting BOTH of those right.
 *
 * The tiles must share a 3D space, so the perspective is a property of the
 * container, not of each tile. (Per-tile `perspective()` in the transform —
 * the earlier logo-wall-flip precedent — gives every tile its own vanishing
 * point directly in front of itself, so an edge tile is drawn as if you had
 * walked over to stand square to it: right tilt, no shared axis, a row of
 * independently pivoting cards.) The cost is the preserve-3d chain below,
 * which must stay UNBROKEN from lane to media — any grouping property
 * (overflow, opacity < 1, filter, mask, clip-path) on track/seq/cell flattens
 * the tiles back into the lane plane and silently restores that look.
 *
 * The eye must be shared BETWEEN the rows too, and the only way to get that is
 * to put the perspective on the BAND rather than on each lane. A perspective
 * on the lane has its vanishing point at that lane's own centre, so every row
 * is drawn as though you stood square in front of it — n rows, n drums — and
 * it also seals each row into a private 3D context, so overlapping rows have
 * no shared depth to sort by. Hoisting it to .sk-coverflow-cells gives the
 * whole wall one eye at the band's centre for free (no per-lane arithmetic)
 * and one depth space, so a nearer tile correctly covers a further one even
 * across rows. Rows above the eye are then seen slightly from below and rows
 * below it from above, the way the bands of one slot-machine drum read.
 *
 * This is why the lane must not clip (see above): a clip anywhere between the
 * band and a tile flattens the chain and takes the shared eye with it.
 *
 * A tile at the band centre sits at z = 0, so the shared eye never displaces
 * the tile you're actually looking at — only the receding ones converge.
 *
 * Perspective stays on even when Curve is none: hover tilt/magnet still need
 * a 3D eye, and a flat wall with no perspective would read the tilt as a
 * sheer. */
.sk-coverflow[data-coverflow="on"] .sk-coverflow-cells {
  perspective: var(--sk-coverflow-perspective);
  perspective-origin: 50% 50%;
}
.sk-coverflow[data-coverflow="on"] .sk-coverflow-lane,
.sk-coverflow[data-coverflow="on"] .sk-coverflow-track,
.sk-coverflow[data-coverflow="on"] .sk-coverflow-seq,
.sk-coverflow[data-coverflow="on"] .sk-coverflow-cell {
  transform-style: preserve-3d;
}
.sk-coverflow-lane[data-dragging] {
  cursor: grabbing;
}
/* THE TRACK'S CHILDREN ARE WHOLE PERIODS, NOT TILES — so the track lays them
 * edge to edge and adds NO gutter of its own. The seam between one copy and
 * the next belongs to the sequence (its trailing padding, below), because the
 * engine wraps the offset by the sequence's own size: a gutter here would
 * push the next copy one gutter further than the wrap distance, and the loop
 * would snap back by exactly that much every time it came round. Keep the
 * gutter in ONE place. */
.sk-coverflow-track {
  display: flex;
  gap: 0;
  will-change: transform;
}
.sk-coverflow-seq {
  display: flex;
  gap: var(--sk-coverflow-gap);
}
.sk-coverflow[data-coverflow="on"][data-orientation="horizontal"] .sk-coverflow-track {
  width: max-content;
  height: 100%;
}
.sk-coverflow[data-coverflow="on"][data-orientation="horizontal"] .sk-coverflow-seq {
  height: 100%;
  /* The wrap seam matches the gutter, so the loop point reads like any
   * other gap (logo-wall). */
  padding-inline-end: var(--sk-coverflow-gap);
}
.sk-coverflow[data-coverflow="on"][data-orientation="vertical"] .sk-coverflow-track {
  flex-direction: column;
  height: max-content;
  width: 100%;
}
.sk-coverflow[data-coverflow="on"][data-orientation="vertical"] .sk-coverflow-seq {
  flex-direction: column;
  width: 100%;
  padding-block-end: var(--sk-coverflow-gap);
}

/* Band tiles. The cell is a flex seat: uniform on the drag axis, the media
 * inside varies on the cross axis and sits per valign. An over-tall (or
 * over-wide) ratio caps at the lane and crops via object-fit: cover — the
 * mixed-ratio contract. */
.sk-coverflow[data-coverflow="on"] .sk-coverflow-cell {
  flex: none;
  display: flex;
}
.sk-coverflow[data-coverflow="on"][data-orientation="horizontal"] .sk-coverflow-cell {
  width: var(--sk-coverflow-tile);
  height: 100%;
  align-items: var(--sk-coverflow-valign, center);
}
.sk-coverflow[data-coverflow="on"][data-orientation="vertical"] .sk-coverflow-cell {
  height: var(--sk-coverflow-tile);
  width: 100%;
  justify-content: var(--sk-coverflow-valign, center);
}
.sk-coverflow[data-coverflow="on"][data-orientation="horizontal"] .sk-coverflow-media {
  width: 100%;
  height: auto;
  max-height: 100%;
}
.sk-coverflow[data-coverflow="on"][data-orientation="vertical"] .sk-coverflow-media {
  height: 100%;
  width: auto;
  max-width: 100%;
}
/* The engine writes opacity every frame, and transform whenever a curve or
 * hover tilt/magnet is live — promote both on the band. */
.sk-coverflow[data-coverflow="on"] .sk-coverflow-media {
  will-change: transform, opacity;
}

/* PARALLAX PORTAL — the frame is an opening, the picture hangs behind it.
 *
 * The engine writes one number per tile, --sk-cf-portal: how far this picture
 * has slid inside its frame right now, signed, in percent of the frame. The
 * axis is the drag axis, because that is the axis the tile turns about. The
 * media box already clips, so the slide simply reveals what the zoom put
 * outside the crop — no extra layer, no second element.
 *
 * Percentages in translate resolve against the element's UNTRANSFORMED box,
 * so the excursion stays honest whatever the zoom is; keeping the zoom at
 * 1 + 2 × excursion is what guarantees the crop never runs past the picture.
 *
 * Scoped to a live curve: with no curve there is no angle to look along, and
 * an always-on transform here would promote a layer per tile for nothing. */
.sk-coverflow[data-coverflow="on"]:not([data-curve="none"]):not([data-parallax="none"])
  .sk-coverflow-media
  .sk-image {
  will-change: transform;
}
.sk-coverflow[data-coverflow="on"]:not([data-curve="none"]):not(
    [data-parallax="none"]
  )[data-orientation="horizontal"]
  .sk-coverflow-media
  .sk-image {
  transform: translate3d(var(--sk-cf-portal, 0%), 0, 0) scale(var(--sk-coverflow-portal-zoom));
}
.sk-coverflow[data-coverflow="on"]:not([data-curve="none"]):not(
    [data-parallax="none"]
  )[data-orientation="vertical"]
  .sk-coverflow-media
  .sk-image {
  transform: translate3d(0, var(--sk-cf-portal, 0%), 0) scale(var(--sk-coverflow-portal-zoom));
}
