/* Components: the pieces the app draws over and over, named once.

   This is where a helper's look lives once the helper stops writing a style
   string. Room-specific styling does not belong here — that goes to the room's
   own sheet as each room is converted (STYLES-PLAN.md, Steps 3 onward). */

/* ---------- the monospace label ----------
   public/lib/helpers.js mono(). Structure here, values from the call site.

   THE INLINE EXCEPTION, WORKED. Two of these are genuinely per-call data: a
   label's letterspacing and its colour. They ride as scoped custom properties
   on the element rather than as declarations, which is the kind of inline
   CLAUDE.md admits — data-driven, documented at the helper that writes it.
   What is NOT per-call is the treatment itself, and that lives here.

   SIZE STOPPED BEING PER-CALL. It used to read --monotype-size, which is why
   324 call sites each named their own: 6, 6.5, 7, 7.5, 8, 8.5, 9 — and mono()
   clamps at 9, so 306 of them were the same 9px wearing eight different
   spellings. A label's size is a step of the scale, not a per-label decision.
   It reads the token now, and the band a call site asked for arrives through
   the modifier class below. mono() still writes --monotype-size and nothing
   reads it: retiring it would rewrite the markup of every screen in the app,
   which is a separate and much noisier commit than this one. */
.monotype{
  font-family:'IBM Plex Mono',monospace;
  font-size:var(--t-label);
  letter-spacing:var(--monotype-ls);
  color:var(--monotype-color);
}
/* ---------- the chip ----------
   public/lib/helpers.js chip(). The small pill that is either the chosen one or
   one of the others: same three properties either way, only the values change,
   and every value is already a token on the element. Nothing rides inline.
   .chip-on must stay below .chip — same weight, so order is what decides. */
.chip{
  border:var(--bd) solid var(--edge);
  background:var(--inset);
  color:var(--muted);
}
.chip-on{
  border-color:var(--accent);
  background:var(--accent);
  color:var(--onAccent);
}

/* The size band rides along as a modifier class — .monotype-9, .monotype-10-5,
   .monotype-13 and so on, the dot spelled as a dash. It is what carries a
   label's band now that size is not per-call: the bands are mapped onto steps
   of the scale at the foot of this file. It was born for a narrower reason,
   so the Council could resize a whole band by hooking a class instead of
   matching on a style string, and it outlived that job when the Council's
   private scale was deleted and the whole app took the same treatment. */

/* ---------- the light there ----------
   Four swatches previewing a destination's theme, at the door, on the mission
   board and in the scene. Each swatch carries the destination's theme NAME and
   reads that block's tokens, so the preview is the destination rather than a
   copy of it.

   --swatch-edge is the trick worth reading. The row is in YOUR theme and
   resolves --edge there; the swatches then change theme underneath it. A
   custom property inherits the value it resolved to, not the expression that
   produced it, so the border stays in the room you are standing in while the
   fill comes from the room you are going to. */
.swatches{display:flex;--swatch-edge:var(--edge);}
.swatches-lg{gap:4px;}
.swatches-sm,.swatches-xs{gap:3px;}
.swatch{border:var(--bd) solid var(--swatch-edge);}
.swatches-lg .swatch{width:22px;height:14px;border-radius:4px;}
.swatches-sm .swatch{width:9px;height:9px;border-radius:2px;}
.swatches-xs .swatch{width:8px;height:8px;border-radius:2px;}
.swatch-setA{background:var(--setA);}
.swatch-accent{background:var(--accent);}
.swatch-panel{background:var(--panel);}
.swatch-bg{background:var(--bg);}

/* ---------- the house prefs ----------
   The gear in the nav opens this sheet. Zoom and the look live here; sound
   does not. The look rows are built from tokens.css (themeRack), so this file
   styles a list whose length it does not know.

   The row swatches use the same standing-in trick as the door above: a row
   offering a theme carries that theme's name, so it previews the real block. */
.prefs{width:100%; max-width:400px;}
.prefs-head{display:flex; align-items:center; justify-content:space-between; gap:var(--s5);}
.prefs-block{display:flex; flex-direction:column; gap:var(--s3);}
.prefs-zoom{display:flex; gap:var(--s2);}
.prefs-zoom-step{flex:1; padding:var(--s4) var(--s3); border-radius:var(--r-sm); text-align:center; font-weight:var(--w-semi);}
/* the chair knobs: ten chips, so the row wraps and nobody gets crushed */
.prefs-minds{display:flex; flex-wrap:wrap; gap:var(--s2);}
.prefs-minds .prefs-zoom-step{flex:none;}
.prefs-looks{display:flex; flex-direction:column; gap:2px;}
.prefs-looks .theme-opt{width:100%;}
.theme-opt{display:flex; align-items:center; gap:10px; padding:8px 9px; border-radius:var(--r-sm); color:var(--ink3);}
.theme-opt:hover{background:var(--sel);}
.theme-opt.on{background:var(--sel); color:var(--accent);}

/* ---------- the nav bar and the stale-brief strip ----------
   public/rooms/partials.js. slot()'s font-size stays inline: it is the
   size of a letter in a hole, not a step of the type scale. */
.nav{position:sticky; top:0; z-index:6; display:flex; flex-wrap:wrap; align-items:center;
  gap:10px 18px; padding:14px 18px 12px; background:var(--chrome);
  backdrop-filter:blur(20px); border-bottom:var(--bd) solid var(--edge);}
.nav-tabs{display:flex; align-items:center; gap:2px; order:3; width:100%; overflow-x:auto;}
.nav-tab{flex:1; min-width:62px; display:flex; flex-direction:column; align-items:center;
  gap:5px; padding:8px 6px 7px; border-radius:var(--r-md); background:transparent; color:var(--muted);}
.nav-tab-on{background:var(--sel); color:var(--accent);}
.nav-tools{margin-left:auto; display:flex; align-items:center; gap:12px;}
.nav-dot{width:5px; height:5px; border-radius:var(--r-round); background:var(--accent);
  box-shadow:0 0 9px var(--accent);}
.stale{display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:10px 13px; border:var(--bd) dashed var(--accent); border-radius:var(--r-md);
  background:var(--sel);}

