/* Live-demo component: a preview thumbnail that expands into a live app.
   Plug-and-play — see live-demo.js for the markup contract.

   Lifecycle: the thumb shows its screenshot immediately (never a blank
   box). The app boots hidden behind it; when ready, the live miniature
   fades in over the screenshot ("parked"). Clicking grows the parked
   miniature to fullscreen; closing shrinks it back — the same running
   pixels the whole time, nothing ever swaps. */

.ld-thumb {
  position: relative;
  margin: 0.75em 0;
  overflow: hidden;
  cursor: pointer;
  line-height: 0;
  background: #0d0d0d;
}
.ld-thumb img {
  width: 100%;
  /* Aspect ratio is matched to the viewport in JS so the expand grows as a
     uniform, distortion-free zoom; 16/10 is the no-JS fallback. */
  aspect-ratio: 16 / 10;
  object-fit: cover;
  /* Top-anchored, NOT centered: the box is the viewport's aspect ratio but the
     screenshot is the capture's, so cover always trims some height. A live
     viewport that's shorter than the capture drops content off the BOTTOM —
     centering instead trims the top too, which reads as a zoom-in that pops
     back out the moment the real app parks over it. */
  object-position: top center;
  display: block;
}
.ld-thumb:focus-visible { outline: 2px solid #333; outline-offset: 2px; }

/* ── Rounded corners on the demo previews ─────────────────────────────
   The thumb's own radius is set on .band-artifact .ld-thumb in style.css.
   But at rest the visible box is the live app parked in .ld-overlay-frame,
   transform-scaled down onto the thumb — so a raw radius here would shrink
   with it. --ld-r, published on the overlay by live-demo.js, is that radius
   pre-divided by the parked scale, so the corner lands at the same on-screen
   size as the thumb's; the frame and the fallback poster both read it.
   The unpak-system diagram never builds a frame here, so it stays sharp. */
.ld-overlay .ld-overlay-frame,
.ld-overlay .ld-overlay-poster {
  border-radius: var(--ld-r, var(--ld-corner, 40px));
}
/* Fullscreen is square. This is the END state of the grow; live-demo.js
   transitions the radius to it on the same clock as the transform, so the
   corners round off gradually and are only fully sharp once fully expanded
   (and round back on the way in). */
.ld-overlay.open .ld-overlay-frame,
.ld-overlay.open .ld-overlay-poster {
  border-radius: 0;
}

/* Icon-only expand button, dead centre of the preview (built in
   live-demo.js). Hidden until the thumbnail is hovered: at rest the preview
   is just the running app, with no chrome sitting on top of it. Hover
   works even though a live iframe
   covers the thumb because the parked overlay is pointer-events:none — the
   hover lands on the figure.

   font-size is pinned rather than inherited so the button is the same size
   on every page whatever the surrounding type.

   Sits ABOVE the parked live miniature (z-index 30 > overlay's 20) so it
   never vanishes when the app takes over the preview; it fades out only
   while the demo is actually open fullscreen, where .ld-back is the one
   control on screen. */
.ld-expand {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 30;
  font-size: 1rem;
  width: 4em;
  height: 4em;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: rgba(20, 20, 20, 0.5);
  opacity: 0;
  /* the centring translate rides along with the reveal's scale */
  transform: translate(-50%, -50%) scale(0.8);
  transition: opacity 0.25s ease, transform 0.25s ease, background 0.2s ease;
  pointer-events: none;
}
.ld-thumb:hover .ld-expand,
.ld-thumb:focus-visible .ld-expand {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
  background: rgba(20, 20, 20, 0.72);
}
.ld-expand svg { width: 1.9em; height: 1.9em; }
body.ld-locked .ld-expand { opacity: 0; }

/* The arrows draw themselves outward from the centre of the button on
   reveal. Every stroke is authored so its START point is the one nearest
   the middle, and pathLength="1" renormalises each one to a single unit —
   so `stroke-dasharray: 1` is exactly one path long whatever the real
   geometry, and dashoffset 1 -> 0 sweeps the ink from the centre out. */
.ld-expand svg line,
.ld-expand svg polyline {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 0.52s ease;
}
/* ...and the two arrows part from each other as they draw: each group
   starts pulled in toward the middle and settles into its corner. */
.ld-arrow { transition: transform 0.68s cubic-bezier(0.2, 0.8, 0.3, 1); }
.ld-arrow-tr { transform: translate(-2.5px, 2.5px); }
.ld-arrow-bl { transform: translate(2.5px, -2.5px); }

.ld-thumb:hover .ld-arrow,
.ld-thumb:focus-visible .ld-arrow { transform: none; }
.ld-thumb:hover .ld-expand line,
.ld-thumb:focus-visible .ld-expand line { stroke-dashoffset: 0; }
/* the arrowhead lands a beat after its own shaft */
.ld-thumb:hover .ld-expand polyline,
.ld-thumb:focus-visible .ld-expand polyline {
  stroke-dashoffset: 0;
  transition-delay: 0.24s;
}

@media (prefers-reduced-motion: reduce) {
  .ld-expand svg line,
  .ld-expand svg polyline {
    stroke-dashoffset: 0;
    transition: none;
  }
  .ld-expand .ld-arrow-tr,
  .ld-expand .ld-arrow-bl { transform: none; transition: none; }
}

/* Below 700px the fullscreen expansion is disabled outright (the guard
   lives in live-demo.js and diagram-expand.js): the previews stay live,
   but a tap or click no longer opens them. Hide the mark so nothing
   advertises an interaction that won't happen, and drop the pointer. */
@media (max-width: 43.75rem) {
  .ld-expand { display: none !important; }
  .ld-thumb { cursor: default; }
}

/* Touch screens have no hover to reveal the expand mark, and a preview
   with no visible control reads as a picture rather than a door. So on
   coarse pointers the mark sits on the preview at rest — smaller and
   quieter than the hover version, arrows already drawn, riding the
   corner instead of covering the middle of the app. */
@media (hover: none) and (pointer: coarse) {
  .ld-expand {
    top: auto;
    left: auto;
    right: 0.9em;
    bottom: 0.9em;
    font-size: 0.8rem;
    width: 3.4em;
    height: 3.4em;
    opacity: 1;
    transform: none;
    background: rgba(20, 20, 20, 0.6);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22),
                0 0.25rem 0.9rem rgba(0, 0, 0, 0.35);
  }
  .ld-expand svg line,
  .ld-expand svg polyline { stroke-dashoffset: 0; }
  .ld-expand .ld-arrow-tr,
  .ld-expand .ld-arrow-bl { transform: none; }
  /* a tap flashes :hover on some mobile browsers; the hover rules above
     re-center the mark, so pin it in its corner at higher specificity */
  .ld-thumb:hover .ld-expand,
  .ld-thumb:focus-visible .ld-expand {
    transform: none;
    background: rgba(20, 20, 20, 0.6);
  }
  body.ld-locked .ld-expand { opacity: 0; }
}

