/* Fabulous — the first arrival, and the only time this product explains itself.
 *
 * COACH-MARKS, AND THE SYSTEM IS FAB'S. One control lit, everything else
 * dimmed, one card beside it: `fab/apps/desktop/ui/static/styles.css`, same
 * box-shadow cut-out and the same card. What is NOT fab's is the palette —
 * this is the Faber `void` variant like the rest of `app.css`, so a token here
 * is a token there and never a third opinion.
 *
 * THE DIM IS A BOX-SHADOW ON A TRANSPARENT BOX, and that is the whole trick:
 * the spread paints everything OUTSIDE the box and the box's own rectangle is
 * left alone. So the lit control is never moved, never cloned and never
 * re-stacked — it renders exactly where and how it always does, and the rest
 * of the page goes dark around it. A version of this that raised the target's
 * z-index would have had to reason about `#stage`, the veil, the compass and
 * the two corners; this one reasons about nothing.
 */

/* Over everything this page has (22 is `#opening`), and under nothing. */
#tour-overlay {
  position: fixed;
  inset: 0;
  z-index: 30;
  /* A transparent click-eater, so the app underneath is inert while it is
     being described. The LAST step turns this off (`.pass`): the whole point
     of that step is a real drag onto the real zone, and a click-to-browse has
     to reach `#drop` rather than a sheet of glass over it. */
  background: transparent;
  opacity: 0;
  transition: opacity var(--dim);
}

#tour-overlay.open { opacity: 1; }
#tour-overlay.pass { pointer-events: none; }

#tour-spotlight {
  position: fixed;
  z-index: 31;
  pointer-events: none;
  border-radius: var(--radius);
  box-shadow:
    /* the dim — everything but the cut-out */
    0 0 0 9999px rgb(var(--ground-rgb) / 0.80),
    /* the ring, in ink, the way every border on this page is */
    0 0 0 1px rgb(var(--ink-rgb) / 0.30),
    /* ...and one breath of acid, which is what a DECISION looks like here */
    0 0 0 5px rgb(var(--accent-rgb) / 0.07);
  transition:
    top var(--enter), left var(--enter),
    width var(--enter), height var(--enter),
    border-radius var(--dim);
}

/* The slow pulse. It is on a pseudo-element so it cannot fight the four
   transitions above, which are what carry the cut-out from one control to the
   next. */
#tour-spotlight::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: inherit;
  animation: tour-ring 2.4s ease-in-out infinite;
}

@keyframes tour-ring {
  0%, 100% { box-shadow: 0 0 0 3px rgb(var(--accent-rgb) / 0); }
  50%      { box-shadow: 0 0 0 6px rgb(var(--accent-rgb) / 0.13); }
}

#tour-card {
  position: fixed;
  z-index: 32;
  pointer-events: auto;
  width: min(330px, calc(100vw - 32px));
  padding: 17px 19px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgb(var(--ground-rgb) / 0.94);
  backdrop-filter: blur(10px);
  box-shadow: 0 18px 48px rgb(0 0 0 / 0.55);
  opacity: 0;
  transform: translateY(6px);
  transition:
    opacity var(--wake), transform var(--wake),
    top var(--enter), left var(--enter);
}

#tour-card.in { opacity: 1; transform: none; }

/* Mono is every label in this system, and a step counter is a label. */
#tour-count {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

/* A CARD TITLE, at the title's own metrics. It was the serif, on the rule
   that the serif is the product's voice; on the site's system Georgia is the
   emphasis inside an h1 and nowhere else, so a whole heading set in it reads
   as a quotation. */
#tour-title {
  margin: 6px 0 5px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.14;
  letter-spacing: -0.04em;
  color: var(--ink);
}

#tour-body {
  margin: 0;
  font-size: 13px;
  line-height: 1.52;
  color: var(--ink-soft);
}

#tour-nav {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 15px;
}

#tour-nav .spacer { flex: 1; }

#tour-skip {
  padding: 6px 2px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12px;
  color: rgb(var(--ink-rgb) / 0.34);
  cursor: pointer;
  transition: color var(--wake);
}

#tour-skip:hover { color: var(--ink-soft); }

#tour-back {
  padding: 7px 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: transparent;
  font: inherit;
  font-size: 12.5px;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background var(--wake), border-color var(--wake), color var(--wake);
}

#tour-back:hover {
  background: rgb(var(--ink-rgb) / 0.06);
  border-color: rgb(var(--ink-rgb) / 0.22);
  color: var(--ink);
}

/* Acid, like `#go` and Download STEP, because it is the thing you press. */
#tour-next {
  padding: 7px 15px;
  border: 0;
  border-radius: var(--radius);
  background: var(--accent-deep);
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--on-accent);
  cursor: pointer;
  transition: background var(--wake), transform 80ms ease;
}

#tour-next:hover { background: rgb(var(--accent-rgb) / 1); }
#tour-next:active { transform: translateY(1px); }
#tour-next:focus-visible,
#tour-back:focus-visible,
#tour-skip:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgb(var(--accent-rgb) / 0.28);
}

