/* 72 Club — Miami Dolphins skin.
 *
 * Loaded ONLY by this tour: deploy-72club injects the <link> into its copy of index.html.
 * Nothing here touches the engine, which is shared by every tour and is currently pinned at
 * /engine/1.0.0/ — a reskin must never become an engine change.
 *
 * Colour and type follow "Miami Dolphins Graphic Standards 2019" (72club-plan/):
 *   AQUA    #008E97   rgb 0/142/151    primary
 *   WHITE   #FFFFFF                    pure white, pairs with aqua
 *   ORANGE  #FC4C02   rgb 252/76/2     primary ACCENT
 *   BLUE    #005776                    deep-ocean accent, sampled from the primary mark
 * The guide's hierarchy is followed literally: aqua carries the interface, orange marks the
 * one thing that is currently active. Reversing that would read as a warning colour.
 *
 * Type: the brand faces are Heroic Condensed Bold (headlines) and Avenir LT Std (body), both
 * commercially licensed and not redistributable. Substituted with the nearest open equivalents:
 *   Heroic Condensed -> Barlow Condensed SemiBold   (condensed grotesque, same voice)
 *   Avenir LT Std    -> Nunito Sans                 (geometric humanist, variable 200-1000)
 * If the studio buys the real faces, drop the woff2 files in and change the two @font-face
 * src lines — nothing else here refers to them by name.
 */

@font-face {
  font-family: 'BrandCondensed';
  src: url('assets/fonts/barlow-cond.07b301a6.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'BrandSans';
  src: url('assets/fonts/nunito-sans.5b2225b6.woff2') format('woff2');
  font-weight: 200 1000; font-style: normal; font-display: swap;
}

:root {
  --aqua: #008E97;
  --aqua-lift: #12A7B0;          /* one step up, for hairlines on near-black */
  --orange: #FC4C02;
  --ocean: #005776;
  --poche: #7FC6CE;         /* aqua lifted for fills under white line-work */

  /* The venue mark's box. #tour-mark and #kicker both derive from these, so the title keeps the
     mark's centreline whatever size the mark is drawn at. Overridden once, for mobile. */
  --mark-x: 22px; --mark-y: 18px; --mark-w: 93px; --mark-h: 74px; --mark-gap: 11px;

  /* PANEL CONVENTIONS. These RECORD what the engine already does rather than propose anything
     new: .plan sits at right/bottom 24 with a 13px radius, and its tab is 11px on the outer edge.
     Named here so a new panel can inherit the convention instead of re-typing the numbers and
     drifting. If the engine's values ever change, these are the two lines to follow it with.
     See docs/ui-conventions.md for the full measured system. */
  --card-inset: 24px;      /* every panel's distance from the viewport edge */
  --card-radius: 13px;     /* .plan */
  --tab-radius: 11px;      /* .plan-tab, on the edge away from the screen border */

  /* Ground retinted from the engine's green-neutral to deep ocean. Kept dark: these are
     photographic panoramas and a light chrome would compete with them. */
  --color-paper: #05171C;
  --color-paper-2: #0A2029;
  --color-paper-3: #123039;
  --color-ink: #F2FAFB;
  --color-ink-2: #8FB3B9;

  --acc: var(--aqua);
  --acc-ink: #04222A;             /* text ON aqua — the guide pairs aqua with pure white */
  --acc-soft: rgb(0 142 151 / .18);
  --acc-wash: rgb(0 142 151 / .09);
  --acc-line: rgb(0 142 151 / .38);
  --hi-hover: rgb(18 167 176 / .16);
  --hi-faint: rgb(255 255 255 / .05);
  --card: rgb(5 23 28 / .74);
  --hairline: rgb(143 179 185 / .18);

  --font: 'BrandSans', 'Inter', system-ui, sans-serif;
  /* The engine uses --font-mono for its small uppercase tracked labels — kickers, plan
     titles, area names. That is exactly where the brand's condensed headline voice belongs,
     so the condensed face is mapped here rather than to body copy. */
  --font-mono: 'BrandCondensed', 'Barlow Condensed', ui-monospace, 'Cascadia Code', monospace;
}

/* NO font-size compensation here. A condensed face does run narrower than the mono it
   replaces, but `font-size` in `em` resolves against the PARENT's size, never the element's
   own — so `1.06em` on labels the engine had set to 8px and 9px resolved against an inherited
   16px and blew them up 2.1x, clipping the plan's area labels off the panel edge.
   `letter-spacing` in em IS safe: for every property except font-size, em resolves against the
   element's own size. Tracking is eased slightly — a condensed face needs less than a mono. */
.plan-head b, .kicker, #kicker2, .plan-lv b, .area-btn, .dock b { letter-spacing: .12em; }
.plan-body .croplbl { letter-spacing: .06em; }

/* The room name is the one true headline in the UI. */
#room-name { font-family: 'BrandCondensed', var(--font-mono); letter-spacing: .02em; }

/* ORANGE = "you are here", or "this is the thing to press" — and nothing else. The guide makes
   orange the primary accent; using it only for the single active or actionable element keeps it
   rare enough to still mean something. The intro's stadium hotspot is the other place it earns. */
.plan-body .dot.on { fill: var(--orange); stroke-width: 1.7; }
.plan-body .halo { stroke: var(--orange); }
.plan-body .fov { fill: var(--orange); opacity: .22; }
.plan-body .crop:hover .cropln,
.plan-body .crop:has(.rm.on) .cropln { stroke: var(--orange); }
.plan-body .crop:hover .croplbl,
.plan-body .crop:has(.rm.on) .croplbl { fill: var(--orange); }

/* Wall poche picks up the ocean blue rather than neutral ink, so the plan reads as part of
   the same palette instead of a grey drawing dropped onto a teal page. */
.plan-body .po { fill: var(--poche); fill-opacity: .22; }

/* Hide the engine's own X-SCAPE mark declaratively, rather than waiting for JS to take it away.
   app.js removes #brand-logo whenever tour.json sets `brand` — but only after it has fetched and
   parsed tour.json, and the element is in the served HTML from the first paint. That gap used to
   be invisible because .topbar sat at z-index 20, underneath the opaque intro film at 200; raising
   the bar to 210 so the venue title could travel with the mark also lifted the X-SCAPE wordmark
   into view, and it flashed top-left for ~50 ms on every load.
   The link goes with it: with the image hidden the anchor is an empty, still-focusable tab stop,
   and on a client deliverable the credit at the bottom is where X-SCAPE is named. */
.brand-link, #brand-logo { display: none; }

/* The venue mark. Injected by deploy-72club as <div id="tour-mark">, NOT by the engine —
   app.js removes #brand-logo whenever tour.json sets `brand`, and this must survive that. */
/* The mark is also the way home, and home is the AERIAL plate -- from the tour, the dive or the
   cutaway alike; on the aerial it is already there. z-index sits ABOVE the intro overlay so it
   never disappears behind the film — it is the one element that is always present. */
#tour-mark {
  position: fixed; left: var(--mark-x); top: var(--mark-y); z-index: 210;
  width: var(--mark-w); height: var(--mark-h); padding: 0; border: 0; background: transparent; cursor: pointer;
  background-image: url('assets/dolphins.d841c0e8.png'); background-size: contain;
  background-position: center; background-repeat: no-repeat;
  filter: drop-shadow(0 2px 10px rgb(0 0 0 / .55));
  opacity: 0;
  transition: transform .22s var(--ease), filter .22s var(--ease);
}
/* The mark is the ONE thing stacked above #intro (210 against 200), because it doubles as the way
   back to the venue map and has to stay reachable over the plate. That also means it is the one
   thing that can appear over the boot animation — and at a .35s delay it did, landing in the
   corner while the logo it duplicates was still assembling in the middle of the screen.
   intro.js stamps `booted` on <html> when the engine's loader is finished; the mark waits for it. */
