/* ================================================================
   SuprBad Window — Frontend CSS
   ================================================================
   World-space traversal model. Two horizontal flex tracks.

   Layout hierarchy (0.11.0, Approach 4, doc 06 § 16):
     .sb-stage           the band: its containing block's full width,
                         transparent; holds the canvas, centred
       .sb-window        the canvas: width ÷ R tall (aspect-ratio),
                         at most cap × R wide; the query container
         .sb-track--top    top horizontal strip, moves left on advance
         .sb-track--bottom bottom horizontal strip, moves right on advance
                           (reversed DOM, so the next half arrives)
           .sb-slot        one pair-half, exactly one canvas width wide
             .sb-half      media container, fills slot
               .sb-media   cover-mode, focal-point cropped, scale(1.20)
                           (img | video | img[src=*.svg])
         .sb-follow--top / --bottom   optional media-free overlay tracks
                           (0.5.0) that move with their image track
       .sb-window-fallback  option D: the configured image, shown only
                           where container queries are missing

   Motion is owned by the TRACK (two translateX values).
   No entering/exiting state machine. No stacked panels.
   No independent per-half transforms.

   JS sets --sb-duration and --sb-easing on .sb-window before each
   advance. CSS transitions on .sb-track inherit these values.

   Longhand top/right/bottom/left only — the `inset` shorthand is
   known-broken on iPad Safari 12 (repository incident record
   incident-ipad-touchactive-2026-06-04); the longhands are a deliberate
   compatibility requirement, not an oversight (review F-6, 0.10.0).
   ================================================================ */
/* stylelint-disable declaration-block-no-redundant-longhand-properties */

/* ================================================================
   STAGE (the band) and WINDOW (the canvas)
   ================================================================

   0.11.0, Approach 4 (doc 06 § 16). The composition has one locked
   ratio R on every screen; the canvas is sized from its container,
   never from the viewport:

     --sb-frame-ratio   R, width ÷ height        default 3
     --sb-frame-cap     the height cap           default min(75vh, 900px)

   Both are written by SB_Viewport_Config::inline_css() on .sb-stage via
   wp_add_inline_style on the 'sb-window' handle, so admin-configured
   values follow this file and win; the defaults here are fallbacks.

   The band takes its containing block's width (no 100vw breakout since
   0.11.0, which is what makes a column, W-42, correct by construction)
   and carries an explicit width: 100%, because the canvas is a query
   container with no natural width, so a band sized to its content
   would be 0 px wide in a flex row (doc 06 § 16.14 item 5). Bands are
   transparent: past the cap the page background shows either side.

   The canvas: width min(100%, cap × R), centred, height from
   aspect-ratio (height: auto, explicitly; doc 06 § 16.14 item 10).

   --sb-stage-h is the canvas's real height, consumed by the per-slot
   overlap extension (W-43). It is an UNREGISTERED custom property
   holding 100cqw / R, substituted inside the slots, so its cqw
   resolves against the canvas (the nearest query container). NEVER
   register it with @property, and never put cqw in a regular property
   of .sb-window itself: either resolves the unit against something
   other than the canvas, which brings W-42 and W-43 back at the cap
   and in a column (doc 06 § 16.14 item 11).

   Browsers without container queries (Safari < 16) get the fallback
   image instead (option D, end of this file).
   ================================================================ */

.sb-stage {
  width: 100%;
  overflow: hidden;
}

