/* ===== DATA DICTIONARY TOURS =====
   Page-specific layer over css/demo.css for salesforce-datadictionary.html.
   The engine (js/cycle.js), the player wiring (js/demo.js, js/demo-nav.js,
   js/modal.js) and the generic window/curtain/transport styling all come from
   the shared build files; everything Salesforce-capture-specific lands here
   instead. What is shared with the studio page's player stays in
   demo.css, even where this page asked for it first: the fullscreen-only
   hide, the launcher card and the modal frame all started here and moved out
   on 24/08/2026 when the studio page went the same way.

   The tours reuse the shared annotation classes where the look is the same
   job (.dash-frame, .dash-badge, .jc-tip, .shot-legend), and add dict-*
   classes only for what these captures need: a light canvas, per-step
   screenshot swaps, the live-search typing stack, spotlight cutouts and the
   simulated click sequences. All four stages sit on one canonical 1467x664
   canvas (top-left anchored crops of the same browser window), so the chain
   hand-off never resizes the frame. */

/* ------------------------------------------------------------ stage sizing
   The captures are 1467x664 (aspect 2.2093), much wider than the Fulltrace
   1010x775 window, so the shared 960px cap renders the Salesforce UI too
   small to read. Wider inline stage, and the same viewport-height guard as
   demo.css so the intro (85% visibility) can always fire. */

.window--dict .shot-stage { width: min(1040px, calc(100vw - var(--demo-gutter))); }

@media (min-width: 700px) {
  .window--dict .shot-stage {
    width: min(1040px, calc(100vw - var(--demo-gutter)), calc((100vh - 130px) * 2.2093));
  }
}

/* Fullscreen: demo.css reads --stage-base with !important, so only the token
   needs overriding, not the rule. 1467 is the capture's own width: a big
   enough screen shows it at exactly 1x, and the other two terms keep it from
   ever upscaling past that. Screens under ~1570px wide never reach the cap,
   so they render as they always did. */
.window--dict.is-modal {
  --stage-base: min(1467px, 100vw, calc((100vh - 104px) * 2.2093));
}

/* ------------------------------------------- fullscreen only, and the frame
   Both used to live here. The studio tours went fullscreen-only too on
   24/08/2026, so the rules behind it are shared now and sit in css/demo.css:
   the `html.has-js .window--fs-only:not(.is-modal)` hide (this page's window
   carries window--fs-only), the .demo-launch button that stands in its
   place, and the 1px frame the modal state would otherwise drop. Nothing
   Salesforce-specific was left behind in any of them. */

/* Welded literal, deliberately NOT a token: sampled from the captures' own
   Lightning page chrome, visible only while a screenshot is still fetching. */
