/* backdrop-orbits-stage: an add-on for components/backdrop-orbits. A stage in the page (page content,
   not part of the backdrop) holds one example per tool; as the orbit runs, the add-on features the
   tools in turn: the tool lights up wherever it is on its orbit, a soft beam runs from it into the
   stage, and the stage crossfades to that tool's example. See STAGE.md.

   Every name here starts backdrop-orbits-stage- (the engine's own inner layer is .backdrop-orbits-stage,
   with no suffix: nothing here touches it). Tokens sit at zero specificity on :where(:root). */
:where(:root) {
  --backdrop-orbits-stage-dwell: 5.2s;           /* how long each tool is featured */
  --backdrop-orbits-stage-follow: none;          /* an angle on the plane (e.g. 20deg): feature the tool passing it, not the timer */
  --backdrop-orbits-stage-lite: timer;           /* follow mode on a device the engine put in lite mode: timer (keep each dwell), follow, or still */
  --backdrop-orbits-stage-fade: .62s;            /* the whole crossfade: the old item out, then the new one in */
  --backdrop-orbits-stage-rise: .9em;            /* how far the new item rises as it fades in */
  --backdrop-orbits-stage-accent: #F5A83C;       /* the featured tool's light and its beam */
  --backdrop-orbits-stage-beam: 3px;             /* the beam's thickness */
  --backdrop-orbits-stage-w: 46em;               /* the stage's box; the host sets font-size to scale it */
  --backdrop-orbits-stage-h: 33em;
  /* the larger view (data-backdrop-stage-zoom on the stage; STAGE.md "The larger view") */
  --backdrop-orbits-stage-zoom-time: .6s;        /* how long it takes to grow out of the stage, and to go back */
  --backdrop-orbits-stage-zoom-delay: .22s;      /* from the click to the start: the hub's burst first */
  --backdrop-orbits-stage-zoom-fill: .86;        /* the share of the free room it may take (1: all of it) */
  --backdrop-orbits-stage-zoom-dim: rgb(4 8 20 / .45); /* laid over the rest of the container */
  --backdrop-orbits-stage-zoom-top: 0px;         /* room kept free on the screen: a fixed header, a dock, the sides */
  --backdrop-orbits-stage-zoom-bottom: 0px;
  --backdrop-orbits-stage-zoom-side: 16px;
  --backdrop-orbits-stage-zoom-reach: container; /* how far the dim reaches: container, or screen when the container is taller than the window */
}

/* ---------- the stage: every item in one grid cell, the box the size of the largest ---------- */
.backdrop-orbits-stage-view {
  position: relative; display: grid; width: var(--backdrop-orbits-stage-w); height: var(--backdrop-orbits-stage-h);
  -webkit-user-select: none; user-select: none;
}
.backdrop-orbits-stage-item {
  grid-area: 1 / 1; display: grid; place-items: center; min-width: 0; min-height: 0; position: relative;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(var(--backdrop-orbits-stage-rise)) scale(.985);
  /* out: fast, and first */
  transition: opacity calc(var(--backdrop-orbits-stage-fade) * .38) ease-in, transform calc(var(--backdrop-orbits-stage-fade) * .38) ease-in,
    visibility 0s linear calc(var(--backdrop-orbits-stage-fade) * .38);
}
.backdrop-orbits-stage-item.is-shown {
  opacity: 1; visibility: visible; transform: none; pointer-events: auto;
  /* in: once the old one has mostly gone */
  transition: opacity calc(var(--backdrop-orbits-stage-fade) * .62) ease-out calc(var(--backdrop-orbits-stage-fade) * .3),
    transform calc(var(--backdrop-orbits-stage-fade) * .7) cubic-bezier(.2, .7, .2, 1) calc(var(--backdrop-orbits-stage-fade) * .3), visibility 0s;
}
/* on stage, and the next one a moment before its turn: drawn on a layer of its own (the next one
   visible but transparent), so the crossfade is compositor work only */
.backdrop-orbits-stage-item.is-shown, .backdrop-orbits-stage-item.is-next { will-change: opacity, transform; }
.backdrop-orbits-stage-item.is-next:not(.is-shown) { visibility: visible; transition: none; }
/* the item's example is scaled down to fit the box if it is bigger (the script measures it) */
.backdrop-orbits-stage-item > * { flex: none; transform-origin: 50% 50%; }
/* ONLY WHAT IS SHOWING IS DRAWN: an item neither on stage, nor next, nor fading out (.is-leaving, set by
   the script for the length of the fade) has content-visibility: hidden, so the browser skips its contents'
   style and layout while it waits (eight examples of 150 to 250 elements were laid out at load and restyled
   by every rule that touched the stage) and keeps what it worked out last time, so bringing it back is cheap.
   Not display: none: that threw the layout away, and every turn of the cycle laid the next example out again
   from nothing (a 400 ms task on a 4x-throttled phone, each time) */