.sb-window {
  position: relative;
  box-sizing: border-box;
  width: min(100%, calc(var(--sb-frame-cap, min(75vh, 900px)) * var(--sb-frame-ratio, 3)));
  height: auto;
  margin-right: auto;
  margin-left: auto;
  aspect-ratio: var(--sb-frame-ratio, 3);
  overflow: hidden;
  container-type: inline-size;

  --sb-stage-h: calc(100cqw / var(--sb-frame-ratio, 3));

  /* Contains the overlay layers (0.5.0): composition overlay hosts sit
     directly in .sb-window, so it must be their stacking context for the
     z scale below to stay inside the carousel. */
  isolation: isolate;

  /* Default split — 50/50 seam. Stage 2 will animate this per-composition. */
  --sb-split: 50;

  /* Z-order in overlap zone. JS writes per-composition in _applyGeometryVars()
     before each transition begins (paint-order only — no layout impact).
     --sb-overlap-ratio is intentionally absent — it is a per-slot CSS custom
     property rendered inline by PHP and never mutated by JS. Each slot owns
     its overlap geometry permanently, so two compositions can coexist in the
     same track during a transition with independent, stable overlap values. */
  --sb-top-z: 1;
  --sb-bottom-z: 1;

  /* Z scale (0.5.0): winning image track 3, the other 1, composition overlay
     "Between the images" 2, overlays "Above both images" 5, nav 10. */
}

/* ================================================================
   TRACKS
   ================================================================

   Two absolutely-positioned flex strips that together tile the window.
   The window's overflow:hidden clips all out-of-viewport slots.

   will-change promotes each track to its own compositor layer.
   GPU textures for ALL slots are resident at all times — no cold-start
   rasterization, no texture pop-in on first transition.

   TOP TRACK:    top:0,    height = split%        (natural only — no overlap)
   BOTTOM TRACK: bottom:0, height = (100-split)%  (natural only — no overlap)

   Overlap extension is per-slot, not per-track. Each .sb-slot independently
   extends past the track's natural boundary via --sb-overlap-ratio set inline
   by PHP. The track height never changes — only translateX moves.
   ================================================================ */

.sb-track {
  position: absolute;
  left: 0;
  width: 100%;
  display: flex;
  flex-direction: row;
  will-change: transform;
  backface-visibility: hidden;
}

.sb-track--top {
  top: 0;
  height: calc(100% * (var(--sb-split, 50) / 100));
  z-index: var(--sb-top-z, 1);

  /* Seam anchoring (0.11.0, doc 06 § 16.8): a contain half rests on the
     slot edge nearest the seam. Read by the contain rules below; a custom
     property, so no selector's specificity changes. */
  --sb-seam-anchor: 50% 100%;
}

.sb-track--bottom {
  bottom: 0;
  height: calc(100% * ((100 - var(--sb-split, 50)) / 100));
  z-index: var(--sb-bottom-z, 1);

  --sb-seam-anchor: 50% 0%;
}

/* ================================================================
   SLOTS
   ================================================================

   Each slot is exactly one canvas width wide (100% of its track, which
   is 100% of .sb-window; 0.11.0, was 100vw). overflow:hidden clips
   the media surface at the slot boundary — prevents scale(1.20) bleed
   from escaping into adjacent slots during track motion.

   BASE HEIGHT: 100% of the track (= natural split allocation).

   OVERLAP EXTENSION — per-slot, immutable, rendered by PHP:
   Each slot carries --sb-overlap-ratio as an inline CSS custom property
   set at page load. JS never changes it. This means:

   • Two compositions can coexist in the same track during a transition
     (one departing, one arriving) with entirely independent overlap values.
   • No global state mutation occurs at transition start or end.
   • Media geometry — height, bleed, focal crop — is frozen for the
     slot's entire lifetime on the page.

   TOP TRACK: slots extend DOWNWARD past the seam.
     slot bottom = seam + (--sb-overlap-ratio × stage-height)

   BOTTOM TRACK: slots extend UPWARD past the seam.
     align-self:flex-end anchors the slot's bottom to the track's
     bottom; the extra height extends toward and past the seam.
     slot top = seam − (--sb-overlap-ratio × stage-height)

   Tracks have no overflow:hidden — only .sb-window clips to the
   stage. Slot overflow past the track boundary is intentional.

   --sb-stage-h: the canvas's real height, declared on .sb-window as
   100cqw / R (0.11.0, W-43). The 100svh fallback applies only when the
   property is undefined, never when its value is invalid.
   ================================================================ */

.sb-slot {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
}

.sb-track--top .sb-slot {
  height: calc(100% + var(--sb-overlap-ratio, 0) * var(--sb-stage-h, 100svh));
}