.dict-svg { background: #f3f2f2; }

/* The caption cards ride a 1467-unit canvas against Fulltrace's 1010, so the
   shared 15px lands smaller on the glass; one size up restores parity. */
/* And the fade with it: demo.css's 0.4s is judged against a 1010-unit stage,
   where the card is a small thing appearing. At 1467 it covers enough of the
   screen that the same 0.4s reads as a pop rather than an arrival. */
.window--dict .jc-tip { transition-duration: 0.6s; }

.window--dict .jc-tip-card { font-size: 16.5px; }
.window--dict .jc-tip-head { font-size: 14.5px; }

/* Welded dark, at both themes. demo.css inverts the card against the PAGE
   theme, which is the right read for Fulltrace: its captures are dark VS Code,
   so a light grey card floats above them and a light page gets the dark card
   instead. These captures are white Salesforce UI whatever the page is doing,
   so that light card lands white-on-white and the annotation layer stops
   reading as a layer. Invert against the CAPTURE instead: the dark card
   demo.css already ships for light mode, used at both. Sampled literals like
   the ones they override, so leave them out of any token sweep. */
.window--dict .shot-svg .jc-tip-card {
  color: #f0f1f3;
  background: #454850;
  border-color: rgba(255, 255, 255, 0.12);
}

.window--dict .shot-svg .jc-tip-head { color: #C3BBFB; }

/* --------------------------------------------------------- screenshot swaps
   Same contract as the Fulltrace tabs tour: the base capture is always
   painted, each later step's capture stacks above it and fades in when its
   step lights. The outgoing image holds for 1s while the incoming fades over
   it, so the eye never sees the base through the dissolve.

   The 0.3s below is a beat of stillness at the hand-off, asked for across the
   tours (Jason, 20/08/2026): the card and the frame of the new step land, the
   screen it is about waits, and only then does it change. It belongs on every
   swap that crosses a dot. The sequences that play INSIDE one dot keep their
   own cadence: the typing stack and the zoom stack below take the same 0.3s
   on their first frame only, because that frame is the crossing, and the
   later frames just carry it so their spacing stays as it was. The click-led
   swaps (dependency 3-16, ERD 3-4) are untouched, since their wait is the
   click reading and lengthening it would loosen the cause from the effect. */

.dict-step-img { opacity: 0; transition: opacity 1s ease 1s; }

.dict-stage:has(.jc-step[data-step="2"].is-active) .dict-step-img[data-step-img="2"],
.dict-stage:has(.jc-step[data-step="3"].is-active) .dict-step-img[data-step-img="3"],
.dict-stage:has(.jc-step[data-step="4"].is-active) .dict-step-img[data-step-img="4"],
.dict-stage:has(.jc-step[data-step="5"].is-active) .dict-step-img[data-step-img="5"],
.dict-stage:has(.jc-step[data-step="6"].is-active) .dict-step-img[data-step-img="6"],
.dict-stage:has(.jc-step[data-step="7"].is-active) .dict-step-img[data-step-img="7"],
.dict-stage:has(.jc-step[data-step="8"].is-active) .dict-step-img[data-step-img="8"],
.dict-stage:has(.jc-step[data-step="9"].is-active) .dict-step-img[data-step-img="9"],
.dict-stage:has(.jc-step[data-step="10"].is-active) .dict-step-img[data-step-img="10"],
.dict-stage:has(.jc-step[data-step="11"].is-active) .dict-step-img[data-step-img="11"],
.dict-stage:has(.jc-step[data-step="12"].is-active) .dict-step-img[data-step-img="12"],
.dict-stage:has(.jc-step[data-step="13"].is-active) .dict-step-img[data-step-img="13"],
.dict-stage:has(.jc-step[data-step="14"].is-active) .dict-step-img[data-step-img="14"],
.dict-stage:has(.jc-step[data-step="15"].is-active) .dict-step-img[data-step-img="15"],
.dict-stage:has(.jc-step[data-step="16"].is-active) .dict-step-img[data-step-img="16"] {
  opacity: 1;
  transition-delay: 0.3s;
}

/* Theme-matched floor between the base and the swaps, for the tab-style tours
   (setup, explorer) whose whole screen changes per step: a dissolve passes
   through this panel grey rather than through the base capture. The graph
   tours carry no floor element, so their near-identical frames dissolve into
   each other directly. */
.dict-swap-floor { fill: #f3f2f2; opacity: 0; transition: opacity 0.25s ease; }

.dict-stage:has(.jc-step[data-step="2"].is-active) .dict-swap-floor,
.dict-stage:has(.jc-step[data-step="3"].is-active) .dict-swap-floor,
.dict-stage:has(.jc-step[data-step="4"].is-active) .dict-swap-floor,
.dict-stage:has(.jc-step[data-step="5"].is-active) .dict-swap-floor,
.dict-stage:has(.jc-step[data-step="6"].is-active) .dict-swap-floor {
  opacity: 1;
  transition-delay: 0.3s;      /* with the swap above: grey must not arrive before the picture */
}

/* ------------------------------------------------------- live-search typing
   The explorer's step 5 plays the search narrowing as a typed sequence: four
   captures (nu, num, numbero, numberofe) stack in order, each arriving on its
   own beat and staying (fill both), so the table visibly shrinks 14, 12, 2, 1
   under a single step. Driven by the step class, so stepping away resets the
   stack and the exit fades out on the shared swap timing. */

.dict-type-img { opacity: 0; transition: opacity 1s ease 1s; }

.dict-stage:has(.jc-step[data-step="5"].is-active) .dict-type-img {
  animation: dict-type 0.45s ease 0.3s both;
}

/* 0.3s carried through, so the keystrokes stay 1.5s apart and only the arrival
   of the first one moves. */
.dict-stage:has(.jc-step[data-step="5"].is-active) .dict-type-img[data-type="2"] { animation-delay: 1.8s; }
.dict-stage:has(.jc-step[data-step="5"].is-active) .dict-type-img[data-type="3"] { animation-delay: 3.3s; }
.dict-stage:has(.jc-step[data-step="5"].is-active) .dict-type-img[data-type="4"] { animation-delay: 4.8s; }

@keyframes dict-type {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ------------------------------------------------------------- spotlights
   A full-canvas scrim with the step's region cut out (fill-rule evenodd in
   the markup, which takes as many cutouts as a step needs: several of these
   light a control and the thing it drives at once, because scrimming the
   payoff to point at the button that summoned it reads backwards). Lighter
   than the Fulltrace 0.86: these captures are white UI, and the surroundings
   should stay readable as context.

   Every step of the explorer, ERD and documentation tours carries one. The
   dependency tour scrims sparingly: its click steps play a cursor across a
   screen that is still arriving, so only the steps whose payoff IS a redrawn
   region (3, 4, 6 and 7) scrim, and each waits for the response before it
   fades in (see the hold-back delays below). */

/* In light mode the shared token resolves --demo-dim to the PAGE background,
   which for Fulltrace's dark VS Code captures is the right way round: a pale
   scrim over a dark screenshot dims it. These captures are white Salesforce
   UI at both themes, so the same token painted near-white over near-white
   dimmed nothing and the spotlight did not read at all (Jason, 20/08/2026).

   A mid grey instead, and only for this window, so the Fulltrace player keeps
   the token it was tuned against. Measured off the rendered frame rather than
   guessed: under the 0.55 below, #54565C takes the capture's white chrome from
   230 down to about 151 and its white panels to about 161, against the 251 of
   whatever the step has cut out. Dark mode's own scrim lands the same chrome
   at 112, so this is a shade lighter than that on purpose: it is the same
   spotlight, read on a light page, and the surroundings still have to stay
   legible as context, which is the whole bargain of the lighter 0.55. */
:root[data-theme="light"] .window--dict { --demo-dim: #54565C; }

.dict-spot {
  fill: var(--demo-dim);
  opacity: 0;
  transition: opacity 0.65s ease;
  pointer-events: none;
}

.jc-step.is-active .dict-spot { opacity: 0.55; }

/* A second, lighter scrim laid inside the first one's cutout, so a step can
   read at three levels rather than two: the framed control at full white, the
   region it acted on under a touch of shade (still legible, plainly the
   result), and everything else under the full 0.55. Its outer edge is the
   cutout it sits in, so the two scrims never stack. */
.jc-step.is-active .dict-spot--soft { opacity: 0.2; }

/* ------------------------------------------------------------ staged shadow
   A blurred stroke drawn on the cutout's own outline and clipped to the scrim,
   so only the half that falls on the dimmed side survives. The lit region then
   reads as lifted off the page rather than punched through it, which the flat
   cutout on its own never quite said. Stroke and blur are in user units, so
   both scale with the stage instead of thinning out at fullscreen. */
.dict-stage-shadow {
  fill: none;
  stroke: #0E0E11;
  stroke-width: 30;
  filter: blur(14px);
  opacity: 0;
  transition: opacity 0.65s ease;
  pointer-events: none;
}

.jc-step.is-active .dict-stage-shadow { opacity: 0.5; }

/* ---------------------------------------------- simulated clicks (dep 2-16)
   The dependency tour plays its interactions rather than describing them:
   each click step acts on the PREVIOUS step's screen. A cursor and ripple
   (.dict-click, one per step, inside its own jc-step group) read first, and
   only then does the response fade in: the inspector filling (3), the map
   refocusing after a double-click (4), the node menu (5), Grouped View (6),
   RecordType then ApexClass clicked open (7, 8), the traversal into a
   class's own perspective and its objects (9, 10), the double-click home to
   the grouped census (11), the menu reopened on the root (12), Metadata
   Overview landing collapsed (13), two tree types unfolding (14, 15), and
   the double-click on a listed class handing back to the graph (16).
   Everything is step-scoped, so stepping away resets a sequence and exits
   ride the shared swap timing. */

/* Step 2's zoom: four captures arriving on their own beats, 6% to 80% (the
   explorer's typing stack, reused for motion). Each frame lands and stays,
   and the last persists through step 3 as the screen its click lands on. */
.dict-zoom-img { opacity: 0; transition: opacity 1s ease 1s; }

[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="2"].is-active) .dict-zoom-img {
  animation: dict-type 0.45s ease 0.3s both;
}

/* Carried through as well, so the four zoom beats stay 1.1s apart. */
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="2"].is-active) .dict-zoom-img[data-zoom="2"] { animation-delay: 1.4s; }
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="2"].is-active) .dict-zoom-img[data-zoom="3"] { animation-delay: 2.5s; }
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="2"].is-active) .dict-zoom-img[data-zoom="4"] { animation-delay: 3.6s; }

