/* Map editor chrome. Layered on the shared demo-theme tokens so the tool
   follows the site's light and dark exactly as the landing page does: the
   neutrals (--panel, --line, --muted) are derived from --bg/--text with
   color-mix rather than picked twice. --accent, --ok and --err are fixed hex
   constants instead: brand and status colors that must read the same in both
   themes, and they are used throughout the chrome, not just over the canvas.
   The paint surface (--surface) is also fixed and stays dark in both modes:
   tile colors come from the MATERIALS minimap fields and are tuned against
   dark, and the controls that float over the surface are styled against it
   too. */

:root {
  --panel: color-mix(in srgb, var(--text) 5%, var(--bg));
  --line: color-mix(in srgb, var(--text) 15%, transparent);
  --muted: color-mix(in srgb, var(--text) 55%, transparent);
  --accent: #ffb000;
  --ok: #6fcf6f;
  --err: #e06060;
  --surface: #05070a;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 100dvh;
  overflow: hidden;
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  background: var(--bg);
  color: var(--text);
  font: 13px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
}

/* --- header ------------------------------------------------------------ */
#bar {
  display: flex; align-items: center; gap: .8rem;
  padding: .5rem .8rem;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.brand {
  display: inline-flex; align-items: center; gap: .5rem;
  color: var(--accent); font-weight: 800; font-size: .74rem;
  text-transform: uppercase; letter-spacing: .14em; white-space: nowrap;
}
/* The same mask mechanism the landing page uses, and the same arena floorplan
   icon the Tools card carries, so arriving here lands on the mark you clicked.
   Copied rather than shared: the landing CSS is generated into its page and
   this is a static file. */
