/* ── Devon Properties — MetaTours skin ───────────────────────────────────────

   This is the Devon Properties wrapper (projects/seaview-two/
   seaview-two-wrapper.css in the MetaTours tree) carried into Tour Studio as a
   per-tour skin: same markup, same rules, same numbers. It is NOT a
   re-interpretation of that design — an earlier attempt here hand-rebuilt the
   look inside the shared MetaTours wrapper and drifted on 98 of the 133
   selectors the two files share, which is exactly what Danny saw as "it feels
   rebuilt".

   Two deliberate differences from the 3DVista file, both about being a skin
   rather than a one-off:

   1. No @font-face. The per-tour theme.css is generated by src/tourTheme.ts
      and linked AFTER this file; it carries the font faces and the
      --tw-serif/--tw-sans stacks for whichever faces the tour picked.
   2. The Devon palette tokens (--sv-*) are aliases of the shared theme tokens
      instead of hardcoded hex, so the same skin serves every Devon property
      and still tracks a tour's own palette. The values below the aliases are
      what Seaview Two's theme resolves to.

   Everything after the --sv-* aliases is the Devon file, rule for rule.
   ------------------------------------------------------------------------- */

:root {
  --sv-base:      var(--tw-navy);         /* Seaview Two: #00263A */
  --sv-base-rgb:  var(--tw-navy-rgb);     /* 0, 38, 58 */
  --sv-accent:    var(--tw-gold);         /* Seaview Two: #84DADE */
  --sv-accent-rgb: var(--tw-gold-rgb);    /* 132, 218, 222 */
  /* The Devon palette's desaturated second accent (#66A8AB at Seaview) sits
     between the accent and the ground — derived rather than hardcoded so it
     stays right on a palette that isn't Seaview's. */
  --sv-muted-accent: color-mix(in srgb, var(--tw-gold) 72%, var(--tw-navy));
  --sv-white:     var(--tw-cream);        /* Seaview Two: #F6FBFB */
  --sv-white-rgb: var(--tw-cream-rgb);    /* 246, 251, 251 */
  --tw-ease:  cubic-bezier(0.25, 0.46, 0.45, 0.94);
  /* Symmetric ease-in-out for the splash exit — see enterTour() in the JS,
     which now drives this fade directly via requestAnimationFrame rather
     than relying on this CSS transition (confirmed elsewhere in the product
     line that a CSS opacity transition here can simply never start
     interpolating under real-world conditions — see spencer-block.md round
     3/4). This value is kept only as a declared fallback. */
  --tw-ease-inout: cubic-bezier(0.65, 0, 0.35, 1);

  /* Onboarding hint — deliberately independent of every --sv-* token above.
     This teaches tour MECHANICS ("drag to look around"), not brand, so it's
     designed to look identical on any project regardless of that project's
     palette or fonts — same reasoning as the native 3DVista hotspot icons
     staying white/neutral across every client (see the metatours-3dvista
     skill). Portable as-is: copy this whole block into another project's
     CSS and it needs zero adjustment. System font stack on purpose, not
     --tw-sans — this component shouldn't depend on whatever custom webfont
     a given project happens to load. */
  --hint-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --hint-bg: rgba(255, 255, 255, 0.58);
  --hint-border: rgba(255, 255, 255, 0.5);
  --hint-ink: rgba(22, 22, 26, 0.9);
  --hint-ink-soft: rgba(22, 22, 26, 0.62);
  --hint-ink-faint: rgba(22, 22, 26, 0.48);
}

/* Paints the instant the CSS is parsed, before the splash <div> even exists
   in the DOM (only inserted once the 3DVista player is ready) — closes the
   "blank/white flash before anything splash-colored shows up" gap on a slow
   initial load. */
html, body { background: var(--sv-base); }

/* ── SPLASH ──────────────────────────────────────────────────────────────── */

#tw-splash {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Solid brand navy, now purely a fallback behind #tw-splash-video — it
     paints instantly, so it covers the beat before the video's first frame
     decodes and any case where the video fails to load. Deliberately NOT a
     static hero image: the video usually decodes fast enough that an
     image-then-crossfade sequence reads as a glitch rather than a reveal. */
  background: var(--sv-base);
  transition: opacity 1.6s var(--tw-ease-inout);
  overflow: hidden;
}

/* The tour's own pano is the backdrop (no splash video, no splash pano), so the
   shell must paint nothing over it — otherwise the pano behind is hidden by the
   very splash meant to show it, and entering has to swap one image for another.
   The veil below still carries the legibility and the brand treatment. */
#tw-splash.tw-splash-live { background: transparent; }

/* Static hero image support — inert until the JS sets HERO_PATH and this tag
   actually renders. Same warm-cache + fade-in treatment already proven on
   Spencer Block: starts invisible over the solid navy fallback, crossfades
   in once decoded (see the `tw-hero-loaded` toggle in init()) rather than
   popping in the instant the <img> resolves. */
/* Fades in from the solid navy fallback rather than popping in the instant a
   frame decodes (Danny 2026-08-10: "it just suddenly appears"). Originally
   built with no fade at all on the theory that video decodes fast enough for
   a pop to read as instant — wrong in practice, the navy-to-frame cut was
   visible. Same entrance mechanism already proven on #tw-splash-hero below:
   opacity 0 -> 1 via a CSS transition, toggled by a `tw-video-loaded` class
   in init() (see the `loadeddata` listener there). This is NOT the exit
   push-in, which stays rAF-driven — the CSS-transition-freeze bug that rule
   is dodging is specific to properties toggled mid-exit alongside several
   others in the same tick; a single one-shot entrance fade driven by a media
   event, off the critical path of any click handler, doesn't hit it (the
   hero image has used exactly this pattern since Spencer Block with no
   issue). The exit push-in (scale) still applies on top of this via inline
   styles from the rAF loop, untouched by this transition. */
#tw-splash-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  transform: scale(1);
  opacity: 0;
  transition: opacity 1.4s var(--tw-ease-inout);
  user-select: none;
  pointer-events: none;
}

#tw-splash-video.tw-video-loaded { opacity: 1; }

#tw-splash-hero {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  transform: scale(1);
  opacity: 0;
  transition: opacity 1s var(--tw-ease-inout), transform 1.6s var(--tw-ease-inout);
  user-select: none;
  pointer-events: none;
}

#tw-splash-hero.tw-hero-loaded { opacity: 1; }

/* The tour's selected splash pano is rendered over a solid loading colour.
   A custom splash video takes precedence (see buildSplashHtml). */
#tw-splash-pano {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  transform: scale(1);
  opacity: 0;
  /* Keep the chosen loading colour visible until the pano is ready. */
  background: transparent;
  /* Revealed by the `tw-pano-ready` class the JS adds on the splash scene's
     first rendered frame — NOT a timed animation. A fixed 2.2s reveal could
     finish while the pano was still decoding, which on a slow connection put a
     blank layer across the whole splash. See startSplashPano() in wrapper.js. */
  transition: opacity 1.1s var(--tw-ease-inout);
  user-select: none;
  pointer-events: none;
}
#tw-splash.tw-pano-ready #tw-splash-pano { opacity: 1; }
#tw-splash-pano canvas { display: block; }

/* Veil over the splash video. The previous version bottomed out at 0.05 alpha
   at 42% height — precisely where the lockup sits — and carried a *lightening*
   light-blue radial on top of that, so the white wordmark had almost nothing
   to hold against bright drone footage (sunlit water, pale rooftops).
   Now it's darkest exactly behind the lockup and eases out toward the edges,
   so the logo keeps its contrast on every frame of the clip without the whole
   splash reading as a flat grey slab. Keeping the WHITE logo and darkening the
   backdrop, rather than switching to the navy mark — navy on aerial footage
   this busy would disappear into the shadows instead. */
.tw-splash-overlay {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 78% 62% at 50% 42%, rgba(var(--sv-base-rgb), 0.62) 0%, rgba(var(--sv-base-rgb), 0.42) 55%, rgba(var(--sv-base-rgb), 0.30) 100%),
    linear-gradient(180deg, rgba(var(--sv-base-rgb), 0.52) 0%, rgba(var(--sv-base-rgb), 0.22) 45%, rgba(var(--sv-base-rgb), 0.60) 100%);
  z-index: 1;
}

#tw-splash.tw-hiding {
  pointer-events: none;
}

#tw-splash.tw-gone {
  display: none;
}

.tw-splash-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 48px 36px;
  width: 100%;
  max-width: 720px;
  position: relative;
  z-index: 2;
}

.tw-splash-icon-img {
  width: clamp(76px, 7.2vw, 116px);
  height: auto;
  margin-bottom: clamp(14px, 1.4vw, 20px);
  opacity: 0;
  transform: translateY(20px);
  animation: tw-rise 1.3s 0.3s var(--tw-ease) forwards;
  user-select: none;
  pointer-events: none;
}

.tw-splash-wordmark-img {
  opacity: 0;
  transform: translateY(24px);
  animation: tw-rise 1.5s 0.45s var(--tw-ease) forwards;
  user-select: none;
}
/* The splash mark gets its own slot, bigger than the floating one, for the same
   reason: the box is fixed, the asset fits inside it. */
.tw-splash-logo-box {
  width: var(--tw-splash-logo-w);
  height: var(--tw-splash-logo-h);
  margin: 0 auto;
}
/* The property wordmark is a dark logo: it is drawn to sit on the splash
   photograph. With no hero set the splash falls back to brand navy, where a
   dark mark is invisible — so invert it to white instead of shipping a splash
   with no logo on it. (This is not hypothetical: the theme's splashHeroSrc went
   missing and the splash read as "the seaview logo is not visible".) */
#tw-splash.tw-splash-no-hero .tw-splash-wordmark-img {
  filter: brightness(0) invert(1);
}
/* And the text around it is already light-on-dark, so the overlay needs to stop
   doing the job the photograph was doing. */
#tw-splash.tw-splash-no-hero .tw-splash-overlay {
  background: none;
  pointer-events: none;
}

/* Light solid backdrop — the author's theme.splashBackground is a light colour
   (default white), so the mark must stay DARK and the location/lockup text go
   dark too, or a white logo would vanish into a white load screen. */
#tw-splash.tw-splash-no-hero.tw-splash-light .tw-splash-wordmark-img {
  filter: none;
}
#tw-splash.tw-splash-light .tw-splash-location { color: rgba(var(--sv-base-rgb), 0.72); }
#tw-splash.tw-splash-light .tw-splash-devon-label { color: rgba(var(--sv-base-rgb), 0.45); }
/* The Devon lockup is a light-on-footage mark; on white it needs darkening, the
   same reason the property wordmark does. */
#tw-splash.tw-splash-light .tw-splash-devon-img { filter: brightness(0) opacity(0.82); }

/* The pano has drawn its first frame: restore the photographic treatment the
   splash locks were designed on (dark veil + white mark + light text). The
   class is what the JS adds once the splash viewer reports a real frame. */
#tw-splash.tw-pano-ready .tw-splash-overlay {
  background:
    radial-gradient(ellipse 78% 62% at 50% 42%, rgba(var(--sv-base-rgb), 0.62) 0%, rgba(var(--sv-base-rgb), 0.42) 55%, rgba(var(--sv-base-rgb), 0.30) 100%),
    linear-gradient(180deg, rgba(var(--sv-base-rgb), 0.52) 0%, rgba(var(--sv-base-rgb), 0.22) 45%, rgba(var(--sv-base-rgb), 0.60) 100%);
}
#tw-splash.tw-pano-ready .tw-splash-wordmark-img { filter: brightness(0) invert(1); }
#tw-splash.tw-pano-ready .tw-splash-location { color: rgba(var(--sv-white-rgb), 0.78); }
#tw-splash.tw-pano-ready .tw-splash-devon-label { color: rgba(var(--sv-white-rgb), 0.42); }
#tw-splash.tw-pano-ready .tw-splash-devon-img { filter: drop-shadow(0 1px 10px rgba(0,0,0,0.45)); }

.tw-splash-rule {
  /* Widened modestly for the Seaview wordmark (Danny, 2026-09-25): the accent
     line read as a stub beside the logo instead of an underline. */
  width: clamp(96px, 9vw, 150px);
  height: 1.5px;
  background: linear-gradient(90deg, transparent, var(--sv-accent), transparent);
  margin: clamp(18px, 1.8vw, 28px) auto;
  opacity: 0;
  animation: tw-fade 1.1s 0.9s var(--tw-ease) forwards;
}

.tw-splash-location {
  font-family: var(--tw-sans);
  font-size: clamp(15px, 1.4vw, 21px);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.78);
  margin-top: 14px;
  opacity: 0;
  animation: tw-fade 1.0s 1.15s var(--tw-ease) forwards;
}

.tw-splash-enter {
  margin-top: clamp(24px, 2.4vw, 42px);
  font-family: var(--tw-sans);
  font-size: clamp(15px, 1.2vw, 18px);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  /* Base-colour text on the primary accent follows RentSeaview's own
     dark-on-light treatment. */
  color: var(--sv-base);
  background: var(--sv-accent);
  border: none;
  border-radius: 150px;
  padding: clamp(18px, 1.8vw, 24px) clamp(42px, 4vw, 66px);
  min-height: 72px;
  min-width: 66px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 18px;
  touch-action: manipulation;
  box-shadow: 0 4px 24px rgba(var(--sv-accent-rgb), 0.30);
  transition: background 0.22s, box-shadow 0.22s, transform 0.18s var(--tw-ease);
  opacity: 0;
  animation: tw-rise 1.1s 1.4s var(--tw-ease) forwards;
}

.tw-splash-enter:hover {
  background: var(--sv-muted-accent);
  box-shadow: 0 6px 32px rgba(var(--sv-accent-rgb), 0.45);
  transform: translateY(-1.5px);
}

.tw-splash-enter:focus-visible {
  outline: 2px solid var(--sv-accent);
  outline-offset: 4px;
}

.tw-splash-arrow {
  width: 21px;
  height: 21px;
  transition: transform 0.18s var(--tw-ease);
}

.tw-splash-enter:hover .tw-splash-arrow {
  transform: translateX(4.5px);
}

/* Devon Properties — pinned to bottom of splash, with label */
.tw-splash-devon-wrap {
  position: absolute;
  bottom: 40px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 11px;
  opacity: 0;
  animation: tw-fade 1.0s 1.65s var(--tw-ease) forwards;
  white-space: nowrap;
}

.tw-splash-devon-label {
  font-family: var(--tw-sans);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.42);
}

.tw-splash-devon-img {
  width: clamp(100px, 8vw, 138px);
  height: auto;
  filter: drop-shadow(0 1px 10px rgba(0,0,0,0.45));
  user-select: none;
  pointer-events: none;
}

/* ── ANIMATIONS ──────────────────────────────────────────────────────────── */