/* ---------- one button ----------

   Step 4 box 2. Every button in the app was hand-styled where it stood: 257 of
   them across 15 files, declaring border-radius 134 times in 23 flavours,
   border 110 times, padding in 40. Surveyed before this was written, and the
   variants below are the shapes that survey actually found:

     95  no inline style at all      -> .btn on its own
     94  1px border, no fill         -> .btn (that IS the house button)
     29  text only, no border, no bg -> .btn-quiet
     16  accent fill                 -> .btn-primary
     14  tinted fill plus a border   -> .btn-tinted
      8  everything else

   Sizes come from the same survey: square icon buttons clustered hard at 34px
   with 32 and 38 either side, so there is one icon size and two modifiers, not
   nine. Everything reads the kit tokens, so a change to the scale reaches every
   button rather than 257 style attributes.

   A room adopts these as it is converted (Step 5). Adding the classes changes
   nothing on its own.

   THE TYPE STEP WAS ONE LOW, AND THIS IS WHY — worth reading before trusting
   any other number that came out of a survey of written attributes.

   This button was surveyed BEFORE the one type scale landed, so the survey
   counted what the rooms had TYPED. A room that wrote `font-size:14px` did
   not render at 14: a remapper used to send a written 14 to --t-body, which
   is 15. The survey saw the string "14" everywhere and named
   --t-small, the token whose VALUE is 14. It read the attribute instead of the
   bridge, which is the one thing CLAUDE.md says never to do, and the kit ended
   up a step below the app it was measured from.

   Measured in the running app before this was changed: of the 417 sans-labelled
   buttons not yet wearing .btn, 183 render at 15px, 97 at 14, 49 at 18, 29 at
   13 and 17 at 16. Fifteen is the plurality, and inside the Council it is 158
   of 279. The mono-label buttons are not in this at all — mono() owns their
   size, and no mono button wears .btn anywhere in the app.

   So all three steps move up one, together, because all three were read off the
   same attributes. What that moved, named rather than discovered later:

     · the Council's four tabs, its two panel buttons and the dialog's Cancel
       and Yes go 14px to 15px, and a pixel taller. Those tabs are the exact
       labels slice one of the Council shrank from 15 to 13; they came back at
       14 when the standard landed and this is what puts them back.
     · .btn-lg reaches exactly two text buttons — the scene transport's
       Play/Pause and "Choose advisors from the Library" — 15px to 16px.
     · .btn-sm reaches NO text at all: every .btn-sm in the app is a
       .btn-icon.btn-sm square. Its size moves on paper only.

   line-height:1 is deliberately NOT touched. It is right for a button, whose
   box should be padding-driven rather than text-metric-driven. But it is the
   other half of adopting this class and a converting room must know it: 283 of
   those bare buttons sit at line-height 22.5, the page's 1.5, so .btn takes
   about seven pixels of height out of them. A room puts that back as padding in
   its OWN sheet, the way the Council put three declarations back on .field.
   A class must never change a size. */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--s2);
  padding:var(--s4) var(--s5); border:var(--bd) solid var(--edge); border-radius:var(--r-md);
  background:transparent; color:var(--muted); font-size:var(--t-body); line-height:1;
  white-space:nowrap; transition:color var(--mo-fast), background var(--mo-fast), border-color var(--mo-fast);
}
.btn:hover{color:var(--text); border-color:var(--ink4);}
.btn[disabled],.btn[aria-disabled="true"]{opacity:.5; cursor:default;}
.btn[disabled]:hover,.btn[aria-disabled="true"]:hover{color:var(--muted); border-color:var(--edge);}

/* the fills */
.btn-primary{background:var(--accent); border-color:var(--accent); color:var(--onAccent); font-weight:var(--w-semi);}
.btn-primary:hover{color:var(--onAccent); border-color:var(--accent); filter:brightness(1.08);}
.btn-tinted{background:var(--sel);}
.btn-panel{background:var(--panel);}
.btn-inset{background:var(--inset);}
/* a text weight, not a fill: the confirm in a dialog is tinted like its
   neighbours and speaks at full strength. */
.btn-strong{color:var(--text); font-weight:var(--w-semi);}
.btn-strong:hover{color:var(--text);}
.btn-quiet{border-color:transparent; background:transparent;}
.btn-quiet:hover{border-color:transparent; background:var(--sel); color:var(--text);}

/* the states a button carries itself, rather than a room deciding in a string.
   btn-on is the app's existing on-state to the letter: accent edge, accent
   label, no fill. A room that wants a fill as well adds btn-tinted. */
.btn-on{border-color:var(--accent); color:var(--accent);}
.btn-on:hover{border-color:var(--accent); color:var(--accent);}
.btn-danger{color:var(--accent);}
/* a dashed edge always means the same thing in this app: an empty state or a
   place something can be dropped */
.btn-dashed{border-style:dashed;}
/* for a compound label whose parts read as one word, like the zoom button's
   A and its +1 */
.btn-tight{gap:1px;}

/* size first, then shape: an icon button is a fixed square and must be able to
   drop the padding a size modifier gives it, which only works if it is
   declared after. */
.btn-sm{padding:var(--s2) var(--s4); font-size:var(--t-small);}
.btn-lg{padding:var(--s5) var(--s6); font-size:var(--t-lead);}
.btn-icon{width:34px; height:34px; padding:0; flex:none;}
.btn-icon.btn-sm{width:32px; height:32px;}
.btn-icon.btn-lg{width:38px; height:38px;}
.btn-round{border-radius:var(--r-round);}
.btn-pill{border-radius:var(--r-pill);}
.btn-wide{width:100%;}
.btn-stack{flex-direction:column; gap:var(--s1);}

/* ---------- one panel ----------

   Step 4 box 3. Surveyed like the buttons: 144 surfaces across the rooms,
   declaring border 17 ways, radius 17 ways, padding 36 ways. The shapes the
   survey found are the variants, in the order it found them:

     39  a --panel fill with a 1px edge   -> .panel
     27  an edge and no fill              -> .panel-bare
     25  a --inset fill with an edge      -> .panel-inset
     15  a dashed edge, always an empty state or a drop target -> .panel-empty

   Radius follows the same clustering: 13, 14 and 15 are one thing (--r-lg) and
   17 and 20 are the bigger thing a modal wants (--r-xl, on .panel-lg). */