.ic {
  display: inline-block; width: 1.5em; height: 1.5em; flex: none;
  background: currentColor;
  -webkit-mask: var(--m) center / contain no-repeat; mask: var(--m) center / contain no-repeat;
}
.ic-map { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='0' y='0' width='12' height='1'/%3E%3Crect x='0' y='11' width='12' height='1'/%3E%3Crect x='0' y='1' width='1' height='10'/%3E%3Crect x='11' y='1' width='1' height='10'/%3E%3Crect x='5' y='1' width='1' height='3'/%3E%3Crect x='5' y='7' width='1' height='4'/%3E%3Crect x='6' y='7' width='5' height='1'/%3E%3Crect x='2' y='7' width='2' height='2'/%3E%3Crect x='2' y='3' width='2' height='1'/%3E%3C/svg%3E"); }

/* The tool and operation icons. Same 12x12 crispEdges mask idiom as .ic-map,
   painted in currentColor, so one definition covers light, dark, the amber
   selected state and the dark-on-amber Play button with no extra asset.
   .ic-dead is the one exception: it leaves crispEdges off on purpose, because
   its X runs diagonally and crisp edges would stair-step it. */
.ic-pen { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='0' y='11' width='1' height='1'/%3E%3Crect x='1' y='10' width='2' height='1'/%3E%3Crect x='2' y='9' width='2' height='1'/%3E%3Crect x='3' y='8' width='2' height='1'/%3E%3Crect x='4' y='7' width='2' height='1'/%3E%3Crect x='5' y='6' width='2' height='1'/%3E%3Crect x='6' y='5' width='2' height='1'/%3E%3Crect x='7' y='4' width='2' height='1'/%3E%3Crect x='8' y='3' width='2' height='1'/%3E%3Crect x='9' y='2' width='2' height='1'/%3E%3Crect x='9' y='1' width='2' height='1'/%3E%3C/svg%3E"); }
.ic-line { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='0' y='9' width='2' height='2'/%3E%3Crect x='2' y='9' width='1' height='1'/%3E%3Crect x='3' y='8' width='1' height='1'/%3E%3Crect x='4' y='7' width='1' height='1'/%3E%3Crect x='5' y='6' width='1' height='1'/%3E%3Crect x='6' y='5' width='1' height='1'/%3E%3Crect x='7' y='4' width='1' height='1'/%3E%3Crect x='8' y='3' width='1' height='1'/%3E%3Crect x='9' y='2' width='1' height='1'/%3E%3Crect x='10' y='1' width='2' height='2'/%3E%3C/svg%3E"); }
.ic-rect { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='1' y='2' width='10' height='1'/%3E%3Crect x='1' y='9' width='10' height='1'/%3E%3Crect x='1' y='2' width='1' height='8'/%3E%3Crect x='10' y='2' width='1' height='8'/%3E%3C/svg%3E"); }
.ic-rectfill { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='1' y='2' width='10' height='8'/%3E%3C/svg%3E"); }
.ic-circle { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='4' y='1' width='4' height='1'/%3E%3Crect x='2' y='2' width='2' height='1'/%3E%3Crect x='8' y='2' width='2' height='1'/%3E%3Crect x='2' y='3' width='1' height='1'/%3E%3Crect x='9' y='3' width='1' height='1'/%3E%3Crect x='1' y='4' width='1' height='4'/%3E%3Crect x='10' y='4' width='1' height='4'/%3E%3Crect x='2' y='8' width='1' height='1'/%3E%3Crect x='9' y='8' width='1' height='1'/%3E%3Crect x='2' y='9' width='2' height='1'/%3E%3Crect x='8' y='9' width='2' height='1'/%3E%3Crect x='4' y='10' width='4' height='1'/%3E%3C/svg%3E"); }
.ic-circlefill { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='4' y='1' width='4' height='1'/%3E%3Crect x='2' y='2' width='8' height='2'/%3E%3Crect x='1' y='4' width='10' height='4'/%3E%3Crect x='2' y='8' width='8' height='2'/%3E%3Crect x='4' y='10' width='4' height='1'/%3E%3C/svg%3E"); }
/* A marquee rather than a solid rectangle, because a zone names an area instead
   of painting one: .ic-rect is already the rectangle that paints. The zoombar's
   .ic-zones is the same marquee with the area inside it filled, so the toggle
   reads as "the wash over that area" rather than as a second zone tool. */
.ic-zone { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='1' y='1' width='3' height='1'/%3E%3Crect x='6' y='1' width='3' height='1'/%3E%3Crect x='1' y='10' width='3' height='1'/%3E%3Crect x='6' y='10' width='3' height='1'/%3E%3Crect x='1' y='2' width='1' height='3'/%3E%3Crect x='1' y='7' width='1' height='3'/%3E%3Crect x='10' y='2' width='1' height='3'/%3E%3Crect x='10' y='7' width='1' height='3'/%3E%3C/svg%3E"); }
.ic-zones { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='1' y='1' width='3' height='1'/%3E%3Crect x='6' y='1' width='3' height='1'/%3E%3Crect x='1' y='10' width='3' height='1'/%3E%3Crect x='6' y='10' width='3' height='1'/%3E%3Crect x='1' y='2' width='1' height='3'/%3E%3Crect x='1' y='7' width='1' height='3'/%3E%3Crect x='10' y='2' width='1' height='3'/%3E%3Crect x='10' y='7' width='1' height='3'/%3E%3Crect x='4' y='4' width='4' height='4'/%3E%3C/svg%3E"); }
.ic-new { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='5' y='1' width='2' height='10'/%3E%3Crect x='1' y='5' width='10' height='2'/%3E%3C/svg%3E"); }
.ic-open { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='0' y='2' width='5' height='1'/%3E%3Crect x='0' y='3' width='12' height='1'/%3E%3Crect x='0' y='3' width='1' height='7'/%3E%3Crect x='11' y='3' width='1' height='7'/%3E%3Crect x='0' y='10' width='12' height='1'/%3E%3C/svg%3E"); }
.ic-save { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='5' y='0' width='2' height='5'/%3E%3Crect x='2' y='5' width='8' height='1'/%3E%3Crect x='3' y='6' width='6' height='1'/%3E%3Crect x='4' y='7' width='4' height='1'/%3E%3Crect x='5' y='8' width='2' height='1'/%3E%3Crect x='0' y='9' width='1' height='3'/%3E%3Crect x='11' y='9' width='1' height='3'/%3E%3Crect x='0' y='11' width='12' height='1'/%3E%3C/svg%3E"); }
.ic-saveas { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='3' y='0' width='2' height='5'/%3E%3Crect x='0' y='5' width='8' height='1'/%3E%3Crect x='1' y='6' width='6' height='1'/%3E%3Crect x='2' y='7' width='4' height='1'/%3E%3Crect x='3' y='8' width='2' height='1'/%3E%3Crect x='0' y='9' width='1' height='3'/%3E%3Crect x='7' y='9' width='1' height='3'/%3E%3Crect x='0' y='11' width='8' height='1'/%3E%3Crect x='10' y='2' width='1' height='3'/%3E%3Crect x='9' y='3' width='3' height='1'/%3E%3C/svg%3E"); }
.ic-delete { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='4' y='0' width='4' height='1'/%3E%3Crect x='2' y='1' width='8' height='1'/%3E%3Crect x='2' y='3' width='1' height='8'/%3E%3Crect x='9' y='3' width='1' height='8'/%3E%3Crect x='2' y='10' width='8' height='1'/%3E%3Crect x='4' y='5' width='1' height='4'/%3E%3Crect x='7' y='5' width='1' height='4'/%3E%3C/svg%3E"); }
.ic-play { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='3' y='1' width='1' height='10'/%3E%3Crect x='4' y='2' width='1' height='8'/%3E%3Crect x='5' y='3' width='1' height='6'/%3E%3Crect x='6' y='4' width='1' height='4'/%3E%3Crect x='7' y='5' width='1' height='2'/%3E%3C/svg%3E"); }
.ic-textures { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='0' y='0' width='4' height='4'/%3E%3Crect x='8' y='0' width='4' height='4'/%3E%3Crect x='4' y='4' width='4' height='4'/%3E%3Crect x='0' y='8' width='4' height='4'/%3E%3Crect x='8' y='8' width='4' height='4'/%3E%3C/svg%3E"); }
.ic-dead { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M0 0h12v2H0zM0 10h12v2H0zM0 2h2v8H0zM10 2h2v8h-2z'/%3E%3Cpath d='M2.7 3.6l1-1 5.7 5.7-1 1zM8.4 2.6l1 1-5.7 5.7-1-1z'/%3E%3C/svg%3E"); }

/* --- tool strip --------------------------------------------------------- */
/* Selection reads exactly like .swatch.sel, so "what is selected" looks the
   same whether it is a tool or a brush. */
.toolstrip { display: grid; grid-template-columns: repeat(3, 1fr); gap: .25rem; margin-bottom: .8rem; }
/* The selected tool's hint (TOOLS in shapes.js), for the one thing an icon
   cannot say: that Shift-drag is how a ceiling opening is closed again. It
   takes the strip's own bottom margin over while it is up, so showing and
   hiding it does not shunt the palette below by a hint's height plus a
   gap. Muted small print, like every other aside in this rail -- it is a
   standing reminder next to a selected tool, not a message. */
.toolstrip:has(+ .toolhint:not([hidden])) { margin-bottom: .3rem; }
.toolhint {
  margin: 0 0 .8rem; font-size: .68rem; line-height: 1.35; color: var(--muted);
}
.toolhint[hidden] { display: none; }
.toolstrip button {
  display: flex; align-items: center; justify-content: center;
  padding: .3rem 0; background: none;
}
.toolstrip button[aria-pressed="true"] {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: var(--accent);
}

/* The layer strip: one button per surface, labelled rather than iconed (the
   tool strip's 3-column icon grid does not fit text), so it uses the same
   loose wrapping row the other editors' .toolset already is, with the tool
   strip's own selected-state look so a pressed layer button and a pressed
   tool button read as one language. */
.toolset { display: flex; flex-wrap: wrap; gap: .25rem; margin-bottom: .8rem; }
.toolset button[aria-pressed="true"] {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: var(--accent);
}

.actions { display: flex; flex-wrap: wrap; gap: .3rem; }

button {
  display: inline-flex; align-items: center; gap: .4rem;
  font: inherit; font-size: .8rem; font-weight: 600;
  color: var(--text);
  background: color-mix(in srgb, var(--text) 8%, transparent);
  border: 1px solid var(--line); border-radius: .3rem;
  padding: .32rem .7rem; cursor: pointer;
  transition: border-color .12s, background .12s;
}
/* An icon inside a control is smaller than the brand mark, which sets .ic to
   1.5em for a heading. */
button .ic { width: 1.15em; height: 1.15em; }
button:hover {
  border-color: color-mix(in srgb, var(--text) 40%, transparent);
  background: color-mix(in srgb, var(--text) 14%, transparent);
}
button.primary {
  background: var(--accent); color: #14171c;
  border-color: var(--accent); font-weight: 800;
}
button.primary:hover { background: #ffc23d; border-color: #ffc23d; }
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}

/* The marker and the badge sit together at the right end of the bar. The
   marker carries the auto margin that pushes the pair over; when it is hidden
   the badge inherits that job. */
#dirty {
  margin-left: auto; font-size: .7rem; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--accent);
}
#dirty[hidden] { display: none; }
#dirty[hidden] + #badge { margin-left: auto; }
#badge { font: 12px var(--mono); color: var(--muted); white-space: nowrap; }
#badge.ok { color: var(--ok); }
#badge.err { color: var(--err); }

/* --- inline notice ------------------------------------------------------ */
/* .err is an actual problem; .info just confirms something worked (e.g. a
   delete), and must not read as a failure. */
#notice {
  margin: 0; padding: .45rem .8rem; font-size: .8rem;
}
#notice.err {
  background: color-mix(in srgb, var(--err) 16%, var(--bg));
  border-bottom: 1px solid color-mix(in srgb, var(--err) 45%, transparent);
}
#notice.info {
  background: color-mix(in srgb, var(--text) 8%, var(--bg));
  border-bottom: 1px solid var(--line);
}
#notice[hidden] { display: none; }

/* --- three columns ------------------------------------------------------ */
#shell { display: grid; grid-template-columns: 200px 1fr 240px; min-height: 0; }
#rail, #inspector { background: var(--panel); overflow: auto; padding: .7rem; }
#rail { border-right: 1px solid var(--line); }
#inspector { border-left: 1px solid var(--line); }
@media (max-width: 900px) { #shell { grid-template-columns: 160px 1fr 200px; } }

input, select, textarea {
  font: inherit; color: var(--text); background: var(--bg);
  border: 1px solid var(--line); border-radius: .25rem; padding: .28rem .45rem;
}
#filter { width: 100%; margin-bottom: .6rem; }

.lab {
  margin: 0 0 .5rem; font-size: .64rem; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; color: var(--muted);
}
.group + .group { margin-top: .9rem; }
/* The inspector's headings separate blocks of controls rather than opening the
   panel, so every one but the first is spaced off what precedes it. */
#inspector .lab { margin-top: 1.1rem; }
#inspector .lab:first-child { margin-top: 0; }

/* The rail's headings open and shut their section, so each is a button wearing
   the same amber disclosure as #drawertoggle below, and the swatches sit in a
   sibling the heading hides. A shut heading drops its bottom margin: with the
   swatches gone, the group gap alone is the whole spacing.

   Baseline rather than #drawertoggle's centre, because a rail heading wraps
   where the drawer's never does: 'Portals (see-through)' takes two lines in a
   200px rail and one in the 160px one, and centring leaves the marker floating
   between them instead of pointing at the line the label starts on. */
.grouphead {
  display: flex; align-items: baseline; gap: .4rem; width: 100%;
  padding: .1rem 0; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: .64rem; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; color: var(--muted);
  text-align: left;
}
.grouphead::before { content: "\25B8"; color: var(--accent); transition: transform .15s; }
.grouphead[aria-expanded="true"]::before { transform: rotate(90deg); }
.grouphead[aria-expanded="false"] { margin-bottom: 0; }
.grouphead:hover { color: var(--text); }
.swatches[hidden] { display: none; }

.swatch {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  padding: .22rem .35rem; border: 0; border-radius: .25rem;
  background: none; color: inherit; font-size: .82rem; font-weight: 400;
  text-align: left; cursor: pointer;
}
.swatch:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
.swatch.sel {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.chip {
  display: flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; flex: none; border-radius: 2px;
  font: 700 10px/1 var(--mono); color: var(--surface); font-style: normal;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .55);
}
/* A prop stands on floor rather than replacing it, so its chip is drawn as a
   marker inset in a cell, matching how the canvas paints one. */
.chip.prop { background-clip: content-box; padding: 3px; }
/* Real art rather than a flat color: the pack's pixels, unsmoothed, filling
   the chip. */
.chip.art {
  background-size: cover; background-position: center;
  image-rendering: pixelated;
}
.chip.prop.art { background-size: contain; background-repeat: no-repeat; }

/* --- selected brush preview --------------------------------------------- */
/* Above the filter rather than below it, so it does not scroll away while the
   user is searching the palette. */
/* One column, art on top: the rail is 200px wide and a tile is square, so a
   full-width swatch reads the material's pattern at a glance where the old
   48px chip could only show its colour. */
#brushpreview {
  display: grid; grid-template-columns: 1fr auto; gap: .1rem .4rem;
  margin-bottom: .9rem;
  padding-bottom: .8rem; border-bottom: 1px solid var(--line);
}
#brushart {
  grid-column: 1 / -1;
  width: 100%; aspect-ratio: 1; border-radius: .25rem;
  background-size: cover; background-position: center;
  image-rendering: pixelated;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .55);
  margin-bottom: .35rem;
}
#brushname { align-self: end; font-size: .86rem; font-weight: 600; word-break: break-all; }
#brushkind {
  align-self: start; font-size: .62rem; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; color: var(--muted);
}
/* Beside the two label lines, spanning both: the shortcut belongs to the
   material as a whole, not to its name or its kind. Hidden on every brush the
   Wall Editor cannot open, and the `auto` column collapses to nothing then,
   so the labels sit exactly where they did before this button existed. */
#brushedit {
  grid-column: 2; grid-row: 2 / 4; align-self: center;
  display: flex; align-items: center; justify-content: center;
  width: 1.7rem; height: 1.7rem; font-size: .8rem;
  color: var(--muted); border: 1px solid var(--line); border-radius: .25rem;
}
#brushedit:hover { color: var(--text); border-color: var(--muted); }
#brushedit[hidden] { display: none; }

/* The Wall Editor's masthead mark, on #brushedit. Kept identical to the
   .ic-brick in assets/css/wall-editor.css and in scripts/releasenotes.js, so
   the button, the landing page's Tools card and the page all three open agree
   on what a wall looks like. */
.ic-brick { --m: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' shape-rendering='crispEdges'%3E%3Crect x='0' y='0' width='5' height='2'/%3E%3Crect x='6' y='0' width='5' height='2'/%3E%3Crect x='0' y='3' width='2' height='2'/%3E%3Crect x='3' y='3' width='5' height='2'/%3E%3Crect x='9' y='3' width='3' height='2'/%3E%3Crect x='0' y='6' width='5' height='2'/%3E%3Crect x='6' y='6' width='5' height='2'/%3E%3Crect x='0' y='9' width='2' height='2'/%3E%3Crect x='3' y='9' width='5' height='2'/%3E%3Crect x='9' y='9' width='3' height='2'/%3E%3C/svg%3E"); }

/* --- canvas ------------------------------------------------------------- */
#stage { position: relative; background: var(--surface); min-width: 0; overflow: hidden; }
#grid {
  display: block; width: 100%; height: 100%;
  image-rendering: pixelated; cursor: crosshair; touch-action: none;
}
#grid.panning { cursor: grabbing; }
#readout, #zoombar { position: absolute; bottom: .6rem; font: 11px var(--mono); }
#readout { left: .7rem; color: #9aa4b2; }
#zoombar { right: .7rem; display: flex; align-items: center; gap: .25rem; }
/* These float over the fixed-dark surface, so they cannot use the themed
   button colors: in light mode those would be dark text on a dark canvas. */
#zoombar button {
  padding: .12rem .5rem; font-size: .72rem;
  color: #d6d7d9; background: rgba(255, 255, 255, .08);
  border-color: rgba(255, 255, 255, .22);
}
#zoombar button:hover {
  background: rgba(255, 255, 255, .18); border-color: rgba(255, 255, 255, .45);
}
/* Off reads as dimmed rather than as a different control: the zoombar floats
   over the fixed-dark surface and cannot use the themed button colors. */
#zoombar #textures[aria-pressed="false"], #zoombar #dead[aria-pressed="false"] { color: #6b727c; }
#zoomlevel { min-width: 3.4em; text-align: center; color: #9aa4b2; }

/* --- inspector ---------------------------------------------------------- */
.field { display: block; margin: 0 0 .5rem; }
.field > span {
  display: block; margin-bottom: .18rem; font-size: .64rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}
.field input, .field select { width: 100%; }
#id { font-family: var(--mono); }
input.bad { border-color: var(--err); }
.size { display: flex; align-items: center; gap: .35rem; }
.size input { width: 3.8rem; font-family: var(--mono); }

/* --- environment --------------------------------------------------------- */
/* div.env, not .env: the swatch buttons also carry the "env" class (for the
   inset outline below) and .swatch already sets display: flex on them; an
   unscoped .env here would outrank it on source order and turn each button
   into a two-column grid, stranding its chip away from its label. */
div.env { display: grid; grid-template-columns: 4.4rem 1fr; gap: .3rem .5rem; align-items: center; }
.envlab {
  font-size: .64rem; letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}
.swatch.env { box-shadow: inset 0 0 0 1px var(--line); }
/* floorpick and ceilpick are buttons, and button already sets display:
   inline-flex as an author rule, which outranks the [hidden] UA rule
   regardless of specificity (author beats UA at equal importance) -- the
   same reason #dirty, #notice and the rest of this file's hideable elements
   all carry their own [hidden] override. skypick and the envlab spans need
   none: nothing here sets display on a <select> or a bare span. */
#floorpick[hidden], #ceilpick[hidden] { display: none; }
/* #brushrow is div.env too (the rail's own surfaceBrush swatch, above the
   palette), hidden on the Walls layer where nothing consumes surfaceBrush --
   same author-beats-UA reason as the pair above, since div.env's display:
   grid would otherwise outrank the [hidden] UA rule. */
#brushrow[hidden] { display: none; }
/* The fog colour's live swatch. A span rather than a button: it shows the
   value, it is not a picker, and the text field beside it is what edits. Fixed
   size so an empty colour (inheriting the sky) still leaves a chip in the row
   rather than collapsing it -- .amb's flex would otherwise give a zero-width
   span no box at all, and the row would jump every time the field was cleared.
   `flex: none` because .amb input takes flex: 1 and would squeeze it away. */
.fogsw {
  flex: none; width: 1.1rem; height: 1.1rem; border-radius: 2px;
  background: var(--panel);
}
.amb { display: flex; align-items: center; gap: .4rem; }
.amb input { flex: 1; min-width: 0; }
.amb output { font-family: var(--mono); font-size: .74rem; color: var(--muted); }
/* --- interior context menu ----------------------------------------------- */
/* Positioned in JS at the click, so only the look lives here. Sits above the
   canvas and below the modal backdrop, which is a native top-layer thing and
   needs no z-index of its own. Every colour is one of the theme-derived
   neutrals, so the panel reads in light and dark alike; the drop shadow is the
   only fixed value, and a soft black reads as depth over either background. */
#ctxmenu {
  position: fixed;
  z-index: 40;
  min-width: 11rem;
  max-height: 60vh;
  overflow: auto;
  padding: .3rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .35);
}
#ctxmenu[hidden] { display: none; }
#ctxmenu .lab { margin: .1rem .3rem .3rem; }
#ctxmenu button {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  padding: .25rem .4rem;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
#ctxmenu button:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
#ctxmenu button.sel { background: color-mix(in srgb, var(--text) 14%, transparent); }
#ctxmenu .kc { width: .8rem; height: .8rem; border-radius: 3px; flex: none; }
#ctxmenu .none { color: var(--muted); padding: .25rem .4rem; font-size: .78rem; }

/* --- zone list ------------------------------------------------------------ */
/* One row per zone: a pick button sized to the label plus two fixed-width
   action buttons, so a long zone name never pushes Rename/Delete off the
   panel's edge. */
#zonelist .none { color: var(--muted); font-size: .78rem; }
.zonerow {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: .3rem;
  align-items: center;
  padding: .18rem 0;
}
.zonerow + .zonerow { border-top: 1px solid var(--line); }
/* Reuses the ctxmenu selected-state colour rather than a second one: the same
   "this row is what the canvas highlights" meaning, in a different list. */
.zonerow.sel { background: color-mix(in srgb, var(--text) 14%, transparent); }
.zonepick {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: .22rem .35rem; border: 0; border-radius: .25rem;
  background: none; color: inherit; font: inherit; font-size: .78rem;
  text-align: left; cursor: pointer;
}
.zonepick:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
.zonerow button:not(.zonepick) {
  padding: .18rem .4rem; font-size: .74rem;
  border: 1px solid var(--line); border-radius: .25rem;
  background: var(--bg); color: inherit; cursor: pointer;
}
.zonerow button:not(.zonepick):hover {
  background: color-mix(in srgb, var(--text) 8%, transparent);
}

/* Sits beside the Rules button rather than inside #zonelist or #ctxmenu, so it
   needs its own rule rather than inheriting one of theirs -- same muted,
   small-print treatment as the other machinery counts in this rail. */
#rulecount.none { margin: .3rem 0 0; font-size: .78rem; color: var(--muted); }

#stats { margin: 0; font-size: .8rem; color: var(--muted); white-space: pre-wrap; }
#stats.err { color: var(--err); }
.note {
  margin: 1.1rem 0 0; padding-top: .7rem;
  border-top: 1px solid var(--line); font-size: .74rem; color: var(--muted);
}

/* --- raw drawer --------------------------------------------------------- */
#drawer { background: var(--panel); border-top: 1px solid var(--line); }
#drawertoggle {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  padding: .45rem .8rem; border: 0; border-radius: 0; background: none;
  font-size: .64rem; font-weight: 700; letter-spacing: .2em;
  text-transform: uppercase; color: var(--muted);
}
#drawertoggle:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
/* The same amber disclosure the landing page puts on <summary>. */
#drawertoggle::before { content: "\25B8"; color: var(--accent); transition: transform .15s; }
#drawertoggle[aria-expanded="true"]::before { transform: rotate(90deg); }
#raw {
  display: block; width: 100%; height: 28vh; resize: vertical;
  border: 0; border-top: 1px solid var(--line); border-radius: 0;
  padding: .6rem .8rem; font: 12px/1.45 var(--mono);
  background: color-mix(in srgb, var(--text) 6%, var(--bg));
}
#raw[hidden] { display: none; }

/* --- dialogs ------------------------------------------------------------ */
#modal {
  color: var(--text); background: var(--panel);
  border: 1px solid var(--line); border-radius: .5rem;
  padding: 1rem 1.1rem; min-width: 21rem; max-width: 34rem;
}
#modal::backdrop { background: rgba(0, 0, 0, .55); }
#modal h2 {
  margin: 0 0 .9rem; font-size: .74rem; font-weight: 800;
  letter-spacing: .2em; text-transform: uppercase; color: var(--accent);
}
#modal .row { display: flex; align-items: center; gap: .6rem; margin: 0 0 .5rem; }
#modal .row > span { min-width: 4.5rem; font-size: .78rem; color: var(--muted); }
#modal .row input, #modal .row select { flex: 1; }
#modal .err { margin: .4rem 0 0; font-size: .78rem; color: var(--err); }
#modal .err:empty { display: none; }
#modal .buttons { display: flex; justify-content: flex-end; gap: .4rem; margin-top: 1rem; }
.picklist { max-height: 46vh; overflow: auto; }
.picklist .lab { margin-top: .8rem; }
.picklist > .lab:first-child { margin-top: 0; }
.pick {
  display: flex; align-items: baseline; gap: .6rem; width: 100%;
  padding: .3rem .5rem; border: 0; border-radius: .25rem;
  background: none; text-align: left; font-weight: 400;
}
.pick:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.pick .pid { font-family: var(--mono); font-size: .8rem; }
.pick .pname { color: var(--muted); font-size: .78rem; }
.pick .pbad { margin-left: auto; font-size: .7rem; color: var(--err); }
.empty { margin: 0 0 .2rem; padding: .3rem .5rem; font-size: .78rem; color: var(--muted); }

