/* Block containers — layout only. Entrance animations removed: they
   ran 80–120ms per block during live streaming (replay was already
   exempt via .stream-no-anim) and caused visible stagger when combined
   with the paced governor in streamRenderer.js. Refresh always rendered
   instant; live now matches. */

.stream-block {
    /* Explicit sizing: parents up the chain are flex columns, where a
       block-level child without min-width:0 collapses to its smallest
       intrinsic width — one character per line. The old appendTextMessage
       path didn't have this wrapper so never hit it. */
    width: 100%;
    min-width: 0;
}

/* Defensive min-width:0 up the chain. Flex items default to
   min-width: auto which prevents them from shrinking below their content's
   intrinsic width; in fast-streaming partial states the intrinsic width
   can flip and cause a cascade where .text-message shrinks to ~1ch and
   each character wraps to its own line. Setting min-width:0 lets flex
   ancestors shrink without starving their block children. */
.ai-message .text-message-container,
.ai-message .text-message,
.ai-message .message-content {
    min-width: 0;
}

/* No per-block entrance animations — paced char-by-char reveal from the
   governor is the only streaming cue the user needs. Keeps live and
   replay byte-identical at the CSS layer. */

@media (prefers-reduced-motion: reduce) {
    .stream-cursor {
        animation: none;
    }
}

/* Skeleton shimmer while a code block is streaming */

/* While streaming there's nothing to copy yet — keep it in flow but
   invisible, offset to the right. When the block finalizes, is-streaming
   is removed and the button slides in (transition lives on .copy-button). */
.block-code.is-streaming .copy-button {
    opacity: 0;
    transform: translateX(8px);
    pointer-events: none;
}

@keyframes streamShimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

/* Math / mermaid pending placeholder */

.block-math.is-pending,
.block-mermaid.is-pending {
    opacity: 0.6;
    min-height: 1.4em;
}

.pulse-dots {
    display: inline-block;
    animation: streamPulseDots 1.2s ease-in-out infinite;
}

@keyframes streamPulseDots {
    0%, 100% { opacity: 0.3; }
    50%      { opacity: 1; }
}

/* Cursor — ChatGPT-style pulsing dot. Must match .streaming-cursor
   (debug.css) exactly so the pre-stream dot and the in-stream cursor
   look like the SAME cursor — no visible swap when streaming starts. */

.stream-cursor,
.streaming-cursor {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.85);
    margin-left: 6px;
    vertical-align: middle;
    animation: chatgpt-pulse 1.5s ease-in-out infinite;
}

@keyframes chatgpt-pulse {
    0%, 100% { transform: scale(1);   opacity: 0.4; }
    50%      { transform: scale(1.3); opacity: 1;   }
}

/* Pre-warm layout variants */

[data-stream-layout="prose"]      { max-width: 65ch; }
[data-stream-layout="structured"] { max-width: 75ch; }
[data-stream-layout="code-heavy"] { max-width: 85ch; }
[data-stream-layout="enumerated"] { max-width: 70ch; }

/* Replay mode — suppress entrance animations across all stream blocks */
.stream-no-anim {
    animation: none !important;
}

/* ---------------------------------------------------------------------------
   Block-wrapper spacing.
   Each markdown block (<p>, <h2>, <ul>, etc.) is now wrapped in a .stream-block
   div. Existing .text-message rules like `p:last-child { margin-bottom: 0 }`
   and adjacent-sibling rules like `h2 + p` no longer work because every inner
   element is :last-child of its wrapper and siblings are separated by divs.
   Move all spacing to the wrapper level; zero out internal margins to avoid
   double spacing.
   --------------------------------------------------------------------------- */

/* ChatGPT-style unified rhythm: 1em between every stream-block by default,
   slightly more (1.25em) after a heading so sections breathe. */
.text-message .stream-block + .stream-block {
    margin-top: 1em;
}

/* Post-stream injections (crypto chart wrapper, email composer card, image
   caption, chart-intro-text, etc.) append to .message-content AFTER the
   stream blocks. They aren't wrapped in .stream-block, so the adjacent-
   sibling rule above doesn't match them. Action buttons are excluded —
   they have their own spacing. */
.text-message .stream-block + *:not(.stream-block):not(.action-buttons) {
    margin-top: 1em;
}

.text-message .stream-block.block-heading + .stream-block {
    margin-top: 0.6em;
}