html.booted #tour-mark { animation: markIn 1s var(--ease) .25s forwards; }
#tour-mark:hover { transform: scale(1.06); filter: drop-shadow(0 3px 14px rgb(0 142 151 / .75)); }
#tour-mark:active { transform: scale(.98); }
#tour-mark:focus-visible { outline: 2px solid var(--acc); outline-offset: 4px; border-radius: 6px; }
@keyframes markIn { from { opacity: 0; transform: translateY(-6px) scale(.94); }
                    to   { opacity: 1; transform: none; } }

/* THE LOCKUP IS THE MARK **AND** THE TITLE, in every view.
   The mark sits at 210 because it doubles as the way back to the venue map, so it clears the film
   at 200. The title lived in .topbar at 20 and therefore did not: on the opening film and on the
   venue map — two of the three views — the client saw a logo with no words next to it, and only
   the tour itself carried the full lockup.

   Raising the bar that carries the title is enough, because nothing else in this tour sits between
   50 and 200; the only things above are the film and the mark.

   The control cluster used to be held back entirely for as long as the film was open. It is not
   any more: a lone mute button floating over the aerial read as an orphan. The bar now shows over
   the film too, but carries ONLY the controls that can actually do something there —

     mute · share · fullscreen

   — because zoom, play, gyro, dollhouse and location all act on a pano that is not on screen yet,
   and a control that does nothing is worse than one that is absent.

   Screenshot is excluded for a sharper reason: it does not do nothing, it does the WRONG thing.
   The engine's handler captures D.domElement.toDataURL() -- the WebGL canvas -- and behind the
   film that canvas is holding the PANO. Pressed over the aerial it silently saves a picture of a
   room the visitor has not reached. A control that quietly returns the wrong answer is worse than
   one that is missing, so it is missing until the film is.

   `#intro:not(.gone) ~ .topbar` works without :has() because the deploy injects #intro directly
   after <body>, ahead of the header — a sibling, and an earlier one. If that injection order ever
   changes, this selector stops matching and every control appears over the film.

   .topbar alone LOSES. The engine declares `.topbar{...z-index:20}` at the same specificity and
   later in the cascade, so this rule computed to 20 and was quietly doing nothing -- the bar sat
   under the film at 200 the whole time. `#intro ~ .topbar` carries an id and cannot lose.
   .topbar is position:fixed, which makes it a stacking context: a child can never climb out of
   it, so raising #ctrls on its own could not have worked either. */
.topbar { z-index: 210; }
#intro:not(.gone) ~ .topbar { z-index: 215; }

/* WHICH controls survive over the film. The buttons live inside .seg wrappers, so hiding "every
   child except these four" hid the WRAPPERS and took the whole bar with it. Named individually:

     seg-view     zoomout · zoomin · dollhouse   -> the whole segment goes
     seg-modes    play · audio · gyro            -> audio stays
     seg-capture  share · loc · shot             -> share stays; shot captures the wrong canvas
     seg-system   fs · more                      -> fullscreen stays                          */
#intro:not(.gone) ~ .topbar #seg-view,
#intro:not(.gone) ~ .topbar #c-play,
#intro:not(.gone) ~ .topbar #c-gyro,
#intro:not(.gone) ~ .topbar #c-loc,
#intro:not(.gone) ~ .topbar #c-shot,
#intro:not(.gone) ~ .topbar #c-more { display: none; }

/* Over the film the title changes clothes. --color-ink-2 (#8FB3B9) is a muted grey-teal chosen to
   sit quietly on the tour's near-black chrome, and it does that well — but the same value over a
   sunlit aerial, or over blue stands, is a low-contrast smudge. The mark never had this problem
   because it is saturated orange and white artwork.
   So: the tour keeps the quiet ink it was signed off with, and only the two views that put the
   title over photography lift it to the bright end of the same ramp, with a heavier shadow to
   carry the thin letterforms. */
#intro:not(.gone) ~ .topbar #kicker {
  color: var(--color-ink);
  text-shadow: 0 1px 3px rgb(0 0 0 / .95), 0 2px 16px rgb(0 0 0 / .8);
}

/* ...and the title now enters WITH the mark rather than being revealed by the film lifting off it.
   Same keyframes, same delay, so the two read as one piece of artwork assembling once. Without
   this the title would be painted over the boot loader, which the mark is gated against for the
   very same reason — it would land in the corner while the logo is still assembling mid-screen. */