/* --- rules modal --------------------------------------------------------- */
/* Two panes: the rules on the left, the selected one built on the right. Wider
   than the other dialogs because a token row is a line of prose in dropdowns
   ("door 4,2 unlock") and wrapping it mid-sentence is what makes it stop
   reading as one. Selected by :has rather than by a class on #modal, because
   shell() clears the dialog's children and not its className -- a class left
   behind would widen the next dialog to use the element. */
#modal:has(.rulespane) { max-width: 48rem; }
.rulespane {
  display: grid; grid-template-columns: 15rem 1fr; gap: .8rem;
  max-height: 56vh; overflow: auto;
}
.rulelist { border-right: 1px solid var(--line); padding-right: .8rem; }
.ruleedit { min-width: 0; }
.ruleedit .none { margin: 0; font-size: .78rem; color: var(--muted); }
.ruleedit .lab { margin-top: 1.1rem; }
.ruleedit .lab:first-child { margin-top: 0; }

/* One row per rule, laid out like .zonerow: a pick button sized to the label
   plus a fixed-width action, so a long event line never pushes Delete off. */
.rulerow { display: grid; grid-template-columns: 1fr auto; gap: .3rem; align-items: center; }
.rulerow + .rulerow { border-top: 1px solid var(--line); }
/* The ctxmenu and zone-list selected-state colour, a third time: same "this row
   is what the other pane is showing" meaning, in one more list. */