.panel{padding:var(--s7); border:var(--bd) solid var(--edge); border-radius:var(--r-lg); background:var(--panel);}
.panel-inset{background:var(--inset);}
.panel-bare{background:transparent;}
.panel-empty{background:transparent; border-style:dashed; color:var(--muted);}
.panel-lg{padding:var(--s8); border-radius:var(--r-xl);}
.panel-sm{padding:var(--s5); border-radius:var(--r-md);}
/* depth is a separate decision from surface: most panels sit flat on the page */
.panel-raised{box-shadow:var(--sh-card);}
.panel-float{box-shadow:var(--sh-modal);}
.panel-col{display:flex; flex-direction:column; gap:var(--s5);}

/* ---------- one input ----------

   96 of them: 45 input, 34 textarea, 17 select, and 87 carried an inline style.
   They agreed on more than the buttons did — 50 of 64 borders were already
   1px solid var(--edge), 40 of 62 fills were --inset — so this is mostly
   writing down what they already were. Radius 10, 11 and 12 become --r-md.

   The reading sizes are the one real split: a one-line field sits at body size
   and a textarea people write paragraphs into sat at 15, so that is .field-lg
   rather than a second class. */
.field{width:100%; padding:var(--s4) var(--s5); border:var(--bd) solid var(--edge); border-radius:var(--r-md);
  background:var(--inset); color:var(--ink2); font-size:var(--t-body); line-height:var(--lh-body);
  transition:border-color var(--mo-fast);}
.field:focus{border-color:var(--accent); color:var(--text);}
.field::placeholder{color:var(--muted); opacity:.6; font-style:italic;}
.field-lg{font-size:var(--t-lead);}
.field-sm{padding:var(--s2) var(--s4); font-size:var(--t-small);}
.field-flat{background:transparent;}
.field-tall{min-height:96px; resize:vertical;}

/* ---------- one type scale ----------

   The sizes are tokens; these are the roles that use them, so a room says what
   a line IS rather than how big it is. The mono label is not here: mono() in
   lib/helpers.js already owns that treatment and the Council resizes by its
   band. */
.txt-room{font-size:var(--t-room); line-height:1.04; letter-spacing:var(--ls-head); font-weight:var(--w-semi);}
.txt-display{font-size:var(--t-display); line-height:1.1; letter-spacing:var(--ls-head); font-weight:var(--w-semi);}
.txt-head{font-size:var(--t-head);}
.txt-title{font-size:var(--t-title); letter-spacing:var(--ls-head); font-weight:var(--w-semi);}
.txt-sub{font-size:var(--t-sub);}
.txt-lead{font-size:var(--t-lead);}
.txt-body{font-size:var(--t-body); line-height:var(--lh-body);}
.txt-small{font-size:var(--t-small); line-height:var(--lh-body);}
.txt-read{font-family:Georgia,serif; font-size:var(--t-sub); line-height:var(--lh-read);}
/* The one display voice for a house room's primary title and its authored
   section heads. Both are opt-in: card names, projects, tools and dialogs keep
   the working face. The house-stage scope makes these rules stronger than a
   room's legacy title weight without !important or another font declaration. */
.house-stage .house-room-title,.house-stage .council-title{font-family:var(--font-trajan);font-weight:var(--w-med);letter-spacing:.015em;}
.house-stage .house-section-title{font-family:var(--font-trajan);font-weight:var(--w-med);letter-spacing:.015em;}
/* weight, colour and measure as modifiers, so they compose with any of the above.
   Size and weight are separate on purpose: the app has 15px headings and 15px
   body, and folding the two together is what forces a room back into a style
   string when it wants one without the other. */
.txt-strong{font-weight:var(--w-semi);}
.txt-bold{font-weight:var(--w-bold);}
.txt-muted{color:var(--muted);}
.txt-dim{color:var(--ink3);}
.txt-accent{color:var(--accent);}
.txt-measure{max-width:62ch;}
.txt-clip{white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}

/* ---------- mono size bands ----------
   A call site names a band. The token is the size. ORDER IS LOAD-BEARING:
   .monotype-10 must beat the bare .monotype rule above it. */
.monotype-10,.monotype-10-5,.monotype-11,.monotype-12-5{font-size:var(--t-tiny);}
.monotype-13{font-size:var(--t-small);}
.monotype-14{font-size:var(--t-body);}
.monotype-15{font-size:var(--t-lead);}

/* ---------- the house dialog ----------
   lib/dialog.js draws it, rooms/modals/dialog.js is its markup. It is a
   component rather than a room, so its shape lives here and it is built out of
   the panel and the type roles above rather than beside them.

   .modal-scrim is the shape nine other modals already draw by hand. They adopt
   it as their rooms are converted; naming it here is what lets them. */
/* The modal law (Mark, 2026-08-15): a modal is a floating window, not a veil.
   The scrim paints nothing and passes pointers through — the page stays live
   behind it — while its child, the window itself, catches its own. Every
   window is draggable (lib/drag.js) and closes only on its cross. */
.modal-scrim{position:fixed; inset:0; z-index:95; display:flex; align-items:center; justify-content:center;
  padding:var(--s7); pointer-events:none; animation:fadeUp .25s ease both;}
/* THE ENTRANCE PLAYS ONCE, the house pattern (door.css, cellar.css, every
   room): the renderer rebuilds the whole tree on every state write, so an
   ungated entrance replays on every click while the window is open, and a
   window full of instant controls strobes under the hand (Mark, 2026-08-22:
   "every time i click the modal blinks"). This was the one entrance in the
   app without the gate. */
[data-render-phase="update"] .modal-scrim{animation:none!important;}
.modal-scrim>*{pointer-events:auto;}
.dialog{width:100%; max-width:440px;}
.dialog-head{display:flex; align-items:flex-start; justify-content:space-between; gap:var(--s5);}
.dialog-label{display:flex; flex-direction:column; gap:var(--s1); min-width:0;}
.dialog-kicker{display:flex; align-items:center; gap:6px;}
.dialog-kicker svg{display:block;}
.dialog-title{font-family:Georgia,serif; font-size:var(--t-head); letter-spacing:-.01em;}
.voice-gem{display:inline-flex; align-items:center; color:var(--accent);}
.dialog-body{font-size:var(--t-lead); line-height:var(--lh-read); color:var(--ink2); white-space:pre-wrap;}
/* pick()'s own rows (2026-08-23): a plain .btn reads as one centred word,
   which is wrong for a name — left-aligned and full width, one per line, so
   a list of people reads like a list rather than a row of buttons. */
