/* guest.css — all styling for the public world-guest surface.
   CSP forbids inline styles: every rule ships here.

   PALETTE LAW. Only the five brand tokens plus neutrals (black, white,
   transparent, currentcolor, inherit) appear in this file. Tints and shades
   only: a brand token may be color-mixed with a NEUTRAL, never with another
   brand token — blending two of the five manufactures a sixth hue, which is
   exactly what the contract forbids. The private app's night/parchment/muted
   tokens deliberately do not cross: grounds here are black with alpha and ink
   is white with alpha.

   MAP DOMINANCE. The map pane is full-bleed; all four chrome pieces are
   position:fixed above it. Nothing here may inset, reflow, or crop the map. */

:root {
  color-scheme: dark;

  --glaucous: #59789F;
  --old-moss-green: #7F8330;
  --antique-ruby: #B32850;
  --cherry-blossom: #E9ABAE;
  --jasper-orange: #DF8A4D;

  --ink: #FFFFFF;
  --ink-2: color-mix(in srgb, #FFFFFF 78%, transparent);
  --ink-3: color-mix(in srgb, #FFFFFF 62%, transparent);
  --ground: color-mix(in srgb, #000000 90%, transparent);
  --ground-bar: linear-gradient(180deg, color-mix(in srgb, #000000 92%, transparent), color-mix(in srgb, #000000 66%, transparent));
  --hairline: color-mix(in srgb, var(--glaucous) 34%, transparent);
  --shadow: 0 18px 60px color-mix(in srgb, #000000 55%, transparent);

  --bar-h: 72px;
  --rail-h: 72px;
  --pad: 24px;
  --about-w: min(520px, 42vw);

  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", Inter, system-ui, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #000000;
  color: var(--ink);
  font: 15px/1.45 var(--sans);
  -webkit-font-smoothing: antialiased;
}

#world-root { position: fixed; inset: 0; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

button, input { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: 0; }
a { color: inherit; text-decoration: none; }
:where(button, a, input):focus-visible { outline: 2px solid var(--cherry-blossom); outline-offset: 3px; }

.ico {
  width: 20px; height: 20px; flex: none;
  fill: none; stroke: currentcolor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ─────────────────────────  boot (pre-shell)  ──────────────────────── */
.boot-message, .boot-failure {
  position: fixed; z-index: 100;
  inset: 50% auto auto 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  text-align: center;
}
.boot-message { font: 400 22px/1.2 var(--serif); color: var(--ink-2); }
.boot-failure {
  width: min(440px, calc(100vw - 40px));
  padding: 24px;
  border: 1px solid var(--hairline);
  border-radius: 16px;
  background: var(--ground);
  box-shadow: var(--shadow);
}
.boot-failure h1 { margin: 0 0 8px; font: 400 26px/1.1 var(--serif); }
.boot-failure p { color: var(--ink-3); }
.boot-failure button {
  min-height: 40px; padding: 0 16px; border-radius: 10px;
  border: 1px solid var(--jasper-orange); color: var(--jasper-orange);
}

/* ───────────────────────────  the map  ─────────────────────────────── */
/* Full-bleed. Nothing below is allowed to inset, reflow, or crop it. */
.mappane {
  position: fixed;
  inset: 0;
  width: 100dvw;
  height: 100dvh;
  background: #000000;
}
.mappane[aria-hidden="true"] { visibility: hidden; }

/* ─────────────────────────  ① top bar  ─────────────────────────────── */
#topbar {
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--bar-h);
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 0 28px;
  background: var(--ground-bar);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--hairline);
  z-index: 30;
}

.wordmark {
  flex: none;
  font: 400 26px/1 var(--serif);
  letter-spacing: .01em;
  color: var(--ink);
}

/* world-runtime.js fills this with one <a> per world; .active is the current */
#world-toggle { display: flex; align-items: center; gap: 6px; flex: none; }
#world-toggle a {
  display: inline-flex; align-items: center; gap: 8px;
  height: 36px; padding: 0 14px;
  border: 1px solid transparent;
  border-radius: 10px;
  font: 600 14px/1 var(--sans);
  color: var(--ink-3);
}
#world-toggle a span { font-size: 15px; line-height: 1; }
#world-toggle a:hover { color: var(--ink); }
#world-toggle a.active {
  border-color: var(--jasper-orange);
  background: color-mix(in srgb, var(--jasper-orange) 12%, transparent);
  color: var(--jasper-orange);
}

.search {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  width: min(420px, 34vw);
  height: 40px;
  margin: 0 auto;
  padding: 0 14px;
  border: 1px solid color-mix(in srgb, var(--glaucous) 30%, transparent);
  border-radius: 10px;
  background: color-mix(in srgb, #000000 55%, transparent);
  color: var(--ink-3);
}
.search:focus-within { border-color: var(--glaucous); }
/* the ring belongs on the field, not the bare input, so it frames the whole
   control; :has keeps it keyboard-only, matching every other focus ring here */
.search:has(input:focus-visible) {
  outline: 2px solid var(--cherry-blossom);
  outline-offset: 2px;
}
.search input {
  flex: 1 1 auto; min-width: 0; height: 100%;
  border: 0; background: none;
  color: var(--ink);
  font: 400 15px/1 var(--sans);
}
.search input::placeholder { color: color-mix(in srgb, #FFFFFF 60%, transparent); }
.search input:focus { outline: 0; }
.search input::-webkit-search-decoration,
.search input::-webkit-search-cancel-button { -webkit-appearance: none; }

.search-scopes {
  display: inline-flex;
  flex: 0 0 auto;
  gap: 5px;
}
.search-scopes:empty { display: none; }
.search-scope {
  min-height: 26px;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 0 8px;
  border: 1px solid color-mix(in srgb, var(--jasper-orange) 48%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--jasper-orange) 14%, transparent);
  color: var(--ink);
  font: 650 11px/1 var(--sans);
  white-space: nowrap;
}
.search-scope::after {
  content: "\00d7";
  color: color-mix(in srgb, #FFFFFF 58%, transparent);
  font-size: 14px;
}
.search-scope:hover::after { color: var(--ink); }

#guest-search-results {
  position: absolute;
  top: calc(100% + 8px); left: 0; right: 0;
  max-height: 46dvh;
  overflow-y: auto;
  padding: 6px;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: color-mix(in srgb, #000000 94%, transparent);
  backdrop-filter: blur(14px);
  box-shadow: var(--shadow);
}
#guest-search-results[hidden] { display: none; }
#guest-search-results button {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: 10px;
  width: 100%; padding: 9px 10px;
  border-radius: 8px;
  text-align: left;
  color: var(--ink-2);
}
#guest-search-results button span { min-width: 0; }
#guest-search-results button strong {
  display: block;
  overflow: hidden;
  color: inherit;
  font: 600 14px/1.25 var(--sans);
  text-overflow: ellipsis;
  white-space: nowrap;
}
#guest-search-results button small {
  display: block;
  overflow: hidden;
  margin-top: 2px;
  color: color-mix(in srgb, #FFFFFF 52%, transparent);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#guest-search-results button em {
  color: var(--jasper-orange);
  font: 700 9px/1 var(--sans);
  font-style: normal;
  letter-spacing: .09em;
  text-transform: uppercase;
  white-space: nowrap;
}
#guest-search-results button[data-row-type="scope"] em { color: var(--cherry-blossom); }
#guest-search-results button:hover,
#guest-search-results button[aria-selected="true"] {
  background: color-mix(in srgb, var(--glaucous) 26%, transparent);
  color: var(--ink);
}
#guest-search-results .empty { padding: 10px; color: var(--ink-3); font-size: 14px; }

/* the single entry affordance — this surface has no sign-in */
.enter {
  display: inline-flex; align-items: center; gap: 9px;
  flex: none; margin-left: auto;
  font: 600 15px/1 var(--sans);
  color: var(--cherry-blossom);              /* cherry carries the text … */
}
.enter .ico { color: var(--antique-ruby); }  /* … ruby carries the identity glyph */
.enter:hover { color: var(--ink); }

/* ────────────────────  ② welcome cartouche  ────────────────────────── */
#welcome {
  position: fixed;
  top: calc(var(--bar-h) + 28px);
  left: var(--pad);
  width: 272px;
  padding: 26px 18px 18px;
  border-radius: 4px;
  background: var(--ground);
  text-align: center;
  box-shadow: var(--shadow);
  z-index: 20;
}
/* double rule with 45° cut corners — the one ornament on the surface */
#welcome::before,
#welcome::after {
  content: "";
  position: absolute;
  border: 1px solid color-mix(in srgb, var(--jasper-orange) 46%, transparent);
  clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
  pointer-events: none;
}
#welcome::before { inset: 0; }
#welcome::after { inset: 6px; border-color: color-mix(in srgb, var(--jasper-orange) 30%, transparent); }