@keyframes tw-rise {
  from { opacity: 0; transform: translateY(21px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes tw-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ── VIGNETTES ───────────────────────────────────────────────────────────── */
/* Raised-cosine ease (alpha(t) = peak * 0.5 * (1 + cos(pi*t))), not hand-
   picked linear steps — a linear ramp ending in a flat 0 still has a slope
   discontinuity right at that last stop, visible as a Mach band over a
   large flat bright surface (a ceiling). Cosine has zero slope at both
   ends. Cornered radials use `farthest-side`, not the `farthest-corner`
   default — farthest-corner over-sizes the ellipse ~41% along the box's
   straight edges, leaving a visible rectangular edge at the box boundary
   before alpha reaches true 0. Both fixes applied from day one here (see
   CLAUDE.md's Ellis writeup for the two bugs this avoids). */
#tw-vignette-top {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 195px;
  background: linear-gradient(to bottom,
    rgba(var(--sv-base-rgb), 0.45) 0%, rgba(var(--sv-base-rgb), 0.44) 15%, rgba(var(--sv-base-rgb), 0.36) 30%,
    rgba(var(--sv-base-rgb), 0.26) 45%, rgba(var(--sv-base-rgb), 0.16) 60%, rgba(var(--sv-base-rgb), 0.07) 75%,
    rgba(var(--sv-base-rgb), 0.01) 90%, rgba(var(--sv-base-rgb), 0) 100%
  );
  z-index: 98;
  pointer-events: none;
}

#tw-vignette-bottom {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  height: 270px;
  background: linear-gradient(to top,
    rgba(var(--sv-base-rgb), 0.40) 0%, rgba(var(--sv-base-rgb), 0.38) 15%, rgba(var(--sv-base-rgb), 0.32) 30%,
    rgba(var(--sv-base-rgb), 0.23) 45%, rgba(var(--sv-base-rgb), 0.14) 60%, rgba(var(--sv-base-rgb), 0.06) 75%,
    rgba(var(--sv-base-rgb), 0.01) 90%, rgba(var(--sv-base-rgb), 0) 100%
  );
  z-index: 98;
  pointer-events: none;
}

#tw-vignette-tl {
  position: fixed;
  top: 0; left: 0;
  width: 480px;
  height: 280px;
  background: radial-gradient(ellipse farthest-side at 0% 0%,
    rgba(var(--sv-base-rgb), 0.18) 0%, rgba(var(--sv-base-rgb), 0.17) 15%, rgba(var(--sv-base-rgb), 0.14) 30%,
    rgba(var(--sv-base-rgb), 0.10) 45%, rgba(var(--sv-base-rgb), 0.06) 60%, rgba(var(--sv-base-rgb), 0.03) 75%,
    rgba(var(--sv-base-rgb), 0.005) 90%, rgba(var(--sv-base-rgb), 0) 100%
  );
  z-index: 98;
  pointer-events: none;
}

#tw-vignette-toast {
  position: fixed;
  bottom: 0; left: 0;
  width: 850px;
  height: 680px;
  background: radial-gradient(ellipse farthest-side at 0% 100%,
    rgba(var(--sv-base-rgb), 0.95) 0%, rgba(var(--sv-base-rgb), 0.90) 15%, rgba(var(--sv-base-rgb), 0.75) 30%,
    rgba(var(--sv-base-rgb), 0.55) 45%, rgba(var(--sv-base-rgb), 0.33) 60%, rgba(var(--sv-base-rgb), 0.14) 75%,
    rgba(var(--sv-base-rgb), 0.02) 90%, rgba(var(--sv-base-rgb), 0) 100%
  );
  z-index: 98;
  pointer-events: none;
}

/* ── UI FLOAT VISIBILITY ─────────────────────────────────────────────────── */

.tw-ui-el {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.55s var(--tw-ease);
}

.tw-ui-el.tw-show {
  opacity: 1;
  pointer-events: all;
}

#tw-devon-logo-float { transition-delay: 0.04s; }
#tw-logo-float        { transition-delay: 0.08s; }
#tw-cta-btn           { transition-delay: 0.12s; }
#tw-menu-bar          { transition-delay: 0.22s; }
/* Mobile-only "Leasing by Devon Properties" credit (2026-09-28 v2) — see the
   ≤768px block for its full styling. Hidden by default here so desktop/
   tablet never see it; the mobile block flips it to display:block. */
#tw-mobile-credit     { display: none; transition-delay: 0.26s; }

/* ── FLOATING LOGOS ──────────────────────────────────────────────────────── */

/* ── The property mark's slot ──────────────────────────────────────────────
   One box, any logo. Danny, 2026-09-19: *"We need to set up a predefined
   container size for this property logo section which should auto scale the
   logo, because this is a common issue we run into... Some logos will have
   different sizes when we download, so it would be good if we deterministically
   scale into the right viewing size and make it easy for you on future
   projects."*

   Every wordmark we are handed is a different pixel size and a different shape:
   Seaview's own is 2400x544 (4.4:1), an "Icon+name" lockup is nearer 3:1, a
   stacked mark is taller than it is wide. Sizing the IMAGE (a height here, a
   width there) means each new logo has to be re-tuned by hand, and a logo that
   disagrees gets cropped or blown up. So the image is never sized: the BOX is,
   once, per breakpoint, and the image is capped to fit inside it with its own
   proportions intact. A 2400px asset and a 400px asset with the same shape land
   in exactly the same place at exactly the same size.

   The height is what sets how big the mark reads; the width is only a bound, so
   an unusually wide lockup gets a little shorter instead of running into the
   CTA. To change the mark's size for a whole tour, change these two tokens. */
:root {
  --tw-logo-w: 340px;   /* the slot at desktop */
  --tw-logo-h: 64px;
  --tw-splash-logo-w: clamp(300px, 38vw, 560px);
  --tw-splash-logo-h: clamp(84px, 12vh, 136px);
}

.tw-logo-box {
  display: flex;
  align-items: center;
  justify-content: center;
}
.tw-logo-box > img {
  /* Fill the slot with the mark's own proportions kept. Both, not one: a logo
     handed to us small still lands at the intended size, and one handed to us
     2400px wide does not. This is the "deterministic" half. */
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* ── How the mark is coloured ──────────────────────────────────────────────
   A wordmark is supplied in the client's own colour, and these tours are shown
   over whatever the camera happens to be facing, so the same asset is legible
   in one room and gone in the next. Rather than shipping two files per property
   (and having to keep them in step), the tour names a treatment:

     auto   the mark exactly as supplied, with a soft shadow to lift it off a
            mid-tone wall (the default, and what every tour had before this)
     light  any mark rendered as a flat white silhouette with a dark shadow, for
            a mark that has to survive dark imagery
     dark   the mark left in its own colour with a pale halo, for a dark logo on
            a dark room

   Danny asked for this by name: *"The seaview logo needs a white version or
   something at the top."* */
.tw-logo-auto > img  { filter: drop-shadow(0 1px 10px rgba(0,0,0,0.38)); }
.tw-logo-light > img { filter: brightness(0) invert(1) drop-shadow(0 1px 8px rgba(8,14,22,0.55)); }
.tw-logo-dark > img  { filter: drop-shadow(0 0 9px rgba(255,255,255,0.72)) drop-shadow(0 1px 2px rgba(255,255,255,0.5)); }

/* Devon Properties — top left (property manager mark) */
#tw-devon-logo-float {
  position: fixed;
  top: 30px;
  left: 36px;
  z-index: 100;
}

.tw-devon-logo-img {
  display: block;
  height: 52px;
  width: auto;
  filter: drop-shadow(0 2px 14px rgba(0,0,0,0.55));
}

/* Seaview Two — horizontally centred at top. Icon + wordmark side by side,
   matching the feature sheet's own real lockup composition (icon left of
   wordmark) — these are two separately-extracted assets (no single
   pre-composed horizontal file exists), composed here via flex. */
#tw-logo-float {
  position: fixed;
  top: 30px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  /* The slot, not the image, is the fixed thing — see .tw-logo-box. */
  width: var(--tw-logo-w);
  height: var(--tw-logo-h);
}

.tw-logo-icon-img {
  display: block;
  height: 46px;
  width: auto;
  filter: drop-shadow(0 2px 14px rgba(0,0,0,0.55));
}

.tw-logo-wordmark-img {
  display: block;
}

/* ── FLOATING CTA ─────────────────────────────────────────────────────────── */

#tw-cta-btn {
  position: fixed;
  top: 27px;
  right: 30px;
  z-index: 100;
  font-family: var(--tw-sans);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  text-decoration: none;
  /* Navy text on light-blue fill — same rationale as .tw-splash-enter. */
  color: var(--sv-base);
  background: var(--sv-accent);
  border-radius: 150px;
  padding: 0 33px;
  height: 66px;
  min-width: 66px;
  display: flex;
  align-items: center;
  white-space: nowrap;
  transition: opacity 0.55s var(--tw-ease), filter 0.2s;
  transition-delay: 0.12s, 0s;
}

#tw-cta-btn:hover  { filter: brightness(1.06); }
#tw-cta-btn:focus-visible { outline: 2px solid var(--sv-white); outline-offset: 3px; }

/* ── BOTTOM MENU BAR ─────────────────────────────────────────────────────── */

#tw-menu-bar {
  position: fixed;
  bottom: 42px;
  left: 50%;
  transform: translateX(-50%);
  /* Higher than #tw-map-card's z-index:100 — NOT a redundant belt-and-
     suspenders alongside .tw-dropdown's own z-index:200. A fixed-position
     descendant's z-index only competes within its NEAREST ancestor stacking
     context: #tw-menu-bar (position:fixed + z-index:100) already establishes
     one, so .tw-dropdown's z-index:200 only wins against OTHER children of
     #tw-menu-bar — it never escapes to compete against #tw-map-card (a
     separate top-level stacking context, also z-index:100, later in DOM
     order so it wins the tie and paints over the entire #tw-menu-bar
     subtree, dropdown included). Confirmed live on Ellis's deployed tour,
     2026-08-06: bumping the child alone did nothing; bumping this container
     past #tw-map-card's 100 fixed it immediately. */
  z-index: 150;
}

.tw-menu-pill {
  display: flex;
  align-items: center;
  background: rgba(var(--sv-base-rgb), 0.84);
  backdrop-filter: blur(24px) saturate(170%);
  -webkit-backdrop-filter: blur(24px) saturate(170%);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.18);
  border-radius: 150px;
  padding: 0 9px;
  height: 78px;
  gap: 0;
  box-shadow: 0 12px 54px rgba(0, 0, 0, 0.4), 0 1.5px 0 rgba(var(--sv-accent-rgb), 0.06) inset;
}

/* ── MENU TRIGGER BUTTONS ─────────────────────────────────────────────────── */

.tw-menu-group { position: relative; }

.tw-menu-btn {
  display: flex;
  align-items: center;
  gap: 10.5px;
  font-family: var(--tw-sans);
  font-size: 16px;
  /* Bold, on Danny's ask (2026-09-25): these two labels are the only text in
     the pill and they read as the tour's own controls, not as body copy —
     "can we make the amenities text and the floor plan text (like C1) in this
     pill selector at the bottom bold". The suites label carries the current
     plan code ("C1") once a suite is active, so it has to hold the same weight
     as "Amenities" beside it. */
  font-weight: 700;
  letter-spacing: 0.06em;
  color: rgba(var(--sv-white-rgb), 0.52);
  background: transparent;
  border: none;
  padding: 0 25.5px;
  height: 60px;
  border-radius: 150px;
  cursor: pointer;
  white-space: nowrap;
  touch-action: manipulation;
  transition: color 0.18s, background 0.18s;
}

.tw-menu-btn:hover {
  color: var(--sv-muted-accent);
  background: rgba(255, 255, 255, 0.05);
}

.tw-menu-btn[aria-expanded="true"] {
  color: var(--sv-white);
  background: rgba(255, 255, 255, 0.07);
}

.tw-menu-btn.tw-active { color: var(--sv-accent); }

.tw-menu-btn:focus-visible {
  outline: 2px solid var(--sv-accent);
  outline-offset: -2px;
  border-radius: 150px;
}

.tw-menu-chevron {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  opacity: 0.4;
  transition: transform 0.22s var(--tw-ease), opacity 0.18s;
}

.tw-menu-btn:hover .tw-menu-chevron { opacity: 0.65; }
.tw-menu-btn.tw-active .tw-menu-chevron { opacity: 0.65; }
.tw-menu-btn[aria-expanded="true"] .tw-menu-chevron {
  transform: rotate(180deg);
  opacity: 0.9;
}

.tw-menu-sep {
  width: 1.5px;
  height: 27px;
  background: rgba(var(--sv-white-rgb), 0.1);
  flex-shrink: 0;
  margin: 0 3px;
}

/* ── DRONE / AERIAL BUTTON ───────────────────────────────────────────────── */

#tw-aerial {
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  width: 66px;
  height: 60px;
  border-radius: 150px;
  cursor: pointer;
  color: rgba(var(--sv-white-rgb), 0.52);
  touch-action: manipulation;
  transition: color 0.18s, background 0.18s;
  margin: 0 3px;
}

#tw-aerial:hover {
  color: rgba(var(--sv-white-rgb), 0.88);
  background: rgba(255, 255, 255, 0.05);
}

#tw-aerial.tw-active { color: var(--sv-accent); }
#tw-aerial svg { width: 24px; height: 24px; }
#tw-aerial:focus-visible {
  outline: 2px solid var(--sv-accent);
  outline-offset: -2px;
  border-radius: 150px;
}

/* ── DROPDOWN CARDS ──────────────────────────────────────────────────────── */

.tw-dropdown {
  position: absolute;
  bottom: calc(100% + 24px);
  left: 50%;
  transform: translateX(-50%) translateY(12px);
  min-width: 277.5px;
  background: rgba(var(--sv-base-rgb), 0.96);
  backdrop-filter: blur(28px) saturate(160%);
  -webkit-backdrop-filter: blur(28px) saturate(160%);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.2);
  border-radius: 24px;
  padding: 9px;
  box-shadow: 0 30px 84px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(var(--sv-accent-rgb), 0.06);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s var(--tw-ease), transform 0.2s var(--tw-ease);
  max-height: 62vh;
  overflow-y: auto;
  scroll-behavior: smooth;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--sv-accent-rgb), 0.32) transparent;
}

#tw-suites-dd { min-width: 290px; }

/* Wider than the base 277.5px — rows pair a label with a "Coming Soon" tag,
   which clips at the narrower width. */
#tw-amenities-dd { min-width: 320px; }

.tw-dropdown.tw-open {
  opacity: 1;
  pointer-events: all;
  transform: translateX(-50%) translateY(0);
}

.tw-dropdown::-webkit-scrollbar { width: 10px; }
.tw-dropdown::-webkit-scrollbar-track { background: transparent; }
.tw-dropdown::-webkit-scrollbar-thumb {
  background: rgba(var(--sv-accent-rgb), 0.3);
  border-radius: 99px;
  border: 3px solid transparent;
  background-clip: padding-box;
}
.tw-dropdown::-webkit-scrollbar-thumb:hover {
  background: rgba(var(--sv-accent-rgb), 0.55);
  background-clip: padding-box;
}

.tw-dropdown-label {
  font-family: var(--tw-sans);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.25);
  padding: 9px 21px 12px;
  display: block;
  border-bottom: 1px solid rgba(var(--sv-accent-rgb), 0.1);
  margin-bottom: 6px;
}

.tw-dropdown-empty {
  display: block;
  font-family: var(--tw-sans);
  font-size: 13.5px;
  font-weight: 400;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.28);
  padding: 14px 21px 18px;
  text-align: center;
}

/* Per-item "Coming Soon" row — every suite until the shoot lands, plus every
   practical building feature without an explorable pano. */
.tw-dropdown-item-soon {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  font-family: var(--tw-sans);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0.02em;
  color: rgba(var(--sv-white-rgb), 0.32);
  padding: 11px 21px;
  border-radius: 15px;
  cursor: default;
  user-select: none;
}

.tw-dropdown-soon-tag {
  flex-shrink: 0;
  font-family: var(--tw-sans);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(var(--sv-accent-rgb), 0.85);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.35);
  border-radius: 999px;
  padding: 3px 9px;
  white-space: nowrap;
}

.tw-dropdown-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
  width: 100%;
  font-family: var(--tw-sans);
  font-size: 17px;
  font-weight: 400;
  letter-spacing: 0.02em;
  color: rgba(var(--sv-white-rgb), 0.55);
  background: transparent;
  border: none;
  padding: 11px 21px 11px 39px;
  border-radius: 15px;
  cursor: pointer;
  text-align: left;
  touch-action: manipulation;
  transition: color 0.15s, background 0.15s;
  position: relative;
}

