/* ---- Drop-on-the-floor confirmation ------------------------------------------------------------
   The same box as the split prompt (`.rpg-split`), so the two quantity gestures look like one
   idea; `.rpg-drop` only adds the line naming what is about to be thrown away, and warms the OK
   button, because this is the one inventory action nobody can undo. */
/* FIXED, so it floats over the interface instead of living inside a panel. Mounted on the panel it
   was an absolutely-positioned box that contributed to the panel's scroll extent, and the inventory
   window visibly grew to contain it. See `openDropConfirm`. */
.rpg-drop { position: fixed; width: 186px; z-index: 60; pointer-events: auto; }
.rpg-drop-name { margin-bottom: 8px; font-family: var(--font-body); font-size: 12px;
  color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rpg-drop .rpg-split-ok { border-color: var(--ember); }

/* ---- Heart sockets -----------------------------------------------------------------------------
   One pip per socket, along the bottom of the item's tile. An empty socket is a dashed hollow ring
   and a filled one carries the heart's glyph — so "this takes hearts" and "this has one in it" are
   the same strip read at a glance, on the grid, where the decision to keep a drop is made.

   `pointer-events: none` throughout: the strip sits on top of the tile and the tile is a drag
   handle, so a pip that swallowed the press would make socketed gear the one thing in the bag you
   could not pick up. */
/* BOTTOM-LEFT, RUNNING RIGHT (owner's call). Centred, the strip moved horizontally as sockets were
   added or filled, so the same piece of gear looked subtly different from itself and the eye had to
   re-find the pips on every tile. Anchored to one corner they start in the same place on every
   item, and a second, third or fourth socket simply extends the row — which is also what makes four
   fit: `MAX_SOCKETS` is this strip's width budget, not a balance decision. */
.rpg-pips { position: absolute; bottom: 2px; left: 3px; z-index: 3;
  display: flex; justify-content: flex-start; gap: 2px; pointer-events: none; }
.rpg-pip { width: 10px; height: 10px; border-radius: 50%; display: grid; place-items: center;
  font-size: 7px; line-height: 1;
  background: rgba(8, 10, 28, .78); border: 1px dashed rgba(255, 255, 255, .38);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .55); }
.rpg-pip.is-filled { border-style: solid; border-color: #ff7591;
  background: radial-gradient(circle at 50% 30%, rgba(255, 117, 145, .40) 0%, transparent 62%),
    rgba(8, 10, 28, .85);
  box-shadow: 0 0 6px rgba(255, 117, 145, .55); }
/* THE BOTTOM OF THE TILE, on the paperdoll too (owner's call). The strip used to lift clear of the
   slot's label — but an OCCUPIED slot hides that label (`[data-rarity] .rpg-slot-tag` fades it), and
   a slot with sockets is by definition occupied, so it was dodging something that is not there. */
.rpg-slot .rpg-pips { bottom: 2px; }
/* The item card's art box is far larger; the pips grow with it so the heart glyph stays legible. */
.rpg-itemcard-art .rpg-pips { bottom: 5px; left: 7px; gap: 4px; }
.rpg-itemcard-art .rpg-pip { width: 17px; height: 17px; font-size: 11px; }

/* ---- Party portraits, cog and spacing ----------------------------------------------------------
   THE FACE. Two coloured shapes, not a render: a skin disc under a hair cap, drawn from the two
   colours the party state carries (see `PartyMemberState.skinTone`). A real CharacterView per row
   would mean a WebGL context per member. What a portrait is for in a list of five is finding one
   person at a glance, and colour does that faster than reading five names.

   The cap is a ::before rather than a second element so `memberPortrait` returns one node and both
   party windows can place it without knowing its internals. */
.rpg-party-face { position: relative; flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; overflow: hidden;
  background: var(--skin, linear-gradient(180deg, var(--plum-hi), var(--plum-lo)));
  border: 1px solid var(--gold); box-shadow: 0 0 7px -2px var(--gold-glow);
  font: 700 12px var(--font-body); color: var(--gold-hi); }
/* A REAL RENDERED HEAD now, not two coloured shapes: `partyFaces.ts` draws each appearance once
   through one shared offscreen renderer and hands back an image. The medallion is just its frame. */
.rpg-party-face img { width: 100%; height: 100%; object-fit: cover; display: block;
  border-radius: 50%; -webkit-user-drag: none; user-select: none; }