.dialog-options{display:flex; flex-direction:column; gap:var(--s2);}
.dialog-option{justify-content:flex-start; text-align:left;}
.dialog-actions{display:flex; justify-content:flex-end; gap:var(--s3);}

/* ---------- the listening window ----------
   rooms/modals/listen.js draws it; actions/listen.js is its behaviour. The
   same floating panel as the dialog, holding one <audio> (carried across
   repaints by data-keep) and the recording's chapters as a scrollable rail. */
.listen{width:100%; max-width:520px; position:relative; overflow:hidden;}
/* the card's own painting behind the window, under a veil of the panel's own
   colour — a layer over the picture, never a filter on it. Everything else in
   the window rides above the pair. */
.listen>*{position:relative; z-index:2;}
.listen-art{position:absolute; inset:0; z-index:0;}
.listen-art img{width:100%; height:100%; object-fit:cover; display:block;}
.listen-veil{position:absolute; inset:0; z-index:1; background:var(--panel); opacity:.86;}
.listen-head{display:flex; align-items:flex-start; justify-content:space-between; gap:var(--s5);}
.listen-name{display:flex; flex-direction:column; gap:var(--s1); min-width:0;}
.listen-title{font-family:Georgia,serif; font-size:var(--t-head); letter-spacing:-.01em;}
.listen-sub{font-size:var(--t-label); color:var(--muted); letter-spacing:.04em;}
.listen-line{font-size:var(--t-body); color:var(--ink2);}
/* the browser draws the player's own chrome; told dark, it stops being the
   white bar that poked a hole in Mark's eye (2026-08-22) */
.listen-player audio{width:100%; display:block; color-scheme:dark; border-radius:var(--r-md);}
/* THE SKIPS, under the player (Mark, 2026-08-24: an audiobook needs the
   controls a normal media player has). Play, pause and the scrub bar are the
   browser's own directly above; this row adds only what that bar has no
   button for. Four even columns rather than a flex row so the numbers line up
   as a rack, which is how a transport reads at a glance. */
.listen-skips{display:grid; grid-template-columns:repeat(4, 1fr); gap:var(--s2); margin-top:var(--s2);}
.listen-skip{display:inline-flex; align-items:center; justify-content:center; gap:4px;
  padding:var(--s2) 0; border:var(--bd) solid var(--edge); border-radius:var(--r-md);
  background:none; color:var(--muted); font-size:var(--t-label); cursor:pointer;
  transition:background var(--mo-fast), color var(--mo-fast), border-color var(--mo-fast);}
.listen-skip:hover{background:var(--sel); color:var(--accent); border-color:var(--accent);}
.listen-skip svg{flex:none;}
.listen-skip-n{font-variant-numeric:tabular-nums; line-height:1;}
.listen-kept{font-size:var(--t-label); color:var(--muted); letter-spacing:.03em;}
.listen-keep-row{display:flex; align-items:center; gap:var(--s4);}
.listen-keep-line{font-size:var(--t-label); color:var(--muted); flex:1; min-width:0;}
/* the shared button is full-width on a card, and its rule sits LATER in this
   sheet, so a bare .listen-keep-go loses the cascade and the line beside it is
   squeezed to one word a line (shipped 2026-08-22, caught by Mark's eye). The
   two-class rule outranks it wherever either rule lives. */
.listen-keep-row .work-button.listen-keep-go{flex:none; width:auto;}
.listen-parts{display:flex; flex-direction:column; gap:2px; max-height:300px; overflow-y:auto;}
.listen-part{display:flex; align-items:baseline; justify-content:space-between; gap:var(--s4);
  padding:var(--s2) var(--s4); text-align:left; border-radius:var(--r-md); color:var(--muted);
  transition:background var(--mo-fast), color var(--mo-fast);}
.listen-part:hover{background:var(--sel);}
.listen-part.is-on{background:var(--sel); color:var(--accent);}
.listen-part-name{font-size:var(--t-small); min-width:0; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap;}
.listen-part-time{font-family:'IBM Plex Mono',monospace; font-size:var(--t-label); flex:none;}

/* ---------- the side nav ----------
   Sections down the left, the open one filling the rest. rooms/partials.js
   sideNav() builds the rail; a caller wraps the pair in .sidenav-page.

   It carried the Council's name until 2026-08-20 (`[data-council-settings]`,
   `.assistant-settings-nav`, `.settings-nav-item`) because that is the room it
   was born in. The Time Machine wanted the same shape, and the choice was a
   second spelling of one pattern or a rename. This is the rename.

   An item's second line is what it is currently set to, which is the whole
   argument for a rail over a row of tabs: a long form reads at a glance
   without opening any of it. */
.sidenav-page{display:grid; grid-template-columns:172px minmax(0,1fr);
  gap:var(--s7); align-items:start;}
.sidenav{display:flex; flex-direction:column; gap:var(--s1);}
.sidenav-item{display:flex; flex-direction:column; gap:1px; padding:var(--s4) var(--s5);
  text-align:left; border-radius:var(--r-md); color:var(--muted);
  transition:background var(--mo-fast), color var(--mo-fast);}
.sidenav-item:hover{background:var(--sel);}
.sidenav-item-on{background:var(--sel); color:var(--accent);}
.sidenav-item-name{font-size:var(--t-body); font-weight:var(--w-semi);}
/* the setting truncates rather than wrapping: a glance that becomes two lines
   is a paragraph, and the rail stops being scannable */
.sidenav-item-set{font-family:'IBM Plex Mono',monospace; font-size:var(--t-label);
  letter-spacing:.06em; color:var(--muted);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.sidenav-back{margin-bottom:var(--s1); padding:var(--s4) var(--s5); text-align:left;
  border:var(--bd) solid var(--edge); border-radius:var(--r-md); color:var(--muted);}

@media (max-width:1100px){
  .sidenav-page{grid-template-columns:1fr;}
  .sidenav{flex-direction:row; overflow-x:auto; gap:var(--s2); padding-bottom:2px;}
  .sidenav-item{flex:none;}
  .sidenav-item-set{display:none;}
}

/* ---------- THE ASSISTANT'S CONTROL BAR ----------
   Under the box, because the box is at the top: talk to her, choose whether
   she answers aloud, and clear the conversation. Clear empties the TRANSCRIPT
   and never her memory — memory is wiped on purpose, with warnings, from her
   own card (Story System/THE-ASSISTANT.md §5). */
.house-ann-bar{display:flex;align-items:center;gap:var(--s2);padding:0 var(--ann-pad) var(--ann-gap);}
.house-ann-bar-gap{flex:1;}
.house-ann-btn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;
  padding:0;border:var(--bd) solid var(--edge);border-radius:var(--r-round);background:var(--inset);
  color:var(--muted);cursor:pointer;}
