/* ============================================================================
   NOTEBOOK TUTOR — the notes tutor sheet, in the margin of a topic leaf.

   WHAT THIS PAINTS
   One component: the tutor margin on the notebook notes twin
   (noteviewer/templates/noteviewer/nb_resource_detail.html) and the panel
   partial it holds (pages/templates/pages/includes/chat_panel.html). It paints
   nothing else and must never be asked to.

   THE DESIGN, WHICH IS A SEPARATE OBJECT
   The tutor is a physical sheet of cream paper lying on the desk, bound at its
   left edge, beside the note and never inside it. The page and the sheet share
   a screen but NOT a language. The note speaks in the site's voice: blue ink
   for links and actions, serif italic for margin notes, cards, dotted paper.
   The sheet speaks only in ink, pencil and one drop of sepia.

   EVERY WORD ON THE PAPER IS INK (owner, 2026-09-24: "make all the text
   BLACK color"). The intro, Try asking and its questions, the hint, the
   placeholder, the answers, the sources and every meta line read in
   --nb-tutor-ink (13.74:1 on the paper). The pencil is left to one mark
   that is not a word: the pen line that draws while a turn is in flight.
   Hierarchy is carried by weight and size, never by a lighter ink, and no
   small line sits at 400, where a 13px stroke antialiases to grey (§5c).

   THE FOUR ABSENCES, WHICH ARE THE DESIGN
     · ZERO blue      · ZERO red      · ZERO italic      · ZERO serif
   These are not a side effect. They are the thing most likely to erode by
   accident, because every generic selector in the notebook layers underneath
   this one supplies exactly what the sheet must refuse. The reset at §1 is the
   guard, and it is deliberately blunt. If you add a rule here, hold the four
   absences by hand: nothing in the build will catch you.

   THE SEPIA PAINTS EXACTLY FOUR THINGS
     1 the Premium chip
     2 the THIS TOPIC track fill and the dot riding its tip
     3 the nib beside the input
     4 the diamond on the folded tab (or the phone pill) when an answer waits
   A fifth use is a defect, not an extension. Each site below is marked. The
   pen line that shows while a turn is in flight is PENCIL, not sepia.

   THE DOM CONTRACT
     aside.nb-tutor-margin     the grid cell. Its grid-column is notebook-page
                               .css's business (§12), not this file's.
       div.nb-chat             the sheet: paper, ruling, border, shadow. This
                               class is frozen: noteviewer/tests_chat_panel.py
                               and pages/tests_engine_flag.py both assert on the
                               literal 'class="nb-chat"'.
         .nb-tutor-bind        the punched edge. A repeating paint, not
                               elements, so the pitch is fixed at every height.
         .nb-tutor-mrule       the margin rule beside the holes
         .nb-tutor-body        the ONLY thing that scrolls
   The five ids chat-poll.js resolves as literals (chat-scope, chat-message,
   chat-send, chat-status, chat-log) live in the partial, not here. The sheet is
   position: fixed in ONE place only, the phone bottom sheet (§15b), and that
   is safe because the aside is a grid SIBLING of the note column, outside its
   transform: see nb_resource_detail.html for the containing-block trap that
   costs 468px when anything INSIDE the column is made fixed.

   THE PEN LINE IS LIT BY THE OBSERVER
   .nb-tutor-write shows only while a turn is in flight. chat-poll.js varies no
   class or attribute with its status, so tutor-sheet.js sets data-phase on the
   line from its Send observer ("writing" out, "" back) and §9 reads that.

   SHEETS THIS ASSUMES UNDERNEATH IT
   notebook-tokens.css (the --nb-tutor-* palette, the type scale, spacing,
   radii, motion) and notebook-page.css (the grid that gives the aside its
   track). It does NOT assume notebook-components.css: the sheet borrows no
   component, precisely because borrowing one is how the blue gets in.

   RATCHETS THIS FILE IS IN, ALL DELIBERATELY
     · the font census, pages/tests_font_foundation.py:642 — every font-size
       here is a var() token, never a literal
     · ZERO_RAW_COLOR_FILES, pages/tests_notebook.py:3000 — zero hex, zero
       rgba(). Every tone is color-mix() over a --nb-tutor-* token
     · the nb- class prefix
     · the identity-token ban: this file names no per-resource, per-programme
       or per-board identity token, not even in a comment
   ============================================================================ */

/* ── §1 · THE FOUR ABSENCES, ENFORCED ───────────────────────────────────────
   The leaf loads notebook-notes.css to paint the prose, and that sheet plus the
   component layer carry serif italic and blue on generic selectors. Family and
   style only, so weight and size stay local to each rule below. Maths is
   notation, not the sheet's voice: KaTeX keeps its own faces (owner,
   2026-10-05), so "lim", digits and variables read as one formula. ── */
.nb-chat,
.nb-chat *:not(.katex):not(.katex *) {
  font-family: var(--nb-sans);
  font-style: normal;
}
/* Anchors with no class of their own only. At (0,2,1) a blanket `.nb-chat a`
   outranks every .nb-tutor-* class, so any colour set on it would silently
   repaint every classed link on the sheet (the chip, Get Premium's pill, Log
   in, Practise) over its own rule. */
.nb-chat a:not([class]) { color: inherit; }

/* ── §2 · THE MARGIN, and the sheet inside it ──────────────────────────────
   The aside is sticky at the rail's own offset. The sheet does not scroll; the
   BODY does, so the binding and the margin rule hold still while a long
   conversation moves past them. ── */
.nb-tutor-margin {
  position: sticky;
  top: calc(var(--nb-chrome-h) + var(--nb-s6));
  max-height: calc(100vh - var(--nb-chrome-h) - var(--nb-s7));
  display: flex;
  min-width: 0;
}

.nb-chat {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  background-color: var(--nb-tutor-paper);
  /* The ruling. One hairline every 28px at ink 9%, the reference's own pitch.
     It is texture, not a baseline grid: no line-height on this sheet is 28px
     and nothing is meant to sit on it. */
  background-image: repeating-linear-gradient(
    to bottom,
    transparent 0 27px,
    color-mix(in srgb, var(--nb-tutor-ink) 9%, transparent) 27px 28px
  );
  border: 1px solid var(--nb-tutor-edge);
  /* 12px on the three unbound corners; the bound top-left corner stays square */
  border-radius: 0 var(--nb-r-lg) var(--nb-r-lg) var(--nb-r-lg);
  box-shadow: var(--nb-tutor-shadow);
  color: var(--nb-tutor-ink);
}

/* The punched edge. A repeating paint at a FIXED 22px pitch rather than ring
   elements, so the pitch never becomes a function of the sheet's height.

   ⚑ THE OLD COMMENT HERE CLAIMED "a final partial tile can only ever cut empty
   space, never a ring" AND THAT WAS FALSE. The ring is drawn at `circle at
   17px 18px` with radii 2.5px and 4px, so within a 22px tile it occupies y=14
   to y=22. A partial tile of height h shows y=0..h: for h < 14 no ring at all,
   which is the case the comment was describing, but for 14 <= h < 22 it shows
   a SLICED ring. The sheet's height is content-driven, so h lands in that
   8px-wide window about a third of the time. Captured at 1440 on the unenrolled
   state: the bottom ring is cut clean in half. That is the sheet ending as a
   crop rather than as paper.

   The fix is to paint a WHOLE NUMBER OF TILES and nothing else. round() is
   CSS Values 4 (Chrome 125+, Safari 15.4+, Firefox 118+); where it is not
   supported the declaration is dropped and top/bottom below still apply, which
   is exactly today's behaviour — so this degrades to the old bug rather than to
   no binding at all. top: 8px puts the first ring 22px inside the paper;
   subtracting 30px (8 top + 22 bottom) leaves the run the rings may use. */
.nb-tutor-bind {
  position: absolute;
  left: 0;
  top: 8px;
  bottom: 22px;
  height: round(down, calc(100% - 30px), 22px);
  width: 34px;
  padding: 0;
  background-image: radial-gradient(
    circle at 17px 18px,
    transparent 0 2.5px,
    color-mix(in srgb, var(--nb-tutor-ink) 42%, transparent) 2.5px 4px,
    transparent 4px
  );
  background-size: 100% 22px;
  background-repeat: repeat-y;
  background-origin: content-box;
  background-clip: content-box;
}
.nb-tutor-mrule {
  position: absolute;
  left: 42px;
  top: 0;
  bottom: 0;
  width: 1px;
  background: color-mix(in srgb, var(--nb-tutor-ink) 20%, transparent);
}

.nb-tutor-body {
  flex: 1 1 auto;
  min-width: 0;
  overflow-y: auto;
  /* scrolling the tutor never scrolls the note behind it (owner 2026-09-30) */
  overscroll-behavior: contain;
  padding: 38px 30px 34px 62px;
  /* ⚑ NO SCROLLBAR (owner, 2026-09-24: "why is there a scrollbar here, NO
     SCROLLBAR"). The body still scrolls by wheel, touch and keys, and the
     Latest button jumps to the end of a long conversation. */
  scrollbar-width: none;
}
.nb-tutor-body::-webkit-scrollbar { display: none; width: 0; }

/* ── §3 · Header. The nib, YOUR TUTOR in ink small caps, the Premium chip ──
   SEPIA USE 1 OF 4 is the chip, and it is a SOLID sepia block with cream
   lettering, not a tint with sepia text. ── */
.nb-tutor-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--nb-s3);
  margin-bottom: 32px;
}
.nb-tutor-kick {
  display: flex;
  align-items: center;
  gap: var(--nb-s2);
  margin: 0;
  font-size: var(--nb-text-xs);
  font-weight: 600;
  line-height: normal;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--nb-tutor-ink);
}
.nb-tutor-kick svg { flex: none; color: var(--nb-tutor-ink); }
.nb-tutor-chip {
  flex: none;
  padding: 5px 10px 4px;
  background: var(--nb-tutor-accent);
  border-radius: var(--nb-r-sm);
  color: var(--nb-tutor-chip-ink);
  font-size: var(--nb-text-xs);
  font-weight: 700;
  line-height: normal;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
}
a.nb-tutor-chip:focus-visible { outline: 2px solid var(--nb-tutor-ink); outline-offset: 2px; }
/* The sheet's opening line, asking, pulled up under the header: the head
   keeps its 32px below, so this sits at -22px and leaves 10px to the writing
   line. tutor-sheet.js names the note in it once ("What would you like to
   know about 1.1 Introducing calculus?"), so no question repeats it. */
.nb-tutor-sub {
  margin: -22px 0 0;
  font-size: var(--nb-text-sm);
  line-height: 1.5;
  color: var(--nb-tutor-ink);
  text-wrap: balance;
}
.nb-tutor-note { font-weight: 600; }

/* Kickers, all in ink. The reference sets
   these at 10px and 9.5px; the smallest token on the notebook scale is
   --nb-text-xs, and a literal would fail the font census, so they sit there. */
.nb-tutor-kick2 {
  margin: 0 0 var(--nb-s3);
  font-size: var(--nb-text-xs);
  font-weight: 600;
  line-height: normal;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--nb-tutor-ink);
}
.nb-tutor-nk { color: var(--nb-tutor-ink); margin-bottom: var(--nb-s1); }

/* ── §4 · This topic. A 2px track in the ink at 14%, inked in sepia to the
   figure, with a dot riding the tip of the fill. SEPIA USE 2 OF 4: the fill and
   its dot are one use. The sentence carries no emphasis inside it.
   ⚑ This block renders ONLY when the view supplies `this_topic`. There is no
   empty state by design: a student who has answered questions must never be
   told they have not. ── */
.nb-tutor-track {
  height: 2px;
  margin-bottom: 14px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--nb-tutor-ink) 14%, transparent);
}
.nb-tutor-fill {
  position: relative;
  height: 2px;
  border-radius: 2px;
  background: var(--nb-tutor-accent);
}
.nb-tutor-fill::after {
  content: "";
  position: absolute;
  right: -3px;
  top: -2.5px;
  width: 7px;
  height: 7px;
  border-radius: var(--nb-r-pill);
  background: var(--nb-tutor-accent);
}
.nb-tutor-sentence {
  margin: 0;
  font-size: var(--nb-text-sm);
  font-weight: 500;
  line-height: 1.6;
  color: var(--nb-tutor-ink);
  text-wrap: pretty;
}

/* ── §5 · The writing line. Nib, field, Send, and ONE rule under the whole row:
   the heaviest rule on the sheet. SEPIA USE 3 OF 4 is the nib.
   ⚑ SEND IS PRESENT AND ENABLED IN EVERY STATE, free included. Owner ruling of
   2026-09-13, recorded in full in dashboard.html: the gate is at the wire,
   where POST /engine/chat/ refuses an unentitled account outright, and
   `disabled` would be the same mistake in a new coat. Nothing here may add it
   and nothing here may hide the row. ── */
/* THE COMPOSER IS A RAISED CARD ON THE SHEET, at the foot of the body (§13
   orders it last). Its field takes the whole first row; the nib, the keys
   hint, the counter and Send share the second. The first shadow is a band of
   paper above the card, so a conversation scrolling under a sticky composer
   fades rather than cuts. */