#kicker { opacity: 0;
  /* Now that it is shown over the film as well as over the tour, it needs the same protection the
     mark has always had. The mark carries drop-shadow(0 2px 10px / .55) and reads on anything;
     the title had nothing, because until now it only ever sat on the tour's own dark chrome. Over
     a sunlit aerial and over the stands it was close to invisible.
     Two shadows, not one: a tight opaque pass to give thin 10px letterforms an edge against light
     pixels, and the mark's own soft pass so the pair sit in the same light. */
  text-shadow: 0 1px 2px rgb(0 0 0 / .85), 0 2px 12px rgb(0 0 0 / .6);
}
html.booted #kicker { animation: markIn 1s var(--ease) .25s forwards; }
/* The tour title sits at the top-left, and with `brand` set the engine has already removed its
   own logo from that corner — so the mark lands straight on top of #kicker.
   Two lines: the venue on top, the club underneath — the engine writes one string into #kicker,
   so the break travels in tour.json as a newline and `pre-line` is what renders it. line-height
   1.35 rather than the engine's 1: at 10px with .22em tracking two stacked lines at 1.0 collide.
   #kicker2 in the bottom bar gets the same string and stays single-line, where the newline
   collapses to a space — which reads correctly there.

   VERTICAL ALIGNMENT. The title is given the mark's OWN BOX — same top, same height — and centres
   inside it, so the two share a centreline by construction rather than by coincidence. The
   previous version left it in the topbar's flow and noted that the centres "already line up
   (43 vs 42.5)". They did, at the time. The mark was later redrawn taller, its centre moved to 55,
   and nothing pointed at the comment: the title simply sat 12 px high, which is what the client saw.
   Anchoring to --mark-* means resizing the mark moves the title with it.

   `position: fixed` here does NOT lift the title over the intro film: .topbar carries z-index 20
   and no transform or opacity, so this stays inside its stacking context (under the film at 200)
   while `top` still resolves against the viewport. The mark is the one element deliberately above
   it at 210, because it doubles as the way back to the venue map. */
#kicker {
  position: fixed;
  left: calc(var(--mark-x) + var(--mark-w) + var(--mark-gap));
  top: var(--mark-y); height: var(--mark-h);
  display: flex; align-items: center;
  white-space: pre-line; line-height: 1.35;
}

@media (max-width: 640px) {
  /* The old 46x44 was 46x36 padded out to clear the 44px tap floor — `contain` draws the mark
     at its own 62:49 ratio regardless, so the extra height was target, not logo. At 1.5x the
     drawn mark is 69x55 and clears the floor on its own, so the box is the mark again. */
  /* Only the geometry changes; #kicker follows it without a rule of its own. */
  :root { --mark-x: 14px; --mark-y: 12px; --mark-w: 69px; --mark-h: 55px; --mark-gap: 8px; }
  /* The ENGINE hides the title here (`.brand .bt { display:none }` at this same breakpoint) and
     that is correct: at 420px the control pill reaches back across the space the title wants.
     Restated because #kicker's `display:flex` above is an ID selector and outranks the engine's
     class rule — without this line, centring the title also silently un-hides it on phones and it
     lands under the controls. The rule this replaced set `margin-left` here and carried a comment
     about the two lines still stacking on mobile; both were dead, because nothing was visible. */
  #kicker { display: none; }
}
@media (prefers-reduced-motion: reduce) { #tour-mark { animation: none; opacity: 1; } }

/* ── Venue intro ─────────────────────────────────────────────────────────────────────────
   Two clips and a still, injected by deploy-72club. Opaque from first paint so the engine's
   own X-SCAPE loader is never visible on this tour, and it does not lift until the tour is
   ready — see intro.js. Layered stack: A under B under the still, crossfaded on opacity only
   (transform/opacity are the only things worth animating). */
/* Starts INVISIBLE and is revealed by intro.js once the engine's loader has finished.
   #intro is z-index 200, opaque black and covers the viewport; the engine's boot animation is
   z-index 40. So for as long as this overlay was up from the first frame, it sat on top of the
   loader and the entry animation was never seen once — not hidden, not broken, just covered. */
#intro {
  position: fixed; inset: 0; z-index: 200; background: #000;
  opacity: 0; visibility: visible; transition: opacity .5s var(--ease);
  /* visibility flips only AFTER the fade, which keeps the dissolve intact while still taking the
     dismissed overlay out of the tab order — it is no longer removed from the DOM, so without
     this the skip button and all nine regions stay keyboard-reachable behind the tour. */
  transition: opacity .9s var(--ease), visibility 0s linear .9s;
}
#intro.lit  { opacity: 1; }
#intro.gone { opacity: 0; visibility: hidden; pointer-events: none; }
#intro video, #intro img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transition: opacity .38s linear;
}
#intro video.on, #intro img.on { opacity: 1; }
/* A vignette keeps the skip control legible over a bright aerial without dimming the film. */
#intro::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 90% at 50% 45%, transparent 55%, rgb(0 0 0 / .45));
}
/* The way in, and it now looks like it. This was a small ghost pill in the bottom-right corner
   labelled "Skip intro" — which named what the visitor would MISS rather than what they would get,
   and put the tour's primary action in the corner reserved for dismissals. Centred and filled, it
   reads as the thing to press; the aerial's stadium hotspot is still the richer way in, and this
   is the plain one beside it.

   The label is "Enter tour" from the first frame now. It already said that on the cutaway — the
   film simply used to open with a different word for the same button. */
#intro-skip {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 26px; right: auto;
  z-index: 6; padding: 12px 26px; border-radius: 999px; cursor: pointer;
  font: 600 11px/1 var(--font-mono); letter-spacing: .18em; text-transform: uppercase;
  /* The fill is the one it always had. Filled with the accent it read as a gradient rather than a
     colour: the plate underneath never stops moving, so a translucent wash over it shifts frame by
     frame. A near-opaque dark pill sits still against anything. */
  color: var(--color-ink); background: rgb(5 23 28 / .8); border: 1px solid rgb(242 250 251 / .28);
  opacity: 0; transition: opacity .4s var(--ease), background .2s var(--ease);
  /* Blur removed with the scene menu's, and for the same reason: this sits over a video that
     never stops, so the backdrop is re-composited and re-blurred on every decoded frame. Older
     than the scene switch, but the same continuous cost, and it made no sense to leave one of
     three when the complaint was a laptop running out of CPU. */
}
#intro-skip.on { opacity: 1; }
#intro-skip:hover { opacity: 1; background: rgb(0 142 151 / .55); }
#intro-skip:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; opacity: 1; }
@media (max-width: 640px) {
  #intro-skip { bottom: 18px; padding: 11px 20px; letter-spacing: .14em; }
}
@media (prefers-reduced-motion: reduce) { #intro { transition: opacity .15s linear; } }

/* Interactive areas on the intro's final frame. regions.svg is traced from the studio mattes by
   scripts/intro_regions.py; it shares the still's 16:9 framing and uses preserveAspectRatio
   "slice", which is the SVG equivalent of object-fit:cover — so the two stay registered at any
   window shape. Hit targets are filled transparent, never fill:none: `none` takes no pointer
   events at all, which is the mistake that made the plan footprints only hoverable on their
   outline. */
#intro-regions { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 5;
  opacity: 0; pointer-events: none; transition: opacity .6s var(--ease); }
