/* ═══════════════════════════════════════════════════════════════════════
   THE WRITTEN WORLD — page styles

   Theme: Jazz Age / Art Deco — deep night blues, champagne-gold frames, and
   the green light across the bay (#3df58f) as the one luminous accent. The
   green equals palette.js NAMED.green_std and the stage background equals
   STRUCT.night_sky, so the UI and the 3D world share one vocabulary.

   Every selector is prefixed .tww/.twm/.twe and this sheet loads on one page
   only. It deliberately does NOT redefine :root, so nothing here can reach
   any other page. Colours come from styles/core/main.css variables where
   they exist, with local fallbacks for the feature's own accents.
   ═══════════════════════════════════════════════════════════════════════ */

/* The token block is scoped, not global — and it is scoped to a GROUP,
   because the room bar, explore button + HUD, touch pad, teacher button and
   the front-door menu are all appended to <body> by JS, outside .tww. A
   token declared on .tww alone never reaches them. */
.tww,
.tww-room,
.tww-explore-btn,
.tww-explore-hud,
.tww-touch,
.tww-teacher-btn,
.twm-root {
  --tww-glow: #3df58f;                       /* == palette.js NAMED.green_std — do not shift */
  --tww-glow-dim: rgba(61, 245, 143, 0.16);
  --tww-gold: #d8b56c;                       /* champagne — structural frame gold */
  --tww-gold-bright: #ecd9a8;
  --tww-gold-deep: #8a6d3b;
  --tww-gold-edge: rgba(216, 181, 108, 0.3);
  --tww-gold-dim: rgba(216, 181, 108, 0.14);
  --tww-ink: #eae8de;
  --tww-paper: rgba(13, 19, 31, 0.85);
  --tww-edge: rgba(141, 170, 210, 0.14);
  --tww-serif: "Cormorant Garamond", Georgia, "Iowan Old Style", "Times New Roman", serif;
  --tww-sans: "Jost", "Segoe UI", system-ui, sans-serif;
  --tww-display: "Limelight", Georgia, serif;
  --tww-mono: "Consolas", "SF Mono", ui-monospace, monospace;

  font-family: var(--tww-sans);
}

.tww {
  width: min(1400px, 100%);
  margin: 0 auto;
  padding: 2rem 1.25rem 4rem;
  flex: 1;
}

/* ── Intro ─────────────────────────────────────────────────────────────── */

.tww-intro {
  max-width: 62ch;
  margin-bottom: 1.9rem;
}

.tww-title {
  margin: 0 0 0.45rem;
  font-family: var(--tww-display);
  font-weight: 400;
  font-size: clamp(1.9rem, 4vw, 2.7rem);
  line-height: 1.12;
  letter-spacing: 0.05em;
  color: var(--tww-gold-bright);
  text-shadow: 0 0 32px rgba(216, 181, 108, 0.2);
  /* One-time marquee settle on page load; registered in the
     prefers-reduced-motion block below. */
  animation: tww-title-in 0.9s cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

@keyframes tww-title-in {
  from { opacity: 0; letter-spacing: 0.28em; }
  to { opacity: 1; letter-spacing: 0.05em; }
}

.tww-blurb {
  margin: 0;
  color: var(--mute, #8ca0c0);
  font-family: var(--tww-serif);
  font-style: italic;
  font-size: 1.08rem;
}

/* ── Layout ────────────────────────────────────────────────────────────── */

.tww-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
}

/* ── Stage ─────────────────────────────────────────────────────────────── */

.tww-stage {
  position: sticky;
  top: calc(var(--header-h, 64px) + 1rem);
  border: 1px solid var(--tww-gold-edge);
  border-radius: 6px;
  background: #060a12; /* == palette.js STRUCT.night_sky — do not shift */
  overflow: hidden;
  box-shadow: var(--shadow, 0 18px 60px rgba(0, 0, 0, 0.38));
}

.tww-canvas-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 11;
  min-height: 320px;
}

.tww-canvas-wrap canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* ── Panels ────────────────────────────────────────────────────────────── */

.tww-panels {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  min-width: 0;
}

/* A stepped-corner Deco card: the element is clipped with 45° chamfers off
   the top corners, ::before is a gold plate filling the clip and ::after is
   the night paper inset 1px, which leaves a gold hairline that follows the
   chamfer. Both pseudos sit at z-index -1 inside the card's own stacking
   context, so the JS-built children need no changes. */
.tww-panel {
  position: relative;
  z-index: 0;
  padding: 1.2rem 1.3rem 1.1rem;
  border: 0;
  border-radius: 0;
  background: transparent;
  clip-path: polygon(12px 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%, 0 12px);
}

.tww-panel::before,
.tww-panel::after {
  content: "";
  position: absolute;
  z-index: -1;
}

.tww-panel::before {
  inset: 0;
  background: linear-gradient(
    165deg,
    rgba(236, 217, 168, 0.55),
    rgba(216, 181, 108, 0.3) 30%,
    rgba(138, 109, 59, 0.5) 70%,
    rgba(216, 181, 108, 0.42)
  );
}

.tww-panel::after {
  inset: 1px;
  clip-path: polygon(12px 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%, 0 12px);
  background: linear-gradient(180deg, rgba(16, 24, 38, 0.96), rgba(10, 15, 25, 0.97));
}

/* The head carries the one ornament this page repeats: a thin double rule
   with a small diamond punched through its centre. */
.tww-panel-head {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin-bottom: 1.05rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--tww-gold-edge);
}

.tww-panel-head::before,
.twm-head::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 1px;
  background: var(--tww-gold-dim);
}

.tww-panel-head::after,
.twm-head::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -7px;
  width: 7px;
  height: 7px;
  transform: translateX(-50%) rotate(45deg);
  background: #0d1420;
  border: 1px solid var(--tww-gold);
}

.tww-panel-head h2 {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--tww-gold-bright);
}

.tww-prompt {
  margin: 0 0 0.7rem;
  font-family: var(--tww-serif);
  font-size: 1.12rem;
  font-weight: 500;
  line-height: 1.6;
  color: var(--tww-ink);
}