.nb-tutor-input {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--nb-s2) var(--nb-s3);
  margin: 22px 0 0;
  padding: 12px 12px 10px 14px;
  background-color: var(--nb-tutor-paper);
  border: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 22%, transparent);
  border-radius: var(--nb-r-lg);
  box-shadow:
    0 -14px 12px 2px var(--nb-tutor-paper),
    0 6px 16px color-mix(in srgb, var(--nb-tutor-ink) 10%, transparent);
  transition: border-color var(--nb-dur-1) var(--nb-ease);
}
.nb-tutor-input:focus-within { border-color: color-mix(in srgb, var(--nb-tutor-ink) 55%, transparent); }
.nb-tutor-nib { flex: none; order: 2; color: var(--nb-tutor-accent); }
/* The field's label is for a screen reader only: the placeholder carries the
   instruction visually, and a visible label would put a second voice on a row
   that is already nib, field and Send. */
.nb-tutor-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.nb-tutor-field {
  flex: 1 0 100%;
  order: 1;
  min-width: 0;
  min-height: 24px;
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  color: var(--nb-tutor-ink);
  font-family: inherit;
  font-size: var(--nb-text-base);
  line-height: 1.5;
  resize: none;
  overflow-y: auto;
  max-height: 144px;
}
/* The reference sets the placeholder at a 45% ink wash, which measures 2.68:1
   and fails AA. A placeholder is instructional text and not decoration, so it
   took the pencil (5.44:1), and since 2026-09-24 the ink, like every word
   on the paper (13.74:1). */
.nb-tutor-field::placeholder { color: var(--nb-tutor-ink); opacity: 1; }
/* The card's border is the field's focus: it darkens while the field is
   being written in (see .nb-tutor-input:focus-within). */
.nb-tutor-field:focus { outline: none; }
.nb-tutor-send {
  flex: none;
  order: 5;
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  box-sizing: border-box;
  margin: 0 0 2px;
  padding: 0 14px;
  background: var(--nb-tutor-ink);
  border: 1px solid var(--nb-tutor-ink);
  border-radius: var(--nb-r-pill);
  color: var(--nb-tutor-paper);
  font-family: inherit;
  font-size: var(--nb-text-sm);
  font-weight: 600;
  line-height: normal;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: background var(--nb-dur-1) var(--nb-ease), border-color var(--nb-dur-1) var(--nb-ease), transform var(--nb-dur-2) var(--nb-ease);
}
/* ⚑ EVERY HOVER ON THE SHEET WAITS FOR A POINTER THAT HOVERS (audit N-020,
   2026-09-25). Mobile Chromium and Safari keep :hover on the last element a
   finger tapped, so after a tap Send stayed grey and lifted, reading as
   disabled, and a tapped starter kept its wash. A desk with a mouse matches
   (hover: hover) and sees exactly what it did; a tap still shows the press
   (:active) and a keyboard the ring (:focus-visible). The same guard stands on
   each hover below. */
@media (hover: hover) {
  .nb-tutor-send:hover:not(:disabled) { background: color-mix(in srgb, var(--nb-tutor-ink) 84%, var(--nb-tutor-paper)); border-color: transparent; transform: translateY(-1px); }
}
.nb-tutor-send:active:not(:disabled) { transform: translateY(0) scale(0.97); transition-duration: var(--nb-dur-1); }
.nb-tutor-send:disabled { background: color-mix(in srgb, var(--nb-tutor-ink) 22%, var(--nb-tutor-paper)); border-color: transparent; color: var(--nb-tutor-paper); cursor: not-allowed; }
.nb-tutor-send:focus-visible {
  outline: 2px solid var(--nb-tutor-accent);
  outline-offset: 3px;
  border-radius: var(--nb-r-sm);
}
.nb-tutor-status {
  display: block;
  /* 12px under the writing line's rule: the row used to carry 26px below and
     this line pulled up 14px; the row now carries none, so the 12px is here. */
  margin: 12px 0 0;
  min-width: 0;
  font-size: var(--nb-text-xs);
  line-height: 1.5;
  color: var(--nb-tutor-ink);
  overflow-wrap: anywhere;
}
.nb-tutor-status:empty { display: none; }

/* ── §5b · Try asking. Three questions in ink under the writing line for a
   sheet with nothing on it yet. Each is a real button (tutor-sheet.js fills
   the field on click and never sends), drawn as a quiet line with a small
   chevron so it reads as a suggestion rather than a control. They stand only
   while the transcript is empty and never while a turn is in flight. ── */
.nb-tutor-starters {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  margin: 22px 0 0;
}
.nb-tutor-starters .nb-tutor-kick2 { margin-bottom: var(--nb-s1); color: var(--nb-tutor-ink); }
.nb-tutor-starter {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--nb-s2);
  min-height: 32px;
  margin: 0 0 0 -6px;
  padding: 0 8px 0 6px;
  background: none;
  border: 0;
  border-radius: var(--nb-r-sm);
  color: var(--nb-tutor-ink);
  font-size: var(--nb-text-sm);
  line-height: 1.4;
  text-align: left;
  cursor: pointer;
  transition: color var(--nb-dur-1) var(--nb-ease), background var(--nb-dur-1) var(--nb-ease);
}
.nb-tutor-starter::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  opacity: 0.6;
  transition: transform var(--nb-dur-2) var(--nb-ease), opacity var(--nb-dur-1) var(--nb-ease);
}
@media (hover: hover) {
  .nb-tutor-starter:hover { color: var(--nb-tutor-ink); text-decoration: underline; text-underline-offset: 3px; }
  .nb-tutor-starter:hover::before { opacity: 1; transform: rotate(-45deg) translate(1px, 1px); }
}
.nb-tutor-starter:focus-visible { outline: 2px solid var(--nb-tutor-accent); outline-offset: 2px; }
/* Gone once there is a conversation, and gone while one is arriving. */
.nb-tutor-log:not(:empty) ~ .nb-tutor-starters,
.nb-chat.is-inflight .nb-tutor-starters { display: none; }

/* ── §5c · WEIGHT CARRIES THE INK (owner, 2026-09-24, of the locked sheet:
   "make those types of text like the 'Your tutor is part of Premium' same
   color"). The colour was already identical, every word in --nb-tutor-ink;
   what read lighter was the WEIGHT. At 13px a 400 stroke is thinner than a
   pixel and antialiases to grey on the cream. Measured on the rendered sheet
   (mean stroke cover, 1x / 2x): the bold line (15px 600) 0.71 / 0.81; a
   starter at 400 0.53 / 0.70, at 500 0.60 / 0.74, at 600 0.62 / 0.77. So
   the questions take 600, the nearest match at their size: the locked
   examples, the working starters and the follow-ups, which share the class.
   The small lines take 500 so they read as ink too (400 measured 0.54, the
   starters' old grey): the intro, the lock card's second line and its Log
   in, the key hint, the counter, Latest, the status, the panel's actions,
   and a turn's note and source rows. Stop keeps its 600 (§13b outranks
   .nb-tutor-act). The answer itself stays 400: at 15px it reads as ink, and
   a paragraph set at 500 is heavier than a sheet of notes should be.
   ⚑ ONE RULE PER WEIGHT, so the font census pays two entries, not twelve.
   ⚑ EXAMPLE BEFORE STARTER in the first list, and a turn's source rows
   LAST in the second: noteviewer/tests_tutor_sheet.py finds each class's
   own rule by "\n.<class> {", and a list ending in one of the classes it
   reads (the starter's rule is above, so it is safe) would be found first. ── */
.nb-tutor-example,
.nb-tutor-starter { font-weight: 600; }
.nb-tutor-sub,
.nb-tutor-lock-sub,
.nb-tutor-quiet-way,
.nb-tutor-hint,
.nb-tutor-count,
.nb-tutor-jump,
.nb-tutor-status,
.nb-tutor-act,
.nb-tutor-log .chat-note,
.nb-tutor-log .chat-cite { font-weight: 500; }
/* The follow-ups under an answer: one quiet group, lighter than the
   question above them (owner, 2026-10-05). */
.nb-tutor-followups .nb-tutor-starter { font-weight: 500; }

/* ── §6 · The conversation. Question in ink at 600, the answer in ink at 400.
   chat-poll.js appends into .nb-tutor-log with textContent only. ── */
.nb-tutor-log { min-width: 0; }
.nb-tutor-log:not(:empty) { margin-top: 22px; }
.nb-tutor-turn + .nb-tutor-turn { margin-top: 26px; }
.nb-tutor-q {
  margin: 0 0 13px;
  font-size: var(--nb-text-base);
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: -0.005em;
  color: var(--nb-tutor-ink);
  text-wrap: pretty;
}
.nb-tutor-a {
  margin: 0;
  font-size: var(--nb-text-sm);
  line-height: 1.68;
  color: var(--nb-tutor-ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  text-wrap: pretty;
}

/* ── §6b · THE NODES chat-poll.js BUILDS, a FOREIGN FROZEN CONTRACT.
   Every class below is owned by pages/static/pages/js/chat-poll.js, which is a
   frozen-contract file this phase may not edit, so they are restyled here and
   never renamed — the same arrangement notebook-page.css has with the classes
   note-reading.js and the sidebar own. They are the only non-nb- classes in
   this sheet and they are here because the alternative is an unstyled
   transcript: section 17 of notebook-notes.css used to paint them and this
   file replaces it.

   Redrawn in the sheet's two tones. The originals reached for --nb-ink,
   --nb-ink-3, --nb-border and --nb-border-strong, and the last of those is the
   ink the sheet must not have. ── */
/* Turns are told apart by space, not rules: 32px between them, 12px from a
   question to its answer (owner, 2026-10-05: "more organised"). */
.nb-tutor-log .chat-turn { min-width: 0; }
.nb-tutor-log .chat-turn + .chat-turn { margin-top: var(--nb-s7); }
/* The student's question, only their words (the note is named once, in the
   opening line): a highlighted line on the answer's own edge, the shade
   hanging into the gutter like a marker stroke. A shade on this sheet only
   ever means "you asked this". */
.nb-tutor-log .chat-turn-q {
  width: fit-content;
  max-width: calc(100% + var(--nb-s3));
  margin: 0 0 var(--nb-s3) calc(-1 * var(--nb-s3));
  padding: 6px var(--nb-s3);
  border-radius: var(--nb-r-md);
  background: color-mix(in srgb, var(--nb-tutor-ink) 10%, var(--nb-tutor-paper));
  font-size: var(--nb-text-base);
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: -0.005em;
  color: var(--nb-tutor-ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.nb-tutor-log .chat-prose {
  min-width: 0;
  margin: 0;
  font-size: var(--nb-text-base);
  line-height: 1.68;
  color: var(--nb-tutor-ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.nb-tutor-log .chat-prose--empty { white-space: normal; }
/* Waiting. The sheet's real indicator is inert until Phase B, so this is the
   only thing a student sees between Send and the first sentence: one ink
   line and a still bar. No animation, because the keyframes it used to borrow
   belonged to the section this file replaces. */
.nb-tutor-log .chat-pending {
  display: flex;
  align-items: center;
  gap: var(--nb-s3);
  margin: 0;
  font-size: var(--nb-text-sm);
  line-height: 1.5;
  color: var(--nb-tutor-ink);
}
.nb-tutor-log .chat-pending-bar {
  display: block;
  flex: 1 1 auto;
  max-width: 160px;
  height: 3px;
  border-radius: var(--nb-r-pill);
  background: color-mix(in srgb, var(--nb-tutor-ink) 20%, transparent);
}
/* The citations the engine returns. Inert for the same reason the fixture rows
   are: the payload carries no slug and no spec key. */
.nb-tutor-log .chat-cites-label {
  margin: var(--nb-s5) 0 var(--nb-s1);
  font-size: var(--nb-text-xs);
  font-weight: 600;
  line-height: normal;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--nb-tutor-ink);
}
.nb-tutor-log .chat-cites { display: block; }
.nb-tutor-log .chat-cite {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: var(--nb-s3);
  font-size: var(--nb-text-sm);
  line-height: normal;
  color: var(--nb-tutor-ink);
  cursor: default;
}
/* A line the script adds beside a reply: what to do next, or why a turn
   stopped. Never engine text. */
.nb-tutor-log .chat-note {
  margin: var(--nb-s3) 0 0;
  padding-left: var(--nb-s3);
  border-left: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 20%, transparent);
  font-size: var(--nb-text-sm);
  line-height: 1.5;
  color: var(--nb-tutor-ink);
  overflow-wrap: anywhere;
}
/* A turn that stopped: its note is ruled heavier in ink (never red), and
   carries a Try again that puts the question back in the field. */
.nb-tutor-log .chat-note--stopped {
  border-left: 3px solid color-mix(in srgb, var(--nb-tutor-ink) 55%, transparent);
  color: var(--nb-tutor-ink);
}
/* The quiz a reply carries (owner, 2026-10-05: "when i type quiz me ... the
   btn has no css"). chat-poll.js draws Take the quiz as a bare link, and the
   browser painted it its own blue and underline on a sheet that has no
   blue. It is the one thing to do after that answer, so it is drawn as Send
   is: an ink pill with paper words, and an arrow. The words keep the sheet's
   own weight (no new type declaration). */
.nb-tutor-log .chat-quiz { margin: var(--nb-s4) 0 0; }
.nb-tutor-log .chat-quiz-link {
  display: inline-flex;
  align-items: center;
  gap: var(--nb-s2);
  min-height: 36px;
  box-sizing: border-box;
  padding: 0 16px;
  background: var(--nb-tutor-ink);
  border-radius: var(--nb-r-pill);
  color: var(--nb-tutor-paper);
  font-size: var(--nb-text-sm);
  line-height: normal;
  text-decoration: none;
  transition: background var(--nb-dur-1) var(--nb-ease), transform var(--nb-dur-2) var(--nb-ease);
}
.nb-tutor-log .chat-quiz-link::after {
  content: "";
  flex: none;
  width: 14px;
  height: 14px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8h10M9 4l4 4-4 4'/%3E%3C/svg%3E") center / 14px 14px no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8h10M9 4l4 4-4 4'/%3E%3C/svg%3E") center / 14px 14px no-repeat;
}
@media (hover: hover) {
  .nb-tutor-log .chat-quiz-link:hover { background: color-mix(in srgb, var(--nb-tutor-ink) 84%, var(--nb-tutor-paper)); transform: translateY(-1px); }
}
.nb-tutor-log .chat-quiz-link:active { transform: translateY(0) scale(0.97); transition-duration: var(--nb-dur-1); }
.nb-tutor-log .chat-quiz-link:focus-visible { outline: 2px solid var(--nb-tutor-accent); outline-offset: 3px; }

/* ── §6c · The two panel actions. Quiet ink, no button chrome, revealed by
   state: Stop while a turn is in flight, Clear once the transcript has
   something in it. A sheet with nothing happening shows neither, so the row
   collapses rather than standing empty. ── */
.nb-tutor-acts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nb-s4);
  margin: var(--nb-s4) 0 0;
}
.nb-tutor-acts:not(:has(.nb-tutor-act:not([hidden]))) { display: none; }
.nb-tutor-act {
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  color: var(--nb-tutor-ink);
  font-family: inherit;
  font-size: var(--nb-text-xs);
  line-height: normal;
  text-align: left;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--nb-tutor-ink) 45%, transparent);
  cursor: pointer;
  transition: color var(--nb-dur-1) var(--nb-ease);
}
@media (hover: hover) {
  .nb-tutor-act:hover { color: var(--nb-tutor-ink); text-decoration-color: currentColor; }
}
.nb-tutor-act:focus-visible {
  outline: 2px solid var(--nb-tutor-accent);
  outline-offset: 3px;
  border-radius: var(--nb-r-sm);
}