.tw-dd-unit-name {
  font-size: 17px;
  font-weight: 400;
  color: inherit;
  letter-spacing: 0.02em;
}

.tw-dd-meta {
  display: flex;
  align-items: center;
  gap: 13px;
  opacity: 0.6;
  transition: opacity 0.15s;
}

.tw-dropdown-item:hover     .tw-dd-meta { opacity: 0.85; }
.tw-dropdown-item.tw-active .tw-dd-meta { opacity: 0.75; }

/* Unit metadata is intentionally blank until Seaview unit and floor-plan evidence is confirmed. */
.tw-dd-unit-tag {
  padding: 2px 7px;
  border-radius: 5px;
  background: rgba(var(--sv-accent-rgb), 0.16);
  color: var(--sv-accent);
  font-weight: 600;
}

.tw-dd-meta-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--tw-sans);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.75);
  /* Without this, two-word meta ("2 Bed") breaks mid-item onto a second line
     the instant the row runs out of width — reads as a layout bug, not a
     wrap. Paired with flex-wrap on the row itself at the mobile breakpoint
     (see .tw-toast-meta below for the same fix on the toast). */
  white-space: nowrap;
}

.tw-dropdown-item::before {
  content: '';
  position: absolute;
  left: 15px;
  top: 50%;
  transform: translateY(-50%);
  width: 4.5px;
  height: 4.5px;
  border-radius: 50%;
  background: var(--sv-accent);
  opacity: 0;
  transition: opacity 0.15s;
}

.tw-dropdown-item:hover {
  color: var(--sv-white);
  background: rgba(255, 255, 255, 0.05);
}

.tw-dropdown-item.tw-active {
  color: var(--sv-white);
  font-weight: 500;
}

.tw-dropdown-item.tw-active::before { opacity: 1; }

.tw-dropdown-item:focus-visible {
  outline: 2px solid var(--sv-accent);
  outline-offset: -2px;
  border-radius: 15px;
}

/* ── ROOM NAME TOAST ─────────────────────────────────────────────────────── */

/* Bottom-left is the toast's alone again — #tw-hint-trigger moved to the
   its natural height instead of being pushed up to clear that button. */
#tw-toast {
  position: fixed;
  bottom: 52px;
  left: 48px;
  z-index: 99;
  pointer-events: none;
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.7s var(--tw-ease), transform 0.7s var(--tw-ease);
  max-width: 500px;
}

#tw-toast.tw-show {
  opacity: 1;
  transform: translateY(0);
}

.tw-toast-label {
  display: block;
  font-family: var(--tw-sans);
  font-size: clamp(22px, 1.6vw, 28px);
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sv-accent);
  /* Spacing below now belongs to .tw-toast-eyebrow, the flex row that wraps
     this label and the unit chip — a bottom margin on a flex ITEM would offset
     it against the chip beside it instead of spacing the group. */
  margin-bottom: 0;
  text-shadow: 0 1.5px 15px rgba(0,0,0,0.6);
}

.tw-toast-room {
  display: block;
  font-family: var(--tw-serif);
  font-size: clamp(54px, 4.2vw, 86px);
  font-weight: 300;
  color: var(--sv-white);
  line-height: 1.05;
  text-shadow: 0 3px 30px rgba(0,0,0,0.55);
  letter-spacing: 0.01em;
}

.tw-toast-meta {
  display: flex;
  align-items: center;
  gap: 24px;
  margin-top: 18px;
  font-family: var(--tw-sans);
  font-size: clamp(17px, 1.3vw, 22px);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.78);
  text-shadow: 0 1.5px 12px rgba(0,0,0,0.6);
}

.tw-toast-meta-item {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  /* Same backstop as .tw-dd-meta-item above — without this, "2 Bed" breaks
     mid-item on a narrow screen. Paired with flex-wrap on the row itself at
     the mobile breakpoint below (nowrap alone just trades a mid-item break
     for a silent horizontal overflow — both halves are required together). */
  white-space: nowrap;
}

/* Eyebrow row: "NOW VIEWING" plus the unit number chip. The chip sits here
   rather than in the meta row below because a 4th meta chip pushed the toast
   wide enough to hit the centred nav pill at desktop widths (measured). */
.tw-toast-eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.tw-toast-unit {
  padding: 3px 9px;
  border-radius: 6px;
  background: rgba(var(--sv-accent-rgb), 0.16);
  color: var(--sv-accent);
  font-family: var(--tw-sans);
  font-size: 0.66em;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.tw-toast-meta-icon {
  color: var(--sv-accent);
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
}

.tw-toast-meta-icon svg { width: 30px; height: 30px; }

/* ── ONBOARDING HINT ─────────────────────────────────────────────────────── */
/* Shown once per device after Explore Now (see maybeShowHintAutomatically()
   in the JS), teaching first-time visitors the pano is draggable. Both the
   overlay and card are pointer-events:none throughout — this is a pure
   visual layer that never intercepts a drag gesture aimed at the pano
   underneath (see showHint()'s dismiss listener for why). Only
   .tw-hint-link (the embedded-only "open in a new tab" line) re-enables
   pointer-events on itself, since it's the one genuinely clickable thing
   here.

   DELIBERATELY brand-neutral (Danny 2026-08-06: "I want this to feel like
   it works on any tour, no matter the colours, fonts, etc... like the
   animated hotspot designs we made") — every color/font here comes from
   the --hint-* tokens in :root, never from --sv-*. White frosted glass
   (0.58 alpha + heavy blur) rather than the dark navy glass the rest of
   the UI uses, specifically so this reads as universal tour CHROME (how
   the interface works) rather than this property's BRAND (which the rest
   of the floating UI already carries). Same reasoning the native 3DVista
   hotspot icons stay white/neutral regardless of client palette. */
#tw-hint-overlay {
  position: fixed;
  inset: 0;
  z-index: 250;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.45s var(--tw-ease);
}

#tw-hint-overlay.tw-show { opacity: 1; }

.tw-hint-card {
  pointer-events: none;
  position: relative; /* anchors .tw-hint-close */
  max-width: 320px;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 26px 24px 0;
  border-radius: 22px;
  overflow: hidden;      /* the disclaimer band has square corners */
  background: var(--hint-bg);
  backdrop-filter: blur(32px) saturate(180%);
  -webkit-backdrop-filter: blur(32px) saturate(180%);
  border: 1px solid var(--hint-border);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.28), 0 1px 0 rgba(255, 255, 255, 0.6) inset;
  transform: scale(0.96) translateY(6px);
  transition: transform 0.45s var(--tw-ease);
}

#tw-hint-overlay.tw-show .tw-hint-card {
  transform: scale(1) translateY(0);
}

/* Explicit dismiss. `pointer-events: auto` is required — the overlay and card
   are both pointer-events:none so a drag aimed at the pano passes straight
   through, and each genuinely clickable child has to opt back in (same as
   .tw-hint-link). Deliberately quiet: it should read as a way out, not compete
   with the instruction the card is there to deliver. */
.tw-hint-close {
  pointer-events: auto;
  position: absolute;
  top: 10px;
  right: 10px;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--hint-ink);
  opacity: 0.4;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: opacity 0.18s, background 0.18s;
}

.tw-hint-close:hover { opacity: 0.85; background: rgba(0, 0, 0, 0.06); }
.tw-hint-close:focus-visible { opacity: 0.85; outline: 2px solid var(--hint-ink); outline-offset: 2px; }

.tw-hint-icon-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  height: 34px;
  margin-bottom: 12px;
  color: var(--hint-ink);
}

.tw-hint-chevron {
  display: flex;
  opacity: 0.3;
}

.tw-hint-pointer {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  animation: tw-hint-drag 2.4s ease-in-out infinite;
}

/* Touch variant — HINT_HTML leaves this span genuinely empty (no cursor
   SVG) when IS_TOUCH, so a plain filled dot stands in for a fingertip. */
.tw-hint-pointer:empty {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 6px rgba(22, 22, 26, 0.08);
}

@keyframes tw-hint-drag {
  0%   { transform: translateX(-16px); opacity: 0.5; }
  50%  { transform: translateX(16px);  opacity: 1; }
  100% { transform: translateX(-16px); opacity: 0.5; }
}

/* ── The steps ──────────────────────────────────────────────────────────────
   One card, three panels. The panels are stacked and only the active one is
   shown, so the card is as tall as its tallest step and nothing jumps as the
   visitor moves through it. */
.tw-hint-steps { width: 100%; display: flex; flex-direction: column; }
.tw-hint-step { display: none; flex-direction: column; align-items: center; width: 100%; }
.tw-hint-step.is-active { display: flex; animation: tw-hint-step-in 0.32s var(--tw-ease) both; }
@keyframes tw-hint-step-in {
  from { opacity: 0; transform: translateX(8px); }
  to   { opacity: 1; transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .tw-hint-step.is-active { animation: none; }
}

/* The walk hotspot, previewed in the card's own ink (--hint-ink, the same dark
   grey as the cursor and chevrons on step one) rather than the tour accent, so
   the card stays brand-neutral. Same motion as the real one: the pulse is born
   at the ring and only travels outwards. */
.tw-hint-icon-slot {
  display: flex; align-items: center; justify-content: center;
  gap: 22px;
  /* Tall enough for the pulse, which is the widest thing in the card: the ring
     is a 38px box inside the 44px drawing and `tw-hint-hotspot-ping` takes it
     to 1.72x (about 65px), so 76px leaves it clear of the top and bottom edges.
     At 64px the pulse was being cut off square by the overflow below — Danny,
     2026-09-24: "keep the animated ring inside its container". */
  width: 100%; height: 76px; margin-bottom: 2px; overflow: hidden;
  /* The illustrations draw in currentColor; this is what makes that the card's
     own ink rather than the body's black. */
  color: var(--hint-ink);
}
.tw-hint-hotspot { position: relative; display: block; width: 44px; height: 44px; }
.tw-hint-hotspot-ring {
  /* Same proportions as the real floating marker, scaled to this 44px drawing:
     the ring came in off the edge and the dot grew, so the card shows the
     marker Danny will actually see. */
  position: absolute; inset: 3px; border-radius: 50%;
  border: 2.5px solid currentColor;
  animation: tw-hint-hotspot-swell 2.6s cubic-bezier(0.3, 0.6, 0.25, 1) infinite;
}
@keyframes tw-hint-hotspot-swell {
  0%, 100% { transform: scale(1);    border-width: 2.5px; }
  18%      { transform: scale(1.07); border-width: 2.8px; }
  46%      { transform: scale(1);    border-width: 2.5px; }
}
@keyframes tw-hint-hotspot-ping {
  0%   { transform: scale(1);    opacity: 0;    border-width: 3px; }
  10%  { opacity: 0.45; }
  65%  { opacity: 0.14; }
  /* 1.72, not the 2.1 the real marker reaches: the card is 320px wide and the
     pulse has to stay inside the slot above (see .tw-hint-icon-slot). The
     gesture — out from the ring, fading and thinning — is the same one. */
  100% { transform: scale(1.72); opacity: 0;    border-width: 0.7px; }
}
.tw-hint-hotspot-ping {
  position: absolute; inset: 3px; border-radius: 50%;
  border: 2.5px solid currentColor;
  animation: tw-hint-hotspot-ping 2.6s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
}
@keyframes tw-hint-hotspot-nudge {
  0%, 22% { transform: translate(-50%, 3px)  rotate(45deg); opacity: 0; }
  38%     { opacity: 0.9; }
  70%     { transform: translate(-50%, -9px) rotate(45deg); opacity: 0; }
  100%    { transform: translate(-50%, -9px) rotate(45deg); opacity: 0; }
}
/* The centre the pulse comes from: static, like the real one. */
.tw-hint-hotspot-dot {
  position: absolute; left: 50%; top: 50%; width: 10px; height: 10px; margin: -5px 0 0 -5px;
  border-radius: 50%; background: currentColor;
}
/* The floor marker, drawn the way the player draws it in the pano: the same
   ring, foreshortened onto the ground. Still, like the real one (the pulse
   belongs to the floating marker), and quieter than it, because in the tour a
   floor ring reads as a surface rather than a control floating in front of
   you. */
.tw-hint-hotspot-floor { width: 44px; height: 44px; }
.tw-hint-floor-ring {
  position: absolute; left: 50%; top: 50%;
  width: 42px; height: 20px; margin: -10px 0 0 -21px;
  border-radius: 50%;
  border: 2.5px solid currentColor;
  opacity: 0.72;
}
@media (prefers-reduced-motion: reduce) {
  .tw-hint-hotspot-ring { animation: none; }
  .tw-hint-hotspot-ping { animation: none; opacity: 0; }
}
/* The other kind of marker: a symbol instead of a ring (info / media hotspots,
   and the door toggles). */
.tw-hint-chip {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 8px;
  border: 1.6px solid currentColor;
  font: 700 13px/1 var(--hint-sans);
}

/* Dots + Back/Next. The buttons are the only things in the card that need the
   pointer back (the card is pointer-events:none so a drag passes through to the
   pano). */
.tw-hint-dots { display: flex; gap: 7px; justify-content: center; align-items: center; width: 100%; margin: 16px 0 12px; }
.tw-hint-dot {
  pointer-events: auto;
  width: 7px; height: 7px; padding: 0; border: 0; border-radius: 50%;
  background: var(--hint-ink-faint);
  cursor: pointer;
  transition: background 0.18s, transform 0.18s;
}
.tw-hint-dot.is-active { background: var(--hint-ink); transform: scale(1.25); }
.tw-hint-dot:focus-visible { outline: 2px solid var(--hint-ink); outline-offset: 3px; }
.tw-hint-nav { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; }
/* On the first step Back is hidden (see renderHintStep) but still in the flow,
   and a hidden button takes a button's width, which pushed Next off centre —
   Danny, 2026-09-24: "center Next when there is no other button beside it".
   Only the step that has no Back drops it from the flow; the others keep the
   reserved space so Next does not jump sideways. */
.tw-hint-nav.tw-hint-nav-solo #tw-hint-back { display: none; }
.tw-hint-btn {
  pointer-events: auto;
  font-family: var(--hint-sans);
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.02em;
  padding: 9px 16px;
  border-radius: 100px;
  border: 1px solid var(--hint-ink-faint);
  background: transparent;
  color: var(--hint-ink-soft);
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.18s, color 0.18s, border-color 0.18s;
}
.tw-hint-btn:hover { background: rgba(22, 22, 26, 0.06); color: var(--hint-ink); }
.tw-hint-btn:focus-visible { outline: 2px solid var(--hint-ink); outline-offset: 2px; }
.tw-hint-btn-primary {
  background: var(--hint-ink);
  border-color: var(--hint-ink);
  color: #fff;
}
.tw-hint-btn-primary:hover { background: rgba(22, 22, 26, 0.85); color: #fff; }

.tw-hint-title {
  font-family: var(--hint-sans);
  font-size: 16px;
  font-weight: 600;
  color: var(--hint-ink);
  letter-spacing: 0.002em;
}

.tw-hint-sub {
  font-family: var(--hint-sans);
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.4;
  color: var(--hint-ink-soft);
  margin-top: 8px;
  max-width: 26ch;
}

.tw-hint-sub b { font-weight: 600; color: var(--hint-ink); }

.tw-hint-footer {
  margin-top: 16px;
  padding-top: 13px;
  width: 100%;
  border-top: 1px solid rgba(22, 22, 26, 0.12);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Its own band across the full width of the card (Danny, 2026-09-19: "make
   the disclaimer at the bottom a bit smaller, and in it's own section with a
   slightly grey background, full width in the window, and change font to
   white"). Negative margins cancel the card's own side padding, and the card
   clips its corners. */
.tw-hint-disclaimer {
  font-family: var(--hint-sans);
  font-size: 10px;
  font-weight: 400;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.9);
  width: auto;
  margin: 14px -24px 0;
  padding: 10px 20px;
  background: rgba(22, 22, 26, 0.34);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  text-align: center;
}

.tw-hint-link {
  pointer-events: auto;
  width: 100%;
  background: none;
  border: none;
  padding: 0;
  font-family: var(--hint-sans);
  font-size: 10.5px;
  font-weight: 400;
  color: var(--hint-ink-faint);
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.tw-hint-link span {
  color: var(--hint-ink);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.tw-hint-link:hover span,
.tw-hint-link:focus-visible span { color: var(--hint-ink); opacity: 0.7; }
.tw-hint-link:focus-visible { outline: 2px solid var(--hint-ink); outline-offset: 2px; border-radius: 4px; }

@media (max-width: 768px) {
  .tw-hint-card { padding: 22px 20px 16px; border-radius: 18px; }
  .tw-hint-title { font-size: 15px; }
  .tw-hint-sub { font-size: 12px; }
  .tw-hint-footer { margin-top: 14px; padding-top: 11px; }
  .tw-hint-disclaimer,
  .tw-hint-link { font-size: 10px; }
}

/* ── FLOATING MAP PILL ───────────────────────────────────────────────────── */
/* Bottom-right glass pill — a single button styled like the other floating
   pills, opening the fullscreen map modal directly. */

/* Back to sitting in the same row as the nav pill (Danny 2026-08-10): stacking
   it above #tw-hint-trigger read as "Location belongs to the help button",
   the opposite of the intent — Location IS one of the branded controls, help
   is the thing that should read as separate from it. `bottom: 42px` matches
   #tw-menu-bar's own bottom exactly, so the two sit on a shared baseline. */
#tw-map-card {
  position: fixed;
  bottom: 42px;
  right: 36px;
  z-index: 100;
  border-radius: 999px;
  overflow: hidden;
  border: 1px solid rgba(var(--sv-accent-rgb), 0.18);
  box-shadow: 0 12px 60px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(var(--sv-accent-rgb), 0.06) inset;
  background: rgba(var(--sv-base-rgb), 0.84);
  backdrop-filter: blur(24px) saturate(170%);
  -webkit-backdrop-filter: blur(24px) saturate(170%);
  transition-delay: 0.3s;
}

/* `width: 100%` is load-bearing, not cosmetic: #tw-map-card carries all the
   pill styling (background, border, radius) while this button is the only
   clickable thing inside it. If the card is ever given a width larger than
   this button's content width, the difference becomes a dead zone that still
   looks clickable. Stretching the button to the card guarantees the hit area
   and the visible pill are always the same rectangle. */
.tw-map-header {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 12px;
  padding: 16.5px 24px;
  background: transparent;
  border: none;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.18s;
}

.tw-map-header:hover { background: rgba(255, 255, 255, 0.04); }
.tw-map-header:focus-visible { outline: 2px solid var(--sv-accent); outline-offset: -2px; }

/* ── RE-OPEN ONBOARDING HINT ─────────────────────────────────────────────── */
/* Bottom-RIGHT corner, with the Location pill sitting inboard of it (Danny
   "Now Viewing" toast into an unnaturally high position, and sharing that
   corner made it read as part of the property's branded UI. It isn't — it
   explains how the TOUR works, the same category as 3DVista's own neutral
   hotspot icons.
   Styled from the --hint-* tokens (white frosted glass), NOT --sv-*, so it
   matches the card it opens and stays portable to any other property
   unchanged. Standalone, never in the nav pill — that slot is the
   aerial/drone button's (Danny 2026-08-06). */
#tw-hint-trigger {
  position: fixed;
  /* Sits clearly BELOW the LOCATION pill (Danny, 2026-09-19: "the how to use
     button needs to be below the location button"). At 26px on a 14px inset it
     was 2px under the pill's bottom edge, so the two read as one crowded
     control and the tooltip overlapped the pill. Smaller and lower gives the
     tooltip room to sit between them. */
  /* Genuinely alone in the corner, not stacked with Location anymore (Danny
     2026-08-10: stacking it under Location made IT read as the branded thing
     — pulling it right against the edge, independent of every other floating
     control, is what makes it read as tour chrome instead). Small and close
     to the edge on purpose: the least important, most native-feeling control
     on screen sits where a viewer's OWN UI would put a minor affordance. */
  bottom: 8px;
  right: 18px;
  left: auto;
  z-index: 100;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--hint-bg);
  backdrop-filter: blur(32px) saturate(180%);
  -webkit-backdrop-filter: blur(32px) saturate(180%);
  border: 1px solid var(--hint-border);
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.3), 0 1px 0 rgba(255, 255, 255, 0.6) inset;
  color: var(--hint-ink);
  opacity: 0.82;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: opacity 0.55s var(--tw-ease), background 0.18s, transform 0.15s var(--tw-ease);
  transition-delay: 0.3s, 0s, 0s;
}

#tw-hint-trigger svg { width: 12px; height: 12px; }

#tw-hint-trigger:hover { opacity: 1; transform: scale(1.06); }
#tw-hint-trigger:focus-visible { opacity: 1; outline: 2px solid var(--hint-ink); outline-offset: 2px; }

/* Hover label sits to the LEFT of the button, which now leaves it sitting
   neatly underneath the Location pill rather than colliding with it — the
   collision was only a problem back when the two shared a row. Vertically
   centred on the button. Pointer-events off so it can never swallow a click
   meant for the button. */
#tw-hint-trigger::after {
  content: 'How to use';
  position: absolute;
  right: calc(100% + 9px);
  top: 50%;
  transform: translateY(-50%) translateX(4px);
  padding: 7px 12px;
  border-radius: 999px;
  background: var(--hint-bg);
  backdrop-filter: blur(32px) saturate(180%);
  -webkit-backdrop-filter: blur(32px) saturate(180%);
  border: 1px solid var(--hint-border);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.26);
  color: var(--hint-ink);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s var(--tw-ease), transform 0.18s var(--tw-ease);
}