[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="3"].is-active) .dict-zoom-img[data-zoom="4"] {
  opacity: 1;
  transition-delay: 0s;
}

/* Each overlay holds through the step after it, as the screen the next
   step's click lands on. Step 11's homecoming is the census again: the same
   file as step 6 in its own data-step-img="11" element, ordered above the
   step-10 fan it has to be seen dissolving over. */
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="4"].is-active) .dict-step-img[data-step-img="3"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="5"].is-active) .dict-step-img[data-step-img="4"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="6"].is-active) .dict-step-img[data-step-img="5"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="7"].is-active) .dict-step-img[data-step-img="6"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="8"].is-active) .dict-step-img[data-step-img="7"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="9"].is-active) .dict-step-img[data-step-img="8"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="10"].is-active) .dict-step-img[data-step-img="9"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="11"].is-active) .dict-step-img[data-step-img="10"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="12"].is-active) .dict-step-img[data-step-img="11"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="13"].is-active) .dict-step-img[data-step-img="12"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="14"].is-active) .dict-step-img[data-step-img="13"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="15"].is-active) .dict-step-img[data-step-img="14"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="16"].is-active) .dict-step-img[data-step-img="15"] {
  opacity: 1;
  transition-delay: 0s;
}

/* The responses arrive after their click has read (the animation's fill
   holds them at 0 through the delay, overriding the generic step rule).
   Doubles land at 2s, singles at 1.5-1.6s. */
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="5"].is-active) .dict-step-img[data-step-img="5"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="12"].is-active) .dict-step-img[data-step-img="12"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="14"].is-active) .dict-step-img[data-step-img="14"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="15"].is-active) .dict-step-img[data-step-img="15"] {
  animation: dict-type 0.5s ease 1.5s both;
}