.rulerow.sel { background: color-mix(in srgb, var(--text) 14%, transparent); }
.rulepick {
  display: flex; align-items: baseline; gap: .4rem; width: 100%; min-width: 0;
  padding: .22rem .35rem; border: 0; border-radius: .25rem;
  background: none; color: inherit; font: inherit; font-size: .78rem;
  text-align: left; cursor: pointer;
}
.rulepick:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
.rulepick .ron {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--mono);
}
/* The event line is the part that gives, so the count and the badge stay whole:
   a "2 steps" broken across two lines makes the row taller than its neighbours
   and reads as a second rule. */
.rulepick .rsteps { margin-left: auto; font-size: .7rem; color: var(--muted); white-space: nowrap; }
.rulepick .rbad { font-size: .7rem; color: var(--err); white-space: nowrap; }

/* One line of the rule, wrapping only when the dialog is too narrow to hold it.
   The fields sit at their natural width so each is as wide as the longest thing
   it can say, which keeps the row reading as the line it writes. */
.tokenrow { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; min-width: 0; }
.tokenrow select, .tokenrow input { padding: .18rem .3rem; font-size: .78rem; max-width: 14rem; }
/* The typed kinds are a cell and a number of seconds, both short. Sized to what
   they hold rather than to the input default, so the longest row -- `sfx boom
   at 6,6`, five fields -- still fits on one line. */