.text-message .stream-block.block-paragraph + .stream-block.block-heading,
.text-message .stream-block.block-list-ul + .stream-block.block-heading,
.text-message .stream-block.block-list-ol + .stream-block.block-heading {
    margin-top: 1.5em;
}

.text-message .stream-block > p,
.text-message .stream-block > h1,
.text-message .stream-block > h2,
.text-message .stream-block > h3,
.text-message .stream-block > h4,
.text-message .stream-block > h5,
.text-message .stream-block > h6,
.text-message .stream-block > ul,
.text-message .stream-block > ol,
.text-message .stream-block > blockquote {
    margin-top: 0;
    margin-bottom: 0;
}

/* Blockquotes inside a stream-block (e.g. the auto-wrapped verbatim
   quote split into prose-before / blockquote / prose-after) need vertical
   separation from their siblings — the rule above zeros everything, so
   restore rhythm around blockquotes specifically. Matches ChatGPT's
   1em+ breathing room so the quote card reads as its own visual unit. */
.text-message .stream-block > * + blockquote,
.text-message .stream-block > blockquote + * {
    margin-top: 1em;
}

/* Lists (when the stream-block itself IS the <ul>/<ol>) — keep bullet indent. */
.text-message ul.stream-block,
.text-message ol.stream-block {
    margin: 0;
    padding-left: 1.5em;
}

/* ---------------------------------------------------------------------------
   Legacy-CSS compatibility shims.
   Fixes 7 critical + 4 important findings from the post-merge CSS audit.
   These rules win over legacy .ai-message / mobile breakpoint rules that
   re-impose margins on elements-that-ARE-the-stream-block (pre, ul, ol).
   Specificity trumps via the full chain .message.ai-message … and mobile
   rules use !important because some breakpoint rules themselves use
   specific compound selectors.
   --------------------------------------------------------------------------- */

/* Critical #1, #2, #5, #6: pre/ul/ol at the stream-block level.
   Zero their OWN margins — all spacing is owned by .stream-block + .stream-block
   above. Higher-specificity variants cover .ai-message and .message.ai-message
   chains that would otherwise re-impose margins (message-styles.css:637-641,
   message-styles.css:1131-1142, message-styles.css:715-720). */
.text-message pre.stream-block,
.text-message ul.stream-block,
.text-message ol.stream-block,
.ai-message .text-message pre.stream-block,
.ai-message .text-message ul.stream-block,
.ai-message .text-message ol.stream-block,
.message.ai-message .text-message pre.stream-block,
.message.ai-message .text-message ul.stream-block,
.message.ai-message .text-message ol.stream-block {
    margin-top: 0;
    margin-bottom: 0;
}

/* Critical #3, #4, #6, #7: mobile breakpoint rules (mobile.css:2822, 2836,
   3484, 3513, 3707, 3739) re-impose margins on pre/ul/ol/p at multiple
   mobile widths. Match their specificity and neutralize. !important is the
   pragmatic choice: some mobile rules use compound chains specific enough
   to tie with us and win on source-order in older Safari. */
@media (max-width: 768px) {
    .message .text-message pre.stream-block,
    .message .text-message ul.stream-block,
    .message .text-message ol.stream-block,
    .message.ai-message .text-message pre.stream-block,
    .message.ai-message .text-message ul.stream-block,
    .message.ai-message .text-message ol.stream-block {
        margin-top: 0 !important;
        margin-bottom: 0 !important;
    }
    /* Paragraphs inside stream-block wrappers — mobile.css:3484 kills
       margin-bottom via p:last-child (every streamed <p> is last-child of
       its wrapper). Our existing `stream-block > p { margin: 0 }` handles
       this at tie-specificity with source-order, but mobile may reorder; be
       explicit. */
    .message.ai-message .text-message .stream-block > p {
        margin-top: 0 !important;
        margin-bottom: 0 !important;
    }
}

/* Important #1: .text-message p:last-child { margin-bottom: 0 } in
   message-styles.css:653 fires on every streamed <p> (each is the only
   child of its wrapper). Our stream-block > p rule at line 126 zeros both
   margins, so the legacy rule is already neutralized on desktop — this is
   documentation only, no extra rule needed. Same for Important #2 (adjacent
   siblings h2+p etc.) — those selectors simply don't match the new DOM,
   their effect is dropped silently; spacing is now governed by the
   .stream-block + .stream-block rule. */

/* Important #3: list own-margin load-order fragility. Already covered by
   the pre/ul/ol zero-out above for AI content; user-message lists still go
   through appendTextMessage and keep their legacy margins. */

