:root {
  --bg: #f4f5f7;
  --panel: #ffffff;
  --ink: #1f2430;
  --ink-dim: #6b7280;
  --line: #dfe2e8;
  --accent: #3959cc;
  --accent-dark: #2b45a4;
  --ok: #0f7b4f;
  --warn: #9a5b00;
  --danger: #c02a4b;
  --radius: 10px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--ink);
  background: var(--bg);
}

code { font: 12.5px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }

/* ---------------------------------------------------------------- topbar */

.topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 52px;
  padding: 0 18px;
  background: #fff;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 20;
}
.brand { font-weight: 700; color: var(--ink); text-decoration: none; letter-spacing: -0.2px; }
.toplink {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-dim);
  text-decoration: none;
}
.toplink:hover { color: var(--ink); }
.toplink[aria-current="page"] { color: var(--ink); }
.crumb { color: var(--ink-dim); }
.template-name { color: var(--ink-dim); }
.spacer { flex: 1; }

.llm-badge {
  font-size: 12px;
  padding: 4px 9px;
  border-radius: 20px;
  border: 1px solid var(--line);
  color: var(--ink-dim);
  white-space: nowrap;
}
.llm-badge.ok { color: var(--ok); border-color: #bfe3d0; background: #f2fbf6; }
.llm-badge.warn { color: var(--warn); border-color: #f0dcae; background: #fffaef; }

.who {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  padding: 4px 9px;
  border-radius: 20px;
  background: var(--bg);
  border: 1px solid var(--line);
  white-space: nowrap;
}
.signout { margin: 0; }

/* Sign-in screen: one narrow panel, centred, no app chrome to distract. */
.signin { max-width: 380px; margin: 8vh auto; padding: 0 16px; }
.signin .panel { padding: 22px 24px; }
.signin h1 { margin-top: 0; }

.activity { list-style: none; margin: 0; padding: 0; font-size: 12.5px; }
.activity li {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: baseline;
  padding: 6px 0;
  border-top: 1px solid var(--line);
  color: var(--ink-dim);
}
.activity li:first-child { border-top: 0; }
.act-when { white-space: nowrap; font-variant-numeric: tabular-nums; min-width: 150px; }
.act-user { font-weight: 700; color: var(--ink); min-width: 74px; }
.act-what { color: var(--ink); }
.act-detail { color: var(--ink-dim); }

/* Ends of the trail keep their arrows as dimmed text rather than disappearing,
   so the two links do not swap sides between the first and last page. */
.pager {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  font-size: 12.5px;
}
.pager a { color: var(--accent); font-weight: 600; text-decoration: none; }
.pager a:hover { text-decoration: underline; }
.pager .off { color: var(--ink-dim); opacity: 0.55; }
.pager-at { margin-left: auto; margin-right: auto; color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.pager + .hint { margin-top: 8px; }

.msg-when { font-weight: 400; color: var(--ink-dim); letter-spacing: 0; text-transform: none; }

.rename { display: flex; align-items: center; gap: 6px; }
.rename input {
  font-size: 15px;
  font-weight: 600;
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  min-width: 240px;
}
.rename input:hover { border-color: var(--line); }
.rename input:focus { border-color: var(--accent); background: #fff; outline: none; }

/* ---------------------------------------------------------------- flashes */

.flashes { padding: 12px 18px 0; display: grid; gap: 8px; }
.flash, .callout {
  padding: 10px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: #fff;
  font-size: 14px;
}
.flash.error, .callout.error { border-color: #f0c2cd; background: #fdf3f5; color: #8c1f39; }
.flash.ok { border-color: #bfe3d0; background: #f2fbf6; color: #0d6440; }
.callout.warn { border-color: #f0dcae; background: #fffaef; color: #6b4300; }

/* ---------------------------------------------------------------- forms */

label { display: grid; gap: 5px; font-size: 13px; font-weight: 600; color: var(--ink-dim); }
/* Enumerated by type, so a field type missing from this list silently falls back
   to the browser default - which is how the login password box ended up narrower
   than the username box above it. Add new text-like types here. */
input[type=text], input[type=number], input[type=password], textarea, select {
  font: inherit;
  font-weight: 400;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  width: 100%;
}
textarea { resize: vertical; line-height: 1.45; }
input:focus, textarea:focus, select:focus { outline: 2px solid #cbd3f2; border-color: var(--accent); }
input[type=file] { font-size: 13px; font-weight: 400; padding: 7px; background: #fff; }

button {
  font: inherit;
  font-weight: 600;
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  cursor: pointer;
}
button:hover { background: #f7f8fa; }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.primary:hover { background: var(--accent-dark); }
button:disabled { opacity: 0.55; cursor: default; }
button.link, .link {
  background: none;
  border: none;
  padding: 2px 0;
  color: var(--accent);
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  text-decoration: none;
}
button.link:hover, .link:hover { text-decoration: underline; background: none; }
button.link.danger, .danger { color: var(--danger); }
.primary-link { color: var(--accent-dark); }

.stack { display: grid; gap: 14px; }
.row { display: flex; gap: 12px; align-items: flex-end; }
.row .grow { flex: 1; }
.row .narrow { width: 110px; }
.hint { font-size: 12px; font-weight: 400; color: var(--ink-dim); margin: 0; }

/* ---------------------------------------------------------------- dashboard */

.dashboard {
  max-width: 1120px;
  margin: 0 auto;
  padding: 18px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  align-items: start;
}
.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px;
}
.panel.wide { grid-column: 1 / -1; }
.dashboard > .callout { grid-column: 1 / -1; margin: 0; }
.panel h1 { font-size: 15px; margin: 0 0 14px; letter-spacing: -0.1px; }
.count {
  font-size: 12px;
  color: var(--ink-dim);
  font-weight: 500;
  background: var(--bg);
  border-radius: 20px;
  padding: 2px 8px;
  margin-left: 4px;
}
.empty { color: var(--ink-dim); font-size: 14px; margin: 0; }

.cards { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.card { border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px; }
.card-title { font-weight: 700; color: var(--ink); text-decoration: none; }
a.card-title:hover { color: var(--accent); }
.card-meta { font-size: 12.5px; color: var(--ink-dim); margin-top: 3px; }
.card-actions {
  display: flex;
  gap: 14px;
  align-items: center;
  margin-top: 9px;
  flex-wrap: wrap;
}
.card-actions form { margin: 0; }
/* Starting a page is the primary thing a template is for, so it leads the card's
   actions as a button rather than sitting among the plain links. */
.card-actions .new-page { display: flex; gap: 8px; align-items: center; }
.card-actions .new-page select { width: auto; font-size: 13px; padding: 6px 8px; }
.hint.lead { margin: -4px 0 12px; }
.tag {
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-dim);
  background: var(--bg);
  border-radius: 4px;
  padding: 1px 6px;
  margin-left: 6px;
}
.tag.pending { color: var(--warn); background: #fffaef; }

details.inline { display: inline-block; }
details.inline > summary {
  color: var(--accent);
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  list-style: none;
}
details.inline > summary::-webkit-details-marker { display: none; }
form.mini {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 8px;
  padding: 10px;
  background: var(--bg);
  border-radius: 8px;
}
form.mini input[type=text] { width: 200px; }
form.mini input[type=number] { width: 90px; }

/* ------------------------------------------------------------- demo mode */

/* The page on a phone and nothing else. Dark, because a demo is looked at rather
   than worked in: the studio's light chrome would compete with the screen, and
   there is no chrome here to be consistent with. */
.demo-body { height: 100vh; overflow: hidden; background: #14161c; }
.demo {
  height: 100vh;
  padding: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* The frame is fitted to this box (device.js measures it, padding included), so
     it should never overflow — and if a future size does, clipping is a better
     demo than a scrollbar across the presenter's screen. */
  overflow: hidden;
}
/* ---------------------------------------------------------------- editor */

/* The editor claims whatever the topbar and any flash banner leave, measured
   rather than assumed. It used to be `height: calc(100vh - 52px)`, hardcoding
   the topbar's height and knowing nothing about the flash above it — so a
   flashed message ("Reverted to revision 2.") pushed the grid ~55px past the
   bottom of the window, taking the end of the chat with it. The messages list
   scrolled to its own end and still showed nothing more, because the clipping
   was outside the scrolling element and `overflow: hidden` here denied the body
   the scroll that would have revealed it. */
.editor-body {
  overflow: hidden;
  height: 100vh;
  display: flex;
  flex-direction: column;
}
.editor {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 400px;
  /* An explicit row is required: otherwise the row auto-sizes to the very tall
     preview iframe and the chat composer is pushed below the viewport. */
  grid-template-rows: minmax(0, 1fr);
  flex: 1;
  min-height: 0;
}

.stage {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  border-right: 1px solid var(--line);
}

/* The template preview: the same stage, claiming the width the chat panel takes
   in the editor. It borrows `editor-body` for the measured full-height column
   rather than restating it, so a fix to the flash-banner problem documented above
   lands on both screens. The stage's right-hand rule is the seam against the chat
   panel, so there is nothing for it to divide here. */
.preview-screen { display: flex; flex: 1; min-height: 0; }
.preview-screen .stage { flex: 1; border-right: 0; }
/* Facts about the template under the frame, where the editor puts the composer.
   In the foot rather than the bar because they are reference, not controls, and
   the bar is already the busiest row on the screen. */
.stage-foot {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 8px 16px;
  background: #fff;
  border-top: 1px solid var(--line);
  font-size: 12px;
}
.stage-foot a { color: inherit; }
.stage-bar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 8px 16px;
  background: #fff;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  /* Containing block for the "Save as template" panel below. */
  position: relative;
}
/* "Save as template" opens as a panel hanging off the bar, not inside it: the
   bar is a flex row above a preview that takes the remaining height, so letting
   the open form grow it would resize the preview - and reflow the page being
   looked at - every time the form is opened. */
/* Hung off the bar rather than off the summary: the summary is centred among
   taller controls, so anchoring to it would leave the panel straddling the bar's
   lower edge. */
.stage-bar details.inline > form.mini {
  position: absolute;
  top: 100%;
  right: 16px;
  z-index: 5;
  margin-top: 0;
  width: 320px;
  background: #fff;
  border: 1px solid var(--line);
  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.13);
}
.stage-bar details.inline > form.mini input[type=text] { width: 100%; }
/* The width panel opens under the control it belongs to, at the bar's left end;
   "Save as template" is at the other end and keeps the right edge. */
.stage-bar details.inline#page-width > form.mini { right: auto; left: 16px; }

.dim { color: var(--ink-dim); }
.zoom, .viewport { display: flex; align-items: center; gap: 6px; font-size: 12px; }
.zoom select, .viewport select { width: auto; padding: 3px 6px; font-size: 12px; }

.stage-scroll {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 20px;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  background:
    linear-gradient(45deg, #eceef1 25%, transparent 25%) -10px 0,
    linear-gradient(-45deg, #eceef1 25%, transparent 25%) -10px 0,
    linear-gradient(45deg, transparent 75%, #eceef1 75%),
    linear-gradient(-45deg, transparent 75%, #eceef1 75%);
  background-size: 20px 20px;
  background-color: #f4f5f7;
}
.device {
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 6px 24px rgba(20, 26, 40, 0.16);
  overflow: hidden;
  transform-origin: top center;
  flex: none;
}
.device.busy { opacity: 0.55; }
.device .screen { position: relative; }
/* Off unless a device is picked, so "Fit to content" is exactly the bare frame
   it always was. */
.device .chrome { display: none; }
/* 78vh is the pre-JS fallback only; app.js sets a real height either way. */
#preview { display: block; width: 100%; height: 78vh; border: 0; background: #fff; }

/* ------------------------------------------------------- device frame */

/* The frame answers "how does this look on a phone", which is a question about a
   fixed screen: in this mode the iframe stops growing to its content and the page
   scrolls inside the bezel instead. app.js owns the sizes (see DEVICES there) —
   this file only draws the hardware. */
.device.framed {
  --bezel: 13px;
  padding: var(--bezel);
  background: linear-gradient(160deg, #4a4d55, #23252b 45%, #3c3f47);
  border-radius: 68px;
  box-shadow:
    0 18px 44px rgba(20, 26, 40, 0.32),
    inset 0 0 0 1px rgba(255, 255, 255, 0.14);
}
.device.framed .screen { border-radius: 55px; overflow: hidden; }
.device.framed .chrome {
  display: block;
  position: absolute;
  inset: 0;
  /* The overlay covers the whole screen, so it must never swallow a click or a
     scroll aimed at the page under it. */
  pointer-events: none;
  /* Glyph colour for the status bar and home indicator. Dark is the default
     because these pages are light; app.js flips it per page (see tintChrome). */
  color: #0b0c0f;
}
.device.framed.chrome-light .chrome { color: #fff; }

.device.framed .status-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 54px;
  padding: 12px 30px 0;
  font: 600 15px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.device.framed .status-time { font-variant-numeric: tabular-nums; }
.device.framed .status-icons { display: flex; align-items: center; gap: 5px; }
.device.framed .status-icons svg { display: block; }

/* The island is a hole in the panel, not a glyph, so it stays black whichever way
   the status bar is tinted. */
.device.framed .island {
  position: absolute;
  top: 11px;
  left: 50%;
  transform: translateX(-50%);
  width: 125px;
  height: 37px;
  border-radius: 19px;
  background: #000;
}
.device.framed .home-bar {
  position: absolute;
  bottom: 9px;
  left: 50%;
  transform: translateX(-50%);
  width: 140px;
  height: 5px;
  border-radius: 3px;
  background: currentColor;
  opacity: 0.85;
}

/* ---------------------------------------------------------------- chat */

.chat { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: #fff; }
.chat-head {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.chat-head h2 { font-size: 14px; margin: 0; }

/* Shrinkable, so the picker gives way to the tallies beside it in a narrow
   panel rather than wrapping "Cost $0.0000" onto a second line. */
.model-pick { min-width: 0; }
.model-pick select {
  font-size: 11.5px;
  font-weight: 400;
  padding: 2px 4px;
  max-width: 180px;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--ink-dim);
  background: #fff;
}
.model-pick select:disabled { opacity: 0.55; }

.chat-note {
  margin: 0;
  padding: 8px 16px;
  font-size: 12px;
  color: var(--warn);
  background: #fffaef;
  border-bottom: 1px solid var(--line);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.revisions > summary {
  font-size: 12px;
  color: var(--accent);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  /* Both tallies keep their count on the same line as their word; the model
     picker beside them is the flex item that gives way. */
  white-space: nowrap;
}
.revisions > summary::-webkit-details-marker { display: none; }
.revisions ul {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  max-height: 220px;
  overflow: auto;
  font-size: 12px;
}
.revisions li {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 4px 0;
  border-top: 1px solid var(--line);
  color: var(--ink-dim);
}
.revisions form { margin: 0; }
/* The cost panel is a Revisions-shaped <details>, so it inherits that summary and
   only has to describe its own table. It opens as an overlay rather than in the
   flow, because the head is a single flex row: opening it inline squeezed the
   table into whatever was left between the model picker and Revisions, and
   wrapping the row instead pushed Revisions down a line every time. */
.costs { position: relative; }
.costs > summary { font-variant-numeric: tabular-nums; white-space: nowrap; }
.cost-panel { font-size: 11.5px; color: var(--ink-dim); }
.costs[open] .cost-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 5;
  width: 250px;
  padding: 4px 12px 10px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 8px 20px rgba(20, 24, 40, 0.12);
}
.cost-rows { margin: 8px 0 0; border-collapse: collapse; width: 100%; }
.cost-rows th {
  text-align: left;
  font-weight: 400;
  padding: 3px 0;
  border-top: 1px solid var(--line);
  white-space: nowrap;
}
.cost-rows td {
  text-align: right;
  padding: 3px 0 3px 10px;
  border-top: 1px solid var(--line);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.cost-rows .cost-sum th, .cost-rows .cost-sum td { font-weight: 700; color: var(--ink); }
.cost-panel .hint { display: block; margin: 6px 0 0; }
.rev-n { font-weight: 700; color: var(--ink); }
.rev-label { flex: 1; }
.rev-ts { white-space: nowrap; }

.messages {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 14px 16px;
  display: grid;
  gap: 14px;
  align-content: start;
}
.msg { font-size: 14px; }
.msg-role {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 4px;
}
.msg-body { white-space: pre-wrap; word-wrap: break-word; }
.msg.user .msg-body {
  background: #f2f6fd;
  border: 1px solid #dbe4f8;
  border-radius: 10px;
  padding: 9px 11px;
}
.msg.assistant .msg-body { padding: 0 2px; }
.msg.system .msg-body {
  font-size: 13px;
  color: var(--warn);
  background: #fffaef;
  border: 1px solid #f0dcae;
  border-radius: 8px;
  padding: 8px 10px;
}
/* A rendered turn brings its own block structure, so pre-wrap has to go: kept on,
   the blank line between two Markdown paragraphs would show up as a third one. */
.msg-body.md { white-space: normal; }
.msg-body.md > :first-child { margin-top: 0; }
.msg-body.md > :last-child { margin-bottom: 0; }
.msg-body.md p { margin: 0 0 8px; }
.msg-body.md h3, .msg-body.md h4, .msg-body.md h5, .msg-body.md h6 {
  font-size: 13px;
  font-weight: 700;
  margin: 12px 0 5px;
  line-height: 1.35;
}
.msg-body.md ul, .msg-body.md ol { margin: 0 0 8px; padding-left: 20px; }
.msg-body.md li { margin: 0 0 3px; }
.msg-body.md li > ul, .msg-body.md li > ol { margin: 3px 0 0; }
.msg-body.md code {
  font-size: 12px;
  background: #f1f3f7;
  border-radius: 4px;
  padding: 1px 4px;
}
.msg-body.md pre {
  margin: 0 0 8px;
  padding: 8px 10px;
  background: #f6f7fa;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow-x: auto;
}
.msg-body.md pre code { font-size: 11.5px; background: none; padding: 0; }
.msg-body.md blockquote {
  margin: 0 0 8px;
  padding-left: 9px;
  border-left: 2px solid var(--line);
  color: var(--ink-dim);
}
.msg-body.md hr { border: 0; border-top: 1px solid var(--line); margin: 10px 0; }
.msg-body.md a { color: var(--accent); }

.msg-meta { font-size: 11.5px; color: var(--ink-dim); margin-top: 5px; }
/* The turn's accounting. Wraps rather than truncates — a long model slug at the
   end of it must not push the dollars out of view. */
.msg-cost {
  font-size: 11.5px;
  color: var(--ink-dim);
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}
.msg-note {
  font-size: 11.5px;
  color: var(--warn);
  margin-top: 4px;
  padding-left: 8px;
  border-left: 2px solid #f0dcae;
}
/* ------------------------------------------------------- turn progress */
/* What the turn in flight is doing, drawn where its reply will land. The strip
   is the whole request/response cycle, so the step the turn is on reads as a
   position in something finite rather than as an unbounded wait. */
.turn-progress { margin-top: 6px; display: grid; gap: 7px; }

.turn-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 0;
  font-size: 11px;
}
.step { display: flex; align-items: center; gap: 5px; color: var(--ink-dim); }
/* The connector between steps, drawn by the step it leads into so the first
   one has none. A chevron rather than a rule: the strip is a sequence. */
.step + .step::before { content: "›"; color: var(--line); padding-left: 6px; }
.step.done { color: var(--ok); }
.step.done .step-name::before { content: "✓ "; }
.step.now { color: var(--accent); font-weight: 700; }
.step.now .step-name::after {
  content: "";
  display: inline-block;
  width: 5px;
  height: 5px;
  margin-left: 5px;
  vertical-align: middle;
  border-radius: 50%;
  background: currentColor;
  animation: step-pulse 1.1s ease-in-out infinite;
}
.step.todo { opacity: 0.55; }
.step-ms { font-weight: 400; color: var(--ink-dim); font-variant-numeric: tabular-nums; }

@keyframes step-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.2; }
}

.turn-bar {
  height: 3px;
  border-radius: 2px;
  background: #e7eaf1;
  overflow: hidden;
}
.turn-bar i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 2px;
  background: var(--accent);
  /* Slower than the 500ms tick that sets it, so the fill glides rather than
     stepping once a beat. */
  transition: width 600ms linear;
}
/* No pace to fill against — the first turn on a page, or one whose history
   predates the timing. An indeterminate sweep says "running", and says nothing
   about how far along it is, which is all that is honestly known. */
.turn-bar.guess i {
  width: 34%;
  animation: bar-sweep 1.6s ease-in-out infinite;
  transition: none;
}
.turn-bar.over i { background: var(--warn); }

@keyframes bar-sweep {
  0% { margin-left: -34%; }
  100% { margin-left: 100%; }
}

.turn-note {
  font-size: 11.5px;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .step.now .step-name::after, .turn-bar.guess i { animation: none; }
}
.msg.error .msg-body {
  color: #8c1f39;
  background: #fdf3f5;
  border: 1px solid #f0c2cd;
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 13px;
}

/* ---------------------------------------------------------------- assets */

.assets { padding: 10px 16px; border-top: 1px solid var(--line); }
.assets.dropping { background: #f2f6fd; box-shadow: inset 0 0 0 2px var(--accent); }
.assets-head { display: flex; align-items: baseline; gap: 10px; }
.assets-title { font-size: 12px; font-weight: 700; color: var(--ink-dim); }
.assets-empty { margin: 6px 0 0; font-size: 11.5px; color: var(--ink-dim); line-height: 1.5; }
.assets-empty code { font-size: 11px; }

.asset-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: grid;
  gap: 4px;
  max-height: 168px;
  overflow: auto;
}
.asset { display: flex; align-items: center; gap: 8px; }
/* Chequerboard, so a transparent PNG reads as transparent rather than white. */
.asset-thumb {
  width: 30px;
  height: 30px;
  flex: none;
  object-fit: contain;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: #fff repeating-conic-gradient(#eef1f5 0% 25%, #fff 0% 50%) 0 0 / 10px 10px;
}
.asset-ref {
  flex: 1;
  min-width: 0;
  display: grid;
  text-align: left;
  border: 0;
  background: none;
  cursor: pointer;
  padding: 2px 4px;
  border-radius: 6px;
  font: inherit;
}
.asset-ref:hover { background: #f2f6fd; }
.asset-name {
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.asset-dim { font-size: 10.5px; color: var(--ink-dim); }
.asset-del { flex: none; font-size: 11px; }

/* The composer is the last thing in the column, so its rule divides it from the
   Assets strip above rather than from anything below. */
.composer { border-top: 1px solid var(--line); padding: 12px 16px; display: grid; gap: 8px; }
.composer-bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
#status { min-height: 16px; }

@media (max-width: 900px) {
  .dashboard { grid-template-columns: 1fr; }
  /* Narrow enough that the two panes stack and the page scrolls as one, so the
     viewport-height layout above is undone rather than adapted. */
  .editor { grid-template-columns: 1fr; flex: none; height: auto; }
  .editor-body { display: block; height: auto; overflow: auto; }
}