.house-ann-btn:hover{color:var(--ink);border-color:var(--accent-line);}
.house-ann-btn.on{color:var(--accent);border-color:var(--accent);}
.house-ann-clear{padding:3px 9px;border:var(--bd) solid var(--edge);border-radius:var(--r-pill);
  background:none;color:var(--muted);font:inherit;font-size:var(--t-tiny);cursor:pointer;}
.house-ann-clear:hover{color:var(--ink);border-color:var(--accent-line);}
/* THE DIG CHIP (plans/ANN-MEMORY.md; the lane brief, 2026-08-23): one quiet
   press on her latest answer when deeper memory is on offer. .house-ann-clear's
   own dress — same pill, same rest state — but on its OWN line under the
   words it follows rather than crowding them, and in the accent because it
   is an offer rather than a housekeeping verb. No price named on the chip
   itself: nothing in this build can honestly name what a dig costs yet. */
.house-ann-dig{display:block;margin-top:6px;padding:3px 9px;border:var(--bd) solid var(--edge);
  border-radius:var(--r-pill);background:none;color:var(--accent);font:inherit;font-size:var(--t-tiny);cursor:pointer;}
.house-ann-dig:hover{color:var(--ink);border-color:var(--accent-line);background:var(--sel);}
/* THE MESSAGES NEED AIR (Mark, 2026-08-21). They were a stack of lines with
   nothing between them and the panel edge, which reads as a wall of text
   rather than as a conversation.

   THE AIR IS INSIDE THE BUBBLES, NOT IN THE PANEL'S EDGE (2026-08-31). This
   block used to widen the log's own inset to 12 to get it, which pushed the
   messages two pixels off the left edge every other row in the panel sits on
   — the misalignment Mark was looking at. The gap between bubbles and the
   padding within them do the same job without moving the column. */
.house-ann-top .house-ann-log{gap:var(--s4);padding-bottom:var(--s6);}
.house-ann-top .house-ann-her{padding:var(--s3) var(--s4);border-radius:12px;background:var(--inset);}
.house-ann-top .house-ann-you{padding:var(--s3) var(--s4);}

/* ---------- THE WORK CARD ----------
   One card for every room that shows a work (rooms/partials.js: workCard).
   It moved here out of marketplace.css the day MY LIBRARY became its own room
   and a second screen needed to draw it: a fragment two rooms share is a
   component, and a component's look has one home.

   The grid is here for the same reason. Both shelves lay their cards out the
   same way and always should. */
.work-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;}
.work-card{min-width:0;padding:18px;border:var(--bd) solid var(--edge);border-radius:var(--r-md);
  background:var(--panel);box-shadow:var(--sh-card);
  display:flex;flex-direction:column;gap:12px;}
.work-card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;}
.work-card-head{min-width:0;}
.work-title{margin:0;color:var(--ink);font-size:var(--t-title);font-weight:var(--w-semi);}
.work-by{margin-top:3px;color:var(--muted);font-size:var(--t-small);}
/* THE BADGE IS QUIET TEXT, not a pill. Mark's mockups show a small grey word
   in the corner and he put them in front of me four times before I looked
   properly. An accent-bordered pill was fine on a plain card and reads as a
   button over a photograph, which is the last thing it is. */
.work-badge{flex:none;color:var(--muted);font-size:var(--t-small);font-weight:var(--w-med);}
/* The blurb takes the slack so every button in a row sits on one line. */
.work-blurb{flex:1;margin:0;color:var(--ink2);font-size:var(--t-body);line-height:1.5;}
.work-note{color:var(--muted);font-size:var(--t-tiny);}
.work-line{color:var(--ink2);font-size:var(--t-small);font-weight:var(--w-med);}
.work-button{width:100%;padding:9px 12px;border:var(--bd) solid var(--accent-line);border-radius:var(--r-md);
  background:var(--inset);color:var(--accent);font:inherit;font-size:var(--t-small);
  font-weight:var(--w-semi);cursor:pointer;}
.work-button:hover{border-color:var(--accent);color:var(--ink);}
.work-button[disabled]{opacity:.55;cursor:default;}
/* Two verbs on one card share a row (the music rack's Play and + Playlist);
   the second reads quieter so the first stays the card's act. */
.work-actions{display:flex;gap:8px;}
.work-actions .work-button{flex:1;min-width:0;}
.work-actions .work-button-2{color:var(--ink2);border-color:var(--edge);}
.work-actions .work-button-2:hover{border-color:var(--accent);color:var(--ink);}
/* A refusal printed on the card that was pressed. No colour of its own: the
   house has no failure token and inventing one here would be a theme decision
   made in the wrong file. */
.work-said{margin:0;padding:8px 12px;border-left:2px solid var(--accent-line);
  background:var(--inset);border-radius:var(--r-sm);color:var(--ink2);font-size:var(--t-small);}

/* ---------- THE COMIC CARD ----------
   The cover is the work, not mood art behind the work. It therefore sits
   untouched in its own frame with the blurb below it. No scrim, filter,
   blend mode or crop is allowed between the reader and the original art, and
   every clause of that still holds: the cover above is uncropped, unscrimmed,
   unfiltered and on top.

   WHAT THE SENTENCE DID NOT KNOW ABOUT is a SECOND, decorative instance of the
   same picture, bleeding down behind the card's own words so its colours glow
   through the glass under them (Mark, 2026-08-31: the comic cards go glass).
   That copy is dressing rather than the work, and the work is still the crisp
   one nothing comes between you and.

   AND THE THEME LAW DOES NOT BEND FOR IT. "No filter, blend mode, or hue on
   art, ever" forbids putting a filter ON a picture. Nothing here does: the
   bleed carries no filter at all, and the frosting is backdrop-filter on the
   PANE in front of it, which is what frosted glass is and what .cf-create in
   comic-forge.css already does over a wall. The picture is untouched and the
   glass does the blurring. */