.sb-track--bottom .sb-slot {
  align-self: flex-end;
  height: calc(100% + var(--sb-overlap-ratio, 0) * var(--sb-stage-h, 100svh));
}

/* ================================================================
   HALF CONTAINERS
   ================================================================

   Fills its slot. Separate layer so the scale(1.20) bleed is clipped
   at the half boundary, not at the track boundary.
   ================================================================ */

.sb-half {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  overflow: hidden;
}

/* ================================================================
   MEDIA SURFACE
   ================================================================

   .sb-media is the canonical class for all half-content elements:
   <img>, <video> (WebM/MP4 loop), <img src="*.svg">.

   Backward compatibility: bare <img> and <video> inside .sb-half
   without .sb-media receive identical rules via the fallback selectors.

   All motion transforms operate on .sb-track, never on .sb-media.
   The scale(1.20) here is a static bleed margin, not an animation.

   object-fit: cover fills the slot, cropping to the focal point.
   Container (track) moves; media crop stays. No media transform
   during transitions.
   ================================================================ */

.sb-media,
.sb-half img,
.sb-half video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--sb-focal-x, 50%) var(--sb-focal-y, 50%);
  transform: scale(1.2);
}

/* Fit mode: SVG compositions, typographic assets, full-frame content.
   position:absolute with top/right/bottom/left 0 pins the element to all four edges of .sb-half,
   guaranteeing concrete 100%×100% dimensions without depending on the
   fragile percentage-height chain through non-positioned ancestors.
   Double-class selector specificity: .sb-media → (0,3,0), img/video → (0,2,1).
   Both beat any single-class theme override regardless of source order. */
.sb-half.sb-half--fit .sb-media,
.sb-half.sb-half--fit img,
.sb-half.sb-half--fit video {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: var(--sb-seam-anchor, center center);
  transform: none;
}

/* ================================================================
   TRACK TRANSITIONS
   ================================================================

   Scoped to .sb-window--ready — added by JS after the first frame
   to prevent the initial track position from animating in on load.

   When JS snaps (a backward move, and the restore at the end of the
   last → first wrap, 0.8.0): transitionProperty is set to 'none'
   inline, overriding this rule for that frame. After the snap, the
   inline style is cleared and this rule resumes. Reduced motion does
   not snap; the engine uses its shorter reduced-motion presets.
   ================================================================ */

.sb-window--ready .sb-track,
.sb-window--ready .sb-follow {
  transition: transform var(--sb-duration, 1000ms) var(--sb-easing, cubic-bezier(0.4, 0, 0.2, 1))
    0ms;
}

/* The outgoing fade (0.11.2, owner decision 2026-10-07; doc 01 § 8.7
   and § 10.2 notes, the one sanctioned opacity transition on a slot).
   On a forward slide the engine puts this class on the leaving
   composition's top and bottom image slots and sets
   --sb-leave-duration to the time the slide takes to cover
   SBWindow.LAYER_SWITCH_AT of its distance (invariant 6: the timing
   is JS-owned). The arriving layer order waits for this fade's
   transitionend. The transition lives only on the class, so removing
   it at landing restores opacity 1 at once, off screen. */
.sb-window--ready .sb-slot--leaving {
  opacity: 0;
  transition: opacity var(--sb-leave-duration, 400ms) linear;
}

/* ================================================================
   OVERLAY FOLLOWER TRACKS (0.5.0)
   ================================================================

   Emitted only when some half's overlay is laid "Above both images".
   Each mirrors its image track exactly — same position, height, slot
   count, order and per-slot overlap extension — but holds no media, so
   an overlay inside it can paint above both image tracks while moving
   with its own image. sb-window.js gives it the image track's translateX.
   Separate classes from .sb-track/.sb-slot so no engine selector sees it.
   ================================================================ */

.sb-follow {
  position: absolute;
  left: 0;
  width: 100%;
  display: flex;
  flex-direction: row;
  z-index: 5;
  pointer-events: none;
}

.sb-follow--top {
  top: 0;
  height: calc(100% * (var(--sb-split, 50) / 100));
}

.sb-follow--bottom {
  bottom: 0;
  height: calc(100% * ((100 - var(--sb-split, 50)) / 100));
}