/* ── §7 · From your notes. A hairline, the kicker in ink, then ONE line per
   source: a page glyph, the chapter number and the chapter name.
   ⚑ THESE ARE INERT AND MUST STAY INERT. The engine's citations carry no slug
   and no spec key, so there is nothing to navigate to. They are <span>, never
   <a>: a class-scoped rule can be outranked, but a span is not an anchor and no
   anchor rule reaches it at any specificity. No underline, no hover, no pointer
   and no touch-target growth, because all four advertise an action that does
   not exist. They become links only if the payload ever carries an identifier. ── */
.nb-tutor-hr {
  height: 1px;
  margin: var(--nb-s6) 0 18px;
  background: color-mix(in srgb, var(--nb-tutor-ink) 16%, transparent);
}
.nb-chat .nb-tutor-src {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: var(--nb-s3);
  font-size: var(--nb-text-sm);
  font-weight: 400;
  line-height: normal;
  color: var(--nb-tutor-ink);
  text-decoration: none;
  cursor: default;
}
.nb-chat .nb-tutor-src svg { flex: none; color: var(--nb-tutor-ink); }

/* ── §8 · The designed silences. Premium-required and not-enrolled both take
   this shape, and never a bare grey status word: a hollow nib, ONE upright line
   in ink, and one quiet underlined way forward, in ink too.
   ⚑ NOT .nb-empty. That family's note is serif italic and its glyph is
   --nb-border-strong, which would put a serif and a foreign ink on a sheet
   whose whole contract is that it has neither. Same shape, drawn in the
   sheet's own two tones. ── */
.nb-tutor-quiet {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  /* No bottom margin: in the states that show it nothing follows, and the
     paper's own padding is the sheet's foot. The intro line is absent in
     those states too, so the head's 32px is the only air above. */
  margin: 0;
}
/* The header carries the one nib; the message stands alone. */
.nb-tutor-quiet > svg { display: none; }
.nb-tutor-quiet-line {
  margin: 0;
  font-size: var(--nb-text-base);
  font-weight: 600;
  line-height: 1.45;
  color: var(--nb-tutor-ink);
  text-wrap: pretty;
}
.nb-tutor-quiet-way {
  display: inline-block;
  margin-top: 9px;
  font-size: var(--nb-text-sm);
  color: var(--nb-tutor-ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--nb-tutor-ink) 45%, transparent);
  transition: color var(--nb-dur-1) var(--nb-ease);
}
@media (hover: hover) {
  .nb-tutor-quiet-way:hover { color: var(--nb-tutor-ink); text-decoration-color: currentColor; }
}
.nb-tutor-quiet-way:focus-visible {
  outline: 2px solid var(--nb-tutor-accent);
  outline-offset: 3px;
  border-radius: var(--nb-r-sm);
}

/* ── §9 · The thinking indicator, lit by tutor-sheet.js through data-phase
   while a question is in flight; with no data-phase it does not render at
   all. See the header for why there is no CSS-only hook. ── */
.nb-tutor-write { display: none; }
.nb-tutor-write[data-phase]:not([data-phase=""]) {
  display: block;
  width: 148px;
  height: 20px;
  margin: var(--nb-s3) 0 var(--nb-s1);
  color: var(--nb-tutor-pencil);
}

/* ── §10 · Below the fold. The aside leaves the third track and sits at the
   foot of the reading column at its full measure. It must stay a positioning
   context at EVERY width: the binding and the margin rule are absolute, and
   with a static ancestor they resolve against the page layout instead of the
   sheet, which paints them thousands of pixels away. `relative` lays out
   exactly as `static` did.

   ⚑ 1080, NOT 1399. THIS QUERY IS THE COMPLEMENT OF THE DOCK. notebook-page.css
   §8b docks the sheet into the third track from min-width 1081, so anything
   this block does between 1081 and 1399 lands on a sheet that IS in the track.
   It was written when the dock started at 1400 and the two numbers were each
   other's complement; moving the dock to 1081 broke that and left a 319px band
   where the sheet was docked AND styled as though it were in the flow.
   Measured at 1100/1280/1366 open, before this line: `.nb-chat` computed
   `display: block`, so the binding strip and the paper stopped being flex
   siblings and the die-cut holes collapsed onto the paper. The two numbers must
   stay complements -- change one, change the other. ── */
@media (max-width: 1080px) {
  .nb-tutor-margin {
    position: relative;
    /* ⚑ `top` IS NOT INHERITED AWAY BY `position: relative`. The base rule is
       sticky at top: calc(--nb-chrome-h + --nb-s6) = 88px, and a relative box
       honours `top` too -- but it PAINTS 88px down while its grid row stays
       where it was. Measured at 390px: the row ended at 14905 and the painted
       paper ended at 14961, 8px past a footer starting at 14953, so the bottom
       corners sat behind the footer no matter what margin was set below. */
    top: auto;
    max-height: none;
    display: block;
    width: 100%;
    max-width: 800px;
    /* ⚑ THE SHEET NEEDS A BOTTOM, NOT JUST A TOP. Inline, it is the last thing
       in the column, and it had a top margin and nothing below it. The same
       token on both sides, so the sheet is spaced from the footer the way it is
       spaced from the note above it. */
    margin-block: var(--nb-s8);
    margin-inline: auto;
  }
  .nb-chat { display: block; overflow: visible; }
  .nb-tutor-body { overflow-y: visible; }
}

/* Mobile: the sheet sits at the foot, full width, and the binding rotates onto
   the TOP edge so the holes run horizontally. The bound edge moves with them,
   so the square corners move to the top. */
@media (max-width: 860px) {
  /* Both sides together, for the reason in §10: a lone margin-top would leave
     the phone sheet with s7 above it and s8 below, which reads as off-centre
     against a footer. */
  .nb-tutor-margin { margin-block: var(--nb-s7); }
  .nb-chat { border-radius: 0 0 var(--nb-r-lg) var(--nb-r-lg); }
  .nb-tutor-bind {
    /* ⚑ top: 0 IS NOT REDUNDANT. The base rule moved from top: 0 to top: 8px so
       the vertical run could be rounded to whole tiles, and this block does not
       otherwise reset it — without this line the rotated binding sat 8px below
       the paper's top edge while .nb-tutor-mrule stayed at 40px. Caught by
       measurement after the base change, not by reading. */
    top: 0;
    height: 34px;
    width: auto;
    /* The same whole-tile rounding as the vertical case, on the other axis.
       left: 0 + width wins over right: 0, and if round() is unsupported the
       `width: auto` above still stands, which is the old behaviour. */
    width: round(down, calc(100% - 30px), 22px);
    right: 0;
    bottom: auto;
    padding: 0 22px 0 8px;
    background-image: radial-gradient(
      circle at 18px 17px,
      transparent 0 2.5px,
      color-mix(in srgb, var(--nb-tutor-ink) 42%, transparent) 2.5px 4px,
      transparent 4px
    );
    background-size: 22px 100%;
    background-repeat: repeat-x;
  }
  .nb-tutor-mrule { top: 40px; left: 0; right: 0; bottom: auto; width: auto; height: 1px; }
  .nb-tutor-body { padding: 58px 26px 30px; }
}

/* ── §11 · Touch and reduced motion. The chip, Send and the quiet ways are
   small by design, so the target is grown with a pseudo-element rather than the
   box. .nb-tutor-src is deliberately absent: it is inert, and a 44px target on
   it would advertise an action that does not exist. ── */
@media (pointer: coarse) {
  .nb-tutor-starter { min-height: 44px; }
  a.nb-tutor-chip,
  .nb-tutor-send,
  .nb-tutor-act,
  .nb-tutor-quiet-way { position: relative; }
  a.nb-tutor-chip::after,
  .nb-tutor-send::after,
  .nb-tutor-act::after,
  .nb-tutor-quiet-way::after {
    content: "";
    position: absolute;
    left: -8px;
    right: -8px;
    top: 50%;
    height: 44px;
    transform: translateY(-50%);
  }
}
@media (prefers-reduced-motion: reduce) {
  .nb-tutor-send,
  .nb-tutor-act,
  .nb-tutor-quiet-way { transition: none; }
}


/* Section 12 stood here: a full-height fixed `.nb-tutor-edge` strip, ported
   from an earlier reading of the source before the design settled on a 44px
   BOOKMARK TAB instead. Its markup went when the tab replaced it, leaving the
   rules matching nothing -- except 12b, which was not inert at all: it forced
   the folded sheet visible again below 1400px, which is why the sheet stacked
   under the note at 1100-1366 instead of docking. Deleted whole. The tab and
   its fold live in section 14. */

/* ==========================================================================
   13 . READING ORDER WITHOUT MOVING MARKUP
   ==========================================================================
   Three things have to sit somewhere other than where the shared partial puts
   them, and NONE of them may be moved in the markup: chat_panel.html is shared
   with the legacy twin, and the standing rule is that the legacy render may
   differ only by the section-2 text and the one sanctioned Stop move. So the
   placement is done with flex order on this sheet alone. The legacy twin never
   loads this file and its DOM is untouched.

     .nb-tutor-quiet   the not-studying message: up front. order: 3.
     .nb-tutor-examples  a locked sheet's example questions. order: 5.
     .nb-tutor-lock    a locked sheet's card, in the composer's place. order: 9.
     .nb-tutor-log     the conversation, then the starters. order: 5.
     .nb-tutor-write   the pen line, under the conversation. order: 6.
     .nb-tutor-status  one line of state, just above the composer. order: 7.
     .nb-tutor-acts    Clear. order: 8.
     .nb-tutor-input   the composer, LAST: at the foot of the sheet, where a
                       conversation's newest line is. order: 9.

   . FLEX ORDER MOVES PAINT, NOT FOCUS, AND THAT WAS THE RISK. Tab follows
   the DOM, and pass 3 filled the sheet with focusables (Fold, the answer
   tools, follow-ups, Try again, Clear, Latest) that the
   old "question -> Send -> the link" proof never covered: the composer, drawn
   last, was reached first. ⚑ SO tutor-sheet.js MOVES THE COMPOSER TO THE
   END OF THE BODY AT WIRE, on this sheet alone. The DOM then runs in the
   painted order, top to bottom: Fold, the conversation's tools, Clear, the
   composer. (It also moved the free / lapsed message to before the log; the
   locked sheet of 2026-09-24 has no log, and its markup is already in
   painted order, so that move is gone.) The partial is untouched (the
   legacy twin never runs the script) and chat-poll.js finds everything by
   id. Without script nothing moves. */
.nb-tutor-body {
  display: flex;
  flex-direction: column;
}
.nb-tutor-input { order: 9; }
/* THE STATUS LINE SITS UNDER THE CONVERSATION, just above the composer
   (order 7). chat-poll.js writes a refusal to BOTH the status line and the
   turn; that once put the two 25px apart and overlapping by 14px. It cannot
   now: tutor-sheet.js rewrites a status that repeats the note to "Not
   answered. See the note above.", so the line never restates the note beside
   it (measured: note bottom 317, status top 329 at 1440). */
.nb-tutor-status { order: 7; }
.nb-tutor-quiet { order: 3; }
.nb-tutor-write { order: 6; }
.nb-tutor-log   { order: 5; }
/* The starters are emitted AFTER the log (so a sibling selector can hide them
   once it has a turn) and drawn BEFORE it: with an empty log the two orders
   are indistinguishable, and the moment the log has content they are gone. */