[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="3"].is-active) .dict-step-img[data-step-img="3"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="6"].is-active) .dict-step-img[data-step-img="6"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="7"].is-active) .dict-step-img[data-step-img="7"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="8"].is-active) .dict-step-img[data-step-img="8"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="10"].is-active) .dict-step-img[data-step-img="10"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="13"].is-active) .dict-step-img[data-step-img="13"] {
  animation: dict-type 0.5s ease 1.6s both;
}

[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="4"].is-active) .dict-step-img[data-step-img="4"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="9"].is-active) .dict-step-img[data-step-img="9"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="11"].is-active) .dict-step-img[data-step-img="11"],
[data-demo-panel="dependencies"] .dict-stage:has(.jc-step[data-step="16"].is-active) .dict-step-img[data-step-img="16"] {
  animation: dict-type 0.5s ease 2s both;
}

/* Frames, badges and scrims on the click steps hold back until the response
   has landed, so nothing rings or shades a screen that is not there yet.
   Enter only. */
[data-demo-panel="dependencies"] .jc-step[data-step="3"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="3"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="3"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="3"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="5"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="5"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="5"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="5"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="6"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="6"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="6"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="6"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="7"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="7"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="7"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="7"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="8"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="8"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="8"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="8"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="10"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="10"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="10"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="10"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="12"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="12"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="12"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="12"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="13"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="13"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="13"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="13"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="14"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="14"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="14"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="14"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="15"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="15"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="15"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="15"].is-active .dict-spot { transition-delay: 2.1s; }

[data-demo-panel="dependencies"] .jc-step[data-step="4"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="4"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="4"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="4"].is-active .dict-spot { transition-delay: 2.5s; }