.sb-follow__slot {
  flex: 0 0 100%;
  width: 100%;
  height: calc(100% + var(--sb-overlap-ratio, 0) * var(--sb-stage-h, 100svh));
  position: relative;
  overflow: hidden;
}

.sb-follow--bottom .sb-follow__slot {
  align-self: flex-end;
}

/* ================================================================
   PRESET TIMING REFERENCE
   ================================================================

   JS sets --sb-duration and --sb-easing on .sb-window before every
   transition via _applyTimingVars(). Authoritative values live in
   _presetOf() in sb-window.js. Reduced-motion variants are also
   owned by _presetOf(), not CSS.

   Preset defaults for reference:
     flow:  1000ms  cubic-bezier(0.4, 0, 0.2, 1)
     void:   800ms  ease-in-out
     drift: 1400ms  cubic-bezier(0.3, 0, 0.15, 1)
     snap:   550ms  ease-in-out

   The fallback values on .sb-window above are the 'flow' defaults.
   No attribute selectors are used here — data-current-preset is not
   written by JS and selector-based timing would never take effect.
   ================================================================ */

/* ================================================================
   NAVIGATION
   ================================================================ */

/* Controls (0.9.0, review § 27.10.2, § 27.14). Hidden until the mouse moves
   or a touch (JS adds .sb-window--controls and removes it after
   CONTROLS_IDLE_MS); always shown while paused (C-5), hovered with a mouse
   (0.9.2: a tapped button keeps :hover on touch screens, so the hover rule
   is in the desktop query below), or focused from the keyboard (.sb-window--keyboard, 0.9.1: not :focus-within, which
   also held them after a click or tap). Hidden with opacity and
   pointer-events, not visibility, so the buttons stay in the tab order and
   keyboard focus can show them. The fade-out time
   comes from JS (--sb-controls-fade, invariant 6).
   No flex gap (Safari 12): spacing is margins. */

.sb-window__nav {
  position: absolute;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  z-index: 10;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--sb-controls-fade, 1s) ease;
}

.sb-window--controls .sb-window__nav,
.sb-window--paused .sb-window__nav,
.sb-window--keyboard .sb-window__nav {
  opacity: 1;
  pointer-events: auto;
  transition-duration: 200ms;
}

/* Each dot is a 24 x 24 target (C-10, WCAG 2.5.8); the drawn dot is the
   same 8 px ring as before, on ::before. Phones show no dots (C-3a);
   the desktop query below brings them back. */
.sb-nav__dot {
  display: none;
  position: relative;
  width: 24px;
  height: 24px;
  margin: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  padding: 0;
  cursor: pointer;
}

.sb-nav__dot::before {
  content: '';
  position: absolute;
  top: 8px;
  left: 8px;
  box-sizing: border-box;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1.5px solid rgb(255 255 255 / 60%);
  background: transparent;
  transition:
    background 200ms ease,
    border-color 200ms ease;
}

.sb-nav__dot--active::before,
.sb-nav__dot:hover::before {
  background: rgb(255 255 255 / 90%);
  border-color: rgb(255 255 255 / 90%);
}

/* Pause / Play and Forward, drawn with the owner's row-1 glyphs.
   Touch first (C-3a): no dots, 44 px targets, the glyph inside a squircle
   ring that matches the dots' 1.5 px ring. */
.sb-window__control {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 44px;
  height: 44px;
  min-height: 0;
  margin: 0 4px;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  padding: 0;
  color: #fff;
  line-height: 0;
  cursor: pointer;
  appearance: none;
}

.sb-window__control::before {
  content: '';
  position: absolute;
  top: 5px;
  left: 5px;
  box-sizing: border-box;
  width: 34px;
  height: 34px;
  border-radius: 11px;
  border: 1.5px solid rgb(255 255 255 / 90%);
}

.sb-window__icon {
  display: block;
  width: 14px;
  height: 14px;
}

.sb-window__icon--play,
.sb-window--paused .sb-window__icon--pause {
  display: none;
}

.sb-window--paused .sb-window__icon--play {
  display: block;
}