#welcome .rosette {
  position: absolute; top: -13px; left: 50%;
  width: 26px; height: 26px; margin-left: -13px;
  fill: #000000; stroke: var(--jasper-orange); stroke-width: 1.3; stroke-linejoin: round;
  z-index: 1;
}
#welcome .dismiss {
  position: absolute; top: 6px; right: 6px;
  width: 26px; height: 26px;
  display: grid; place-items: center;
  color: color-mix(in srgb, #FFFFFF 55%, transparent);
  z-index: 1;
}
#welcome .dismiss:hover { color: var(--ink); }
#welcome .dismiss svg {
  width: 12px; height: 12px;
  fill: none; stroke: currentcolor; stroke-width: 1.8; stroke-linecap: round;
}
.welcome-copy {
  margin: 4px 0 16px;
  font: 400 15px/1.4 var(--serif);
  color: var(--ink);
  text-wrap: balance;
}
.tour {
  width: 100%; height: 36px;
  border: 1px solid var(--jasper-orange);
  border-radius: 8px;
  background: transparent;
  color: var(--jasper-orange);
  font: 700 15px/1 var(--sans);
  transition: background-color .14s ease;
}
.tour:hover { background: color-mix(in srgb, var(--jasper-orange) 16%, transparent); }
.tour:active { background: color-mix(in srgb, var(--jasper-orange) 26%, transparent); }