#intro-regions.on { pointer-events: auto; }
/* The traced SVG carries only a viewBox, so without this it defaults to width:100% and AUTO
   height — it keeps its own 16:9 ratio, top-aligns, and stops matching the still underneath,
   which uses object-fit:cover. On any window wider than 16:9 it overflowed downward and every
   region sat low. Sized explicitly, with preserveAspectRatio="slice" in the file, it is the
   exact SVG equivalent of cover and the two stay locked at any window shape. */
#intro-regions svg { display: block; width: 100%; height: 100%; }
#intro-regions.on { opacity: 1; }
#intro-regions .ir { fill: transparent; stroke: rgb(242 250 251 / .22); stroke-width: 2;
  cursor: pointer; transition: fill .2s var(--ease), stroke .2s var(--ease); }
/* Weight 600, not 700: the face ships 600 only, and asking for 700 makes the browser synthesise
   bold — which widens the text and thickens the strokes into mush at small sizes. font-size and
   stroke-width are set per label by fitLabel() in intro.js, from the real measured text box. */
#intro-regions .irl { fill: var(--color-ink); font: 600 26px/1 'BrandCondensed', var(--font-mono);
  letter-spacing: .04em; text-anchor: middle; pointer-events: none; opacity: 0;
  paint-order: stroke fill; stroke: rgb(5 23 28 / .92); stroke-linejoin: round;
  transition: opacity .2s var(--ease); }
#intro-regions .irg:hover .ir { fill: rgb(0 142 151 / .34); stroke: var(--orange); stroke-width: 3; }
#intro-regions .irg:hover .irl { opacity: 1; }

/* Touch has no hover. On a device that cannot hover, a label that only appears on hover never
   appears at all — measured: nine outlines, no names, until a finger lands on one. The rule for
   touch is that every target is readable AT REST and answers the tap itself, so on such devices
   the labels are always on, the outlines a step stronger so the rooms read as targets, and the
   highlight desktop gets on :hover arrives on :active instead. Desktop keeps its hover reveal.
   Both queries, because a touch laptop reports hover:hover while a stylus tablet can report
   pointer:fine — matching either catches every device that cannot rely on a hover. */
@media (hover: none), (pointer: coarse) {
  #intro-regions .irl { opacity: 1; }
  #intro-regions .ir { stroke: rgb(242 250 251 / .45); }
  #intro-regions .irg:active .ir { fill: rgb(0 142 151 / .34); stroke: var(--orange); stroke-width: 3; }
}

@media (max-width: 640px) { #intro-regions .irl { font-size: 34px; } }

/* ── The venue card ──────────────────────────────────────────────────────────────────────────
 * Hallmark · component: info card · genre: atmospheric · theme: the tour's own system
 * states: default · hidden (tab) · scrolling  ·  copy links: default · hover · focus-visible
 *
 * Reworked to the 2026-08-28 mockup (client feedback #4): the room, its area, a PICTURE of the
 * space and the copy about it — and nothing else. The action row (Share / Add to Card / Download
 * PDF) is gone: Share lives in the top bar, the rest was retired.
 *
 * WHERE: left edge at --card-inset, and vertically at MID-SCREEN rather than in the bottom
 * corner — the card is taller now (picture + prose) and needs the room; the bottom-left corner
 * belongs to the credit. It is "a little bit bigger" than the plan (320 over 272) because it
 * carries a picture and reading copy where the plan carries a drawing.
 *
 * MORE TRANSPARENT than the other panels — the point of the picture is the room behind it.
 * The engine's room title stays at bottom-left, as it always was: the card and the strip are
 * additions, not a replacement for it, and it is the one thing that names the room when the
 * card is folded away. Mid-screen, the card no longer overlaps it, so nothing lifts or hides. */
#venue {
  left: var(--card-inset); right: auto; bottom: auto;
  top: 50%; transform: translateY(-50%);
  width: min(320px, 46vw);
  max-height: min(78vh, 720px);
  display: flex; flex-direction: column;
  background: rgb(5 23 28 / .52);
  transition: opacity .32s var(--ease), transform .32s var(--ease);
}
#venue.hidden { transform: translate(-12px, -50%); }    /* .plan slides right; this one slides left */
#venue .plan-head { padding: 7px 8px 0 13px; }
#venue-tab { left: 0; right: auto; top: 50%; bottom: auto; border-radius: 0 11px 11px 0; transform: translate(-8px, -50%); }
#venue-tab.show { transform: translate(0, -50%); }

.venue-body { padding: 0 15px 15px; overflow-y: auto; }
#venue-where b {
  display: block; font-family: 'BrandCondensed', var(--font-mono);
  font-size: 24px; line-height: 1.05; letter-spacing: .01em; color: var(--color-ink);
}
#venue-where span {
  display: block; margin-top: 4px;
  font: 500 9px/1.4 var(--font-mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--acc);
}
/* The picture: 16:9, the arrival view of this room, drawn by info.js. Hidden until it has one. */
#venue-pic { display: none; margin: 12px 0 0; border-radius: 9px; overflow: hidden; aspect-ratio: 16 / 9;
  background: rgb(5 23 28 / .35); }
#venue-pic.on { display: block; }
#venue-pic canvas { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The copy: two type roles only — the sans for reading, the tracked condensed for a heading. */
#venue-copy { margin-top: 12px; font-size: 12.5px; line-height: 1.55; color: var(--color-ink); }
#venue-copy p { margin: 0 0 9px; opacity: .88; }
#venue-copy p:last-child, #venue-copy ul:last-child { margin-bottom: 0; }
#venue-copy h4 { margin: 12px 0 5px; font: 600 9px/1.4 var(--font-mono); letter-spacing: .16em; text-transform: uppercase; color: var(--color-ink-2); }
#venue-copy ul { margin: 0 0 9px; padding: 0 0 0 14px; }
#venue-copy li { margin: 0 0 6px; opacity: .88; }
#venue-copy li b { font-weight: 700; color: var(--color-ink); }
/* Links are underlined, not merely tinted: hue alone is a poor way to say "this is a link". */
#venue-copy a { color: var(--acc); text-decoration: underline; text-underline-offset: 2px; }
#venue-copy a:hover { color: var(--color-ink); }
#venue-copy a:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; border-radius: 3px; }

@media (max-width: 640px) {
  #venue { left: 10px; width: min(320px, calc(100vw - 20px)); max-height: 70vh; }
}