.nb-tutor-starters { order: 5; }
.nb-tutor-acts  { order: 8; }
/* The AI notice (Website list v1.0, item 6): one line in the sheet's ink,
   next to the message box, never dismissible. It takes the composer's own
   order (9, section 13) and comes before it in the DOM (tutor-sheet.js
   moves the composer to the end), so it is drawn right above the box; on a
   locked sheet right under the card that stands in the box's place, and
   under the quiet message on a sheet that cannot ask. */
.nb-tutor-body .nb-ai-notice {
  order: 9;
  display: flex;
  align-items: center;
  gap: 6px;
  margin: var(--nb-s2) 0;
  font-size: var(--nb-text-sm);
  line-height: 1.5;
  color: var(--nb-tutor-ink);
}
.nb-tutor-body .nb-ai-notice .ai-notice-ic { flex: none; }
/* Where the box asks, the notice is the box's own last line (owner,
   2026-10-05): it speaks for the tutor, not for the answer above it, and it
   stays in view with the sticky box. No glyph there. */
.nb-tutor-body .nb-tutor-input .nb-ai-notice {
  order: 10;
  flex: 1 0 100%;
  margin: 2px 0 0;
  font-size: var(--nb-text-xs);
  line-height: 1.4;
}
.nb-tutor-body .nb-tutor-input .nb-ai-notice .ai-notice-ic { display: none; }
.nb-tutor-body .nb-tutor-quiet + .nb-ai-notice { order: 3; }

/* . 13b . STOP STANDS WHERE SEND STANDS.
   The markup now puts the Stop button inside .nb-tutor-input immediately after
   Send (the one structural move the owner sanctioned, proven safe first:
   chat-poll.js reaches it only through the attribute selector and els.stop,
   and the panel tests assert counts, never nesting). chat-poll.js owns Stop's
   own `hidden` attribute, so this sheet only has to make the two look like one
   control in one place -- same ink, same size, same spot. Send is taken out of
   the flow while a turn runs by the observer file, which keys on the disabled
   toggle chat-poll.js already performs. */
.nb-tutor-act--stop:not([hidden]) {
  order: 5;
  /* ⚑ NO font-family HERE, DELIBERATELY. It would be a third declaration of
     the same thing and a third entry on the typography census: `.nb-chat *`
     (:77-80) already gives every descendant --nb-sans, and `.nb-tutor-act`
     (:498) sets font-family: inherit again for this very button. Only the
     WEIGHT is new -- Stop has to read as ink, like the Send it replaces.
     ⚑ :not([hidden]) IS LOAD-BEARING: chat-poll.js hides Stop with the
     attribute, whose UA display:none loses to any author `display`. */
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  box-sizing: border-box;
  margin: 0 0 2px;
  padding: 0 14px;
  font-weight: 600;
  color: var(--nb-tutor-ink);
  background: none;
  border: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 40%, transparent);
  border-radius: var(--nb-r-pill);
  text-decoration: none;
  cursor: pointer;
}
@media (hover: hover) {
  .nb-tutor-act--stop:hover { border-color: var(--nb-tutor-ink); text-decoration: none; }
}
/* While a turn is in flight the observer marks the sheet and Send steps aside,
   leaving Stop in exactly the place Send occupied. */
.nb-chat.is-inflight .nb-tutor-send { display: none; }


/* ==========================================================================
   14 . THE DIE-CUT BINDING, THE TEXTURE, AND THE BOOKMARK TAB
   ==========================================================================
   Ported from the owner's design file. Variant B throughout: 15px slot depth
   on a 52px pitch, the proportions the file itself selects.

   . THE BINDING IS A MASK, NOT PAINT. The old binding drew rings ON the cream
   with a radial-gradient; this one REMOVES paper. .nb-tutor-bind is a real
   strip of the sheet's own cream whose pill is cut out by an SVG alpha mask,
   so what shows inside every slot is the actual page behind it -- in both
   themes, with no theme logic, because nothing is painted there at all.

   . THE %23fff AND %23000 INSIDE THE MASK ARE NOT COLOURS AND CANNOT BE
   TOKENS. They are the mask's alpha channel: white keeps paper, black cuts it.
   A var() cannot be interpolated into a data-URI -- the URI is one opaque
   string to the parser -- and the raw-colour ratchet does not catch them
   either: its regex is #[0-9A-Fa-f]{3,8}|rgba?\( and %23 contains no '#'.
   Verified against the regex before writing, not assumed.

   . THE PITCH IS FIXED IN PIXELS. mask-size is an absolute pair and
   mask-position anchors the run to the top edge, so a taller sheet gains more
   slots and never stretches the ones it has. */
.nb-tutor-bind {
  /* the strip is paper; the mask takes the slots out of it */
  width: 15px;
  padding: 0;
  background-color: var(--nb-tutor-paper);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.12 0 0 0 0 0.11 0 0 0 0 0.09 0 0 0 0.055 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23p)'/%3E%3C/svg%3E");
  background-repeat: repeat;

  /* ⚑ A FLEX ITEM, NOT AN OVERLAY, AND THIS IS THE WHOLE FIX. The first build
     kept the strip absolutely positioned INSIDE .nb-chat, which carried the
     cream itself. Cutting pills out of a cream strip that sits on cream paper
     reveals more cream: the holes were there, masked correctly, and completely
     invisible. Measured at 2550px -- strip 15px wide, mask applied, and the
     sheet's own background behind it rgb(244,241,234).
     The design makes the strip a SIBLING of the paper, so the only thing
     behind the holes is the page. That is what makes them read as punched.

     ⚑ height: auto IS LOAD-BEARING. §2 still sets `height: round(down,
     calc(100% - 30px), 22px)` for the ring binding this replaced, and it
     survived the move: the strip stopped up to 51px above the paper's foot
     (measured 45px at 2550), leaving the last notch open and the corner
     broken. align-self: stretch gives the strip the paper's full height only
     once that height is released.

     ⚑ relative, NOT static, so the strip can sit above the ::before that
     tints the holes in light mode (§14e); with inset auto it lays out exactly
     as static did. */
  position: relative;
  z-index: 1;
  inset: auto;
  height: auto;
  flex: none;
  align-self: stretch;

  /* ⚑ THE PATTERN FITS THE SHEET: whole notches only, the same air above the
     first as below the last, evenly spaced between. Two mask layers:
       1  the notch alone -- a 15x52 tile whose ONLY opaque shape is the pill
          (x -14, y 12, 29x28, rx 14: the same cut as the design). Repeated
          with `space`, which lays whole tiles from the top edge to the
          bottom edge and shares the remainder equally between them. The
          notch keeps its exact size; only the gaps breathe, by at most
          52/(n-1) px, so a taller sheet drifts less.
       2  a solid strip, the same opaque ink everywhere (a mask reads alpha,
          and the low-glare ratchet forbids a bright keyword).
     Composited with `exclude` (xor): the strip is kept wherever the notch
     layer is clear, and cut wherever a pill sits. The old single-layer tile
     drew the notches INTO the tile, which is why `space` could not be used
     before -- its gaps would have been unmasked and cut the strip in two.
     Tile 12px above the pill and 12px below it, so top gap = bottom gap. */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='52'%3E%3Crect x='-14' y='12' width='29' height='28' rx='14' fill='%23000'/%3E%3C/svg%3E"), linear-gradient(var(--nb-tutor-ink), var(--nb-tutor-ink));
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='52'%3E%3Crect x='-14' y='12' width='29' height='28' rx='14' fill='%23000'/%3E%3C/svg%3E"), linear-gradient(var(--nb-tutor-ink), var(--nb-tutor-ink));
  -webkit-mask-size: 15px 52px, 100% 100%;
  mask-size: 15px 52px, 100% 100%;
  -webkit-mask-position: left top, left top;
  mask-position: left top, left top;
  -webkit-mask-repeat: no-repeat space, no-repeat;
  mask-repeat: no-repeat space, no-repeat;
  -webkit-mask-composite: xor, source-over;
  mask-composite: exclude, add;
}

/* ⚑ .nb-chat IS NOW JUST THE FLEX ROW. Everything that made it look like a
   sheet moves to .nb-tutor-body, which is the paper. If the row keeps a
   background the strip has nothing to reveal. */
.nb-chat {
  background: none;
  background-color: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
}

/* The paper. It carries the cream, the noise, the border, the radius and the
   shadow -- everything .nb-chat used to. The left border is absent because the
   strip is the left edge. */
.nb-tutor-body {
  flex: 1;
  min-width: 0;
  padding-left: var(--nb-s6);
  background-color: var(--nb-tutor-paper);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.12 0 0 0 0 0.11 0 0 0 0 0.09 0 0 0 0.055 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23p)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  border: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 12%, transparent);
  border-left: none;
  border-radius: 0 var(--nb-r-lg) var(--nb-r-lg) 0;
  box-shadow: var(--nb-tutor-shadow);
}

/* . 14e . LIGHT MODE EDGE. The paper is the same cream in both themes by
   design and does not change here. On the day desk the page is nearly that
   colour, so in light only the sheet gets a firmer hairline and a deeper
   shadow, and the holes get a depth of their own: a ::before the width of the
   strip, BEHIND it (z-index 0 under the strip's 1), masked to each notch.
   ⚑ THE HOLES SHOW THE PAGE. The first light build filled that ::before with
   one flat shade, so every notch read as a grey disc stuck on the edge rather
   than a hole (owner, 2026-09-23). Now the ::before paints the sheet's ink
   only where its mask lets it: one hairline rim, the paper's cut edge.
   ⚑ NO SHADOW IN THE HOLE (owner, 2026-09-24: "the holes should be natural,
   right now it looks shadow or something"). A shade fading down each notch
   read as a grey bump, not a hole. The inside is clear, so the dotted page
   shows through each hole exactly as it lies around the sheet. The mask tile is the strip's own tile, laid
   with the same `space` over the same height, so the two line up notch for
   notch. %23000 and the opacities are the mask's alpha, not colours. In dark
   the night page shows through by itself and none of this applies. */
/* ⚑ THE SHADOW FALLS DOWN AND RIGHT, NEVER LEFT (2026-09-24). The holes sit
   against the body's left edge, so a shadow spread to the left darkened the
   page seen through each notch, strongest at the body's edge: every hole read
   as a shaded ball. Offset x plus the negative spread equals the blur in each
   layer (16 + 32 = 48, 4 + 4 = 8), so neither reaches past the left edge and
   the page inside a hole is the same page as around the sheet. */
html.theme-light .nb-tutor-body {
  border-color: color-mix(in srgb, var(--nb-tutor-ink) 28%, transparent);
  box-shadow:
    16px 24px 48px -32px color-mix(in srgb, var(--nb-tutor-ink) 30%, transparent),
    4px 3px 8px -4px color-mix(in srgb, var(--nb-tutor-ink) 16%, transparent);
}
html.theme-light .nb-chat::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 15px;
  background: var(--nb-tutor-ink);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='52'%3E%3Crect x='-14' y='12.5' width='28.5' height='27' rx='13.5' fill='none' stroke='%23000' stroke-opacity='.22'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='52'%3E%3Crect x='-14' y='12.5' width='28.5' height='27' rx='13.5' fill='none' stroke='%23000' stroke-opacity='.22'/%3E%3C/svg%3E");
  -webkit-mask-size: 15px 52px;
  mask-size: 15px 52px;
  -webkit-mask-position: left top;
  mask-position: left top;
  -webkit-mask-repeat: no-repeat space;
  mask-repeat: no-repeat space;
  z-index: 0;
  pointer-events: none;
}
html.theme-light .nb-tutor-tab {
  border-color: color-mix(in srgb, var(--nb-tutor-ink) 40%, transparent);
  box-shadow: -14px 0 30px color-mix(in srgb, var(--nb-tutor-ink) 22%, transparent);
}

/* ⚑ NO MARGIN RULE. The design has none: the punched strip is the left edge
   and a second vertical line beside it is one line too many. The element stays
   in the shared partial because that markup is also the legacy twin's, where
   notes.css still draws it. */
.nb-tutor-mrule { display: none; }

/* . 14b . THE BOOKMARK TAB . the sheet's corner peeking out ----------------
   One 44px strip of the same cream with the same noise, fixed to the right of
   the viewport. It is the folded sheet.

   . LEVEL WITH THE CHAPTERS RIBBON (owner, 2026-09-24: "make the tutor
   collapsed the same level as the chapter collapsed so same height when
   folded"). The folded chapters are a ribbon on the LEFT edge
   (notebook-page.css 8c): its top 16px tucked under the navbar, 290px
   hanging below the navbar's foot. The folded tutor is its mirror on the
   RIGHT edge, the same tuck and the same 290px, so the two folded things
   start and end on the same lines whichever one is put away. The tucked top
   is square (the navbar covers it); the one free corner, at the foot on the
   page side, keeps the paper's 12px round. ⚑ THE TWO NUMBERS ARE COPIES, not
   a shared token: the ribbon's live on .nb-rail-fold in notebook-page.css
   and a custom property does not cross elements. Change one, change both;
   noteviewer/tests_tutor_sheet.py reads the two files and compares them.

   . SEPIA ONLY WHILE AN ANSWER WAITS. The diamond is the signal, and .wait is
   set by the observer file when a reply lands on a closed sheet. Nothing else
   on this tab is ever sepia; that is the fifth-use rule DESIGN.md states. */