.tokenrow input { width: 6rem; }
/* A typed field carries its own grammar beside it: muted while the field is
   merely empty, and in --err once what is in it cannot be parsed. input.bad,
   defined with the inspector above, colours the border in the second case. */
.typed { display: flex; align-items: center; gap: .3rem; }
.why { font-size: .7rem; color: var(--muted); }
.why.bad { color: var(--err); }
.why:empty { display: none; }
/* A stale value: the rule still says it, and the mark says the map no longer
   has it. Border only, so the text stays readable. */
.tokenrow .orphan { border-color: var(--err); }
.tokenrow .lit { font-family: var(--mono); font-size: .78rem; color: var(--muted); }
/* A kind with an empty list reads as a sentence, not as a control: there is
   nothing here to operate, and the fix is out in the map. */
.tokenrow .nokind { font-size: .74rem; font-style: italic; color: var(--err); }

.steprow { display: grid; grid-template-columns: 1fr auto; gap: .3rem; align-items: center; margin-bottom: .3rem; }
.once { display: flex; align-items: center; gap: .4rem; margin-top: .4rem; font-size: .78rem; }
.once input { padding: 0; }
/* Scoped to the pane rather than left as a bare .small, the way the zone list
   scopes its own actions to .zonerow: nothing else claims the class today, and
   a global one in a shared stylesheet is a collision waiting for the next
   dialog that wants a small button of its own. */
