/* Configurator poster zoom (see poster-zoom.js). The #poster figure never moves or scales: it
   keeps the exact box fitPoster() gives it, preserving neighbours and page geometry. Raster
   previews move inside that box. A retained vector poster is moved into the fixed viewport
   presentation below and receives real layout dimensions, never a bitmap-producing transform. */
.poster.is-zoomable{overflow:hidden;touch-action:pan-y;cursor:zoom-in;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent}
.poster.is-zoomable canvas,.poster.is-zoomable svg{touch-action:pan-y}
/* Zoomed: the map owns every touch inside it (drag = pan, pinch = zoom); the page keeps its
   normal scrolling everywhere else, and again over the map as soon as the zoom is back to 1. */
.poster.is-zoomed,.poster.is-zoomed canvas,.poster.is-zoomed svg{touch-action:none}
.poster.is-zoomed{cursor:grab}
.poster.is-zoomed.is-panning{cursor:grabbing}
/* Set once a 2nd finger joins and held until every pointer lifts (poster-zoom.js), independent
   of the animated is-zoomed/scale state: touch-action must not change value while a touch
   sequence is in progress, or WebKit can cancel the gesture outright mid-pinch. */
.poster.is-touch-locked,.poster.is-touch-locked canvas,.poster.is-touch-locked svg{touch-action:none}
.poster.is-zoomable>#poster-canvas,.poster-zoom-layer{transform-origin:0 0}
.poster.is-zoomed>#poster-canvas,.poster.is-zoomed>.poster-zoom-layer{transform:translate(var(--zoom-x,0px),var(--zoom-y,0px)) scale(var(--zoom-scale,1));will-change:transform}
/* Vector poster (poster-svg-renderer.js): shown instead of the canvas for a classic star chart.
   At rest it fills the figure. During zoom the exact node lives in .poster-zoom-presentation. */
#poster-svg{display:block;width:100%;height:100%;pointer-events:none}
.poster.is-vector>#poster-canvas,.poster:not(.is-vector)>#poster-svg{display:none}
.poster.is-vector-presented{background:transparent;box-shadow:none}
/* Viewport museum presentation. Its surface grows via left/top/width/height declarations set by
   JS; deliberately do not add transform/zoom here, as that may cache a bitmap on mobile Safari. */
.poster-zoom-portal{position:fixed;inset:0;z-index:40;overflow:hidden;pointer-events:none;contain:layout style paint}
.poster-zoom-portal[hidden],.poster-zoom-presentation[hidden]{display:none}
.poster-zoom-presentation{position:absolute;pointer-events:auto;touch-action:none;cursor:grab;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;box-shadow:0 24px 80px #0006,0 8px 28px #0004;background:transparent}
.poster-zoom-presentation:active{cursor:grabbing}
.poster-zoom-presentation>svg{display:block;width:100%;height:100%;pointer-events:none;touch-action:none}
/* The retained node may be adopted while the vector intro is still travelling. Mirror the
   figure's existing reveal contract on the portal: the sky stays live and sharp, while paper,
   borders, text and shadow remain hidden until the same fade clock reveals them. */
body:has(.poster.intro-sky) .poster-zoom-presentation{box-shadow:none}
body:has(.poster.intro-sky) .poster-zoom-presentation>svg>:not(defs):not(clipPath):not([data-layer="sky"]){visibility:hidden}
body:has(.poster.intro-sky-fade) .poster-zoom-presentation{animation:poster-intro-sky-fade 600ms ease-in both}
body:has(.poster.intro-sky-fade) .poster-zoom-presentation>svg>:not(defs):not(clipPath):not([data-layer="sky"]){animation:poster-intro-fade 600ms ease-in both}
/* High-resolution layer: same CSS box and transform as the base canvas, shown only when zoomed
   and up to date, so the pixels under the fingers are the same picture at a finer resolution. */
.poster-zoom-layer{position:absolute;inset:0;width:100%;height:100%;display:block;pointer-events:none}
/* Reset affordance: visible only while zoomed (hidden attribute toggled by the controller). */
.poster-zoom-reset{position:fixed;z-index:2;pointer-events:auto;min-height:36px;padding:6px 12px;border:1px solid rgba(255,255,255,.35);border-radius:999px;background:rgba(17,17,17,.78);color:#fff;font-size:13px;line-height:1.2;box-shadow:0 2px 8px rgba(0,0,0,.25);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}
.poster-zoom-reset[data-zoom]::before{content:attr(data-zoom) " · ";opacity:.75}
.poster-zoom-reset:hover{background:rgba(17,17,17,.92)}
.poster-zoom-reset:focus-visible{outline:2px solid #fff;outline-offset:2px}
@media(prefers-reduced-motion:reduce){.poster.is-zoomed>#poster-canvas,.poster.is-zoomed>.poster-zoom-layer{will-change:auto}body:has(.poster.intro-sky) .poster-zoom-presentation{box-shadow:0 24px 80px #0006,0 8px 28px #0004}body:has(.poster.intro-sky) .poster-zoom-presentation>svg>*{visibility:visible}body:has(.poster.intro-sky-fade) .poster-zoom-presentation,body:has(.poster.intro-sky-fade) .poster-zoom-presentation>svg>*{animation:none}}