.comic-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--s5);}
.comic-card{position:relative;min-width:0;overflow:hidden;border:var(--bd) solid var(--edge);
  border-radius:var(--r-md);background:var(--panel);box-shadow:var(--sh-card);
  display:flex;flex-direction:column;}
/* The bleed sits under everything. There is no rule hiding it on a card with
   the glass off, because such a card does not draw one: rooms/partials.js
   leaves it out rather than the stylesheet folding it away. */
.comic-bleed{position:absolute;inset:0;z-index:0;overflow:hidden;}
.comic-bleed img{display:block;width:100%;height:100%;object-fit:cover;}
.comic-cover,.comic-card-copy{position:relative;z-index:1;}
.comic-cover{aspect-ratio:16/10;display:grid;place-items:center;overflow:hidden;
  border-bottom:var(--bd) solid var(--edge);background:var(--inset);}
.comic-cover img{display:block;width:100%;height:100%;object-fit:contain;}
.comic-cover-type{width:100%;height:100%;display:flex;flex-direction:column;justify-content:flex-end;
  gap:var(--s3);padding:var(--s7);background:var(--inset);border-bottom:var(--bd) solid var(--edge);}
.comic-cover-title{font-family:var(--font-trajan);font-size:var(--t-display);line-height:1.05;
  color:var(--ink);letter-spacing:var(--ls-head);}
.comic-cover-by{color:var(--muted);font-size:var(--t-small);}
.comic-card-copy{flex:1;display:flex;flex-direction:column;gap:var(--s3);padding:var(--s5);}
.comic-card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s4);}
.comic-card-head{min-width:0;}
.comic-title{margin:0;color:var(--ink);font-size:var(--t-title);font-weight:var(--w-semi);}
.comic-by{margin-top:var(--s1);color:var(--muted);font-size:var(--t-small);}
.comic-badge{flex:none;color:var(--muted);font-size:var(--t-small);font-weight:var(--w-med);}
.comic-blurb{flex:1;margin:0;color:var(--ink2);font-size:var(--t-body);line-height:var(--lh-read);}
.comic-note{color:var(--muted);font-size:var(--t-tiny);}
.comic-line{color:var(--ink2);font-size:var(--t-small);font-weight:var(--w-med);}

/* THE GLASS ITSELF. The two numbers are on the document root, worn once per
   render by lib/render.js beside the wall's own properties, so nothing here is
   a step table and nothing in the markup is a number: the census stays at zero
   without the room doing arithmetic. The fallbacks are the record's own
   defaults, so a stylesheet loaded before the first render still frosts.

   ONE PREFERENCE, EVERY CARD. The properties are on the root rather than the
   card, so per-card variation is impossible by construction, which is what the
   design asked for.

   backdrop-filter is on the PANE and never on the picture. The theme law is
   intact and the comment above says why. */
.comic-lit .comic-card-copy{
  background:color-mix(in srgb, var(--panel) var(--comic-frost, 62%), transparent);
  backdrop-filter:blur(var(--comic-blur, 12px));
  -webkit-backdrop-filter:blur(var(--comic-blur, 12px));}

@media (max-width:620px){
  .comic-grid{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));}
}

/* ---------- THE BROWSE BAR ----------
   Search, then a rail of tabs (rooms/partials.js: browseBar). Shared from the
   first day, because The Stacks needs it now and MY LIBRARY needs it the
   moment somebody owns more than a screenful.

   THE SAME SHAPE AS THE COMMUNITY'S TAB RAIL on purpose: an inline-flex pill
   that hugs its own buttons, scrolls sideways rather than wrapping, and marks
   the live one with the raised panel. Community draws it as `.apt-feed-tabs
   .seg-btn`, which is scoped inside that room's sheet and cannot be reached
   from here. They are two homes for one shape and they want lifting into one
   when somebody does the chrome pass; matching it exactly is how that stays a
   tidy-up later instead of a redesign. */
.browse{display:flex;flex-direction:column;gap:12px;}
.browse-tools{display:flex;align-items:stretch;gap:10px;flex-wrap:wrap;}
.browse-find{flex:0 1 470px;min-width:240px;max-width:100%;padding:11px 14px;border:var(--bd) solid var(--edge);border-radius:var(--r-pill);
  background:var(--inset);color:var(--ink);font:inherit;font-size:var(--t-body);}
.browse-find::placeholder{color:var(--muted);}
.browse-find:focus{outline:none;border-color:var(--accent);}
/* THE FILTER IS A HOUSE MENU, NOT A NATIVE SELECT (Mark: "fix this ugly chrome
   in the pull down"). partials.js's browseFilterMenu() draws a button styled
   as the old closed chip and a floating list shaped like Titan's pickers
   (.titan-pick-row, styles/titan.css), reached here not duplicated. */
.browse-filter{position:relative;min-width:184px;}
.browse-filter-btn{width:100%;min-width:0;height:100%;min-height:43px;display:flex;align-items:center;gap:7px;padding:0 12px;border:var(--bd) solid var(--edge);border-radius:var(--r-pill);background:var(--inset);color:var(--ink);font:inherit;cursor:pointer;text-align:left;}
.browse-filter-btn:hover{border-color:var(--accent-line);}
.browse-filter-btn:focus-visible{outline:none;border-color:var(--accent);}
.browse-filter-open .browse-filter-btn{border-color:var(--accent);}
.browse-filter-label{flex:none;color:var(--muted);font-size:var(--t-tiny);font-weight:var(--w-semi);}
.browse-filter-value{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink);font-size:var(--t-small);font-weight:var(--w-semi);}
.browse-filter-chevron{flex:none;color:var(--muted);}
.browse-filter-open .browse-filter-chevron{color:var(--accent);transform:rotate(180deg);}
.browse-filter-list{position:absolute;z-index:30;top:calc(100% + 6px);left:0;min-width:100%;max-height:280px;overflow-y:auto;border:var(--bd) solid var(--edge);border-radius:var(--r-md);background:var(--panel);box-shadow:var(--sh-card);}
.browse-filter-row{width:100%;box-sizing:border-box;border:0;background:transparent;text-align:left;}
.browse-applied{display:flex;align-items:center;gap:7px;min-height:27px;}
.browse-chip{display:inline-flex;align-items:center;gap:7px;padding:5px 7px 5px 11px;
  border:var(--bd) solid var(--accent-line);border-radius:var(--r-pill);background:var(--sel);
  color:var(--accent);font-size:var(--t-tiny);font-weight:var(--w-semi);}
