/* AppBlocks `chat` block — chat log + message bubbles + composer row.
   Theme hooks with fallbacks, same as layout.css. */
/* overflow-y: visible (2026-09-18, real bug w/ 4 screenshots: "that UI
   element shouldn't have a scroll. It's not the history scroll. It's
   another scroll") -- .chat-panel is also given the generic
   `.stack-section` class (shared/layout/layout.js's convention, host
   apps' own markup) for every OTHER use of that class (work items list,
   shortcuts, designer, ...) that class's own overflow-y:auto (layout.
   css) is exactly the point -- each of those independently scrolls its
   own overflowing content. For the chat panel specifically that's
   actively wrong: #chat-log already has its own internal scroll for
   message history (its own `overflow: auto` below), and the composer
   is meant to always render fully, right below it, with no scroll of
   its own (this work item's own repeated "always bottommost" ask) --
   a SECOND, panel-level scrollbar just meant the composer's buttons
   could sit just past this outer scroll's own fold, needing an extra,
   unexpected manual scroll to reach even though everything actually
   fit. Overriding back to visible here removes that second layer;
   #chat-log's own scroll stays the only one inside this panel. */
.chat-panel { display: flex; flex-direction: column; min-height: 0; overflow-y: visible; }
/* min-height: 0, not 90px (2026-09-18, real regression report: "instead
   of making that field height cause the anchor to match its end, you
   now made the input 1-lined for multi-line or long messages... what's
   the problem [with] keeping it, only make the bottom fitted and the
   section above it adapt and roll out?"). The 90px floor meant #chat-
   log stopped giving up space once it hit that limit -- so on a
   cramped screen, once the LOG was already at 90px, the extra fix
   earlier in this work item (clampToViewport, chat.js) started
   shrinking the COMPOSER itself instead, down to one line with its own
   internal scroll for anything longer. That's backwards from what was
   actually wanted: the log (which already scrolls internally, right
   below) should be the one that always yields space first, so the
   composer gets to show its real height. min-height:0 lets it shrink
   all the way down before the composer ever has to give up any of its
   own; clampToViewport (chat.js) still exists underneath as a last-
   resort safety net for the (much rarer) case where even a fully
   collapsed log isn't enough. */