.backdrop-orbits-stage-view.is-live > .backdrop-orbits-stage-item:not(.is-shown):not(.is-next):not(.is-leaving) { content-visibility: hidden; }
/* without the script (or before a host starts it: data-backdrop-stage-defer), the first item is the stage */
.backdrop-orbits-stage-view:not(.is-live) > .backdrop-orbits-stage-item:first-child { opacity: 1; visibility: visible; transform: none; }
.backdrop-orbits-stage-view:not(.is-live) > .backdrop-orbits-stage-item:not(:first-child) { display: none; }
/* an item not on stage keeps its own loops paused: nobody sees them */
.backdrop-orbits-stage-item:not(.is-shown) *, .backdrop-orbits-stage-item:not(.is-shown) *::before, .backdrop-orbits-stage-item:not(.is-shown) *::after { animation-play-state: paused !important; }
/* stopped (off screen, parked under the host's header after a curtain lifts, a hidden tab, the engine
   paused): the item on stage pauses its loops too, so nothing runs where nobody sees it */
.backdrop-orbits-stage-view.is-stopped .backdrop-orbits-stage-item *, .backdrop-orbits-stage-view.is-stopped .backdrop-orbits-stage-item *::before,
.backdrop-orbits-stage-view.is-stopped .backdrop-orbits-stage-item *::after { animation-play-state: paused !important; }
/* lite (the engine's .is-lite, mirrored on the stage by the script: a device that cannot keep the orbit's
   pace): the examples show their finished frames. The script withholds .is-shown from the example on
   stage (its motion is keyed on it); this holds anything else that loops in an item. The crossfade stays */
.backdrop-orbits-stage-view.is-lite .backdrop-orbits-stage-item *, .backdrop-orbits-stage-view.is-lite .backdrop-orbits-stage-item *::before,
.backdrop-orbits-stage-view.is-lite .backdrop-orbits-stage-item *::after { animation-play-state: paused !important; }
/* held still (reduced motion, the host's still class): a plain fade, no travel */
.backdrop-orbits-stage-view.is-still > .backdrop-orbits-stage-item { transform: none; transition: opacity .16s ease, visibility 0s linear .16s; }
.backdrop-orbits-stage-view.is-still > .backdrop-orbits-stage-item.is-shown { transition: opacity .16s ease; }
/* the first item, and a host that wants a hard cut */
.backdrop-orbits-stage-view.is-instant > .backdrop-orbits-stage-item { transition: none !important; }

/* ---------- the larger view (data-backdrop-stage-zoom) ----------
   A click on the stage (the engine's hub burst first) grows a COPY of the example on stage into a larger view over
   the container (the backdrop's host), the rest of it dimmed; a click anywhere on it, Escape, a scroll or a resize
   sends it back. The script places the box and animates its transform and opacity (and the dim's opacity): nothing
   here moves on its own. The layer is the container's last child, over its content */
.backdrop-orbits-stage-view.is-zoomable { cursor: zoom-in; }
/* the example on stage steps aside while its copy is out, and holds its loops: nobody sees it */
.backdrop-orbits-stage-view.is-zoomed > .backdrop-orbits-stage-item.is-shown { visibility: hidden; }
.backdrop-orbits-stage-view.is-zoomed .backdrop-orbits-stage-item *, .backdrop-orbits-stage-view.is-zoomed .backdrop-orbits-stage-item *::before,
.backdrop-orbits-stage-view.is-zoomed .backdrop-orbits-stage-item *::after { animation-play-state: paused !important; }
.backdrop-orbits-stage-zoom {
  display: none; position: absolute; inset: 0; z-index: 20; box-sizing: border-box; overflow: hidden;
  /* not painted: read by the script as the room to keep free (resolved lengths, so the tokens may be calc()s) */
  padding: var(--backdrop-orbits-stage-zoom-top) var(--backdrop-orbits-stage-zoom-side) var(--backdrop-orbits-stage-zoom-bottom);
  cursor: zoom-out; -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
}
.backdrop-orbits-stage-zoom.is-open { display: block; }
/* --backdrop-orbits-stage-zoom-reach: screen. A container TALLER THAN THE WINDOW dims only its own part of
   the screen, and on a phone that left between a third and over half the screen bright and fully legible
   beside the open view, which reads as unfinished rather than as a frame (the /work hero, 2026-10-10
   review: 70% covered with the stage centred, 44% with it near the top). The script adds this class when
   the token says screen, and then the layer IS the window: nothing else moves, because the room was
   already computed as the layer's box intersected with the window. The reach is the layer's own box only;
   an ancestor with a transform, a filter, will-change or contain would make the containing block, so a
   host that sets this has to keep the layer out from under one. */