.rulespane button.small {
  padding: .18rem .4rem; font-size: .74rem;
  border: 1px solid var(--line); border-radius: .25rem;
  background: var(--bg); color: inherit; cursor: pointer;
}
.rulespane button.small:hover:not(:disabled) {
  background: color-mix(in srgb, var(--text) 8%, transparent);
}
.rulespane button.small:disabled { color: var(--muted); cursor: default; }
.rulespane button.small.add { margin-top: .5rem; }

/* --- material picker ----------------------------------------------------- */
.swatchgrid { max-height: 46vh; overflow: auto; }
.swatchgrid .lab { margin-top: .8rem; }
.swatchgrid > .swatch:first-child { margin-bottom: .2rem; }
.swatchgrid .row {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); gap: .15rem;
}

/* --- play overlay -------------------------------------------------------- */
/* Not a <dialog>: showModal() would make the editor inert and the whole point
   is painting while the game runs. Fixed rather than absolute so it does not
   move with the canvas scroll.

   The z-index sits above the rail, the readout and the notice (none of which
   set one, so any positive value clears them) and BELOW #ctxmenu at 40. That
   ordering is load bearing in exactly one state, and it is a state this
   feature creates: minimized, the pane is a corner window with a sealed frame,
   so a right-click aimed at the map under that corner passes through and opens
   the interior menu -- which, above the pane, would open behind it and be
   unreadable and unclickable. Lowering this rather than raising #ctxmenu keeps
   that element's own rule ("above the canvas and below the modal backdrop")
   true for every other page it serves. A dialog's top layer is the browser's
   and is above both regardless. */