[data-demo-panel="dependencies"] .jc-step[data-step="9"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="9"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="9"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="9"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="11"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="11"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="11"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="11"].is-active .dict-spot,
[data-demo-panel="dependencies"] .jc-step[data-step="16"].is-active .dash-frame,
[data-demo-panel="dependencies"] .jc-step[data-step="16"].is-active .dash-badge,
[data-demo-panel="dependencies"] .jc-step[data-step="16"].is-active .dash-badge-num,
[data-demo-panel="dependencies"] .jc-step[data-step="16"].is-active .dict-spot { transition-delay: 2.4s; }

/* Every card waits for the response its step describes, so the words, the
   ring and the shading all land together: painting with the step put them on
   screen while the cursor was still travelling. Each delay matches its own
   step's frame and scrim in the block above, so these three groups track those
   three. Steps 1 and 2 have no click to wait for and paint with the step.
   Enter only: the exit rides the tip's base transition. */
[data-demo-panel="dependencies"] .jc-tip[data-step="3"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="5"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="6"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="7"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="8"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="10"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="12"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="13"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="14"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="15"].is-active { transition-delay: 2.1s; }

[data-demo-panel="dependencies"] .jc-tip[data-step="9"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="11"].is-active,
[data-demo-panel="dependencies"] .jc-tip[data-step="16"].is-active { transition-delay: 2.4s; }

[data-demo-panel="dependencies"] .jc-tip[data-step="4"].is-active { transition-delay: 2.5s; }

/* The ERD's step 3 is a click too, and the glyph rules below are keyed on the
   step rather than the panel, so it inherits their timing for free. What it
   needs of its own is the response and the annotations behind it: the filled
   inspector at 1.6s, then scrim, panel frame and card at 2.1s. */
/* Delayed transitions, NOT animations, and the difference is the whole bug that
   was here: an animation holding an image at 1 is cancelled the moment its step
   loses .is-active, and the value drops to the declared 0 with no transition to
   run, because the declared value never changed. Step 3's capture was vanishing
   in a single frame at the hand-off, uncovering the 63% base for the full
   second step 4's menu took to fade in: two diagrams at two zooms, one of them
   the green unselected Account node. A delayed transition holds the old value
   through the delay just as the animation did, and still fades out properly
   when the step changes. */
[data-demo-panel="erd"] .dict-stage:has(.jc-step[data-step="3"].is-active) .dict-step-img[data-step-img="3"] {
  transition: opacity 0.5s ease 1.6s;
}

/* And step 3's capture holds through step 4, which is the same screen plus a
   menu. Nothing underneath is ever uncovered, so the swap is only the menu
   arriving. Same contract as the dependency tour's held overlays. */
[data-demo-panel="erd"] .dict-stage:has(.jc-step[data-step="4"].is-active) .dict-step-img[data-step-img="3"] {
  opacity: 1;
  transition-delay: 0s;
}

/* Step 4's own capture waits for the right-click to have read. */
[data-demo-panel="erd"] .dict-stage:has(.jc-step[data-step="4"].is-active) .dict-step-img[data-step-img="4"] {
  transition: opacity 0.5s ease 1.6s;
}

/* The right-click itself. The unscoped step-4 rules further down belong to the
   dependency tour, where 4 is a double-click; this is one press, so both the
   glyph and its single pulse are re-declared panel-scoped. */
[data-demo-panel="erd"] .jc-step[data-step="4"].is-active .dict-click {
  animation: dict-click-show 2.4s ease 0.3s both;
}

[data-demo-panel="erd"] .jc-step[data-step="4"].is-active .dict-click-ripple {
  animation: dict-ripple 0.9s ease-out 0.8s;
}

[data-demo-panel="erd"] .jc-step[data-step="3"].is-active .dash-frame,
[data-demo-panel="erd"] .jc-step[data-step="3"].is-active .dash-badge,
[data-demo-panel="erd"] .jc-step[data-step="3"].is-active .dash-badge-num,
[data-demo-panel="erd"] .jc-step[data-step="3"].is-active .dict-spot,
[data-demo-panel="erd"] .jc-tip[data-step="3"].is-active { transition-delay: 2.1s; }

/* Step 4 now plays its right-click rather than presenting the result: cursor at
   0.3s, one pulse at 0.8s, the menu at 1.6s, and only then the frame, the scrim
   and the card. Its annotations used to land at 0 against a screen still
   dissolving, which is why the card read as popping in over a moving picture. */