/* The frame keeps its lit ground behind the picture, so a face with transparent margins sits on
   something rather than on the panel. */
.rpg-party-face[data-drawn="face"] { background: radial-gradient(circle at 50% 34%, var(--plum-hi), var(--plum-lo)); }

/* THE FLOATING WINDOW'S ROW becomes two columns: the face down the left, the name-and-bar column
   beside it. It was a plain flex COLUMN, so a portrait appended to it would have been a full-width
   block sitting above the name. The face spans both rows so it centres against the pair. */
/* TIGHTER, now the place shares the name's line. A member was two text rows plus a bar; it is one
   row plus a bar, and the padding and gaps come in to match — ten of these stack down the left of
   the screen and every pixel of row height is paid ten times. */
.rpg-party-win-row { display: grid; grid-template-columns: auto minmax(0, 1fr);
  column-gap: 7px; row-gap: 1px; align-items: center; padding: 3px 6px; }
.rpg-party-win-row .rpg-party-face { grid-row: 1 / span 2; }
.rpg-party-win-row .rpg-party-win-top,
.rpg-party-win-row .rpg-party-hp { grid-column: 2; }

/* The cog sits beside the minimise chevron; same size and weight so the pair reads as one control
   group rather than as a button and a decoration. */
.rpg-party-win-cog { flex: none; width: 20px; height: 20px; line-height: 1; margin-left: auto;
  padding: 0; border-radius: var(--r-1, 5px); border: 1px solid var(--glass-stroke);
  background: var(--glass-hi); color: var(--ink-soft); font-size: 12px; cursor: pointer; }
.rpg-party-win-cog:hover { color: var(--ink); border-color: var(--gold); }
/* The chevron loses its auto margin now that the cog carries it, or the two would be pushed apart
   to opposite ends of the header. */
.rpg-party-win-head .rpg-party-win-toggle { margin-left: 4px; }

/* AIR BEFORE AN IRREVERSIBLE BUTTON. Leaving the party sat flush under the drops rule, reading as
   one more setting in a stack of settings. */
.rpg-social-act.is-parted { margin-top: 14px; }

/* ---- Bestiary: breathing room, and the creature's page ------------------------------------------
   THE PADDING WAS THE BUG. `.rpg-bestiary-art` is 76x60 with the image at `width:100%;height:100%`,
   so a render that fills its own frame edge to edge sat flush against the plate's rim — and on the
   taller creatures the bottom row of pixels was clipped by the plate's `overflow: hidden` and its
   own rounded corner. Reported as "some get the bottom cut off", and it is not a centring problem:
   `object-fit: contain` was already centring it, inside a box with no margin to centre within.

   5px of padding on all four sides, with `box-sizing: border-box` so the plate does not grow. That
   is the minimum the owner asked for and it is enough — `contain` then fits the render inside the
   inset box, which is what makes it visibly centred as well as uncropped. */
/* FLEX, NOT GRID, and that is the whole fix rather than the padding was.
   The plate is a 76x60 border-box with a 1px border and 5px of padding, so its content box is
   64 x 48 — but the image measured 64 x 64, hanging 15px past the padding edge and being cropped
   by `overflow: hidden`. Always the bottom, on every creature, which is exactly what was reported.
   `width: 100%` resolved correctly (64). `height: 100%` did not, and neither did `max-height:
   100%`: the img was a GRID ITEM in an implicit AUTO-SIZED ROW, so a percentage against that track
   is cyclic and the browser falls back to `auto` — and `auto` height on a 256x256 source already
   capped to 64px wide is 64px. A square image in a landscape box, overflowing every time.
   A flex container with a definite height has no such cycle, so the percentage bound below
   resolves and the picture is bounded by the plate instead of by its own aspect ratio. */
.rpg-bestiary-art { box-sizing: border-box; padding: 5px;
  display: flex; align-items: center; justify-content: center; }
/* Bounded by the box, sized by the picture, centred by the flexbox. No `object-fit` needed once the
   element itself is the right size — the box IS the image. */
.rpg-bestiary-img { width: auto; height: auto; max-width: 100%; max-height: 100%; }

/* TWO COLUMNS: the collection, and one creature's page beside it. The grid keeps its auto-fill
   behaviour inside its own column, so nothing about the tiles changes. */