.playpane {
  position: fixed; z-index: 30;
  inset: 3vh 3vw;
  display: flex; flex-direction: column;
  background: var(--bg); border: 1px solid var(--line); border-radius: .4rem;
  box-shadow: 0 1rem 3rem rgba(0, 0, 0, .5);
  overflow: hidden;
}
/* Minimized: a corner window, still running. `inset: auto` first, or the full
   size's four edges keep winning against two of these. */
.playpane.mini {
  inset: auto 1rem 1rem auto;
  width: min(28rem, 40vw); height: min(18rem, 30vh);
}
.pp-bar {
  display: flex; align-items: center; gap: .5rem;
  padding: .3rem .5rem;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  border-bottom: 1px solid var(--line);
  font-size: .8rem;
}
.pp-title { font-weight: 600; }
.pp-status { color: var(--muted); font-size: .74rem; margin-right: auto; }
.pp-status.bad { color: var(--err); }
.pp-bar button {
  padding: .18rem .5rem; font-size: .74rem;
  border: 1px solid var(--line); border-radius: .25rem;
  background: var(--bg); color: inherit; cursor: pointer;
}
.pp-bar button:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
.pp-frame { flex: 1; width: 100%; border: 0; display: block; background: #000; }
/* The input seal. Minimized, the frame takes no pointer, so a click aimed at
   the map behind the corner window reaches the map; the titlebar is outside
   the seal, which is what leaves something to click to come back. */
.playpane.mini .pp-frame { pointer-events: none; }

@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