[data-demo-panel="erd"] .jc-step[data-step="4"].is-active .dash-frame,
[data-demo-panel="erd"] .jc-step[data-step="4"].is-active .dash-badge,
[data-demo-panel="erd"] .jc-step[data-step="4"].is-active .dash-badge-num,
[data-demo-panel="erd"] .jc-step[data-step="4"].is-active .dict-spot,
[data-demo-panel="erd"] .jc-tip[data-step="4"].is-active { transition-delay: 2.1s; }

/* The glyphs. Scoped through the owning step, so the clicks coexist. Click
   semantics per Jason: double-click enters a node's perspective (4, 9, 11)
   or jumps out of the overview's list (16); the node menus (5, 12) are
   right-clicks, which the words carry since the glyph has no separate form
   for it; opening a group, the menu items and the tree rows are single
   clicks. */
.dict-click { opacity: 0; pointer-events: none; }

.jc-step[data-step="5"].is-active .dict-click,
.jc-step[data-step="12"].is-active .dict-click,
.jc-step[data-step="14"].is-active .dict-click,
.jc-step[data-step="15"].is-active .dict-click { animation: dict-click-show 2.2s ease 0.3s both; }
.jc-step[data-step="3"].is-active .dict-click,
.jc-step[data-step="6"].is-active .dict-click,
.jc-step[data-step="7"].is-active .dict-click,
.jc-step[data-step="8"].is-active .dict-click,
.jc-step[data-step="10"].is-active .dict-click,
.jc-step[data-step="13"].is-active .dict-click { animation: dict-click-show 2.4s ease 0.3s both; }
.jc-step[data-step="4"].is-active .dict-click,
.jc-step[data-step="9"].is-active .dict-click,
.jc-step[data-step="11"].is-active .dict-click,
.jc-step[data-step="16"].is-active .dict-click { animation: dict-click-show 2.6s ease 0.3s both; }

/* Literals, not tokens: the cursor sits on the captures' light UI in both
   site themes, so the heading token (near-white in dark mode) would vanish. */