/* Desktop (C-1): the dots return, and the controls sit either side of them
   unframed, at the dots' 24 px target size. */
@media (hover: hover) and (pointer: fine) {
  .sb-window__nav:hover {
    opacity: 1;
    pointer-events: auto;
    transition-duration: 200ms;
  }

  .sb-nav__dot {
    display: block;
  }

  .sb-window__control {
    width: 24px;
    height: 24px;
    margin: 0;
  }

  .sb-window__control::before {
    content: none;
  }

  .sb-window__icon {
    width: 11px;
    height: 11px;
  }

  .sb-window__pause {
    margin-right: 8px;
  }

  .sb-window__next {
    margin-left: 8px;
  }
}

/* ================================================================
   SCREEN READER LIVE REGION
   ================================================================ */

.sb-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ================================================================
   REDUCED MOTION — accessibility
   ================================================================

   Duration is handled per-preset in JS _presetOf() via the rm
   sub-object on each preset entry. No CSS override is applied —
   _presetOf() is the sole authority for timing in both modes.
   Motion is preserved; only intensity is reduced.
   ================================================================ */

@media (prefers-reduced-motion: reduce) {
  /* Intentionally empty: _presetOf() owns all duration selection. */
}

/* ================================================================
   PORTRAIT OVERRIDES — orientation-based composition integrity
   ================================================================

   In portrait orientation the canvas keeps its ratio R (0.11.0; the
   9:16 stage is long gone), and portrait focal points replace desktop
   (landscape) focal points. These rules follow orientation only; the
   breakpoint settings never chose them.

   Focal cascade: var(--sb-focal-x-mobile, var(--sb-focal-x, 50%))
   --sb-focal-x-mobile is only output when explicitly set; unset
   attachments inherit desktop focal automatically through this chain.

   Render mode: data-mode-mobile on .sb-half, present only when the
   override differs from 'inherit'. Attribute selectors here override
   the desktop class rules via source-order cascade (later in file).
   ================================================================ */

@media (orientation: portrait) {
  .sb-half:not(.sb-half--fit) .sb-media,
  .sb-half:not(.sb-half--fit) img,
  .sb-half:not(.sb-half--fit) video {
    object-position: var(--sb-focal-x-mobile, var(--sb-focal-x, 50%))
      var(--sb-focal-y-mobile, var(--sb-focal-y, 50%));
  }

  .sb-half[data-mode-mobile='cover'] .sb-media,
  .sb-half[data-mode-mobile='cover'] img,
  .sb-half[data-mode-mobile='cover'] video {
    object-fit: cover;
    object-position: var(--sb-focal-x-mobile, var(--sb-focal-x, 50%))
      var(--sb-focal-y-mobile, var(--sb-focal-y, 50%));
    transform: scale(1.2);
  }

  .sb-half[data-mode-mobile='contain'] .sb-media,
  .sb-half[data-mode-mobile='contain'] img,
  .sb-half[data-mode-mobile='contain'] video {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    object-position: var(--sb-seam-anchor, center center);
    transform: none;
  }
}

/* ================================================================
   BROWSER FLOOR — option D fallback image (0.11.0, doc 06 § 18.10)
   ================================================================

   SB_Renderer prints <img class="sb-window-fallback" hidden> after the
   canvas only when SuprBad Window → Settings has a fallback image.
   Browsers with container queries never show it (the hidden attribute
   stands, and loading="lazy" keeps it from being fetched). Browsers
   without them hide the canvas, which could not be sized there, and
   show the image at the band's full width, its height from its own
   ratio, capped by the inline block's max-height (style (i)), contain
   so a capped image is never cropped. sb-window.js returns before it
   starts on the same browsers, so the hidden Window runs no timers.
   No image configured: nothing is printed and nothing shows.
   ================================================================ */

@supports not (container-type: inline-size) {
  .sb-window {
    display: none;
  }

  .sb-window-fallback[hidden] {
    display: block;
    width: 100%;
    height: auto;
    margin-right: auto;
    margin-left: auto;
    object-fit: contain;
  }
}