.chat-log { background: var(--card, #fff); border: 1px solid var(--rule, #e2e2dd); border-radius: 12px; padding: 12px; flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 10px; }
/* Two-mode composer (2026-09-16). First attempt overlaid the buttons on
   a permanently-reserved corner of the textarea -- fixed the text-under-
   buttons problem but never actually gave a short "yes"/"no" message
   back its full width, which is what was really being asked for:
   "the first line would be a one-liner... followed by those buttons.
   The first time it overflows... a large field... moving all the
   buttons below." Simpler than it first looked: not a real text-wraps-
   around-a-shape effect (impossible on a plain textarea, no child
   elements to wrap around) -- just TWO flex layouts, switched by
   chat.js's resizeInput() adding/removing .multiline once the message
   actually grows past one line (measured live per input, never a
   guessed pixel threshold): row (input | buttons side by side) below
   that, column (input full-width on top, buttons own row underneath,
   right-aligned) at or above it. */
/* padding-bottom: env(safe-area-inset-bottom) (2026-09-18, "the prompt
   line, Send, Attach, and every other button will never be hidden but
   will always be bottommost") -- on mobile this composer IS the
   permanently-visible bottommost content (never a .mobile-panel like
   its siblings, see index.html), so on a fullscreen/standalone PWA
   (viewport-fit=cover, app-blocks' index.html) it's the row most at
   risk of sitting under a home-indicator/gesture-nav bar. 0px on every
   device without one (regular browser tab, or no notch/gesture bar at
   all), so this is a no-op there.
   flex: none (2026-09-18, real bug: "even when there are attachments
   and few lines of text with no scroll, the buttons are shown below")
   -- .chat-form is a flex child of .chat-panel, sharing that column
   with #chat-log (flex:1 1 auto, floors at min-height:90px) and
   .chat-attach (already flex:none, fixed for this exact class of bug
   2026-09-16 -- see its own comment below). .chat-form itself was still
   left at the flex default (flex-shrink:1, no protection): once
   .chat-attach's real height (an attachment chip row) plus a short,
   NOT-oversized composer (the resizeInput fix just above this file)
   together still didn't leave enough room in a short/constrained chat
   panel, .chat-form -- Send/Attach and every other composer button --
   was the one silently pushed past the panel's own bottom edge instead
   of #chat-log giving up more of its space first. flex:none takes it
   out of the shrink pool entirely, same fix, same reasoning, as
   .chat-attach already got. */
.chat-form { display: flex; flex-direction: row; align-items: flex-end; gap: 8px; margin: 8px 0 0; padding-bottom: env(safe-area-inset-bottom, 0px); flex: none; }
.chat-form.multiline { flex-direction: column; align-items: stretch; }
.chat-form.multiline input, .chat-form.multiline textarea { flex: none; }
/* [type=file] excluded explicitly, not just left to `hidden` (2026-09-16,
   real bug found live, screenshot: the raw "No file chosen" native file
   input rendered, ugly) -- this rule's own specificity (0,1,1: a class
   + a type selector) beats the UA default [hidden]{display:none}
   (0,1,0), the EXACT same class of bug already fixed once this session
   for .chat-stop on mobile ("there always was stop button...") and
   reintroduced here by accident the moment `.chat-form input` (no
   :not()) started matching the hidden file picker too. */
.chat-form input:not([type="file"]), .chat-form textarea { flex: 1; min-width: 0; font: inherit; border: 1px solid var(--rule, #e2e2dd); border-radius: 9px; padding: 8px 12px; resize: none; max-height: 200px; box-sizing: border-box; }
.chat-form input[type="file"] { display: none; }
.chat-btn-row { display: flex; gap: 4px; flex: none; }
.chat-form.multiline .chat-btn-row { align-self: flex-end; }
/* "all buttons should have the same height as the 1 initial text
   height, and same widths (square buttons, currently uneven)"
   (2026-09-15) — attach/send/stop each had their OWN fixed min-width/
   min-height, none matching each other or the input's own rendered
   height. :not([hidden]) still matters below — the old mobile-only
   override that ignored .chat-stop's [hidden] attribute (author-rule
   specificity beating the UA default) is the same class of bug this
   guards against generally, not just that one past fix (see the
   [type=file] note above — it happened again, in a different spot,
   this same session). */
/* Explicit fixed size, not aspect-ratio:1 + auto (2026-09-15, tried
   first, found live via Playwright measurement to be unreliable —
   each button's own glyph (📎/➤/■) has a different natural content
   width, and aspect-ratio's own auto-sizing algorithm resolved that
   differently per element even with identical padding/font-size,
   giving 24/20/18px instead of one consistent size). --chat-btn-size
   is set once, roughly matching the input/textarea rule above's own
   box: 8px+8px padding + ~1px+1px border + ~20px single-line text ≈
   38px — an explicit, deterministic value beats "clever" auto-sizing
   that measurably didn't produce equal buttons in practice. */
:root { --chat-btn-size: 38px; }
/* 2026-09-16: "the send button is square while all the other icons... are
   rectangular with round corners" -- real gap, not a guess: .chat-attach-btn
   and .chat-stop each have their own explicit `border-radius: 9px` rule
   further down this file, but .chat-send never got one anywhere, so it
   fell back to the bare/browser default (sharp corners) while its two
   neighbors were already rounded. Declared here, on the shared sizing
   rule, so all three definitely match regardless of load order. */
.chat-attach-btn, .chat-send, .chat-stop, .chat-downvote { width: var(--chat-btn-size); height: var(--chat-btn-size); min-width: 0; min-height: 0; padding: 0; flex: none; display: flex; align-items: center; justify-content: center; box-sizing: border-box; font-size: 16px; line-height: 1; border-radius: 9px; }
.chat-attach-btn[hidden], .chat-send[hidden], .chat-stop[hidden] { display: none; }
.msg { position: relative; max-width: 92%; padding: 8px 12px; border-radius: 12px; white-space: pre-wrap; word-break: break-word; font-size: 14px; }
/* "add a middle handle, centered at a chat bubble's bottom, to expand/
   collapse that bubble" (2026-09-12) — a second, more discoverable way
   to toggle a peekable message besides clicking the text itself
   (buildMsgNode, chat.js); only added for bubbles that actually have a
   .msg-text-peek body. Extra bottom padding on the bubble keeps the
   handle from sitting on top of the last visible line of text. */
.msg:has(.msg-text-peek) { padding-bottom: 16px; }
.msg-handle {
  position: absolute; left: 50%; bottom: 4px; transform: translateX(-50%);
  width: 28px; height: 5px; border-radius: 4px; border: none; padding: 0;
  background: var(--rule, #e2e2dd); cursor: pointer;
}
.msg-handle:hover { background: var(--muted, #6b6b66); }
/* "i'm light gray, you with no bg and no border" (2026-09-12) — user
   bubble: light gray fill. Model (AI) bubble: no fill, no border at
   all — plain text, alignment (left/right) is what tells them apart. */
.msg.user { align-self: flex-end; background: var(--bg, #f6f6f4); color: var(--ink, #1c1c1a); }
.msg.model { align-self: flex-start; background: none; border: none; }
.msg.sys { align-self: center; color: var(--muted, #6b6b66); font-size: 12.5px; }
.msg.tool { align-self: flex-start; color: var(--muted, #6b6b66); font-size: 12.5px; font-family: ui-monospace, Menlo, Consolas, monospace; padding: 2px 8px; }
.msg.thinking { opacity: .7; }
/* Blinking AI sparkle (2026-09-10/2026-09-12) — used for the desktop
   chat-log placeholder shown while waiting on an external (Use My AI)
   reply, same convention as the mobile toolbar's .mobile-progress-ai. */
.ai-sparkle { display: inline-block; animation: ai-sparkle-blink 1s ease-in-out infinite; }
@keyframes ai-sparkle-blink { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
/* "for long messages, add peek. show 3 lines clear, and half line
   fading, click on it will expand and collapse" (2026-09-11) — an alpha
   mask alone (previous version here) faded the TEXT to transparent but
   read as no affordance at all against a plain background (2026-09-12,
   real user report: "I asked for bottom dark gradient... not see it
   either") — replaced with a real visible ::after gradient bar, fading
   to each bubble's own solid background color (--peek-fade-bg,
   overridden per .msg variant below) so it still looks right on any
   bubble background/theme without hardcoding one color everywhere.
   IMPORTANT: no mask-image here anymore — a first attempt kept BOTH the
   old alpha mask AND the new ::after bar, but mask-image applies to the
   whole element's painted output, pseudo-elements included, so the
   outer mask faded the new gradient bar to transparent too, right where
   it needed to be solid (2026-09-12, real user report it still wasn't
   visible after that "fix"). line-height fixed here so the 3.5-line
   max-height math is exact. */
.msg-text-peek { display: block; position: relative; line-height: 1.4; cursor: pointer; }
.msg-text-peek:not(.msg-text-expanded) {
  max-height: 4.9em; /* 3 lines + a half-line peek, at line-height 1.4 */
  overflow: hidden;
}
/* "must fade to dark/black specifically" (2026-09-12, re-clarified
   after the previous --ink-based contrast-color version, which flipped
   to a LIGHT fade in the dark theme since --ink itself flips to
   near-white there — user confirmed twice that read as wrong): always
   a literal black overlay now, both themes, no variable indirection. */
.msg-text-peek:not(.msg-text-expanded)::after {
  content: ''; position: absolute; inset-inline: 0; bottom: 0; height: 1.4em; pointer-events: none;
  background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.65));
}
/* One turn's tool steps, collapsed by default (2026-09-09) — a native
   <details>/<summary> so no JS is needed to toggle it. */
.msg.turn { align-self: flex-start; max-width: 100%; background: var(--bg, #f6f6f4); border-radius: 10px; padding: 4px 10px; }
.msg.turn > summary { cursor: pointer; font-size: 12.5px; color: var(--muted, #6b6b66); list-style: none; }
.msg.turn > summary::-webkit-details-marker { display: none; }
.msg.turn > summary::before { content: '▸ '; }
.msg.turn[open] > summary::before { content: '▾ '; }
/* max-width: none here (2026-09-13, real user report w/ screenshot) —
   each step line is a .msg.tool, and generic .msg already caps
   max-width at 92%. .msg.turn itself shrink-wraps to content
   (align-self: flex-start, no explicit width), so that 92% resolves
   against an as-yet-undetermined parent width: the browser settles on
   a self-consistent but too-narrow fixed point, wrapping a step's text
   onto 2 lines even with lots of unused width beside the whole box.
   The 92% cap only matters for user/model bubbles running the full
   chat-log width; .msg.turn already caps itself, so its step lines
   don't need it too — dropping it breaks the circular sizing and lets
   the box (and each line) size to its actual content width. Verified
   with Playwright against a standalone repro of this markup: before,
   step lines wrapped at ~247px inside a 288px box with an 900px-wide
   panel available; after, all 4 lines render on one line each and the
   box is exactly as wide as its longest line (268px), still capped by
   max-width so it can't overflow a narrow chat-log. */
.msg.turn > .msg.tool { margin-top: 4px; padding: 2px 0; max-width: none; }
/* A commit-line step (chat.js's wireToolClick) is clickable to expand a
   diff below it (2026-09-14) -- underline-on-hover is the only generic
   affordance chat.js can own; the diff block itself is app-supplied
   content rendered into .msg.tool-diff via onToolClick. */
.msg.tool.tool-clickable { cursor: pointer; }
.msg.tool.tool-clickable:hover { text-decoration: underline; }
.msg.tool-diff { margin-top: 2px; }
@media (max-width: 640px) {
  .msg { max-width: 100%; }
  /* The float-inside-the-box composer (above) needs no mobile-specific
     rule at all any more — .chat-form's overlay layout is the same on
     every viewport by construction, unlike the old flex-row composer
     this replaced (2026-09-16), which needed its own mobile
     flex-direction/flex-wrap fix here ("there always was stop button,
     visible in mobile only" traces back to that same old duplication). */
  .chat-attach { max-height: 64px; overflow: auto; }
}

/* Attachments: chips above the composer, inline images/files in messages.
   flex: none (2026-09-16, real mobile bug w/ screenshot: "attachment pane
   is hidden below the text input, cannot press X to delete/preview") —
   this sits in the same flex column as #chat-log (flex:1 1 auto, floors
   at min-height:90px) and #chat-form. On a short mobile composer (WI
   switcher + priority above the log all stacked, per chat-scope-row2),
   once #chat-log hits its 90px floor there is still not enough room, and
   the flex-shrink algorithm looks for the next shrinkable sibling. This
   element used no explicit flex-shrink (default 1) and gets overflow:auto
   on mobile (below), which per spec resolves its auto min-height to 0 —
   so it was the one silently squeezed to a near-0px sliver instead of the
   log, hiding the chip and its X/preview button with no way to scroll to
   it. flex:none takes it out of the shrink pool entirely: it always
   renders at its real content height (or the mobile max-height:64px cap,
   scrollable internally), and #chat-log (the panel's actual intended
   flexible element) absorbs the squeeze instead. */
.chat-attach { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; flex: none; }
.chat-attach-chip { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; padding: 3px 8px; border-radius: 999px; background: var(--chip, #ecece8); }
/* "same proportion but always landscape... only reduce image size, not
   inflate it, for very narrow/small-width attachments" (2026-09-16) - was
   height:28px + width:auto, so a PORTRAIT source rendered as a portrait-
   shaped chip (narrower than tall), inconsistent with every other chip.
   A fixed 56x28 (2:1, same ratio as .msg-img's 120x60 below) landscape
   box + object-fit:scale-down: every chip is the same landscape shape
   regardless of source orientation, and scale-down (not contain) means a
   small/narrow image is shown at its own natural size, centered, never
   stretched up to fill the box - same "shrink, never inflate" rule
   .msg-img already follows. */
.chat-attach-chip img { width: 56px; height: 28px; object-fit: scale-down; border-radius: 4px; border: 1px solid var(--rule, #e2e2dd); background: var(--card, #fff); }
/* Slightly bigger thumbnail on mobile (was dead code above the base rule
   before 2026-09-16 -- equal specificity + earlier source order meant it
   never actually applied; a rule AFTER the base one, same as every other
   real mobile override in this file, is what actually wins the cascade). */
@media (max-width: 640px) {
  .chat-attach-chip img { width: 72px; height: 36px; }
}
.chat-attach-chip button { border: 0; background: none; cursor: pointer; font: inherit; color: var(--muted, #6b6b66); }
/* "add an X button with a circle on the corner of each attachment"
   (2026-09-16) - chat-attach-thumb is the positioning context (relative)
   for an image attachment's thumbnail; chat-attach-remove is the actual
   circular badge, absolutely pinned to ITS top-right corner there. A
   non-image attachment has no thumbnail box to pin a corner onto, so it
   keeps the same circular button inline, trailing the filename (chat.js
   only adds the .chat-attach-thumb wrapper for a.kind === 'image').
   Two classes on the selector ((0,2,0) specificity) so this beats the
   plain ".chat-attach-chip button" rule right above it ((0,1,1)) instead
   of losing the background/color to it by CSS specificity math. */
.chat-attach-thumb { position: relative; display: inline-flex; line-height: 0; }
.chat-attach-chip .chat-attach-remove {
  width: 16px; height: 16px; min-width: 0; padding: 0; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .65); color: #fff; font-size: 11px; line-height: 1;
}
.chat-attach-chip .chat-attach-remove:hover { background: rgba(0, 0, 0, .85); }
.chat-attach-thumb .chat-attach-remove { position: absolute; top: -6px; right: -6px; }
/* Sizing (square, matches the input's height) comes from the shared
   .chat-attach-btn/.chat-send/.chat-stop rule near the top of this
   file — only this button's own cosmetic look (border/background/
   radius) lives here. */
.chat-attach-btn { border: 1px solid var(--rule, #e2e2dd); background: var(--card, #fff); border-radius: 9px; cursor: pointer; }
/* "show thumbnail even size and click to full view" (2026-09-11) — was
   max-width/max-height only, so every image rendered at its own native
   aspect ratio (uneven row heights); a fixed 120x60 box + object-fit:
   cover makes every thumbnail the same size regardless of source
   dimensions. The full, uncropped image only appears in the lightbox
   on click. */
.msg-imgs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
/* "shrink to fit when needed, never inflate small images" (2026-09-12)
   — width/height:auto (natural size) capped by max-width/max-height and
   object-fit:contain: a small image stays its own size (never stretched
   up to fill the box, unlike the old fixed width/height + cover, which
   both cropped large images AND upscaled small ones); a large image
   shrinks to fit within the same 120x60 box, uncropped. */
.msg-img { max-width: 120px; max-height: 60px; width: auto; height: auto; object-fit: contain; border-radius: 8px; cursor: zoom-in; }
.chat-img-lightbox { position: fixed; inset: 0; background: rgba(0, 0, 0, .85); display: flex; align-items: center; justify-content: center; z-index: 50; cursor: zoom-out; }
.chat-img-lightbox img { max-width: 92vw; max-height: 92vh; border-radius: 8px; cursor: default; }
/* Explicit close button (2026-09-14: "add a clear close button" -- tap-
   anywhere-to-close alone was not discoverable enough). Fixed to the
   viewport corner, not the image, so it stays reachable regardless of
   image size/aspect ratio; sized as a real touch target on mobile. */
.chat-img-lightbox-close {
  position: fixed; top: max(12px, env(safe-area-inset-top)); right: max(12px, env(safe-area-inset-right));
  width: 40px; height: 40px; min-width: 40px; min-height: 40px; padding: 0;
  border: none; border-radius: 999px; background: rgba(0, 0, 0, .6); color: #fff;
  font-size: 20px; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.chat-img-lightbox-close:hover { background: rgba(0, 0, 0, .8); }
.msg-file { display: inline-block; font-size: 12.5px; margin-top: 4px; margin-inline-end: 6px; opacity: .85; }
.chat-form.busy input::placeholder { color: var(--warn, #b26a00); }

/* Send (➤) / Stop (■) — one slot; Stop shows only while a turn runs. */
/* Sizing (square, matches the input's height) comes from the shared
   .chat-attach-btn/.chat-send/.chat-stop rule near the top of this
   file — only cosmetics (color, border, font) live here. */
/* "inconsistent looking Send button... half border somehow gets into
   the project without even being required" (2026-09-17, real user
   report w/ screenshot) — unlike .chat-attach-btn/.chat-stop/
   .chat-downvote just above and below it, .chat-send never had its OWN
   border/background rule anywhere in this file; only the shared sizing
   rule (width/height/radius) applied to it. With no author background/
   border, the browser's native button chrome shows through instead
   (a glossy, part-highlighted "half border" look, clearly different
   from its flat, evenly-bordered neighbors). Matches .chat-attach-btn's
   own flat look exactly so all three composer buttons read as one set. */
.chat-send { border: 1px solid var(--rule, #e2e2dd); background: var(--card, #fff); color: var(--ink, #1c1c1a); cursor: pointer; }
.chat-stop { border-radius: 9px; border: 1px solid #b23a2f; background: #b23a2f; color: #fff; cursor: pointer; }
/* Downvote (2026-09-16) — "a hollow button with a hand down", always
   visible (never [hidden] like Stop), same slot/sizing/radius as its
   neighbors. "I told you several times not to use SVGs" — the icon is
   now the user's own real PNG assets (chat.js: DOWNVOTE_HOLLOW_PNG /
   DOWNVOTE_RED_PNG, an <img> whose src swaps on vote), not a hand-drawn
   shape at all; this button chrome (border/background) is the only
   thing still styled here. */
.chat-downvote { border: 1px solid var(--rule, #e2e2dd); background: transparent; cursor: pointer; opacity: .75; }
.chat-downvote:hover { opacity: 1; }
/* "If it's pressed it can be filled but until it's filled I expect to
   see only a rectangle with rounded corners and a hollow hand"
   (2026-09-16) — the img's own src swap (chat.js) already shows the
   real red PNG; this just also tints the button chrome to match.
   Disabled after voting: one report per click, not a toggle. */
.chat-downvote.voted { background: transparent; opacity: 1; cursor: default; }

.msg-time {
  font-size: 12px;
  color: var(--muted, #6b6b66);
  opacity: .8;
  margin-right: 6px;
}

/* "add to chat for every prompt from me or from the AI a small copy
   action with an icon" (2026-09-13) — small, unobtrusive, real icon. */
.msg-copy-btn { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; padding: 0; margin-inline-start: 4px; border: none; background: transparent; color: var(--muted, #6b6b66); cursor: pointer; vertical-align: middle; opacity: .6; }
.msg-copy-btn:hover { opacity: 1; }