/* ── The stadium hotspot on the looping aerial ───────────────────────────────────────────────
   Clip A holds indefinitely, so the visitor has to be told there is something to do. The marker
   is the engine's OWN navigation hotspot (.hs / .hs-ring / .hs-lbl) rather than anything invented
   here: the same ring, pill and sonar pulse the tour puts on every doorway. Someone who has used
   the tour already knows what it means, and someone who has not learns it here first.

   Only two things are added: the polygon that makes the whole building the target, and the
   absolute positioning the engine normally does with its CSS2D renderer. */
#intro-hit {
  position: absolute; inset: 0; width: 100%; height: 100%; z-index: 5;
  opacity: 0; transition: opacity .8s var(--ease);
  /* pointer-events, not just opacity. An element at opacity 0 still swallows clicks, so after the
     dive this overlay sat invisibly on top of the room map and ate every click on it. Fading
     something out is not the same as taking it out of the way. */
  pointer-events: none;
}
#intro-hit.on { opacity: 1; }
#intro-hit.on .sh { pointer-events: auto; }
#intro-hit svg { display: block; width: 100%; height: 100%; }

/* `transparent`, never `none`: fill:none takes no pointer events at all. The building is the hit
   target; the ring merely says where to look. */
#intro-hit .sh {
  fill: transparent; stroke: transparent; stroke-width: 2; cursor: pointer; pointer-events: none;
  transition: fill .3s var(--ease), stroke .3s var(--ease);
}
/* Orange, not aqua. The intro's stadium is the one thing on screen that can be acted on, which is
   the same job the guide gives orange everywhere else — and against a white canopy on a
   blue-green plate, aqua was the one hue with nowhere to stand out from. Kept at the same low
   fill so it tints rather than paints; the line carries the definition. */
#intro-hit .sh:hover { fill: rgb(252 76 2 / .16); stroke: rgb(252 76 2 / .55); }

/* The ring is GONE for anyone using a pointer: the traced outline lighting up in orange already
   says "press me", and a marker on top of it was saying it twice.

   The button itself stays in the DOM, because it is the only way in that is not a mouse. The SVG
   polygon carries the click, but it is aria-hidden and cannot be focused or announced — remove
   this and the intro becomes unreachable by keyboard or screen reader, with no visible sign that
   anything is missing. So it is hidden rather than deleted, and shows itself again the moment it
   takes keyboard focus, so a Tab user can see what they are about to press.

   NOT display:none or visibility:hidden — both remove it from the tab order too, which is the
   thing being preserved. Zero opacity keeps it focusable; pointer-events:none keeps it from
   swallowing clicks meant for the outline underneath. */
/* In the Formula 1 scene the stadium is not a way in — that dressing exists to be shown to a
   client, and no dive is projected from its plate. Hide the hotspot and its Enter pill entirely
   rather than leave them looking pressable: `display: none` also takes the button out of the tab
   order, which `pointer-events: none` would not. intro.js carries the matching guard, so the two
   cannot drift apart into a control that looks dead but still fires. */
html[data-scene="f1"] #intro-hit,
html[data-scene="f1"] #intro-enter { display: none; }
/* THE INTRO'S LAYER STACK, in one place.
   Media 1-4, controls 5-7. Two rules, and breaking either is silent:
   anything interactive must outrank every picture, and every picture must be untouchable — the
   media layers cover the whole viewport at opacity 0 for most of the intro's life, and an element
   at opacity 0 still swallows clicks unless told not to.
   Both were broken at once by giving these four a z-index without raising the controls: the still
   landed above #intro-hit and the stadium stopped responding, and above #intro-regions it buried
   the venue map's own hit areas. Nothing looked wrong; the picture simply stopped answering. */
#intro-a  { z-index: 1; }     /* the scene */
#intro-a2 { z-index: 2; }     /* the alternate scene, dissolving over it */
#intro-b  { z-index: 3; }     /* the dive, covering both */
#intro-still { z-index: 4; }  /* the venue map, covering the dive */
#intro video, #intro img { pointer-events: none; }

/* ── info points ─────────────────────────────────────────────────────────────────────────────
 * Hallmark · component: map callout · genre: atmospheric · theme: the tour's own system
 * states: default · hover · focus-visible · active · open  (disabled/loading/error: n/a, static data)
 * contrast: title + body ink on the .glass fill; kind label ink-2 is chrome, same as every head
 * pre-emit critique: P5 H4 E4 S5 R4 V4
 *
 * The marker is a point, the card is a panel. The card wears the SAME glass as the plan, the venue
 * and the scene card -- fill, hairline, blur, radius, head label -- so a fourth surface does not
 * arrive with a fourth vocabulary. New here is only what a callout needs and a corner panel does
 * not: a tie back to its point, a flip when the point sits low, and a stat row for a lot.
 *
 * Two sets, one per scene, each SCOPED TWICE. The scene is where a marker's subject exists at all,
 * and the aerial stage is the only moment it describes -- the dive and the cutaway are the inside
 * of the club, where a car park is noise. Named positively, so a fourth stage cannot silently
 * reveal them. z-index 6, ONE above the stadium's dive polygon (#intro-hit, 5): at equal z the
 * polygon, later in the DOM, won -- and the Hard Rock Stadium point in its middle could not be
 * pressed at all; every press dived instead. The layer is hit-transparent and each point is a 34px
 * disc, so the polygon loses a coin-sized hole where the point about the building sits, nothing
 * more. Measured with elementFromPoint at every point on both scenes, now a standing check. */
.mkrs { position: absolute; inset: 0; z-index: 6; pointer-events: none; display: none; }
html[data-scene="f1"]       #intro[data-stage="aerial"] #f1-points       { display: block; }
html[data-scene="dolphins"] #intro[data-stage="aerial"] #dolphins-points { display: block; }

/* The wrapper IS the icon box, so what lands on the stored point is the icon. */
.mkr { position: absolute; transform: translate(-50%, -50%); width: 34px; height: 34px; }
/* The open one outranks its neighbours. A sibling drawn later used to sit on top of the card --
   West Campus opened with another point's icon over its second line. */
.mkr.open { z-index: 2; }

.mkr-i {
  position: relative; width: 100%; height: 100%; padding: 0; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer; pointer-events: auto;
  color: var(--color-ink);
  background: rgb(5 23 28 / .62);
  border: 1px solid rgb(242 250 251 / .28);
  box-shadow: 0 2px 10px rgb(5 23 28 / .45), inset 0 1px 0 rgb(255 255 255 / .06);
  transition: background .18s var(--ease), border-color .18s var(--ease),
              transform .18s var(--ease), color .18s var(--ease);
}
/* A slow ring breathes off every point, staggered so the set never pulses in lockstep. It is the
   only thing on the plate that moves on its own, and it is what says "these are pressable".
   Transform and opacity only -- the compositor animates these without a repaint. */