#tw-hint-trigger:hover::after,
#tw-hint-trigger:focus-visible::after {
  opacity: 1;
  transform: translateY(-50%) translateX(0);
}

/* Touch devices have no hover state, so the label would either never appear
   or stick after a tap. The icon alone carries it there. */
@media (hover: none) {
  #tw-hint-trigger::after { display: none; }
}

/* ── RESPONSIVE ──────────────────────────────────────────────────────────── */

@media (max-width: 1199px) and (min-width: 769px) {
  #tw-devon-logo-float { top: 22px; left: 26px; }
  .tw-devon-logo-img { height: 52px; }   /* same step as the desktop logo */
  #tw-logo-float { top: 24px; --tw-logo-w: 270px; --tw-logo-h: 50px; }
  .tw-logo-icon-img { height: 36px; }
  .tw-splash-logo-box { --tw-splash-logo-w: clamp(260px, 42vw, 430px); --tw-splash-logo-h: clamp(74px, 15vh, 112px); }
  #tw-cta-btn    { top: 22px; right: 22px; height: 56px; padding: 0 24px; font-size: 13.5px; }

  #tw-menu-bar   { bottom: calc(22px + env(safe-area-inset-bottom)); }
  .tw-menu-pill  { height: 64px; padding: 0 6px; }
  .tw-menu-btn   { padding: 0 18px; height: 50px; font-size: 14px; gap: 8px; }
  .tw-menu-sep   { height: 24px; margin: 0 2px; }
  #tw-aerial     { width: 56px; height: 50px; margin: 0 2px; }

  /* Clears the centred nav pill, which occupies 22..88px from the bottom on
     this breakpoint. Unlike desktop — where the toast stays narrow enough to
     sit beside the pill — the room name and its beds/baths/sq.ft. row get wide
     enough here to reach the pill's left edge, so they have to stack instead. */
  #tw-toast      { bottom: calc(104px + env(safe-area-inset-bottom)); left: 24px; max-width: min(42vw, 420px); }
  .tw-toast-label { font-size: clamp(16px, 2vw, 20px); letter-spacing: 0.16em; }
  .tw-toast-eyebrow { margin-bottom: 9px; gap: 9px; }
  .tw-toast-room  { font-size: clamp(36px, 5vw, 54px); }
  .tw-toast-meta  { gap: 16px; margin-top: 12px; font-size: clamp(13px, 1.4vw, 17px); }

  /* No fixed width. A hardcoded `width: 280px` here made the card render far
     wider than the button inside it (which is content-sized), so ~100px of the
     pill was visually part of the control but dead to clicks — and the live
     button was the size the pill should have been all along. Let it hug its
     content exactly like the desktop rule does. */
  /* Matches #tw-menu-bar's own bottom on this breakpoint — same reasoning as
     the desktop rule, same shared baseline. */
  #tw-map-card   { bottom: calc(22px + env(safe-area-inset-bottom)); right: 22px; }
  /* Beside Location rather than stacked below it — Location's own bottom
     offset here (22px) leaves too little room to fit anything beneath it and
     still clear its ~59px height (the desktop/mobile "stack below" approach
     needs the anchor sitting higher than that to have somewhere to stack
     into). Tucked into the narrow strip between Location's right edge and the
     true screen edge instead: still separate from it, still corner-anchored,
     just arranged left-right here rather than top-bottom. Vertically centred
     on Location (22 + 59/2 - 16/2 ≈ 44px). */
  #tw-hint-trigger { bottom: calc(44px + env(safe-area-inset-bottom)); right: 2px; left: auto; width: 16px; height: 16px; }
  #tw-hint-trigger svg { width: 10px; height: 10px; }
  /* Base rule already points the tooltip LEFT (toward Location/the canvas) —
     correct here too and left alone on purpose: the button sits only 2px
     from the true edge, so a right-pointing tooltip would run off-screen. */
}

/* Mobile — all phones (portrait & landscape) and small tablets ≤768px. */
@media (max-width: 768px) {
  #tw-devon-logo-float { display: none; }

  /* The property wordmark stays, centred, at every width.
     Danny: "we should have the property logo in the middle of the screen at the
     top — that's what we usually do on these metatour wrappers for Devon." The
     Devon design swapped to a small icon here, but this project has no icon
     asset (pilot row 37), so the swap produced an empty corner on a phone and
     an empty top-centre in a narrow window. A shorter wordmark fits beside the
     CTA: at 390px the clear space is ~240px and the lockup is ~106px. */
  /* On a 390px phone the top row is already full: the Devon mark ends at 112
     and the CTA starts at 194, so the clear band is 82px and this slot is
     106px because it is centred over both. It is NOT bigger here for the
     same reason — a taller mark runs into the Devon mark. Raising the phone
     size needs one of the other two to move; see the pilot issues log. */
  /* 2026-09-28: the 106px/26px slot here and the CTA's 26px/15px sizing below
     were both tuned assuming an 82px clear band between the (now off at this
     width) Devon mark and the CTA. The CTA actually renders ~176px wide at
     that sizing, not the assumed ~150px, so the centred wordmark (142-248)
     and the CTA (194-370) overlapped by ~54px — confirmed live via
     getBoundingClientRect at 390x844 (T-20260928-01 QA). Shrinking both
     closes the gap to a clean ~13px with no truncation ("Book a Tour" still
     renders on one line, scrollWidth === clientWidth). Revisit sizing here if
     the wordmark asset or CTA copy changes. */
  #tw-logo-float { top: 18px; left: 50%; transform: translateX(-50%); --tw-logo-w: 84px; --tw-logo-h: 26px; }
  .tw-logo-icon-img { display: none; }
  .tw-splash-logo-box { --tw-splash-logo-w: clamp(230px, 62vw, 380px); --tw-splash-logo-h: clamp(66px, 16vh, 104px); }

  #tw-cta-btn {
    top: 18px;
    right: 20px;
    height: 54px;
    min-width: 0;
    padding: 0 16px;
    font-size: 12px;
    letter-spacing: 0.04em;
  }

  /* Nudged up from 18px (v1) to make room for the "Leasing by Devon
     Properties" credit line underneath (Danny 2026-09-28 v2, see
     #tw-mobile-credit below) — the "?" how-to-use trigger (#tw-hint-trigger)
     deliberately does NOT move with it, per Danny's ask. */
  #tw-menu-bar   { bottom: calc(42px + env(safe-area-inset-bottom)); width: calc(100vw - 40px); display: flex; justify-content: center; left: 20px; right: 20px; transform: none; }
  /* backdrop-filter (like transform/filter/perspective/will-change) makes an
     element the containing block for its position:fixed descendants — and
     .tw-dropdown switches to position:fixed at this breakpoint specifically
     to span the full viewport instead of just this pill. With the blur left
     on, the dropdown's left/right below would resolve against the PILL's
     narrower, centered box instead of the viewport. Solid fill instead of
     blur here fixes it; desktop/tablet dropdowns use position:absolute
     anchored via .tw-menu-group's own position:relative, which resolves
     first and never reaches this rule, so their glass look is untouched. */
  .tw-menu-pill  { height: 70px; padding: 0 6px; max-width: 100%; backdrop-filter: none; -webkit-backdrop-filter: none; background: rgba(var(--sv-base-rgb), 0.94); }
  .tw-menu-btn   { padding: 0 20px; height: 58px; font-size: 16px; gap: 8px; letter-spacing: 0.04em; }
  .tw-menu-sep   { height: 26px; margin: 0 2px; }
  #tw-aerial     { width: 58px; height: 58px; margin: 0 2px; }
  .tw-menu-chevron { width: 13px; height: 13px; }
  .tw-menu-pill::-webkit-scrollbar { display: none; }

  #tw-toast       { bottom: auto; top: calc(104px + env(safe-area-inset-top)); left: 20px; right: 20px; max-width: none; transform: translateY(-10px); }
  #tw-toast.tw-show { transform: translateY(0); }
  .tw-toast-label { font-size: 16px; letter-spacing: 0.16em; }
  .tw-toast-eyebrow { margin-bottom: 10px; gap: 8px; }
  .tw-toast-room  { font-size: clamp(38px, 10vw, 56px); line-height: 1.0; }
  /* At 375px the row has ~335px, but the widest suite label ("2 Bedroom +
     Den") needs more than that alongside its meta. nowrap alone (on
     .tw-toast-meta-item above) prevents a mid-item break but trades it for a
     silent horizontal overflow — wrapping the ROW instead lets an item that
     doesn't fit drop to a clean second line as a whole unit. */
  .tw-toast-meta  { flex-direction: row; flex-wrap: wrap; align-items: center; column-gap: 14px; row-gap: 6px; margin-top: 12px; font-size: 15px; letter-spacing: 0.08em; }
  .tw-toast-meta-item { gap: 7px; align-items: center; }
  .tw-toast-meta-icon svg { width: 24px !important; height: 24px !important; }

  /* Floats above the nav pill on this breakpoint out of necessity — the pill
     goes full-width here, so there's no shared row to align Location into
     the way the wider breakpoints do. Back to the original 100px gap now
     that #tw-hint-trigger (below) no longer stacks underneath it. Shifted to
     124px (2026-09-28 v2) — same +24px nudge as #tw-menu-bar above, so the
     round buttons keep their original gap above the pill once the pill
     itself moves up for the Devon credit line. */
  #tw-map-card {
    bottom: calc(124px + env(safe-area-inset-bottom));
    right: 20px;
    left: auto;
    width: 68px;
    height: 68px;
    border-radius: 50%;
  }
  .tw-map-header { width: 100%; height: 100%; padding: 0; gap: 0; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
  .tw-map-header-text { display: none; }
  .tw-map-header-icon { display: flex; align-items: center; justify-content: center; }
  .tw-map-header-icon svg { width: 26px !important; height: 26px !important; stroke-width: 2.2 !important; }

  /* Genuinely in the true bottom-right corner (Danny 2026-08-10, repeated
     ask — the previous "stacked between the pill and Location" placement
     still read as inset, part of the branded cluster rather than the tour
     itself). The nav pill's rendered shape (checked against `.tw-menu-pill`
     itself, not `#tw-menu-bar`'s full-width wrapper) spans nearly edge to
     edge at this width, so there's no empty margin to tuck into — this
     deliberately sits low enough that their bounding boxes overlap, and
     relies on z-index (170, above #tw-menu-bar's 150) plus the pill's own
     rounded end-cap curving away from the true corner to keep it visually
     clear and on top rather than fighting for genuinely empty space that
     doesn't exist here. Verify this on a real phone, not just devtools —
     confirm it doesn't sit under the pill's rounded cap at whatever width
     actually ships. */
  #tw-hint-trigger {
    bottom: calc(10px + env(safe-area-inset-bottom));
    right: 10px;
    left: auto;
    width: 32px;
    height: 32px;
    z-index: 170;
  }
  #tw-hint-trigger svg { width: 15px; height: 15px; }

  .tw-splash-inner { padding: 36px 30px; }
  .tw-splash-icon-img { width: clamp(60px, 15vw, 84px); margin-bottom: 12px; }
  .tw-splash-logo-box { --tw-splash-logo-w: clamp(230px, 60vw, 350px); --tw-splash-logo-h: clamp(62px, 15vh, 96px); }
  .tw-splash-location { font-size: 16px; letter-spacing: 0.12em; margin-top: 12px; }
  .tw-splash-enter {
    margin-top: 32px;
    font-size: 17px;
    padding: 20px 40px;
    min-height: 64px;
    /* Tighter than the desktop 18px gap — reads as oddly separated between
       icon and text at mobile button scale. */
    gap: 10px;
  }
  .tw-splash-arrow { width: 22px; height: 22px; }
  .tw-splash-rule { margin: 16px auto; }
  /* Sized down and pushed up slightly so the "Leasing By" lockup clears the
     bottom safe area on real phones instead of crowding it. */
  .tw-splash-devon-wrap { bottom: 34px; gap: 9px; }
  .tw-splash-devon-img { width: clamp(85px, 24vw, 119px); }

  #tw-vignette-top {
    height: 240px;
    background: linear-gradient(to bottom,
      rgba(var(--sv-base-rgb), 0.82) 0%, rgba(var(--sv-base-rgb), 0.78) 15%, rgba(var(--sv-base-rgb), 0.65) 30%,
      rgba(var(--sv-base-rgb), 0.47) 45%, rgba(var(--sv-base-rgb), 0.28) 60%, rgba(var(--sv-base-rgb), 0.12) 75%,
      rgba(var(--sv-base-rgb), 0.02) 90%, rgba(var(--sv-base-rgb), 0) 100%
    );
  }
  #tw-vignette-bottom {
    height: 200px;
    background: linear-gradient(to top,
      rgba(var(--sv-base-rgb), 0.40) 0%, rgba(var(--sv-base-rgb), 0.38) 15%, rgba(var(--sv-base-rgb), 0.32) 30%,
      rgba(var(--sv-base-rgb), 0.23) 45%, rgba(var(--sv-base-rgb), 0.14) 60%, rgba(var(--sv-base-rgb), 0.06) 75%,
      rgba(var(--sv-base-rgb), 0.01) 90%, rgba(var(--sv-base-rgb), 0) 100%
    );
  }
  #tw-vignette-toast { display: none; }

  .tw-dropdown {
    position: fixed !important;
    left: 20px !important;
    right: 20px !important;
    /* +24px, same nudge as #tw-menu-bar above — keeps the same clearance
       above the pill now that the pill itself sits 24px higher. */
    bottom: calc(128px + env(safe-area-inset-bottom)) !important;
    top: auto !important;
    max-width: none !important;
    min-width: 0 !important;
    width: auto !important;
    z-index: 200 !important;
    transform: translateY(12px) !important;
    max-height: 46vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    border-radius: 20px;
  }
  .tw-dropdown.tw-open { transform: translateY(0) !important; }
  .tw-dropdown-item { padding: 16px 20px 16px 32px; font-size: 16px; }
  .tw-dropdown-item::before { left: 12px; }
  .tw-dropdown-item-soon { padding: 16px 20px; }

  /* "Leasing by Devon Properties" — replaces the Devon mark that used to sit
     in the header on mobile (Danny 2026-09-28 v2: "remove the devon logo on
     mobile, and then below the amenity/unit selector pill at the bottom we
     just add the text 'Leasing by Devon Properties'"). Matches the splash
     screen's own "Leasing By" treatment (.tw-splash-devon-label) for font,
     letter-spacing and colour so the credit reads as the same idiom in both
     places; brightened slightly (0.42 → 0.62 opacity) because this sits over
     live pano footage rather than the splash's own dark overlay, and it
     still rides on #tw-vignette-bottom's standing dark gradient at this
     breakpoint for contrast, same as the header logos ride #tw-vignette-top.
     No per-tour "leasing company" field exists in tour data (CONFIG only
     carries a brokerage logo asset, no name string) — hard-coded to Devon
     Properties here, scoped to the Devon skin file, same as the splash
     label beside it. */
  #tw-mobile-credit {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(12px + env(safe-area-inset-bottom));
    z-index: 140; /* below #tw-menu-bar (150), never competes with it */
    text-align: center;
    font-family: var(--tw-sans);
    font-size: 10.5px;
    font-weight: 500;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: rgba(var(--sv-white-rgb), 0.62);
    text-shadow: 0 1px 6px rgba(0, 0, 0, 0.5);
  }
}

