/* ---------------------------------------------------------------------------
   Study instrument styling.

   Deliberately editorial rather than "product dashboard": a serif display face,
   hairline rules instead of floating cards, flat fills, square corners, and an
   8px spacing scale. The sheet this replaced leaned on the house style of
   generated UI -- Inter, 8px-rounded shadowed cards nested inside cards,
   gradient fills, pill badges, hover lifts -- which reads as template rather
   than instrument.

   Everything here is arm-invariant: no rule keys off condition, so the generic,
   mentor and peer arms stay pixel-identical apart from the words in the chat.
   --------------------------------------------------------------------------- */

:root {
  --umn-maroon: #7a0019;
  --umn-gold: #ffcc33;
  --umn-maroon-dark: #5d0013;
  --gold-soft: #fdf6e3;

  /* Warmer than the old neutral greys: paper, not dashboard. */
  --ink: #1d1b19;
  --ink-muted: #5d5853;
  /* Off-white, not #fff: a full-page white sheet glares, especially next to
     the warm code paper. */
  --surface: #fbfaf7;
  --surface-muted: #f1efe9;
  --paper: #e6e3db;
  --border: #d5d0c7;
  --border-strong: #a8a29a;
  --rule-hair: #e4e0d8;
  --focus-ring: rgba(122, 0, 25, 0.3);

  /* Legacy aliases so any rule or markup still naming these resolves. */
  --umn-charcoal: #1d1b19;
  --umn-gray: #5d5853;

  /* One measure for everything that spans the column on an item screen: the
     code paper, the answer boxes and the prose all end on the same vertical.
     It has to be a length, not `ch` -- `ch` resolves against each element's own
     font, so 72ch of mono and 68ch of sans end in two different places. 38rem
     is ~646px at the 17px root, inside the 45-75 character band for the text
     face and wider than the widest snippet in the bank. */
  --measure: 38rem;

  /* 4/8px scale. Every margin and padding below is drawn from it. */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 48px;

  /* No Inter: the display face is a transitional serif that ships with the OS,
     the text face is the native UI sans. Nothing is fetched over the network. */
  --font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Charter,
    Georgia, "Times New Roman", serif;
  --font-text: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;

  color-scheme: light;
  font-family: var(--font-text);
  font-size: 17px;
  background: var(--paper);
  color: var(--ink);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* The one piece of institutional livery, full bleed across the top. */
body::before {
  content: "";
  position: fixed;
  inset: 0 0 auto;
  height: 4px;
  background: linear-gradient(90deg, var(--umn-maroon) 0 78%, var(--umn-gold) 78% 100%);
  pointer-events: none;
  z-index: 1;
}

button,
input,
textarea {
  font: inherit;
}

.app-shell {
  width: min(900px, calc(100% - var(--s6)));
  margin: 0 auto;
  padding: var(--s7) 0;
}

h1,
h2,
h3,
p {
  margin-top: 0;
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--ink);
}

h1 {
  margin-bottom: 0;
  font-size: 1.9rem;
  line-height: 1.25;
}

h2 {
  margin-bottom: var(--s4);
  font-size: 1.4rem;
  line-height: 1.3;
}

h3 {
  margin-bottom: var(--s2);
  font-size: 1.1rem;
  line-height: 1.35;
}

/* A readable measure: 45-75 characters is the span the typographic literature
   settles on, and prose here is capped near the top of it. */
p {
  max-width: var(--measure);
  margin-bottom: var(--s4);
}

/* --------------------------------------------------------------- page frame
   A status line sitting above the sheet, not a second card wrapped round it. */

.status-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  padding: 0 0 var(--s3);
  border-bottom: 1px solid var(--border);
  color: var(--ink-muted);
  font-size: 0.82rem;
}

.session-pill {
  flex: 0 0 auto;
  max-width: 320px;
  overflow-wrap: anywhere;
  color: var(--umn-maroon);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

#message {
  color: var(--ink-muted);
  text-align: right;
}

#message:empty {
  display: none;
}

.work-area {
  margin-top: var(--s5);
  padding: var(--s6) var(--s6) var(--s6);
  border: 1px solid var(--border);
  border-top: 3px solid var(--umn-maroon);
  background: var(--surface);
}