/* THE PANEL DOES NOT SCROLL; THE TILE GRID DOES.
   `.rpg-panel` is `max-height: 86vh; overflow: auto`, so the whole bestiary scrolled as one sheet —
   the title, the summary and the search box slid away the moment you went looking for a creature,
   and the page on the right went with them, which is the one thing that must stay put while you
   scan. Turning the panel into a fixed-height COLUMN and giving the scroll to the grid alone keeps
   the furniture still: heading, summary and search are flex items that hold their size, and the
   two-column row takes the rest.
   `overflow: hidden` on the panel is what stops it double-scrolling — without it the panel keeps
   its own bar and the grid's is a second one beside it. */
/* FOUR TILES ACROSS, FOUR DOWN — and TALLER than the first attempt at it. 62vh held four rows only
   on a short screen; with two control rows above the grid it showed three and a half, which is the
   one thing a "4x4 by default" is not allowed to do. 78vh clears the furniture and the four rows at
   the sizes this panel actually uses.
   (Original note follows.)
   FOUR TILES ACROSS, FOUR DOWN, and the panel sized to hold exactly that (owner's call). It was
   86vh of mostly grid, which on a tall screen is a wall of creatures; a 4x4 page is a thing you can
   take in at once and scroll deliberately. The height is `max-content`-ish rather than a second
   magic number — the column widths below fix the tile size, and four rows of it plus the furniture
   is what `62vh` buys at the sizes this panel actually uses. Capped at 86vh so a short viewport
   still gets a scroller instead of a window taller than the screen. */
.rpg-panel--bestiary { width: min(1000px, 96vw); height: min(78vh, 90vh); overflow: hidden; }
/* THE FLEX GOES ON `.is-open`, NOT ON THE PANEL. `.rpg-panel` is `display: none` and
   `.rpg-panel.is-open` is `display: block`; a bare `display: flex` here is the same specificity as
   `.rpg-panel` and sits LATER in the file, so it beat the `none` and the bestiary was open on load
   and could not be closed — the toggle was still adding and removing `is-open`, and the panel no
   longer cared. Reported exactly that way.
   Any panel that needs a display mode of its own has to state it on the open state, or it opts out
   of the open/closed mechanism entirely. */
.rpg-panel--bestiary.is-open { display: flex; flex-direction: column; }
/* The furniture keeps its natural height and never shrinks... */
.rpg-panel--bestiary > .rpg-panel-title,
.rpg-panel--bestiary > .rpg-bestiary-summary,
.rpg-panel--bestiary > .rpg-bestiary-titlerow,
.rpg-panel--bestiary > .rpg-bestiary-controls { flex: none; }
/* ...and the columns take everything left. `min-height: 0` is the load-bearing half: a flex item
   defaults to `min-height: auto`, which refuses to shrink below its content, so without it the row
   grows to fit every tile and the grid never becomes a scroller at all. */
.rpg-bestiary-cols { display: grid; grid-template-columns: minmax(0, 1fr) 258px;
  gap: var(--sp-4); align-items: stretch;
  flex: 1 1 auto; min-height: 0; }
/* THE ONE SCROLLER. `min-height: 0` again, for the same reason, and a little right padding so the
   gilded rail does not sit on top of the last column of tiles. */
.rpg-bestiary-grid { overflow-y: auto; overflow-x: hidden; min-height: 0;
  align-content: start; padding-right: var(--sp-2); }
/* The page beside it holds still and scrolls ITSELF if a creature's entry runs long — it must never
   push the grid's height around. `position: sticky` is gone with the panel's own scrolling: there
   is nothing left for it to stick against. */
.rpg-bestiary-detail { overflow-y: auto; min-height: 0; }
/* Below ~820px the page drops under the grid rather than squeezing both into nothing. */
@media (max-width: 860px) { .rpg-bestiary-cols { grid-template-columns: minmax(0, 1fr); } }

.rpg-bestiary-detail { padding: var(--sp-3); border-radius: var(--r-2);
  border: 1px solid var(--glass-stroke); background: var(--glass-deep); }
/* The page's portrait is the tile's plate at double size — same class, same silhouette rule, so a
   creature cannot be revealed in one place and hidden in the other. It also carries the TILE's own
   ground and border (owner's call): the preview is the same picture as the tile the pointer is on,
   and framing it differently made it read as a different kind of object. `.is-known` /
   `.is-unknown` live on the tile, not here, so the two states are re-stated for this plate. */