.mkr-i::after {
  content: ''; position: absolute; inset: -1px; border-radius: 50%;
  border: 1px solid var(--aqua-lift); opacity: 0; pointer-events: none;
  animation: mkr-ring 4.2s var(--ease) infinite;
  animation-delay: calc(var(--i, 0) * .37s);
}
@keyframes mkr-ring {
  0%   { transform: scale(1);   opacity: .55; }
  45%  { transform: scale(1.9); opacity: 0; }
  100% { transform: scale(1.9); opacity: 0; }
}
.mkr-i svg { width: 22px; height: 22px; display: block; fill: none;
  stroke: currentColor; stroke-width: 1.5; }
.mkr-i svg .dot { fill: currentColor; stroke: none; }
.mkr-i svg .glyph { stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.mkr-i:hover { background: rgb(0 142 151 / .5); border-color: var(--aqua-lift); transform: scale(1.12); }
.mkr-i:focus-visible { outline: 2px solid var(--acc); outline-offset: 3px; }
.mkr-i:active { transform: scale(1.02); }
.mkr-i[aria-expanded="true"] { background: var(--acc); border-color: var(--aqua-lift);
  color: var(--acc-ink); transform: scale(1.08); }
.mkr-i[aria-expanded="true"]::after { animation: none; opacity: 0; }

/* The card. CLOSED until pressed -- a dozen names permanently over the plate is a legend, and
   this shot is a photograph. visibility rather than display, so it keeps a measurable width for
   the nudge in placePoints() and leaves the tab order while shut. */
.mkr-p {
  position: absolute; top: 100%; left: 50%;
  transform: translate(calc(-50% + var(--nx, 0px)), 4px);
  margin-top: 12px;
  /* px, NOT ch: --font-mono is BrandCondensed and a condensed face has a narrow zero, so a
     ch-based width resolved to 142px and the copy wrapped four deep. */
  width: min(250px, 68vw);
  text-align: left; white-space: normal; pointer-events: none;
  background: var(--card); border: 1px solid var(--hairline);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  box-shadow: inset 0 1px 0 var(--hi-faint), 0 10px 30px rgb(0 0 0 / .4);
  border-radius: var(--card-radius); padding: 12px 14px 13px;
  opacity: 0; visibility: hidden;
  transition: opacity .22s var(--ease), transform .22s var(--ease), visibility 0s linear .22s;
}
.mkr-i[aria-expanded="true"] + .mkr-p {
  opacity: 1; visibility: visible; pointer-events: auto; transition-delay: 0s;
  transform: translate(calc(-50% + var(--nx, 0px)), 0);
}
/* The tie: a hairline from the point into the card, so a nudged card still reads as belonging to
   ITS marker and not to the one it now sits nearer to. Placed at the marker, not the card centre. */
.mkr-tie {
  position: absolute; left: calc(50% - var(--nx, 0px)); top: -13px; width: 1px; height: 12px;
  background: linear-gradient(var(--aqua-lift), var(--hairline));
}
/* Low on the frame, the card opens upward instead of off the bottom edge. */
.mkr.up .mkr-p { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 12px;
  transform: translate(calc(-50% + var(--nx, 0px)), -4px); }
.mkr.up .mkr-i[aria-expanded="true"] + .mkr-p { transform: translate(calc(-50% + var(--nx, 0px)), 0); }
.mkr.up .mkr-tie { top: auto; bottom: -13px;
  background: linear-gradient(var(--hairline), var(--aqua-lift)); }

/* Two type roles only, as everywhere in this tour: tracked-uppercase condensed for the chrome
   (kind, stat unit), the condensed face at reading size for the title, the sans for the copy. */
.mkr-kind { display: block; color: var(--color-ink-2);
  font: 600 9px/1 var(--font-mono); letter-spacing: .18em; text-transform: uppercase; }
.mkr-title { display: block; margin-top: 5px; color: var(--color-ink);
  font: 600 17px/1.15 var(--font-mono); letter-spacing: .02em; }
.mkr-stat { display: flex; align-items: baseline; gap: 6px; margin-top: 8px; color: var(--color-ink); }
.mkr-stat strong { font: 600 26px/1 var(--font-mono); letter-spacing: .01em; }
.mkr-stat span { color: var(--color-ink-2);
  font: 600 9px/1 var(--font-mono); letter-spacing: .18em; text-transform: uppercase; }
.mkr-p em { display: block; margin-top: 7px; font-style: normal;
  font: 400 12.5px/1.45 var(--font); color: var(--color-ink); opacity: .84; }
/* Orange is the one actionable thing on screen, and a reservation is exactly that. Rendered only
   when a real URL exists -- the placeholder rows carry none. */
.mkr-cta { display: inline-block; margin-top: 10px; padding: 7px 11px; border-radius: 999px;
  background: var(--orange); color: #fff; text-decoration: none;
  font: 600 10px/1 var(--font-mono); letter-spacing: .14em; text-transform: uppercase;
  transition: transform .16s var(--ease), filter .16s var(--ease); }
.mkr-cta:hover { filter: brightness(1.08); }
.mkr-cta:active { transform: translateY(1px); }
.mkr-cta:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; }

@media (max-width: 640px) {
  .mkr { width: 39px; height: 39px; }        /* closer to the 44px tap-target guidance */
  .mkr-i svg { width: 25px; height: 25px; }
  .mkr-p { width: min(220px, 72vw); }
}
@media (prefers-reduced-motion: reduce) {
  .mkr-i::after { animation: none; }
  .mkr-i, .mkr-p { transition: opacity .15s linear; }
}

/* ── mute ─────────────────────────────────────────────────────────────────────────────────────
 * There is almost nothing here on purpose. The intro's mute IS one of the engine's controls now --
 * built with class="ctrl" and inserted into #seg-modes -- so it inherits size, hover, focus ring,
 * accent colour and the active underline from the engine, and cannot drift away from the buttons
 * beside it. The version this replaced was a separate element floated over #c-audio: it carried
 * its own fill, its own border and an orange muted state, and it read as a stranger in the bar.
 *
 * Its slot is #c-audio's, so #c-audio stands down for as long as the film is up. */
#intro:not(.gone) ~ .topbar #c-audio { display: none; }
#intro.gone ~ .topbar #intro-mute { display: none; }