.item-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-bottom: var(--s5);
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--rule-hair);
}

.tag {
  color: var(--ink-muted);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.question-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 400px);
  gap: var(--s6);
}

/* ----------------------------------------------------------------- surfaces
   Panels are bounded by a rule and a flat fill: no shadow, no radius, nothing
   floating above the page. */

.placeholder-panel,
.support-panel,
.response-panel {
  padding: var(--s5);
  border: 1px solid var(--border);
  background: var(--surface-muted);
}

.placeholder-panel > :last-child,
.response-panel > :last-child {
  margin-bottom: 0;
}

/* Code keeps its own paper and a maroon spine: the one place a colour accent
   earns its keep, marking "this is the program" at a glance. */
.code-block {
  min-height: 0;
  max-width: var(--measure);
  margin: 0 0 var(--s5);
  padding: var(--s4) var(--s5);
  overflow-x: auto;
  /* Never wrap: a wrapped continuation line reads as an indented line, and
     several items turn on whether a statement sits inside the loop. */
  white-space: pre;
  border: 1px solid var(--rule-hair);
  border-left: 3px solid var(--umn-maroon);
  background: var(--gold-soft);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.92rem;
  line-height: 1.6;
  tab-size: 4;
}

.support-panel {
  padding: var(--s4);
}

.support-panel p {
  color: var(--ink-muted);
}

/* The one affordance label, identical in every arm (2026-09-30). Sits above
   the chat window and reads as instruction, not as a tutor utterance, so it
   is deliberately not styled like a chat bubble. */
.chat-panel-label {
  max-width: none;
  margin: var(--s1) 0 0;
  font-size: 0.86rem;
  line-height: 1.45;
}

/* --------------------------------------------------------------------- chat
   A messaging thread: rounded bubbles, tutor left and participant right,
   speaker named once per run of consecutive turns rather than on every line.
   Arm-invariant -- no rule below keys off condition, so the generic, mentor
   and peer arms stay pixel-identical apart from the words in the bubbles. */

.chat-window {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  height: 420px;
  margin-top: var(--s3);
  padding: var(--s3);
  overflow-y: auto;
  border: 1px solid var(--border);
  background: var(--surface);
}

.chat-message {
  display: flex;
}

.chat-message.user {
  justify-content: flex-end;
}

/* A new speaker starts a new group: air above the turn that changes hands,
   none between a speaker's own consecutive bubbles. */
.chat-message + .chat-message {
  margin-top: var(--s1);
}

.chat-message.user + .chat-message.tutor,
.chat-message.tutor + .chat-message.user {
  margin-top: var(--s3);
}

.chat-bubble {
  max-width: 82%;
  padding: var(--s2) var(--s3);
  border-radius: 16px;
  background: var(--surface-muted);
  font-size: 0.88rem;
  line-height: 1.45;
}

.chat-message.tutor .chat-bubble {
  border-bottom-left-radius: 5px;
  background: var(--gold-soft);
}

.chat-message.thinking .chat-bubble {
  color: var(--ink-muted);
  background: var(--surface-muted);
}

.animated-dots::after,
.chat-message.thinking .chat-text::after {
  content: "";
  display: inline-block;
  width: 1.2em;
  animation: thinkingDots 1.1s steps(4, end) infinite;
}

.chat-message.user .chat-bubble {
  border-bottom-left-radius: 16px;
  border-bottom-right-radius: 5px;
  color: var(--surface);
  background: var(--umn-maroon);
}

.chat-speaker {
  display: block;
  margin-bottom: 2px;
  color: var(--ink-muted);
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

/* Texting convention: the name is dropped inside a run of turns from the same
   speaker. Clipped rather than display:none so the live region still reads
   who is talking. */
.chat-message.tutor + .chat-message.tutor .chat-speaker,
.chat-message.user + .chat-message.user .chat-speaker {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.chat-message.user .chat-speaker,
.chat-message.user p {
  color: rgba(255, 255, 255, 0.92);
}

.chat-message.user .chat-speaker {
  text-align: right;
}

.chat-bubble p {
  max-width: none;
  margin-bottom: 0;
  overflow-wrap: anywhere;
  color: inherit;
}

/* Render newlines so burst-style multi-line replies read as separate lines.
   It belongs on the text node alone: on .chat-bubble it also rendered the
   markup's own indentation, padding every bubble with blank lines. */
.chat-text {
  white-space: pre-wrap;
}

.chat-composer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s2);
  align-items: end;
  margin-top: var(--s3);
}