.nb-tutor-tab {
  --nb-tutor-tab-tuck: var(--nb-s4);  /* the part kept under the navbar, as the ribbon's */
  --nb-tutor-tab-len: 290px;          /* what hangs below the navbar, as the ribbon's */
  position: fixed;
  top: calc(var(--nb-chrome-h) - var(--nb-tutor-tab-tuck));
  right: 0;
  z-index: 200;
  width: 44px;
  height: calc(var(--nb-tutor-tab-len) + var(--nb-tutor-tab-tuck));
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: calc(var(--nb-tutor-tab-tuck) + 18px) 0 16px;
  background-color: var(--nb-tutor-paper);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.12 0 0 0 0 0.11 0 0 0 0 0.09 0 0 0 0.055 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23p)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  border: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 30%, transparent);
  border-right: none;
  border-radius: 0 0 0 var(--nb-r-lg);
  box-shadow: var(--nb-tutor-edge-shadow);
  cursor: pointer;
  font: var(--nb-type-label);
  transition: transform var(--nb-dur-3) var(--nb-ease),
              opacity var(--nb-dur-2) var(--nb-ease),
              width var(--nb-dur-2) var(--nb-ease);
}
@media (hover: hover) {
  .nb-tutor-tab:hover { width: 50px; }
}
.nb-tutor-tab:focus-visible { outline: 2px solid var(--nb-tutor-accent); outline-offset: -3px; }
/* Open: the tab slides off the edge it came from and stops taking focus. */
.nb-tutor-open .nb-tutor-tab {
  opacity: 0;
  pointer-events: none;
  transform: translateX(112%);
  visibility: hidden;
}
.nb-tutor-tab-ic {
  flex: none;
  display: flex;
  color: color-mix(in srgb, var(--nb-tutor-ink) 78%, transparent);
  transition: color var(--nb-dur-2) var(--nb-ease);
}
.nb-tutor-tab-lbl {
  margin-top: 14px;
  writing-mode: vertical-rl;
  font-size: var(--nb-text-xs);
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--nb-tutor-ink);
  user-select: none;
  white-space: nowrap;
}
.nb-tutor-tab-dm {
  margin-top: auto;
  padding-top: 18px;
  display: flex;
  color: color-mix(in srgb, var(--nb-tutor-ink) 42%, transparent);
  transition: color var(--nb-dur-2) var(--nb-ease);
}
/* SEPIA USE: an answer is waiting on a folded sheet. */
.nb-tutor-tab--wait .nb-tutor-tab-dm,
.nb-tutor-tab--wait .nb-tutor-tab-ic { color: var(--nb-tutor-accent); }
/* Locked (no Premium, or not signed in): a padlock where the diamond stands,
   in the nib's ink so it reads before the tab is opened. Never sepia: no
   answer can wait on a sheet that cannot ask. */
.nb-tutor-tab-dm--lock { color: color-mix(in srgb, var(--nb-tutor-ink) 78%, transparent); }

/* . 14c . Folded by default -----------------------------------------------
   The sheet is tucked until the student asks for it. The grid track it lives
   in shrinks to the tab's width in the same motion, so the note takes the rest
   back rather than leaving a hole.

   ⚑ THE SLIDE HAS TO BE CLIPPED OR IT SCROLLS THE PAGE. A folded sheet is
   still laid out -- it is only visibility: hidden -- and translateX pushes its
   400px of content past the right edge, which is scrollable overflow, not
   nothing. Measured at 1920 folded: .nb-tutor-body ended at 2376px against a
   1920px viewport and the whole page gained a horizontal scrollbar.

   overflow-x: clip, NOT hidden. `hidden` makes the element a scroll container,
   which would break the sticky chapter rail inside it; `clip` does not create
   one, so sticky keeps working against the real scrollport. Scoped with :has()
   so a layout with no tutor is left exactly as it is. */
.nb-page-layout:has(.nb-tutor-margin) { overflow-x: clip; }
/* ⚑ overflow-x: clip IS SAFARI 16+. Below that the declaration is dropped, the
   axis falls back to visible, and the folded sheet's translate scrolls the page
   sideways again -- the exact bug this rule exists to stop. `overflow: hidden`
   is not the fallback: it makes the element a scroll container and the sticky
   chapter rail inside it stops sticking, which is a worse break than a stray
   scrollbar. So the fallback removes the overflowing box instead of trying to
   contain it: a folded sheet is already visibility: hidden and has nothing to
   show, and display: none costs only the slide animation, on those browsers
   alone. Everything else about the fold is unchanged there. */
@supports not (overflow-x: clip) {
  html:not(.nb-tutor-open) .nb-tutor-margin { display: none; }
}
html:not(.nb-tutor-open) .nb-tutor-margin {
  transform: translateX(478px);
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
}
.nb-tutor-margin {
  transition: transform var(--nb-dur-3) var(--nb-ease),
              opacity var(--nb-dur-2) var(--nb-ease),
              visibility 0s linear var(--nb-dur-3);
}
.nb-tutor-open .nb-tutor-margin { transition-delay: 0s; }

/* . 14d . Below 1080px ----------------------------------------------------
   Without script the sheet stops docking and sits inline under the note, and
   the binding rotates onto the top edge. With script §15b takes over: the tab
   comes back as a pill and the sheet opens as a bottom sheet, which is why the
   display: none below is overridden later in the file. */
@media (max-width: 1080px) {
  .nb-tutor-tab { display: none; }
  html:not(.nb-tutor-open) .nb-tutor-margin {
    transform: none;
    opacity: 1;
    pointer-events: auto;
    visibility: visible;
  }
  /* The bottom margin that clears the footer is NOT here: it is §10's
     `margin-block`, which owns both sides at once so they cannot drift apart.
     A `margin-bottom` here would sit later in the file and silently win over
     half of it. */
  /* The row becomes a column, so the same two flex items stack: strip on top,
     paper beneath. Nothing is repositioned -- only the axis changes. */
  .nb-chat { flex-direction: column; }
  /* The same two-layer mask as the vertical strip, turned on its side: the
     notch-only tile is 52x15 with the pill at x 12 (12px of paper each side),
     laid left to right with `space`, over a solid strip, composited with
     exclude. Whole notches only, the same air before the first as after the
     last, the rest shared evenly. */
  .nb-tutor-bind {
    width: auto;
    height: 15px;
    align-self: stretch;
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='52' height='15'%3E%3Crect x='12' y='-14' width='28' height='29' rx='14' fill='%23000'/%3E%3C/svg%3E"), linear-gradient(var(--nb-tutor-ink), var(--nb-tutor-ink));
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='52' height='15'%3E%3Crect x='12' y='-14' width='28' height='29' rx='14' fill='%23000'/%3E%3C/svg%3E"), linear-gradient(var(--nb-tutor-ink), var(--nb-tutor-ink));
    -webkit-mask-size: 52px 15px, 100% 100%;
    mask-size: 52px 15px, 100% 100%;
    -webkit-mask-position: left top, left top;
    mask-position: left top, left top;
    -webkit-mask-repeat: space no-repeat, no-repeat;
    mask-repeat: space no-repeat, no-repeat;
    -webkit-mask-composite: xor, source-over;
    mask-composite: exclude, add;
  }
  html.theme-light .nb-chat::before {
    left: 0; right: 0; top: 0; bottom: auto; width: auto; height: 15px;
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='52' height='15'%3E%3Crect x='12' y='-14' width='28' height='29' rx='14' fill-opacity='.03'/%3E%3Crect x='12.5' y='-14' width='27' height='28.5' rx='13.5' fill='none' stroke='%23000' stroke-opacity='.3'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='52' height='15'%3E%3Crect x='12' y='-14' width='28' height='29' rx='14' fill-opacity='.03'/%3E%3Crect x='12.5' y='-14' width='27' height='28.5' rx='13.5' fill='none' stroke='%23000' stroke-opacity='.3'/%3E%3C/svg%3E");
    -webkit-mask-size: 52px 15px;
    mask-size: 52px 15px;
    -webkit-mask-repeat: space no-repeat;
    mask-repeat: space no-repeat;
  }
  /* The bound edge is the TOP one now, so the square corners move with it. */
  .nb-tutor-body {
    padding-left: var(--nb-s6);
    padding-top: 30px;
    border: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 12%, transparent);
    border-top: none;
    border-radius: 0 0 var(--nb-r-lg) var(--nb-r-lg);
  }
}


/* ==========================================================================
   15 . PASS 3 (2026-09-23): THE COMPOSER, FOLLOWING, TOOLS, PHONE SHEET
   ==========================================================================
   Everything here is drawn by tutor-sheet.js (the partial gains no markup).
   ⚑ NO font, font-family OR font-weight DECLARATION IN THIS SECTION: the
   typography census fingerprints every one, and the classes below reuse the
   weights their neighbours already carry (.nb-tutor-act, .nb-tutor-starter).
   ⚑ [hidden] LOSES TO AN AUTHOR display, so every element here that is
   hidden by attribute sets its display under :not([hidden]). */

/* The composer's pieces, in the card's second row. */
/* "ASK ABOUT" IS GONE (owner, 2026-09-24: "remove the ask about this note
   and all of biology"). The This note | All of <Subject> switch and the line
   under it are deleted with their script. Every question is about this note,
   as the "This note" choice made it: tutor-sheet.js still names the note at
   the head of the message on its way out. */
.nb-tutor-hint {
  order: 3;
  /* ⚑ IT SHRINKS, SEND DOES NOT MOVE. At max-content the hint is 250px of a
     302px row, which pushed Send onto a third row at every desktop width. */
  flex: 1 1 0;
  min-width: 0;
  margin-right: auto;
  font-size: var(--nb-text-xs);
  line-height: 1.4;
  color: var(--nb-tutor-ink);
  /* one small line (owner: "so big"): plain words, no key boxes */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* While Latest shows, the hint steps aside rather than be cut short. */
.nb-tutor-input:has(.nb-tutor-jump:not([hidden])) .nb-tutor-hint { display: none; }
.nb-tutor-count { order: 4; font-size: var(--nb-text-xs); line-height: 1.4; color: var(--nb-tutor-ink); font-variant-numeric: tabular-nums; }
.nb-tutor-count.is-over { color: var(--nb-tutor-ink); text-decoration: underline; text-underline-offset: 3px; }
.nb-tutor-send, .nb-tutor-act--stop:not([hidden]) { margin-left: auto; }

/* Latest: back to the newest line, above the card, while reading back. */
/* Latest: in the composer's own row, before Send. It used to float 40px
   above the card, over live content, and covered citations and the Practise
   link (review, 2026-09-23). */
.nb-tutor-jump:not([hidden]) {
  order: 4;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 30px;
  padding: 0 10px 0 12px;
  background: var(--nb-tutor-paper);
  border: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 35%, transparent);
  border-radius: var(--nb-r-pill);
  color: var(--nb-tutor-ink);
  font-size: var(--nb-text-xs);
  cursor: pointer;
}
.nb-tutor-jump:focus-visible { outline: 2px solid var(--nb-tutor-ink); outline-offset: 2px; }

/* The fold button, after the chip. */
.nb-tutor-head:has(.nb-tutor-fold) { justify-content: flex-start; }
.nb-tutor-head:has(.nb-tutor-fold) .nb-tutor-chip { margin-left: auto; }
.nb-tutor-fold {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin: 0 -6px 0 0;
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--nb-r-sm);
  color: var(--nb-tutor-ink);
  cursor: pointer;
  transition: background-color var(--nb-dur-1) var(--nb-ease);
}
@media (hover: hover) {
  .nb-tutor-fold:hover { background: color-mix(in srgb, var(--nb-tutor-ink) 8%, transparent); }
}
.nb-tutor-fold:focus-visible { outline: 2px solid var(--nb-tutor-ink); outline-offset: 2px; }

/* In flight: the transport's own "Thinking" line steps aside for the pen line,
   and Clear waits until the turn settles. */
.nb-tutor-log .chat-pending { display: none; }
.nb-chat.is-inflight .nb-tutor-acts { display: none; }
/* Nothing can be sent while a turn is out, so the keys hint steps aside and
   Stop waiting stands alone, never beside a hint cut short; the last
   answer's follow-ups step out with it, the new answer brings its own. */
.nb-chat.is-inflight .nb-tutor-hint,
.nb-chat.is-inflight .nb-tutor-followups { display: none; }

/* THE THOUGHT LINE ON THE SHEET (owner, 2026-10-09: "make the thinking look
   better than that ugly style", then "remove the logo of the tutor next to
   the thinking"). thought-line.js stands it in the turn under the question
   while the tutor works: words only, "Thinking…" shimmering beside a live
   clock, the stages arriving under it (Reading your question, Reading your
   notes on 6.1 Photosynthesis, Writing your answer), then it settles into
   "Thought for 3.2s" over the answer, the stages folded under its chevron.
   No glyph, no blue, no accent. Every word stays ink (owner, 2026-09-24:
   "make all the text BLACK color"): a finished stage is told by its tick.
   The pen line stays dark (tutor-sheet.js) and the status line steps aside
   while the line works; with no thought-line.js both draw as before. */
.nb-tutor-log .chat-turn > .esa-tl {
  --esa-tl-ink: var(--nb-tutor-ink);
  --esa-tl-soft: var(--nb-tutor-ink);
  --esa-tl-indent: 0px;
  margin: 0 0 var(--nb-s4);
  font-size: var(--nb-text-sm);
}
.nb-tutor-log .chat-turn > .esa-tl .esa-tl-glyph { display: none; }
.nb-tutor-log .chat-turn > .esa-tl .esa-tl-steps { gap: var(--nb-s2); padding-top: var(--nb-s3); line-height: 1.4; }
/* A long note name wraps under its own words, the mark beside its first line. */
.nb-tutor-log .chat-turn > .esa-tl .esa-tl-step { align-items: flex-start; }
.nb-tutor-log .chat-turn > .esa-tl .esa-tl-mark { height: 1.4em; }
.nb-tutor-log .chat-turn > .esa-tl .esa-tl-head:focus-visible { outline-color: var(--nb-tutor-ink); }
.nb-chat:has(.esa-tl[data-working]) .nb-tutor-status { display: none; }