@media (max-height: 680px) {
  .tw-splash-icon-img { width: clamp(50px, 10vw, 70px); margin-bottom: 10px; }
  .tw-splash-logo-box { --tw-splash-logo-w: clamp(200px, 30vw, 300px); --tw-splash-logo-h: clamp(52px, 11vh, 84px); }
  .tw-splash-rule  { margin: 18px auto; }
  .tw-splash-enter { margin-top: 24px; font-size: 15px; min-height: 54px; padding: 16px 36px; }
  /* Same +24px nudge as the main ≤768px block, for short phones (e.g.
     375x667) where this query also matches. */
  #tw-menu-bar     { bottom: calc(34px + env(safe-area-inset-bottom)); }
  .tw-menu-pill    { height: 58px; }
  .tw-menu-btn     { height: 46px; font-size: 14px; }
  #tw-vignette-top { height: 180px; }
}

/* Danny 2026-08-10: "the unit selector pill can block the text here for some
   units/screen sizes." Real gap, not just a longer-unit-name issue — the
   query above drops the nav pill to bottom:10px/height:58px (a 10..68px
   band), but nothing had ever adjusted #tw-toast for short viewports, so it
   was still sitting at the desktop base bottom:52px, landing squarely inside
   that band. Horizontal separation (toast left-anchored vs. pill centered)
   hides this on wide desktop windows, which is why it only showed up "for
   some screen sizes" — a short-but-still-desktop-width browser window (a
   smaller laptop, or Chrome just not maximized vertically) is exactly where
   width alone doesn't give enough of a horizontal gap to save it.
   Scoped to desktop width on purpose: the ≤1199px tablet breakpoint already
   bumps #tw-toast to bottom:104px with its own comment explaining exactly
   this clearance math, and the ≤768px mobile breakpoint moves the toast to
   the TOP of the screen entirely (bottom:auto) — folding this into the
   shared max-height query above would win the cascade on any viewport
   matching both max-width and max-height (any phone in landscape, for one)
   and silently reinstate a bottom value on a toast meant to stay top-anchored. */
@media (max-height: 680px) and (min-width: 1200px) {
  #tw-toast { bottom: 92px; }
}

/* ── HIDE NATIVE 3DVISTA LOAD BAR & PRELOADER ───────────────────────────── */
#viewer div[style*="height: 3px"][style*="z-index: 2"] { visibility: hidden !important; }
#preloadContainer { display: none !important; }

/* ── BRANDED LOAD INDICATOR ─────────────────────────────────────────────── */

#tw-loader {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2px;
  z-index: 200;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s var(--tw-ease);
}

#tw-loader.tw-loader-active { opacity: 1; }
#tw-loader.tw-loader-done   { opacity: 0; transition: opacity 0.3s var(--tw-ease) 0.15s; }

.tw-loader-fill {
  width: 100%;
  height: 100%;
  transform-origin: left center;
  transform: scaleX(0);
  background: linear-gradient(90deg, rgba(var(--sv-accent-rgb), 0), var(--sv-accent) 30%, var(--sv-white) 70%, rgba(var(--sv-white-rgb),0));
  box-shadow: 0 0 12px rgba(var(--sv-accent-rgb), 0.5);
}

/* ── FULLSCREEN MAP MODAL ────────────────────────────────────────────────── */

#tw-map-modal {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 1;
  transition: opacity 0.2s var(--tw-ease);
}

#tw-map-modal.tw-map-modal-hidden {
  opacity: 0;
  pointer-events: none;
}

.tw-map-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(var(--sv-base-rgb), 0.72);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  cursor: pointer;
}

.tw-map-modal-card {
  position: relative;
  width: 92vw;
  height: 88vh;
  max-width: 2100px;
  background: rgba(var(--sv-base-rgb), 0.95);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.2);
  border-radius: 24px;
  box-shadow: 0 30px 120px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(var(--sv-accent-rgb), 0.06) inset;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transform: scale(1);
  transition: transform 0.2s var(--tw-ease);
}

#tw-map-modal.tw-map-modal-hidden .tw-map-modal-card { transform: scale(0.96); }

.tw-map-modal-topbar {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 21px 27px;
  border-bottom: 1px solid rgba(var(--sv-accent-rgb), 0.12);
  background: rgba(var(--sv-base-rgb), 0.4);
}

.tw-map-modal-title {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--tw-sans);
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.55);
  flex-shrink: 0;
}

.tw-map-modal-title .tw-map-header-icon { color: var(--sv-accent); }
.tw-map-header-icon { color: var(--sv-accent); flex-shrink: 0; display: flex; align-items: center; }
.tw-map-header-text {
  font-family: var(--tw-sans);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.72);
  text-align: left;
  white-space: nowrap;
}

.tw-map-filter {
  display: flex;
  gap: 3px;
  padding: 4.5px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.1);
  border-radius: 15px;
  flex: 1;
  max-width: 630px;
  overflow-x: auto;
  /* Branded scroller. Left unstyled, this renders as the OS default bar — a
     thick light-grey slab spanning the pill, which is what Danny caught on
     mobile. Matches the .tw-dropdown scrollbar treatment: transparent track,
     translucent light-blue thumb. */
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--sv-accent-rgb), 0.34) transparent;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}

.tw-map-filter::-webkit-scrollbar { height: 5px; }
.tw-map-filter::-webkit-scrollbar-track { background: transparent; }
.tw-map-filter::-webkit-scrollbar-thumb {
  background: rgba(var(--sv-accent-rgb), 0.34);
  border-radius: 99px;
}
.tw-map-filter::-webkit-scrollbar-thumb:hover { background: rgba(var(--sv-accent-rgb), 0.6); }

.tw-map-filter-btn {
  flex: 1;
  padding: 9px 21px;
  background: transparent;
  border: none;
  border-radius: 10.5px;
  font-family: var(--tw-sans);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.5);
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.18s, color 0.18s;
  white-space: nowrap;
}

.tw-map-filter-btn:hover { color: rgba(var(--sv-white-rgb), 0.85); }
.tw-map-filter-btn.tw-active {
  background: rgba(var(--sv-accent-rgb), 0.16);
  color: var(--sv-accent);
}

.tw-map-modal-close {
  width: 54px;
  height: 54px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid rgba(var(--sv-accent-rgb), 0.30);
  border-radius: 15px;
  cursor: pointer;
  color: var(--sv-accent);
  transition: background 0.18s, color 0.18s, border-color 0.18s;
  flex-shrink: 0;
  margin-left: auto;
  -webkit-tap-highlight-color: transparent;
}

.tw-map-modal-close:hover {
  color: var(--sv-white);
  background: rgba(var(--sv-accent-rgb), 0.12);
  border-color: rgba(var(--sv-accent-rgb), 0.55);
}

.tw-map-modal-body { position: relative; flex: 1; min-height: 0; }

#tw-map-modal-canvas { position: absolute; inset: 0; background: var(--sv-base); }

#tw-map-modal-canvas .mapboxgl-ctrl-logo { opacity: 0.35; }
#tw-map-modal-canvas .mapboxgl-ctrl-attrib { font-size: 13.5px; opacity: 0.35; }

#tw-map-modal-canvas .mapboxgl-ctrl-top-right > .mapboxgl-ctrl { margin: 8px 8px 0 0 !important; }
#tw-map-modal-canvas .mapboxgl-ctrl-group {
  background: rgba(var(--sv-base-rgb), 0.88) !important;
  border: 1px solid rgba(var(--sv-accent-rgb), 0.28) !important;
  border-radius: 10px !important;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45) !important;
  overflow: hidden;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
#tw-map-modal-canvas .mapboxgl-ctrl-group button {
  width: 34px !important;
  height: 34px !important;
  background: transparent !important;
  border: none !important;
  border-bottom: 1px solid rgba(var(--sv-accent-rgb), 0.18) !important;
}
#tw-map-modal-canvas .mapboxgl-ctrl-group button:last-child { border-bottom: none !important; }
#tw-map-modal-canvas .mapboxgl-ctrl-group button:hover { background: rgba(var(--sv-accent-rgb), 0.14) !important; }
#tw-map-modal-canvas .mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 18 18'%3E%3Cpath d='M9 3v12M3 9h12' stroke='%23F5F8FC' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E") !important;
}
#tw-map-modal-canvas .mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 18 18'%3E%3Cpath d='M3 9h12' stroke='%23F5F8FC' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E") !important;
}

.tw-map-layer-toggle {
  position: absolute;
  bottom: 27px;
  left: 27px;
  z-index: 5;
  display: flex;
  gap: 3px;
  padding: 4.5px;
  background: rgba(var(--sv-base-rgb), 0.88);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.18);
  border-radius: 15px;
  box-shadow: 0 9px 36px rgba(0, 0, 0, 0.55);
}

.tw-map-layer-btn {
  padding: 9px 18px;
  background: transparent;
  border: none;
  border-radius: 10.5px;
  font-family: var(--tw-sans);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.55);
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.18s, color 0.18s;
}

.tw-map-layer-btn:hover     { color: rgba(var(--sv-white-rgb), 0.85); }
.tw-map-layer-btn.tw-active { background: rgba(var(--sv-accent-rgb), 0.16); color: var(--sv-accent); }

/* ── POI MARKERS + POPUP ─────────────────────────────────────────────────── */

.tw-map-poi-chip {
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: rgba(var(--sv-base-rgb), 0.92);
  border: 1.5px solid rgba(var(--sv-accent-rgb), 0.5);
  border-radius: 50%;
  color: var(--sv-white);
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45), 0 0 0 1.5px rgba(255, 255, 255, 0.10);
  transition: transform 0.15s var(--tw-ease), border-color 0.18s, color 0.18s, background 0.18s;
}

.tw-map-poi-chip:hover {
  transform: scale(1.18);
  border-color: var(--sv-accent);
  background: rgba(var(--sv-base-rgb), 0.98);
  color: var(--sv-accent);
}

