/* styles/map.css — act map screen (mockup: docs/mockups/map-screen.svg) */

.mapscreen {
  flex: none; display: flex; flex-direction: column; overflow: hidden;
  /* Body is zoomed. A percentage-height screen is therefore only a zoomed
     fraction of the glass, leaving the apparent "footer" as empty page below
     it. This is the same viewport compensation used by the combat frame. */
  width: calc(100vw / var(--ui-zoom, 1));
  height: calc(var(--visible-height, 100dvh) / var(--ui-zoom, 1));
}
/* The box the scrollport and its map-only overlays share. The vignette and the
   compact zoom cluster are bounded by this frame, never by the viewport.
   `min-height: 0` is the flex-item clamp the scrollport needs in order to yield
   to the narrow footer and to the expanding location panel. */
.mapscreen .map-frame { position: relative; flex: 1; display: flex; min-height: 0; min-width: 0; }

.mapscreen .map-scroll {
  flex: 1; overflow: auto;
  /* The browser owns a finger or pen on the one axis the map can scroll.
     Mouse drag remains an explicit camera gesture in mapboard.js. */
  touch-action: pan-y pinch-zoom;
  cursor: grab; scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
.mapscreen .map-scroll[data-pan-axis='both'] {
  touch-action: none;
  overscroll-behavior: contain;
}
/* THE UNLIT GROUND IS A LANTERN, NOT A FIELD. Fog-only, decorative, and inert:
   it takes no pointer events, so it cannot repeat #28's untappable nodes, and
   tools/mapreach.mjs is what proves that. In px because a vignette must NOT
   track text size (Law 2 rule 3, Law 4 clause 3) — it is not a box that holds
   text, it is light. */
.mapscreen.map-fog .map-vignette {
  position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background: radial-gradient(ellipse 78% 58% at 50% 46%, rgba(0, 0, 0, 0) 42%, rgba(0, 0, 0, 0.72) 100%);
}
/* The reduced-motion class already means "less spectacle"; a heavy vignette is
   spectacle for anyone who finds the screen busy, so it lightens rather than
   disappears — the paper still needs an edge. */
.reduced-motion .mapscreen.map-fog .map-vignette {
  background: radial-gradient(ellipse 85% 68% at 50% 46%, rgba(0, 0, 0, 0) 55%, rgba(0, 0, 0, 0.45) 100%);
}
.mapscreen .map-scroll.grabbing { cursor: grabbing; }
.mapscreen .map-scroll::-webkit-scrollbar { width: 1.2rem; height: 1.2rem; }
.mapscreen .map-scroll::-webkit-scrollbar-thumb { background: var(--line); border-radius: var(--r-2); border: 3px solid transparent; background-clip: padding-box; }
.mapscreen .map-scroll::-webkit-scrollbar-thumb:hover { background: var(--gold); background-clip: padding-box; }
/* Canvas centers the (possibly smaller-than-viewport) svg horizontally. */
.mapscreen .map-canvas { display: flex; justify-content: center; min-width: min-content; min-height: min-content; }
.mapscreen svg { display: block; }

/* Cartography stays in graph coordinates while the camera moves above it.
   Only the paper extends into the camera's margins. Nodes retain their own
   opaque plates and hit targets over both light parchment and dark terrain. */
.map-terrain .map-fog-ground .terrain-paper { fill: #c4af85; }
.map-terrain .terrain-detail { opacity: 0.9; }
.mapscreen .map-scroll:has(.map-terrain) { background: #c4af85; }
.mapscreen svg:has(.map-terrain) .map-act-title {
  fill: #392613; stroke: #d8c49a; stroke-width: 2; paint-order: stroke;
}
.mapscreen svg:has(.map-terrain) .map-edge {
  stroke: #775125; stroke-opacity: 1; filter: drop-shadow(0 1px 1px #e2c994);
}
.mapscreen svg:has(.map-terrain) .map-edge.traveled { stroke: #b27c1f; }
.mapscreen svg:has(.map-terrain) .map-edge.shrine-lane { stroke: #dec46e; }
.mapscreen.map-fog .map-frame:has(.map-terrain) .map-vignette {
  background: radial-gradient(ellipse at center, transparent 65%, rgba(45, 30, 13, 0.2));
}

/* The map's action buttons belong to its footer flow; the map controls remain
   a separate cluster inside the map frame. Scoped here so other hint bars keep
   their screen-specific positioning. */
.mapscreen .hint-bar {
  position: static; transform: none; align-self: center;
  margin: 0 auto 0.6rem; max-width: 96%;
}

.map-node { cursor: default; }
.map-node circle { fill: var(--panel); stroke: var(--map-structure); stroke-width: 2; }
.map-node text { fill: #8a7a5a; font-size: 13px; text-anchor: middle; dominant-baseline: central; }
.map-node.elite circle { stroke: var(--blood); }
.map-node.elite text { fill: var(--ember); }
.map-node.boss circle { stroke: var(--blood); stroke-width: 3; }
.map-node.shrine text, .map-node.treasure text { fill: var(--gold); }
.map-node.merchant text { fill: var(--grace); }
.map-node.visited circle { stroke: var(--gold); }
/* WALKED NODES FADE, AND THE AMOUNT IS THE PLAYER'S — Constantine, D17 message
   4 (2026-08-08): "previous nodes shoudl be faded, maybe 50% saturation or
   higher (settings for customization)". The knob is Settings → Display →
   Walked nodes (`walkedFade`); main.js applyDisplaySettings writes the chosen
   value onto the root as data-walked-fade, and this ladder is its only reader.
   `half` is the shipping default because it is the number in his sentence.
   ON THE GROUP, not the circle: opacity+saturate on <g> takes the icon and the
   ring down together, and `:not(.current)` because the node under the player's
   feet also wears .visited and must never fade. Measured at 86564e6 before
   this rule existed: visited stroke was full-saturation gold at opacity 1 —
   the trail OUTSHONE the choice. Off restores exactly that look. */
:root[data-walked-fade='subtle'] .map-node.visited:not(.current) { opacity: 0.85; filter: saturate(0.75); }
:root[data-walked-fade='half'] .map-node.visited:not(.current) { opacity: 0.6; filter: saturate(0.5); }
:root[data-walked-fade='strong'] .map-node.visited:not(.current) { opacity: 0.4; filter: saturate(0.25); }
.map-node.current circle {
  stroke: var(--parchment); stroke-width: 3;
  /* FILLED, not just ringed — D17 message 4: "the current node should be white
     and colored in". Measured at 86564e6: fill was var(--panel), a dark disc
     with a white ring. The icon flips to the panel tone so it stays legible
     on the light disc. */
  fill: var(--parchment);
  animation: nodePulse 1.5s ease-in-out infinite alternate;
}
.map-node.current text { fill: var(--panel); }
@keyframes nodePulse { from { stroke-width: 3; } to { stroke-width: 5.5; } }
.map-node.reachable { cursor: pointer; }
.map-node.reachable > circle:not(.node-halo) {
  stroke: var(--gold); stroke-width: 2.5;
  animation: reachStroke 1.4s ease-in-out infinite alternate;
}
@keyframes reachStroke { from { stroke-opacity: 0.7; } to { stroke-opacity: 1; } }
/* rhythmic pulsing halo — the strong "go here next" cue.

   `--halo-peak` IS THE WIDEST THIS RING EVER PAINTS, and it is a custom property
   because the camera has to know it. The entrance frame pads its box by the
   halo's painted reach so the ring is not sliced by the screen edge, and it was
   wrong by exactly this factor on its first draft: I padded for scale 1 and
   tools/mapreach.mjs went red at 3 cells, because a node's rect is what it
   PAINTS and this one paints 35% wider than its own radius eight times a
   second. One home, two readers (the keyframe below, and mountMap). */
.mapscreen { --halo-peak: 1.35; }
/* THE MAP TRAY (owner, 2026-09-14; screens/map.js). One compact row takes layout
   height for the centred action buttons. Potions is independent in the map's
   far-right corner, so it never creates a footer container of its own.
   Picking a lit node slides the reveal up OVER the foot of the map: the node's
   context (a FIXED height, so a long blurb scrolls instead of growing) and Back
   / Enter, centred and held clear of the Potions column. Row + reveal stay
   within the bottom band this replaced. The height is written by map.js from
   the reveal's own content, so there is no second copy of it here. */
.mapscreen > .map-tray { position: relative; z-index: 30; flex: 0 0 auto; container: map-tray / inline-size; background: transparent; border: 0; }
.map-tray-row {
  /* A DEFINITE middle track, not auto: the hint bar is a kit ButtonRow whose
     width is min(100%, its steps), and a percentage against a content-sized
     column has nothing to resolve against — every chip collapsed to 12 px. */
  display: flex; align-items: center; justify-content: center; gap: 1rem;
  min-height: calc(var(--iconbtn-size) + 1rem);
  padding: .5rem 1.4rem calc(.5rem + env(safe-area-inset-bottom, 0px) / var(--ui-zoom, 1));
}
.map-frame > .map-zoom {
  position: absolute; z-index: 3; left: 1.4rem; bottom: 1.4rem;
  gap: .6rem; width: max-content; padding: 0; border: 0; background: none;
}
.mapscreen .map-tray-row > .hint-bar {
  margin: 0; width: min(100%, 54rem); max-width: 54rem;
}
/* POTIONS IS THE BIG CONTROL IN THIS CORNER (owner, 2026-09-17): 1.6x the
   touch row, and the minis stand ABOVE it rather than beside it. The host is
   the button's own box, so the minis hang off its top edge and the row's
   height is what holds it. */
.mapscreen { --map-potions-size: calc(var(--iconbtn-size) + 3.2rem); }
.map-frame > .map-potions {
  position: absolute; z-index: 4; right: 1.4rem; bottom: 1.4rem;
  width: var(--map-potions-size); height: var(--map-potions-size);
}
/* The row stays compact at every width; location details expand above it. */
@container map-tray (max-width: 104rem) {
  .map-tray-row { gap: .5rem; }
}
:root[data-layout='narrow'] .mapscreen .map-tray-row { display: none; }
/* Potions, as in combat's footer: a round control, its minis (the shared icon
   tray) beside it, --icon-tray-slots of them wide before the +N tile. */
.map-potions .run-potions-btn {
  position: absolute; inset: 0; width: 100%; height: 100%;
  min-width: 0; padding: 0; border-radius: 50%; font-size: 1.3rem; line-height: 1.1; letter-spacing: 0;
}
/* THE MINIS COME OUT OF THE BUTTON, and only for a pointer that stays (owner,
   2026-09-17). They keep their space while hidden, so the Potions button never
   moves out from under the cursor; they fade and slide from the button's edge
   after the player's OWN tooltip delay (--potion-reveal-delay, handed over by
   components/runPotions.js from the setting tooltips read) and go back with no
   wait at all. `visibility` keeps them out of the tab order until the corner
   has focus, and the button sits first in the DOM so a keyboard reaches them
   through it. Reduced motion keeps the reveal and drops the movement. */
.map-potions .run-potion-minis {
  visibility: hidden;
  position: absolute; left: 50%; bottom: calc(100% + 0.5rem); transform: translateX(-50%);
  transition: visibility 0s linear var(--potion-reveal-fade, 160ms);
  width: max-content; max-width: 40vw; justify-content: center; flex-wrap: nowrap;
}
/* NOTHING BEHIND THEM (owner, 2026-09-17): the flask's own art over the map
   rather than on a chip, with the count a small number ABOVE it rather than a
   pill on its corner. The pill's ground goes with the chip's; an ink shadow is
   what keeps a bare number readable on parchment. */
.map-potions .potion-mini { background: none; border-color: transparent; overflow: visible; }
.map-potions .potion-mini > .as-pill.round {
  left: 50%; right: auto; top: -1.15rem; bottom: auto; transform: translateX(-50%);
  min-width: 0; padding: 0; background: none; border: 0; border-radius: 0;
  font-size: 0.95rem; line-height: 1; color: var(--parchment);
  text-shadow: 0 1px 2px #000, 0 0 3px #000;
}
.map-potions:has(:focus-visible) .run-potion-minis {
  visibility: visible; transition-delay: var(--potion-focus-delay, 500ms);
}
.map-potions .run-potion-minis > * {
  opacity: 0; transform: translateY(0.7rem);
  transition: opacity var(--potion-reveal-fade, 160ms) ease, transform var(--potion-reveal-fade, 160ms) ease;
}
.map-potions:has(:focus-visible) .run-potion-minis > * { opacity: 1; transform: none; }
/* One after another, nearest the button first — the tray holds four at most. */
.map-potions:has(:focus-visible) .run-potion-minis > *:nth-last-child(1) { transition-delay: var(--potion-focus-delay, 500ms); }
.map-potions:has(:focus-visible) .run-potion-minis > *:nth-last-child(2) { transition-delay: calc(var(--potion-focus-delay, 500ms) + 40ms); }
.map-potions:has(:focus-visible) .run-potion-minis > *:nth-last-child(3) { transition-delay: calc(var(--potion-focus-delay, 500ms) + 80ms); }
.map-potions:has(:focus-visible) .run-potion-minis > *:nth-last-child(4) { transition-delay: calc(var(--potion-focus-delay, 500ms) + 120ms); }
/* Touch browsers may keep :hover latched after a tap. Only a device with an
   actual fine hover pointer gets the hover reveal; touch keeps the large
   Potions button as its direct, one-tap route to the full list. */
@media (hover: hover) and (pointer: fine) {
  .map-potions:hover .run-potion-minis { visibility: visible; transition-delay: var(--potion-reveal-delay, 500ms); }
  .map-potions:hover .run-potion-minis > * { opacity: 1; transform: none; }
  .map-potions:hover .run-potion-minis > *:nth-last-child(1) { transition-delay: var(--potion-reveal-delay, 500ms); }
  .map-potions:hover .run-potion-minis > *:nth-last-child(2) { transition-delay: calc(var(--potion-reveal-delay, 500ms) + 40ms); }
  .map-potions:hover .run-potion-minis > *:nth-last-child(3) { transition-delay: calc(var(--potion-reveal-delay, 500ms) + 80ms); }
  .map-potions:hover .run-potion-minis > *:nth-last-child(4) { transition-delay: calc(var(--potion-reveal-delay, 500ms) + 120ms); }
}
.reduced-motion .map-potions .run-potion-minis > * { transform: none; }

.map-tray-reveal {
  position: absolute; left: 0; right: 0; bottom: 100%; height: 0; overflow: hidden;
  display: flex; flex-direction: column; background: var(--panel); border-top: 0 solid var(--line);
  transition: height 240ms cubic-bezier(.2, .8, .2, 1);
}
.map-tray[data-open="true"] > .map-tray-reveal { border-top-width: 1px; }
.map-tray-reveal > * { opacity: 0; transition: opacity 180ms ease; }
.map-tray[data-shown="true"] > .map-tray-reveal > * { opacity: 1; }
.map-tray-reveal > .map-context { flex: 0 0 auto; box-sizing: border-box; height: min(calc(20dvh / var(--ui-zoom, 1)), 8rem); overflow-y: auto; display: grid; align-content: start; gap: .2rem; padding: .6rem 2.5vw 0; }
.map-tray-reveal > .map-context > * { margin: 0; }
.mapscreen .map-context-title { font: 600 1.5rem/1.2 var(--font-display); color: var(--parchment); }
.mapscreen .map-context-line { color: var(--muted); }
/* Back and Enter are centred on the SCREEN, not on the space left beside
   Potions: they take the row ABOVE it, so the clearance between them is
   vertical and a phone keeps two full-width buttons instead of two slivers. */
.map-tray-pair { flex: 0 0 auto; display: flex; justify-content: center; gap: .8rem; padding: .5rem 1.4rem; }
.map-tray-pair > .as-btn { flex: 0 1 18rem; min-width: 0; }
.map-node.selected > circle:not(.node-halo) { stroke: var(--parchment); stroke-width: 4; }
/* The pad cursor and hover both repaint the stroke; the glow keeps the
   selection visible under either. */
.map-node.selected { filter: drop-shadow(0 0 5px var(--parchment)); }
/* The selection glow is a node's only selection mark (owner, 2026-09-13).
   A press on a node also runs the tooltip's tap-to-select, which marks it
   `.tooltip-selected`; kit.css outlines that in gold, and the box sat beside
   the glow. An open tooltip's own quieter ring ([data-tip-open]) goes too.
   Keyboard and pad users keep the cursor: `.gp-focus` (ui.css) still rings
   the node and strokes its circle gold, and the reachable halo still pulses.
   `g.` is load-bearing: kit.css's `.tooltip-selected:not(.overhead-control)`
   is (0,2,0) and loads after this file, so an equal selector loses. */
g.map-node.tooltip-selected, g.map-node[data-tip-open="true"] { outline: none; }
.map-node .node-halo {
  fill: none; stroke: var(--gold); stroke-width: 2.5;
  transform-box: fill-box; transform-origin: center;
  animation: haloPulse 1.6s ease-in-out infinite;
}
@keyframes haloPulse {
  0% { stroke-opacity: 0.55; transform: scale(0.9); }
  50% { stroke-opacity: 0; transform: scale(var(--halo-peak)); }
  100% { stroke-opacity: 0.55; transform: scale(0.9); }
}
.map-node.reachable:hover circle:not(.node-halo) { fill: #2e2517; stroke: var(--gold); }
.map-node.reachable text { fill: var(--parchment); }

/* Reduced motion: keep a static, high-contrast highlight, no pulsing. */
.reduced-motion .map-node.reachable > circle:not(.node-halo) { animation: none; stroke-opacity: 1; }
.reduced-motion .map-node .node-halo { animation: none; stroke-opacity: 0.5; }
.reduced-motion .map-node.current circle { animation: none; }
.map-node.revealed circle { stroke-dasharray: 5 3; }

.map-edge { stroke: var(--map-structure); stroke-width: 2; fill: none; }
.map-edge.traveled { stroke: var(--gold); stroke-width: 3; }

/* ---- the shrine lane (Settings → Display · Shrine path glow) ---------------
   "as new paths open, the path to the nearest shrine should have a glowing
   effect" — Constantine, 2026-08-16. WHICH nodes is decided in
   model/mapknowledge.js and clipped to what the fog already draws
   (ui/components/mapboard.js); this block is only what that set looks like.

   IT MUST NOT READ AS `reachable`, which is the one thing this could break. The
   halo and the gold stroke already mean "you may step here", and a second gold
   cue on nodes you cannot click would make the strongest signal on the screen
   ambiguous. So the lane is a GLOW AROUND the node rather than a stroke on it —
   a different channel from every existing cue — and the reachable ring still
   paints on top of any node wearing both. This is a placeholder that stays out
   of the way of the cues that already work: the look is Freja's to author and
   whether it reads is Sunna's to judge. Neither has seen it.

   NO NEW UNIT AND NO STRAY px. The stroke width below is in the SVG's own user
   space, the one space every other line on this board is drawn in (Law 2).
   Nothing here is text and nothing here is a box, so Law 4 has no subject. */
.map-edge.shrine-lane {
  stroke: var(--gold);
  stroke-width: 3;
  stroke-opacity: 0.9;
  filter: drop-shadow(0 0 5px var(--gold));
  animation: shrineLanePulse 2.2s ease-in-out infinite alternate;
}
.map-node.shrine-lane > circle:not(.node-halo) {
  filter: drop-shadow(0 0 5px var(--gold));
  animation: shrineLanePulse 2.2s ease-in-out infinite alternate;
}
/* The glow is painted once at its mid radius and the PULSE is opacity: an
   animated drop-shadow re-rasterised every lane and node on the board each
   frame, for the life of the map screen. */
@keyframes shrineLanePulse {
  from { opacity: 0.72; }
  to { opacity: 1; }
}
/* Same contract as every other pulse on this board: the cue survives, the
   motion does not. A player on Reduced motion still sees the lane. */
.reduced-motion .map-edge.shrine-lane,
.reduced-motion .map-node.shrine-lane > circle:not(.node-halo) {
  animation: none;
  filter: drop-shadow(0 0 5px var(--gold));
}

/* Fog controls node knowledge; regional terrain uses the same discovered set.
   These fallback washes also support the legacy parchment loader. SVG geometry
   follows the board zoom, while detailed terrain remains anchored to the graph. */
.map-fog-ground rect {
  fill: var(--fog-parchment, #17130d);
}
/* THE PLATE IS TEXTURE, NOT COLOUR — it is generated grey (tools/parchment.mjs)
   and `soft-light` is what makes that a promise rather than a hope: a 50% grey
   pixel changes nothing, so the per-act tone above stays the ONE home for the
   colour and the plate can only add grain, fibre and blotch on top of it. Swap
   the blend for `normal` and the sheet would paint its own grey over his act
   tone — which is precisely what my first draft did, photographed: the map read
   as flat concrete. */
.map-fog-ground image { opacity: 1; mix-blend-mode: soft-light; }
/* Three acts, three washes. Deliberately close to each other and to the panel
   colour — the ground is meant to read as "nothing legible here", not as a
   feature competing with the nodes drawn on top of it. */
.map-scroll.parch-1 { --fog-parchment: #17130d; }
.map-scroll.parch-2 { --fog-parchment: #14140f; }
.map-scroll.parch-3 { --fog-parchment: #191009; }

/* The lit region gets its warmth from the nodes themselves; the fog gets a
   slight lift so the parchment reads as material rather than as absence. */
.mapscreen.map-fog .map-scroll { background: var(--fog-parchment, #17130d); }

/* A `placed` node — "something is here, you do not know what."

   SCOPED TO FOG, AND THAT IS THE POINT. Unscoped, this rule put a dashed ring on
   every unresolved `?` on the SHIPPED map — measured in a screenshot, not
   reasoned about — which broke the one promise the toggle makes: nobody who does
   not opt in sees a pixel move. It also collided with `.revealed`'s dash (5 3),
   so the map would have carried two dash patterns meaning two different things
   with nothing saying which was which. The rung is derived in both modes; only
   fog paints it differently. */
.mapscreen.map-fog .map-node.k-placed circle { stroke-dasharray: 3 4; stroke-opacity: 0.8; }

/* Under fog the trail behind you is the strongest thing on the board after the
   split in front of you: it is the only record of where the act has taken you,
   and there is no full map to fall back on. */
.mapscreen.map-fog .map-node.visited circle { stroke-opacity: 1; }
.mapscreen.map-fog .map-edge.traveled { stroke-width: 3.5; }
/* An untraveled edge inside the light is a step you are choosing, not structure
   you are reading past — the fog map has far fewer of them, so they carry more. */
.mapscreen.map-fog .map-edge { stroke-opacity: 0.9; }


/* World Journey: fixed geography with discovered roads and local inspection.
   W4b bands (10 / 60 / 20 / 10): a header, the map scene, the selected place's
   context, and a Recenter / Enter footer. The screen never scrolls; the scene's
   scrollport is the camera, and the context band scrolls inside itself when
   its content outgrows its share. */
.world-atlas-screen { display:grid; grid-template-rows:auto minmax(0,1fr) auto auto; overflow:hidden; min-height:0; background:#171612; color:#d5c5a4; }
.atlas-header { display:grid; grid-template-columns:minmax(0,1fr) auto auto; grid-template-areas:"title vitals actions"; align-items:center; gap:4px 16px; padding:8px 2.5vw; border-bottom:1px solid #5b4a30; background:#171512; }
.atlas-title { grid-area:title; min-width:0; }
.atlas-header h1 { font:26px Georgia,serif; letter-spacing:.035em; margin:2px 0 0; color:#eee0be; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.atlas-eyebrow { font:11px system-ui,sans-serif; letter-spacing:.18em; color:#b49a68; }
.atlas-header-actions { grid-area:actions; display:flex; align-items:center; justify-content:flex-end; gap:6px; }
.atlas-vitals { grid-area:vitals; font-size:13px; white-space:nowrap; }
.atlas-scene { position:relative; display:flex; min-height:0; padding:0 2.5vw; }
.atlas-map-tools { position:absolute; top:8px; right:calc(2.5vw + 8px); z-index:3; display:flex; gap:5px; }
.atlas-map-tools .as-btn { min-width:var(--tap-floor); min-height:var(--tap-floor); }
.atlas-scrollport { flex:1; min-width:0; min-height:0; overflow:auto; border:1px solid #8a744d; background:#9d8051; box-shadow:0 10px 45px #0006; }
.atlas-world { position:relative; width:calc(100% * var(--atlas-zoom)); min-width:600px; aspect-ratio:1; isolation:isolate; }
.atlas-terrain { display:block; position:absolute; inset:0; width:100%; height:100%; }
.atlas-roads path { stroke:#322a1c; stroke-width:3.5; fill:none; stroke-dasharray:4 4; }
.atlas-roads .traveled { stroke:#e9c477; stroke-width:4; stroke-dasharray:none; }
.atlas-node { position:absolute; z-index:2; transform:translate(-50%,-50%); width:44px; height:44px; padding:0; background:none; border:0; cursor:pointer; display:grid; place-items:center; }
.atlas-node:focus-visible { outline:var(--focus-ring); outline-offset:3px; border-radius:50%; }
.atlas-landmark { position:absolute; width:110px; height:110px; bottom:19px; left:50%; transform:translateX(-50%); transform-origin:50% 95%; transition:transform 180ms ease; pointer-events:none; filter:drop-shadow(0 5px 5px #18100a99); }
.atlas-landmark img,.atlas-landmark svg { width:100%; height:100%; object-fit:contain; }
.atlas-landmark svg { mask-image:radial-gradient(ellipse,#000 30%,transparent 70%); }
.atlas-core:hover,.atlas-core:focus-visible { z-index:8; }
.atlas-core:hover .atlas-landmark,.atlas-core:focus-visible .atlas-landmark { transform:translateX(-50%) scale(1.28); }
.atlas-node-label { position:absolute; top:38px; left:50%; transform:translateX(-50%); color:#f0e0b4; background:#211a12eb; padding:4px 9px; border:1px solid #80663b; white-space:nowrap; font:13px Georgia,serif; }
.atlas-compass { position:absolute; top:5%; left:7%; font:32px Georgia,serif; line-height:1.2; text-align:center; color:#5c4b2e; pointer-events:none; }
.atlas-map-caption { position:absolute; bottom:4%; width:100%; text-align:center; color:#554226; letter-spacing:.25em; font:18px Georgia,serif; pointer-events:none; }
.atlas-map-caption small { font:11px Georgia,serif; letter-spacing:.04em; }
.atlas-node[aria-pressed="true"]::before { content:""; position:absolute; inset:-5px; border:3px solid #f6e7bd; border-radius:50%; box-shadow:0 0 0 2px #1b150ecc; pointer-events:none; }
.atlas-context { display:grid; grid-template-columns:minmax(0,1.3fr) minmax(0,1fr) minmax(0,1fr); grid-template-rows:auto 1fr; grid-template-areas:"place roads journey" "description roads journey"; gap:4px 28px; align-items:start; max-height:calc(20dvh / var(--ui-zoom,1)); overflow-y:auto; overscroll-behavior:contain; padding:10px 2.5vw; border-top:1px solid #5b4a30; background:linear-gradient(#282218aa,#171612); }
.atlas-context h2 { font:22px Georgia,serif; margin:4px 0 2px; color:#eee0be; }
.atlas-context h3 { font:15px Georgia,serif; margin:0 0 6px; color:#d6bc84; }
.atlas-context p { margin:2px 0; line-height:1.45; font-size:13px; }
.atlas-context-place { grid-area:place; min-width:0; }
.atlas-context-roads { grid-area:roads; min-width:0; }
.atlas-context-journey { grid-area:journey; min-width:0; }
.atlas-context-status { color:#e9d6a6; }
.atlas-context .atlas-context-description { grid-area:description; margin:0; color:#bfae8b; }
.atlas-context-heading { display:flex; align-items:center; justify-content:space-between; gap:10px; min-width:0; }
.atlas-context-heading h2 { min-width:0; overflow-wrap:anywhere; }
.atlas-context .atlas-inspect { flex:none; min-height:var(--tap-floor); }
.atlas-legend { display:flex; flex-wrap:wrap; gap:4px 14px; font-size:11px; color:#b8a581; margin:6px 0; }
.atlas-objective { display:flex; justify-content:space-between; gap:8px; padding:2px 0; font-size:12px; }
.atlas-objective>span { font-size:10px; text-transform:uppercase; letter-spacing:.13em; color:#9b8664; }
.atlas-road-list { display:flex; flex-wrap:wrap; gap:6px; }
.atlas-road-list .as-btn { white-space:normal; text-align:left; min-height:var(--tap-floor); padding:6px 12px; line-height:1.3; }
.atlas-road-list .as-btn[aria-pressed="true"] { border-color:#f6e7bd; color:#f6e7bd; }
.atlas-progress,.atlas-seed { color:#aa9774; }
.atlas-footer { padding:8px 2.5vw calc(8px + env(safe-area-inset-bottom,0px) / var(--ui-zoom,1)); border-top:1px solid #5b4a30; background:#171512; }
.atlas-footer > .modal-btnrow { width:100%; }
.atlas-dialog { position:fixed; inset:0; margin:auto; padding:0; width:min(1080px,calc(100vw - 32px)); max-width:none; max-height:calc(100dvh - 32px); border:1px solid #947548; color:#dbc9a4; background:#1b1813; box-shadow:0 30px 90px #000b; z-index:500; overflow:auto; }
.atlas-dialog::backdrop { background:#080808bc; backdrop-filter:blur(4px); }
.atlas-dialog-head,.atlas-dialog-foot { display:flex; justify-content:space-between; align-items:center; gap:20px; padding:18px 24px; background:#211b14; }
.atlas-dialog-head { border-bottom:1px solid #655034; }
.atlas-dialog h2 { margin:7px 0 0; font:29px Georgia,serif; color:#f0dfb7; }
.atlas-location-body { display:grid; grid-template-columns:minmax(0,1.55fr) minmax(240px,1fr); }
.atlas-local-wrap { min-width:0; padding:18px; background:#0f100f; }
.atlas-local-map { position:relative; aspect-ratio:1; background:#9b805b; }
.atlas-local-map>img { width:100%; height:100%; object-fit:cover; }
.atlas-local-point { position:absolute; transform:translate(-50%,-50%); z-index:1; width:44px; height:44px; border:0; background:none; padding:0; display:grid; place-items:center; color:#f4ddb0; cursor:pointer; }
.atlas-local-point>span { width:28px; height:28px; border:1px solid #d2ad62; border-radius:50%; background:#19150fec; display:grid; place-items:center; }
.atlas-local-point small { position:absolute; top:37px; white-space:nowrap; font:11px Georgia,serif; background:#21190fe6; padding:3px 5px; }
.atlas-local-point[aria-pressed=true]>span,.atlas-local-point:focus-visible>span { background:#d5ad62; color:#18120c; box-shadow:0 0 0 4px #f1d99a50; }
.atlas-local-point[data-local-point*='/gate:'] small { display:none; }
.atlas-local-point[data-local-point*='/gate:']:focus small,.atlas-local-point[data-local-point*='/gate:']:hover small { display:block; }
.atlas-location-detail { padding:30px 26px; min-width:0; overflow-wrap:anywhere; }
.atlas-location-detail h3 { font:28px Georgia,serif; color:#ead7a9; margin:10px 0 20px; }
.atlas-location-detail p { font-size:15px; line-height:1.7; color:#bfae8b; }
.atlas-location-detail .as-btn { display:block; width:100%; margin-top:14px; min-height:46px; white-space:normal; line-height:1.4; }
.atlas-dialog-foot { border-top:1px solid #655034; font-size:13px; }
.atlas-dialog-foot .as-btn { max-width:65%; white-space:normal; }
.atlas-location-illustration { aspect-ratio:1; }
.atlas-location-illustration svg { width:100%; height:100%; }
@media(max-width:800px) {
 .atlas-header { grid-template-columns:minmax(0,1fr) auto; grid-template-areas:"title actions" "vitals actions"; gap:0 10px; padding:6px 2.5vw; }
 .atlas-header .atlas-eyebrow { display:none; }
 .atlas-header h1 { font-size:19px; margin:0; }
 .atlas-vitals { font-size:12px; }
 .atlas-header-actions .as-btn { padding-inline:10px; }
 .atlas-context { grid-template-columns:1fr; grid-template-rows:none; grid-template-areas:"place" "roads" "description" "journey"; gap:8px; padding-block:8px; }
 .atlas-context h2 { font-size:19px; }
 .atlas-dialog { width:calc(100vw - 16px); max-height:calc(100dvh - 16px); }
 .atlas-dialog-head,.atlas-dialog-foot { padding:14px; gap:10px; }
 .atlas-dialog h2 { font-size:24px; }
 .atlas-location-body { grid-template-columns:1fr; }
 .atlas-local-wrap { padding:10px; }
 .atlas-location-detail { padding:20px; }
 .atlas-local-point small { font-size:10px; }
 .atlas-dialog-foot { position:sticky; bottom:0; }
}
/* A phone keeps the whole title: title and vitals share the first row and the
   three actions take equal shares of the second, within W4b's 10% header. */
@media(max-width:480px) {
 .atlas-header { grid-template-columns:minmax(0,1fr) auto; grid-template-areas:"title vitals" "actions actions"; gap:4px 10px; }
 .atlas-header h1 { white-space:normal; overflow-wrap:anywhere; }
 .atlas-header-actions { justify-content:stretch; }
 .atlas-header-actions .as-btn { flex:1; min-width:0; }
}
@media(prefers-reduced-motion:reduce) { .atlas-landmark { transition:none; } }
/* Native dialogs own layout and focus; the shared veil class only marks input ownership. */
dialog.atlas-dialog.modal-veil[open] { display:block; padding:0; background:#1b1813; }
.atlas-node,.atlas-local-point { overflow:visible; }
.atlas-node-label,.atlas-local-point small { pointer-events:none; }

/* Local maps keep controls and information outside the gesture viewport. */
dialog.atlas-dialog.modal-veil[open]:has(.atlas-local-port) { display:flex; flex-direction:column; align-items:stretch; justify-content:flex-start; width:min(1080px,calc((100vw - 24px) / var(--ui-zoom,1))); height:min(860px,calc((100dvh - 24px) / var(--ui-zoom,1))); max-height:calc((100dvh - 24px) / var(--ui-zoom,1)); overflow:hidden; }
.atlas-dialog:has(.atlas-local-port) .atlas-dialog-head,.atlas-dialog:has(.atlas-local-port) .atlas-dialog-foot { flex:none; }
.atlas-dialog:has(.atlas-local-port) .atlas-location-body { flex:1; min-height:0; }
.atlas-dialog:has(.atlas-local-port) .atlas-local-wrap { display:flex; flex-direction:column; gap:8px; min-height:0; }
.atlas-local-tools,.atlas-local-zoom-tools,.atlas-local-pan-tools { display:flex; align-items:center; gap:5px; }
.atlas-local-tools { justify-content:space-between; flex-wrap:wrap; }
.atlas-local-tools .as-btn { min-width:max(36px,var(--tap-floor)); min-height:max(36px,var(--tap-floor)); padding:6px 10px; }
.atlas-local-zoom-tools output { min-width:45px; text-align:center; font-size:13px; }
.atlas-local-help { margin:0; font-size:12px; line-height:1.4; color:#c2b38e; }
.atlas-local-port { position:relative; flex:1; min-height:170px; overflow:clip; background:#8b7d61; touch-action:none; overscroll-behavior:contain; cursor:grab; isolation:isolate; }
.atlas-local-port:focus-visible { outline:var(--focus-ring); outline-offset:var(--focus-ring-offset); }
.atlas-local-port[data-dragging] { cursor:grabbing; user-select:none; }
.atlas-local-map { position:absolute; aspect-ratio:auto; }
.atlas-local-map>svg { position:absolute; width:100%; height:100%; pointer-events:none; }
.atlas-local-point { touch-action:none; width:max(44px,var(--tap-floor)); height:max(44px,var(--tap-floor)); }
.atlas-local-point>span { width:34px; height:34px; box-shadow:0 2px 5px #000b; }
.atlas-local-point small { max-width:115px; white-space:normal; width:max-content; text-align:center; font-size:12px; line-height:1.2; border:1px solid #8c7041; }
.atlas-local-port[data-labels=selected] .atlas-local-point:not([aria-pressed=true]):not(:focus):not(:hover) small { display:none; }
.atlas-local-point[aria-pressed=true] { z-index:3; }
.atlas-local-point:hover,.atlas-local-point:focus-visible { z-index:4; }
.atlas-local-picker-label { display:flex; align-items:center; gap:12px; font-size:13px; }
.atlas-local-picker-label select { flex:1; min-width:0; min-height:max(44px,var(--tap-floor)); color:#f3dfb7; border:1px solid #947548; background:#211b14; padding:8px; }
.atlas-dialog:has(.atlas-local-port) .atlas-location-detail { overflow:auto; overscroll-behavior:contain; padding:22px; }
.atlas-service-benefits h4 { margin:22px 0 10px; color:#f1d18b; }
.atlas-service-benefits .atlas-service-benefit,.atlas-service-benefit { color:#f2dfb6; font-size:16px; }
.atlas-service-benefits ul { padding-left:20px; font-size:14px; line-height:1.6; }
.atlas-service-benefits li+li { margin-top:9px; }
.atlas-dialog:has(.atlas-local-port) .atlas-location-detail:has(.atlas-detail-scroll) { display:flex; flex-direction:column; overflow:hidden; }
.atlas-detail-scroll { flex:1; min-height:0; overflow:auto; overscroll-behavior:contain; }
.atlas-detail-actions { flex:none; padding-top:8px; }
.atlas-detail-actions .as-btn { margin-top:0; background:#302514; }
@media(max-width:700px) {
  dialog.atlas-dialog.modal-veil[open]:has(.atlas-local-port) { height:calc((100dvh - 16px) / var(--ui-zoom,1)); }
  .atlas-dialog:has(.atlas-local-port) .atlas-location-body { grid-template-rows:minmax(0,1.2fr) minmax(0,.8fr); }
  .atlas-dialog:has(.atlas-local-port) .atlas-dialog-head,.atlas-dialog:has(.atlas-local-port) .atlas-dialog-foot { padding:10px 12px; }
  .atlas-dialog:has(.atlas-local-port) .atlas-local-wrap { padding:8px; gap:5px; }
  .atlas-dialog:has(.atlas-local-port) .atlas-location-detail { padding:12px 18px; border-top:1px solid #655034; }
  .atlas-local-help { font-size:max(10px,calc(11px / var(--ui-zoom,1))); }
  .atlas-local-tools { gap:3px; }
  .atlas-local-tools .as-btn { padding:4px; }
  .atlas-local-zoom-tools,.atlas-local-pan-tools { gap:2px; }
  .atlas-local-zoom-tools output { min-width:38px; }
}
@media(max-height:500px) and (min-width:701px) {
  .atlas-dialog:has(.atlas-local-port) .atlas-dialog-head,.atlas-dialog:has(.atlas-local-port) .atlas-dialog-foot { padding:6px 12px; }
  .atlas-dialog:has(.atlas-local-port) .atlas-local-wrap { padding:6px; gap:3px; }
  .atlas-local-help { display:none; }
  .atlas-local-port { min-height:80px; }
}
.world-atlas-screen .atlas-roads path { stroke:#775125; stroke-width:3; stroke-dasharray:none; filter:none; }
.world-atlas-screen .atlas-roads .traveled { stroke:#b27c1f; stroke-width:4; }

/* The relic rail shows on the map as on every screen (owner, 2026-09-14). It is
   an icon tray, so only its icons take a press: the board under the empty rest
   of the row stays live, which is what hiding the rail used to buy. */

/* Atlas profiles use the same compact circular node vocabulary as traditional runs. */
.atlas-node-label { top:40px; max-width:130px; white-space:normal; text-align:center; pointer-events:none; }
@media (max-width:600px) {
 .atlas-node-label { display:none; }
 .atlas-scrollport { box-shadow:none; }
 .atlas-world { min-width:0; }
}

/* All run modes use the traditional map's vector discs and close-up scale. */
.atlas-scrollport { background:#c4af85; }
.atlas-node { width:calc(var(--atlas-node-size) * var(--atlas-face-scale,1)); height:calc(var(--atlas-node-size) * var(--atlas-face-scale,1)); min-width:var(--tap-floor); min-height:var(--tap-floor); }
.atlas-node-face { display:block; width:100%; height:100%; overflow:visible; pointer-events:none; }
.atlas-node-label { top:calc(100% + 8px); }
.atlas-world { min-width:0; }
.atlas-roads path { stroke:#806b49; stroke-width:3; stroke-dasharray:none; }
.atlas-roads .traveled { stroke:#b89030; stroke-width:4; }
.atlas-compass,.atlas-map-caption { pointer-events:none; }

/* Engraved fog and outlined vector roads stay readable at every detail tier. */
.map-detail-surface, .map-detail-tiles { pointer-events:none; }
.mapscreen .map-route-outline, .world-atlas-screen .atlas-roads .map-route-outline {
  stroke:#29251a; stroke-width:var(--map-route-outline-width,6px); stroke-linecap:round; stroke-dasharray:none; fill:none; opacity:.95;
}
.mapscreen svg:has(.map-terrain) .map-edge, .world-atlas-screen .atlas-roads path:not(.map-route-outline) {
  stroke:#c9b987; stroke-width:var(--map-route-width,3px); stroke-opacity:1; stroke-linecap:round;
}
.mapscreen svg:has(.map-terrain) .map-edge.traveled, .world-atlas-screen .atlas-roads .traveled { stroke:#ead090; }
.mapscreen svg:has(.map-terrain) .map-edge.shrine-lane, .world-atlas-screen .atlas-roads .available { stroke:#ffe39a; stroke-width:3.5px; }
.mapscreen .map-scroll:has(.map-terrain), .atlas-scrollport, .atlas-world { background:#8b7d61; }
.map-terrain .terrain-detail { opacity:1; }

/* Tall local-location shell: the same map and action layout for every city. */
dialog.atlas-dialog.modal-veil[open]:has(.atlas-local-port) { height:calc((100dvh - 24px) / var(--ui-zoom,1)); max-height:calc((100dvh - 24px) / var(--ui-zoom,1)); }
.atlas-dialog:has(.atlas-local-port) .atlas-location-body { display:grid; grid-template-columns:minmax(0,1fr) auto; grid-template-rows:minmax(320px, 2fr) minmax(150px,1fr); overflow:auto; overscroll-behavior:contain; }
.atlas-dialog:has(.atlas-local-port) .atlas-local-wrap { grid-column:1 / -1; min-height:280px; padding:12px; }
.atlas-dialog:has(.atlas-local-port) .atlas-local-port { min-height:220px; }
.atlas-dialog:has(.atlas-local-port) .atlas-location-detail { min-width:0; min-height:150px; padding:16px; }
.atlas-dialog:has(.atlas-local-port) .atlas-local-tools { align-self:start; padding:16px 12px; flex-direction:column; }
.atlas-dialog:has(.atlas-local-port) .atlas-local-help { order:2; }
.atlas-dialog:has(.atlas-local-port) .atlas-dialog-foot { display:flex; flex-wrap:wrap; gap:10px; padding:12px; }
.atlas-dialog:has(.atlas-local-port) .atlas-dialog-foot > span { display:none; }
.atlas-dialog:has(.atlas-local-port) .atlas-detail-actions { margin-left:auto; padding:0; display:flex; flex-wrap:wrap; gap:8px; }
.atlas-dialog:has(.atlas-local-port) .atlas-detail-actions .as-btn { margin:0; white-space:normal; }
@media(max-width:700px) {
 .atlas-dialog:has(.atlas-local-port) .atlas-location-body { grid-template-columns:minmax(0,1fr); grid-template-rows:minmax(300px,1.5fr) auto minmax(160px,1fr); }
 .atlas-dialog:has(.atlas-local-port) .atlas-local-wrap { grid-column:1; min-height:300px; }
 .atlas-dialog:has(.atlas-local-port) .atlas-local-tools { grid-row:2; flex-direction:row; padding:6px 10px; }
 .atlas-dialog:has(.atlas-local-port) .atlas-location-detail { min-height:160px; }
}
@media(max-height:500px) and (min-width:701px) {
 .atlas-dialog:has(.atlas-local-port) .atlas-location-body { grid-template-columns:minmax(0,1.3fr) minmax(0,1fr); grid-template-rows:minmax(220px,1fr) auto; }
 .atlas-dialog:has(.atlas-local-port) .atlas-local-wrap { grid-column:1; grid-row:1 / 3; min-height:280px; }
 .atlas-dialog:has(.atlas-local-port) .atlas-location-detail { grid-column:2; grid-row:1; }
 .atlas-dialog:has(.atlas-local-port) .atlas-local-tools { grid-column:2; grid-row:2; padding:6px; flex-direction:row; }
}

.atlas-dialog:has(.atlas-local-port) .atlas-dialog-foot .as-btn { width:auto; min-width:120px; max-width:100%; }
.atlas-detail-scroll { scrollbar-gutter:stable; scrollbar-color:#947548 #211b14; }

/* A spatial direction pad followed by one minus / Fit / plus row. */
.atlas-dialog:has(.atlas-local-port) .atlas-local-tools { display:flex; flex-direction:column; align-items:center; justify-content:flex-start; gap:8px; padding:8px 12px; }
.atlas-local-tools .atlas-local-pan-tools, .atlas-local-tools .atlas-local-zoom-tools { display:grid; grid-template-columns:repeat(3,max(40px,var(--tap-floor))); gap:4px; }
.atlas-local-pan-tools [data-local-pan="up"] { grid-area:1 / 2; }
.atlas-local-pan-tools [data-local-pan="left"] { grid-area:2 / 1; }
.atlas-local-pan-tools [data-local-pan="right"] { grid-area:2 / 3; }
.atlas-local-pan-tools [data-local-pan="down"] { grid-area:3 / 2; }
.atlas-local-tools .as-btn { width:100%; min-width:0; padding:4px; }
.atlas-local-pan-tools output { grid-area:2 / 2; align-self:center; text-align:center; font-size:12px; font-variant-numeric:tabular-nums; }
@media(max-height:500px) and (min-width:701px) {
 .atlas-dialog:has(.atlas-local-port) .atlas-local-tools { flex-direction:row; align-items:center; justify-content:space-evenly; flex-wrap:wrap; }
}

/* Ordinary road/encounter inspection uses scene art, never an enlarged map tile. */
.atlas-dialog[data-local-map="false"] .atlas-location-illustration { aspect-ratio:3 / 2; max-width:calc(768px / var(--ui-zoom,1)); margin:auto; }
.atlas-dialog[data-local-map="false"] .atlas-detail-actions:empty { display:none; }
.atlas-dialog[data-local-map="false"] [data-atlas-travel] { margin-left:auto; }
.atlas-dialog [data-atlas-travel][data-forward="true"]:enabled { background:color-mix(in srgb,var(--green) 22%,var(--panel)); border-color:var(--green); color:var(--parchment); font-weight:600; }
.atlas-dialog [data-atlas-travel][data-forward="true"]:enabled:hover { background:color-mix(in srgb,var(--green) 34%,var(--panel)); }