/* Minor cosmetic: .text-message has a contentFadeIn animation that stacks
   with per-block streamBlockIn. On replay mode we suppress per-block; the
   container fade is unchanged. Not overridden. */

/* ---------------------------------------------------------------------------
   CONTENT-AWARE CONTEXTS
   ---------------------------------------------------------------------------
   StreamRenderer classifies each finalized message and sets
   data-ctx="..." on .text-message-container. Every context adapts the
   inner .text-message width, line-height, and stream-block rhythm to the
   kind of content that actually rendered. Deterministic, no model call.
   Fallback: if data-ctx is missing, base .text-message rules apply.
   --------------------------------------------------------------------------- */

/* Context rules override TWO intermediate width caps:
   .message-content and .text-message-container. Both are bound to
   var(--composer-max-width) which defaults to 880px — without
   overriding both, any context trying to exceed 880 is clipped by the
   outer wrapper. The renderer sets data-ctx on the outermost .message
   element AND the inner .text-message-container, so one rule can win
   over both.

   Typography and block-rhythm rules still target the inner .text-message. */

/* Animate max-width + padding transitions when the finalize classifier
   refines the prewarm ctx. Scoping to data-ctx-source="finalize" means:
     - Prewarm-set ctx takes effect instantly (no delayed slide-in).
     - Server-set ctx (v2 scaffold) takes effect instantly.
     - Only the finalize refinement (prewarm → classifier disagreement)
       animates, turning what used to be a jarring snap at last token
       into a 200ms ease.
   Respects reduced-motion preference. */
[data-ctx-source="finalize"].text-message-container,
.message[data-ctx-source="finalize"],
.ai-message[data-ctx-source="finalize"] {
    transition: max-width 200ms ease, padding 200ms ease;
}
@media (prefers-reduced-motion: reduce) {
    [data-ctx-source="finalize"].text-message-container,
    .message[data-ctx-source="finalize"],
    .ai-message[data-ctx-source="finalize"] {
        transition: none;
    }
}

/* --- Conversational --- 580px */
.message[data-ctx="conversational"] .message-content,
.message[data-ctx="conversational"] .text-message-container,
.text-message-container[data-ctx="conversational"] {
    max-width: min(100%, 580px);
}
[data-ctx="conversational"] .text-message {
    font-size: 15px;
    line-height: 1.7;
}

/* --- Prose --- 640px */
.message[data-ctx="prose"] .message-content,
.message[data-ctx="prose"] .text-message-container,
.text-message-container[data-ctx="prose"] {
    max-width: min(100%, 640px);
}
[data-ctx="prose"] .text-message {
    line-height: 1.85;
}

/* --- Long-prose --- 640px (optimal 60–75ch line length) */
.message[data-ctx="long-prose"] .message-content,
.message[data-ctx="long-prose"] .text-message-container,
.text-message-container[data-ctx="long-prose"] {
    max-width: min(100%, 640px);
}
[data-ctx="long-prose"] .text-message {
    line-height: 1.9;
}
[data-ctx="long-prose"] .stream-block + .stream-block {
    margin-top: 1.2em;
}

/* --- Technical --- 760px */
.message[data-ctx="technical"] .message-content,
.message[data-ctx="technical"] .text-message-container,
.text-message-container[data-ctx="technical"] {
    max-width: min(100%, 760px);
}

/* --- Code-heavy --- 920px (widest, exceeds default composer cap) */
.message[data-ctx="code-heavy"] .message-content,
.message[data-ctx="code-heavy"] .text-message-container,
.text-message-container[data-ctx="code-heavy"] {
    max-width: min(100%, 920px);
}
[data-ctx="code-heavy"] .text-message {
    line-height: 1.6;
}
[data-ctx="code-heavy"] .stream-block + .stream-block {
    margin-top: 0.6em;
}

/* --- List-heavy --- 680px */
.message[data-ctx="list-heavy"] .message-content,
.message[data-ctx="list-heavy"] .text-message-container,
.text-message-container[data-ctx="list-heavy"] {
    max-width: min(100%, 680px);
}
[data-ctx="list-heavy"] .stream-block + .stream-block {
    margin-top: 0.4em;
}

/* --- Structured --- 720px */
.message[data-ctx="structured"] .message-content,
.message[data-ctx="structured"] .text-message-container,
.text-message-container[data-ctx="structured"] {
    max-width: min(100%, 720px);
}
[data-ctx="structured"] .stream-block + .stream-block {
    margin-top: 1em;
}