.browse-chip-clear{display:grid;place-items:center;width:18px;height:18px;padding:0;border:0;
  border-radius:var(--r-pill);background:transparent;color:inherit;font:inherit;cursor:pointer;}
.browse-chip-clear:hover{background:var(--inset);color:var(--ink);}
/* INLINE-FLEX so the rail is the width of its tabs and not the width of the
   room, and NOWRAP with a sideways scroll so seven tabs on a narrow screen
   slide instead of stacking into a block. */
/* THE RAIL WEARS THE DIRECTOR'S TAB (Mark, 2026-08-22: "im fond of this tab
   and its style. lets apply it here and also give the number a little
   padding"). The Story Forge's room tabs are the shape he likes: a panel
   container with room to breathe, and the live tab a filled pill rather than
   an outlined oval. The values are `.dir-tabs` / `.dir-tab` / `.dir-tab-on`
   read off director.css and written in the same tokens, because those classes
   are that room's own and a shared rail may not reach into a room's sheet.
   NOWRAP AND THE SIDEWAYS SCROLL STAY. The Director wraps, and wrapping here
   would stack seven tabs into a block on a phone — the one screen where this
   rail already hurts. Same look, same scroll. */
.browse-tabs{display:inline-flex;flex-wrap:nowrap;align-self:flex-start;max-width:100%;
  overflow-x:auto;background:var(--panel);border:var(--bd) solid var(--edge);
  border-radius:var(--r-lg);padding:5px;gap:6px;scrollbar-width:none;}
.browse-tabs::-webkit-scrollbar{display:none;}
/* The Director's tab is flex with a gap because it carries an ICON. This one
   carries a label and a number, so the spacing is the count's own padding and
   nothing else — no icons here without Mark picking them. */
.browse-tab{white-space:nowrap;flex:0 0 auto;
  border:0;background:transparent;border-radius:var(--r-md);
  padding:9px 15px;font:inherit;font-size:var(--t-small);font-weight:var(--w-semi);
  color:var(--muted);cursor:pointer;}
.browse-tab:hover{color:var(--ink);}
.browse-tab.on{background:var(--sel);color:var(--accent);}
/* and the count stops crowding its label */
.browse-count{margin-left:1px;padding:0 3px;color:var(--muted);font-weight:var(--w-med);}
.browse-tab.on .browse-count{color:var(--accent);}
/* A shelf that narrowed to nothing says so in words. It is never a blank
   grid: an empty tab and a broken room look identical when neither speaks. */
.browse-empty{padding:26px 22px;border:var(--bd) solid var(--edge);border-radius:var(--r-md);
  background:var(--panel);box-shadow:var(--sh-card);color:var(--ink2);font-size:var(--t-body);}
.browse-empty-sub{margin:6px 0 0;color:var(--muted);font-size:var(--t-small);}
@media (max-width:620px){
  .browse-tools{align-items:stretch;}
  .browse-find{flex:1 1 100%;min-width:0;}
  .browse-filter{min-height:42px;}
}

/* ---------- THE PICTURE BEHIND THE CARD ----------
   Mark, 2026-08-21: "we need to insert an image ... to go in behind."

   The card becomes the frame and the art fills it. Everything already on the
   card lifts above it on z-index, so the markup did not have to be rearranged
   around the picture; only the picture had to go underneath.

   THE SCRIM IS A LAYER, NOT A FILTER. CLAUDE.md's theme law: a theme dresses
   the frame around a picture and never touches the picture — no filter, no
   blend mode, no hue on art, ever. A gradient painted OVER the art is a
   different act from tinting the art, and it is the only thing keeping a title
   readable over a bright sky. Never reach for filter: brightness() here.

   Every colour on a lit card comes from the --on-art / --art-veil family in
   tokens.css, which is --mark's family: a lit card is dark whatever the app is
   wearing, so the theme's own ink would go black on black in Day. */
.work-lit{position:relative;overflow:hidden;isolation:isolate;border-color:transparent;}
.work-lit>*{position:relative;z-index:1;}
.work-art{position:absolute;inset:0;z-index:0;}
.work-art img{width:100%;height:100%;object-fit:cover;display:block;}
.work-scrim{position:absolute;inset:0;background:var(--art-foot),var(--art-scrim);}
.work-lit .work-title{color:var(--mark);}
.work-lit .work-by,
.work-lit .work-note,
.work-lit .work-bench-note{color:var(--on-art-dim);}
.work-lit .work-blurb{color:var(--on-art);}
.work-lit .work-line{color:var(--on-art);}
.work-lit .work-badge{color:var(--on-art-dim);}
/* The word on the button keeps the ACCENT over art (Mark, 2026-08-22: "THE
   WORD IN THE ACTION BUTTON ON THE STACKS NEEDS TO BE THE ACCENT COLOR") —
   the one piece of the lit card that stays the house's colour, so the doing
   word reads as a control and not as caption. The veil under it is what
   keeps it legible on a bright frame. */
.work-lit .work-button{border-color:var(--on-art-line);color:var(--accent);background:var(--art-veil);}
.work-lit .work-button:hover{border-color:var(--accent);color:var(--mark);background:var(--art-veil-deep);}

/* ---------- THE BUILDER'S BENCH ----------
   Mark, 2026-08-21: "make the build part a separate thing below the card. the
   image stretches into something the user will not see."

   IT IS OUTSIDE THE CARD NOW, and that was a correctness bug rather than a
   tidiness one. The bench used to be the card's last child; a card is a flex
   column with its art stretched to cover it, so a prompt box and an arrow made
   the card taller and the picture re-cropped to fill the new shape. The
   builder was judging a frame no reader would ever be shown, and approving it.

   The slot holds both. The card takes the row's height the way it does with no
   bench at all, so what is being judged is what will be served. */