/* The divider belongs BETWEEN segments, and .seg + .seg::before does not know that the segment in
   front of it has been hidden -- an adjacent-sibling selector does not care about display. With
   seg-view gone over the film, seg-modes became the first thing in the bar and still drew a rule
   down its left edge, against nothing. */
#intro:not(.gone) ~ .topbar #seg-modes::before { display: none; }

/* The film is not a window. The engine's hotspot pills sit invisibly BEHIND it with their own
   pointer-events (the .labels layer is hit-transparent but each .hs re-enables itself), and at
   coordinates where the overlay does not hit-test, a click on the aerial fell straight through
   and navigated the tour behind the film — measured with elementFromPoint, not theoretical.
   Same guard for the credit link. */
#intro:not(.gone) ~ .labels .hs { pointer-events: none; }
#intro:not(.gone) ~ #credit { pointer-events: none; }

/* ── scene card ──────────────────────────────────────────────────────────────────────────────
 * Hallmark · component: collapsible card · genre: atmospheric · theme: the tour's own system
 * states: default · hover · focus · active · disabled · loading · error · success
 * contrast: pass — ink #F2FAFB on paper #05171C, label ink-2 #8FB3B9 on --card
 * pre-emit critique: P5 H5 E4 S5 R5 V4
 *
 * NO NEW VISUAL VOCABULARY. This reuses .plan .glass .plan-head .plan-tab, exactly as info.js
 * does for the venue panel — same fill, same radius, same head, same collapse, same tab. The only
 * thing declared here is where it sits and how its two rows read.
 *
 * WHERE IT SITS, and why those two numbers and no others:
 *   top   = --mark-y (18px)  its top edge lands on the venue mark's top edge, so the two
 *                            top-row elements share one line across the screen
 *   right = --card-inset     its right edge lands on the plan panel's right edge, so the right
 *                            side of the screen reads as one vertical line
 * Top-right is not a leftover: it is the only free corner, and it is diagonally opposite the plan
 * panel, which balances the composition instead of stacking every panel on one side.
 *
 * WIDTH is a hierarchy rule, not a taste: capped at two thirds of the plan panel's 272px, because
 * a scene preview is subordinate to navigation and must not out-weigh it. */
#scene {
  /* BELOW the engine's control bar, not beside it. The card used to start at --mark-y so its top
     edge lined up with the venue mark's -- but the bar occupies that same row on the right (it
     spans to within 26px of the edge and is 46px tall) and the card sat straight on top of it.
     The bar is the engine's and is pinned; the card is ours, so the card is what moves. */
  top: calc(var(--mark-y) + 58px); right: var(--card-inset); bottom: auto; left: auto;
  width: min(180px, 46vw);
}
#scene.hidden { transform: translateX(12px); }           /* same direction as .plan — right edge */
#scene-tab { right: 0; left: auto; top: calc(var(--mark-y) + 58px); bottom: auto;
             border-radius: var(--tab-radius) 0 0 var(--tab-radius); transform: translateX(8px); }
#scene-tab.show { transform: translateX(0); }

.scene-body { padding: 2px 7px 7px; }
.scene-opt {
  display: flex; align-items: flex-start; gap: 8px; width: 100%;
  padding: 8px 9px; border: 0; border-radius: 7px; background: none; cursor: pointer;
  /* Leading is 1.32, not 1, because the Formula 1 row carries the full official event name and
     wraps to two lines at this width. Set on both rows so the two stay one system; a single-line
     row simply gets a taller box. The break point is not forced — at 133px of room the natural
     break already falls after "F1 CRYPTO.COM" (102px), which is where a reader would put it. */
  font: 500 11px/1.32 var(--font-mono); letter-spacing: .14em; text-transform: uppercase;
  /* --color-ink, not the ink-2 the panel HEADS use. Measured against this card's own worst case —
     its .74 fill over a sunlit cloud, rgb(64 77 81) — ink-2 is 3.88:1 and aqua-lift 2.99:1, both
     under AA for 11px text. ink is 8.27:1. The head label stays ink-2 because that is what every
     panel head in the tour uses and this card must not be the one that differs; that value is a
     system-wide question, noted in docs/ui-conventions.md, not something to patch in one card. */
  color: var(--color-ink); text-align: left;
  transition: background .16s var(--ease), color .16s var(--ease);
}
/* The tick is always in the layout, transparent when unselected — otherwise the label shifts
   sideways as the selection moves, which reads as the list twitching rather than responding. */
.scene-opt::before {
  content: ''; flex: none; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; opacity: 0;
  /* Centred on the FIRST line, not on the row. On the two-line row a row-centred dot would sit in
     the gap between the lines and read as marking neither; on the first line it marks the name. */
  margin-top: calc((1.32em - 5px) / 2);
  transition: opacity .16s var(--ease), transform .16s var(--ease);
}
.scene-opt:hover        { background: var(--hi-faint); color: var(--color-ink); }
.scene-opt:focus-visible{ outline: 2px solid var(--acc); outline-offset: -2px; }
.scene-opt:active       { transform: translateY(1px); }
/* Selection is carried by the DOT and a wash — two channels, neither of them text contrast and
   neither of them hue alone. Tinting the label instead would have meant either failing AA or
   changing its weight, and a weight change on a condensed face reflows the row as it is chosen. */
.scene-opt[aria-pressed="true"]          { background: var(--acc-wash); }
.scene-opt[aria-pressed="true"]::before  { opacity: 1; background: var(--aqua-lift); }
.scene-opt[disabled]    { cursor: default; opacity: .5; }
/* Loading is real here: the alternate plate is fetched only when this card is first opened, so a
   pick made before it has buffered genuinely waits. Saying so beats a control that looks broken. */
.scene-opt[data-state="loading"]::before {
  opacity: 1; border-radius: 1px; animation: sceneWait .9s var(--ease) infinite;
}
@keyframes sceneWait { 0%,100% { transform: scaleY(.4); } 50% { transform: scaleY(1); } }
.scene-opt[data-state="error"]           { color: var(--orange); }
.scene-opt[data-state="error"]::before   { opacity: 1; }
/* WHICH STAGE THIS CARD BELONGS TO — and the reason it now has to say so.
 * The card is a choice of BACKDROP, and that choice only exists while the plate is looping. Once
 * the visitor has gone in, the backdrop is settled and the same card sitting over the axonometric
 * cutaway is offering a choice that no longer applies.
 *
 * It is scoped by naming the stage it belongs to (aerial), not the stages it is absent from, so a
 * fourth stage added later cannot silently re-show it. #intro.js stamps data-stage; see the stage
 * comment there for the sequence. Hidden with visibility, not display, so the collapse transition
 * still runs and neither the card nor its tab stays reachable by keyboard.
 *
 * The precedent this generalises: #intro-hit used to be hand-removed from the DOM at exactly this
 * moment, for exactly this reason, with no rule that would have applied it to anything else. */