/* The overlay is anchored in DOCUMENT coordinates (position:absolute at the
   top of the page), and the parked miniature is placed with a transform that
   includes the scroll offset. Scrolling therefore moves it natively with the
   page — no scroll listener, no one-frame lag, no jiggle. */
.ld-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  /* dynamic viewport units where they exist: on phones 100vh includes the
     space under the collapsing browser chrome, which would leave a strip
     of a fullscreen demo hidden behind the toolbar */
  height: 100dvh;
  z-index: 20;
}
.ld-overlay[hidden] { display: none; }
/* Open OR closing both stay above the page. The grow rides up on top of
   everything; the shrink has to ride back down the same way, revealing the
   page from under the frame as it goes rather than dropping below it at the
   first click — which would pop the TOC and the other demos up over a frame
   that's still moving. .closing is cleared on transitionend (live-demo.js);
   it deliberately does NOT force the corners square the way .open does, so
   the radius rounds back off as the frame shrinks. */
.ld-overlay.open,
.ld-overlay.closing { z-index: 1000; }
/* Parked: purely visual — clicks fall through to the thumbnail underneath. */
.ld-overlay.parked { pointer-events: none; }

/* The live app. Scaling this wrapper performs the grow/shrink, so the end
   state is pixel-perfect with no poster-to-app jump. */