.work-slot{display:flex;flex-direction:column;gap:8px;min-width:0;}
.work-slot .work-card{flex:1;}
/* And it LOOKS like a workbench: a plain panel under the card, never wearing
   the art, never pretending to be part of the thing it is a tool for. */
.work-bench{display:flex;flex-direction:column;gap:7px;padding:10px 12px;
  border:var(--bd) dashed var(--edge);border-radius:var(--r-md);background:var(--inset);}
.work-prompt{width:100%;resize:vertical;padding:7px 9px;border:var(--bd) solid var(--edge);
  border-radius:var(--r-sm);background:var(--panel);color:var(--ink2);
  font:inherit;font-size:var(--t-tiny);line-height:1.45;}
.work-prompt:focus{outline:none;border-color:var(--accent);}
/* Before there is a prompt there is no box, because an empty box says "type
   here" and the answer is "press the arrow" (Mark: "i have to write the
   prompt?"). */
.work-bench-idle{margin:0;color:var(--muted);font-size:var(--t-tiny);line-height:1.45;}
.work-bench-row{display:flex;align-items:center;gap:8px;}
.work-bench-note{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  color:var(--muted);font-size:var(--t-tiny);}
.work-redraw{flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:28px;height:28px;padding:0;border:var(--bd) solid var(--edge);border-radius:var(--r-round);
  background:var(--panel);color:var(--muted);cursor:pointer;}
.work-redraw:hover{color:var(--accent);border-color:var(--accent);}
.work-redraw[disabled]{cursor:default;}
.work-spin{animation:work-turn 1s linear infinite;}
@keyframes work-turn{to{transform:rotate(360deg);}}
@media (prefers-reduced-motion:reduce){.work-spin{animation:none;}}

/* The shared-voice mark: the small triangle a cast row wears when its voice
   is also another seat's in the same book (CLAUDE.md, the amended
   one-voice-one-body rule). Sharing is allowed there; this is how you see
   you have done it. The title carries who else has the voice. */
.voice-shared{flex:none;display:inline-flex;align-items:center;color:var(--danger);}

/* ---------- THE ONE PICTURE WINDOW ----------
   rooms/modals/picture.js draws it. M-7, M-8b and M-10 are three asks for one
   thing, a picture at full size with its acts inside it, so there is one
   window and three callers (plans/MARKS-CALLS.md, THE ONE WINDOW). Its look
   lives here rather than in a room sheet because three rooms call it, and this
   file is where the parts live. The window itself is .panel .panel-lg
   .panel-float .panel-col, so its surface, its radius and its shadow are the
   house's and not a fourth opinion about what a modal looks like.

   Z-INDEX 89, AND THE REASON IT IS WRITTEN DOWN. The camera's scrim is 88 and
   the engine's zoom was 88 as well, so a picture opened from the camera
   stacked only by the order lib/render.js happened to emit the two of them in.
   89 makes it a rule: above the camera, under the house dialog's 95, which is
   the line every scrim in this app keeps and the one the camera once broke. */
.pic-scrim{position:fixed; inset:0; z-index:89; display:flex; align-items:center;
  justify-content:center; padding:16px; pointer-events:none; animation:fadeUp .25s ease both;}
/* the entrance plays once: the renderer rebuilds the tree on every state
   write, and an ungated entrance strobes under the hand */
[data-render-phase="update"] .pic-scrim{animation:none!important;}
.pic-scrim>*{pointer-events:auto;}
.pic{max-width:min(92vw,940px); max-height:94vh; overflow-y:auto; gap:13px;}
.pic-head{display:flex; align-items:flex-start; justify-content:space-between; gap:16px;}
.pic-label{display:flex; flex-direction:column; gap:var(--s1); min-width:0;}
.pic-title{font-size:var(--t-title); font-weight:var(--w-semi);
  letter-spacing:var(--ls-head); margin-top:4px;}
.pic-x{font-size:var(--t-sub); line-height:1;}
/* THE STAGE IS A FIXED BOX and the picture is contained inside it, which is a
   correctness rule wearing a layout's clothes. lib/drag.js holds a window's
   corner still by measuring its box between renders, so a window whose SIZE is
   its picture's size drags its own corner about when the bytes arrive from the
   network. drag.js carries an explicit carve-out (`el.tagName !== "IMG"`) for
   exactly that, written for the engine's zoom, which was a bare <img>. This
   window is not one, so the carve-out would not cover it. A fixed stage means
   nothing to cover: the box is the same size before and after the picture
   loads. */
.pic-stage{width:min(84vw,860px); height:min(58vh,620px); display:flex;
  align-items:center; justify-content:center; overflow:hidden;
  border-radius:var(--r-lg); background:var(--inset);}
.pic-img{max-width:100%; max-height:100%; object-fit:contain;}
.pic-credit{margin:0;}
/* THE ACTS ROW, and the line under it (M-7 D1). The row carries five presses
   now that DOWNLOAD and SHARE THE PICTURE have joined it, so it wraps onto a
   second line on a narrow window and the gap keeps the two lines reading as
   one row rather than two blocks. The note is drawn ONLY when this browser
   cannot hand a file to another app, and it points at the press that still
   works, so a limitation never reads as a dead control. */
.pic-acts{display:flex; flex-wrap:wrap; gap:8px;}
.pic-acts-note{margin:0; max-width:62ch;}
/* THE WORDS, AS A FIELD (M-7 D2). The block reads the same whether the words
   are being read or edited, so the textarea wears the body's own type and
   colour and adds only what a field needs: a line to sit in and room to grow.
   The foot is the press and the sentence that says where the words go, on one
   line while there is room and wrapping rather than crowding when there is
   not. Every colour is a token; nothing here knows a theme by name. */
.pic-prompt-ta{width:100%; box-sizing:border-box; resize:vertical; padding:11px 12px;
  border:var(--bd) solid var(--accent); border-radius:var(--r-md); background:var(--inset);
  color:var(--ink2); font-size:var(--t-tiny); line-height:1.55;}
.pic-prompt-foot{display:flex; flex-wrap:wrap; align-items:center; gap:10px;}
.pic-prompt-note{margin:0;}
.pic-prompt{display:flex; flex-direction:column; gap:6px; padding:var(--s5);
  border-radius:var(--r-md);}
.pic-prompt-body{font-size:var(--t-tiny); line-height:1.55; color:var(--ink2);
  word-break:break-word;}