/* Both classes are on the composer, and .textarea's tall min-height is
   declared later: name both so the composer stays two lines high. */
.textarea.chat-input {
  min-height: 42px;
  max-height: 92px;
  padding: var(--s2) var(--s3);
  border-radius: 18px;
  font-size: 0.88rem;
}

.chat-composer .button {
  padding: var(--s2) var(--s4);
  border-radius: 18px;
  font-size: 0.88rem;
}

.wait-block {
  display: flex;
  gap: 2px;
  align-items: baseline;
  padding: var(--s3) var(--s4);
  border-left: 3px solid var(--umn-gold);
  background: var(--surface-muted);
  color: var(--ink-muted);
  font-size: 0.92rem;
}

#practice-feedback .wait-block {
  margin-top: var(--s3);
}

@media (prefers-reduced-motion: reduce) {
  .animated-dots::after,
  .chat-message.thinking .chat-text::after {
    content: "...";
    animation: none;
  }
}

@keyframes thinkingDots {
  0% {
    content: "";
  }
  25% {
    content: ".";
  }
  50% {
    content: "..";
  }
  75%,
  100% {
    content: "...";
  }
}

.response-panel {
  margin-top: var(--s4);
  border-left: 3px solid var(--umn-gold);
  background: var(--surface);
}

.debug-panel {
  margin: var(--s3) 0;
  padding: var(--s3);
  border: 1px dashed var(--border-strong);
  background: var(--surface);
  font-size: 0.86rem;
}

.debug-panel p {
  margin-bottom: var(--s2);
}

.debug-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

.debug-link {
  display: inline-flex;
  min-height: 32px;
  align-items: center;
  padding: var(--s1) var(--s2);
  border: 1px solid var(--umn-maroon);
  color: var(--umn-maroon);
  text-decoration: none;
  background: var(--surface);
}

.debug-link.active {
  color: var(--surface);
  background: var(--umn-maroon);
}

/* ------------------------------------------------------------------ options
   A ruled list of answers. Selection is carried by a solid maroon spine as
   well as the tint, so it survives greyscale and does not rest on colour. */

.option-list {
  display: grid;
  gap: var(--s2);
  max-width: var(--measure);
  margin: var(--s4) 0 var(--s5);
}

/* Each answer is a box of its own, spanning the full measure of the list. All
   four are identical in width and border so no answer is visually privileged;
   the only thing that distinguishes one is being chosen. The 3px channel on the
   left is on every row whether or not it is picked, so the rows stay aligned --
   choosing fills it with maroon and carries the border round with it. */
.option {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr);
  gap: var(--s3);
  align-items: baseline;
  padding: var(--s2) var(--s4);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: 2px;
  cursor: pointer;
  background: var(--surface);
  transition:
    background 120ms ease,
    border-color 120ms ease;
}

.option input {
  accent-color: var(--umn-maroon);
  transform: translateY(1px);
}

.option:hover {
  border-color: var(--border-strong);
  background: var(--surface-muted);
}

.option:has(input:checked) {
  border-color: var(--umn-maroon);
  background: var(--gold-soft);
}

.option span {
  overflow-wrap: anywhere;
}

/* Program output sits on the same baseline as the "A." label beside it, and
   carries a rule so it still reads as a value on a tinted panel. */
.option-output {
  display: inline-block;
  vertical-align: baseline;
  white-space: pre;
  font-family: var(--font-mono);
  font-size: 0.9em;
  line-height: 1.4;
  padding: 1px 6px;
  border: 1px solid var(--rule-hair);
  background: var(--surface-muted);
}