/* THE SHEET IN MOTION (owner, 2026-10-09: "add the animations you added to
   the profile, dashboard and so on. cool user flow"). The dashboard tutor's
   grammar in this sheet's ink. Opened, the opening line and the starters
   deal in one after another. A starter or follow-up pressed washes, lets its
   words go and they fly into the box (tutor-sheet.js), the box's own words
   appearing as they land; Send gives one nudge. Asked, the question slides
   in; the Thought Line rises under it; the answer is written down the page;
   its sources, quiz and note follow; the next steps deal in last. Send lifts
   and presses as the profile's buttons do (above). An answer that lands on a
   folded sheet bounces the tab's nib. Only the newest turn moves; reduced
   motion stills it all (the block after §15). */
@keyframes nb-tutor-deal { from { opacity: 0; transform: translateY(6px); } }
@keyframes nb-tutor-ask { from { opacity: 0; translate: 14px 6px; } }
@keyframes nb-tutor-write { from { -webkit-mask-position: 0 100%; mask-position: 0 100%; } }
@keyframes nb-tutor-follow { from { opacity: 0; translate: 0 8px; } }
@keyframes nb-tutor-nudge { 45% { scale: 1.12; } }
@keyframes nb-tutor-nib { 0%, 100% { scale: 1; } 30% { scale: 1.6; } 60% { scale: 0.85; } }
html.nb-tutor-open .nb-tutor-sub { animation: nb-tutor-deal 360ms var(--nb-ease) 60ms backwards; }
html.nb-tutor-open .nb-tutor-starters > * { animation: nb-tutor-deal 420ms var(--nb-ease) 140ms backwards; }
html.nb-tutor-open .nb-tutor-starters > :nth-child(2) { animation-delay: 200ms; }
html.nb-tutor-open .nb-tutor-starters > :nth-child(3) { animation-delay: 260ms; }
html.nb-tutor-open .nb-tutor-starters > :nth-child(4) { animation-delay: 320ms; }
@media (hover: hover) {
  .nb-tutor-starter:hover { background: color-mix(in srgb, var(--nb-tutor-ink) 6%, transparent); text-decoration: none; }
  .nb-tutor-starter:hover::before { transform: rotate(-45deg) translate(2px, 2px); }
}
.nb-tutor-starter:active { background: color-mix(in srgb, var(--nb-tutor-ink) 11%, transparent); }
.nb-tutor-starter.is-sent { color: color-mix(in srgb, var(--nb-tutor-ink) 35%, transparent); }
/* the words in flight, from their line to where the box's words begin */
.nb-tutor-flyer { position: fixed; z-index: 900; margin: 0; pointer-events: none; white-space: nowrap; }
.nb-tutor-input .nb-tutor-field { transition: color 260ms var(--nb-ease); }
.nb-tutor-input.is-landing .nb-tutor-field { color: transparent; transition: none; }
.nb-tutor-input.is-ready .nb-tutor-send:not(:disabled) { animation: nb-tutor-nudge 420ms var(--nb-ease); }
.nb-chat.is-inflight .nb-tutor-act--stop:not([hidden]) { animation: nb-tutor-deal 300ms var(--nb-ease) backwards; }
.nb-tutor-log .chat-turn:last-child > .chat-turn-q { animation: nb-tutor-ask 360ms var(--nb-ease) backwards; }
.nb-tutor-log .chat-turn:last-child > .esa-tl { animation: nb-tutor-follow 420ms var(--nb-ease) 120ms backwards; }
.nb-tutor-log .chat-turn:last-child > .chat-prose {
  -webkit-mask: linear-gradient(to bottom, var(--nb-tutor-ink) 0 33.4%, transparent 66.6%) 0 0 / 100% 300% no-repeat;
  mask: linear-gradient(to bottom, var(--nb-tutor-ink) 0 33.4%, transparent 66.6%) 0 0 / 100% 300% no-repeat;
  animation: nb-tutor-write 900ms var(--nb-ease) backwards;
}
.nb-tutor-log .chat-turn:last-child > :is(.chat-cites-label, .chat-cites, .chat-quiz) { animation: nb-tutor-follow 480ms var(--nb-ease) 380ms backwards; }
.nb-tutor-log .chat-turn:last-child > .chat-note { animation: nb-tutor-follow 420ms var(--nb-ease) 120ms backwards; }
.nb-tutor-log .chat-turn:last-child > .nb-tutor-followups > * { animation: nb-tutor-deal 420ms var(--nb-ease) 520ms backwards; }
.nb-tutor-log .chat-turn:last-child > .nb-tutor-followups > :nth-child(2) { animation-delay: 580ms; }
.nb-tutor-log .chat-turn:last-child > .nb-tutor-followups > :nth-child(3) { animation-delay: 640ms; }
.nb-tutor-log .chat-turn:last-child > .nb-tutor-followups > :nth-child(4) { animation-delay: 700ms; }
.nb-tutor-tab--wait .nb-tutor-tab-dm { animation: nb-tutor-nib 640ms var(--nb-ease) 2; }

/* Next steps, under the newest answer only: the three follow-ups and
   Practise this topic as their last row, one quiet group set apart from the
   sources (owner, 2026-10-05). An older answer keeps only its words. */
.nb-tutor-followups { display: flex; flex-direction: column; align-items: flex-start; gap: 0; margin: var(--nb-s5) 0 0; }
.nb-tutor-followups a.nb-tutor-starter { text-decoration: none; }
.nb-tutor-again { margin-left: var(--nb-s1); color: var(--nb-tutor-ink); }

/* The citation rows: a small page glyph in ink. Inert spans still. */
.nb-tutor-log .chat-cite::before {
  content: "";
  flex: none;
  width: 11px;
  height: 13px;
  margin-top: 3px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 13'%3E%3Cpath d='M1.5 1h5.5l2.5 2.5v8.5h-8z' fill='none' stroke='%23000' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 13'%3E%3Cpath d='M1.5 1h5.5l2.5 2.5v8.5h-8z' fill='none' stroke='%23000' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* An answer's entrance is the sheet in motion's, above: written down the page. */

/* PAPER UNDER THE STUCK COMPOSER. A conversation scrolling under the card
   must not show in the strip between the card and the sheet's edge, so a
   strip of the sheet's own paper sticks there.
   ⚑ IT IS THE BODY'S LAST CHILD, NOT THE CARD'S (owner, 2026-09-24: "the box
   is cut off"). As the card's ::after it painted OVER the card: first over
   its 1px bottom border (top: 100% is the padding box's foot), then, moved
   below the border, over the card's own drop shadow, which left a flat pale
   slab under the card. As the body's own ::after, sticky at bottom: 0 on
   z-index 0 with the card stuck one strip higher on z-index 1, it sits
   BEHIND the card, so the card's border and shadow land on it like anywhere
   else on the sheet. Shown only where the card is sticky (the desk dock and
   the open phone sheet), and only on a body whose foot IS a card (the
   composer, or the locked card): that is also where the body gives up its
   bottom padding for it. A sheet that is only a message keeps its padding. */
.nb-tutor-body::after {
  content: "";
  display: none;
  order: 10;
  flex: none;
  position: sticky;
  bottom: 0;
  z-index: 0;
  /* one corner radius taller, tucked up behind the card: the card's rounded
     foot corners are open, and scrolled text showed in them */
  height: calc(var(--nb-s3) + var(--nb-r-lg));
  margin-top: calc(-1 * var(--nb-r-lg));
  background-color: var(--nb-tutor-paper);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.12 0 0 0 0 0.11 0 0 0 0 0.09 0 0 0 0.055 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23p)'/%3E%3C/svg%3E");
  pointer-events: none;
}

/* Docked on a desk: the composer stays at the foot of the scrolling body,
   and the sheet stops short of the corner so the theme switch sits under it. */
@media (min-width: 1081px) {
  /* ⚑ THE GAP IS THE PAPER STRIP, NOT PADDING. Chrome takes a sticky offset
     from the scroller's content box, so bottom: 12px over 34px of padding left
     a 47px band under the card where answers scrolled visibly. A body whose
     foot is a card (the composer, or the locked card in its place) keeps no
     bottom padding; its sticky ::after strip (above) is the 12px gap, at rest
     and stuck alike (no jump), and the card sits right above it.
     ⚑ ONLY UNDER A CARD. The not-studying sheet has no card: its message and
     its one link are the last things on the paper. The strip once showed
     there too, and with the padding gone it sat over the link, half hiding
     "Add it to your subjects" (review, 2026-09-24). That sheet keeps the
     body's own 34px foot and no strip. */
  .nb-tutor-body:has(> .nb-tutor-input, > .nb-tutor-lock) { padding-bottom: 0; }
  .nb-tutor-input { position: sticky; bottom: var(--nb-s3); z-index: 1; }
  .nb-tutor-body:has(> .nb-tutor-input, > .nb-tutor-lock)::after { display: block; }
  html.nb-tutor-open .nb-tutor-margin { max-height: calc(100vh - var(--nb-chrome-h) - var(--nb-s6) - 94px); }
  /* ⚑ 50% LONGER (owner, 2026-09-24: "make it longer 50% longer"). The empty
     docked sheet measured 467px, drawn by its content. It is now one sheet
     of 700px in every state, still capped by the max-height above, so it
     always fits under the navbar with the theme switch clear below it (a
     900px window allows 710px, a 720px one 530px). Paper does not grow with
     what is written on it: a long conversation scrolls inside the body. */
  .nb-tutor-margin { height: 700px; }
  /* Except a sheet that is only a message (Premium, not studying this
     subject): one line and a link on 700px of blank paper read as a sheet
     that failed to load, so it keeps its own height, as before. */
  .nb-tutor-margin:has(> .nb-chat > .nb-tutor-body > .nb-tutor-quiet) { height: auto; }
  /* THE FOOT IS WHERE YOU WRITE. A spacer after the conversation (order 8,
     before Clear and the composer or the locked card, both order 9) takes
     the free height, so the composer rests at the sheet's foot on an empty
     sheet and keeps its own 22px above it on a full one. A margin-top: auto
     on the card would do the first and lose the second: at 0 the card's
     paper band would sit on the last line of the conversation. */
  .nb-tutor-body::before { content: ""; order: 8; flex: 1 0 0; }
  /* A sheet with no field takes focus itself when it opens (tutor-sheet.js),
     as the phone sheet does: a starting point, not a control, so no ring. */
  .nb-tutor-margin:focus { outline: none; }
}

/* Answers a touch larger on a phone, where the sheet is the whole screen.
   A citation is a flex row whose words are one anonymous flex item, and its
   min-content was the whole URL, which ran past the sheet (The No-Sideways
   Rule, DESIGN.md): anywhere, with min-width 0, lets it break. */
@media (max-width: 860px) {
  .nb-tutor-log .chat-prose { font-size: var(--nb-text-md); }
  .nb-tutor-log .chat-cite { overflow-wrap: anywhere; min-width: 0; }
}

/* Touch: the field above the zoom threshold, and every control a thumb's
   size. The keys hint goes: a touch keyboard's Enter is a new line. */
@media (max-width: 1080px), (pointer: coarse) {
  .nb-tutor-field { font-size: var(--nb-text-md); min-height: 44px; }
}
@media (pointer: coarse) {
  .nb-tutor-hint { display: none; }
  /* With no key hint on touch, the notice takes its place beside Send rather
     than a line of its own. */
  .nb-tutor-body .nb-tutor-input .nb-ai-notice { order: 3; flex: 1 1 0; min-width: 0; margin: 0 auto 0 0; }
  .nb-tutor-send, .nb-tutor-act--stop:not([hidden]) { min-height: 44px; }
  .nb-tutor-fold { width: 44px; height: 44px; margin-right: -10px; }
  .nb-tutor-jump:not([hidden]) { min-height: 44px; }
}

/* ── 15b . Phones and tablets (1080px and below): a pill, and a bottom sheet ──
   The tab turns into a cream pill at the foot of the screen, and opens the
   sheet over the note as a bottom sheet with a scrim. Without script the sheet
   stays inline at the foot of the note (html.nb-tutor-js is set by the
   script), as it always has. */