body.welcome-dismissed #welcome { display: none; }

/* the tour steps inside the cartouche rather than beside it */
.tour-step { display: none; text-align: left; }
#welcome.is-touring .welcome-copy,
#welcome.is-touring .tour { display: none; }
#welcome.is-touring .tour-step { display: block; }
.tour-count {
  margin: 0 0 6px;
  font: 700 11px/1 var(--sans);
  letter-spacing: .14em;
  color: var(--jasper-orange);
}
.tour-title { margin: 0 0 6px; font: 400 18px/1.2 var(--serif); color: var(--ink); }
.tour-body { margin: 0 0 14px; font: 400 14px/1.5 var(--sans); color: var(--ink-2); }
.tour-nav { display: flex; gap: 8px; }
.tour-nav button {
  flex: 1;
  height: 34px;
  border: 1px solid color-mix(in srgb, var(--jasper-orange) 55%, transparent);
  border-radius: 8px;
  color: var(--jasper-orange);
  font: 600 14px/1 var(--sans);
}
.tour-nav button:hover:not(:disabled) { background: color-mix(in srgb, var(--jasper-orange) 16%, transparent); }
.tour-nav button:disabled { opacity: .4; cursor: default; }
.tour-nav .tour-next {
  border-color: var(--jasper-orange);
  background: color-mix(in srgb, var(--jasper-orange) 18%, transparent);
}

/* ────────────────────────  ④ map navigation  ───────────────────────── */
#map-navigation {
  position: fixed;
  top: calc(var(--bar-h) + var(--pad));
  right: var(--pad);
  width: 44px;
  display: flex; flex-direction: column;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--ground);
  overflow: hidden;
  box-shadow: var(--shadow);
  z-index: 20;
}
#map-navigation button {
  height: 40px;
  display: grid; place-items: center;
  color: color-mix(in srgb, #FFFFFF 80%, transparent);
  font: 400 20px/1 var(--sans);
}
#map-navigation button + button { border-top: 1px solid color-mix(in srgb, var(--glaucous) 22%, transparent); }
/* the About sheet slides in from the right; the controls step aside for it
   rather than being covered, so the map stays operable while you read */
#map-navigation { transition: transform .18s cubic-bezier(.2, .7, .2, 1); }
body.about-open #map-navigation,
body.directory-open #map-navigation { transform: translateX(calc(-1 * var(--about-w) - 12px)); }
#map-navigation button:hover { background: color-mix(in srgb, #FFFFFF 8%, transparent); color: var(--ink); }

/* ── imagery attribution (Earth) ────────────────────────────────────────
   The runtime appends this row of licence links to the map container. With no
   rule for it, static flow put it at the top of the pane — underneath the
   opaque top bar, so it computed as visible while nothing could be read or
   clicked. The imagery licences require the credit to be *seen*, so anchor it
   to the bottom-right, above the map's own attribution row, and step it aside
   for the About sheet exactly as the zoom stack does. */
