/* The before/after component (T sections 3, 6 and 10).
   Every colour comes from a token in S section 1. There is no colour value in
   this file. Pair it with /static/hub/tokens.css and /static/hub/ba.js.

   With no JS at all the frame is already correct: the server writes --x:50% in
   the style attribute, so both photographs show, split down the middle, with
   both tags. That is the whole no-JS fallback. There is no noscript block. */

/* --x must be registered or the teaching wipe and the hero scrub cannot
   interpolate it: an unregistered custom property animates as a discrete
   swap. */
@property --x{ syntax:"<percentage>"; inherits:true; initial-value:50%; }

.cmp{
  position:relative; overflow:hidden; isolation:isolate;
  aspect-ratio:var(--cmp-ar, 4/5);
  border-radius:var(--px-r-card); border:var(--px-hair) solid var(--px-rule);
  background:var(--px-ink-3) var(--lqip) center/cover no-repeat;
  touch-action:pan-y;                  /* playable.py:142, the frame keeps the scroll */
  user-select:none; -webkit-user-select:none;
  cursor:ew-resize;
  contain:paint;                       /* a card's repaint stays inside the card */
}
/* The range never receives a touch. It stays focusable, still carries
   role=slider, aria-valuenow, arrows, Home and End, and still fires input.
   playable.py:145: this line is the fix for the scroll lock. */
.cmp > *{ pointer-events:none; }
/* touch-action:none belongs on the handle and nowhere else. The handle is a
   marker inside a pointer-events:none subtree, so this is a declaration of
   intent for anyone who later makes it interactive, not a live rule. */
.cmp-line::after{ touch-action:none; }

.cmp img{ position:absolute; inset:0; width:100%; height:100%;
          object-fit:cover; object-position:50% 50%; display:block; }
.cmp .over{ clip-path:inset(0 calc(100% - var(--x,50%)) 0 0); }

.cmp-line{ position:absolute; top:0; bottom:0; left:var(--x,50%);
           width:var(--px-hair); background:var(--px-seam);
           box-shadow:0 0 0 .5px color-mix(in srgb, var(--px-ink) 45%, transparent); }
.cmp-line::after{
  content:""; position:absolute; top:50%; left:50%;
  transform:translate(-50%,-50%);
  width:var(--cmp-handle,28px); height:var(--cmp-handle,28px);
  border-radius:var(--px-r-pill);
  /* A solid pill with the glyph drawn ON it, never a mask: a mask clips the
     element's own box-shadow too, which left thin white strokes with no disc
     and no shadow, invisible on a photograph. The owner read that as a slider
     that does not work (2026-09-10). Background image, not mask. */
  background:var(--px-chalk) var(--px-icon-drag) center/15px no-repeat;
  box-shadow:0 1px 6px color-mix(in srgb, var(--px-ink) 55%, transparent),
             0 0 0 1px color-mix(in srgb, var(--px-ink) 12%, transparent);
}

.cmp-tag{ position:absolute; top:var(--px-2);
          font:600 var(--px-t-micro)/1 var(--px-font); letter-spacing:.04em;
          padding:3px 7px; border-radius:var(--px-r-chip);
          background:color-mix(in srgb, var(--px-ink) 62%, transparent);
          color:var(--px-chalk);
          -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px); }
.cmp-tag.before{ left:var(--px-2); } .cmp-tag.after{ right:var(--px-2); }

.cmp input[type=range]{ position:absolute; inset:0; width:100%; height:100%;
                        opacity:0; margin:0; }
/* The ring goes on the visible frame, not on an invisible input. */
.cmp:has(input:focus-visible){ outline:2px solid var(--px-focus); outline-offset:2px; }

.cmp.is-dragging{ cursor:grabbing; }
/* The delta gate (T section 7): the server adds .static below the threshold,
   so the after frame shows whole and no slider chrome is rendered. */
.cmp.static .cmp-line,.cmp.static .cmp-tag.before,.cmp.static input{ display:none; }
.cmp.static .over{ clip-path:none; }
/* A photograph 404'd (playable.py:143-145): whichever layer loaded shows
   whole, with no seam and no handle. */
.cmp.bare .cmp-line,.cmp.bare .cmp-tag,.cmp.bare input{ display:none; }
.cmp.bare .over{ clip-path:none; }

/* The teaching wipe, once per session, at most four cards (T section 10).
   520 ms and a return to 50 percent, so any screenshot taken after the beat
   is the designed one. ba.js adds the class and the per-card delay, and
   removes both on the first pointerdown. */
@keyframes cmp-teach{
  0%{ --x:50%; } 35%{ --x:62%; } 70%{ --x:38%; } 100%{ --x:50%; }
}
.cmp.is-wiping{ animation:cmp-teach 520ms var(--px-e-wipe) both; }

/* Hover reveal, fine pointers only, and only where the frame asks for it.
   No transition during the move: a lag between the cursor and the seam reads
   as broken. The return to 50 is the only thing that eases. */
@media (hover:hover) and (pointer:fine){
  .cmp[data-hover="1"] .over{ transition:none; }
  .cmp[data-hover="1"].is-returning .over{ transition:clip-path var(--px-t-enter) var(--px-e-enter); }
  .cmp[data-hover="1"].is-returning .cmp-line{ transition:left var(--px-t-enter) var(--px-e-enter); }
}

@media (prefers-reduced-motion:reduce){
  .cmp.is-wiping{ animation:none; }
  .cmp .over,.cmp .cmp-line{ transition:none !important; }
}

/* Variants. A variant sets the box and the handle and nothing else. A changed
   aspect ratio is --cmp-ar at the call site, not a sixth variant. */
.cmp[data-variant="card"]{ --cmp-ar:4/5; --cmp-handle:28px; }
.cmp[data-variant="card"] .cmp-tag.before{ display:none; }

.cmp[data-variant="hero-m"]{
  --cmp-handle:44px;
  aspect-ratio:auto; height:clamp(240px, calc(100svh - 264px), 434px);
  border-radius:0; border-inline:0;
}
.cmp[data-variant="hero-d"]{ --cmp-ar:4/5; --cmp-handle:44px; }
.cmp[data-variant^="hero"] .cmp-tag{ font-size:12.5px; top:var(--px-3); }
.cmp[data-variant^="hero"] .cmp-tag.before{ left:var(--px-3); }
.cmp[data-variant^="hero"] .cmp-tag.after{ right:var(--px-3); }

/* THE SCROLL DRIVEN HERO SCRUB IS DELETED. It animated --x from 30 to 78 on a
   view() timeline with fill both, and a CSS animation always beats an inline
   style, so the hero's seam was owned by the scroll position for the life of
   the page: dragging it set --x to 13 percent while the computed value stayed
   at 55, the clip and the handle ignored the drag, and the owner reported the
   big slider as simply not working (2026-09-10). A comparison control the
   visitor cannot command is worth less than any reveal it buys. Nothing may
   animate --x except the one-shot teaching wipe, which ends and is cancelled
   by the first touch. If a scroll reveal comes back, it must animate a
   different property or stop on pointerdown, and it must be tested by
   dragging, not by looking. */

/* Nothing holds the seam once it is grabbed. */
.cmp.is-dragging{ animation:none !important; }