@media (max-width: 1080px) {
  .nb-tutor-tab {
    display: flex;
    top: auto;
    right: auto;
    left: 50%;
    bottom: calc(var(--nb-s4) + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    width: auto;
    height: 48px;
    min-height: 48px;
    flex-direction: row;
    align-items: center;
    gap: var(--nb-s2);
    padding: 0 18px;
    border: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 30%, transparent);
    border-radius: var(--nb-r-pill);
    z-index: 790;
  }
  .nb-tutor-tab:hover { width: auto; }
  .nb-tutor-tab-lbl { writing-mode: horizontal-tb; margin: 0; letter-spacing: 0.18em; }
  .nb-tutor-tab-dm { margin: 0; padding: 0; }
  html.theme-light .nb-tutor-tab { box-shadow: 0 8px 24px color-mix(in srgb, var(--nb-tutor-ink) 22%, transparent); }
  /* visibility too, or a keyboard user lands on an invisible pill.
     ⚑ IT IS TRANSITIONED ON THE WAY OUT ONLY. Going, the pill stays visible
     while it fades and slides (the rule for the hidden state carries the
     visibility transition). Coming back, it is visible AT ONCE (the resting
     rule carries visibility 0s), because close() hands it focus in the same
     frame: a pill still at the start of a visibility transition is hidden,
     focus() on it does nothing, and Esc or Close left keyboard focus on the
     page's body at every width up to 1080px (review, 2026-09-24). Reduced
     motion hid this, its durations being zero. */
  .nb-tutor-tab { transition: transform var(--nb-dur-3) var(--nb-ease), opacity var(--nb-dur-2) var(--nb-ease), visibility 0s; }
  .nb-tutor-open .nb-tutor-tab {
    transform: translate(-50%, 140%);
    transition: transform var(--nb-dur-3) var(--nb-ease), opacity var(--nb-dur-2) var(--nb-ease), visibility var(--nb-dur-2) var(--nb-ease);
  }
  .nb-tutor-tab--tucked {
    opacity: 0;
    pointer-events: none;
    visibility: hidden;
    transition: transform var(--nb-dur-3) var(--nb-ease), opacity var(--nb-dur-2) var(--nb-ease), visibility var(--nb-dur-2) var(--nb-ease);
  }
  html:not(.nb-tutor-js) .nb-tutor-tab { display: none; }

  html.nb-tutor-js:not(.nb-tutor-open) .nb-tutor-margin { display: none; }
  html.nb-tutor-open .nb-tutor-margin {
    position: fixed;
    left: 0;
    right: 0;
    top: auto;
    bottom: var(--nb-tutor-kb, 0px);
    height: min(86dvh, calc(100dvh - var(--nb-tutor-kb, 0px) - var(--nb-chrome-h) - 8px));
    max-height: none;
    width: auto;
    max-width: none;
    margin: 0;
    z-index: 850;
    display: flex;
    transform: none;
  }
  /* A sheet that is only a message (Premium, not studying this subject) is
     as tall as its message, as on the desk (the dock block above), still
     anchored to the foot and still capped like any open sheet. One line and
     a link on 86% of a phone's height read as a sheet that failed to load
     (audit N-007, 2026-09-25: 554px of blank paper at 390). */
  html.nb-tutor-open .nb-tutor-margin:has(> .nb-chat > .nb-tutor-body > .nb-tutor-quiet) {
    height: auto;
    max-height: min(86dvh, calc(100dvh - var(--nb-tutor-kb, 0px) - var(--nb-chrome-h) - 8px));
  }
  html.nb-tutor-open .nb-tutor-margin:focus { outline: none; }
  html.nb-tutor-open .nb-tutor-margin::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    background: color-mix(in srgb, var(--nb-tutor-ink) 38%, transparent);
    touch-action: none;
  }
  html.nb-tutor-open .nb-chat { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: visible; }
  html.nb-tutor-open .nb-tutor-body { overflow-y: auto; overscroll-behavior: contain; min-height: 0; border-radius: 0; }
  html.nb-tutor-open.theme-light .nb-tutor-body { box-shadow: 0 -12px 32px color-mix(in srgb, var(--nb-tutor-ink) 18%, transparent); }
  /* Under a card only, as on the desk (the dock block above says why). */
  html.nb-tutor-open .nb-tutor-body:has(> .nb-tutor-input, > .nb-tutor-lock) { padding-bottom: 0; }
  html.nb-tutor-open .nb-tutor-body:has(> .nb-tutor-input, > .nb-tutor-lock)::after { display: block; }
  /* THE FOOT IS WHERE YOU WRITE, as on the desk: the desk's own spacer
     (order 8, before Clear and the card) takes the free height, so the
     composer or the locked card rests at the foot of a short sheet, in the
     thumb zone, and keeps its own 22px above it on a full one. It was a
     margin-top: auto on the card, which resolved to 0 once the body
     overflowed: Clear sat half under the composer and the lock card touched
     the last example (audit N-008, 2026-09-25).
     ⚑ z-index 3, above the pinned Close (z-index 2, the rule below): on a
     landscape phone with the keyboard up the two can meet, and Send must
     stay pressable. */
  html.nb-tutor-open .nb-tutor-input { position: sticky; bottom: var(--nb-s3); z-index: 3; }
  /* ⚑ THE FIELD'S CAP FOLLOWS THE SHEET (audit N-G2, 2026-09-25). A fixed
     144px cap on a short sheet (a 320px phone zoomed 150%, 379px tall) made
     the composer 222 to 274px of a 292px body: 5 to 57px of transcript was
     left and Clear could not be reached. The cap is 18% of the height the
     sheet can use (the keyboard's share, --nb-tutor-kb, set on the sheet by
     tutor-sheet.js, taken off), and never more than the 144px it was. From
     800px of screen up it is 144px, as before; a landscape phone keeps its
     own two-line cap (rule 21, later, wins). */
  html.nb-tutor-open .nb-tutor-field { max-height: min(144px, calc((100dvh - var(--nb-tutor-kb, 0px)) * 0.18)); }
  html.nb-tutor-open .nb-tutor-body::before { content: ""; order: 8; flex: 1 0 0; }
  /* ⚑ CLOSE STAYS ON THE SHEET WHILE THE CONVERSATION SCROLLS (audit N-003,
     2026-09-25). The fold was in the head, inside the scrolling body, so a
     long conversation (or a landscape phone) scrolled the sheet's only close
     control away. It is pinned to .nb-chat (positioned, and outside the
     scroller) exactly where it stood in the head: 15px of binding plus the
     body's 30px top padding down, and the body's right padding plus its 1px
     border in (its negative margin still carries it into the gutter). The
     head keeps the fold's height and the room it took beside the chip
     (width + margin + the 12px gap), so nothing in the head moves.
     A ground of the sheet's own paper and grain, so a line scrolling under
     it never shows through and, at rest, it is not a disc on the paper. */
  html.nb-tutor-open .nb-tutor-head { min-height: 28px; padding-right: 34px; }
  html.nb-tutor-open .nb-tutor-fold {
    position: absolute;
    top: 45px;
    right: 27px;
    z-index: 2;
    background-color: var(--nb-tutor-paper);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.12 0 0 0 0 0.11 0 0 0 0 0.09 0 0 0 0.055 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23p)'/%3E%3C/svg%3E");
    border-radius: var(--nb-r-pill);
  }
  @media (hover: hover) {
    html.nb-tutor-open .nb-tutor-fold:hover { background-color: color-mix(in srgb, var(--nb-tutor-ink) 8%, var(--nb-tutor-paper)); }
  }
  html.nb-tutor-open body { overflow: hidden; }
  /* 19: the scrim reaches the top bar, which is inert under the sheet and
     must not look live. */
  html.nb-tutor-open .topbar::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 100;
    background: color-mix(in srgb, var(--nb-tutor-ink) 38%, transparent);
    pointer-events: none;
  }
  /* ⚑ BY NIGHT THE SCRIM IS THE NIGHT PAPER (audit N-021, 2026-09-25). The
     tutor ink is the same #20242F in both themes, so on the near-black desk
     the 38% ink scrim was as light as, or lighter than, what it covered: the
     topbar dimmed 2 to 6%, and the band above a phone's sheet got BRIGHTER.
     The inert page and bar looked live. By night both scrims dim toward the
     night paper instead, as the chapter drawer's backdrop does (tracing
     paper, never black glass): about a third darker, as the day scrim is. */
  html:not(.theme-light).nb-tutor-open .nb-tutor-margin::before,
  html:not(.theme-light).nb-tutor-open .topbar::before {
    background: color-mix(in srgb, var(--nb-paper) 60%, transparent);
  }
  /* The floating Chapters button sits above everything on a phone; under an
     open sheet it would be on the sheet's head. */
  html.nb-tutor-open .nb-rail-trigger { visibility: hidden; }
}


/* 20: a tablet's bottom sheet keeps a reading measure (it ran 1024px wide,
   about 130 characters a line). Phones keep the full width.
   ⚑ FROM 721px, AND NO TALLER THAN THE DESK'S SHEET (audit N-022,
   2026-09-25). The measure began at 861, so a portrait tablet (768, 820) and
   a landscape phone took a sheet the full screen wide, and 86% of a 1024 or
   1180 screen tall: 881 and 1015px of cream, 450 to 625px of it empty, a
   quarter to a half taller than the desk sheet the owner sized at 700px. Now
   every screen from 721px holds the 720px measure and at most that 700px;
   a keyboard, or a short screen, still takes the sheet lower (the same
   formula as 15b, and rule 21 below keeps a landscape phone's own height).
   Judgement call: DESIGN.md sets no tablet size, so the desk's 700px sheet
   and this rule's 720px measure are the reference. Phones (720 and below)
   are unchanged. */
@media (min-width: 721px) and (max-width: 1080px) {
  html.nb-tutor-open .nb-tutor-margin {
    max-width: 720px;
    margin-inline: auto;
    height: min(700px, 86dvh, calc(100dvh - var(--nb-tutor-kb, 0px) - var(--nb-chrome-h) - 8px));
  }
}
@media (min-width: 861px) and (max-width: 1080px) {
  /* the body's right padding is 30px here, not 26px */
  html.nb-tutor-open .nb-tutor-fold { right: 31px; }
}
/* A thumb-sized fold is 44px square and pulls 10px into the gutter, so the
   head keeps 44px of height and 44 - 10 + 12 = 46px beside the chip. */
@media (max-width: 1080px) and (pointer: coarse) {
  html.nb-tutor-open .nb-tutor-head { min-height: 44px; padding-right: 46px; }
}

/* 21: A LANDSCAPE PHONE WITH THE KEYBOARD UP (audit N-002, 2026-09-25). A
   390px-tall screen less a 215px keyboard, the 72px navbar and 8px left a
   95px sheet, and the two-row composer (122px) overflowed it upward: the
   field sat above the sheet's top edge, behind the navbar, and only the nib
   and Send showed. On a short screen only:
     · the sheet may take the navbar's band (inert and scrimmed under an open
       sheet anyway) and is never less than 220px while that fits. With no
       keyboard both formulas give today's sheet, so nothing moves at rest.
       Above the navbar (1000) its own scrim covers the bar, so the bar's
       second scrim is switched off here, or the band would be tinted twice.
     · the composer is ONE row, the field beside Send, capped at two lines
       (it scrolls inside itself after that), so it always fits the body.
     · the body's 30px top padding becomes the head's top margin: the same
       place for the head (and the pinned Close), but a sticky card may now
       rise to the paper's top edge. A scroller's padding is outside the
       room a sticky box is kept in, and with a 568 x 320 phone's keyboard up
       the card was pushed 25px out of the foot of the sheet.
   Judgement call: the nib is decorative and the navbar is inert, so both
   give way before the field does. Portrait phones, tablets and the desk
   never match (their heights are all over 500px). */
@media (max-width: 1080px) and (max-height: 500px) {
  html.nb-tutor-open .nb-tutor-margin {
    z-index: 1001;
    height: min(calc(100dvh - var(--nb-tutor-kb, 0px) - 8px), max(min(86dvh, calc(100dvh - var(--nb-tutor-kb, 0px) - var(--nb-chrome-h) - 8px)), 220px));
  }
  html.nb-tutor-open .topbar::before { display: none; }
  html.nb-tutor-open .nb-tutor-body { padding-top: 0; }
  html.nb-tutor-open .nb-tutor-head { margin-top: 30px; }
  html.nb-tutor-open .nb-tutor-field { flex: 2 1 0; max-height: 3em; }
  html.nb-tutor-open .nb-tutor-nib { display: none; }
}
/* 22: THE PILL ON A SHORT SCREEN (audit N-031b, 2026-09-25). On a landscape
   phone (844 x 390) the 48px pill and its 16px gap were a 64px band over the
   note at all times, a sixth of the screen. On a short screen only it is the
   touch floor, 44px, and sits 8px off the foot (the safe area still added):
   a 52px band. Its width, label and place in the middle are unchanged, and
   the open sheet (rule 21) does not move. The floating Chapters button on
   its baseline steps with it (notebook-page.css, the same query). Portrait
   phones, tablets and the desk never match (every one of their heights is
   over 500px). */
@media (max-width: 1080px) and (max-height: 500px) {
  .nb-tutor-tab {
    height: 44px;
    min-height: 44px;
    bottom: calc(var(--nb-s2) + env(safe-area-inset-bottom, 0px));
  }
}

@media (prefers-reduced-motion: reduce) {
  .nb-tutor-input, .nb-tutor-fold { transition: none; }
  .nb-tutor-log .chat-prose { animation: none; }
  html.nb-tutor-open .nb-tutor-sub,
  html.nb-tutor-open .nb-tutor-starters > *,
  .nb-tutor-input.is-ready .nb-tutor-send,
  .nb-chat.is-inflight .nb-tutor-act--stop,
  .nb-tutor-log .chat-turn > *,
  .nb-tutor-log .chat-turn > .nb-tutor-followups > *,
  .nb-tutor-tab--wait .nb-tutor-tab-dm { animation: none !important; }
  .nb-tutor-log .chat-turn > .chat-prose { -webkit-mask: none; mask: none; }
  .nb-tutor-input .nb-tutor-field, .nb-tutor-starter { transition: none; }
}