.rpg-bestiary-bigart { width: 100%; height: 132px; margin-bottom: var(--sp-3);
  background: radial-gradient(circle at 50% 34%, var(--plum-hi), var(--plum-lo));
  border: 1px solid var(--gold-deep); }
.rpg-bestiary-detail[data-state="empty"] { display: grid; place-items: center;
  min-height: 132px; text-align: center; }
/* An undiscovered creature's plate matches the silhouetted TILE instead — lighter ground so a black
   outline reads on it, and the dashed rim that says "unfilled". A class set by the renderer rather
   than `:has()`, which is the one selector here that would silently do nothing on an older engine
   and leave a black shape on a near-black plate. */
.rpg-bestiary-bigart.is-unknown { background: #4a4468;
  border: 1px dashed rgba(255,255,255,.10); }

/* Level and element, on one line under the name. */
.rpg-bestiary-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 4px; }
.rpg-bestiary-tag { padding: 2px 8px; border-radius: 999px; font: 11px var(--font-body);
  border: 1px solid var(--glass-stroke); background: var(--glass-deep); color: var(--ink-soft); }
.rpg-bestiary-tag.is-element { color: var(--ink); }
/* One hue per element, so the wheel is recognisable before the words are read. */
.rpg-bestiary-tag[data-element="fire"]  { border-color: #d4622f; }
.rpg-bestiary-tag[data-element="water"] { border-color: #3f8fc4; }
.rpg-bestiary-tag[data-element="earth"] { border-color: #8a7040; }
.rpg-bestiary-tag[data-element="wind"]  { border-color: #6fae8f; }
.rpg-bestiary-detail-name { margin: 0 0 6px; font: 600 15px var(--font-display);
  letter-spacing: .04em; color: var(--gold-hi); }
.rpg-bestiary-detail-hint { margin: 0; font: italic 12px var(--font-quill);
  color: var(--ink-mute); }
.rpg-bestiary-detail-head { margin: var(--sp-3) 0 5px; font: 600 10px var(--font-banner);
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft); }
.rpg-bestiary-detail-body { margin: 0; font: 12px var(--font-body); color: var(--ink); }

.rpg-bestiary-stats { display: grid; gap: 3px; }
.rpg-bestiary-stat { display: flex; justify-content: space-between; align-items: baseline;
  gap: 8px; font: 12px var(--font-body); color: var(--ink-soft); }
.rpg-bestiary-stat b { font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  color: var(--ink); }

.rpg-bestiary-drops { display: grid; gap: 4px; }
.rpg-bestiary-drop { display: flex; align-items: center; gap: 7px;
  font: 12px var(--font-body); color: var(--ink); }
.rpg-bestiary-drop-art { flex: none; width: 26px; height: 26px; display: grid; place-items: center;
  border-radius: var(--r-1, 5px); font-size: 14px;
  background: radial-gradient(circle at 50% 34%, var(--plum-hi), var(--plum-lo));
  border: 1px solid var(--gold-deep); }
.rpg-bestiary-drop-art img { width: 22px; height: 22px; object-fit: contain; }
/* A DROP YOU HAVE NEVER HAD keeps its ROW — "there is something else this drops" is the hook — and
   loses everything that would name it: dashed plate, a question mark, muted italic label. The same
   grammar the undiscovered tile uses, so the two masks read as one idea. */
.rpg-bestiary-drop.is-masked .rpg-bestiary-drop-art { background: #4a4468; color: #2a2444;
  border: 1px dashed rgba(255,255,255,.14); }
.rpg-bestiary-drop.is-masked .rpg-bestiary-drop-name { color: var(--ink-mute);
  font-family: var(--font-quill); font-style: italic; }

/* THE DROP ODDS, pushed to the right of the row so the eye reads name-then-number down a column
   rather than hunting for it after a ragged name. Tabular figures and the quill face: it is a
   secondary fact about the drop, not a headline. */
.rpg-bestiary-drop-odds { margin-left: auto; padding-left: 8px; flex: none;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 11px;
  color: var(--ink-quill); opacity: .85; }

/* ---- World map: the ungated column, and what lives on a tile -------------------------------------
   The ungated zones sit in a column of their OWN, to the left of the ten-by-ten board, with nothing
   else in it. The moat used to be a column of the board left undrawn, which read as a missing
   column of the grid — because that is what it was. Being off the board says it properly.
   The tile stays dashed, so its edge says "not on the road network" even when the gutter beside it
   is scrolled out of view. */
.rpg-map-tile.is-loose { border-style: dashed; }

/* ── A DUNGEON ON THE BOARD ───────────────────────────────────────────────────────────────────
   It hangs off the side of its parent with nothing between them, because it is not a neighbour on
   the surface — it is UNDER one. The dashed border says the same thing the ungated tiles' does
   ("not part of the road network"), and the cool inner shadow says which way it goes: a place with
   no daylight in it. */
.rpg-map-tile.is-dungeon { border-style: dashed; border-color: #6a5fa0;
  box-shadow: inset 0 0 14px rgba(8,6,20,.85); }
.rpg-map-tile.is-dungeon.is-here { border-color: var(--gold); }

/* ── THE ROCK A DUNGEON'S OWN GRID IS CUT OUT OF ──────────────────────────────────────────────
   A dungeon is no longer one tile in the gutter: it is a column of them, one per level, and they
   have to read as ONE PLACE with floors rather than as three unrelated squares that happen to be
   stacked. So they sit on a slab.

   It is deliberately unlike anything on the surface board. The board's empty cells are flat and
   pale ("unexplored ground"); this is dark, cold and CHISELLED — a repeating-gradient mortar grid
   at an angle no map tile uses, a hard bevel round the rim, and a rough inner shadow so the middle
   of the slab is deeper than its edge. Drawn one element behind the tiles (`z-index: 0` against
   their auto stacking) and inset a couple of pixels, so a rim of rock shows round the levels like
   a shaft wall round a stair.

   One element spans the whole run rather than one per level, so the mortar carries ACROSS the
   joins instead of restarting at each tile — which is the difference between cut rock and tiling. */
.rpg-map-dungeon-field {
  position: relative; z-index: 0; display: block;
  margin: calc(var(--map-gap) * -1 + 2px) -5px;
  border-radius: 5px;
  border: 1px solid #2c2740;
  background-color: #221e33;
  background-image:
    /* the mortar: two crossing sets of thin light lines on a 22px pitch, offset from each other so
       the blocks are staggered like courses rather than a square grid */
    repeating-linear-gradient(0deg,
      rgba(150,140,190,.13) 0 1px, transparent 1px 22px),
    repeating-linear-gradient(90deg,
      rgba(150,140,190,.10) 0 1px, transparent 1px 34px),
    /* the grain: a fine diagonal tooth, so the flat colour is never quite flat */
    repeating-linear-gradient(58deg,
      rgba(255,255,255,.028) 0 2px, transparent 2px 5px),
    radial-gradient(120% 90% at 50% 40%, #2b263d 0%, #1a1728 72%, #12101d 100%);
  box-shadow:
    inset 0 1px 0 rgba(160,150,200,.16),          /* the top of the cut, catching what light there is */
    inset 0 -1px 0 rgba(0,0,0,.55),
    inset 0 0 26px rgba(6,4,14,.75),              /* the middle is deeper than the rim */
    0 0 0 1px rgba(8,6,20,.6);
}

/* THE LINE DOWN TO IT. Dotted rather than solid, and CRAWLING rather than static: a solid stroke on
   this board means a road, and the one thing this connection is not is a road you walk along the
   surface. The march is slow — it is a hint about what connects to what, not a thing competing with
   the tiles for attention. Drawn only while one end is selected (see `drawDungeonLinks`). */
.rpg-map-link { stroke: var(--gold-hi); stroke-width: 2; stroke-linecap: round;
  stroke-dasharray: 2 7; opacity: .85;
  filter: drop-shadow(0 0 3px rgba(201,164,110,.55));
  animation: rpg-map-crawl 1.4s linear infinite; }
@keyframes rpg-map-crawl { to { stroke-dashoffset: -9; } }
/* THE RING IS THE SAME STROKE AS THE RUN, so the two read as one mark: the dashes cross the gutter
   and keep going round the tile. Sharing `.rpg-map-link` means the width, the dash, the colour and
   the crawl are defined once — a border styled separately would drift the first time either moved. */
.rpg-map-link-ring { }
/* NO MIRRORED KEYFRAME. Reversing the crawl is done by SWAPPING THE LINE'S ENDS in
   `positionLinks` — a decreasing dash offset always runs from (x1,y1) to (x2,y2) — and doing it
   here as well reversed it twice, which is exactly no reversal at all. */
@media (prefers-reduced-motion: reduce) { .rpg-map-link { animation: none; } }

/* THE PICKED TILE. A quieter ring than "you are here" — being here is a fact about the player and
   outranks a selection, so the two must not compete. */
.rpg-map-tile { cursor: pointer; }
.rpg-map-tile.is-picked { outline: 2px solid var(--gold-hi); outline-offset: -2px; }
.rpg-map-tile.is-here.is-picked { outline-color: var(--gold); }

/* WHAT LIVES THERE. Replaces the "Off the road" strip, which said one thing the map itself now
   says better by drawing it off to one side. */
/* A FIXED HEIGHT, so picking a town does not resize the window. Greenwood has nothing hostile in
   it, so its list is one short sentence where the meadow's is a row of cards — and the panel was
   jumping by the difference every time the player clicked between them. Tall enough for one row of
   cards plus the heading; a zone with more creatures than fit wraps and scrolls inside it. */
/* THE BOARD AND ITS ROSTER, SIDE BY SIDE — mirroring the Bestiary, at the owner's request.
   Underneath the map the creature list pushed the window taller than the screen on a tall zone, so
   the panel had to scroll to reach a list that is only ever four or five rows. Beside it, the
   window fits, and the two halves read the way every other two-column panel in the game does.
   `minmax(0, 1fr)` on the map column rather than `1fr`: the viewport inside it is a scroller with
   wide content, and a bare `1fr` lets that content set the column's minimum width, which pushes
   the roster off the right-hand edge. Collapses to one column when there is no room for two. */
.rpg-map-body { display: grid; grid-template-columns: minmax(0, 1fr) 268px;
  gap: var(--sp-4); align-items: start; }
@media (max-width: 900px) { .rpg-map-body { grid-template-columns: minmax(0, 1fr); } }
.rpg-map-lives { min-height: 104px; }
/* Air under the heading. The title sat directly on the row of cards, so the two read as one block
   and the heading looked like a label on the first card rather than on the group. */
.rpg-map-lives .rpg-group-title { margin-bottom: var(--sp-3); }
.rpg-map-lives-empty { margin: 0; font: italic 12px var(--font-quill); color: var(--ink-mute); }
/* THE FAST-TRAVEL BUTTON. Above the "Lives in X" heading it answers, and gold rather than the
   glass every other control here uses — this is the one action on the board that actually MOVES
   the player, and the palette note above reserves gold for exactly that kind of punctuation. */
.rpg-map-travel { display: block; width: 100%; margin: 0 0 var(--sp-3); padding: 8px 12px;
  border-radius: var(--r-2); border: 1px solid var(--gold-deep);
  background: var(--glass-deep); color: var(--gold-hi);
  font: 600 13px var(--font-body); text-align: center; cursor: pointer; }
.rpg-map-travel:hover:not(:disabled) { border-color: var(--gold); color: #fff4de;
  box-shadow: 0 0 14px -2px var(--gold-glow, rgba(201,164,110,.4)); }
/* A REFUSED CONTROL MUST LOOK REFUSED. There was no :disabled rule at all, so the held fast-travel
   button kept its gold text, its full border and `cursor: pointer` — it looked completely live and
   did nothing when clicked. The dim + `not-allowed` is the half a player reads before they click;
   `.rpg-map-held` below is the half that says why. */
.rpg-map-travel:disabled { opacity: .5; cursor: not-allowed; box-shadow: none;
  border-color: var(--gold-deep); }
.rpg-map-held { margin: calc(-1 * var(--sp-2)) 0 var(--sp-3); padding: 0 2px;
  font: 400 12px/1.4 var(--font-body); color: var(--ink-soft, #c3b39a); text-align: center; }
/* A GRID OF FOUR, not a wrapping flex row. Flex laid the cards out by their CONTENT, so whether
   four fitted depended on how long the creatures' names happened to be — "Thistleback" and
   "Bramblebug" together were enough to push the fourth down. Four equal columns is the rule the
   panel width was chosen for, and it holds whatever the names are. */
/* ONE COLUMN, since the roster moved to the side. It was four across when it ran the full width of
   the panel under the map; in a 268px rail four columns would be 60px each, which is narrower than
   the shortest creature name. The note that replaced a wrapping flex row still applies — the count
   is fixed rather than content-driven, so the layout does not change with the names. */
.rpg-map-lives-list { display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-2); align-items: stretch; }
@media (max-width: 900px) {
  .rpg-map-lives-list { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.rpg-map-life { display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 5px 12px 5px 5px; border-radius: var(--r-2);
  border: 1px solid var(--glass-stroke); background: var(--glass-deep);
  font: 13px var(--font-body); color: var(--ink); }
/* Flex for the reason `.rpg-bestiary-art` is — see the note there. */
/* Bigger again (owner's call). These are whole animals seen side-on, and the wasp and the snake in
   particular need the width before there is anything left to recognise. */
.rpg-map-life-art { flex: none; width: 64px; height: 50px;
  display: flex; align-items: center; justify-content: center;
  box-sizing: border-box; padding: 3px; border-radius: var(--r-1, 5px); overflow: hidden;
  background: radial-gradient(circle at 50% 34%, var(--plum-hi), var(--plum-lo));
  border: 1px solid var(--gold-deep); }
/* Same rule, same reason as `.rpg-bestiary-img` above: a percentage height on a grid item in an
   auto row falls back to `auto`, and a square source then overflows a landscape plate. */
.rpg-map-life-art img { width: auto; height: auto; max-width: 100%; max-height: 100%; }
/* AN UNKILLED CREATURE keeps the bestiary's grammar exactly: the same PNG blacked out by
   `.is-silhouette`, the lighter plate under it so a black shape reads, a dashed rim, and no name.
   Two panels, one mask — a creature revealed on the map and hidden in the bestiary would make
   nonsense of both. */
.rpg-map-life.is-unknown .rpg-map-life-art { background: #4a4468;
  border: 1px dashed rgba(255,255,255,.12); }
.rpg-map-life.is-unknown .rpg-map-life-name { color: var(--ink-mute);
  font-family: var(--font-quill); font-style: italic; }
.rpg-map-life-count { font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 11px; color: var(--ink-soft); }
/* The name is the only elastic part of a card, so it is the part that must be allowed to shrink —
   without `min-width: 0` a long name refuses to ellipsis and forces the grid column wider, which
   is the flex behaviour this replaced, reintroduced one level down. */
.rpg-map-life-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* COMPACT ROSTER (owner, 2026-09-22): TWO small cards per row in the 268px rail, monsters and
   resources alike, so the busiest zone's "Lives in" + "Found in" fit the window at its default size
   with no scroll bar. The one-card-per-row list at 64px plates made the window grow past its frame on
   a zone with four creatures and five resources. Gated in `worldMapPanel.test.ts`
   (`MAP_ROSTER_COLUMNS`), which reads THIS rule. */
.rpg-map-lives-list { grid-template-columns: minmax(0, 1fr); gap: 4px; }  /* owner 2026-09-24: one per row again, bigger */
.rpg-map-lives .rpg-group-title { margin-bottom: var(--sp-2); }
.rpg-map-found-title { margin-top: var(--sp-3); }
.rpg-map-life { gap: 5px; padding: 3px 6px 3px 3px; font-size: 11.5px; line-height: 1.2; }
.rpg-map-life-art { width: 60px; height: 46px; padding: 2px; }
.rpg-map-life-count { font-size: 12px; margin-left: auto; }

/* ---- The item card, INLINE ---------------------------------------------------------------------
   `.rpg-itemcard` is authored as the inventory's floating tooltip: absolutely positioned against
   the panel, parked off to the right of it, and `pointer-events: none` so it can never intercept a
   drag. The craft window uses the same component as its OUTPUT BOX, in normal flow inside a column,
   and unmodified it did not appear in that column at all — it hung beside the window where the
   absolute coordinates put it.

   A modifier rather than a second component: the card's job (say everything about one item) is
   identical in both places, and a copy would be a second thing to update the next time an item
   gains a stat. Only the POSITIONING differs, so only the positioning is overridden. */
.rpg-itemcard.is-inline { position: static; transform: none; width: auto;
  pointer-events: auto; margin-bottom: var(--sp-3); }

/* ---- World map: the gates -----------------------------------------------------------------------
   A mark ON the seam between two maps, saying the road between them is really there. Anchored to
   the upper-left cell of the pair (see `portalMarks`) and pushed half its own size plus half the
   gap onto the shared edge, so it straddles the join rather than sitting inside either tile.

   `pointer-events: none`, because the tiles beneath are the click targets and a 16px dot that
   swallowed a press on the seam would be a tile you cannot always select. */
/* SIZED FROM THE TILE, so the mark grows and shrinks with the zoom instead of staying a 14px dot
   that is a landmark on a small board and a speck on a large one. Clamped at both ends: below about
   10px the gradient has nothing to show, and above 26px it starts covering the maps it sits between.
   `--along` is where on the shared edge the gate actually is, 0..1, written by `portalMarks`. */
/* A GLOWING AURA, like the gate itself. The marker was a gold bead with a hard rim — a pin dropped
   on the map — where the thing it stands for is a standing membrane of light with a halo round it.
   It is built the same way the shader builds that: a bright core, a body that keeps its colour, and
   a glow that falls off OUTSIDE the disc rather than stopping at a border.
   No `border`: an outline is the one thing a portal in this game is deliberately not (the gate was
   rebuilt twice to stop reading as a wire hoop). The falloff does the edge.
   OVAL, not round, and upright — the doorway is taller than it is wide, so the mark on the map is
   the same portrait shape at a glance.
   Slightly smaller (owner's call): 0.20 of a tile against 0.26. */
.rpg-map-portal { --mark: clamp(8px, calc(var(--map-tile) * 0.20), 20px); --along: 0.5;
  width: var(--mark); height: calc(var(--mark) * 1.35); border-radius: 50%; z-index: 3;
  pointer-events: none; box-sizing: border-box;
  background:
    radial-gradient(closest-side at 50% 46%,
      rgba(255, 252, 236, .96) 0%,
      rgba(190, 240, 225, .80) 34%,
      rgba(110, 210, 200, .42) 62%,
      rgba(90, 190, 190, .12) 84%,
      rgba(90, 190, 190, 0) 100%);
  box-shadow: 0 0 6px 2px rgba(140, 235, 215, .30), 0 0 14px 5px rgba(110, 210, 200, .16); }
/* East-west: hard against the right edge of its cell and out by half itself plus half the gap, so it
   straddles the join; then DOWN the seam by `--along` of the tile's height. */
.rpg-map-portal.is-h { justify-self: end; align-self: start;
  margin-right: calc(var(--mark) / -2 - var(--map-gap) / 2);
  /* Half the mark's HEIGHT, not its width — the oval is taller than it is wide. */
  margin-top: calc(var(--map-tile) * var(--along) - var(--mark) * 0.675); }
/* North-south: the same, rotated — along the seam is now ACROSS the tile. */
.rpg-map-portal.is-v { justify-self: start; align-self: end;
  margin-bottom: calc(var(--mark) * -0.675 - var(--map-gap) / 2);
  margin-left: calc(var(--map-tile) * var(--along) - var(--mark) / 2); }

/* ---- The party leader's crown --------------------------------------------------------------------
   One mark, three places: the floating party window, the social panel's member list, and every chat
   line the leader speaks. It replaces a "Leader" word-chip in one of those and a gold left edge in
   another — two marks for one fact, and neither of them in chat.
   `flex: none` because two of its three homes are flex rows whose name cell is `1 1 auto`; without
   it the crown is the item that gets squeezed when a name is long. */
.rpg-leader-crown { flex: none; margin-left: 4px; color: var(--gold);
  font-size: 11px; line-height: 1; text-shadow: 0 0 6px var(--gold-glow, rgba(201,164,110,.7)); }

/* The slain mark, left of a creature's kill count. Gold, and appearing on every discovered tile, so
   it must not compete with the number it labels.

   BOTH ON ONE CENTRE LINE. The glyph and the digits come from different families (a symbol face and
   the tabular-numeral mono) with different metrics, so `align-items: center` alone still hung them
   off their own boxes and the mark sat low against the number. `line-height: 1` on both collapses
   each to its own glyph box, which is what makes centring them mean the same thing.

   The mark is 20% larger too: 11px -> 13px, matching the count's own bump (set where the count's
   other type rules are, so the size is not spread across two blocks). */
.rpg-bestiary-count { display: inline-flex; align-items: center; gap: 5px; line-height: 1; }
.rpg-bestiary-slain { color: var(--gold); font-size: 13px; line-height: 1;
  display: inline-flex; align-items: center; }