.guest-earth-credit {
  position: absolute;
  right: 10px;
  bottom: 26px;
  z-index: 5;
  margin: 0;
  font: 500 11px/1.4 var(--sans);
  color: color-mix(in srgb, #FFFFFF 62%, transparent);
  text-shadow: 0 1px 2px #000000;
  transition: transform .18s cubic-bezier(.2, .7, .2, 1);
}
.guest-earth-credit a {
  color: color-mix(in srgb, #FFFFFF 82%, transparent);
  text-decoration: underline;
}
.guest-earth-credit a:hover { color: var(--ink); }
body.about-open .guest-earth-credit,
body.directory-open .guest-earth-credit { transform: translateX(calc(-1 * var(--about-w) - 12px)); }

/* the renderer's own attribution control sits in the same corner and is the
   other half of the same obligation — it steps aside for the sheet too */
.maplibregl-ctrl-bottom-right { transition: transform .18s cubic-bezier(.2, .7, .2, 1); }
body.about-open .maplibregl-ctrl-bottom-right,
body.directory-open .maplibregl-ctrl-bottom-right { transform: translateX(calc(-1 * var(--about-w) - 12px)); }

/* The control ships a pale vendor default meant for light basemaps. On a
   near-black surface that reads as a grey slab — tolerable tucked in a corner,
   not once it is lifted somewhere prominent on a phone. Same ground and same
   muted ink as the credit row beneath it, so the two read as one block.
   Two class names on purpose: the vendor sets this as
   `.maplibregl-ctrl.maplibregl-ctrl-attrib`, so a single-class rule loses to it
   even though guest.css loads second. */
.maplibregl-ctrl.maplibregl-ctrl-attrib {
  background-color: color-mix(in srgb, #000000 72%, transparent);
  border-radius: 4px;
  color: color-mix(in srgb, #FFFFFF 62%, transparent);
  font: 500 11px/1.4 var(--sans);
  padding: 2px 6px;
}
.maplibregl-ctrl.maplibregl-ctrl-attrib a { color: color-mix(in srgb, #FFFFFF 82%, transparent); }
.maplibregl-ctrl.maplibregl-ctrl-attrib a:hover { color: var(--ink); }

/* world-runtime.js sets --bearing on .compass-dial via CSSOM (not an inline
   style attribute in the shipped HTML, so the CSP is satisfied). */
.compass-dial {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  transform: rotate(var(--bearing, 0deg));
  transition: transform .2s ease;
}
.compass-dial i {
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 7px solid var(--antique-ruby);
}
.compass-dial b { font: 700 10px/1 var(--sans); letter-spacing: .06em; }

/* ─────────────────────────  ③ bottom rail  ─────────────────────────── */
#rail {
  position: fixed;
  left: 0; bottom: 0;
  height: var(--rail-h);
  display: flex; align-items: center; gap: 28px;
  padding: 0 32px 0 var(--pad);
  background: var(--ground-bar);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--hairline);
  border-right: 1px solid var(--hairline);
  border-radius: 0 20px 0 0;
  z-index: 30;
}
#rail button {
  position: relative;
  height: 100%;
  display: inline-flex; align-items: center; gap: 10px;
  font: 400 15px/1 var(--sans);
  color: color-mix(in srgb, #FFFFFF 74%, transparent);
}
#rail button .ico { width: 22px; height: 22px; }
#rail button:hover { color: var(--ink); }
#rail button.is-active { color: var(--cherry-blossom); font-weight: 600; }
#rail button.is-active::after {
  content: "";
  position: absolute;
  left: 32px; right: 0; bottom: 18px;
  height: 2px;
  background: var(--antique-ruby);
}

/* ───────────────────── company and atlas ledgers ───────────────────── */
#scrim {
  position: fixed; inset: 0;
  background: color-mix(in srgb, #000000 34%, transparent);
  z-index: 35;
}
#scrim[hidden] { display: none; }

#about,
#directory {
  position: fixed;
  top: var(--bar-h); right: 0; bottom: 0;
  width: var(--about-w);
  display: flex; flex-direction: column;
  border-left: 1px solid color-mix(in srgb, var(--glaucous) 40%, transparent);
  background: color-mix(in srgb, #000000 94%, transparent);
  backdrop-filter: blur(18px);
  box-shadow: var(--shadow);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform .18s cubic-bezier(.2, .7, .2, 1);
  z-index: 40;
}
#about.is-open,
#directory.is-open { transform: none; visibility: visible; }

#about .handle,
#directory .handle { display: none; }
#about .close,
#directory .close {
  position: absolute; top: 10px; right: 10px;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  color: color-mix(in srgb, #FFFFFF 66%, transparent);
}
#about .close:hover,
#directory .close:hover { color: var(--ink); }
#about .close svg,
#directory .close svg {
  width: 15px; height: 15px;
  fill: none; stroke: currentcolor; stroke-width: 1.8; stroke-linecap: round;
}

.about-scroll,
.directory-scroll { overflow-y: auto; overflow-x: hidden; padding: 40px 40px 32px; }
.eyebrow {
  margin: 0 0 10px;
  font: 700 11px/1 var(--sans);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--jasper-orange);
}
#about h1,
#directory h1 { margin: 0 0 14px; font: 400 30px/1.15 var(--serif); color: var(--ink); }
.about-body .lede { margin: 0 0 22px; font: 400 17px/1.5 var(--sans); color: var(--ink-2); max-width: 62ch; }
.about-body h2 {
  margin: 24px 0 8px;
  padding-left: 12px;
  border-left: 8px solid var(--glaucous);
  font: 400 19px/1.25 var(--serif);
  color: var(--ink);
}
.about-body p {
  margin: 0 0 12px;
  max-width: 62ch;
  font: 400 15px/1.6 var(--sans);
  color: color-mix(in srgb, #FFFFFF 72%, transparent);
}
.directory-intro {
  margin: 0 0 20px;
  max-width: 56ch;
  color: var(--ink-2);
  font: 400 15px/1.55 var(--sans);
}
.directory-body {
  border-top: 1px solid color-mix(in srgb, var(--glaucous) 26%, transparent);
}
.directory-row {
  width: 100%;
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  align-items: center;
  gap: 11px;
  padding: 13px 2px;
  border-bottom: 1px solid color-mix(in srgb, var(--glaucous) 20%, transparent);
  color: var(--ink-2);
  text-align: left;
}
.directory-row:hover { color: var(--ink); }
.directory-row:focus-visible { outline: 2px solid var(--cherry-blossom); outline-offset: 2px; }
.directory-mark {
  width: 10px; height: 10px;
  justify-self: center;
  border: 1px solid color-mix(in srgb, currentcolor 55%, transparent);
  border-radius: 50%;
  background: var(--row-accent, var(--glaucous));
  box-shadow: 0 0 12px color-mix(in srgb, var(--row-accent, var(--glaucous)) 35%, transparent);
}
.directory-copy { min-width: 0; }
.directory-copy strong {
  display: block;
  color: var(--ink);
  font: 500 16px/1.2 var(--serif);
}
.directory-copy small {
  display: block;
  margin-top: 4px;
  color: color-mix(in srgb, #FFFFFF 58%, transparent);
  font: 400 12px/1.4 var(--sans);
}
.directory-kind {
  color: var(--jasper-orange);
  font: 700 9px/1 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
}
.ribbon { display: flex; height: 4px; margin-top: 30px; border-radius: 2px; overflow: hidden; }
.ribbon i { flex: 1; }
.ribbon i:nth-child(1) { background: var(--glaucous); }
.ribbon i:nth-child(2) { background: var(--old-moss-green); }
.ribbon i:nth-child(3) { background: var(--antique-ruby); }
.ribbon i:nth-child(4) { background: var(--cherry-blossom); }
.ribbon i:nth-child(5) { background: var(--jasper-orange); }

/* A resident opens in a small map cartouche, never a profile page or roster.
   The five-color line ties it to the same world grammar as the ledgers. */
.guest-agent-detail {
  position: fixed;
  z-index: 45;
  left: var(--pad);
  bottom: calc(var(--rail-h) + env(safe-area-inset-bottom) + 22px);
  width: min(340px, calc(100vw - 2 * var(--pad)));
  padding: 25px 24px 22px;
  border: 1px solid color-mix(in srgb, var(--glaucous) 42%, transparent);
  border-radius: 16px;
  background:
    linear-gradient(90deg,
      var(--glaucous) 0 20%,
      var(--old-moss-green) 20% 40%,
      var(--antique-ruby) 40% 60%,
      var(--cherry-blossom) 60% 80%,
      var(--jasper-orange) 80% 100%) top / 100% 4px no-repeat,
    color-mix(in srgb, #000000 94%, transparent);
  box-shadow: var(--shadow);
  backdrop-filter: blur(18px);
}
.guest-agent-detail-close {
  position: absolute;
  top: 13px; right: 14px;
  color: color-mix(in srgb, #FFFFFF 66%, transparent);
  font: 650 11px/1 var(--sans);
}
.guest-agent-detail-close:hover { color: var(--ink); }
.guest-agent-detail-eyebrow {
  margin: 0 0 8px;
  color: var(--jasper-orange);
  font: 700 10px/1 var(--sans);
  letter-spacing: .13em;
  text-transform: uppercase;
}
.guest-agent-detail h2 {
  margin: 0;
  color: var(--ink);
  font: 400 27px/1.15 var(--serif);
}
.guest-agent-detail-role {
  margin: 5px 0 14px;
  color: var(--cherry-blossom);
  font: 650 12px/1.2 var(--sans);
}
.guest-agent-detail-blurb {
  margin: 0;
  color: var(--ink-2);
  font: 400 14px/1.55 var(--sans);
}

/* ──────────────────────────  responsive  ───────────────────────────── */
@media (max-width: 1279px) {
  .search { width: min(340px, 30vw); }
}

@media (max-width: 1079px) {
  :root { --about-w: min(460px, 62vw); }
}

@media (max-width: 1023px) {
  :root { --bar-h: 64px; }
  #topbar { gap: 14px; padding: 0 20px; }
  .search { width: 40px; padding: 0; margin: 0 0 0 auto; justify-content: center; }
  .search input,
  .search .search-scopes { display: none; }
  body.search-open .search { width: min(320px, 52vw); padding: 0 14px; justify-content: flex-start; }
  body.search-open .search input { display: block; }
  body.search-open .search .search-scopes:not(:empty) { display: inline-flex; }
  .enter { margin-left: 18px; }
  #welcome { width: 240px; }
  #rail { gap: 20px; }
}

@media (max-width: 767px) {
  :root { --bar-h: 56px; --rail-h: 60px; --pad: 16px; }
  #topbar { gap: 10px; padding: 0 14px; }

  /* Phone only. The bar cannot hold the wordmark, the world toggle, the entry
     affordance AND an open search field at once: measured at 390, the expanded
     field and its results panel ran 113px past the right edge. While search is
     open the bar belongs to search and the rest yields — they return the moment
     it closes, because this is keyed on the same state class. The results panel
     is anchored to the field (left:0 right:0), so sizing the field to the padded
     bar contains both. Tablet and desktop are untouched; both measured 0px
     overflow at 768, 1023, 1024 and 1440. */
  body.search-open .wordmark,
  body.search-open #world-toggle,
  body.search-open .enter { display: none; }
  body.search-open .search {
    width: auto;
    flex: 1 1 auto;
    margin: 0;
  }
  .wordmark { font-size: 21px; }
  #world-toggle a { height: 32px; padding: 0 10px; }

  /* the cartouche leaves the top-left: that is where a thumb pans */
  #welcome {
    top: auto;
    bottom: calc(var(--rail-h) + env(safe-area-inset-bottom) + 16px);
    left: 16px; right: 16px;
    width: auto;
    padding: 20px 16px 14px;
  }
  .welcome-copy { font-size: 15px; margin-bottom: 12px; }
  .tour { height: 34px; }

  #map-navigation { top: calc(var(--bar-h) + 12px); right: 12px; }
  body.about-open #map-navigation,
  body.directory-open #map-navigation { transform: none; }

  /* Attribution on a phone. There are TWO rows and both are obligations: the
     renderer's own control carries the provider text on both worlds, and on
     Earth a second runtime row carries the required outbound links. Lifting
     only the control would trade an occluded control for occluded links and
     still read green on a check that samples the control alone.

     The whole bottom edge is spoken for — full-width rail, the cartouche
     directly above it, About arriving as a bottom sheet. Measured at 390x844
     the only region no overlay uses is under the bar on the left, opposite the
     zoom stack (which starts at x=334). Both rows go there, stacked. */
  .maplibregl-ctrl-bottom-right {
    top: calc(var(--bar-h) + 12px);
    left: 12px;
    right: auto;
    bottom: auto;
  }

  /* (the phone gate-caption override lives after the .portal-caption base
     rule, further down — placed here it lost the cascade to it) */
  .guest-earth-credit {
    top: calc(var(--bar-h) + 38px);
    left: 12px;
    right: auto;
    bottom: auto;
  }
  /* the sheet is a bottom sheet here, so nothing steps sideways for it —
     without these the desktop translate carries both rows off the left edge */
  body.about-open .guest-earth-credit,
  body.about-open .maplibregl-ctrl-bottom-right,
  body.directory-open .guest-earth-credit,
  body.directory-open .maplibregl-ctrl-bottom-right { transform: none; }

  /* A room interior fills a phone screen. Leaving the cartouche and the zoom
     stack showing underneath is clutter competing with the room, and the zoom
     control is unusable while a room is open. */
  body:has(.guest-interior) #welcome,
  body:has(.guest-interior) #map-navigation,
  body:has(.guest-agent-detail) #welcome { display: none; }

  #rail {
    right: 0;
    height: calc(var(--rail-h) + env(safe-area-inset-bottom));
    padding: 0 4px env(safe-area-inset-bottom);
    gap: 0;
    border-right: 0;
    border-radius: 0;
    justify-content: space-around;
  }
  #rail button { flex-direction: column; gap: 4px; height: var(--rail-h); font-size: 11px; }
  #rail button .ico { width: 20px; height: 20px; }
  #rail button.is-active::after { left: 50%; right: auto; bottom: 8px; width: 22px; margin-left: -11px; }

  /* About becomes a bottom sheet */
  #about,
  #directory {
    top: auto; left: 0; right: 0; bottom: 0;
    width: auto;
    /* Leave a real gap below the 122px map-nav stack. At 390×844 the old
       78dvh sheet began 4px before the stack ended. */
    max-height: 76dvh;
    border-left: 0;
    border-top: 1px solid color-mix(in srgb, var(--glaucous) 40%, transparent);
    border-radius: 20px 20px 0 0;
    transform: translateY(100%);
  }
  #about .handle,
  #directory .handle {
    display: block;
    width: 38px; height: 4px;
    margin: 10px auto 0;
    border-radius: 2px;
    background: color-mix(in srgb, #FFFFFF 30%, transparent);
  }
  .about-scroll,
  .directory-scroll { padding: 18px 22px calc(24px + env(safe-area-inset-bottom)); }
  #about h1,
  #directory h1 { font-size: 25px; }
  .about-body .lede { font-size: 16px; }
  #scrim { background: color-mix(in srgb, #000000 46%, transparent); }

  .guest-agent-detail {
    left: 16px; right: 16px;
    width: auto;
    bottom: calc(var(--rail-h) + env(safe-area-inset-bottom) + 16px);
  }
}

@media (max-width: 420px) {
  .enter span { display: none; }
}

/* Global, not per-component: a probe element or any future rule must obey it
   too. !important because a reduced-motion request outranks any local style. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
}

/* ═══════════════════  map furniture — pins, cast, interiors  ═══════════
   The markup is the adapter's; these rules are the look. Two structural
   rules it already honours and this file must not undo: the label is the
   FIRST child so it renders above its marker, and labels drop a zoom step
   before markers do. Never set `display` on a label — the adapter toggles
   display inline to drop labels, and fighting it breaks that behaviour.
   Style visibility / opacity / transform freely. */

.guest-place-pin,
.guest-cast-marker {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
.guest-cast-marker { position: relative; }
.guest-place-pin.is-hidden,
.guest-cast-marker.is-hidden { visibility: hidden; opacity: 0; }

/* the pill that names a thing — one grammar for places and for people */
.guest-place-label,
.guest-cast-name {
  padding: 3px 8px;
  border-radius: 6px;
  background: color-mix(in srgb, #000000 82%, transparent);
  color: var(--ink);
  white-space: nowrap;
  text-shadow: 0 1px 2px #000000;
}
.guest-place-label {
  border: 1px solid color-mix(in srgb, var(--jasper-orange) 38%, transparent);
  font: 600 13px/1.2 var(--sans);
  transform: translateX(var(--guest-label-shift-x, 0px));
}
/* a person reads distinct from a place at a glance: ruby hairline, smaller */
.guest-cast-name {
  position: absolute;
  top: calc(100% + 4px);
  left: 50%;
  border: 1px solid color-mix(in srgb, var(--antique-ruby) 55%, transparent);
  font: 600 12px/1.2 var(--sans);
  transform: translateX(calc(-50% + var(--guest-label-shift-x, 0px)));
}

/* the teardrop, keylined in black so it survives light terrain */
.guest-place-mark {
  width: 14px;
  height: 14px;
  border: 1px solid #000000;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  background: var(--jasper-orange);
  box-shadow: 0 2px 6px color-mix(in srgb, #000000 55%, transparent);
}
.guest-place-pin:hover .guest-place-mark,
.guest-place-pin:focus-visible .guest-place-mark { background: var(--cherry-blossom); }

/* one 32x32 window onto a 96x128 twelve-frame walk sheet. Do not declare
   background-position here — the adapter owns the frame choice. */
.guest-cast-sprite {
  width: 32px;
  height: 32px;
  background-repeat: no-repeat;
  image-rendering: pixelated;
  filter: drop-shadow(0 2px 3px color-mix(in srgb, #000000 60%, transparent));
}
.guest-cast-role {
  font: 500 10px/1 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: color-mix(in srgb, #FFFFFF 66%, transparent);
  text-shadow: 0 1px 2px #000000;
}

/* ── a gate between worlds ──────────────────────────────────────────────
   The world runtime builds these markers as
     .portal.world-portal > .ring + .glyph + .portal-caption > strong + small
   and styles them in the private app's own stylesheet, which this artifact
   deliberately does not ship. Without the rules below every child computes
   display:inline and the marker renders as one run-together string in the
   default UA font — on the opening scene of both worlds.

   Third role, third accent: a place is jasper, a resident is ruby, a gate is
   glaucous — the sea a crossing goes over. Same pill grammar as the other two
   so the map keeps one vocabulary. Stacking the name over the caption is what
   contains it: side by side the string ran ~200px and clipped the phone
   viewport; stacked it is the width of its longest line. */
.world-portal {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}
/* the runtime emits an empty decorative span; the glyph is the mark */
.world-portal .ring { display: none; }
.world-portal .glyph {
  font-size: 13px;
  line-height: 1;
  color: var(--glaucous);
  text-shadow: 0 1px 3px #000000;
}
.portal-caption {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 8px;
  border: 1px solid color-mix(in srgb, var(--glaucous) 60%, transparent);
  border-radius: 6px;
  background: color-mix(in srgb, #000000 82%, transparent);
  white-space: nowrap;
  text-shadow: 0 1px 2px #000000;
}
.portal-caption strong {
  font: 600 12px/1.2 var(--sans);
  color: var(--ink);
}
.portal-caption small {
  font: 500 10px/1 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: color-mix(in srgb, #FFFFFF 66%, transparent);
}

/* A gate sits where its geography puts it, so near a viewport edge it will
   overhang. Letting the caption wrap costs a line and buys back the width that
   pushed it off a 390px screen. This block MUST come after the base rule: an
   earlier copy of it lost the cascade to `white-space: nowrap` above at equal
   specificity, so max-width applied, wrapping did not, and the caption clipped
   mid-word while every width number still read inside budget. */
@media (max-width: 767px) {
  .portal-caption {
    max-width: 104px;
    white-space: normal;
  }
  .portal-caption small { line-height: 1.25; }
}

/* ── a room interior, opened from a pin ─────────────────────────────── */
.guest-interior {
  position: fixed;
  top: calc(var(--bar-h) + var(--pad));
  left: 50%;
  transform: translateX(-50%);
  width: min(720px, calc(100vw - 2 * var(--pad)));
  max-height: calc(100dvh - var(--bar-h) - var(--rail-h) - 2 * var(--pad));
  overflow: hidden;
  display: flex;
  flex-direction: column;
  border: 1px solid color-mix(in srgb, var(--room-accent, var(--jasper-orange)) 46%, transparent);
  border-radius: 12px;
  background: color-mix(in srgb, #000000 94%, transparent);
  box-shadow: var(--shadow);
  z-index: 25;
}
.guest-interior-title {
  margin: 0;
  padding: 13px 18px 4px;
  border-top: 1px solid var(--hairline);
  font: 400 20px/1.15 var(--serif);
  color: var(--ink);
}
.guest-interior-art {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
.guest-interior-close {
  position: absolute;
  top: 8px;
  right: 8px;
  min-width: 40px;
  min-height: 32px;
  padding: 0 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid color-mix(in srgb, #FFFFFF 22%, transparent);
  border-radius: 8px;
  background: color-mix(in srgb, #000000 62%, transparent);
  backdrop-filter: blur(6px);
  color: color-mix(in srgb, #FFFFFF 82%, transparent);
  font: 600 13px/1 var(--sans);
}
.guest-interior-close:hover { color: var(--ink); background: color-mix(in srgb, #FFFFFF 10%, transparent); }

/* the app hotspots painted over the interior art */
/* The hotspot layer must cover exactly the art box, because the pins inside it
   are positioned by percentage. Two forms, so the rule is correct before and
   after the adapter wraps art+layer in a shrink-wrapping frame:

   INTERIM — layer is a bare sibling of the art, so it needs an explicit ratio.
   1200/896 is the real interior-art ratio (26 of 27 files), not 4/3; the one
   exception is library at true 4:3, ~2px of drift on a 718px card.
   DESTINATION — inside a frame, inset:0 needs no ratio at all and no room can
   ever drift, whatever size the art is reprojected at. */
.guest-app-layer {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  aspect-ratio: 1200 / 896;
  pointer-events: none;
}
.guest-interior-frame { position: relative; display: block; }
.guest-interior-frame > .guest-app-layer {
  inset: 0;
  aspect-ratio: auto;
}
.guest-app-pin {
  position: absolute;
  /* the inline left/top percentage is the hotspot's CENTRE, not its corner */
  transform: translate(-50%, -50%);
  pointer-events: auto;
  min-width: 0;
  min-height: 24px;
  padding: 3px 5px 3px 3px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  justify-content: center;
  white-space: nowrap;
  border: 1px solid color-mix(in srgb, #FFFFFF 34%, transparent);
  border-radius: 2px;
  background: color-mix(in srgb, #000000 78%, transparent);
  color: var(--ink);
  font: 700 10px/1 var(--sans);
  letter-spacing: .07em;
  text-transform: uppercase;
  text-shadow: 0 1px 2px #000000;
  box-shadow: 0 1px 5px color-mix(in srgb, #000000 58%, transparent);
}
.guest-app-route-mark {
  width: 8px;
  height: 8px;
  flex: none;
  border: 1px solid #000000;
  background: var(--room-accent, var(--jasper-orange));
  box-shadow: 0 0 0 1px color-mix(in srgb, #FFFFFF 46%, transparent);
  transform: rotate(45deg);
}
.guest-app-pin:hover,
.guest-app-pin:focus-visible,
.guest-app-pin[aria-expanded="true"] {
  border-color: var(--cherry-blossom);
  background: color-mix(in srgb, #000000 88%, transparent);
  color: var(--cherry-blossom);
}
.guest-app-pin:hover .guest-app-route-mark,
.guest-app-pin:focus-visible .guest-app-route-mark,
.guest-app-pin[aria-expanded="true"] .guest-app-route-mark {
  background: var(--cherry-blossom);
}
.guest-app-route-note {
  margin: 0;
  padding: 0 18px 13px;
  color: color-mix(in srgb, #FFFFFF 62%, transparent);
  font: 600 11px/1.4 var(--sans);
  letter-spacing: .045em;
}
.guest-app-explain {
  padding: 12px 18px 16px;
  border-top: 1px solid var(--hairline);
}
.guest-app-explain-title {
  margin: 0 0 4px;
  font: 600 14px/1.2 var(--sans);
  color: var(--ink);
}
.guest-app-explain-body {
  margin: 0;
  max-width: 62ch;
  font: 400 14px/1.55 var(--sans);
  color: color-mix(in srgb, #FFFFFF 72%, transparent);
}

@media (max-width: 767px) {
  .guest-interior {
    top: calc(var(--bar-h) + 10px);
    width: calc(100vw - 20px);
  }
  .guest-interior-title { font-size: 18px; padding: 12px 48px 4px 14px; }
  .guest-app-route-note { padding: 0 14px 10px; font-size: 10px; }
  .guest-app-explain { padding: 10px 14px 14px; }
}