/* THE LAST STEP HAS NO BUTTON, and the card says what it is waiting for
   instead: a real file, onto the real window. The dot is the only thing on
   this page that moves while nothing is happening. */
#tour-wait {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-soft);
}

#tour-wait::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: var(--radius);
  background: var(--accent-deep);
  animation: tour-wait 1.7s ease-in-out infinite;
}

@keyframes tour-wait {
  0%, 100% { opacity: 0.25; }
  50%      { opacity: 1; }
}

/* ---------------------------------------------------------- the preview state
 *
 * THE BUTTONS THIS PRODUCT IS MADE OF ARE NOT ON THE FIRST SCREEN, and that is
 * the whole difficulty of describing it. `#marks`, `#step`, `#place` and the
 * layer switches appear when there is geometry to use them on — which is after
 * the first turn, i.e. after the moment this walkthrough exists to get somebody
 * to. So a step that presents one of them REVEALS the real control, in place,
 * for the length of that step, and puts it back.
 *
 * REAL NODES AND NOT MOCK-UPS, for fab's reason (`injectTourDemo`): a picture
 * of a button drifts from the button. And REVEALED IN CSS rather than by
 * clearing `hidden`, so that `paintMarks`, `paintPlace` and `stepMark` — which
 * run on a poll and would each put their control straight back — cannot fight
 * it. The attribute stays exactly as the app left it; only the painting
 * changes, and it changes for one step.
 *
 * `!important` and this specificity because the rules being overridden are
 * themselves `#x[hidden] { display: none !important }`.
 */
body.tour-show-marks  #marks[hidden]  { display: flex !important; }
body.tour-show-marks  #marks button[hidden] { display: revert !important; }

/* **`#mark-said` IS NOT REVEALED, AND THAT IS THE POINT OF THIS LINE NOT
   BEING HERE.** The paragraph field came off the page on 2026-08-29 (Jules:
   *"la prompt bar disparait pour l'instant"*) and is `hidden` rather than
   deleted, so the store, the route and `loop._user_content` survive behind it.
   The reveal rule above used to name it too — written when the field was on
   the page — so the walkthrough went on forcing it visible and the FIRST
   SCREEN A STRANGER SEES advertised a control the product does not have.
   Found by watching the film (2026-08-31), which is the only way it could
   have been: every check here asks whether a step lights its node, and this
   step lit one node too many. */
body.tour-show-step   #step[hidden]   { display: block !important; }
body.tour-show-place  #place[hidden]  { display: flex !important; }
body.tour-show-layers #layers[hidden] { display: flex !important; }
body.tour-show-go     #go[hidden]     { display: block !important; }
/* `#mode` is the one that is already in flow and merely invisible — it
   reserves its row on the idle screen so the composition does not jump when a
   file is staged (`app.css`). Revealing it is therefore the same one word the
   staged state uses, and nothing about the layout moves. */
body.tour-show-mode   #mode           { visibility: visible; }

/* A REVEALED CONTROL IS NOT A LIVE ONE. The overlay already eats every pointer
   event for the steps that reveal anything, and this is the second half: a
   control that is being described must not be reachable by the keyboard
   either, or Tab walks into a Send button on a part that does not exist. */
body.tour-showing #marks button,
body.tour-showing #step,
body.tour-showing #place,
body.tour-showing #layers button,
body.tour-showing #go,
body.tour-showing #mode button { pointer-events: none; }

/* THE ONE SENTENCE THIS PAGE EVER SAYS HAS TO OUTRANK THE DIM.
   `#refused` sits at z-index 20 and the overlay at 30, so a file refused
   during the last step — which is exactly the step that asks for a file —
   would have been painted, correct, and under a sheet of black. That is not a
   hypothetical: this repo has already paid for it once, at z-index 15 under a
   cover at 20, and the check that missed it was reading `textContent` and was
   perfectly happy. Measured in pixels by `dev/try/try_tour.py`. */
body.tour-live #refused { z-index: 33; }

/* ------------------------------------------------------------------ the reel
 *
 * THE FIRST STEP SHOWS THE PRODUCT INSTEAD OF DESCRIBING IT, and it is the one
 * card here that lights nothing. Every other step's dim is a box-shadow spread
 * around a cut-out, so with no cut-out there is no dim — the overlay carries
 * it for this step instead, which is why the class is on `body` rather than a
 * modifier on the card: the thing that changes is the whole screen.
 *
 * NO BORDER IS DRAWN ROUND THE CLIPS, and one is visible anyway. They are
 * renders on this product's own near-black ground, so a rule, a radius or a
 * lighter panel behind them would be a line the picture does not have — but
 * the card is `ground / 0.94` over a dim and the clip's own ground is opaque,
 * which sets each pane one shade below the card it sits in. That edge is the
 * two alphas meeting, not a border, and it is the only one wanted here.
 */
body.tour-reel #tour-overlay {
  background: rgb(var(--ground-rgb) / 0.86);
  backdrop-filter: blur(3px);
}