#intro:not([data-stage="aerial"]) #scene,
#intro:not([data-stage="aerial"]) #scene-tab {
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .28s var(--ease), visibility 0s linear .28s;
}

/* On a phone the card is 46vw — 147px at 320px wide — which leaves 102px of text room against a
   102px first line. That is a coin toss, and losing it breaks the name into three lines. Buying
   the 8px back out of the padding rather than out of the width keeps the subordination rule
   intact: the card must never grow toward the plan panel's size to fit a label. */
@media (max-width: 640px) {
  .scene-body { padding: 2px 5px 5px; }
  .scene-opt  { padding-left: 7px; padding-right: 7px; }
}
@media (prefers-reduced-motion: reduce) {
  .scene-opt::before { animation: none; }
  #scene, #scene-tab { transition: opacity .15s linear; }
}

#intro-enter {
  position: absolute; z-index: 6; transform: translate(-50%, -6px);
  padding: 0; border: 0; background: none; cursor: pointer;
  opacity: 0; pointer-events: none;
}
#intro-enter:focus-visible {
  opacity: 1; pointer-events: auto;
  outline: 2px solid var(--acc); outline-offset: 6px; border-radius: 12px;
}
#intro-enter:focus-visible .hs-ring { transform: scale(1.15); }

/* ── entry loader: the Dolphins mark, not X-SCAPE ─────────────────────────────────────────────
   The engine's boot animation is dot-grid → scrambled wordmark → line → pixel curtain → logo
   sway, and it is hard-wired to the X-SCAPE logo. This is a client deliverable: tour.json's
   `brand` already strips the X-SCAPE mark from the corner, and the loader was the one place it
   survived. Everything here is a skin override — src/app.js is untouched, because the engine is
   shared by every tour and pinned per tour.

   `content:` on the <img> swaps the artwork while leaving the engine's own opacity animation on
   it intact, so the choreography is exactly the aquamarine one with a different mark in it. */
#load-logo { content: url('assets/dolphins.d841c0e8.png'); width: min(190px, 42vw); height: auto; }

/* The curtain slices its image into a 14x5 sprite, with the cell positions set inline by the
   engine. That maths holds for any picture — but the container's aspect ratio has to match the
   picture, or every tile shows the wrong slice. The Dolphins mark is 377x300 where the X-SCAPE
   wordmark is 1243x211, which is why this cannot simply inherit. */
#load .pixels { aspect-ratio: 377 / 300; width: min(190px, 42vw); }
#load .pixels i { background-image: url('assets/dolphins.d841c0e8.png'); }

/* ...and the stage was sized for a wordmark five times wider than it is tall. */
#load .stage { height: 172px; }

/* The sway glow is X-SCAPE's lime. Aqua is the club's primary. */
#load-logo.sway { animation: swayDolphins 3.4s var(--ease) infinite; }
@keyframes swayDolphins {
  0%, 100% { transform: rotate(-1.4deg); filter: drop-shadow(0 6px 18px rgb(0 142 151 / .22)); }
  50%      { transform: rotate(1.4deg);  filter: drop-shadow(0 6px 32px rgb(0 142 151 / .55)); }
}

/* The engine writes "X-SCAPE" into #load-scr every frame. It is left in place and merely made
   invisible rather than removed: the engine looks it up by id at boot and would throw on a null,
   and that throw would take the whole loader down with it — including the class that hides it. */
#load-scr { visibility: hidden; }

/* "LOADING TOUR…" is centred on its FULL string, ellipsis included — so six pixels of trailing
   punctuation, which the eye does not weigh as a letter, push the words themselves off the mark's
   centreline. Measured on the rendered pixels: the letters span 643..745, centring at 694.0, while
   the mark centres at 699.5. Five and a half pixels, and visible.

   Hang the punctuation instead. `text-indent` on a centred line shifts its content by HALF the
   value (the indent is taken off the available width, then what remains is centred), so 1em at
   11px moves the letters back the 5.5px and leaves the ellipsis overhanging to the right.

   This is deliberately in the skin, not the engine: `.lt` is shared by every tour and only this
   one sets a mark beside it to line up with. */
#load .lt { text-indent: 1em; }
/* Same correction as .lt, and for the same reason: letter-spacing puts a trailing gap after the
   LAST character too, so a box that is centred draws its glyphs half that gap to the left. At
   .44em this is the worst offender in the loader — measured 4px off the mark's centreline with
   animations frozen. text-indent shifts a centred line by half its value, so .44em cancels it. */
#load-word { position: absolute; font: 500 clamp(12px, 2vw, 15px)/1 var(--font-mono);
  letter-spacing: .44em; text-indent: .44em; color: var(--color-ink); opacity: 0; white-space: nowrap;
  filter: drop-shadow(0 2px 14px rgb(0 0 0 / .55)); }

/* ...and the credit, on the status line. The wording is tour.json's `brand.text` verbatim, which
   the engine also renders as the in-tour credit link — one phrase for one thing, in both places.
   It is duplicated here rather than read from tour.json because the loader plays before the engine
   has fetched it; if that string ever changes, this is the other place to change.

   NO entrance of its own. It shares "Loading tour…"'s lifetime exactly: both are up the moment the
   loader paints and both leave when #load takes .hide. An earlier version faded it in at 2.4s to
   land after the logo settled, which meant that on a warm cache — where the whole loader is gone
   in under two seconds — the credit had not appeared at all by the time the tour did. */
#load .lt::after {
  content: 'BY X-SCAPE';
  /* text-indent inherits, and this line has no trailing punctuation to hang — it already centres
     on the mark exactly. Cancel it, or correcting the line above silently pushes this one right. */
  text-indent: 0;
  /* block, so it sits on its own line beneath "Loading tour…" rather than trailing it */
  display: block; margin-top: 10px; text-align: center;
  /* The string is already capitals, so `text-transform: none` leaves it alone rather than
     re-casing it; the looser tracking is what separates it from .lt's .24em status voice, now
     that it can no longer be told apart by case. */
  text-transform: none; letter-spacing: .06em;
  font: 500 10px/1 var(--font-mono); color: var(--color-ink-2); opacity: .85;
}