.tw-map-pin-large {
  width: 22px !important;
  height: 22px !important;
  border-radius: 50%;
  background: var(--sv-accent);
  border: 3px solid white !important;
  box-shadow: 0 0 0 6px rgba(var(--sv-accent-rgb), 0.5), 0 0 0 12px rgba(var(--sv-accent-rgb), 0.18), 0 4px 18px rgba(0, 0, 0, 0.45);
  animation: tw-pin-pulse 2.2s ease-in-out infinite;
  cursor: default;
}

.tw-map-aerial-pin {
  position: relative;
  width: 48px;
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: var(--sv-accent);
  border: 1.5px solid rgba(var(--sv-base-rgb), 0.9);
  border-radius: 12px;
  color: var(--sv-base);
  cursor: pointer;
  box-shadow: 0 6px 21px rgba(0, 0, 0, 0.7), 0 0 0 4.5px rgba(var(--sv-accent-rgb), 0.18);
  transition: transform 0.18s var(--tw-ease), box-shadow 0.18s;
}

.tw-map-aerial-pin:hover {
  transform: scale(1.12);
  box-shadow: 0 9px 27px rgba(0, 0, 0, 0.75), 0 0 0 7.5px rgba(var(--sv-accent-rgb), 0.32);
}

.tw-map-aerial-num {
  position: absolute;
  top: -9px;
  right: -9px;
  min-width: 24px;
  height: 24px;
  padding: 0 6px;
  font: 700 15px/24px var(--tw-sans);
  color: var(--sv-accent);
  background: rgba(var(--sv-base-rgb), 0.96);
  border: 1px solid var(--sv-accent);
  border-radius: 15px;
  text-align: center;
  box-sizing: border-box;
}

@keyframes tw-pin-pulse {
  0%, 100% { box-shadow: 0 0 0 9px rgba(var(--sv-accent-rgb), 0.28), 0 6px 24px rgba(0, 0, 0, 0.65); }
  50%      { box-shadow: 0 0 0 21px rgba(var(--sv-accent-rgb), 0.05), 0 6px 24px rgba(0, 0, 0, 0.65); }
}

.tw-map-popup-wrap .mapboxgl-popup-content {
  background: rgba(var(--sv-base-rgb), 0.96);
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.25);
  border-radius: 15px;
  padding: 15px 21px;
  box-shadow: 0 15px 60px rgba(0, 0, 0, 0.6);
  color: var(--sv-white);
}

.tw-map-popup-wrap .mapboxgl-popup-tip { display: none; }

.tw-map-poi-popup .tw-map-poi-cat {
  font-family: var(--tw-sans);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sv-accent);
}

.tw-map-poi-popup .tw-map-poi-name {
  font-family: var(--tw-serif);
  font-size: 24px;
  font-weight: 400;
  color: var(--sv-white);
  margin-top: 3px;
  line-height: 1.25;
  max-width: 330px;
}

.tw-map-poi-popup .tw-map-poi-dist {
  font-family: var(--tw-sans);
  font-size: 15px;
  color: rgba(var(--sv-white-rgb), 0.55);
  margin-top: 6px;
}

body.tw-modal-open { overflow: hidden; }

/* ── RESPONSIVE — MODAL ──────────────────────────────────────────────────── */

@media (max-width: 768px) {
  .tw-map-modal-card { width: 100vw; height: 100dvh; max-width: none; border-radius: 0; }
  .tw-map-modal-topbar { flex-wrap: wrap; gap: 12px; padding: calc(12px + env(safe-area-inset-top)) 16px 12px; }
  .tw-map-modal-title { order: 1; flex: 1; font-size: 12px; gap: 8px; min-width: 0; }
  .tw-map-modal-title span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tw-map-modal-close { order: 2; width: 44px; height: 44px; }
  /* The five filter labels don't fit at phone width, so this row scrolls. Two
     things make that read as intentional rather than broken: the buttons stop
     stretching (`flex: 0 0 auto`) so each keeps its natural width instead of
     being squeezed, and the trailing edge fades out — a half-cut chip under a
     fade says "there's more" far better than a scrollbar does. Extra
     padding-bottom leaves room for the slim scroller without it sitting on
     top of the buttons. */
  .tw-map-filter {
    order: 3;
    flex-basis: 100%;
    max-width: none;
    padding-bottom: 7px;
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 22px), transparent 100%);
    mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 22px), transparent 100%);
  }
  .tw-map-filter-btn { flex: 0 0 auto; padding: 9px 14px; font-size: 12.5px; }
  .tw-map-layer-toggle { bottom: calc(18px + env(safe-area-inset-bottom)); left: 18px; padding: 4px; }
  .tw-map-layer-btn { padding: 8px 14px; font-size: 12.5px; }
  .tw-map-popup-wrap .mapboxgl-popup-content { max-width: calc(100vw - 60px); }
  .tw-map-poi-popup .tw-map-poi-name { font-size: 19px; max-width: 240px; }
}

/* ── REDUCED MOTION ──────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .tw-splash-icon-img,
  .tw-splash-wordmark-img,
  .tw-splash-rule,
  .tw-splash-location,
  .tw-splash-enter {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .tw-ui-el,
  #tw-toast,
  .tw-dropdown,
  .tw-splash-enter,
  .tw-menu-chevron,
  #tw-loader,
  .tw-loader-fill,
  #tw-map-modal,
  .tw-map-modal-card,
  .tw-map-poi-chip,
  .tw-map-pin-large,
  .tw-map-aerial-pin,
  #tw-hint-overlay,
  .tw-hint-card,
  .tw-hint-pointer { transition: none; animation: none; }
  .tw-loader-fill { transform: scaleX(1); }
}

/* ══════════════════════════════════════════════════════════════════════════
   TOUR STUDIO COMPONENTS

   Everything below is Tour Studio, not the Devon 3DVista build: the floorplan
   card and its enlarged view, and the pano zoom buttons (3DVista provided zoom
   itself; Marzipano does not). They are drawn with this shell's own tokens and
   spacing — same panel fills, borders, radii and typography as the LOCATION
   pill and modal above, because the floorplan and the map are the same kind of
   control and sat next to each other on the approved 3DVista layout's corner.

   Placement: the Devon shell gives bottom-left to the "Now viewing" toast and
   bottom-centre to the nav pill, so the floorplan card stacks on the RIGHT,
   directly above LOCATION, growing upward when it opens. On phones it becomes
   a round button stacked above the map's round button, and the enlarged view
   is the only place it opens.
   ══════════════════════════════════════════════════════════════════════════ */

/* The splash is built once the tour data lands; until then the pano behind it
   must not show (see the tw-skin-pending block at the top of wrapper.js). */
.tw-skin-pending #viewer { opacity: 0; }

/* ── Floorplan card ───────────────────────────────────────────────────────── */
#tw-plan-card {
  position: fixed;
  right: 36px;
  bottom: 126px;
  z-index: 100;
  /* Wide enough for "FLOORPLAN · Suite C1" plus the two header buttons on one
     line — the suite label was truncating to "S…" at 340px. */
  /* NOTE: this is the top-of-file default only. The card's real width lives in
     the always-visible mini-viewer block further down (search "always-visible
     mini viewer"), and that is the rule which actually wins. */
  width: 386px;
  border-radius: 24px;
  overflow: hidden;
  border: 1px solid rgba(var(--sv-accent-rgb), 0.18);
  box-shadow: 0 12px 60px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(var(--sv-accent-rgb), 0.06) inset;
  background: rgba(var(--sv-base-rgb), 0.84);
  backdrop-filter: blur(24px) saturate(170%);
  -webkit-backdrop-filter: blur(24px) saturate(170%);
  transition-delay: 0.34s;
}
/* Collapsed, the card is the same pill as LOCATION — it only becomes a panel
   once it is open. */
#tw-plan-card.tw-plan-collapsed { border-radius: 999px; }

.tw-plan-header-row { display: flex; align-items: center; }

.tw-plan-header {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
  min-width: 0;
  padding: 16.5px 24px;
  background: transparent;
  border: none;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.18s;
}
.tw-plan-header:hover { background: rgba(255, 255, 255, 0.04); }
.tw-plan-header:focus-visible { outline: 2px solid var(--sv-accent); outline-offset: -2px; }

.tw-plan-header-icon { color: var(--sv-accent); flex-shrink: 0; display: flex; align-items: center; }
.tw-plan-header-icon svg { width: 15px; height: 15px; }

.tw-plan-header-text {
  font-family: var(--tw-sans);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.72);
  text-align: left;
  white-space: nowrap;
}
/* Which suite the card is showing. The card never changes suite itself — the
   bottom Floor Plans pill does that — so this is a label, not a control. */
.tw-plan-header-suite {
  font-family: var(--tw-sans);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sv-accent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-left: auto;
}

.tw-plan-iconbtn {
  flex-shrink: 0;
  width: 52px;
  height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  cursor: pointer;
  color: rgba(var(--sv-white-rgb), 0.55);
  transition: color 0.18s, background 0.18s, transform 0.28s var(--tw-ease);
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.tw-plan-iconbtn:hover { color: var(--sv-accent); background: rgba(255, 255, 255, 0.04); }
.tw-plan-iconbtn:focus-visible { outline: 2px solid var(--sv-accent); outline-offset: -4px; }
.tw-plan-chevron-btn .tw-plan-chevron { color: inherit; transition: transform 0.28s var(--tw-ease); }
#tw-plan-card.tw-plan-collapsed .tw-plan-chevron-btn .tw-plan-chevron { transform: rotate(180deg); }

#tw-plan-body {
  max-height: 460px;
  overflow: hidden;
  transition: max-height 0.3s var(--tw-ease);
  border-top: 1px solid rgba(var(--sv-accent-rgb), 0.08);
}
#tw-plan-card.tw-plan-collapsed #tw-plan-body { max-height: 0; border-top-color: transparent; }

.tw-plan-stage {
  position: relative;
  width: calc(100% - 24px);
  margin: 12px;
  background: rgba(var(--sv-white-rgb), 0.04);
  border-radius: 12px;
  border: 1px solid rgba(var(--sv-accent-rgb), 0.08);
  overflow: hidden;
}
.tw-plan-img {
  width: 100%;
  height: auto;
  display: block;
  /* The extracted plans are dark ink on white; inverted to sit on this ground.
     Danny's taste call to make — see the tour-studio issues log. */
  filter: invert(1) hue-rotate(180deg) brightness(1.1) contrast(0.95);
  opacity: 0.86;
  user-select: none;
  pointer-events: none;
}
.tw-plan-zoom-layer {
  position: relative;
  width: 100%;
  transform-origin: center center;
  transition: transform 0.2s var(--tw-ease);
  cursor: default;
}
.tw-plan-zoom-layer[data-scale]:not([data-scale="1"]) { cursor: grab; }
.tw-plan-zoom-layer.tw-plan-panning { cursor: grabbing; transition: none; }

.tw-plan-zoom-ctrl {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 10;
  display: flex;
  flex-direction: column;
  background: rgba(var(--sv-base-rgb), 0.88);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.28);
  border-radius: 10px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
  overflow: hidden;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s var(--tw-ease);
}
.tw-plan-stage:hover .tw-plan-zoom-ctrl,
.tw-plan-zoom-ctrl:focus-within { opacity: 1; pointer-events: auto; }
/* The enlarged view's zoom buttons are always visible — there is no hover on a
   phone, and the modal is where zoom actually matters. */
.tw-plan-zoom-ctrl-modal { opacity: 1 !important; pointer-events: auto !important; top: 12px; right: 12px; }

.tw-plan-zoom-btn {
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
  color: var(--sv-white);
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.tw-plan-zoom-btn:first-child { border-bottom: 1px solid rgba(var(--sv-accent-rgb), 0.18); }
.tw-plan-zoom-btn:hover { background: rgba(var(--sv-accent-rgb), 0.14); }
.tw-plan-zoom-btn:focus-visible { outline: 2px solid var(--sv-accent); outline-offset: -2px; }
.tw-plan-zoom-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.tw-plan-zoom-btn:disabled:hover { background: transparent; }

.tw-plan-markers { position: absolute; inset: 0; pointer-events: none; }
.tw-plan-marker {
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: auto;
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 0;
  transition: left 0.35s var(--tw-ease), top 0.35s var(--tw-ease);
}
/* No invisible hit padding here on purpose. The markers sit close together on a
   small plan, so enlarged targets overlap and the last one in the DOM wins —
   which is how a press intended for one node landed on its neighbour. The
   holder's click handler picks the NEAREST marker instead (see
   renderFloorplanMarkers), so the drawn dot only has to be accurate, not big. */
.tw-plan-dot {
  position: absolute;
  top: 0; left: 0;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--sv-accent);
  border: 1.5px solid rgba(var(--sv-white-rgb), 0.95);
  box-shadow: 0 0 0 3px rgba(var(--sv-accent-rgb), 0.18), 0 1px 4px rgba(0, 0, 0, 0.55);
  transform: translate(-50%, -50%);
  transition: width 0.18s, height 0.18s, box-shadow 0.18s;
}
.tw-plan-marker:hover .tw-plan-dot {
  width: 15px; height: 15px;
  box-shadow: 0 0 0 5px rgba(var(--sv-accent-rgb), 0.28), 0 2px 6px rgba(0, 0, 0, 0.6);
}
.tw-plan-marker-active .tw-plan-dot {
  width: 17px;
  height: 17px;
  background: var(--sv-white);
  border-color: var(--sv-accent);
  box-shadow: 0 0 0 4px rgba(var(--sv-accent-rgb), 0.32), 0 2px 8px rgba(0, 0, 0, 0.55);
}
.tw-plan-cone {
  position: absolute;
  top: 0; left: 0;
  width: 50px;
  height: 56px;
  background: radial-gradient(ellipse at 50% 100%, rgba(var(--sv-accent-rgb), 0.6) 0%, rgba(var(--sv-accent-rgb), 0.18) 40%, transparent 75%);
  clip-path: polygon(50% 100%, 0% 0%, 100% 0%);
  transform-origin: 50% 100%;
  transform: translate(-50%, -100%);
  pointer-events: none;
}

/* ── Enlarged floorplan ───────────────────────────────────────────────────── */
#tw-plan-modal {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 1;
  transition: opacity 0.2s var(--tw-ease);
}
#tw-plan-modal.tw-plan-modal-hidden { opacity: 0; pointer-events: none; }
/* 2026-09-26/28 fix: .tw-plan-modal-markers sets pointer-events:none on itself
   (see below), but the .tw-plan-marker buttons rendered inside it explicitly
   set pointer-events:auto (they have to, to be clickable while the modal is
   open) — an explicit value on a descendant is not overridden by an ancestor's
   pointer-events, only by another explicit rule on that same descendant or its
   own ancestors. So with the modal closed but still laid out (display:flex,
   opacity:0), those dots stayed clickable at their screen positions and a tap
   there silently jumped the scene with no modal ever visibly opening
   (T-20260928-01 QA, confirmed via a dispatched click on a closed-modal dot).
   Making the markers inert here, keyed off the same .tw-plan-modal-hidden
   state the container already uses, is the actual fix — not a bigger hitbox
   or a JS display toggle. */
#tw-plan-modal.tw-plan-modal-hidden .tw-plan-marker { pointer-events: none !important; }

.tw-plan-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(var(--sv-base-rgb), 0.72);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  cursor: pointer;
}

.tw-plan-modal-card {
  position: relative;
  width: 92vw;
  /* Taller card and less padding around the plan (2026-09-25). The extracted
     plans are square, so inside a landscape stage they are limited by HEIGHT —
     every pixel of body height goes straight into making the plan's own labels
     bigger, which is what Danny asked for, and nothing gets cropped. */
  height: 93vh;
  max-width: 2100px;
  background: rgba(var(--sv-base-rgb), 0.95);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.2);
  border-radius: 24px;
  box-shadow: 0 30px 120px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(var(--sv-accent-rgb), 0.06) inset;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transform: scale(1);
  transition: transform 0.2s var(--tw-ease);
}
#tw-plan-modal.tw-plan-modal-hidden .tw-plan-modal-card { transform: scale(0.96); }