.ld-overlay-frame {
  position: absolute;
  inset: 0;
  /* clip, not hidden: the iframe inside can be laid out wider than the
     frame and transform-scaled down (see frameGeom in live-demo.js), and a
     hidden-overflow box is still programmatically scrollable — a focus
     jump inside the app could drag the canvas off its top-left anchor */
  overflow: clip;
  background: #0d0d0d;
  opacity: 0;
  transition: opacity 0.45s ease;
  transform-origin: top left;
  will-change: transform;
}
.ld-overlay.revealed .ld-overlay-frame { opacity: 1; }
.ld-overlay-frame iframe {
  width: 100%;
  height: 100%;
  border: none;
  display: block;
}

/* Fallback poster for a click that beats the app's boot: a still image
   grows compositor-smooth, then cross-fades to the app when it's ready. */
.ld-overlay-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;   /* same framing as the thumb — see above */
  transform-origin: top left;
  will-change: transform, opacity;
  background: #0d0d0d;
}
/* once revealed the poster is invisible but would still swallow every
   click — an opacity-0 element still hit-tests — so let events through */
.ld-overlay.revealed .ld-overlay-poster { opacity: 0; pointer-events: none; }

/* The site's back control, re-provided inside the overlay (built in
   live-demo.js). It carries the .back class too, so the mark, the chevron
   and the ring-drawing-itself-closed hover all come straight from
   style.css and can't drift from the real one. What's different here:

   - it has to clear the overlay, so z-index goes above 1000;
   - it's bigger than the page's, because it's the one exit from a
     fullscreen app and has to be an obvious target;
   - it gets a scrim. The page's back link can rely on the site's own dark
     background; this one sits over whatever the embedded app painted,
     which is cream in FloorSense and near-black in PapeAgNet — so it
     carries its own contrast rather than hoping for it.
   - it does NOT auto-hide or wait on a hover. It is the only control on
     screen once a demo is open, and once it has arrived it stays put — an
     exit you have to go looking for isn't one. Its one piece of timing is
     the entrance: it holds off until the grow has landed, then pops in. */
.ld-back {
  /* Fixed, matching the page's own .back: the overlay is anchored in
     document coordinates, so an absolute child would sit wherever the top
     of the page is — i.e. off-screen entirely for any demo opened below
     the fold. While a demo is open the body is locked and nothing scrolls,
     so viewport-pinned is the only thing that stays on screen. */
  position: fixed;
  z-index: 1001;
  top: 1.25rem;
  left: 1.25rem;
  width: 2.75rem;
  height: 2.75rem;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  color: #fff;
  background: rgba(20, 20, 20, 0.8);
  /* A dark disc alone is only guaranteed to read on a light app — over
     PapeAgNet's near-black header it vanishes into the chrome. The hairline
     and the shadow are what make it legible in BOTH directions: the light
     ring separates it from a dark app, the shadow from a light one. */
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.25),
              0 0.35rem 1.25rem rgba(0, 0, 0, 0.45);
  /* Hidden and intangible until .ld-ready, which live-demo.js sets when the
     grow lands — then it pops into existence. The overshoot curve is the
     one .back already uses for its own hover swell in style.css, so the
     control arrives the way the rest of the site moves. */
  opacity: 0;
  transform: scale(0.4);
  pointer-events: none;
  transition: opacity 0.16s ease,
              transform 0.36s cubic-bezier(0.34, 1.56, 0.64, 1),
              background 0.2s ease;
}
.ld-overlay.open.ld-ready .ld-back {
  opacity: 1;
  transform: scale(1);
  pointer-events: auto;
}
.ld-back .back-mark { width: 1.875rem; height: 1.875rem; }
/* .back's own hover recolours toward --fg, which is a page colour; on the
   scrim the mark is already white, so darken the disc instead. */
.ld-back:hover,
.ld-back:focus-visible {
  color: #fff;
  background: rgba(20, 20, 20, 1);
}

/* Scroll lock while the demo is open. Pinning the body (with its top set
   to -scrollY in JS) rather than just hiding overflow: on a full-height
   body, `overflow: hidden` alone collapses the scrollable area and the
   page silently jumps to the top under the overlay. */
body.ld-locked {
  position: fixed;
  width: 100%;
  overflow: hidden;
}
