/* The two families, vendored under vendor/fonts with their OFL licences
   (ts/tests/vendor.test.ts holds them to @fontsource 5.3.0). Each weight
   comes in two files: latin, and latin-ext, where Polish lives. No CDN: the
   composer runs from a tarball. */
@font-face { font-family: "Figtree"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("vendor/fonts/figtree/figtree-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family: "Figtree"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("vendor/fonts/figtree/figtree-latin-ext-400-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family: "Figtree"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("vendor/fonts/figtree/figtree-latin-500-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family: "Figtree"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("vendor/fonts/figtree/figtree-latin-ext-500-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family: "Figtree"; font-style: normal; font-weight: 600; font-display: swap;
  src: url("vendor/fonts/figtree/figtree-latin-600-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family: "Figtree"; font-style: normal; font-weight: 600; font-display: swap;
  src: url("vendor/fonts/figtree/figtree-latin-ext-600-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family: "Figtree"; font-style: normal; font-weight: 700; font-display: swap;
  src: url("vendor/fonts/figtree/figtree-latin-700-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family: "Figtree"; font-style: normal; font-weight: 700; font-display: swap;
  src: url("vendor/fonts/figtree/figtree-latin-ext-700-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("vendor/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("vendor/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("vendor/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("vendor/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 700; font-display: swap;
  src: url("vendor/fonts/jetbrains-mono/jetbrains-mono-latin-700-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 700; font-display: swap;
  src: url("vendor/fonts/jetbrains-mono/jetbrains-mono-latin-ext-700-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }

/* Every value the page is drawn with lives here: the dark theme, cygnus, in
   :root, and the light one, wireframe, as the overrides below it. The names
   say what a value means, never how light it is — `--paper` is the surface a
   tile sits on, whichever way round the page is that day. `--lit` is what
   answers the pointer and what is selected; the translucent edges and mists
   are mixed from it, so a theme that changes it changes them. */
:root {
  color-scheme: dark;

  /* Type. Two families, and the sizes they are set at. */
  --text: "Figtree", system-ui, sans-serif;
  --fixed: "JetBrains Mono", ui-monospace, "DejaVu Sans Mono", "Liberation Mono", monospace;
  --size-heading: 1.1rem;
  --size-subheading: 1rem;
  --size-body: 14px;
  --size-prose: 13px;   /* the description, and the command line */
  --size-small: 12px;   /* vnums, the trail, a tile's text, a panel's heading */
  --size-fine: 11px;    /* a tile's direction, vnum and door; a key cap */
  --size-tab: 16px;     /* the screens' tabs */
  --size-plus: 30px;    /* the plus on a way that can be made */
  --r: 8px;             /* a control's corners; a panel's are half as round again */

  /* Surfaces, from the page up. */
  --page: #0a0a0a;
  --page-wash: radial-gradient(ellipse 80% 60% at 50% 0%, #17110a, #0a0a0a 70%);
  --paper: #17120b;     /* raised off the page: tiles, the hub, small buttons */
  --ground: #120e08;    /* sunk into it */
  --panel: linear-gradient(180deg, #1a140c, #130f09);
  --panel-shadow: inset 0 1px 0 rgb(245 230 192 / .04), 0 8px 24px rgb(0 0 0 / .45);
  --tab: linear-gradient(180deg, #1c150b, #0a0a0a);  /* the active tab, joined to the page */
  --tile: linear-gradient(180deg, #221a0e, #1a140b);
  --tile-selected: linear-gradient(180deg, #33250c, #22190b);
  --hub: #16110a;
  --hub-shadow: 0 0 0 1px rgb(0 0 0 / .6), 0 10px 30px rgb(0 0 0 / .55), 0 0 40px rgb(250 204 21 / .06);
  --rose-ground: radial-gradient(ellipse at 50% 45%, #21180c, #130f09 70%);

  /* Marks. */
  --ink: #f5e6c0;       /* text, and the strokes drawn as firmly as text */
  --line: rgb(202 138 4 / .22); /* a border that only separates */
  --band: #7d5f26;      /* the rose's bands, the margin rule at column 79 */
  --band-lit: #d9a52a;  /* the band of the way being edited */
  --band-dim: #4a3818;  /* a door card's band, under its door */
  --info: #c9ab6f;      /* read second: vnums, counts, the prompt */
  --absent: #8a7148;    /* what is not there: an empty tile, a closed way */
  --absent-dim: #5c4726; /* the same, at glyph size */
  --passage: #f5b534;   /* what is there and can be walked */
  --error: #e0876a;
  --warning: var(--note-ink);
  --backdrop: rgb(0 0 0 / .75); /* the page behind a dialog, dimmed */

  /* What answers. */
  --lit: #facc15;
  --accent: #ca8a04;    /* a pressed button's fill */
  --on-accent: #0a0a0a; /* text on it */
  --edge: color-mix(in srgb, var(--accent) 25%, transparent);
  --edge-hover: color-mix(in srgb, var(--lit) 50%, transparent);
  --mist: color-mix(in srgb, var(--lit) 5%, transparent);
  --mist-strong: color-mix(in srgb, var(--lit) 12%, transparent);
  --glow: 0 0 0 3px rgb(250 204 21 / .2), 0 0 26px rgb(250 204 21 / .38);
  --glow-soft: 0 0 0 1px rgb(250 204 21 / .12), 0 0 30px rgb(250 204 21 / .12);

  /* A warning is a note: a framed chip, not a colour. */
  --note-bg: #1c1206;
  --note-line: #5c4019;
  --note-ink: #fcd34d;

  /* The rose's drawing. */
  --chevron: rgb(245 230 192 / .38); /* a way that can be walked */
  --chevron-lit: #fff6d6;            /* the same, on the way being edited */
  --door-leaf: #fff3d6;
  --door-halo: #0b0805;              /* under the leaf, so it stands off the band */
  --glyph-on: #fde68a;               /* a lit spoke of the exit glyph */

  /* The game console. What the server prints is drawn in a client's colours:
     the numbers come from game.json, their looks from a palette file, and
     these are the default palette's (data/palettes/mudlet.json) so the
     console is right before that file arrives. palette.ts sets them. */
  --ansi-0: #000000;
  --ansi-1: #800000;
  --ansi-2: #008000;
  --ansi-3: #808000;
  --ansi-4: #000080;
  --ansi-5: #800080;
  --ansi-6: #008080;
  --ansi-7: #c0c0c0;
  --ansi-8: #808080;
  --ansi-9: #ff0000;
  --ansi-10: #00ff00;
  --ansi-11: #ffff00;
  --ansi-12: #0000ff;
  --ansi-13: #ff00ff;
  --ansi-14: #00ffff;
  --ansi-15: #ffffff;
  --ansi-fg: #c0c0c0;
  --ansi-bg: #000000;
  --ansi-command: #717100;
  /* The editor's own marks on that dark ground; no palette changes them. */
  --screen-info: #8a8f98;
  --screen-accent: #e6b450;
  --screen-edge: #3a3f47;
  --screen-margin: #3a3f47;  /* where a classic terminal's 80 columns end */
  --screen-track-on: #6b5a2e; /* a switch's track when it is on, under the accent knob */
  --screen-sun: #e6b450;
  --screen-moon: #b8c4d9;
  /* The console's lower edge: a brass rail, and the shadow it casts on the
     editor below. Not themed — the console keeps its look in both. */
  --screen-rail: linear-gradient(180deg, #1e160a 0%, #6e5220 30%, #a8843c 50%, #6e5220 70%, #1e160a 100%);
  --screen-cast: 0 30px 70px 30px rgb(0 0 0 / .8);
  --screen-rail-shadow: 0 2px 3px rgb(0 0 0 / .7);
}

/* The light theme. The same fonts and shapes; only what is overridden here
   changes. The rose's values (chevrons, doors, glow) were not mocked and are
   set by eye — see the plan's Task 11. */
html[data-theme="wireframe"] {
  color-scheme: light;
  --r: 6px;
  --page: #f4f4f5;
  --page-wash: none;
  --paper: #ffffff;
  --ground: #fafafa;
  --panel: #ffffff;
  --panel-shadow: 0 1px 2px rgb(0 0 0 / .06);
  --tab: #f4f4f5;
  --tile: #ffffff;
  --tile-selected: #eef2f7;
  --hub: #ffffff;
  --hub-shadow: 0 1px 3px rgb(0 0 0 / .08);
  --rose-ground: #ffffff;
  --ink: #1f2023;
  --line: #d6d8dc;
  --band: #9aa0a8;
  --band-lit: #33557a;
  --band-dim: #d6d8dc;
  --info: #6b6f76;
  --absent: #9aa0a8;
  --absent-dim: #c8ccd2;
  --passage: #003366;
  --error: #8a4b4b;
  --backdrop: rgb(0 0 0 / .3);
  --lit: #003366;
  --accent: #2b2d31;
  --on-accent: #ffffff;
  --glow: 0 0 0 3px rgb(0 51 102 / .15), 0 0 14px rgb(0 51 102 / .2);
  --glow-soft: 0 0 0 1px rgb(0 51 102 / .1);
  --note-bg: #fbf7e6;
  --note-line: #ece2b8;
  --note-ink: #7a6a2e;
  --chevron: rgb(31 32 35 / .3);
  --chevron-lit: #ffffff;
  --door-leaf: #1f2023;
  --door-halo: #ffffff;
  --glyph-on: #003366;
}

body {
  font: var(--size-body)/1.5 var(--text);
  background-color: var(--page);
  background-image: var(--page-wash);
  background-repeat: no-repeat;
  color: var(--ink);
  margin: 1rem 1.5rem;
}

h1 { font-size: var(--size-heading); margin: 0; }
h2 { font-size: var(--size-heading); margin: 0 0 0.5rem; }

.quiet { color: var(--info); }
.note { color: var(--warning); }
.error { color: var(--error); }
.warning { color: var(--warning); }
.info { color: var(--info); }

/* Controls are painted. A button is quiet at rest — a mist under a faint
   edge — lit on hover, and full accent when pressed; a field takes a faint
   edge that brightens on focus with a ring. `:where` keeps all of it at no
   specificity, so a component's own rule always wins; the buttons that are
   something else — a tile, a row, a tab, a name that walks, the console's —
   are left out entirely. */
:where(button, .file-button):not(:where(.walk, .group, .tile, .go, .person, .daytime)):not(:where(.item-list li, .screens, .side-switch, .search li, .popup li, .trail, section.console) *) {
  font: inherit;
  font-size: var(--size-small);
  color: color-mix(in srgb, var(--lit) 75%, transparent);
  background: color-mix(in srgb, var(--lit) 6%, transparent);
  border: 1px solid color-mix(in srgb, var(--lit) 20%, transparent);
  border-radius: var(--r);
  padding: 0.4rem 0.85rem;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
:where(button, .file-button):not(:where(.walk, .group, .tile, .go, .person, .daytime)):not(:where(.item-list li, .screens, .side-switch, .search li, .popup li, .trail, section.console) *):hover:not(:disabled) {
  background: var(--mist-strong);
  border-color: var(--edge-hover);
  color: var(--lit);
}
:where(button):disabled { opacity: 0.4; cursor: default; }
:where(button)[aria-pressed="true"]:not(:where(.screens *)) {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
:where(button.danger) {
  color: var(--error);
  background: color-mix(in srgb, var(--error) 6%, transparent);
  border-color: color-mix(in srgb, var(--error) 30%, transparent);
}
:where(button.danger):hover:not(:disabled) {
  color: var(--error);
  background: color-mix(in srgb, var(--error) 12%, transparent);
  border-color: color-mix(in srgb, var(--error) 60%, transparent);
}
:where(select, textarea, input:not([type="checkbox"], [type="radio"], [type="file"])) {
  font: inherit;
  color: var(--ink);
  background: color-mix(in srgb, var(--lit) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--lit) 14%, transparent);
  border-radius: calc(var(--r) - 1px);
  padding: 0.35rem 0.6rem;
  transition: border-color 0.12s, box-shadow 0.12s;
}
/* A checkbox and a radio keep the browser's own focus outline, so the
   keyboard can see where it is; the console's controls keep the console's look. */
:where(select, textarea, input:not([type="checkbox"], [type="radio"])):not(:where(section.console *)):hover {
  border-color: color-mix(in srgb, var(--lit) 30%, transparent);
}
:where(select, textarea, input:not([type="checkbox"], [type="radio"])):not(:where(section.console *)):focus {
  outline: none;
  border-color: color-mix(in srgb, var(--lit) 60%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--lit) 12%, transparent);
}
:where(select) option { background: var(--paper); color: var(--ink); }
:where(input[type="checkbox"], input[type="radio"]) { accent-color: var(--accent); }
:where(legend) {
  color: var(--info);
  font-size: var(--size-small);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0 0.4rem;
}
:where(fieldset) { border: 1px solid var(--edge); border-radius: calc(var(--r) + 2px); }

/* A warning is a note: a small framed chip, so it differs from what can be
   clicked by its shape and not only its colour. Gold means click and
   selected; red stays out, because a warning is not a mistake. */
:is(.range-note, .flag-caveat, .findings li.warning, .lint > .warning) {
  width: fit-content;
  justify-self: start;
  padding: 0.2rem 0.55rem;
  background: var(--note-bg);
  border: 1px solid var(--note-line);
  border-radius: calc(var(--r) - 3px);
  color: var(--note-ink);
  list-style: none;
}
.lint > .warning button.group { color: var(--note-ink); }
.findings { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.35rem; }

/* Every region of a screen is a panel: a brown surface, a translucent gold
   edge that brightens on hover, rounded corners and a soft shadow. A panel
   whose first child is a heading carries it as a small-caps strip over a
   hairline; no panel is given a heading the editor did not already show. */
.panel {
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: calc(var(--r) * 1.5);
  box-shadow: var(--panel-shadow);
  padding: 0 1rem 1rem;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.panel:hover { border-color: color-mix(in srgb, var(--lit) 38%, transparent); }
.panel:not(:has(> :is(.head, h2, h3):first-child)) { padding-top: 1rem; }
.panel > :is(.head, h2, h3):first-child {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  margin: 0 -1rem 0.85rem;
  padding: 0.65rem 1rem;
  font-size: var(--size-small);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--info);
  background: var(--mist);
  border-bottom: 1px solid var(--line);
  border-radius: calc(var(--r) * 1.5) calc(var(--r) * 1.5) 0 0;
}
.panel > :is(.head, h2, h3):first-child :is(.count, .quiet) {
  color: var(--absent);
  letter-spacing: 0.05em;
  font-weight: 500;
}
/* A mob's and an object's groups of numbers are panels too; their legend is the heading. */
form.record :is(.numbers, .tail) .group {
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: calc(var(--r) * 1.5);
  box-shadow: var(--panel-shadow);
  padding: 0.5rem 1rem 1rem;
}
.centre { display: flex; flex-direction: column; gap: 1rem; }
form.room { gap: 1rem; }

header {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
}


main {
  display: grid;
  /* The room list, the rose, and the editing column. No narrower layout is
     designed: the description must stay 79 columns wide, or the margin misleads. */
  grid-template-columns: 16rem max-content minmax(18rem, 1fr);
  gap: 1.5rem;
  align-items: start;
  padding: 1rem 0;
}

button.walk {
  font: inherit;
  color: var(--passage);
  background: none;
  border: none;
  border-bottom: 1px dotted currentColor;
  padding: 0 0.1rem;
  cursor: pointer;
}

/* The lists: rooms, and mobs */
.item-list {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  position: sticky;
  top: 0.5rem;
  max-height: calc(100vh - 6rem);
}
.item-list input { font: inherit; padding: 0.25rem; }
.item-list ol { list-style: none; margin: 0; padding: 0; overflow-y: auto; }
.item-list li button {
  font: inherit;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  width: 100%;
  padding: 0.1rem 0.25rem;
  border: none;
  border-left: 3px solid transparent;
  background: none;
  text-align: left;
  cursor: pointer;
}
.item-list li button {
  border-radius: calc(var(--r) - 1px);
  color: color-mix(in srgb, var(--ink) 80%, transparent);
  transition: background 0.1s;
}
.item-list li button:hover { background: var(--mist); color: var(--ink); }
.item-list li.current button {
  background: var(--mist-strong);
  border-left-color: var(--lit);
  color: var(--lit);
}
.item-list .vnum { font-family: var(--fixed); font-size: var(--size-small); color: var(--info); }
.item-list .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.item-list .type { margin-left: auto; font-size: var(--size-small); color: var(--info); white-space: nowrap; }

/* The mob screen: the list and one mob. A mob has no geography, so no rose. */
main.mob-screen, main.object-screen { grid-template-columns: 16rem minmax(0, 1fr); }
/* The text on the left, as wide as the 79-column description makes it; the
   declension beside it, one case under another; everything else across both. */
form.record {
  display: grid;
  grid-template-columns: max-content minmax(14rem, 20rem);
  gap: 0.6rem 2rem;
  align-items: start;
}
form.record > :not(.texts):not(.cases) { grid-column: 1 / -1; }
form.record .texts { display: flex; flex-direction: column; gap: 0.6rem; }
form.record .texts input { width: 100%; box-sizing: border-box; }
form.record label { display: flex; flex-direction: column; gap: 0.15rem; }
form.record label, form.record .caption { font-size: var(--size-small); color: var(--info); }
form.record input { font: inherit; font-size: var(--size-body); color: var(--ink); padding: 0.25rem; }
form.record .mob-vnum { font-family: var(--fixed); color: var(--info); margin: 0; }
form.record .mob-head { display: flex; align-items: baseline; gap: 1rem; }
form.record > .danger { justify-self: start; }
form.record .room-line { font-family: var(--fixed); font-size: var(--size-prose); width: 81ch; max-width: 100%; }
form.record .mob-description { display: flex; flex-direction: column; gap: 0.15rem; }
form.record .cases input { width: 100%; box-sizing: border-box; }
form.record .cases .question { font-style: italic; margin-left: 0.3rem; }
form.record .cases {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  border: 1px solid var(--line);
  margin: 0;
  /* Level with the name field, below the vnum. */
  margin-top: 1.5rem;
}
/* The numbers, in groups; each group's fields flow across as a grid. */
form.record .numbers { display: flex; flex-direction: column; gap: 0.8rem; }
form.record :is(.numbers, .tail) .group { margin: 0; }
form.record :is(.numbers, .tail) .fields {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
  gap: 0.4rem 1rem;
}
form.record :is(.numbers, .tail) .field input,
form.record :is(.numbers, .tail) .field select { width: 100%; box-sizing: border-box; font: inherit;
  font-size: var(--size-body); color: var(--ink); padding: 0.25rem; }
/* A value the editor will not store — half-typed dice, a negative coin —
   marked while it is being typed, focus ring included. */
form.record :is(.numbers, .tail) input:invalid { border-color: var(--error); outline-color: var(--error); }
/* Outside a range the codex only warns about: stored, and said so. */
form.record :is(.numbers, .tail) .beyond input { border-color: var(--warning); }
form.record :is(.numbers, .tail) .range-note { color: var(--warning); font-size: var(--size-small); }
form.record .numbers .readonly { display: flex; flex-direction: column; gap: 0.15rem; }
form.record .numbers .readonly .label { font-size: var(--size-small); color: var(--info); }
form.record .numbers .readonly .value { font-family: var(--fixed); padding: 0.25rem 0; }
form.record .numbers .readonly.wide { grid-column: 1 / -1; }
/* A spell is a field the size of a number, saying the spell as a list
   would; its searched list opens over the page under it, so opening it moves
   none of the values beside it. */
.spell-picker { position: relative; display: flex; flex-direction: column; gap: 0.15rem; }
.spell-picker > .label { font-size: var(--size-small); color: var(--info); }
.spell-picker .chosen {
  font: inherit;
  font-size: var(--size-body);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--band);
  padding: calc(0.25rem + 1px) 0.25rem;
  text-align: left;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  gap: 0.3rem;
  min-width: 0;
}
.spell-picker .chosen::after { content: "▾"; color: var(--info); }
.spell-picker .chosen .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spell-picker .popup {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 10;
  width: max(100%, 24rem);
  padding: 0.3rem;
  background: var(--paper);
  border: 1px solid var(--band);
  box-shadow: 0 2px 6px var(--backdrop);
}
.spell-picker .popup input { width: 100%; box-sizing: border-box; }
.spell-picker .popup ol {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0;
  max-height: 16rem;
  overflow-y: auto;
}
.spell-picker .popup li button {
  font: inherit;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  padding: 0.15rem 0.25rem;
  border: none;
  border-left: 3px solid transparent;
  background: none;
  text-align: left;
  cursor: pointer;
}
.spell-picker .popup li button:hover { background: var(--ground); }
.spell-picker .popup li button[aria-current] { border-left-color: currentColor; font-weight: 600; }
.spell-picker .gloss { font-size: var(--size-small); color: var(--info); font-weight: normal; }
form.record .numbers .price { margin: 0.4rem 0 0; }
/* A flag column takes the whole row, one line high; its boxes open in the
   flags dialog. */
form.record .numbers .flag-group .fields { grid-template-columns: minmax(0, 1fr); }
form.record pre { font-family: var(--fixed); font-size: var(--size-prose); margin: 0; white-space: pre-wrap; }
form.record h3 { font-size: var(--size-subheading); margin: 0 0 0.3rem; }
form.record .findings { margin: 0; }
form.record textarea { font-family: var(--fixed); font-size: var(--size-prose); width: 81ch; max-width: 100%; box-sizing: border-box; }
/* The tail is drawn as the numbers are, by the rules above that name both:
   an affect is a row of its two fields and its button, and the line under
   the timer is quiet. */
form.record .tail { display: flex; flex-direction: column; gap: 0.8rem; }
form.record .tail .affect-row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) auto;
  gap: 0.4rem 1rem;
  align-items: end;
  margin-bottom: 0.4rem;
}
form.record .tail .affect-row .range-note { grid-column: 1 / -1; }
form.record .tail .field .quiet { font-size: var(--size-small); color: var(--info); }

/* The trail. The column it stands in is as wide as the rose, and the trail must
   not be what decides that: `width: 0` keeps it out of the column's max-content
   width, `min-width: 100%` then gives it the width the rose settled on. It is one
   line, and a history too long for that line loses its oldest visits off the left
   — written newest first and laid out backwards, so the edge that runs out is the
   far end of the walk rather than the room we are standing in. */
.trail {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  width: 0;
  min-width: 100%;
  margin-bottom: 0.5rem;
}
.trail .back { flex: none; }
.trail .visits {
  display: flex;
  flex-direction: row-reverse;
  gap: 0.35rem;
  min-width: 0;
  overflow: hidden;
  /* The visit that only half fits fades out instead of being cut through. */
  mask-image: linear-gradient(to right, transparent, black 2rem);
}
.trail button {
  font: inherit;
  font-size: var(--size-small);
  cursor: pointer;
  background: var(--mist);
  border: 1px solid var(--edge);
  border-radius: calc(var(--r) - 2px);
  color: var(--info);
  padding: 0.2rem 0.6rem;
}
.trail .visit { flex: none; white-space: nowrap; }
.trail .current { color: var(--lit); border-color: color-mix(in srgb, var(--lit) 50%, transparent); font-weight: 600; }

/* The rose. Tiles and the hub are positioned by roseGeometry.ts; the drawing
   is one SVG under the tiles. Every way is in one of three states, never to be
   confused: none, but one can be made (a dashed outline and a plus); there
   (solid, its band dim, its chevrons still); being edited (lit, glowing, its
   band lit and its chevrons moving). Nothing pulses: the selected way has
   already been clicked. */
.rose { position: relative; }
.rose-drawing { position: absolute; left: 0; top: 0; }
.rose-panel { background: var(--rose-ground); }
.rose-drawing .band line { stroke: var(--band); stroke-linecap: butt; }
.rose-drawing .band polygon { fill: var(--band); }
.rose-drawing .way.selected .band line { stroke: var(--band-lit); }
.rose-drawing .way.selected .band polygon { fill: var(--band-lit); }
.rose-drawing .way.selected .band { filter: drop-shadow(0 0 5px color-mix(in srgb, var(--lit) 55%, transparent)); }
.rose-drawing .chevrons path {
  fill: none;
  stroke: var(--chevron);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.rose-drawing .way.selected .chevrons path { stroke: var(--chevron-lit); }
.rose-drawing .way.selected .chevrons { filter: drop-shadow(0 0 3px var(--lit)); }
/* One step per beat: 12px is CHEVRON_STEP in roseGeometry.ts. */
.rose-drawing .way.selected .track { animation: chevrons-ride 0.75s linear infinite; }
@keyframes chevrons-ride { to { transform: translateX(12px); } }
@media (prefers-reduced-motion: reduce) {
  /* As specific as the rule that moves them, or it would win. */
  .rose-drawing .way.selected .track { animation: none; }
}

/* Up, down and enter: their shapes are drawn, their tiles are transparent. */
.rose-drawing .shape { fill: var(--paper); stroke: color-mix(in srgb, var(--lit) 40%, transparent); stroke-width: 1; }
.rose-drawing .shape.empty {
  fill: color-mix(in srgb, var(--lit) 1.5%, transparent);
  stroke: color-mix(in srgb, var(--accent) 42%, transparent);
  stroke-width: 1.5;
  stroke-dasharray: 5 4;
  transition: stroke 0.15s, fill 0.15s;
}
.rose:has(.tile[data-direction="4"]:hover) .way[data-direction="4"] .shape.empty,
.rose:has(.tile[data-direction="5"]:hover) .way[data-direction="5"] .shape.empty,
.rose:has(.tile[data-direction="10"]:hover) .way[data-direction="10"] .shape.empty {
  stroke: color-mix(in srgb, var(--lit) 60%, transparent);
  stroke-dasharray: none;
  fill: var(--mist);
}
.rose-drawing .way.selected .shape {
  stroke: var(--lit);
  stroke-width: 1.5;
  stroke-dasharray: none;
  filter: drop-shadow(0 0 8px color-mix(in srgb, var(--lit) 40%, transparent));
}
/* A door, on the rose and on a card: a light leaf over a dark halo, round
   capped, so it stands off the band; the padlock a squat light body. */
.door .leaf { stroke: var(--door-leaf); stroke-linecap: round; }
.door .halo { stroke: var(--door-halo); stroke-linecap: round; }
.door .swing { fill: none; stroke: var(--door-leaf); stroke-width: 1.3; stroke-dasharray: 2.5 2.5; opacity: 0.85; }
.door .hinge { fill: var(--door-leaf); stroke: var(--door-halo); stroke-width: 1.2; }
.door .padlock .shackle-halo { fill: none; stroke: var(--door-halo); stroke-width: 2.8; }
.door .padlock .shackle { fill: none; stroke: var(--door-leaf); stroke-width: 1.3; }
.door .padlock .body { fill: var(--door-leaf); stroke: var(--door-halo); stroke-width: 0.9; }
.door .padlock .keyhole { fill: var(--door-halo); }

.rose .tile {
  position: absolute;
  box-sizing: border-box;
  font: inherit;
  font-size: var(--size-small);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  text-align: left;
  overflow: hidden;
  cursor: pointer;
}
/* 2. A way that is there. */
.rose .tile {
  line-height: 1.25;
  padding: 0.3rem 0.4rem;
  color: var(--ink);
  background: var(--tile);
  border: 1px solid color-mix(in srgb, var(--lit) 40%, transparent);
  border-radius: var(--r);
  transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}
.rose .tile:not(.empty):not(.selected):hover { border-color: color-mix(in srgb, var(--lit) 70%, transparent); }
/* 1. No way, but one can be made. No word: the glossary's is on the button. */
.rose .tile.empty {
  border: 1.5px dashed color-mix(in srgb, var(--accent) 42%, transparent);
  background: color-mix(in srgb, var(--lit) 1.5%, transparent);
  color: color-mix(in srgb, var(--info) 75%, transparent);
}
.rose .tile.empty::after {
  content: "+";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font: 300 var(--size-plus)/1 var(--text);
  color: color-mix(in srgb, var(--lit) 28%, transparent);
  transition: color 0.15s, transform 0.15s;
  pointer-events: none;
}
.rose .tile.empty:hover { border-style: solid; border-color: color-mix(in srgb, var(--lit) 60%, transparent); background: var(--mist); }
.rose .tile.empty:hover::after { color: var(--lit); transform: scale(1.15); }
/* 3. The way being edited. */
.rose .tile.selected {
  border: 1.5px solid var(--lit);
  background: var(--tile-selected);
  box-shadow: var(--glow);
}
.rose .tile.off-compass {
  background: transparent;
  border: none;
  box-shadow: none;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.rose .tile.off-compass.empty::after { padding-top: 1.6rem; font-size: calc(var(--size-plus) * 0.75); }
.rose .tile.off-compass.empty .dir { transform: translateY(-0.5rem); }
.rose .tile .dir {
  font-weight: 700;
  text-transform: uppercase;
  font-size: var(--size-fine);
  letter-spacing: 0.08em;
  color: var(--info);
}
/* The selected way's letter, and the same chip leading the editing column's
   heading, so it is plain which way the fields on the right belong to. */
.rose .tile.selected .dir, .dirchip {
  align-self: flex-start;
  padding: 0 0.4rem;
  border-radius: 4px;
  background: var(--lit);
  color: var(--on-accent);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.panel.answering {
  border-color: color-mix(in srgb, var(--lit) 55%, transparent);
  box-shadow: var(--glow-soft), var(--panel-shadow);
}
.panel.answering > h3:first-child {
  color: var(--lit);
  background: linear-gradient(90deg, var(--mist-strong), transparent);
}
.rose .tile .to { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rose .tile .vnum { font-family: var(--fixed); font-size: var(--size-fine); color: var(--info); }
.rose .tile .door { font-style: italic; font-size: var(--size-fine); }
.rose-hub { position: absolute; }

/* The description, at the hub of the rose. */
article.hub {
  border: 1px solid color-mix(in srgb, var(--lit) 45%, transparent);
  border-radius: calc(var(--r) * 1.5);
  background: var(--hub);
  box-shadow: var(--hub-shadow);
  padding: 0.4rem 0.6rem;
  font-size: var(--size-prose);
}
article.hub .name { color: var(--lit); }
article.hub .room-name { display: flex; align-items: baseline; gap: 0.5rem; margin: 0 0 0.3rem; }
article.hub .name { flex: 1; font: inherit; font-weight: 700; border: none; padding: 0; }
article.hub .vnum { font-family: var(--fixed); color: var(--info); }
article.hub .side-switch button {
  font: inherit;
  font-size: var(--size-small);
  background: var(--paper);
  border: 1px solid var(--line);
}
article.hub .side-switch button[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); font-weight: 600; }

/* The description field, wherever it stands: the hub, and a mob's form. */
.prose {
  font-family: var(--fixed);
  font-size: var(--size-prose);
  line-height: 1.35;
  display: block;
  /* 79 columns, and room for the mark after a full line. */
  width: 81ch;
  border: none;
  padding: 0;
  resize: none;
  overflow-x: auto;
  white-space: pre;
  /* The margin the terminal imposes, drawn where column 79 ends. */
  background-image: linear-gradient(to right,
    transparent calc(79ch - 1px), var(--band) calc(79ch - 1px),
    var(--band) 79ch, transparent 79ch);
  background-repeat: no-repeat;
  background-attachment: local;
}

/* The description is a page of the game, not a field: no frame, its margin
   at column 79 the only mark on it. */
textarea.prose, article.hub input.name {
  background-color: transparent;
  border-color: transparent;
  box-shadow: none;
}
.width { margin: 0.2rem 0 0; color: var(--info); font-size: var(--size-small); }
.width.over { color: var(--error); font-weight: 700; }
/* What a player is shown under the description: read, and read at its own width. */
article.hub .exits {
  margin: 0.3rem 0 0;
  font-family: var(--fixed);
  font-size: var(--size-prose);
  line-height: 1.35;
  color: var(--info);
  white-space: pre;
  overflow-x: auto;
}
/* A name that walks. No chrome: the line is a printout, not a row of controls. */
article.hub .exits .go {
  display: inline;
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-decoration: underline dotted;
}
article.hub .exits .go:hover, article.hub .exits .go:focus-visible {
  color: var(--ink);
  text-decoration: underline;
}

/* The command line */
.response { margin: 0.5rem 0; }
.response .match {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin-right: 0.5rem;
}

/* The editing column */
.editing { display: flex; flex-direction: column; gap: 1rem; }
.editing h3 { font-size: var(--size-subheading); margin: 0; }

.room, .exit-inspector, .dig { display: flex; flex-direction: column; gap: 0.5rem; }
.room label, .exit-inspector label { display: flex; flex-direction: column; gap: 0.15rem; }
.room input, .room textarea, .room select,
.exit-inspector input, .exit-inspector textarea { font: inherit; padding: 0.25rem; }
.room :is(.terrain, .shuffle) { max-width: 22rem; }
.room .shuffle .quiet { font-size: var(--size-small); }
/* The numbers a line may leave off — a room's teleport and how many fit, an
   exit's distance and pull — side by side where there is room, each with what
   it does under it. A picked room and a list take a row of their own. */
.number-fields {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  gap: 0.5rem 1rem;
  align-items: start;
}
.number-fields .wide { grid-column: span 2; }
/* The exit's column is one field wide. */
.exit-inspector .number-fields { grid-template-columns: minmax(0, 1fr); }
.exit-inspector .number-fields .wide { grid-column: auto; }
.number-fields .field { display: flex; flex-direction: column; gap: 0.15rem; }
.number-fields input[type="number"] { width: 8rem; }
.number-fields .gloss, .number-fields .range-note {
  font-size: var(--size-small);
  line-height: 1.35;
}
.number-fields .gloss { color: var(--info); }
.number-fields .beyond input { border-color: var(--warning); }
.number-fields input:invalid { border-color: var(--error); outline-color: var(--error); }
.flags-summary { margin: 0; display: flex; gap: 0.5rem; flex-wrap: wrap; }
/* One flag under another, each with its explanation — the area's, which
   stands in a narrow column. Newspaper columns would read down the first and
   back up to the top of the second, and put bit 24 above bit 2. */
fieldset.flags {
  border: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.centre .room { max-width: 100%; }
/* One flag, wherever it stands — a room's, an exit's, the area's. The name
   on the first line and what the flag does under it, both to the right of
   the box: a grid rather than a row, so the explanation wraps under the name
   instead of under the checkbox. A flag nobody explains has one line, and the
   grid does not reserve the other. */
fieldset.flags label {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.35rem;
  align-items: start;
  break-inside: avoid;
}
fieldset.flags label input { margin-top: 0.2rem; }
/* The name is what a builder types in the game, so it is set as the vnums and
   the description are, and at full strength; the explanation under it is read
   second. */
fieldset.flags .flag-name {
  font-family: var(--fixed);
  font-size: var(--size-prose);
  color: var(--ink);
}
fieldset.flags .flag-gloss {
  grid-column: 2;
  font-size: var(--size-small);
  line-height: 1.35;
  color: var(--info);
}
/* What we do not know about the flag, under what we do. In the warning
   colour, the one this sheet already uses for a mob's number outside the
   range its sources give: something to look at, stored either way, and not a
   mistake. The flag is the builder's to set — the doubt is ours. */
fieldset.flags .flag-caveat {
  grid-column: 2;
  font-size: var(--size-small);
  line-height: 1.35;
  color: var(--warning);
}
.dig label.check { display: flex; flex-direction: row; gap: 0.25rem; break-inside: avoid; }
/* A field whose control is a component rather than one input: the label cannot
   wrap it, so it stands above it and is drawn like the ones that do. */
.field-label { margin: 0; }
/* The door's four choices as cards. The radio lies over the whole card,
   transparent, so a click anywhere on it and the keyboard both reach it; the
   card that is checked takes the selected glow. */
fieldset.door-state {
  display: grid;
  /* Two and two where a card would be narrower than its word, four in a row
     where the column allows it. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  border: none;
  padding: 0;
  margin: 0;
}
/* Under the cards, whether the reset returns the door to its state. A box
   and its words in a row, unlike the column every other label is. */
.exit-inspector .door-reset { display: flex; flex-direction: column; gap: 0.3rem; }
.exit-inspector label.door-reset-switch {
  flex-direction: row;
  align-items: center;
  gap: 0.4rem;
}
.exit-inspector .door-reset p { margin: 0; font-size: var(--size-small); }
.exit-inspector .door-reset .range-note button { margin-left: 0.5rem; }
form.exit-inspector { container-type: inline-size; }
@container (min-width: 22rem) {
  fieldset.door-state { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.exit-inspector label.door-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: 0.55rem 0.3rem 0.5rem;
  border: 1px solid color-mix(in srgb, var(--lit) 18%, transparent);
  border-radius: calc(var(--r) + 2px);
  background: color-mix(in srgb, var(--lit) 3%, transparent);
  color: var(--info);
  font-size: var(--size-small);
  text-align: center;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s, box-shadow 0.12s, color 0.12s;
}
.door-card input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.door-card:hover { border-color: color-mix(in srgb, var(--lit) 45%, transparent); background: var(--mist); color: var(--ink); }
.door-card:has(input:focus-visible) { outline: 2px solid var(--lit); outline-offset: 2px; }
.door-card:has(input:checked) {
  border-color: var(--lit);
  background: var(--tile-selected);
  color: var(--lit);
  font-weight: 600;
  box-shadow: var(--glow);
}
.door-card svg { width: 100%; max-width: 84px; height: auto; overflow: visible; }
.door-card .card-band { stroke: var(--band-dim); stroke-width: 12; }
.door-card:has(input:checked) .card-band { stroke: var(--band); }
.door-card .chevrons path {
  fill: none;
  stroke: var(--chevron);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.room .findings { margin: 0; }
.danger { align-self: start; }

.exit-inspector, .dig {
  border: 1px solid var(--line);
  background: var(--ground);
  padding: 0.5rem;
}

/* The lint panel */
.lint {
  border-top: 1px solid var(--line);
  margin-top: 1rem;
  padding-top: 0.5rem;
  max-width: calc(82ch + 1.5rem);
}
.lint button.group {
  font: inherit;
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  padding: 0.1rem 0;
}
.lint ul { margin: 0.25rem 0 0.5rem 1rem; }

/* The exit glyph: ▲▼ before the compass, a ring for the portal after it.
   A dim mark means "no passage this way", as a dashed tile does on the rose. */
.exit-glyph {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex: none;
  vertical-align: -0.25em;
}
.exit-glyph svg { display: block; flex: none; }
.exit-glyph .updown {
  display: flex;
  flex-direction: column;
  font-size: calc(var(--glyph-size, 16px) * 0.53);
  line-height: 1.05;
}
.exit-glyph .on { color: var(--glyph-on); stroke: var(--glyph-on); }
.exit-glyph .off { color: var(--absent-dim); stroke: var(--absent-dim); }
.exit-glyph .hub { fill: var(--band); }

/* The top bar. The file on the left, the actions on the right, the screens
   below on a strip of their own as folder tabs: the active one is joined to
   the page under it by a gold top edge, the rest are muted text. */
header.top { display: block; padding: 0; border-bottom: none; margin-bottom: 1rem; }
header.top .bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.25rem;
}
header.top h1 {
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--lit);
}
header.top :is(.file-block, .actions) { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; }
header.top .actions { gap: 0.5rem; }
header.top .file { font-family: var(--fixed); font-weight: 500; }
header.top .file-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--lit);
  border-color: color-mix(in srgb, var(--lit) 35%, transparent);
}
header.top .file-button svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.4; }
/* Hidden but present: the label opens it, and the smoke test hands it a file. */
header.top .file-button input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  clip-path: inset(50%);
}
header.top .file-button:focus-within { outline: 2px solid var(--lit); outline-offset: 2px; }
header.top kbd {
  font-family: var(--fixed);
  font-size: var(--size-fine);
  margin-left: 0.45rem;
  padding: 0 0.3rem;
  border: 1px solid var(--edge);
  border-radius: 3px;
  color: var(--info);
}
header.top :is(.lang, .theme) { display: flex; align-items: center; gap: 0.4rem; font-size: var(--size-small); }
header.top .screens {
  display: flex;
  gap: 0.25rem;
  margin-top: 0.75rem;
  border-bottom: 1px solid var(--edge);
}
header.top .screens button {
  all: unset;
  cursor: pointer;
  font-size: var(--size-tab);
  font-weight: 600;
  letter-spacing: 0.01em;
  padding: 0.55rem 1.1rem 0.6rem;
  color: color-mix(in srgb, var(--ink) 50%, transparent);
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: calc(var(--r) + 2px) calc(var(--r) + 2px) 0 0;
  margin-bottom: -1px;
  transition: color 0.12s, background 0.12s;
}
header.top .screens button:hover { color: var(--ink); background: var(--mist); }
header.top .screens button[aria-pressed="true"] {
  color: var(--lit);
  background: var(--tab);
  border-color: var(--edge);
  box-shadow: inset 0 2px 0 var(--lit);
}
header.top .screens button:focus-visible { outline: 2px solid var(--lit); outline-offset: -2px; }

/* The area screen: the file's paperwork, spread over the page.
   A header is a dozen short fields. One column left the whole right-hand side
   of the window empty and pushed the last sections below the fold, so the
   sections flow through a grid instead — in reading order, which is not the
   file's order.

   The flags keep a column of their own on the right. They are the only field
   here whose height changes, and in the flow every section after them jumped
   when they were opened. */
.area-screen {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: 0.75rem;
  padding: 1rem 0;
}
.area-screen .flow {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  align-items: start;
  /* Strictly in order, never dense: a section that spans two columns can leave
     a gap beside it, and filling that gap from further down the page would put
     what the file declares about itself next to what the area is called. */
  gap: 0.75rem;
}
@media (min-width: 60rem) {
  /* Wide enough that a flag's explanation is one line under its name. At
     19rem most of them broke in two and the sixteen flags ran far below the
     sections beside them; at 28rem only `notransport`, twice as long as any
     other, still wraps. */
  .area-screen { grid-template-columns: minmax(0, 1fr) 28rem; }
}
.area-screen .paper {
  /* A grid item will not shrink below its content without this, and a long
     reset message would then widen the whole column. */
  min-width: 0;
}
/* How much of the row a section takes is decided in headerView.ts, from the
   section's shape. Below this width there is one column and nothing to span. */
@media (min-width: 40rem) {
  .area-screen .flow .double { grid-column: span 2; }
  .area-screen .flow .row { grid-column: 1 / -1; }
}
.area-screen h3 { font-size: var(--size-subheading); margin: 0 0 0.4rem; }
.area-screen .fields { display: flex; flex-wrap: wrap; gap: 0.5rem 0.75rem; }
.area-screen label { display: flex; flex-direction: column; gap: 0.15rem; }
.area-screen label span { font-size: var(--size-small); color: var(--info); }
.area-screen input { font: inherit; padding: 0.25rem; }
.area-screen .fields button { font: inherit; cursor: pointer; }
.area-screen p { margin: 0.3rem 0 0; }
.area-screen ul { margin: 0; padding-left: 1.2rem; }
.area-screen code { font-family: var(--fixed); }
.area-screen .unread { background: none; border-style: dashed; }
/* A tilde-terminated value is a sentence, not a number: it takes the section,
   and the section takes two columns. */
.area-screen input.str { width: 100%; }
.area-screen .fields > label:only-child { flex: 1 1 100%; min-width: 0; }
.area-screen input.num, .area-screen input.vnum { width: 8ch; }
.area-screen input.word { width: 12ch; }
/* Nine level bands are one row of nine, not three rows of three: they are read
   across, and wrapping them mid-way says a break is there that is not. */
.area-screen .row .fields { flex-wrap: nowrap; }
.area-screen .row .fields label { flex: 1 1 0; min-width: 3rem; }
.area-screen .row .fields input.num { width: 100%; }
/* The one row of buttons, which has no reason not to wrap. */
.area-screen .missing .fields { flex-wrap: wrap; }
.area-screen .missing .fields button { flex: 0 0 auto; }
/* The flags take the section's whole width: in their own column they are one
   box per line, and nothing else is beside them to be pushed. */
.area-screen .flag-field { flex: 1 1 100%; min-width: 0; }
.area-screen .beside .flags-summary { margin: 0 0 0.3rem; }
/* The reset frequency shares the section with the flags; below them, it takes
   the column's width rather than eight characters of it. */
.area-screen .beside input.num { width: 100%; }
.area-screen .beside .fields > label { margin-top: 0.4rem; }

/* A field that holds a room's vnum: the room as the room list draws it, the
   number below it, and the same search over the same rooms. The list is
   scrolled rather than allowed to grow, so opening it moves nothing below.
   Two fields use it — the header's #RECALL and #TEMPLE, and the exit's target
   — so only the line that makes it fill a header section stays scoped. */
.area-screen .location-picker { flex: 1 1 100%; min-width: 0; }
.location-picker .chosen,
.location-picker .search li button {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}
.location-picker .vnum {
  font-family: var(--fixed);
  font-size: var(--size-small);
  color: var(--info);
}
.location-picker .chosen .name,
.location-picker .search .name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.location-picker .controls {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
  margin-top: 0.4rem;
}
.location-picker .controls input.vnum { width: 8ch; }
.location-picker .controls button {
  font: inherit;
  font-size: var(--size-small);
  background: var(--paper);
  border: 1px solid var(--line);
  cursor: pointer;
  padding: 0.25rem 0.5rem;
}
.location-picker .search { margin-top: 0.4rem; }
.location-picker .search input { width: 100%; }
.location-picker .search ol {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0;
  max-height: 12rem;
  overflow-y: auto;
  border: 1px solid var(--line);
}
.location-picker .search li button {
  font: inherit;
  width: 100%;
  padding: 0.1rem 0.25rem;
  border: none;
  border-left: 3px solid transparent;
  background: none;
  text-align: left;
  cursor: pointer;
}
.location-picker .search li button[aria-current] {
  border-left-color: currentColor;
  font-weight: 600;
}

/* A room's flags, an exit's and each of a mob's flag columns: one line, always
   the same height, whatever is set — the boxes open in a dialog over the page
   rather than folding open and pushing down what stands below. In the dialog
   they are a grid four wide, which reads across the way the bits are numbered
   and lines its rows up. */
.flags-line {
  margin: 0;
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  white-space: nowrap;
  min-width: 0;
}
.flags-line .quiet { overflow: hidden; text-overflow: ellipsis; }
/* Extra descriptions: the keywords are the buttons, and many of them wrap
   onto a second line rather than run off the side. */
.extras-line { flex-wrap: wrap; white-space: normal; }
dialog.extras-dialog { width: max-content; max-width: 92vw; }
dialog.extras-dialog .extra-keywords {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  margin-bottom: 0.5rem;
}
dialog.extras-dialog .extra-keywords input { flex: 1; font: inherit; }
dialog.extras-dialog button.danger { margin-top: 0.5rem; }
/* The dialog may open inside a panel that sets its fields' font; the text a
   player reads keeps the terminal's, as it does at the hub. */
dialog.extras-dialog textarea.prose {
  font-family: var(--fixed);
  font-size: var(--size-prose);
  line-height: 1.35;
  padding: 0;
}
dialog.flags-dialog {
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--band);
  padding: 0.75rem 1rem 1rem;
  width: min(66rem, 92vw);
  max-height: 85vh;
}
dialog.flags-dialog::backdrop { background: var(--backdrop); }
dialog.flags-dialog header { border: none; padding: 0 0 0.5rem; }
dialog.flags-dialog h3 { font-size: var(--size-subheading); margin: 0; }
dialog.flags-dialog header button { margin-left: auto; font: inherit; }
dialog.flags-dialog fieldset.flags {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.5rem 1rem;
  align-items: start;
}

/* What the resets put into a room: three columns of equal width, drawn even
   when empty, so walking between rooms moves nothing sideways. */
.room-resets h3 { font-size: var(--size-subheading); margin: 0 0 0.3rem; }
.room-resets .reset-columns {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
  align-items: start;
}
.room-resets h4 {
  font-size: var(--size-small);
  font-weight: 600;
  color: var(--info);
  margin: 0 0 0.2rem;
  border-bottom: 1px solid var(--line);
}
.room-resets ul, .mob-resets ul { list-style: none; margin: 0; padding: 0; }
.room-resets ul ul { padding-left: 1.2rem; }
.room-resets li { font-size: var(--size-small); line-height: 1.5; }
/* A trap's fields, set in a room or under an object: its type and charges,
   when it fires, its chance and its button, wrapping in a narrow column. */
.trap {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.8rem;
  align-items: end;
  padding: 0.3rem 0;
  font-size: var(--size-small);
}
.trap .field-label { flex-basis: 100%; }
.trap .field { display: flex; flex-direction: column; gap: 0.15rem; }
.trap input[type="number"] { width: 5rem; }
.trap select { max-width: 14rem; }
.trap .gloss { max-width: 16rem; line-height: 1.35; }
.trap .flags-line { margin: 0; }
/* Under a placement on the object's page: its traps, then whether it lies
   hidden, the box and its words in a row. */
.traps-of .hiding { display: flex; flex-wrap: wrap; gap: 0.4rem 0.8rem; align-items: end; }
.traps-of label.hidden-switch { display: flex; flex-direction: row; align-items: center; gap: 0.4rem; }

/* Where a mob is reset, under its numbers. */
.mob-resets h3 { font-size: var(--size-subheading); margin: 0 0 0.2rem; }
.mob-resets p { margin: 0; }
/* One M line as fields: the room as an exit's target is picked, then the
   limit with what it means under it, then the chance. The columns are
   bounded: the form's first column is as wide as its content, and a sentence
   in an unbounded one would widen the whole page. */
.mob-resets li.mob-reset {
  display: grid;
  grid-template-columns: minmax(0, 24rem) minmax(0, 18rem) 8rem max-content;
  gap: 0.5rem 1rem;
  align-items: start;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--size-body);
}
/* Placing it somewhere else: the room picked as above, then the button. */
.mob-resets .mob-place {
  display: grid;
  grid-template-columns: minmax(0, 24rem) max-content;
  gap: 0.5rem 1rem;
  align-items: end;
  justify-content: start;
  padding: 0.4rem 0;
  font-size: var(--size-body);
}
.mob-resets .mob-place .field-label { grid-column: 1 / -1; }
.mob-resets li.mob-reset .remove { justify-self: start; }
.mob-resets .field { display: flex; flex-direction: column; gap: 0.15rem; }
form.record .mob-resets .field input { width: 6rem; }
.mob-resets .with-unit { display: flex; gap: 0.3rem; align-items: baseline; }
.mob-resets .gloss, .mob-resets .range-note {
  font-size: var(--size-small);
  line-height: 1.35;
}
.mob-resets .range-note { color: var(--warning); }
.mob-resets .beyond input { border-color: var(--warning); }
.mob-resets input:invalid { border-color: var(--error); outline-color: var(--error); }
/* A placement's equipment, across the row under its fields: each item on one
   line — its words, where it is worn, its chance, its button — and what it
   holds indented under it. */
.mob-resets .equipment-of { grid-column: 1 / -1; padding-left: 1.2rem; }
/* Neither the equipment nor a giving row may widen the form: its first
   column is as wide as its content, so these take the width they are given
   and wrap in it rather than asking for more. */
.mob-resets .equipment-of, .mob-resets .give { contain: inline-size; }
.mob-resets .equipment li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  align-items: end;
  padding: 0.25rem 0;
}
.mob-resets .equipment .item-words { min-width: 16rem; align-self: center; }
.mob-resets .equipment .equipment { flex-basis: 100%; padding-left: 1.2rem; }
/* Giving or dressing: what to give, then the buttons and where it is worn. */
.mob-resets .give {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  align-items: start;
  padding: 0.4rem 0;
}
.mob-resets .give .field-label { flex-basis: 100%; }
.mob-resets .give .choice-picker { flex: 0 1 24rem; min-width: 0; }

/* The Resety screen: the file's lines, in its order, one row each. */
main.reset-screen { grid-template-columns: minmax(0, 1fr); }
.reset-table { border-collapse: collapse; font-size: var(--size-small); }
.reset-table td { padding: 0.1rem 0.75rem 0.1rem 0; vertical-align: baseline; }
.reset-table .letter { font-family: var(--fixed); font-weight: 600; }
.reset-table .numbers { white-space: nowrap; color: var(--info); }
/* The line as the file holds it, read second. */
.reset-table .raw {
  font-family: var(--fixed);
  font-size: var(--size-fine);
  color: var(--info);
  white-space: pre;
}
.reset-table .reset-comment td { color: var(--info); }
.reset-table .reset-unreadable td { color: var(--warning); }

/* A run the server prints in colour n; see palette.ts. */
.ansi-0 { color: var(--ansi-0); }
.ansi-1 { color: var(--ansi-1); }
.ansi-2 { color: var(--ansi-2); }
.ansi-3 { color: var(--ansi-3); }
.ansi-4 { color: var(--ansi-4); }
.ansi-5 { color: var(--ansi-5); }
.ansi-6 { color: var(--ansi-6); }
.ansi-7 { color: var(--ansi-7); }
.ansi-8 { color: var(--ansi-8); }
.ansi-9 { color: var(--ansi-9); }
.ansi-10 { color: var(--ansi-10); }
.ansi-11 { color: var(--ansi-11); }
.ansi-12 { color: var(--ansi-12); }
.ansi-13 { color: var(--ansi-13); }
.ansi-14 { color: var(--ansi-14); }
.ansi-15 { color: var(--ansi-15); }
.ansi-fg { color: var(--ansi-fg); }

/* The game console: a screen that drops from the top, over the editor. Its
   ground and text are the palette's; the editor's own words in it are drawn
   in --screen-info so that nothing the editor says passes for the game. */
section.console {
  position: fixed;
  inset: 0 0 auto 0;
  height: 50vh;
  z-index: 50;
  display: flex;
  flex-direction: column;
  background: var(--ansi-bg);
  color: var(--ansi-fg);
  font-family: var(--fixed);
  font-size: var(--size-prose);
  transform: translateY(-100%);
  transition: transform 150ms ease-out, box-shadow 150ms ease-out;
  visibility: hidden;
}
section.console.open { transform: none; visibility: visible; }
/* The drop reads against a dark page by a brass rail along its edge — dark
   gold at the rims, a muted highlight down the middle, no glow — and the
   shadow it casts on the editor, which dims under it. */
section.console.open { box-shadow: var(--screen-cast); }
section.console::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 6px;
  background: var(--screen-rail);
  box-shadow: var(--screen-rail-shadow);
  pointer-events: none;
}
#app > :not(section.console) { transition: filter 0.25s ease; }
#app:has(> section.console.open) > :not(section.console) { filter: brightness(0.7); }
section.console { flex-direction: row; }
/* The screen is a classic terminal's 80 columns and no wider; the panel
   takes the rest of the page. */
section.console .console-screen {
  flex: 0 1 calc(80ch + 2.5rem);
  min-width: 0;
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--screen-edge);
}
/* The panel: what the character has, switched with a click. Its switches
   stand in a grid that gains columns as it gains switches. */
section.console .console-panel {
  flex: 1 1 16rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  font-family: var(--text);
  font-size: var(--size-small);
  overflow-y: auto;
}
section.console .switches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: 0.75rem 1.5rem;
  align-items: start;
}
/* The sun or the moon: which the console shows, and a click turns it. */
section.console button.daytime {
  all: unset;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  cursor: pointer;
  font-weight: 600;
  border-radius: 0.25rem;
}
section.console button.daytime:focus-visible { outline: 2px solid var(--screen-accent); }
section.console button.daytime svg { width: 2rem; height: 2rem; flex: none; }
section.console button.daytime.day svg {
  fill: var(--screen-sun); stroke: var(--screen-sun); stroke-width: 1.8; stroke-linecap: round;
}
section.console button.daytime.night svg { fill: var(--screen-moon); }
/* A switch as a phone draws one: a track, and a knob that slides right when
   it is on. The checkbox under it keeps the keyboard and the reader. */
section.console .switch {
  display: grid;
  grid-template-columns: auto 1fr auto;
  column-gap: 0.75rem;
  align-items: center;
  align-content: start;
  cursor: pointer;
}
section.console .switch input {
  appearance: none;
  position: relative;
  width: 2.25rem;
  height: 1.25rem;
  margin: 0;
  border-radius: 0.625rem;
  background: var(--screen-edge);
  cursor: pointer;
  transition: background 150ms ease-out;
}
section.console .switch input::before {
  content: "";
  position: absolute;
  top: 0.125rem;
  left: 0.125rem;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: var(--screen-info);
  transition: transform 150ms ease-out, background 150ms ease-out;
}
section.console .switch input:checked { background: var(--screen-track-on); }
section.console .switch input:checked::before {
  transform: translateX(1rem);
  background: var(--screen-accent);
}
section.console .switch input:focus-visible { outline: 2px solid var(--screen-accent); outline-offset: 2px; }
section.console .switch-name { font-weight: 600; }
section.console .switch-gloss { grid-column: 2 / -1; color: var(--screen-info); }
/* The command a switch shares, beside its name: what to type for the same. */
section.console .switch-command {
  justify-self: end;
  font-family: var(--fixed);
  font-size: var(--size-fine);
  font-weight: 300;
  color: var(--screen-info);
  white-space: nowrap;
}
/* The sun or the moon, with its commands right after its words. */
section.console .daytime-row {
  display: flex;
  align-items: center;
  gap: 1rem;
}
section.console .scrollback {
  flex: 1;
  overflow-y: auto;
  padding: 0.5rem 0.75rem;
  /* A classic terminal is 80 columns wide: the rule stands where the 80th
     ends, measured from the text as the description field's is at 79. */
  background-image: linear-gradient(to right,
    transparent 80ch, var(--screen-margin) 80ch,
    var(--screen-margin) calc(80ch + 1px), transparent calc(80ch + 1px));
  background-origin: content-box;
  background-clip: padding-box;
  background-repeat: no-repeat;
}
section.console .console-line { min-height: 1.3em; line-height: 1.3; }
/* The game's spaces are its own — `%-5s - ` pads the exits — and are kept;
   the page's own markup around a run is not. */
section.console .console-line span { white-space: pre-wrap; }
section.console .console-editor { color: var(--screen-info); font-style: italic; }
/* A typed command, echoed as the client echoes it. */
section.console .console-echo { color: var(--ansi-command); }
/* The whole line leads to the mob, as a tile leads to its room. */
section.console button.person {
  all: unset;
  display: block;
  width: 100%;
  cursor: pointer;
}
section.console button.person:hover,
section.console button.person:focus-visible {
  text-decoration: underline;
  text-decoration-color: var(--screen-accent);
}
section.console .console-input {
  display: flex;
  gap: 0.5rem;
  padding: 0.3rem 0.75rem;
  border-top: 1px solid var(--screen-edge);
}
section.console .prompt { color: var(--screen-info); }
section.console input[name="console"] {
  flex: 1;
  font: inherit;
  color: var(--ansi-fg);
  caret-color: transparent;
  background: transparent;
  border: none;
  outline: none;
  box-shadow: none; /* the editor's focus ring; the console keeps its look */
  /* One cell after the last character, so that at the end of a scrolled line
     the block has somewhere to stand inside the field. */
  padding: 0 1ch 0 0;
}
/* The cursor: one cell, inverting the character under it, blinking at one
   second's steps; a hollow outline while the line has no focus. */
section.console .cursor-wrap { position: relative; flex: 1; display: flex; }
section.console .block-cursor {
  position: absolute;
  top: 50%;
  left: calc(var(--column, 0) * 1ch);
  width: 1ch;
  height: 1.25em;
  transform: translateY(-50%);
  pointer-events: none;
  box-shadow: inset 0 0 0 1px var(--ansi-fg);
}
section.console .cursor-wrap:focus-within .block-cursor {
  background: var(--ansi-fg);
  mix-blend-mode: difference;
  box-shadow: none;
  animation: block-blink 1s steps(1) infinite;
}
@keyframes block-blink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  section.console .cursor-wrap:focus-within .block-cursor { animation: none; }
}