.tww-prompt--build {
  padding-left: 0.85rem;
  border-left: 2px solid var(--tww-glow-dim);
  color: var(--mute, #8ca0c0);
  font-size: 1.02rem;
  font-style: italic;
}

.tww-input {
  display: block;
  width: 100%;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 3px;
  background: rgba(6, 10, 18, 0.85);
  color: var(--tww-ink);
  font-family: var(--tww-serif);
  font-size: 1.08rem;
  font-weight: 500;
  line-height: 1.55;
  resize: vertical;
}

.tww-input:focus {
  outline: none;
  border-color: rgba(61, 245, 143, 0.5);
  box-shadow: 0 0 0 3px rgba(61, 245, 143, 0.1);
}

/* The rule above sets `display`, so it beats the browser's own [hidden] — the
   same trap as .tww-step-body further down. A picking question hides the box. */
.tww-input[hidden] {
  display: none !important;
}

/* ── the answers on a picking question ──────────────────────────────────────
   Boxed radios, not a bare list. The whole row is the target, which is what
   makes this answerable on a phone, and the lit state is the green-light green
   rather than the teacher's amber: green is the student moving forward
   everywhere on this page. */
.tww-choices {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.tww-choices[hidden] {
  display: none !important;
}

.tww-choice {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 3px;
  background: rgba(6, 10, 18, 0.55);
  color: var(--tww-ink);
  font-family: var(--tww-serif);
  font-size: 1.04rem;
  line-height: 1.5;
  cursor: pointer;
  transition: background 0.16s, border-color 0.16s;
}

.tww-choice:hover {
  border-color: var(--tww-gold-edge);
}

.tww-choice:has(input:checked) {
  border-color: rgba(61, 245, 143, 0.55);
  background: rgba(61, 245, 143, 0.08);
}

/* The ring goes on the row, because the row is what a click lands on. */
.tww-choice:focus-within {
  outline: 2px solid var(--tww-glow);
  outline-offset: 2px;
}

.tww-choice input {
  flex-shrink: 0;
  margin-top: 0.34rem;
  accent-color: var(--tww-glow);
  cursor: pointer;
}

.tww-choice-letter {
  flex-shrink: 0;
  min-width: 1rem;
  color: var(--tww-gold);
  font-family: var(--tww-sans);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  line-height: 2;
}

.tww-choice-text {
  min-width: 0;
}

.tww-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.7rem;
}

/* Primary actions keep the green-light fill: green means the student moving
   forward, everywhere on this page. */
.tww-btn {
  padding: 0.5rem 1.2rem;
  border: 1px solid rgba(61, 245, 143, 0.45);
  border-radius: 999px;
  background: rgba(61, 245, 143, 0.12);
  color: #d6ffe9;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: background 0.16s, border-color 0.16s, color 0.16s;
}

.tww-btn:hover {
  background: rgba(61, 245, 143, 0.2);
  border-color: rgba(61, 245, 143, 0.65);
  color: #fff;
}

.tww-btn:focus-visible {
  outline: 2px solid var(--tww-glow);
  outline-offset: 2px;
}

.tww-hint {
  color: var(--hint, #5a7898);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
}

/* Is this question's object standing? A medallion with two states, because
   there are two states — constant gold ring, green fill when it is up. */
.tww-built {
  margin-left: auto;
  flex-shrink: 0;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--tww-gold-edge);
  border-radius: 999px;
  background: rgba(6, 10, 18, 0.7);
  color: var(--mute, #8ca0c0);
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}

.tww-built[data-built="yes"] {
  border-color: var(--tww-gold);
  background: rgba(61, 245, 143, 0.2);
  color: #eafff3;
}

.tww-feedback {
  min-height: 1.2em;
  margin: 0.85rem 0 0;
  color: var(--tww-ink);
  font-family: var(--tww-serif);
  font-size: 1.05rem;
  line-height: 1.55;
}

.tww-feedback:empty { display: none; }

/* ── The two steps ─────────────────────────────────────────────────────────
   Answering the question and describing the object are separate pieces of work,
   so they are separate cards. Step 2 is not merely disabled while it is locked —
   its prompt is not on screen at all, because two prompts at once is what made
   the single box confusing in the first place. */

.tww-steps {
  margin: 0;
  padding: 0;
  list-style: none;
}

.tww-step {
  position: relative;
  padding-left: 1.9rem;
}

.tww-step + .tww-step {
  margin-top: 1.1rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--tww-gold-dim);
}

.tww-step[hidden] { display: none; }

.tww-step-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.tww-step-num {
  position: absolute;
  left: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border: 1px solid var(--tww-gold-edge);
  border-radius: 999px;
  background: rgba(6, 10, 18, 0.85);
  color: var(--tww-gold);
  font-size: 0.74rem;
  font-weight: 600;
}

.tww-step-name {
  color: var(--hint, #5a7898);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.tww-step-state {
  margin-left: auto;
  color: var(--mute, #8ca0c0);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.08em;
}

.tww-step-state:empty { display: none; }

/* A step that has landed keeps its green number, so the panel reads as progress
   rather than as two identical boxes. */
.tww-step.is-done .tww-step-num {
  border-color: rgba(61, 245, 143, 0.42);
  color: #bdf2d6;
}

.tww-step.is-done .tww-step-name { color: #9fd8ba; }

.tww-step.is-locked .tww-step-num,
.tww-step.is-locked .tww-step-name { opacity: 0.55; }

.tww-locked {
  margin: 0;
  color: var(--hint, #5a7898);
  font-family: var(--tww-serif);
  font-size: 1rem;
  font-style: italic;
  line-height: 1.5;
}

.tww-locked:empty { display: none; }

/* `hidden` on its own loses to any author `display` — see .twe-root[hidden].
   The step body is only ever shown once step 1 has landed. */
.tww-step-body[hidden] { display: none !important; }

/* ── The room bar ──────────────────────────────────────────────────────────
   Bottom-left, opposite the explore button, and deliberately quiet: solo is the
   default and a room is something you opt into. It hides itself in projector and
   explore modes along with everything else that is not the world. */

.tww-room {
  position: fixed;
  left: 1rem;
  bottom: 1rem;
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  max-width: min(94vw, 30rem);
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 3px;
  background: rgba(9, 13, 22, 0.92);
  font-size: 0.78rem;
}

.tww-room-idle,
.tww-room-live {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}

.tww-room-idle[hidden],
.tww-room-live[hidden],
.tww-room-form[hidden] { display: none !important; }

.tww-room-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.4rem;
}

.tww-room-field {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.tww-room-field > span {
  color: var(--hint, #5a7898);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.tww-room-field input {
  width: 7.5rem;
  padding: 0.35rem 0.45rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 3px;
  background: rgba(6, 10, 18, 0.85);
  color: var(--tww-ink);
  font-family: var(--tww-serif);
  font-size: 1rem;
}

.tww-room-field input[data-role="code"] {
  width: 5rem;
  font-family: var(--tww-mono);
  font-size: 1.05rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}

.tww-room-field input:focus {
  outline: none;
  border-color: rgba(61, 245, 143, 0.5);
  box-shadow: 0 0 0 3px rgba(61, 245, 143, 0.1);
}

.tww-room-btn {
  padding: 0.35rem 0.75rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 999px;
  background: rgba(6, 10, 18, 0.7);
  color: var(--tww-ink);
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
}

.tww-room-btn:hover { border-color: var(--tww-gold); color: #fff; }

.tww-room-btn.is-go {
  border-color: rgba(61, 245, 143, 0.42);
  background: rgba(61, 245, 143, 0.12);
  color: #d6ffe9;
}

.tww-room-btn.is-danger { border-color: rgba(255, 138, 138, 0.45); color: #ffbcbc; }

.tww-room-btn[disabled] { opacity: 0.5; pointer-events: none; }

/* The code, big enough to read off a projector from the back of a room. */
.tww-room-code {
  padding: 0.1rem 0.45rem;
  border: 1px solid rgba(61, 245, 143, 0.42);
  border-radius: 3px;
  color: #eafff3;
  font-family: var(--tww-mono);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.2em;
}

.tww-room-status {
  color: var(--mute, #8ca0c0);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.tww-room-status[data-state="live"] { color: #9fd8ba; }
.tww-room-status[data-state="reconnecting"],
.tww-room-status[data-state="connecting"] { color: #ffd489; }
.tww-room-status[data-state="offline"],
.tww-room-status[data-state="closed"] { color: #ffbcbc; }

.tww-room-who {
  flex: 1;
  min-width: 8rem;
  color: var(--mute, #8ca0c0);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tww-room-error {
  flex-basis: 100%;
  margin: 0;
  color: #ffd489;
  font-size: 0.72rem;
}

.tww-room-error:empty { display: none; }

body.tww-projector .tww-room,
body.tww-exploring .tww-room,
body.twe-open .tww-room { display: none; }

@media (max-width: 620px) {
  .tww-room { left: 0.6rem; right: 0.6rem; bottom: 0.6rem; max-width: none; }
}

/* ── One question at a time ────────────────────────────────────────────────
   Only the panel being answered is on screen; the rest of the column is built
   and hidden. `hidden` alone would lose to any author `display` the day one is
   added to .tww-panel — the same trap .tww-input and .tww-step-body already
   carry a note about — so it is spelled out here. */

.tww-panel[hidden] { display: none !important; }

/* The panel takes focus when Back or Next opens it. That is a programmatic
   focus, not a tab stop, so it gets no ring: the ring would land on a whole
   card and read as an error. */
.tww-panel:focus { outline: none; }

.tww-panel:focus-visible {
  outline: 2px solid var(--tww-glow);
  outline-offset: 3px;
}

/* Not a card: a rule under the question with the two ways out of it. The bar
   sits below the panel and stays put as panels change, which is what makes
   Next a place on the page rather than something to hunt for. */
.tww-flow {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--tww-gold-dim);
}

.tww-flow[hidden] { display: none !important; }

.tww-flow-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* Where they are, centred between the two buttons and quiet — a lesson is not
   a progress bar and the count is not a score. */
.tww-flow-count {
  margin: 0 auto;
  color: var(--hint, #5a7898);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* Back is gold — the teacher's colour, the colour of everything on this page
   that is about looking rather than moving. Next is the green light. */
.tww-flow-btn {
  flex-shrink: 0;
  max-width: 45%;
  padding: 0.5rem 1.1rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 999px;
  background: rgba(6, 10, 18, 0.7);
  color: var(--tww-ink);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  transition: background 0.16s, border-color 0.16s, color 0.16s;
}

.tww-flow-btn[hidden] { display: none !important; }

.tww-flow-btn:hover { border-color: var(--tww-gold); color: #fff; }

.tww-flow-btn:focus-visible {
  outline: 2px solid var(--tww-glow);
  outline-offset: 2px;
}

.tww-flow-btn.is-next {
  border-color: rgba(61, 245, 143, 0.45);
  background: rgba(61, 245, 143, 0.12);
  color: #d6ffe9;
}

.tww-flow-btn.is-next:hover {
  background: rgba(61, 245, 143, 0.2);
  border-color: rgba(61, 245, 143, 0.65);
  color: #fff;
}

/* A locked Next stays on screen rather than vanishing: the way on is visible,
   and the line under it says what opens it. A button that is not there teaches
   a student nothing about what to do next. */
.tww-flow-btn[disabled] {
  border-color: var(--tww-gold-dim);
  background: rgba(6, 10, 18, 0.6);
  color: var(--hint, #5a7898);
  cursor: not-allowed;
}

.tww-flow-btn[disabled]:hover {
  border-color: var(--tww-gold-dim);
  background: rgba(6, 10, 18, 0.6);
  color: var(--hint, #5a7898);
}

.tww-flow-note {
  margin: 0;
  color: var(--hint, #5a7898);
  font-family: var(--tww-serif);
  font-size: 0.98rem;
  font-style: italic;
  line-height: 1.5;
  text-align: center;
}

.tww-flow-note:empty { display: none; }

/* ── Responsive ────────────────────────────────────────────────────────── */

@media (max-width: 1040px) {
  .tww-layout { grid-template-columns: minmax(0, 1fr); }
  .tww-stage { position: static; }
}

@media (max-width: 560px) {
  .tww { padding: 1.25rem 0.9rem 3rem; }
  .tww-panel { padding: 0.95rem 0.9rem; }
  .tww-canvas-wrap { aspect-ratio: 4 / 3; }
  /* Two buttons and a count do not fit across a phone, and the count is the
     one of the three nobody taps: it goes on its own line above them. */
  .tww-flow-row { flex-wrap: wrap; justify-content: space-between; }
  .tww-flow-count { order: -1; flex-basis: 100%; margin: 0; text-align: center; }
  .tww-flow-btn { max-width: 48%; }
  /* The step number moves above the card rather than eating a fifth of a phone's
     width from every line of the prompt. */
  .tww-step { padding-left: 0; }
  .tww-step-num { position: static; }
}

@media (prefers-reduced-motion: reduce) {
  .tww-btn { transition: none; }
  .tww-title { animation: none; }
}

/* ── What landed and what didn't ───────────────────────────────────────── */

.tww-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
}

.tww-chips:empty { display: none; }

/* Chips carry the requirement in full, so they wrap rather than clip: a long
   one becomes two lines inside the pill instead of pushing past the card. */
.tww-chip {
  max-width: 100%;
  padding: 0.2rem 0.7rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 0.9rem;
  color: var(--hint, #5a7898);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

/* The chip marker is the page's one diamond, hollow until the idea lands. */
.tww-chip::before {
  content: "◇ ";
  color: var(--tww-gold);
  opacity: 0.75;
}

.tww-chip.is-on {
  border-color: rgba(61, 245, 143, 0.45);
  background: rgba(61, 245, 143, 0.09);
  color: #bdf2d6;
}

.tww-chip.is-on::before {
  content: "◆ ";
  color: var(--tww-glow);
  opacity: 1;
}

/* Dashed and amber: a teacher is deciding this one by hand, not the answers. */
.tww-built.is-forced {
  border-style: dashed;
  border-color: rgba(255, 196, 87, 0.6);
  color: #ffd489;
}

/* ══════════════════════════════════════════════════════════════════════════
   TEACHER CONTROL PANEL
   Only ever in the DOM for a session that passed the dev-access check, so
   none of this reaches a student's browser.
   ══════════════════════════════════════════════════════════════════════════ */

/* Teacher chrome keeps the amber family (#ffd489 ↔ the 3D leader gold) —
   structural champagne gold frames things, amber means "teacher". */
.tww-teacher-btn {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 1200;
  padding: 0.5rem 1rem;
  border: 1px solid rgba(255, 196, 87, 0.45);
  border-radius: 999px;
  background: rgba(18, 15, 10, 0.92);
  color: #ffd489;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.tww-teacher-btn:hover {
  border-color: rgba(255, 196, 87, 0.85);
  color: #fff0cd;
}

/* While walking it sits exactly where the jump button does, and higher in the
   stack — a thumb reaching for jump opened the editor instead. Every other
   piece of page chrome already gets out of the way while exploring; so does
   this. The HUD's "Stop exploring" is the way back. */
body.tww-exploring .tww-teacher-btn { display: none; }

/* ══════════════════════════════════════════════════════════════════════════
   EXPLORE MODE — the student's avatar
   ══════════════════════════════════════════════════════════════════════════ */

.tww-explore-btn {
  position: fixed;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  z-index: 1150;
  padding: 0.6rem 1.3rem;
  border: 1px solid rgba(61, 245, 143, 0.45);
  border-radius: 999px;
  background: rgba(9, 20, 15, 0.94);
  color: #d6ffe9;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.45);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.tww-explore-btn:hover {
  border-color: rgba(61, 245, 143, 0.9);
  color: #fff;
}

.tww-explore-btn[hidden] { display: none; }

.tww-explore-hud {
  position: fixed;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  z-index: 1150;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem 0.9rem;
  max-width: calc(100vw - 2rem);
  padding: 0.55rem 0.9rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 3px;
  background: rgba(9, 13, 22, 0.92);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.tww-explore-hud[hidden] { display: none; }

.tww-hud-who {
  color: var(--tww-glow);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.06em;
}

.tww-hud-keys {
  color: var(--mute, #8ca0c0);
  font-size: 0.76rem;
}

.tww-hud-keys kbd {
  display: inline-block;
  min-width: 1.35em;
  margin: 0 0.05em;
  padding: 0.1em 0.3em;
  border: 1px solid var(--tww-edge);
  border-radius: 3px;
  background: rgba(6, 10, 18, 0.9);
  color: var(--tww-ink);
  font-family: inherit;
  font-size: 0.92em;
  text-align: center;
}

.tww-hud-exit {
  padding: 0.32rem 0.75rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 999px;
  background: rgba(20, 28, 40, 0.9);
  color: var(--text, #dde6f8);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.tww-hud-exit:hover { border-color: rgba(61, 245, 143, 0.6); color: #fff; }

/* ── Touch controls for explore mode ───────────────────────────────────────
   A floating analog stick owns the lower-left half of the screen: touch and
   the stick appears under the thumb, push further to go faster. Jump sits
   bottom-right; the rest of the screen still orbits the camera. Hidden by
   default and revealed only for a COARSE pointer while exploring, so a mouse
   user never meets them. The HUD moves to the top on those devices because
   the bottom edge now belongs to thumbs. */

.tww-touch { display: none; }
.tww-hud-touch { display: none; }

@media (pointer: coarse) {
  body.tww-exploring .tww-touch { display: block; }

  body.tww-exploring .tww-explore-hud {
    top: 0.8rem;
    bottom: auto;
  }

  /* Keyboard hints are noise on a phone; the thumb line replaces them. */
  body.tww-exploring .tww-hud-keys { display: none; }
  body.tww-exploring .tww-hud-touch {
    display: inline;
    color: var(--mute, #8ca0c0);
    font-size: 0.76rem;
  }

  /* The invisible region a thumb can claim the stick in. */
  .tww-stick-zone {
    position: fixed;
    left: 0;
    bottom: 0;
    width: 50vw;
    height: 55vh;
    z-index: 1160;
    /* The three lines that make a game surface feel like one: no scroll
       steal, no long-press text selection, no grey tap flash. */
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
  }

  /* The stick itself: a base ring centred on the touch, a nub that rides the
     thumb. Positioned by app.js; the translate centres it on that point. */
  .tww-stick {
    position: absolute;
    width: 128px;
    height: 128px;
    transform: translate(-50%, -50%);
    border: 1px solid rgba(61, 245, 143, 0.35);
    border-radius: 999px;
    background: rgba(9, 13, 22, 0.42);
    pointer-events: none;
  }

  .tww-stick[hidden] { display: none; }

  .tww-stick-nub {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 52px;
    height: 52px;
    margin: -26px 0 0 -26px;
    border: 1px solid rgba(61, 245, 143, 0.65);
    border-radius: 999px;
    background: rgba(61, 245, 143, 0.18);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  }

  .tww-touch-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--tww-edge);
    background: rgba(9, 13, 22, 0.78);
    color: var(--tww-ink);
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
  }

  .tww-touch-btn.is-on {
    border-color: rgba(61, 245, 143, 0.65);
    background: rgba(61, 245, 143, 0.18);
    color: #eafff3;
  }

  .tww-touch-jump {
    position: fixed;
    right: 1.1rem;
    bottom: 1.6rem;
    z-index: 1161;
    width: 72px;
    height: 72px;
    border-radius: 999px;
    font-size: 1.5rem;
  }
}

/* While walking, the page gets out of the way: the world fills the screen. */
body.tww-exploring .site-header,
body.tww-exploring .site-footer,
body.tww-exploring .tww-intro,
body.tww-exploring .tww-panels { display: none; }

body.tww-exploring { padding-top: 0; overflow: hidden; }

body.tww-exploring .tww { width: 100%; max-width: none; padding: 0; }
body.tww-exploring .tww-layout { display: block; }

body.tww-exploring .tww-stage {
  position: fixed;
  inset: 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

body.tww-exploring .tww-canvas-wrap {
  width: 100vw;
  height: 100vh;
  aspect-ratio: auto;
}

@media (max-width: 620px) {
  .tww-hud-keys { display: none; }
}

/* Nearby object messages stay as crisp DOM type while their anchor is
   projected from the 3D world. The resting offset is lower: entering a trigger
   lifts the card from the object, then leaves it floating just above it. */
.tww-object-messages {
  position: fixed;
  inset: 0;
  z-index: 1140;
  overflow: hidden;
  pointer-events: none;
  font-family: "Jost", "Segoe UI", system-ui, sans-serif;
}

.tww-object-messages[hidden] { display: none !important; }

.tww-object-message {
  position: fixed;
  width: max-content;
  max-width: min(22rem, calc(100vw - 2rem));
  opacity: 0;
  transform: translate(-50%, calc(-100% + 18px)) scale(0.94);
  transform-origin: 50% 100%;
  transition:
    opacity 0.22s ease,
    transform 0.52s cubic-bezier(0.16, 1, 0.3, 1);
  filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.42));
  will-change: transform, opacity;
}

.tww-object-message.is-near.is-onscreen {
  opacity: 1;
  transform: translate(-50%, calc(-100% - 12px)) scale(1);
}

.tww-object-message-copy {
  position: relative;
  padding: 0.72rem 0.9rem 0.76rem 1rem;
  border: 1px solid rgba(231, 202, 133, 0.72);
  border-radius: 10px 10px 10px 3px;
  background: linear-gradient(135deg, rgba(29, 37, 53, 0.97), rgba(8, 14, 25, 0.95));
  color: #fff8e8;
  box-shadow:
    inset 3px 0 0 rgba(61, 245, 143, 0.68),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);
  font-family: "Cormorant Garamond", Georgia, serif;
  font-size: clamp(1rem, 1.5vw, 1.2rem);
  font-weight: 600;
  letter-spacing: 0.015em;
  line-height: 1.35;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.tww-object-message::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 100%;
  width: 12px;
  height: 12px;
  border-right: 1px solid rgba(231, 202, 133, 0.72);
  border-bottom: 1px solid rgba(231, 202, 133, 0.72);
  background: #0d1524;
  transform: translate(-50%, -7px) rotate(45deg);
}

@media (prefers-reduced-motion: reduce) {
  .tww-object-message { transition: opacity 0.12s linear; }
  .tww-object-message.is-near.is-onscreen {
    transform: translate(-50%, calc(-100% - 12px));
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE WORLD EDITOR — full-screen, light theme
   Toolbar / hierarchy / viewport / inspector, the shape every real 3D tool
   uses. Deliberately light: the night scene is the student aesthetic, the
   editor is a daylight drafting room — warm paper, brass rules, dark window
   in the middle. Only ever in the DOM for a session that passed the
   dev-access check.
   ══════════════════════════════════════════════════════════════════════════ */

.twe-root {
  --twe-bg: #efeade;
  --twe-panel: #f8f4ea;
  --twe-line: #d9cfb8;
  --twe-ink: #23262d;
  --twe-mute: #6f6a5c;
  --twe-faint: #a39a83;
  --twe-accent: #8a6d3b;
  --twe-accent-soft: #ece1c6;
  --twe-danger: #a8402f;
  --twe-serif: "Cormorant Garamond", Georgia, "Times New Roman", serif;

  position: fixed;
  inset: 0;
  z-index: 1500;
  display: grid;
  grid-template-rows: 52px minmax(0, 1fr);
  grid-template-columns: 280px minmax(0, 1fr) 320px;
  background: var(--twe-bg);
  color: var(--twe-ink);
  font-family: "Jost", "Segoe UI", system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.45;
}

/* An author display beats the UA [hidden] rule — without this the editor
   could never be closed. (Learned the hard way on the old drawer.) */
.twe-root[hidden] { display: none !important; }

body.twe-open { overflow: hidden; }
body.twe-open .tww-teacher-btn { display: none; }

/* ── toolbar ────────────────────────────────────────────────────────────── */

.twe-top {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0 0.75rem;
  background: var(--twe-panel);
  border-bottom: 1px solid var(--twe-line);
  overflow-x: auto;
}

.twe-brand {
  font-family: "Limelight", Georgia, serif;
  font-weight: 400;
  font-size: 0.92rem;
  letter-spacing: 0.08em;
  color: var(--twe-accent);
  white-space: nowrap;
}

.twe-sep {
  width: 1px;
  height: 24px;
  background: var(--twe-line);
  margin: 0 0.3rem;
  flex-shrink: 0;
}

.twe-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.42rem 0.8rem;
  border: 1px solid #cdbd9b;
  border-radius: 999px;
  background: var(--twe-panel);
  color: #3b382f;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
}

.twe-btn:hover {
  border-color: var(--twe-accent);
  color: var(--twe-accent);
  background: #f3ecdc;
}

.twe-btn.is-on {
  background: var(--twe-accent);
  border-color: var(--twe-accent);
  color: #f8f4ea;
}

.twe-btn.danger { color: var(--twe-danger); border-color: #d9b3a6; }
.twe-btn.danger:hover { background: #f7e9e3; border-color: var(--twe-danger); }
.twe-btn[disabled] { opacity: 0.45; pointer-events: none; }

/* The "Edit with AI" variant: a quiet brass tint, so it reads as a tool of
   the house rather than a foreign feature. */
button.twe-btn.is-ai {
  border-color: #cbb98e;
  background: var(--twe-accent-soft);
  color: #5c4726;
}

button.twe-btn.is-ai:hover {
  border-color: var(--twe-accent);
  background: #f3ecdc;
  color: var(--twe-accent);
}

.twe-done { font-weight: 700; }

.twe-badge {
  margin-left: auto;
  color: var(--twe-faint);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* ── hierarchy tree ─────────────────────────────────────────────────────── */

.twe-tree {
  grid-row: 2;
  grid-column: 1;
  overflow-y: auto;
  background: #f3eddd;
  border-right: 1px solid var(--twe-line);
  padding: 0.35rem 0 3rem;
}

.twe-sect {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 0.9rem 0.3rem;
  color: var(--twe-faint);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.twe-mini-add {
  border: 1px solid #cdbd9b;
  border-radius: 3px;
  background: var(--twe-panel);
  color: var(--twe-accent);
  font-size: 0.8rem;
  line-height: 1;
  padding: 0.1rem 0.4rem;
  cursor: pointer;
}

.twe-mini-add:hover { background: var(--twe-accent-soft); }

.twe-node {
  display: flex;
  align-items: center;
  width: 100%;
  padding: 0.14rem 0.3rem 0.14rem 0;
  border-left: 3px solid transparent;
}

.twe-node:hover { background: #ece5d0; }

.twe-node.is-selected {
  background: var(--twe-accent-soft);
  border-left-color: var(--twe-accent);
}

.twe-node.is-selected .twe-node-label {
  color: #5c4726;
  font-weight: 700;
}

.twe-caret {
  width: 18px;
  flex-shrink: 0;
  border: 0;
  background: none;
  color: var(--twe-faint);
  font-size: 0.62rem;
  cursor: pointer;
  padding: 0.2rem 0;
}

.twe-node-icon {
  width: 17px;
  flex-shrink: 0;
  text-align: center;
  font-size: 0.6rem;
  line-height: 1;
  color: var(--twe-faint);
}

.twe-node-icon.is-story { color: var(--twe-accent); font-size: 0.68rem; }
.twe-node-icon.is-scenery { color: #8b8672; font-size: 0.68rem; }
.twe-node-icon.is-prop { color: #b07a2c; }
/* Combined objects: green, because combining is the one thing in this tree the
   teacher built themselves out of other things. */
.twe-node-icon.is-group { color: #3fae5a; font-size: 0.72rem; }

.twe-node.is-root .twe-node-label { font-weight: 700; }

.twe-node-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border: 0;
  background: none;
  color: #3b382f;
  font-size: 0.82rem;
  text-align: left;
  padding: 0.22rem 0.2rem;
  cursor: pointer;
  /* Shift-clicking a row picks a RUN of objects, and a shift-click is also how
     a browser extends a text selection — without this the sweep leaves every
     name it crossed painted blue. */
  user-select: none;
  -webkit-user-select: none;
}

.twe-eye {
  border: 0;
  background: none;
  color: #b9ae91;
  font-size: 0.78rem;
  cursor: pointer;
  padding: 0.1rem 0.45rem;
  flex-shrink: 0;
}

.twe-eye:hover { color: var(--twe-accent); }
.twe-eye.is-off { color: var(--twe-danger); opacity: 0.85; }

/* ── hierarchy drag and drop ────────────────────────────────────────────── */

.twe-node.is-dragging { opacity: 0.45; }

/* Dropping on the row COMBINES the two objects. */
.twe-node.is-over {
  background: #dcead2;
  box-shadow: inset 0 0 0 2px #3fae5a;
}

.twe-node[draggable="true"] { cursor: grab; }

/* …and this tab, which only exists while something is being dragged, nests one
   object inside the other. Two visible targets, so neither meaning needs a
   modifier key the teacher has to be told about. */
.twe-node-into {
  display: none;
  flex-shrink: 0;
  margin-right: 0.15rem;
  padding: 0.1rem 0.35rem;
  border: 1px dashed #c2b596;
  border-radius: 3px;
  background: var(--twe-panel, #f8f4ea);
  color: var(--twe-faint);
  font-size: 0.66rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: copy;
}

.twe-tree.is-dragging .twe-node:not(.is-dragging):not(.is-nodrop) .twe-node-into { display: inline-flex; }

/* A row this drag cannot land on (its own descendants) says so instead of
   offering a target that would quietly do nothing. */
.twe-tree.is-dragging .twe-node.is-nodrop { opacity: 0.4; }

.twe-node-into.is-over {
  border-style: solid;
  border-color: #3fae5a;
  background: #dcead2;
  color: #1d6b33;
}

.twe-droproot {
  margin: 0.6rem 0.6rem 0;
  padding: 0.55rem 0.6rem;
  border: 1px dashed #c2b596;
  border-radius: 3px;
  color: var(--twe-faint);
  font-size: 0.73rem;
  text-align: center;
}

.twe-droproot.is-over {
  border-color: #3fae5a;
  border-style: solid;
  background: #dcead2;
  color: #1d6b33;
}

.twe-av-dot {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
  border: 1px solid rgba(0, 0, 0, 0.25);
  border-radius: 50%;
}

.twe-empty-note {
  margin: 0.1rem 0 0;
  padding: 0 0.95rem;
  color: var(--twe-mute);
  font-family: var(--twe-serif);
  font-size: 0.88rem;
  font-style: italic;
}

/* ── viewport ───────────────────────────────────────────────────────────── */

/* The dark window in the light room — the night world seen from the
   drafting table. Do not lighten. */
.twe-view {
  grid-row: 2;
  grid-column: 2;
  position: relative;
  min-width: 0;
  min-height: 0;
  background: #101724;
}

.twe-view canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* ── inspector / side panel ─────────────────────────────────────────────── */

.twe-side {
  grid-row: 2;
  grid-column: 3;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--twe-panel);
  border-left: 1px solid var(--twe-line);
}

.twe-tabs {
  display: flex;
  flex-shrink: 0;
  border-bottom: 1px solid var(--twe-line);
}

.twe-tab {
  position: relative;
  flex: 1;
  padding: 0.6rem 0;
  border: 0;
  border-bottom: 2px solid transparent;
  background: #efe8d6;
  color: var(--twe-mute);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
}

.twe-tab.is-on {
  background: var(--twe-panel);
  color: var(--twe-accent);
  border-bottom-color: var(--twe-accent);
}

/* The active tab carries the house diamond on its brass underline. */
.twe-tab.is-on::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -4px;
  width: 6px;
  height: 6px;
  transform: translateX(-50%) rotate(45deg);
  background: var(--twe-accent);
}

.twe-pane {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.85rem 0.9rem 3rem;
}

.twe-selname {
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}

.twe-belongs {
  margin: 0.1rem 0 0.65rem;
  color: var(--twe-faint);
  font-size: 0.73rem;
}

.twe-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  margin-bottom: 0.55rem;
}

.twe-stack {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.twe-field { display: block; margin-bottom: 0.55rem; }

.twe-label {
  display: block;
  margin-bottom: 0.22rem;
  color: var(--twe-faint);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.twe-input,
.twe-num,
.twe-select {
  width: 100%;
  padding: 0.42rem 0.5rem;
  border: 1px solid #cdbd9b;
  border-radius: 3px;
  background: #fdfbf4;
  color: var(--twe-ink, #23262d);
  font: inherit;
  font-size: 0.82rem;
}

.twe-top .twe-select { width: auto; }

.twe-input:focus,
.twe-num:focus,
.twe-select:focus {
  outline: none;
  border-color: var(--twe-accent);
  box-shadow: 0 0 0 3px rgba(138, 109, 59, 0.16);
}

textarea.twe-input { resize: vertical; line-height: 1.45; }

.twe-radius-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.65rem;
}

.twe-radius {
  width: 100%;
  accent-color: var(--twe-accent);
}

.twe-radius-value {
  min-width: 4.7rem;
  color: var(--twe-ink);
  font-size: 0.76rem;
  font-weight: 600;
  text-align: right;
}

.twe-vec { display: flex; gap: 0.35rem; }
.twe-num { min-width: 0; flex: 1; }

.twe-color {
  width: 46px;
  height: 34px;
  padding: 2px;
  border: 1px solid #cdbd9b;
  border-radius: 3px;
  background: var(--twe-panel);
  flex-shrink: 0;
  cursor: pointer;
}

.twe-hint {
  margin: 0.35rem 0;
  color: var(--twe-mute);
  font-size: 0.74rem;
  line-height: 1.5;
}

/* Section headings are brass rules: the hairline above does the work the old
   gray borders did, one tone, one weight. */
.twe-subhead {
  margin: 0.9rem 0 0.35rem;
  color: var(--twe-accent);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.twe-pane .twe-subhead:not(:first-child) {
  border-top: 1px solid var(--twe-line);
  padding-top: 0.8rem;
}

.twe-listrow {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.35rem;
}

.twe-listname {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.82rem;
}

/* "kept" — a member of a multi-selection that Delete will not take, marked in
   the list itself so the count on the button is never a surprise. */
.twe-listnote {
  flex-shrink: 0;
  color: var(--twe-faint);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.twe-q {
  margin-bottom: 0.5rem;
  padding: 0 0.6rem 0.5rem;
  border: 1px solid #ddd3ba;
  border-radius: 3px;
  background: #fbf8ef;
}

.twe-q > summary {
  padding: 0.55rem 0;
  color: #3b382f;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
}

/* ── the explainer, and question headings ─────────────────────────────────── */

.twe-explain {
  margin-bottom: 0.7rem;
  padding: 0 0.6rem 0.5rem;
  border: 1px solid #d6c9a8;
  border-radius: 3px;
  background: #f5efdf;
}

.twe-explain > summary {
  padding: 0.55rem 0;
  color: var(--twe-accent);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  cursor: pointer;
}

.twe-explain-line {
  margin: 0 0 0.45rem;
  color: var(--twe-ink);
  font-size: 0.76rem;
  line-height: 1.5;
}

.twe-explain-line strong { color: #5c4726; }

.twe-q > summary { display: flex; align-items: baseline; gap: 0.4rem; }

.twe-q-title { flex: 1; min-width: 0; }

/* The machine-readable id and what the question builds, kept quiet: useful to
   tell two questions apart, never the thing a teacher has to read first. */
.twe-q-tag {
  flex-shrink: 0;
  color: var(--twe-faint);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: lowercase;
}

/* ── editable rubric rows ──────────────────────────────────────────────────
   A condition is a card rather than a line, because it is three fields that
   belong together: what it is, what counts as it, and how much it matters. */

.twe-cond {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-bottom: 0.5rem;
  padding: 0.45rem 0.5rem;
  border: 1px solid #ddd3ba;
  border-radius: 3px;
  background: var(--twe-panel, #f8f4ea);
}

.twe-cond-top {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

/* min-width:0 on the growing child, flex-shrink:0 on the ×. A flex item's
   default min-width is its content, so on a narrow screen the select refused to
   shrink and pushed the delete button out past the edge of the pane — which is
   how a form that fits everywhere else ends up scrolling sideways on a phone. */
.twe-cond-top .twe-select { width: auto; flex: 1; min-width: 0; }

.twe-cond-top > .twe-btn { flex-shrink: 0; }

.twe-cond-kind {
  flex: 1;
  color: var(--twe-faint);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* One answer on a picking question — the condition card again, because it is
   the same job: a row the teacher fills in. The right one is lit down its edge
   so "which of these is correct" is answerable at a glance from the top of the
   column, without reading four radio buttons to find the filled one. */
.twe-cond--choice.is-correct {
  border-color: var(--twe-accent);
  box-shadow: inset 3px 0 0 var(--twe-accent);
}

/* Inside a .twe-cond-top, so the checkrow's own bottom margin would push the
   row's controls out of line with the × beside them. */
.twe-choice-mark {
  flex: 1;
  min-width: 0;
  margin-bottom: 0;
}

/* A / B / C / D, or TRUE / FALSE — the row's name, as a chip rather than a word
   in a sentence, so the eye can run down the column and find one. */
.twe-choice-letter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  min-width: 1.5rem;
  height: 1.4rem;
  padding: 0 0.35rem;
  border: 1px solid #cbb98e;
  border-radius: 3px;
  background: var(--twe-accent-soft);
  color: #5c4726;
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.twe-cond--choice.is-correct .twe-choice-letter {
  border-color: var(--twe-accent);
  background: var(--twe-accent);
  color: #f8f4ea;
}

/* Two helper buttons sit here on a picking question. They wrap rather than
   squeezing labels that have to stay readable to be worth pressing. */
.twe-assist {
  flex-wrap: wrap;
  gap: 0.35rem;
}

/* A condition with no words can never be met, and a teacher should hear that
   from the row itself rather than from a student who cannot pass it. */
.twe-warn {
  margin: 0.1rem 0 0;
  color: #9a6320;
  font-size: 0.7rem;
  line-height: 1.4;
}

.twe-checkrow {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
  color: var(--twe-ink);
  font-size: 0.82rem;
  cursor: pointer;
}

.twe-check {
  width: 15px;
  height: 15px;
  accent-color: var(--twe-accent);
  cursor: pointer;
}

/* ── menus ──────────────────────────────────────────────────────────────── */

/* The --twe-* tokens are declared on .twe-root, and every one of these
   surfaces is appended to <body>, so none of the tokens cascade into them on
   their own. That made the build popover's Keep button literally invisible:
   `.twe-btn.is-on` asked for var(--twe-accent), got nothing, and painted
   white text on the white panel. So the tokens are declared again on every
   body-level surface, and the ink colour and typeface with them — the page
   behind the editor is dark-themed, and inherited light text (and the page's
   font stack) vanish on these panels. KEEP THIS BLOCK IDENTICAL to the
   .twe-root token block above. */
.twe-menu,
.twe-ai,
.twe-map,
.twe-paintwin,
.twe-qm {
  --twe-bg: #efeade;
  --twe-panel: #f8f4ea;
  --twe-line: #d9cfb8;
  --twe-ink: #23262d;
  --twe-mute: #6f6a5c;
  --twe-faint: #a39a83;
  --twe-accent: #8a6d3b;
  --twe-accent-soft: #ece1c6;
  --twe-danger: #a8402f;
  --twe-serif: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  color: var(--twe-ink);
  font-family: "Jost", "Segoe UI", system-ui, sans-serif;
}

.twe-menu {
  position: fixed;
  z-index: 1600;
  min-width: 180px;
  max-width: 320px;
  display: flex;
  flex-direction: column;
  padding: 0.3rem;
  background: var(--twe-panel, #f8f4ea);
  border: 1px solid #cdbd9b;
  border-radius: 4px;
  box-shadow: 0 14px 40px rgba(56, 44, 20, 0.2);
}

.twe-menu-item {
  padding: 0.45rem 0.7rem;
  border: 0;
  border-radius: 3px;
  background: none;
  color: #3b382f;
  font-size: 0.82rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.twe-menu-item:hover { background: var(--twe-accent-soft); color: #5c4726; }

.twe-menu-note {
  padding: 0.35rem 0.7rem;
  color: var(--twe-mute);
  font-size: 0.76rem;
  line-height: 1.45;
}

/* ── build an object by describing it ───────────────────────────────────── */

/* A panel rather than a menu, because it holds a text field and has to stay
   open while the teacher orbits the camera around the preview. Same surface
   treatment as .twe-menu so it reads as part of the same toolbar. */
.twe-ai {
  position: fixed;
  z-index: 1600;
  width: min(23rem, calc(100vw - 1rem));
  display: flex;
  flex-direction: column;
  padding: 0.7rem 0.8rem 0.8rem;
  background: var(--twe-panel, #f8f4ea);
  border: 1px solid #cdbd9b;
  border-radius: 4px;
  box-shadow: 0 18px 46px rgba(56, 44, 20, 0.24);
}

/* An author `display: flex` beats the user-agent [hidden] rule, so hiding it
   needs to be said explicitly — the same trap that once kept the old drawer
   permanently open. */
.twe-ai[hidden] { display: none !important; }

.twe-ai-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.1rem;
}

.twe-ai-head strong {
  color: var(--twe-accent);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.twe-ai-shut {
  padding: 0.15rem 0.45rem;
  font-size: 0.8rem;
  line-height: 1;
}

/* What the popover is doing right now — "Building new object" or
   "Editing: <name>" — one small-caps brass line above the textarea. */
.twe-ai-mode {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0.3rem 0 0;
  color: var(--twe-accent);
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* The object being edited, named as a chip inside that line. */
.twe-ai-target {
  display: inline-block;
  max-width: 13rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0.05rem 0.5rem;
  border: 1px solid #cbb98e;
  border-radius: 999px;
  background: var(--twe-accent-soft);
  color: #5c4726;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: none;
}

.twe-ai-input {
  width: 100%;
  margin: 0.35rem 0 0.5rem;
  resize: vertical;
}

.twe-ai-go { font-weight: 700; }

/* Build controls — the style doctrine and the piece budget. One quiet row
   each; hidden entirely while editing, because an edit re-enters the style
   stored with the object rather than today's dials. */
.twe-ai-controls {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin: 0 0 0.55rem;
}

.twe-ai-controls[hidden] { display: none !important; }

.twe-ai-styles {
  display: flex;
  gap: 0.3rem;
}

.twe-ai-style {
  flex: 1 1 0;
  font-size: 0.78rem;
  padding: 0.28rem 0.2rem;
}

.twe-ai-pieces-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}

.twe-ai-pieces {
  flex: 1 1 auto;
  min-width: 0;
  accent-color: var(--twe-gold, #a8842c);
}

.twe-ai-effort {
  width: 100%;
  font-size: 0.78rem;
  padding: 0.28rem 0.2rem;
}

.twe-ai-pieces-out {
  flex: 0 0 auto;
  min-width: 6.5em;
  text-align: right;
  color: var(--twe-mute);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}

/* The API returns one completed object rather than streaming exact server
   milestones. This bar advances through clearly labelled ESTIMATED phases and
   stops short of full until the preview actually arrives. */
.twe-ai-progress {
  margin: 0.55rem 0 0.1rem;
}

.twe-ai-progress[hidden] { display: none !important; }

.twe-ai-progress-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.65rem;
  margin-bottom: 0.3rem;
}

.twe-ai-progress-label {
  min-width: 0;
  color: var(--twe-ink);
  font-size: 0.74rem;
  font-weight: 600;
  line-height: 1.35;
}

.twe-ai-progress-estimate {
  flex-shrink: 0;
  color: var(--twe-faint);
  font-size: 0.58rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.twe-ai-progress-track {
  display: block;
  height: 6px;
  overflow: hidden;
  border-radius: 999px;
  background: #dfd6c3;
}

.twe-ai-progress-fill {
  position: relative;
  display: block;
  width: 0;
  height: 100%;
  overflow: hidden;
  border-radius: inherit;
  background: var(--twe-accent);
  transition: width 0.65s ease;
}

.twe-ai-progress-fill::after {
  content: '';
  position: absolute;
  inset: 0 auto 0 -45%;
  width: 38%;
  background: rgba(255, 255, 255, 0.46);
  transform: skewX(-18deg);
  animation: twe-ai-progress-sweep 1.7s ease-in-out infinite;
}

@keyframes twe-ai-progress-sweep {
  from { transform: translateX(0) skewX(-18deg); }
  to { transform: translateX(390%) skewX(-18deg); }
}

/* Keep is the answer most of the time, so it carries the active treatment and
   sits first; Discard keeps the danger colour it has everywhere else. */
.twe-ai-decide {
  margin-top: 0.55rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--twe-line);
}

.twe-ai-decide[hidden] { display: none !important; }

/* The model's one-sentence note about what it built. */
.twe-ai-note {
  margin: 0.45rem 0 0;
  color: var(--twe-mute);
  font-family: var(--twe-serif);
  font-size: 0.9rem;
  font-style: italic;
  line-height: 1.5;
}

.twe-ai-note:empty { display: none; }

/* Earlier versions of the object, each with its Restore. */
.twe-ai-history {
  margin-top: 0.55rem;
  max-height: 180px;
  overflow-y: auto;
  border: 1px solid var(--twe-line);
  border-radius: 3px;
  background: #fbf8ef;
}

.twe-ai-ver {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.4rem 0.55rem;
}

.twe-ai-ver + .twe-ai-ver { border-top: 1px solid var(--twe-line); }

.twe-ai-ver-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--twe-ink);
  font-size: 0.78rem;
  font-weight: 500;
}

.twe-ai-ver-when {
  margin-left: 0.35rem;
  color: var(--twe-mute);
  font-size: 0.68rem;
  font-weight: 400;
}

.twe-ai-ver-btn {
  flex-shrink: 0;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--twe-accent);
  border-radius: 999px;
  background: transparent;
  color: var(--twe-accent);
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
}

.twe-ai-ver-btn:hover { background: var(--twe-accent-soft); }

/* Author displays above (flex) beat the UA [hidden] rule — same trap as
   .twe-ai itself, guarded here once for the whole family. */
.twe-ai-mode[hidden],
.twe-ai-target[hidden],
.twe-ai-history[hidden],
.twe-ai-ver[hidden],
.twe-ai-note[hidden] { display: none !important; }

/* ── the questions window ───────────────────────────────────────────────── */

/* Writing the lesson is the longest thing a teacher does in here and it is not
   about the selected object, so it gets a window instead of a third of a 320px
   column. Between the compact drawers (1550) and the toolbar menus (1600), so a
   menu opened from inside it still lands on top. */
.twe-qm {
  position: fixed;
  inset: 0;
  z-index: 1560;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.1rem;
  background: rgba(8, 11, 18, 0.52);
  backdrop-filter: blur(3px);

  /* Tokens and ink come from the shared body-level-surfaces rule above the
     menus section — this used to declare its own copy while .twe-ai went
     without, which is how the Keep button managed to be white on white. */
  font-size: 14px;
}

/* An author `display: flex` beats the user-agent [hidden] rule. */
.twe-qm[hidden] { display: none !important; }

.twe-qm-card {
  display: flex;
  flex-direction: column;
  width: min(1180px, 100%);
  height: min(880px, calc(100dvh - 2.2rem));
  overflow: hidden;
  background: var(--twe-panel);
  border: 1px solid #cdbd9b;
  border-radius: 4px;
  box-shadow: 0 26px 70px rgba(30, 23, 10, 0.35);
}

/* The window head carries the same double rule + diamond the student panels
   use, in brass. Raised above the body so the diamond is never painted over. */
.twe-qm-head {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  flex: 0 0 auto;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--twe-line);
}

.twe-qm-head::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 1px;
  background: var(--twe-line);
}

.twe-qm-head::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -7px;
  width: 7px;
  height: 7px;
  transform: translateX(-50%) rotate(45deg);
  background: var(--twe-panel);
  border: 1px solid var(--twe-accent);
}

.twe-qm-head strong {
  color: var(--twe-accent);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.twe-qm-head .twe-hint { margin: 0; }

.twe-qm-shut {
  margin-left: auto;
  align-self: center;
  padding: 0.15rem 0.45rem;
  font-size: 0.8rem;
  line-height: 1;
}

.twe-qm-body {
  display: grid;
  grid-template-columns: 250px minmax(0, 1fr);
  flex: 1;
  min-height: 0;
}

/* The rail: every question in the lesson, one line each. Picking one is what
   the old accordion made you do by scrolling past the others. */
.twe-qm-list {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-height: 0;
  overflow-y: auto;
  padding: 0.85rem 0.6rem 2rem;
  border-right: 1px solid var(--twe-line);
  background: #f2ecdb;
}

.twe-qm-add { margin-bottom: 0.4rem; }

.twe-qm-item {
  display: flex;
  flex-direction: column;
  gap: 0.12rem;
  padding: 0.45rem 0.55rem;
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 3px;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.twe-qm-item:hover { background: #ebe4cf; }

.twe-qm-item.is-on {
  background: var(--twe-accent-soft);
  border-color: #cbb98e;
}

.twe-qm-item-title { font-weight: 600; }

.twe-qm-item-tag {
  color: var(--twe-mute);
  font-size: 0.72rem;
}

.twe-qm-edit {
  min-height: 0;
  overflow-y: auto;
  padding: 0.95rem 1rem 3rem;
}

/* The question's NAME and its KIND, above the numbered flow — neither is a step
   in it: one is a label, the other decides which steps there are. */
.twe-qm-top {
  display: flex;
  flex-wrap: wrap;
  gap: 0 1.6rem;
  margin-bottom: 1rem;
  padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--twe-line);
}

.twe-qm-top .twe-field {
  flex: 1 1 16rem;
  min-width: 0;
  margin-bottom: 0;
}

/* What the class is asked beside how it is marked. In one column the rubric
   buried the question it belonged to. */
.twe-qm-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 1.6rem;
  align-items: start;
}

.twe-qm-col { min-width: 0; }

/* Which half of the form this is. Heavier than a step heading and in the ink
   colour rather than brass, so the two of them read as the page's two headings
   and everything under each reads as belonging to it. */
.twe-qm-colhead {
  margin: 0 0 0.9rem;
  padding-bottom: 0.45rem;
  border-bottom: 2px solid var(--twe-accent);
  color: var(--twe-ink);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* A numbered step. The rule above each one separates it from the last; the
   number is what makes the whole thing read as an order to work through
   instead of a wall of boxes. */
.twe-qm-col .twe-subhead {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-top: 1.1rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--twe-line);
}

/* The first step in a column sits directly under that column's own heading,
   which already draws a line. */
.twe-qm-colhead + .twe-subhead {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.twe-qm-step-n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 999px;
  background: var(--twe-accent);
  color: #f8f4ea;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0;
}

.twe-qm-step-t { flex: 1; min-width: 0; }

/* ── The class's answers ──────────────────────────────────────────────────
   Below both columns, full width, because these are paragraphs a teacher has
   to READ before choosing between them — a 320px column of clipped prose is
   not something anyone can pick the best answer from. */
.twe-answers {
  margin-top: 1.4rem;
  padding-top: 1rem;
  border-top: 1px solid var(--twe-line);
}

.twe-answer {
  margin-top: 0.7rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--twe-line);
  border-radius: 8px;
  background: var(--twe-panel);
}

/* The one the class is looking at, marked in the editor's brass. */
.twe-answer.is-showing {
  border-color: var(--twe-accent);
  box-shadow: inset 3px 0 0 var(--twe-accent);
}

.twe-answer-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.twe-answer-mark {
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--twe-line);
  border-radius: 999px;
  color: var(--twe-mute);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.twe-answer-mark.is-on {
  border-color: rgba(37, 132, 82, 0.5);
  background: rgba(37, 132, 82, 0.1);
  color: #1f7a4d;
}

.twe-answer-text {
  margin: 0.45rem 0 0;
  font-size: 0.82rem;
  line-height: 1.5;
  white-space: pre-wrap;
}

/* The description reads as the second thing they wrote, not a second answer. */
.twe-answer-text--build {
  padding-left: 0.6rem;
  border-left: 2px solid var(--twe-line);
  color: var(--twe-mute);
}

/* Destructive actions, below both columns rather than half way down one of
   them. The note sits on the same line and pushes them to the right, so the
   thing a teacher reads is not the thing that deletes their question. */
.twe-qm-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1.6rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--twe-line);
}

.twe-qm-actions .twe-hint {
  flex: 1;
  min-width: 0;
  margin: 0;
}

@media (max-width: 900px) {
  .twe-qm { padding: 0.5rem; }
  .twe-qm-card { height: calc(100dvh - 1rem); }
  .twe-qm-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  /* A concrete cap, not a percentage: the grid row above is `auto`, so a
     percentage height resolves against a row that is sizing itself from this
     element, and the rail collapsed to less than one item tall. */
  .twe-qm-list {
    flex-direction: row;
    flex-wrap: wrap;
    align-content: flex-start;
    max-height: 8.5rem;
    padding-bottom: 0.6rem;
    border-right: 0;
    border-bottom: 1px solid var(--twe-line);
  }
  .twe-qm-add { flex: 0 0 auto; margin-bottom: 0; }
  .twe-qm-item { flex: 1 1 9rem; }
  .twe-qm-cols { grid-template-columns: minmax(0, 1fr); gap: 0; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE GROUND MAP
   A plan of the world, drawn on with a finger. Same window furniture as the
   Questions window — one overlay, one card — because they are the same KIND of
   thing: a whole job that does not belong to the selected object.

   EVERY COLOUR IN HERE IS A TOKEN. This window is appended to <body>, so it
   only has the --twe-* palette because it is named in the body-level token
   block near the menus — and the first version of it also carried a dozen
   hand-typed hexes, which is how it ended up half parchment and half whatever
   was showing through from the page behind. One source for the palette, and
   the window cannot drift away from the panel it belongs to again.
   ══════════════════════════════════════════════════════════════════════════ */

.twe-map {
  position: fixed;
  inset: 0;
  z-index: 1570;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.1rem;
  background: rgba(8, 11, 18, 0.62);
  backdrop-filter: blur(3px);
  font-size: 14px;
}

.twe-map[hidden] { display: none !important; }

/* OPAQUE, all the way through. Every strip inside this card paints its own
   background rather than letting the card's show past it: a translucent
   toolbar over a dimmed page is where the brush labels went grey-on-grey. */
.twe-map-card {
  display: flex;
  flex-direction: column;
  width: min(1180px, 100%);
  height: min(880px, calc(100dvh - 2.2rem));
  overflow: hidden;
  background: var(--twe-panel);
  color: var(--twe-ink);
  border: 1px solid var(--twe-line);
  border-radius: 4px;
  box-shadow: 0 26px 70px rgba(30, 23, 10, 0.4);
}

/* ── the head: the same double rule and diamond the other windows wear ──── */

.twe-map-head {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  flex: 0 0 auto;
  padding: 0.7rem 0.9rem;
  background: var(--twe-panel);
  border-bottom: 1px solid var(--twe-line);
}

.twe-map-head::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 1px;
  background: var(--twe-line);
}

.twe-map-head::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -7px;
  width: 7px;
  height: 7px;
  transform: translateX(-50%) rotate(45deg);
  background: var(--twe-panel);
  border: 1px solid var(--twe-line);
}

/* IDENTICAL to .twe-qm-head strong. These two windows are the same kind of
   thing — a whole job, lifted out of the panel — so they wear the same title,
   and a heading invented for one of them would make it the odd one out. */
.twe-map-head strong {
  color: var(--twe-accent);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.twe-map-head .twe-hint { margin: 0; color: var(--twe-mute); }
.twe-map-shut { margin-left: auto; align-self: center; }

/* ── the ladder of brushes ──────────────────────────────────────────────── */

.twe-map-tools {
  flex: 0 0 auto;
  padding: 0.55rem 0.9rem;
  background: var(--twe-bg);
  border-bottom: 1px solid var(--twe-line);
}

/* Scrolls sideways rather than wrapping to three rows on a phone: these are a
   ladder from the waterline up to a hilltop, and a wrapped ladder stops reading
   as one. */
.twe-map-brushes {
  display: flex;
  gap: 0.4rem;
  overflow-x: auto;
  padding-bottom: 0.15rem;
  scrollbar-width: thin;
}

.twe-map-brush {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  flex: 0 0 auto;
  padding: 0.4rem 0.7rem;
  background: var(--twe-panel);
  border: 1px solid var(--twe-line);
  border-radius: 999px;
  color: var(--twe-ink);
  font: inherit;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
}

.twe-map-brush:hover {
  border-color: var(--twe-accent);
  background: var(--twe-accent-soft);
}

/* The armed brush is unmistakable — filled, not merely outlined. Which level
   the next drag paints is the single most important state in this window. */
/* White, not the panel cream: this label is 11px bold on the gold fill, and the
   cream landed at 4.4:1 — just under the readable threshold, on the one chip in
   the window whose whole job is to be unmistakable. White clears it at 5.1:1. */
.twe-map-brush.is-on {
  background: var(--twe-accent);
  border-color: var(--twe-accent);
  color: #fff;
  box-shadow: 0 1px 6px rgba(56, 44, 20, 0.28);
}

.twe-map-brush.is-on .twe-map-swatch {
  border-color: rgba(255, 255, 255, 0.85);
  box-shadow: 0 0 0 1px rgba(56, 44, 20, 0.35);
}

.twe-map-swatch {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
  border: 1px solid rgba(35, 38, 45, 0.45);
  border-radius: 3px;
}

/* ── the plan itself ────────────────────────────────────────────────────── */

.twe-map-stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: var(--twe-bg);
}

/* touch-action is the whole reason one finger can draw here: without it the
   browser claims the gesture for scrolling before a pointermove ever arrives. */
.twe-map-canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
  cursor: crosshair;
}

/* What the next stroke would do, in words, sitting over the plan. */
.twe-map-readout {
  position: absolute;
  left: 0.7rem;
  bottom: 0.7rem;
  margin: 0;
  padding: 0.35rem 0.65rem;
  background: var(--twe-panel);
  border: 1px solid var(--twe-line);
  border-radius: 3px;
  box-shadow: 0 2px 10px rgba(56, 44, 20, 0.18);
  color: var(--twe-ink);
  font-family: var(--twe-mono, ui-monospace, monospace);
  font-size: 0.74rem;
  pointer-events: none;
}

/* Naming what is standing in the way earns the one warm colour in here. */
.twe-map-readout.is-warning {
  background: var(--twe-accent-soft);
  border-color: var(--twe-accent);
  color: var(--twe-accent);
  font-weight: 700;
}

/* ── the foot: what this brush means, and the world's size ──────────────── */

.twe-map-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.9rem;
  flex: 0 0 auto;
  padding: 0.55rem 0.9rem;
  background: var(--twe-panel);
  border-top: 1px solid var(--twe-line);
}

.twe-map-foot .twe-row { margin-bottom: 0; flex: 0 0 auto; }
.twe-map-foot .twe-select { width: auto; min-width: 13rem; }

.twe-map-hint {
  margin: 0;
  color: var(--twe-mute);
  font-size: 0.78rem;
}

@media (max-width: 900px) {
  .twe-map { padding: 0.4rem; }
  .twe-map-card { height: calc(100dvh - 0.8rem); }
  .twe-map-head { padding: 0.6rem 0.7rem; }
  .twe-map-head .twe-hint { display: none; }   /* the title and ✕ keep the row */
  .twe-map-foot {
    flex-direction: column;
    align-items: stretch;
    gap: 0.45rem;
  }
  .twe-map-foot .twe-row { justify-content: space-between; }
  .twe-map-foot .twe-select { width: 100%; min-width: 0; }
}


/* ── responsive ─────────────────────────────────────────────────────────── */

.twe-compact-only { display: none; }

/* The thumb bar and the drawer scrim do not exist on a mouse-driven desktop.
   Everything about the bar EXCEPT `display` is declared once here, so the two
   gates below only decide whether it is shown and which buttons it carries —
   in particular `flex-wrap`, without which the last control falls off the
   right edge of a 320px screen instead of moving to a second row. */
.twe-actions {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  padding: 0.35rem 0.5rem;
  padding-bottom: max(0.35rem, env(safe-area-inset-bottom));
  background: var(--twe-panel);
  border-top: 1px solid var(--twe-line);
}

.twe-scrim { display: none; }

@media (max-width: 1120px) {
  .twe-root { grid-template-columns: 240px minmax(0, 1fr) 300px; }
}

/* NOTE: a phone in landscape (844x390) matches this AND the phone block below,
   and both set grid-template-columns at the same specificity — so the phone
   block wins on SOURCE ORDER alone. It must stay after this one. Reordering
   the responsive section silently gives a rotated iPhone the three-column
   desktop layout again, with a 344px viewport, and nothing will warn you. */
@media (max-width: 900px) {
  .twe-root { grid-template-columns: 220px minmax(0, 1fr) 280px; }
}

/* ── no keyboard, whatever the screen size ──────────────────────────────────
   Gated on the INPUT DEVICE, not the width, because an iPad has no Ctrl key
   either. Its layout is fine — that is the one thing the teacher said worked —
   but Ctrl-click to multi-select and the Delete key are both unreachable on
   it, so the two controls that stand in for them appear on any touch device.
   The mode chips are NOT repeated here: on a screen this wide they are already
   in the toolbar, and the phone block below is what puts them back for a
   thumb. */
@media (pointer: coarse) {
  .twe-root { grid-template-rows: 52px minmax(0, 1fr) auto; }

  .twe-actions { display: flex; grid-row: 3; grid-column: 1 / -1; }

  .twe-actions .twe-bar-btn,
  .twe-actions .twe-sep { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE PHONE EDITOR
   ══════════════════════════════════════════════════════════════════════════
   WHY A PHONE NEEDS ITS OWN LAYOUT AND AN iPAD DOES NOT
     An iPad is 820px across its short side and gets the three-column drafting
     room, which is why it has always been fine. A phone is 390px in EITHER
     orientation, so the second condition below is not decoration: rotating an
     iPhone to landscape makes it 844px wide, which used to slip past a
     width-only query and hand a 390px-tall phone the full desktop layout with
     a 344px viewport between two permanent columns.

   THE BUG THIS BLOCK EXISTS FOR
     `.twe-view` carries `grid-column: 2` from the desktop layout. Collapsing
     the template to ONE column did not remove that, so the view landed in an
     IMPLICIT second track that took the whole 390px, while the explicit `1fr`
     track — the only one `.twe-top`'s `grid-column: 1 / -1` can span, because
     `-1` counts lines in the EXPLICIT grid — collapsed to 0px. The toolbar
     rendered as a 24px sliver holding 1400px of buttons: no Done, no drawer
     toggles, no way out of the editor at all. Every `grid-column` below is
     load-bearing.
   ══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 780px), (max-height: 540px) and (pointer: coarse) {
  .twe-root {
    /* top bar · the world · the thumb bar */
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;

    /* `position: fixed; inset: 0` resolves against iOS's LARGE viewport, so the
       bottom of the editor — which is now the bar holding Delete — sits under
       Safari's toolbar until the teacher scrolls it away, and they cannot
       scroll, because the body is locked. svh is the small viewport: the one
       that is always on screen. Setting top + height rather than inset means
       `bottom` is over-constrained and correctly ignored. */
    height: 100vh;
    height: 100svh;
  }

  .twe-top { grid-row: 1; grid-column: 1; }
  .twe-view { grid-row: 2; grid-column: 1; }

  .twe-compact-only { display: inline-flex; }
  .twe-wide-only { display: none; }

  /* The top bar is navigation only — Done, the two drawers, undo/redo and the
     one button that reaches everything else. It must never scroll: a control
     you have to swipe sideways to find is a control nobody finds. */
  .twe-top {
    gap: 0.25rem;
    padding: 0.3rem 0.4rem;
    padding-top: max(0.3rem, env(safe-area-inset-top));
    padding-left: max(0.4rem, env(safe-area-inset-left));
    padding-right: max(0.4rem, env(safe-area-inset-right));
    /* The six controls measure ~360px and fit a 390 or 375px screen. On a 320px
       SE they must WRAP to a second row rather than overflow: `margin-right:
       auto` on Done pushes the surplus off the right edge, and with a visible
       overflow the item that hangs off is unreachable rather than merely
       out of sight. Wrapping keeps every control on the screen at any width. */
    flex-wrap: wrap;
    overflow-x: visible;
  }

  .twe-top .twe-sep { display: none; }
  .twe-done { margin-right: auto; }        /* the way out, hard against the left */
  .twe-btn-word { display: none; }         /* ↶ and ↷ keep the glyph, drop the word */

  /* ── the thumb bar ─────────────────────────────────────────────────────── */

  .twe-actions { grid-row: 3; grid-column: 1; display: flex; }

  /* On a phone the modes come back — the toolbar's copies are hidden here, and
     a thumb should not have to reach the top of the screen to switch tool. */
  .twe-actions .twe-bar-btn {
    display: inline-flex;
    flex: 1 1 0;
    justify-content: center;
    min-width: 3.2rem;
  }

  .twe-actions .twe-sep { display: block; height: 28px; margin: 0 0.1rem; }

  /* ── the drawers ───────────────────────────────────────────────────────── */

  /* Narrower than they were: at 320px on a 390px screen the world being edited
     was 82% covered, and the strip left over was live canvas rather than a way
     out. Now a scrim owns that strip and closes the drawer. */
  .twe-tree,
  .twe-side {
    position: fixed;
    top: 0;
    bottom: 0;
    z-index: 1550;
    width: min(300px, 84vw);
    padding-top: max(0.35rem, env(safe-area-inset-top));
    padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
    box-shadow: 0 18px 50px rgba(56, 44, 20, 0.3);
    transition: transform 0.18s ease;
    /* iOS keeps scrolling the page once a nested scroller hits its end, which
       drags Safari's chrome back over a `position: fixed` layout. */
    overscroll-behavior: contain;
  }

  .twe-tree { left: 0; transform: translateX(-105%); }
  .twe-side { right: 0; transform: translateX(105%); border-left: 1px solid var(--twe-line); }
  .twe-tree.is-open,
  .twe-side.is-open { transform: none; }

  .twe-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 1540;
    background: rgba(30, 23, 10, 0.38);
  }

  .twe-scrim[hidden] { display: none; }

  /* ── finger sizes ──────────────────────────────────────────────────────── */

  /* iOS asks for 44px and a fingertip is wider than that. The tree row's four
     hit zones used to sit edge to edge at 30px tall, so a tap aimed at a name
     landed on the caret and collapsed the object, or on the eye and hid it. */
  /* 44px tall for the finger, narrow for the six that have to share 390px. The
     glyph-only ones (↶ ↷ ⋯ 🗑) would come out ~37px wide on their content
     alone, so they get the width back explicitly — a min-width on every button
     would instead stretch "☰ Objects" and "Details" and cost the fit. */
  .twe-btn {
    min-height: 44px;
    padding: 0.5rem 0.55rem;
    font-size: 0.7rem;
    letter-spacing: 0.06em;
  }

  .twe-glyph { min-width: 44px; justify-content: center; }

  .twe-node { padding: 0.3rem 0.3rem 0.3rem 0; }
  .twe-node-label { min-height: 40px; }
  .twe-caret, .twe-eye { min-width: 40px; min-height: 40px; }

  .twe-tree, .twe-side, .twe-pane { overscroll-behavior: contain; }

  /* Safari zooms the whole page when a focused field is under 16px, and a
     zoomed page moves `position: fixed` chrome off-screen — a second, quite
     separate way for the toolbar to disappear. 16px exactly, nothing smaller. */
  .twe-input,
  .twe-num,
  .twe-select,
  textarea.twe-input { font-size: 16px; }

  /* Drag-and-drop is how rows are combined and nested, and iPhone Safari does
     not synthesise it from touch. Advertising a target that cannot receive a
     drop is worse than not offering it. (iPadOS does support it, and iPadOS
     never reaches this block.) */
  .twe-droproot { display: none; }

  /* One window, one screen. */
  .twe-ai,
  .twe-menu {
    max-width: calc(100vw - 1rem);
    max-height: 70vh;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* Editor chrome is a control surface, not a document: no grey tap flash, no
   text selection when a drag starts on a label, no iOS callout on long-press.
   The project already calls these "the three lines that make a game surface
   feel like one" — the editor simply never had them. */
@media (pointer: coarse) {
  .twe-top,
  .twe-actions,
  .twe-tree,
  .twe-node,
  .twe-btn {
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  .twe-tree, .twe-side, .twe-ai-progress-fill { transition: none; }
  .twe-ai-progress-fill::after { animation: none; }
}

/* ── Projector mode ────────────────────────────────────────────────────── */

body.tww-projector .site-header,
body.tww-projector .site-footer,
body.tww-projector .tww-intro,
body.tww-projector .tww-panels,
body.tww-projector .tww-teacher-btn { display: none; }

body.tww-projector { padding-top: 0; }

body.tww-projector .tww {
  width: 100%;
  max-width: none;
  padding: 0;
}

body.tww-projector .tww-layout {
  display: block;
  gap: 0;
}

body.tww-projector .tww-stage {
  position: fixed;
  inset: 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

body.tww-projector .tww-canvas-wrap {
  width: 100vw;
  height: 100vh;
  aspect-ratio: auto;
}


/* ══════════════════════════════════════════════════════════════════════════
   THE FRONT DOOR — the main menu overlay (menu.js)
   The world keeps rendering behind it: backdrop, not loading screen. A Deco
   gate — stepped ziggurat crown, gold frame, a faint static sunburst behind
   the card. Two doors, students green, teachers amber — the same accents the
   rest of the page already means those things by. Sits under the auth modal
   (10000) so signing in stacks on top.
   ══════════════════════════════════════════════════════════════════════════ */

.twm-root {
  position: fixed;
  inset: 0;
  z-index: 3000;
  display: flex;
  /* The root scrolls and the card centres with auto margins, so the card's
     gold frame (drawn by its pseudo-elements) never has to scroll away from
     its own edges. */
  overflow-y: auto;
  padding: 1.4rem 1rem;
  background:
    radial-gradient(60% 42% at 50% 30%, rgba(216, 181, 108, 0.07), transparent 70%),
    repeating-conic-gradient(from -90deg at 50% 30%, rgba(216, 181, 108, 0.035) 0deg 4deg, rgba(216, 181, 108, 0) 4deg 11deg)
    rgba(4, 8, 14, 0.78);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.twm-root[hidden] { display: none !important; }

body.twm-open { overflow: hidden; }

/* The gate: the card is clipped into a two-step ziggurat crown; ::before is
   the gold plate, ::after the night paper 1px inside it, so the hairline
   follows every step. The card's own clip also keeps both pseudos and the
   close button inside the shape. */
.twm-card {
  position: relative;
  z-index: 0;
  width: min(820px, 100%);
  margin: auto;
  padding: 2.2rem 1.8rem 1.6rem;
  border: 0;
  border-radius: 0;
  background: transparent;
  clip-path: polygon(
    0 26px, 14px 26px, 14px 12px, 28px 12px, 28px 0,
    calc(100% - 28px) 0, calc(100% - 28px) 12px, calc(100% - 14px) 12px, calc(100% - 14px) 26px, 100% 26px,
    100% 100%, 0 100%
  );
  filter: drop-shadow(0 24px 60px rgba(0, 0, 0, 0.55));
  animation: twm-in 0.45s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.twm-card::before,
.twm-card::after {
  content: "";
  position: absolute;
  z-index: -1;
}

.twm-card::before {
  inset: 0;
  background: linear-gradient(
    180deg,
    #ecd9a8,
    #d8b56c 18%,
    rgba(138, 109, 59, 0.8) 55%,
    #8a6d3b
  );
}

.twm-card::after {
  inset: 1px;
  clip-path: polygon(
    0 26px, 14px 26px, 14px 12px, 28px 12px, 28px 0,
    calc(100% - 28px) 0, calc(100% - 28px) 12px, calc(100% - 14px) 12px, calc(100% - 14px) 26px, 100% 26px,
    100% 100%, 0 100%
  );
  background: linear-gradient(180deg, rgba(13, 20, 32, 0.98), rgba(8, 13, 21, 0.985));
}

@keyframes twm-in {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .twm-card, .twm-title { animation: none; }
  .twm-btn { transition: none; }
}

/* Kept clear of the clipped corner steps: at this offset the whole button is
   inside the ziggurat shape. */
.twm-close {
  position: absolute;
  top: 1.05rem;
  right: 1.1rem;
  width: 34px;
  height: 34px;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 999px;
  background: rgba(6, 10, 18, 0.7);
  color: var(--mute, #8ca0c0);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.twm-close:hover { border-color: var(--tww-gold); color: #fff; }

.twm-head {
  position: relative;
  margin-bottom: 1.5rem;
  padding-right: 2.4rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--tww-gold-edge);
}
/* ::before (second rule) and ::after (diamond) are shared with
   .tww-panel-head, up in the panels section. */

.twm-title {
  margin: 0;
  font-family: var(--tww-display);
  font-weight: 400;
  font-size: clamp(1.6rem, 4vw, 2.3rem);
  letter-spacing: 0.07em;
  color: var(--tww-gold-bright);
  text-shadow: 0 0 34px rgba(216, 181, 108, 0.22);
  /* One-time marquee settle when the gate opens. */
  animation: twm-title-in 0.8s cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

@keyframes twm-title-in {
  from { opacity: 0; letter-spacing: 0.26em; }
  to { opacity: 1; letter-spacing: 0.07em; }
}

.twm-tag {
  margin: 0.35rem 0 0;
  font-family: var(--tww-serif);
  font-style: italic;
  font-size: 1.08rem;
  color: var(--mute, #8ca0c0);
}

.twm-doors {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

@media (max-width: 720px) {
  .twm-doors { grid-template-columns: 1fr; }
}

/* The two doors as marquee panels. */
.twm-door {
  padding: 1.15rem 1.2rem 1.2rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 2px;
  background: linear-gradient(180deg, rgba(6, 10, 18, 0.72), rgba(6, 10, 18, 0.5));
}

.twm-door-title {
  margin: 0 0 0.9rem;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  text-align: center;
}

.twm-door[aria-label="Students"] .twm-door-title { color: var(--tww-glow); }
.twm-door[aria-label="Teachers"] .twm-door-title { color: #ffd489; }

.twm-field {
  display: block;
  margin-bottom: 0.7rem;
}

.twm-field > span {
  display: block;
  margin-bottom: 0.3rem;
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--hint, #5a7898);
}

.twm-field input[type="text"] {
  width: 100%;
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 3px;
  background: rgba(6, 10, 18, 0.85);
  color: var(--tww-ink);
  font-family: var(--tww-serif);
  font-size: 1.08rem;
}

.twm-field input[data-role="code"] {
  font-family: var(--tww-mono);
  font-size: 1.25rem;
  letter-spacing: 0.35em;
  text-transform: uppercase;
}

.twm-field input:focus {
  outline: none;
  border-color: rgba(61, 245, 143, 0.5);
  box-shadow: 0 0 0 3px rgba(61, 245, 143, 0.1);
}

.twm-btn {
  display: block;
  width: 100%;
  padding: 0.55rem 1rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 999px;
  background: rgba(9, 13, 22, 0.78);
  color: var(--tww-ink);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 0.16s, background 0.16s, color 0.16s;
}

/* The author `display: block` above beats the user-agent [hidden] rule, so
   `newBtn.hidden = true` in menu.js left "＋ New world" on screen next to the
   form it had just opened. Same trap as .twe-root and .twe-ai. */
.twm-btn[hidden] { display: none !important; }

.twm-btn:hover { border-color: var(--tww-gold); color: #fff; }

.twm-btn.is-go {
  border-color: rgba(61, 245, 143, 0.42);
  background: rgba(61, 245, 143, 0.12);
  color: #d6ffe9;
}

.twm-btn.is-go:hover {
  border-color: rgba(61, 245, 143, 0.65);
  background: rgba(61, 245, 143, 0.2);
  color: #fff;
}

.twm-btn[disabled] { opacity: 0.5; pointer-events: none; }

.twm-row { display: flex; gap: 0.5rem; }
.twm-row .twm-btn { width: auto; flex: 1; }

.twm-or {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.8rem 0;
  color: var(--hint, #5a7898);
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.twm-or::before,
.twm-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--tww-gold-dim);
}

.twm-hint {
  margin: 0.5rem 0 0;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--mute, #8ca0c0);
}

.twm-signed { margin: 0 0 0.7rem; }
.twm-signed strong { color: #ffd489; }

.twm-error {
  margin: 0.5rem 0 0;
  font-size: 0.78rem;
  color: #ffbcbc;
}

.twm-error:empty { display: none; }

/* ── the world shelf ── */

.twm-worlds {
  list-style: none;
  margin: 0 0 0.8rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  max-height: 260px;
  overflow-y: auto;
}

.twm-world { display: flex; gap: 0.4rem; }

.twm-world-open {
  flex: 1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 3px;
  background: rgba(9, 13, 22, 0.78);
  color: var(--tww-ink);
  font-size: 0.88rem;
  cursor: pointer;
  text-align: left;
}

.twm-world-open:hover { border-color: rgba(255, 212, 137, 0.55); color: #fff; }

.twm-world-name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.twm-world-when {
  flex: none;
  font-size: 0.7rem;
  color: var(--hint, #5a7898);
}

.twm-world-del {
  width: 34px;
  border: 1px solid rgba(255, 138, 138, 0.35);
  border-radius: 3px;
  background: rgba(9, 13, 22, 0.78);
  color: #ffbcbc;
  cursor: pointer;
}

.twm-world-del:hover { border-color: rgba(255, 138, 138, 0.7); color: #fff; }

/* ── the new-world form ── */

.twm-new { margin-top: 0.7rem; }

.twm-templates > span {
  display: block;
  margin-bottom: 0.3rem;
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--hint, #5a7898);
}

.twm-radio {
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
  padding: 0.45rem 0.55rem;
  margin-bottom: 0.35rem;
  border: 1px solid var(--tww-gold-dim);
  border-radius: 3px;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--mute, #8ca0c0);
  cursor: pointer;
}

.twm-radio:has(input:checked) {
  border-color: rgba(255, 212, 137, 0.55);
  color: var(--tww-ink);
}

.twm-radio input { margin-top: 0.2rem; accent-color: #ffd489; }
.twm-radio strong { color: var(--text-strong, #f7fbff); }

/* ── the world chip in the room bar ── */

.tww-room-world {
  align-self: center;
  max-width: 11rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0.2rem 0.55rem;
  border: 1px solid rgba(255, 212, 137, 0.35);
  border-radius: 999px;
  background: rgba(24, 20, 10, 0.7);
  color: #ffd489;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.08em;
}

.tww-room-world[hidden] { display: none !important; }

/* ══════════════════════════════════════════════════════════════════════════
   THE SURFACE-PAINT WINDOW — .twe-paintwin

   Deliberately NOT a full-screen modal like the map: the whole point of the
   brush is clicking surfaces in the world while the window is open, so it
   floats as a card and everything around it stays clickable. Same parchment,
   same double-rule head as the other windows: it is one of them, it just does
   not take the room over.

   IT IS DRAGGED BY ITS HEAD, and where it lands is remembered. A fixed corner
   was wrong for a tool used WHILE looking at an object: the object the teacher
   wants to paint is as likely to be under the window as anywhere else, and a
   window that cannot move turns that into "move the camera instead". So JS
   writes left/top on this element (see paint-window.js) and the values below
   are only the first-run fallback.

   EVERY COLOUR HERE COMES FROM THE TOKEN BLOCK, which this surface must be
   listed in — it is appended to <body>, where .twe-root's tokens do not reach.
   Getting that wrong does not fail loudly: `background: var(--twe-panel)` with
   no token resolves to transparent and the window renders as floating text
   over whatever is behind it. Literal fallbacks below so the failure mode is
   "slightly wrong beige" rather than "invisible window".
   ══════════════════════════════════════════════════════════════════════════ */

.twe-paintwin {
  position: fixed;
  left: 12px;
  top: 96px;
  z-index: 1540;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  width: 328px;
  max-height: min(74dvh, 720px);
  overflow-y: auto;
  padding: 0.7rem 0.85rem 0.85rem;
  background: var(--twe-panel, #f8f4ea);
  color: var(--twe-ink, #23262d);
  border: 1px solid var(--twe-line, #d9cfb8);
  border-radius: 4px;
  box-shadow: 0 18px 50px rgba(30, 23, 10, 0.38);
  font-size: 14px;
}

.twe-paintwin[hidden] { display: none !important; }

.twe-paintwin.is-dragging {
  box-shadow: 0 26px 64px rgba(30, 23, 10, 0.46);
  /* Nothing inside may take the pointer or select while the card is moving. */
  user-select: none;
}

.twe-paintwin-head {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex: 0 0 auto;
  padding-bottom: 0.55rem;
  border-bottom: 1px solid var(--twe-line, #d9cfb8);
  cursor: grab;
  /* One finger drags the card; the page must not scroll out from under it. */
  touch-action: none;
  user-select: none;
}

.twe-paintwin.is-dragging .twe-paintwin-head { cursor: grabbing; }

/* The grip: the only affordance saying "this bar is a handle". Sits before the
   title, dim enough to read as furniture rather than as a control. */
.twe-paintwin-grip {
  align-self: center;
  color: var(--twe-faint, #a39a83);
  font-size: 0.8rem;
  letter-spacing: -0.1em;
}

.twe-paintwin-head::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 1px;
  background: var(--twe-line);
}

.twe-paintwin-head::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -7px;
  width: 7px;
  height: 7px;
  transform: translateX(-50%) rotate(45deg);
  background: var(--twe-panel);
  border: 1px solid var(--twe-line);
}

/* Identical treatment to the map and Questions window titles — same kind of
   thing, same dress. */
.twe-paintwin-head strong {
  color: var(--twe-accent);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.twe-paintwin-obj {
  color: var(--twe-mute);
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.twe-paintwin-shut { margin-left: auto; align-self: center; }

/* ── face chips ─────────────────────────────────────────────────────────── */

.twe-paintwin-faces { display: flex; flex-wrap: wrap; gap: 0.3rem; }

.twe-paintwin-face {
  padding: 0.28rem 0.55rem;
  background: var(--twe-bg);
  border: 1px solid var(--twe-line);
  border-radius: 999px;
  color: var(--twe-ink);
  font: inherit;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  cursor: pointer;
}

.twe-paintwin-face.is-on {
  border-color: var(--twe-accent);
  color: var(--twe-accent);
  box-shadow: inset 0 0 0 1px var(--twe-accent);
}

/* A dot marks faces that already carry paint, so they can be found again. */
.twe-paintwin-face.is-painted::after {
  content: "●";
  margin-left: 0.3rem;
  font-size: 0.55em;
  color: var(--twe-accent);
  vertical-align: middle;
}

/* ── the pixel grid ─────────────────────────────────────────────────────── */

.twe-paintwin-gridwrap {
  display: flex;
  justify-content: center;
  /* flex-start, or the wrap's default stretch squashes the canvas to the
     wrap's height and every pixel goes rectangular; flex-shrink 0, or the
     window column squeezes the grid instead of scrolling. */
  align-items: flex-start;
  flex: 0 0 auto;
  padding: 0.4rem;
  background: var(--twe-bg);
  border: 1px solid var(--twe-line);
  border-radius: 3px;
  overflow: auto;
}

.twe-paintwin-grid {
  image-rendering: pixelated;
  cursor: crosshair;
  /* One finger paints; the page must not scroll out from under a stroke. */
  touch-action: none;
  max-width: 100%;
}

/* ── tools and colours ──────────────────────────────────────────────────── */

.twe-paintwin-tools { display: flex; flex-wrap: wrap; gap: 0.35rem; }

.twe-paintwin-tool.is-on {
  border-color: var(--twe-accent);
  color: var(--twe-accent);
  box-shadow: inset 0 0 0 1px var(--twe-accent);
}

.twe-paintwin-colors { display: flex; align-items: center; gap: 0.5rem; }

.twe-paintwin-swatches { display: flex; flex-wrap: wrap; gap: 4px; }
.twe-paintwin-swatches.is-recent { flex: 1; min-height: 22px; }

/* The world's whole palette is sixty-odd colours — six rows of chips, which
   made the card taller than the objects it was being used on. Capped and
   scrolled: the recents row above it is what a teacher actually reaches for. */
.twe-paintwin-swatches.is-palette {
  max-height: 78px;
  overflow-y: auto;
  padding-right: 2px;
  scrollbar-width: thin;
}

.twe-paintwin-swatch {
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid rgba(20, 16, 8, 0.35);
  border-radius: 3px;
  cursor: pointer;
}

.twe-paintwin-swatch:hover {
  outline: 2px solid var(--twe-accent);
  outline-offset: 1px;
}

.twe-paintwin-voxnote {
  margin: 0;
  color: var(--twe-mute);
  font-size: 0.78rem;
  line-height: 1.45;
}

.twe-paintwin-status {
  min-height: 1em;
  color: var(--twe-accent);
  font-size: 0.72rem;
  letter-spacing: 0.05em;
}

/* Phone: a bottom sheet, matching how the rest of the editor collapses — and
   NOT draggable, because there is nowhere to drag it to. paint-window.js
   clears its inline left/top at this width, or those would beat these rules
   (an inline style outranks a media query) and strand the sheet mid-screen. */
@media (max-width: 760px) {
  .twe-paintwin {
    left: 8px;
    right: 8px;
    top: auto;
    width: auto;
    bottom: calc(8px + env(safe-area-inset-bottom));
    max-height: 52dvh;
  }

  .twe-paintwin-head { cursor: default; }
  .twe-paintwin-grip { display: none; }
}