.backdrop-orbits-stage-zoom.is-screen { position: fixed; }
.backdrop-orbits-stage-zoom-dim { position: absolute; inset: 0; background: var(--backdrop-orbits-stage-zoom-dim); opacity: 0; }
.backdrop-orbits-stage-zoom-box { position: absolute; display: grid; place-items: center; transform-origin: 0 0; opacity: 0; will-change: transform, opacity; }
.backdrop-orbits-stage-zoom-box > * { flex: none; }
@media print { .backdrop-orbits-stage-zoom { display: none !important; } }

/* ---------- the caption: the featured tool's name and its one plain line ---------- */
.backdrop-orbits-stage-caption { transition: opacity .2s ease; }
.backdrop-orbits-stage-caption.is-swapping { opacity: 0; transition-duration: .14s; }
.backdrop-orbits-stage-caption-name { font-weight: 700; }
.backdrop-orbits-stage-caption-name::after { content: " "; }
.backdrop-orbits-stage-caption-time:not(:empty)::after { content: " \B7  "; }
/* the one sentence a screen reader gets (the stage and the caption are decorative) */
.backdrop-orbits-stage-sr {
  position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---------- the featured tool (the add-on puts .is-featured on its .backdrop-orbits-body) ----------
   A default any skin can override: the card lights in the accent, at zero specificity. Skins style
   .backdrop-orbits-body.is-featured for a look of their own (their hover look is a good start). */
:where(.backdrop-orbits) :where(.backdrop-orbits-body.is-featured .backdrop-orbits-card) {
  filter: drop-shadow(0 0 1px var(--backdrop-orbits-stage-accent)) drop-shadow(0 0 9px color-mix(in srgb, var(--backdrop-orbits-stage-accent) 70%, transparent));
}
/* a name plate dropped while it overlaps the page's words or boxes (data-backdrop-stage-clear); the globe
   stays. The featured tool's too (it would sit clipped under the stage's frame; the stage's caption names
   it); a hovered tool keeps its own. Over a look's own opacity and transition for the name */
.backdrop-orbits-body.backdrop-orbits-stage-quiet:not(.is-hover) .backdrop-orbits-name { opacity: 0 !important; transition: opacity .18s ease !important; }
/* the beam: inside the tool's spoke (the engine places the spoke from the hub to the tool every frame;
   the hub is under the stage), so it follows the tool for free and runs into the stage. The packet runs
   along it from the tool to the stage. The spoke is 100 px wide, stretched to its length. */
.backdrop-orbits-stage-beam, .backdrop-orbits-stage-packet { position: absolute; left: 0; top: 50%; pointer-events: none; opacity: 0; }
.backdrop-orbits-stage-beam {
  width: 100%; height: var(--backdrop-orbits-stage-beam); margin-top: calc(var(--backdrop-orbits-stage-beam) / -2); border-radius: 99px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--backdrop-orbits-stage-accent) 0%, transparent) 12%, color-mix(in srgb, var(--backdrop-orbits-stage-accent) 45%, transparent) 52%, var(--backdrop-orbits-stage-accent));
  box-shadow: 0 0 6px color-mix(in srgb, var(--backdrop-orbits-stage-accent) 55%, transparent);
  transition: opacity .45s ease;
}
.backdrop-orbits-stage-packet {
  width: 7px; height: calc(var(--backdrop-orbits-stage-beam) * 2.4); margin: calc(var(--backdrop-orbits-stage-beam) * -1.2) 0 0 -3.5px; border-radius: 50%;
  background: radial-gradient(closest-side, #FFF8E8, color-mix(in srgb, var(--backdrop-orbits-stage-accent) 90%, #fff) 55%, color-mix(in srgb, var(--backdrop-orbits-stage-accent) 0%, transparent));
}
.backdrop-orbits-spoke.is-featured .backdrop-orbits-stage-beam { opacity: 1; }
.backdrop-orbits-spoke.is-featured .backdrop-orbits-stage-packet { animation: backdrop-orbits-stage-run 1.8s cubic-bezier(.5, 0, .6, 1) .3s infinite; }
/* lite: the beam stays, its packet rests */
.backdrop-orbits.is-lite .backdrop-orbits-spoke.is-featured .backdrop-orbits-stage-packet { animation: none; }
/* the engine's loop stopped (off screen, parked under a header, held still): the packet holds where it is,
   so nothing loops where nobody sees it and a host needs no rule of its own over the whole backdrop */
.backdrop-orbits.is-paused .backdrop-orbits-spoke.is-featured .backdrop-orbits-stage-packet { animation-play-state: paused; }
@keyframes backdrop-orbits-stage-run {
  0% { transform: translateX(100px); opacity: 0; }
  14% { opacity: 1; }
  80% { opacity: 1; }
  100% { transform: translateX(8px); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .backdrop-orbits-stage-view > .backdrop-orbits-stage-item { transform: none; }
  .backdrop-orbits-spoke.is-featured .backdrop-orbits-stage-packet { animation: none; }
}
@media (forced-colors: active) {
  .backdrop-orbits-stage-beam, .backdrop-orbits-stage-packet { display: none; }
}