/* Hidden rather than merely un-positioned: a spotlight left on screen with no
   target is a 9999px black spread over a card that is trying to be watched. */
body.tour-reel #tour-spotlight { display: none; }

/* WIDE, because two clips at 200 px each are two thumbnails and prove nothing.
   At this width a pane is ~530 px, which is where the drawing stops being a
   grey texture and starts reading as a drawing. */
#tour-card.reel {
  width: min(1140px, calc(100vw - 48px));
  padding: 20px 22px 16px;
}

/* THE CARD IS WIDE; THE PROSE IS NOT. A 1140 px card sets the two sentences
   above the clips at about 150 characters a line, which is roughly twice the
   measure anybody reads comfortably and looks like text that ran out of room
   to stop. The clips get the width; the words keep a column. */
#tour-card.reel #tour-title,
#tour-card.reel #tour-body { max-width: 66ch; }

#tour-reel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  margin-top: 16px;
}

.tour-pane { margin: 0; }

/* THE ASPECT RATIO IS DECLARED AND NOT MEASURED. `place()` centres this card
   off `offsetHeight` on the same frame it is built, and a `<video>` with no
   metadata yet is zero pixels tall — so the card would be centred for a
   height it is about to stop having, and jump. 8:5 is the clips' own
   1280x800. */
.tour-clip {
  display: block;
  width: 100%;
  aspect-ratio: 8 / 5;
  background: rgb(var(--ground-rgb) / 1);
  /* CONTAIN AND NOT COVER. At this width the pane is the clip's own 8:5 and
     the two are identical — but the narrow layout below reshapes the box, and
     `cover` answers that by cropping the part out of frame. The letterboxing
     `contain` leaves instead is the clip's own black on the pane's own black,
     which is to say invisible. */
  object-fit: contain;
}

/* THE SUFFIXES, under the clip they belong to, and one rank quieter than the
   label above them: the label is what the thing IS and this is bookkeeping
   about it. Written by `tour.js` out of `#hint`, never typed. */
.tour-pane-kinds {
  margin-top: 3px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: rgb(var(--ink-rgb) / 0.34);
}

/* ---------------------------------------------------------------- the gate
 *
 * THE DEMO IS WATCHED BEFORE IT IS LEFT (Jules, 2026-08-30), so while the
 * clips are running their first pass this card has NO nav at all — and
 * `display: none` rather than a `disabled` button, because a greyed-out
 * control is a thing to keep pressing. What stands in its place is the one
 * shape this page already uses for "something is happening and you may not
 * hurry it": a mono line and a bar that fills.
 *
 * It is the same row height either way, so nothing moves when the gate opens
 * — the bar is replaced by buttons in place, and a card that jumped 30 px at
 * the exact moment somebody went to press Next would lose the press.
 */
#tour-card.gated #tour-nav { display: none; }
#tour-card:not(.gated) #tour-gate { display: none; }

#tour-gate {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-top: 15px;
  min-height: 30px;
}

/* The hairline this page draws everywhere, with the acid running along it. */
#tour-gate-bar {
  flex: 1;
  height: 1px;
  background: rgb(var(--ink-rgb) / 0.18);
  overflow: hidden;
}

#tour-gate-bar i {
  display: block;
  width: 0%;
  height: 100%;
  background: var(--accent-deep);
  /* Longer than `--wake`: `timeupdate` lands about four times a second, so a
     fast transition finishes and stops between ticks and the bar advances in
     visible steps. This one is still travelling when the next tick arrives. */
  transition: width 320ms linear;
}

#tour-gate-say {
  margin: 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-soft);
}

/* Mono, uppercase, mute — a label, like every other label in this system. */
.tour-pane-label {
  margin-top: 9px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

/* NARROW WINDOWS, and the breakpoint is late on purpose. Stacking is what
   makes this card tall, and tall is the failure mode here — measured at
   780x900, where one column put the clips at 470 px each, the card at 868 px,
   and BOTH BUTTONS below the fold on a card whose entire content is "press
   Next". Two columns at 600 px are 275 px panes: small, and still the shape,
   the ghost and the labels. So the stack starts only where two columns would
   be genuinely useless. */
@media (max-width: 600px) {
  #tour-reel { grid-template-columns: 1fr; gap: 14px; }
}

/* ...and the belt for whatever window nobody thought of: the clips are the
   only part allowed to be scrolled past, so Skip and Next are on screen at
   every size. A card you cannot leave is worse than a card you cannot read. */
@media (max-height: 720px), (max-width: 600px) {
  #tour-card.reel {
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 32px);
  }
  #tour-card.reel #tour-reel { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
  #tour-card.reel #tour-nav { flex: 0 0 auto; }
}

@media (prefers-reduced-motion: reduce) {
  #tour-spotlight { transition: none; }
  #tour-spotlight::after { animation: none; }
  #tour-card { transition: opacity var(--wake); }
  #tour-wait::before { animation: none; opacity: 1; }
}