/* ==========================================================================
   16 . THE LOCKED SHEET (2026-09-24)
   ==========================================================================
   Owner: "the tutor should show for people not logged in or not premium but
   it is locked behind a premium", and "make it differentiate between who is
   signed in and who is premium". The same paper, head and intro line; then
   what the tutor answers, as plain text; then ONE card at the foot, in the
   composer's own place and shape, with a padlock, who you are and the way
   in (anonymous: Get Premium and Log in; free: signed in, Get Premium;
   lapsed: Renew). There is no field, no Send and no transport on a locked
   sheet (chat_panel.html, nb_resource_detail.html).
   ⚑ NO font, font-family OR font-weight IN THIS SECTION: the bold line is
   .nb-tutor-quiet-line and Get Premium wears .nb-tutor-send, so the weights
   are the ones those rules already carry and the typography census does
   not move. */
.nb-tutor-examples {
  order: 5;
  margin: 22px 0 0;
}
.nb-tutor-examples .nb-tutor-kick2 { margin-bottom: var(--nb-s3); }
.nb-tutor-example-list {
  display: flex;
  flex-direction: column;
  gap: var(--nb-s2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.nb-tutor-example {
  position: relative;
  padding-left: var(--nb-s5);
  font-size: var(--nb-text-sm);
  line-height: 1.45;
  color: var(--nb-tutor-ink);
  text-wrap: pretty;
}
/* A small open diamond, the sheet's own mark, for a list that is not a set
   of buttons: the working sheet's starters carry a chevron, which says
   "press", and these cannot be pressed. */
.nb-tutor-example::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 0.5em;
  width: 5px;
  height: 5px;
  box-sizing: border-box;
  border: 1.4px solid currentColor;
  transform: rotate(45deg);
}
/* The card: the composer's border, radius and lift, so it reads as the place
   you would write, locked. Relative on z-index 1 like the stuck composer, so
   the paper strip at the body's foot (the body's ::after) sits behind it. */
.nb-tutor-lock {
  order: 9;
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-start;
  gap: var(--nb-s3);
  margin: 22px 0 0;
  padding: 16px 16px 16px 14px;
  background-color: var(--nb-tutor-paper);
  border: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 22%, transparent);
  border-radius: var(--nb-r-lg);
  box-shadow: 0 6px 16px color-mix(in srgb, var(--nb-tutor-ink) 10%, transparent);
  color: var(--nb-tutor-ink);
}
.nb-tutor-lock-ic { flex: none; margin-top: 3px; }
.nb-tutor-lock-text { flex: 1 1 auto; min-width: 0; }
.nb-tutor-lock-sub {
  margin: var(--nb-s1) 0 0;
  font-size: var(--nb-text-sm);
  line-height: 1.5;
  color: var(--nb-tutor-ink);
  text-wrap: pretty;
}
.nb-tutor-lock-ways {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--nb-s3) var(--nb-s5);
  margin: 14px 0 0;
}
/* Get Premium wears Send's ink pill because it stands where Send stands on a
   Premium sheet. Only what an anchor brings is undone here: Send's place in
   the composer row (its order and its push to the right) and the underline. */
.nb-tutor-lock .nb-tutor-lock-go {
  order: 0;
  margin: 0;
  text-decoration: none;
}
.nb-tutor-lock .nb-tutor-quiet-way { margin-top: 0; }

/* ==========================================================================
   17 . THE THUMB BAR (Thumb Bar v2, Stage 2, 2026-09-27)
   ==========================================================================
   Under 861px on a page with the thumb bar, the tutor's phone form is the
   cream orb in the bar (its place, its cradle and its blue focus ring are
   notebook-thumbbar.css's; its colours are here, in the sheet's own tokens),
   and the phone sheet sits ON the bar instead of on the screen's foot. The
   pill (15b) is hidden there by the bar's sheet. Everything in this section
   is inside a max-width 860 query and keyed on the bar, so every width
   without one keeps the rules above, and their strings, as they are; the
   only rules outside a 860 query are the named sealed-page rules at the end.
   ⚑ NO font, font-family OR font-weight IN THIS SECTION (the pass rule). */
@media (max-width: 860px) {
  /* The orb: the sheet's cream and ink, ringed in the ink at 55% so the
     disc holds a 3:1 edge on the day bar. The same paper by night: paper
     does not change colour when the lamp changes. */
  #nb-tbar .nb-tutor-orb {
    background: var(--nb-tutor-paper);
    color: var(--nb-tutor-ink);
    border: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 55%, transparent);
  }
  #nb-tbar .nb-tutor-orb-lock {
    background: var(--nb-tutor-paper);
    color: var(--nb-tutor-ink);
    border: 1px solid color-mix(in srgb, var(--nb-tutor-ink) 55%, transparent);
  }
  /* SEPIA USE (the fourth use, the folded tab's wait mark, in its phone
     form): an answer is waiting, so the nib turns sepia and a sepia dot
     rides the orb's shoulder, as the diamond does on the desk's tab. */
  #nb-tbar .nb-tutor-orb.is-wait > .nb-tbar-icon--nib {
    color: var(--nb-tutor-accent);
  }
  #nb-tbar .nb-tutor-orb-wait {
    background: var(--nb-tutor-accent);
    border: 2px solid var(--nb-tutor-paper);
  }

  /* The phone sheet on the bar. Its foot is the bar's top edge
     (--nb-tb-h), or the keyboard's top while it is up (the bar steps away
     then, html.nb-kbd-open), and its height counts that foot, so its head
     stays clear of the 64px top bar: at 320 x 568 the sheet is 440px tall
     and its top is at 72px. The field's cap follows the same foot. */
  html.nb-tutor-open:has(> body > #nb-tbar) .nb-tutor-margin {
    bottom: var(--nb-tb-h, 0px);
    height: min(86dvh, calc(100dvh - var(--nb-tb-h, 0px) - var(--nb-chrome-h) - 8px));
  }
  html.nb-tutor-open.nb-kbd-open:has(> body > #nb-tbar) .nb-tutor-margin {
    bottom: var(--nb-tutor-kb, 0px);
    height: min(86dvh, calc(100dvh - var(--nb-tutor-kb, 0px) - var(--nb-chrome-h) - 8px));
  }
  html.nb-tutor-open:has(> body > #nb-tbar) .nb-tutor-margin:has(> .nb-chat > .nb-tutor-body > .nb-tutor-quiet) {
    height: auto;
    max-height: min(86dvh, calc(100dvh - var(--nb-tb-h, 0px) - var(--nb-chrome-h) - 8px));
  }
  html.nb-tutor-open.nb-kbd-open:has(> body > #nb-tbar) .nb-tutor-margin:has(> .nb-chat > .nb-tutor-body > .nb-tutor-quiet) {
    height: auto;
    max-height: min(86dvh, calc(100dvh - var(--nb-tutor-kb, 0px) - var(--nb-chrome-h) - 8px));
  }
  html.nb-tutor-open:has(> body > #nb-tbar) .nb-tutor-field {
    max-height: min(144px, calc((100dvh - var(--nb-tb-h, 0px)) * 0.18));
  }
  html.nb-tutor-open.nb-kbd-open:has(> body > #nb-tbar) .nb-tutor-field {
    max-height: min(144px, calc((100dvh - var(--nb-tutor-kb, 0px)) * 0.18));
  }
}
/* 721 to 860 (a portrait tablet): rule 20's measure and 700px, on the bar. */
@media (min-width: 721px) and (max-width: 860px) {
  html.nb-tutor-open:has(> body > #nb-tbar) .nb-tutor-margin {
    height: min(700px, 86dvh, calc(100dvh - var(--nb-tb-h, 0px) - var(--nb-chrome-h) - 8px));
  }
  html.nb-tutor-open.nb-kbd-open:has(> body > #nb-tbar) .nb-tutor-margin {
    height: min(700px, 86dvh, calc(100dvh - var(--nb-tutor-kb, 0px) - var(--nb-chrome-h) - 8px));
  }
}
/* 768 to 860: capped at 640px and centred, as Chapters and More are there
   (review TB2-PH-04); rule 20's margin-inline: auto centres it. The short
   landscape rule below (a side sheet) comes later and lifts the cap. */
@media (min-width: 768px) and (max-width: 860px) {
  html.nb-tutor-open:has(> body > #nb-tbar) .nb-tutor-margin {
    max-width: 640px;
  }
}
/* A short screen on the bar: rule 21's sheet (it may take the top bar's
   band, never under 220px) and its two-line field, over the same foot. */
@media (max-width: 860px) and (max-height: 500px) {
  html.nb-tutor-open:has(> body > #nb-tbar) .nb-tutor-margin {
    height: min(calc(100dvh - var(--nb-tb-h, 0px) - 8px), max(min(86dvh, calc(100dvh - var(--nb-tb-h, 0px) - var(--nb-chrome-h) - 8px)), 220px));
  }
  html.nb-tutor-open.nb-kbd-open:has(> body > #nb-tbar) .nb-tutor-margin {
    height: min(calc(100dvh - var(--nb-tutor-kb, 0px) - 8px), max(min(86dvh, calc(100dvh - var(--nb-tutor-kb, 0px) - var(--nb-chrome-h) - 8px)), 220px));
  }
  html.nb-tutor-open:has(> body > #nb-tbar) .nb-tutor-field,
  html.nb-tutor-open.nb-kbd-open:has(> body > #nb-tbar) .nb-tutor-field {
    max-height: 3em;
  }
}
/* Short landscape: the bar is a rail on the left, and the sheet comes in
   from the rail's side, the full height of the screen (the keyboard's top
   while it is up). Its punched edge stays on top: it is the same paper. */
@media (max-width: 860px) and (orientation: landscape) and (max-height: 500px) {
  html.nb-tutor-open:has(> body > #nb-tbar) .nb-tutor-margin,
  html.nb-tutor-open.nb-kbd-open:has(> body > #nb-tbar) .nb-tutor-margin {
    top: 0;
    right: auto;
    bottom: 0;
    left: var(--nb-tb-rail, 0px);
    width: min(400px, calc(100vw - var(--nb-tb-rail, 0px) - 48px));
    max-width: none;
    height: auto;
    max-height: none;
    margin: 0;
  }
  html.nb-tutor-open.nb-kbd-open:has(> body > #nb-tbar) .nb-tutor-margin {
    bottom: var(--nb-tutor-kb, 0px);
  }
  html.nb-tutor-open:has(> body > #nb-tbar) .nb-tutor-margin:has(> .nb-chat > .nb-tutor-body > .nb-tutor-quiet),
  html.nb-tutor-open.nb-kbd-open:has(> body > #nb-tbar) .nb-tutor-margin:has(> .nb-chat > .nb-tutor-body > .nb-tutor-quiet) {
    height: auto;
    max-height: none;
  }
}

/* THE SEALED PAGE AT THE DESK (Option B, the owner's pick; the Stage 0
   spike's corrected rules, guard/spike/report.json "final"). A fully
   paywalled chapter now renders the padlocked sheet, folded, with its
   bookmark tab (the pill from 861 to 1080px). With script and folded, the
   folded aside draws nothing at 1081px and up (notebook-page.css keeps the
   page's own two tracks), and it clips nothing at any width: the layout's
   overflow-x: clip above is there for a folded sheet's slide, and a sealed
   one has none, while the clip would cut the paywall card's shadow from
   861 to about 900px. Opened, S stops matching and everything above
   applies. Without script (no html.nb-tutor-js) the noscript dock stands.
   S = the sealed layout, (0,9,1): html.nb-tutor-js:not(.nb-tutor-open)
   :not(.note-focus-mode) .nb-page-layout:has(> .nb-tutor-margin):has(>
   .nb-notes-column > .nb-notes > .nb-paywall-wrap:first-child). */
html.nb-tutor-js:not(.nb-tutor-open):not(.note-focus-mode) .nb-page-layout:has(> .nb-tutor-margin):has(> .nb-notes-column > .nb-notes > .nb-paywall-wrap:first-child) {
  overflow-x: visible;
}
@media (min-width: 1081px) {
  html.nb-tutor-js:not(.nb-tutor-open):not(.note-focus-mode) .nb-page-layout:has(> .nb-tutor-margin):has(> .nb-notes-column > .nb-notes > .nb-paywall-wrap:first-child) > .nb-tutor-margin {
    display: none;
  }
}
/* The same from the FIRST PAINT, at every width (review G2-2): before
   tutor-sheet.js wires, a sealed page drew the folded aside (the desk's
   third track at 1081px and up; the locked sheet inline under the card
   below that, which then vanished, a layout shift of 0.12 at 820 x 1180).
   (scripting: enabled) is true from the first frame when script runs and
   false without it, where the noscript dock (1081 and up) and the inline
   sheet (1080 and below) still stand. Opened, the selector stops matching.
   The class-gated rules above stay for a browser without the feature. */
@media (scripting: enabled) {
  html:not(.nb-tutor-open):not(.note-focus-mode) .nb-page-layout:has(> .nb-tutor-margin):has(> .nb-notes-column > .nb-notes > .nb-paywall-wrap:first-child) {
    overflow-x: visible;
  }
  html:not(.nb-tutor-open):not(.note-focus-mode) .nb-page-layout:has(> .nb-tutor-margin):has(> .nb-notes-column > .nb-notes > .nb-paywall-wrap:first-child) > .nb-tutor-margin {
    display: none;
  }
}

/* Focus mode is for the note alone: no sheet, no tab. LAST IN THE FILE: the
   phone sheet's html.nb-tutor-open .nb-tutor-margin ties with it at (0,2,1),
   so source order decides. */
html.note-focus-mode .nb-tutor-margin,
html.note-focus-mode .nb-tutor-tab { display: none; }