.dict-click-cursor { fill: #1c1e24; stroke: #ffffff; stroke-width: 1px; }

.dict-click-ripple {
  fill: none;
  stroke: var(--demo-accent-line);
  stroke-width: 2.5px;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

/* One measured pulse for a click, two fast ones for a double-click. Entering
   a node's perspective is a double-click (4, 9, 11, 16); opening a group,
   the menus, their items and the tree rows are singles. */
.jc-step[data-step="3"].is-active .dict-click-ripple { animation: dict-ripple 0.9s ease-out 0.8s; }
.jc-step[data-step="4"].is-active .dict-click-ripple,
.jc-step[data-step="9"].is-active .dict-click-ripple,
.jc-step[data-step="11"].is-active .dict-click-ripple,
.jc-step[data-step="16"].is-active .dict-click-ripple { animation: dict-ripple 0.42s ease-out 0.8s 2; }
.jc-step[data-step="5"].is-active .dict-click-ripple,
.jc-step[data-step="6"].is-active .dict-click-ripple,
.jc-step[data-step="7"].is-active .dict-click-ripple,
.jc-step[data-step="8"].is-active .dict-click-ripple,
.jc-step[data-step="10"].is-active .dict-click-ripple,
.jc-step[data-step="12"].is-active .dict-click-ripple,
.jc-step[data-step="13"].is-active .dict-click-ripple,
.jc-step[data-step="14"].is-active .dict-click-ripple,
.jc-step[data-step="15"].is-active .dict-click-ripple { animation: dict-ripple 0.9s ease-out 0.7s; }

/* The explorer's one click, on its own clock. Every rule above opens a step with
   the click that caused it; this one closes a step with the click the NEXT step
   answers, so it has to wait out the card instead of leading it. Step 3's beat is
   8745ms (159 characters at CHAR_MS, inside the stage's dwell floor and ceiling,
   js/cycle.js): the cursor arrives at 5.9s, the ripple pulses at 6.5s, and both
   are gone about 250ms before the swap to the Account-only table starts. Panel
   scoped because the dependency tour owns a step 3 of its own, and re-timed by
   hand if step 3's card is ever re-worded, since the card is what sets the beat. */
[data-demo-panel="explorer"] .jc-step[data-step="3"].is-active .dict-click {
  animation: dict-click-show 2.6s ease 5.9s both;
}

[data-demo-panel="explorer"] .jc-step[data-step="3"].is-active .dict-click-ripple {
  animation: dict-ripple 0.9s ease-out 6.5s;
}

@keyframes dict-click-show {
  0% { opacity: 0; }
  14% { opacity: 1; }
  82% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes dict-ripple {
  0% { transform: scale(0.3); opacity: 0.9; }
  100% { transform: scale(1.5); opacity: 0; }
}

/* Still mode plays the responses but not the cursors: at a phone's scale the
   glyph is a smudge, and the screens changing carry the point on their own. */
@media (max-width: 700px) {
  .dict-click { animation: none !important; opacity: 0; }
}

/* ----------------------------------------------------------- numbered badges
   The circled numeral exists to tie a step to its region when the words are
   somewhere else. Below 700px they are: demo.css drops the cards and the
   legend under the canvas carries the text, so the numeral is the only thing
   joining a legend line to a part of the screen, and it stays.

   Above 700px nothing needs it. Every card opens with its own number, the
   dependency tour's cards sit against the region they describe, and the other
   three now carry a spotlight on every step: the lit region reads as the
   region without a second numeral hanging off its edge. Dropped everywhere,
   and the frame plus the cutout ring it instead. */
@media (min-width: 701px) {
  .dict-stage .dash-badge-g { display: none; }
}

/* ---------------------------------------------------- later-tour curtains
   Same bargain as the Fulltrace panels in demo.css: the first tour's intro
   teaches the controls, every later tour drops the hint line and the arrow.
   Keyed by panel name because that is how demo.css does it; if the parked
   setup tour returns as the opener, move "explorer" into this list. */

[data-demo-panel="erd"] .demo-curtain-hint,
[data-demo-panel="erd"] .demo-curtain-point,
[data-demo-panel="dependencies"] .demo-curtain-hint,
[data-demo-panel="dependencies"] .demo-curtain-point,
[data-demo-panel="documentation"] .demo-curtain-hint,
[data-demo-panel="documentation"] .demo-curtain-point { display: none; }

/* --------------------------------------------------------- curtain purpose
   The line under the tour's name (js/demo.js slots it in from the stage's
   data-cycle-sub), where the hint sentence sits, how the intro arrow is sized
   and every beat's timing all moved to css/demo.css on 24/08/2026, when the
   studio's sidebar tour took the same card. They were written here first
   and are unchanged by the move; this page's four tours read them from the
   shared file now, so the two pages cannot drift apart. Their numbers are
   still tuned against this 2.21 canvas and its 45px transport bar, which is
   the frame the studio page now shares. */

/* -------------------------------------------------------- held/paused cue
   Mirrors the dash-stage rules in demo.css, which are scoped to Fulltrace's
   stages: the held frame stops above the 45px transport bar, and the play
   glyph invites the click while a hover holds the walk. */

.dict-stage::after {
  content: "";
  position: absolute;
  inset: 0 0 45px;
  border: 1px solid color-mix(in srgb, var(--demo-accent-line) 50%, transparent);
  opacity: 0;
  transition: opacity 0.16s ease;
  pointer-events: none;
  z-index: 3;
}

.dict-stage.is-holding::after { opacity: 1; }

.dict-stage.is-holding .demo-hold { opacity: 1; transform: translate(-50%, 0); }

.window--demo .dict-stage.is-holding .cyc-pause { display: none; }
.window--demo .dict-stage.is-holding .cyc-play { display: block; }

/* -------------------------------------------------------------- still mode
   Below 700px the walk plays as swapping stills with the legend carrying the
   words (demo.css hides the tips and relights only the active step's badge,
   counter-scaled): the scrims and held frame go with them. The transport is
   NOT one of them, and has not been since the canvas stopped answering a tap
   on 22/08/2026; it is what drives a tour at this width. */
@media (max-width: 700px) {
  .dict-stage::after { display: none; }
  .dict-stage .jc-step.is-active .dict-spot,
  .dict-stage .jc-step.is-active .dict-stage-shadow { opacity: 0; }
}