.tw-plan-modal-topbar {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 21px 27px;
  border-bottom: 1px solid rgba(var(--sv-accent-rgb), 0.12);
  background: rgba(var(--sv-base-rgb), 0.4);
}
.tw-plan-modal-title {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--tw-sans);
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.55);
  flex-shrink: 0;
}
.tw-plan-modal-title .tw-plan-header-icon { color: var(--sv-accent); }

/* Suite tabs — the one place the suite can be changed from the floorplan UI.
   Picking one also moves the tour behind the modal (see initFloorplanCard). */
.tw-plan-filter {
  display: flex;
  gap: 3px;
  padding: 4.5px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.1);
  border-radius: 15px;
  flex: 1;
  max-width: 630px;
  overflow-x: auto;
}
.tw-plan-filter-btn {
  flex: 1;
  padding: 9px 21px;
  background: transparent;
  border: none;
  border-radius: 10.5px;
  font-family: var(--tw-sans);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(var(--sv-white-rgb), 0.5);
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.18s, color 0.18s;
  white-space: nowrap;
}
.tw-plan-filter-btn:hover { color: rgba(var(--sv-white-rgb), 0.85); }
.tw-plan-filter-btn.tw-active { background: rgba(var(--sv-accent-rgb), 0.16); color: var(--sv-accent); }

.tw-plan-modal-close {
  width: 54px;
  height: 54px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid rgba(var(--sv-accent-rgb), 0.30);
  border-radius: 15px;
  cursor: pointer;
  color: var(--sv-accent);
  transition: background 0.18s, color 0.18s, border-color 0.18s;
  flex-shrink: 0;
  margin-left: auto;
  -webkit-tap-highlight-color: transparent;
}
.tw-plan-modal-close:hover {
  color: var(--sv-white);
  background: rgba(var(--sv-accent-rgb), 0.12);
  border-color: rgba(var(--sv-accent-rgb), 0.55);
}

.tw-plan-modal-body {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(var(--sv-base-rgb), 0.55);
}
.tw-plan-modal-stage {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
.tw-plan-modal-zoom-layer {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  transform-origin: center center;
  transition: transform 0.18s ease;
}
.tw-plan-modal-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  display: block;
  filter: invert(1) hue-rotate(180deg) brightness(1.1) contrast(0.95);
  opacity: 0.9;
  user-select: none;
  pointer-events: none;
}
.tw-plan-modal-markers { position: absolute; inset: 0; pointer-events: none; }
/* Markers are positioned against the image box, which is centred inside the
   stage — so they ride the image, not the stage. */
.tw-plan-modal-markers { width: 100%; height: 100%; }

/* ── Pano zoom (right edge, vertically centred) ───────────────────────────── */
.tw-pano-zoom-ctrl {
  position: fixed;
  top: 50%;
  right: 16px;
  transform: translateY(-50%);
  z-index: 50;
  display: flex;
  flex-direction: column;
  background: rgba(var(--sv-base-rgb), 0.88);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.28);
  border-radius: 10px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
  overflow: hidden;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.tw-pano-zoom-btn {
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
  color: var(--sv-white);
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.tw-pano-zoom-btn:first-child { border-bottom: 1px solid rgba(var(--sv-accent-rgb), 0.18); }
.tw-pano-zoom-btn:hover { background: rgba(var(--sv-accent-rgb), 0.14); }
.tw-pano-zoom-btn:focus-visible { outline: 2px solid var(--sv-accent); outline-offset: -2px; }
.tw-pano-zoom-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.tw-pano-zoom-btn:disabled:hover { background: transparent; }

body.tw-modal-open { overflow: hidden; }

/* ── Tablet ───────────────────────────────────────────────────────────────── */
@media (max-width: 1199px) and (min-width: 769px) {
  #tw-plan-card { right: 28px; bottom: 122px; width: 348px; }
}

/* ── Phone ────────────────────────────────────────────────────────────────── */
@media (max-width: 768px) {
  /* Same treatment the map pill gets at this width: a round button, stacked
     directly above it, opening straight into the enlarged view. */
  #tw-plan-card {
    right: 20px;
    bottom: calc(186px + env(safe-area-inset-bottom));
    width: 68px;
    height: 68px;
    border-radius: 50%;
  }
  .tw-plan-header-row { width: 100%; height: 100%; }
  .tw-plan-header {
    width: 100%; height: 100%; padding: 0; gap: 0;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
  }
  .tw-plan-header-text { display: none; }
  .tw-plan-header-suite { display: none; }
  .tw-plan-header-icon svg { width: 26px !important; height: 26px !important; stroke-width: 2 !important; }
  #tw-plan-expand, #tw-plan-chevron-btn, #tw-plan-body { display: none; }

  .tw-plan-modal-card { width: 100vw; height: 100vh; border-radius: 0; }
  .tw-plan-modal-topbar { padding: 14px 16px; gap: 10px; flex-wrap: wrap; }
  .tw-plan-modal-title span:not(.tw-plan-header-icon) { display: none; }
  .tw-plan-filter { order: 3; flex-basis: 100%; max-width: 100%; }
  .tw-plan-filter-btn { padding: 8px 14px; font-size: 13px; letter-spacing: 0.10em; }
  .tw-plan-modal-close { width: 44px; height: 44px; }
  .tw-plan-modal-body { padding: 14px; }
  .tw-plan-modal-img { max-height: calc(100vh - 180px); }

  .tw-pano-zoom-ctrl { right: 10px; }
}

/* ── Reduced motion ───────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  #tw-plan-card,
  #tw-plan-body,
  .tw-plan-zoom-layer,
  #tw-plan-modal,
  .tw-plan-modal-card,
  #tw-plan-modal.tw-plan-modal-hidden .tw-plan-modal-card,
  .tw-plan-chevron-btn .tw-plan-chevron { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   LAYOUT PASS — Danny's second review, 2026-09-18

   Three deliberate departures from the 3DVista file, all of them his call:

     1. the Devon mark leaves the top-left, and "Now viewing" takes that corner
        (it was bottom-left, where the plan now lives);
     2. the floorplan is a small, always-visible, semi-transparent mini viewer in
        the bottom-left, so you can see where you are and which way you are
        facing as you move; its one control opens the full plan and its suite
        selector;
     3. the pano zoom buttons are larger and sit above the onboarding hint, so
        the first click on them zooms instead of only dismissing the card.

   Everything below overrides the copied Devon rules above it on purpose, so the
   diff against the client's file stays visible rather than being edited away
   in place.
   ══════════════════════════════════════════════════════════════════════════ */

/* 1 — the Devon mark is back on the tour view, above the Now viewing block.
   It sat top-left in the client's own build; an early pass moved it off to
   stop the two from crowding. With the toast pulled down there is room for
   both, which is what Danny asked for: "add the Devon Properties logo back in,
   with the right amount of padding between it and the Now viewing section."
   Logo top 30 + 26px tall, toast top 92: a 36px gap. */
#tw-devon-logo-float {
  display: flex !important;
  top: 26px;
  left: 36px;
  transform: none;
}
/* 34px read a touch small on the live tour (Danny, 2026-09-25). */
.tw-devon-logo-img { height: 44px; }

/* 2026-09-28: on a phone this same "Devon back in, 44px tall" sizing sits
   directly beside the centred property wordmark (#tw-logo-float, 26px tall
   here per the ≤768px block above) with only an 18px gap between them —
   Danny: "the logo doesn't look right on mobile... the devon logo and
   building logo don't feel like they're fighting each other. The devon
   logo can be smaller on mobile." Devon is the property MANAGER's mark and
   belongs secondary to the building's own wordmark, which is the whole
   point of a tour like this.
   Fix, scoped to ≤768px only (desktop/tablet Devon sizing above is
   untouched): shrink Devon to a small top-left mark, then shift the
   wordmark left into a lockup right beside it instead of true-centring it —
   centring was fighting both Devon on the left and the CTA on the right for
   the same cramped middle band, which is exactly what kept the wordmark
   tiny (26px) no matter how much room existed. As a left-aligned lockup the
   wordmark can grow well past Devon's old 44px without touching the CTA's
   shrunk mobile slot (see #tw-cta-btn below), reading as the hero the way
   the property mark should. Sizes/positions measured live at 390px so nothing
   clips or collides; re-check if the Devon or wordmark asset's aspect ratio
   changes.

   2026-09-28 v2 (Danny): "remove the devon logo on mobile" — Devon's own mark
   comes off the header entirely rather than staying as a small secondary
   mark; its credit moves to a "Leasing by Devon Properties" line under the
   bottom pill instead (see the ≤768px block further down, next to
   #tw-menu-bar). The wordmark takes over the LEFT slot Devon used to occupy
   (left:20px, same left edge) at the SAME 150x34 scale Danny approved in the
   v1 pass above — not centred, not enlarged further, just moved into the
   now-empty corner. */
@media (max-width: 768px) {
  #tw-devon-logo-float { display: none !important; }

  #tw-logo-float {
    top: 24px;
    left: 20px;
    transform: none;
    justify-content: flex-start;
    --tw-logo-w: 150px;
    --tw-logo-h: 34px;
  }

  /* Trimmed a touch further to keep clear of the wider wordmark above —
     "Book a Tour" still renders on one line (scrollWidth === clientWidth)
     at 390px, measured live. */
  #tw-cta-btn {
    right: 16px;
    padding: 0 14px;
    font-size: 11px;
  }
}

#tw-toast {
  top: 92px;
  bottom: auto;
  left: 36px;
  max-width: min(46vw, 560px);
}
/* The toast is the top-left block now, so it fades down from above rather than
   up from below (same motion, opposite direction). */
#tw-toast { transform: translateY(-14px); }
#tw-toast.tw-show { transform: translateY(0); }

/* The strong corner scrim belongs to the block it was drawn for.
   "Now viewing" moved from the bottom-left to the top-left in this pass, but
   its scrim stayed where it was — so the 0.95-alpha blob sat behind the
   floorplan card instead, shading the plan for no reason (Danny: "drop the
   vignette scrim behind the floorplan icon"). Same gradient, anchored to the
   corner the toast actually occupies now. */
#tw-vignette-toast {
  /* Pulled down and slimmed: it was a 0.95-alpha blob in the top-left corner
     that read as a second logo competing with the wordmark. Now it starts
     below the wordmark, hugs the left edge rather than spreading across the
     top, and tops out much lighter. */
  /* Starts at the very top: it faded in from 44px and that edge read as a hard
     cut across the screen (Danny: "there's a hard cut in it where it starts at
     the top now after you moved it down"). The corner it is anchored to is the
     left edge, so the wordmark in the middle of the top stays clear anyway. */
  top: 0;
  bottom: auto;
  left: 0;
  width: 620px;
  height: 540px;
  background: radial-gradient(ellipse farthest-side at 0% 0%,
    rgba(var(--sv-base-rgb), 0.74) 0%, rgba(var(--sv-base-rgb), 0.70) 15%, rgba(var(--sv-base-rgb), 0.58) 30%,
    rgba(var(--sv-base-rgb), 0.42) 45%, rgba(var(--sv-base-rgb), 0.25) 60%, rgba(var(--sv-base-rgb), 0.11) 75%,
    rgba(var(--sv-base-rgb), 0.02) 90%, rgba(var(--sv-base-rgb), 0) 100%
  );
}

/* The pano is something you grab and turn, so say so with the cursor. The
   grabbing hand only appears once a drag is actually under way (see
   markViewerDragging in wrapper.js) — a plain click on a walk hotspot must not
   read as a drag, which is what Danny meant by "it registers that I'm clicking
   and dragging even when I'm not". */
/* !important because Marzipano writes `cursor: default` inline on this element
   when it builds the viewer (and `move` while a pan is live), and an inline
   declaration beats any stylesheet. It is the only way to have a say here. */
#viewer { cursor: grab !important; }
body.tw-viewer-dragging #viewer,
body.tw-viewer-dragging #viewer * { cursor: grabbing !important; }

/* 2 — the floorplan as an always-visible mini viewer, bottom-left. */
#tw-plan-card {
  left: 36px;
  right: auto;
  bottom: 42px;
  /* Widened from 264px (2026-09-25). The plan inside is square and sized by this
     width, so it is the one lever that makes the mini plan bigger without
     cropping it — Danny: "the mini floor plan viewer is maybe a touch too small
     and hard to see." */
  width: 320px;
  border-radius: 18px;
  /* Deliberately see-through: it is a heads-up display over the tour, not a
     panel competing with it. */
  background: rgba(var(--sv-base-rgb), 0.55);
  border-color: rgba(var(--sv-accent-rgb), 0.14);
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.42);
  backdrop-filter: blur(16px) saturate(150%);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
}
#tw-plan-card.tw-plan-collapsed { border-radius: 18px; }

.tw-plan-header-row { padding: 10px 8px 0 12px; gap: 9px; }
.tw-plan-header-icon svg { width: 13px; height: 13px; }
.tw-plan-header-text { font-size: 11px; letter-spacing: 0.18em; color: rgba(var(--sv-white-rgb), 0.62); }
.tw-plan-header-suite { font-size: 11px; letter-spacing: 0.06em; margin-left: auto; }
.tw-plan-iconbtn { width: 34px; height: 34px; border-radius: 50%; }
.tw-plan-iconbtn:hover { background: rgba(var(--sv-accent-rgb), 0.14); color: var(--sv-accent); }
/* #tw-plan-expand carries two glyphs and CSS picks one (see the phone block at
   the end of this file): the expand arrows wherever the card is a mini viewer,
   the floorplan mark where the circle is the control. */
.tw-plan-expand-glyph { display: inline-flex; align-items: center; justify-content: center; }
.tw-plan-expand-plan { display: none; }

#tw-plan-body { max-height: 320px; border-top: none; }
.tw-plan-stage {
  margin: 8px 10px 10px;
  border-radius: 10px;
  background: rgba(var(--sv-white-rgb), 0.03);
  border-color: rgba(var(--sv-accent-rgb), 0.06);
}
.tw-plan-img { opacity: 0.62; }               /* the drawing, not the subject */
.tw-plan-dot { width: 10px; height: 10px; }
.tw-plan-marker-active .tw-plan-dot { width: 13px; height: 13px; }
/* The view cone, visible in the mini viewer too.
   It was already being drawn there, but its gradient was brightest at the tip —
   which sits underneath the marker dot — so all that showed was a smudge, and
   the far end (the part that tells you which way you are looking) faded out
   before it could be read. Brighter out at the edges, and a little larger,
   because this card is a quarter the size of the enlarged plan. */
.tw-plan-cone {
  width: 54px;
  height: 64px;
  background: linear-gradient(to top,
    rgba(var(--sv-accent-rgb), 0.62) 0%, rgba(var(--sv-accent-rgb), 0.40) 45%,
    rgba(var(--sv-accent-rgb), 0.16) 80%, rgba(var(--sv-accent-rgb), 0.03) 100%);
}

/* The mini viewer has no zoom control of its own — the full plan does. */
#tw-plan-zoom-ctrl { display: none; }

/* 3 — pano zoom: bigger targets, above the hint card, obviously alive. */
.tw-pano-zoom-ctrl {
  z-index: 260; /* above #tw-hint-overlay's 250: a click on + zooms AND dismisses */
  right: 18px;
  border-radius: 12px;
}
.tw-pano-zoom-btn { width: 46px; height: 46px; }
.tw-pano-zoom-btn svg { width: 18px; height: 18px; }
.tw-pano-zoom-btn:hover { background: rgba(var(--sv-accent-rgb), 0.18); color: var(--sv-accent); }
.tw-pano-zoom-btn:active { background: rgba(var(--sv-accent-rgb), 0.3); }
.tw-pano-zoom-btn:disabled { opacity: 0.28; }