/* Output of several lines stays a chip beside its letter, exactly like a
   one-liner: same padding, same border, same place on the row. Only its height
   differs, and that difference is the answer itself. Giving it its own block
   under the label made one answer in the set look like a different kind of
   thing, and a row that stands out for a reason that is not its content is a
   cue the item did not intend (Tourangeau, Couper & Conrad 2004). `top`, not
   `baseline`: an inline-block aligns its LAST line to the row's baseline, which
   would hang the chip above the letter. */
.option-output.multiline {
  vertical-align: top;
  /* Hugs the output instead of ruling a near-empty band across the row. */
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
}

.inline-code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  line-height: 1.4;
  padding: 1px 4px;
  border: 1px solid var(--rule-hair);
  background: var(--surface-muted);
}

/* ---------------------------------------------------------------- controls */

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  align-items: center;
  margin-top: var(--s5);
}

.button {
  min-height: 42px;
  padding: var(--s2) var(--s5);
  border: 1px solid var(--umn-maroon);
  border-radius: 2px;
  color: var(--surface);
  background: var(--umn-maroon);
  cursor: pointer;
  font-size: 0.95rem;
  font-weight: 600;
  transition:
    background 120ms ease,
    border-color 120ms ease;
}

.button.secondary {
  color: var(--umn-maroon);
  background: var(--surface);
}

.button:not(:disabled):hover {
  border-color: var(--umn-maroon-dark);
  background: var(--umn-maroon-dark);
  color: var(--surface);
}

.button:disabled {
  cursor: not-allowed;
  color: var(--ink-muted);
  border-color: var(--border);
  background: var(--surface-muted);
}

.error {
  padding: var(--s3) var(--s4);
  border: 1px solid var(--umn-maroon);
  border-left: 3px solid var(--umn-maroon);
  color: var(--umn-maroon);
  background: var(--surface);
}

.textarea {
  width: 100%;
  min-height: 150px;
  padding: var(--s3);
  border: 1px solid var(--border-strong);
  border-radius: 2px;
  resize: vertical;
  background: var(--surface);
  color: var(--ink);
  line-height: 1.6;
}

.button:focus-visible,
.debug-link:focus-visible,
.textarea:focus {
  outline: 2px solid var(--umn-maroon);
  outline-offset: 2px;
}

/* Keyboard focus only. :focus-within also fired on a mouse click, which drew a
   hard box round the answer the moment it was picked. */
.option:has(input:focus-visible) {
  outline: 2px solid var(--umn-maroon);
  outline-offset: 2px;
}

.chat-budget {
  margin: var(--s2) 0 0;
  color: var(--ink-muted);
  font-size: 0.8rem;
}

.paste-notice {
  margin: var(--s2) 0 0;
  padding: var(--s2) var(--s3);
  border-left: 3px solid var(--umn-maroon);
  background: var(--surface-muted);
  font-size: 0.82rem;
}

@media (max-width: 760px) {
  :root {
    font-size: 16px;
  }

  .app-shell {
    width: calc(100% - var(--s4));
    padding: var(--s5) 0 var(--s6);
  }

  .work-area {
    padding: var(--s5) var(--s4) var(--s6);
  }

  .status-row {
    align-items: flex-start;
    flex-direction: column;
    gap: var(--s1);
  }

  .session-pill {
    max-width: 100%;
  }

  #message {
    text-align: left;
  }

  .question-grid {
    grid-template-columns: 1fr;
    gap: var(--s5);
  }
}

/* ------------------------------------------------- transfer completion slots
   TRANSFER-02 (design doc s.11) puts a <select> inside the program, one per
   blank. It has to read as part of the code, not as a form control dropped on
   top of it: the monospace face, size and baseline all come from the <pre>, so
   the line it sits in keeps its spacing. */

.code-block .slot-select {
  display: inline;
  padding: 0 0.25rem;
  border: 1px solid var(--border-strong);
  border-radius: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  vertical-align: baseline;
  min-width: 3ch;
}

.code-block .slot-select:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}

/* Which blanks are done, and nothing more: the cue is "filled", never
   "right". Set on change by renderTransferTestlet. */
.code-block .slot-select.slot-filled {
  border-color: var(--umn-maroon);
}

/* G and H sit below the program and reuse the answer-list rules, so an output
   question looks like every other multiple choice in the study. */
.transfer-question {
  margin-top: 1.5rem;
}