@media (max-width: 1199px) and (min-width: 769px) {
  #tw-plan-card { left: 28px; bottom: 42px; width: 288px; }   /* the same +21% step */
  #tw-toast { left: 28px; max-width: min(44vw, 460px); }
}

@media (max-width: 768px) {
  /* The corner swap already happened above; on a phone the toast sits under the
     CTA row, which is where this shell's own stylesheet puts it. */
  #tw-toast {
    top: calc(104px + env(safe-area-inset-top));
    left: 20px;
    right: 20px;
    /* The desktop cap (46vw) would be 179px here and stack the three chips on
       top of each other; the mobile rule this replaces said `none` for exactly
       that reason. */
    max-width: none;
  }
  /* The mini viewer becomes the button that opens the full plan, in the mirror
     of the LOCATION circle: floorplan left, location right. Bottom shifted
     100px → 124px (2026-09-28 v2), same +24px nudge as #tw-map-card, so it
     keeps its original gap above the pill now that the pill has moved up
     for the Devon credit line. */
  #tw-plan-card {
    left: 20px;
    right: auto;
    bottom: calc(124px + env(safe-area-inset-bottom));
    width: 68px;
    height: 68px;
    border-radius: 50%;
  }
  .tw-plan-header-row { width: 100%; height: 100%; padding: 0; align-items: center; justify-content: center; }
  .tw-plan-iconbtn { width: 100%; height: 100%; }
  .tw-plan-iconbtn svg { width: 24px !important; height: 24px !important; stroke-width: 2 !important; }
  #tw-plan-body { display: none; }
  .tw-pano-zoom-ctrl { right: 12px; }
  .tw-pano-zoom-btn { width: 42px; height: 42px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   IN-PANO NAVIGATION HOTSPOTS — the ones you click to walk to another room.

   The player builds this markup in the host page
   (`preview/index.html` / `viewer/index.html`, `buildHotspotEl`): a .tw-hs
   button holding a ring, a dot and a label. Its styling, however, only ever
   existed in the SHARED wrapper.css — and a skin replaces that whole file, so
   the first skinned tour rendered the markup bare. That is the "ugly text
   boxes" Danny reported three reviews running, and the reason the 74-check
   skin suite passed anyway: nothing asserted how a hotspot looks.

   Drawn in this shell's language rather than the shared one (the shared file
   uses a gold dot and a cream pill; Devon's is navy and teal), so the tokens
   here are --sv-*, aliased to the tour's palette by the skin.
   ══════════════════════════════════════════════════════════════════════════ */

/* The walk hotspot is a ring and nothing else. Danny: "a slightly opaque ring
   that's a neutral white colour that turns to the accent colour on hover" —
   the accent used to live on a centre dot, and it is the hover state now. */

/* IMPORTANT: never put `transition: transform` on .tw-hs — Marzipano writes
   the element's transform every frame to position the hotspot, and a CSS
   transition tweens those writes into a laggy follow while the pano is
   dragged. Hover effects belong on the inner elements. will-change keeps the
   hotspot on its own compositor layer. */
.tw-hs {
  position: relative;
  /* The BOX is the hit area and the ring is drawn inside it, so the trigger can
     grow without the marker changing size (see .tw-hs-ring's inset). Widened
     from 56px on 2026-09-25 — Danny: "the user doesn't have to be so accurate
     with their mouse placement in order to go to them." */
  width: 78px;
  height: 78px;
  background: transparent;
  border: 0;
  padding: 0;
  margin-left: -39px;
  margin-top: -39px;
  cursor: pointer;
  font: inherit;
  color: var(--sv-white);
  pointer-events: auto;
  outline: none;
  will-change: transform;
}
.tw-hs:focus-visible {
  outline: 2px solid var(--sv-accent);
  outline-offset: 4px;
  border-radius: 50%;
}
.tw-hs-ring {
  position: absolute;
  /* Pulled in from the edge of the 56px box, so the ring sits nearer the centre
     dot it is read against. Danny, 2026-09-19: "I feel like the rings need to be
     closer to the dot, or the dot needs to be a bit bigger, without changing the
     overall size of the hotspot in the tour, because it looks like the right
     size right now." The BOX is the hotspot's size (56px, and the hit area), so
     none of this moves anything in the room: the ring came in 4px and the dot
     grew, which halves the empty gap between them. The pulse below starts from
     the same inset and reaches the same outer diameter it always did. */
  /* 15px inside a 78px box keeps the ring the 48px it has always been drawn at
     — the box grew, the marker did not. */
  inset: 15px;
  border-radius: 50%;
  border: 3px solid rgba(var(--sv-white-rgb), 0.74);
  background: transparent;
  /* The ring has to hold its edge on a white wall and on a pale floor as well
     as on a dark one (the 3DVista reference is white on a dark floor; this
     tour is white rings on light oak). The shadow does the contrasting, so the
     ring itself can stay neutral instead of carrying a dark fill. */
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.55));
  /* `transform` is free for the HOVER because the swell below owns `inset`
     instead — that is the whole point of the split. Danny: "the ring turning
     into the accent color happens all at once. I think we need to make that
     ring turn into the accent color and scale together, so it feels more
     graceful." Colour and size now run on one transition, on one clock. */
  transition: border-color 0.24s var(--tw-ease), transform 0.24s var(--tw-ease);
  animation: tw-hs-swell 2.6s cubic-bezier(0.3, 0.6, 0.25, 1) infinite;
}
.tw-hs:hover .tw-hs-ring,
.tw-hs:focus-visible .tw-hs-ring {
  border-color: var(--sv-accent);
  transform: scale(1.08);
}
/* …and the centre dot takes the accent with it, so hover lights the whole
   marker instead of leaving a white dot inside an accent ring (Danny,
   2026-09-19: "the floating hotspot has a dot in the middle of it. This should
   change to the accent colour when hovered over, not just the outer ring.").
   A media chip is a white tile carrying a glyph, not a centre dot, so it is
   excluded and keeps its own hover. */
.tw-hs:hover .tw-hs-dot:not(.tw-hs-glyph),
.tw-hs:focus-visible .tw-hs-dot:not(.tw-hs-glyph) {
  background: var(--sv-accent);
}
/* No hover variable any more: the swell animates `inset` and hover animates
   `transform`, so neither can stomp the other. They simply compose — a hovered
   ring breathes at 8% larger than a resting one. */

/* ── The floating marker's loop ─────────────────────────────────────────────
   Two motions on one beat, chosen by Danny from the six drawn on the review page
   ("I like the swell"):

     tw-hs-swell  the ring itself breathes outwards and settles back
     tw-hs-ping   a pulse is born AT the ring's radius and only ever leaves it

   The swell is what makes the whole marker one outward gesture rather than a
   still ring with something moving past it. Cycle 2.6s: ~1.7s of travel, then a
   beat of nothing.

   The three rules the earlier passes settled on, all still true: one pulse per
   cycle so two rings never cross; born at the ring's own radius so nothing
   grows THROUGH it; and thinning as it goes (3px → 0.7px) so the outer edge is
   the faint one. */
@keyframes tw-hs-swell {
  0%, 100% { inset: 15px; border-width: 3px; }
  18%      { inset: 13.4px; border-width: 3.4px; }
  46%      { inset: 15px; border-width: 3px; }
}
@keyframes tw-hs-ping {
  0%   { transform: scale(1);    opacity: 0;    border-width: 3px; }
  10%  { opacity: 0.5; }
  65%  { opacity: 0.16; }
  100% { transform: scale(2.16); opacity: 0;    border-width: 0.7px; }
}
.tw-hs-ping {
  position: absolute;
  inset: 15px;                /* the same box as the ring, so the beat matches */
  border-radius: 50%;
  border: 3px solid rgba(var(--sv-white-rgb), 0.9);
  /* The dark backing stroke rides with it, so the pulse stays legible on a
     white wall as well as on a dark one. */
  box-shadow: 0 0 0 1px rgba(12, 18, 26, 0.22);
  animation: tw-hs-ping 2.6s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
}
/* The centre the pulse comes out of. Static on purpose: it is the anchor the
   motion reads against, so it has to sit still. Bigger since 2026-09-19 so it
   holds its own against the ring. */
.tw-hs-dot {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: rgba(var(--sv-white-rgb), 0.92);
  box-shadow: 0 0 0 1px rgba(12, 18, 26, 0.28);
  /* The ring eases into its accent on the same beat, so the two arrive
     together instead of the dot snapping ahead of the ring. */
  transition: background-color 0.18s var(--tw-ease);
}
@media (prefers-reduced-motion: reduce) {
  .tw-hs-ring { animation: none; }
  .tw-hs-ping { animation: none; opacity: 0; }
}
.tw-hs-label {
  position: absolute;
  left: 50%;
  top: calc(100% + 10px);
  transform: translateX(-50%);
  font-family: var(--tw-sans);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sv-white);
  background: rgba(var(--sv-base-rgb), 0.86);
  border: 1px solid rgba(var(--sv-accent-rgb), 0.24);
  padding: 5px 11px;
  border-radius: 100px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s var(--tw-ease);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
/* No text over a walk hotspot, floor or floating (Danny, 2026-09-19): the name
   is another per-tour variable to get right, and a ring reads as "go here" on
   its own. The element still carries an aria-label for screen readers.
   Info/media hotspots keep their title chip — they are not navigation. */

/* In-pano info/media hotspots (M6): a chip with a type glyph instead of a dot,
   because they are not navigation. Titles show on hover. */
a.tw-hs { display: block; text-decoration: none; }
.tw-hs-media .tw-hs-dot {
  width: 24px;
  height: 24px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--tw-sans);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  color: var(--sv-base);
  background: var(--sv-white);
  box-shadow: 0 0 12px rgba(var(--sv-white-rgb), 0.45);
}
.tw-hs-media .tw-hs-label { display: block; }
.tw-hs-media:hover .tw-hs-label,
.tw-hs-media:focus-visible .tw-hs-label { opacity: 1; }
/* Link hotspots are skipped at render time in unbranded (MLS) mode; this is
   the belt-and-braces guard in case that ever drifts. */
.tw-unbranded .tw-hs-link { display: none !important; }


/* ── Floor hotspots — the parallax "click and go" marker ───────────────────
   3DVista's flat hotspots are rings lying on the ground rather than rings
   hanging at eye level, so the visitor clicks the marker on the floor to walk
   somewhere. The player does the geometry (floorHotspotOptions in
   preview/index.html and viewer/index.html): the marker is placed with
   Marzipano's 3D matrix, tilted to 90deg - its own pitch so the surface is
   level, at a distance that makes it grow and shrink with the zoom the way a
   real object does.

   So there is NO squash and no fill in here: the foreshortening is real (a ring
   on a floor is an ellipse from head height and a circle when you look down at
   it), and the skin draws exactly the same ring as every other hotspot. The
   box is the marker's size in the room; how big that reads on screen is the
   placement's business, not CSS's. */
.tw-hs-floor {
  /* Square, and centred by Marzipano's own 3D transform (it carries the
     translate(-50%, -50%)), so no negative margins here, unlike every other
     .tw-hs. The box is the marker's size IN THE ROOM: the placement magnifies
     it by 1/near, so the same box draws bigger when the link's neighbour is
     close and smaller when it is far away, and it grows with the zoom.

     Widened 26px -> 44px on 2026-09-25 with the ring pulled in to 16% so the
     DRAWN ring is the same 30px it always was (44 - 2x7.04 = 29.9). The box is
     invisible, so this buys target area and nothing else — a pseudo-element
     halo was tried first and does not hit-test, which the check caught.
     Danny: "this hover zone needs to be bigger still… the user can click
     almost in the general area of that hotspot." */
  width: 44px;
  height: 44px;
  margin: 0;
}
.tw-hs-floor .tw-hs-ring {
  /* 16% inside the 44px box draws the same ring the old -8% of 26px did, so the
     marker on the floor is unchanged while the target around it is 1.7x. */
  inset: 16%;
  /* Thicker than the floating ring, in the room's own units, so it survives
     the foreshortening that flattens it into an ellipse. */
  border-width: 2.2px;
  /* No swell here. The loop belongs to the marker that floats in front of the
     room; a marker lying in the room is still (see the rule in the floating
     block), and on the floor the breathing would read as the ring wandering. */
  animation: none;
}

/* ── Oriented hotspots — the general 3D plane ──────────────────────────────
   A link carrying an `orient` object is a plane the author aims at a surface:
   a door's face, a wall, the ground (see orientPlacement in
   preview/index.html and viewer/index.html). Same ring, no pulse — the rule is
   "in the room, still; floating, animated" — so all this class has to do is
   take the centring back off Marzipano's transform, exactly like the floor
   marker above.

   The box stays at the floating marker's 56px because an oriented hotspot sits
   at the SAME depth as an ordinary one by default (distance 1.0 = the
   projection distance), so it draws at its natural size and only the plane's
   angle changes. Pulling the distance slider down is what makes it grow and
   start to slide as the visitor turns. */
.tw-hs-3d {
  margin: 0;
}
/* ...and it is still: it is a thing lying in the room, not a marker floating in
   front of it, so it takes no part in the floating marker's loop. */
.tw-hs-3d .tw-hs-ring { animation: none; }

/* Tour Studio plan geometry: the shared wrapper sizes one image/marker box in
   pixels. Devon replaces that stylesheet, so it needs the same layout rules. */
.tw-plan-img,
.tw-plan-modal-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  -webkit-user-drag: none;
}
.tw-plan-zoom-layer,
.tw-plan-modal-zoom-layer {
  position: relative;
  width: 100%;
  height: auto;
  margin: auto;
  aspect-ratio: var(--tw-plan-ar, 4 / 3);
  touch-action: none;
}
.tw-plan-modal-stage {
  flex: 1 1 auto;
  align-self: stretch;
  min-width: 0;
  min-height: 0;
  width: auto;
  height: auto;
}
@media (max-width: 768px) {
  /* At this width the whole card is one round button, so the one visible
     control is the control that opens the enlarged plan: the icon row becomes
     decoration, and the Expand button fills the dial so any tap lands on it. */
  .tw-plan-header-icon,
  .tw-plan-header-text,
  .tw-plan-header-suite,
  #tw-plan-chevron-btn { display: none; }
  .tw-plan-header-row { padding: 0; }
  #tw-plan-expand {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    color: var(--sv-accent);
  }
  #tw-plan-expand svg { width: 24px !important; height: 24px !important; stroke-width: 2.2 !important; }
  /* The circle is the FLOORPLAN control, so at this width it wears the
     floorplan mark instead of the expand arrows (Danny, 2026-09-24). The button
     is unchanged: same 100% hit area, same click, and its aria-label still
     names the action ("Open the full floorplan"). */
  .tw-plan-expand-glyph { display: none; }
  .tw-plan-expand-plan { display: inline-flex; align-items: center; justify-content: center; }
  /* Same dial as the LOCATION circle beside it. On desktop the card is a
     deliberate see-through mini viewer; on a phone it is a button, so it takes
     the Location button's darker treatment (Danny: "match its button opacity /
     visual treatment to the darker Location button"). */
  #tw-plan-card {
    background: rgba(var(--sv-base-rgb), 0.84);
    border-color: rgba(var(--sv-accent-rgb), 0.18);
    box-shadow: 0 12px 60px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(var(--sv-accent-rgb), 0.06) inset;
    backdrop-filter: blur(24px) saturate(170%);
    -webkit-backdrop-filter: blur(24px) saturate(170%);
  }
  #tw-plan-body { display: none; }
  .tw-plan-modal-img { max-height: none; }
}
