/* ─────────────────────────────────────────────────────────────────────
 * Speech-bubble module — Phase 1
 *
 * Spec: docs/internal/SPEECH_BUBBLE_MIGRATION_PLAN.md
 *
 * IMPORTANT — selector direction:
 *   The bubble-mode class lives on <body>. Any rule that needs to combine
 *   bubble-mode with a descendant state (panel-open, edit-overlay, etc.)
 *   MUST start the selector with `body.bubble-mode …` — not `.bubble-mode …`.
 *   The wrong-direction form silently matches nothing.
 *
 * IMPORTANT — source order is load-bearing:
 *   Several overrides win purely by file-order at equal specificity
 *   (mobile padding-left over base; panel-override over desktop). The
 *   rules below are ordered:
 *     1. Base
 *     2. @media (min-width: 940px) — desktop gutter-hang
 *     3. body.bubble-mode .app-interface:is(.doc-open, …) — panel override
 *     4. @media (max-width: 767px) — mobile
 *   Do not reorder.
 *
 * Zero new tokens. Everything reuses css/styles.css :root variables.
 * ─────────────────────────────────────────────────────────────────────
 */


/* ─── 1. BASE ─────────────────────────────────────────────────────── */

/* Neutralize the chat container's 40px gap; spacing lives on rows instead.
   Without this, every consecutive same-group bubble would be 40px apart. */
.bubble-mode .chat-container {
    gap: 0 !important;
}

/* The row foundation. Block-level by default → flex row with the avatar
   (or spacer) bottom-anchored, 8px gap before the bubble. Carries the
   48rem column constraint that .message-block held in transcript mode. */
.bubble-mode .bubble-row {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    max-width: 48rem;
    width: 100%;
    margin-left: auto;
    margin-right: auto;
    margin-top: 2px;
}
.bubble-mode .bubble-row.bubble-group-end {
    margin-bottom: 14px;
}

/* Empty spacer reserves the avatar slot on non-final rows so the bubble's
   x-position doesn't move when the avatar appears at the end of the run. */
.bubble-mode .bubble-spacer {
    width: var(--bubble-avatar-size);
    flex-shrink: 0;
}

/* Avatar size — ONE variable drives every state: base 1× (34px), bumped to 3×
   on wide desktop, dropped back to 1× when a side panel is open, smaller on
   mobile. The gutter-hang and spacer below derive from it too. */
.bubble-mode { --bubble-avatar-size: 34px; }

/* Avatar. Per-speaker color fill via modifier class; the three sages overlay
   face art (below). Use background-COLOR (not the shorthand) so the art's
   background-image isn't wiped. */
.bubble-mode .bubble-avatar {
    width: var(--bubble-avatar-size);
    height: var(--bubble-avatar-size);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: calc(var(--bubble-avatar-size) * 0.4);  /* initial scales with circle */
    font-weight: 600;
    flex-shrink: 0;
    background-size: cover;        /* face art fills the circle; the circle IS the crop */
    background-position: center;
    background-repeat: no-repeat;
}
.bubble-mode .bubble-avatar--user      { background-color: rgba(255,255,255,0.14); color: var(--text-primary); }
.bubble-mode .bubble-avatar--companion { background-color: var(--companion-color);  color: var(--background-primary); }
.bubble-mode .bubble-avatar--analyst   { background-color: var(--analyst-color);    color: var(--background-primary); }
.bubble-mode .bubble-avatar--creative  { background-color: var(--creative-color);   color: var(--background-primary); }

/* The three sages carry custom face art. The colored fill stays underneath as a
   failure under-layer; the opaque art covers it and the initial is hidden via
   transparent text. ANY other kind (user, or a future character with no art)
   keeps its colored initial — that is the deliberate fallback. */
.bubble-mode .bubble-avatar--companion,
.bubble-mode .bubble-avatar--analyst,
.bubble-mode .bubble-avatar--creative { color: transparent; }
.bubble-mode .bubble-avatar--companion { background-image: url('/avatars/sets/3sages/web/tiger2.webp'); }
.bubble-mode .bubble-avatar--analyst   { background-image: url('/avatars/sets/3sages/web/tortoise2.webp'); }
.bubble-mode .bubble-avatar--creative  { background-image: url('/avatars/sets/3sages/web/buffalo3.webp'); }

/* Name label — INSIDE the bubble at the top, WhatsApp pattern. Inserted as
   first child of .message-content on group-start messages. Inherits bubble
   width naturally; no column constraint needed.
   Sage color inherited from existing .speaker-name.{companion|analyst|creative}
   rules in styles.css (since .bubble-name is composed with .speaker-name). */
.bubble-mode .bubble-name {
    display: block;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    margin-bottom: 4px;
    user-select: none;
}

/* Bubble itself — overrides .message-block in bubble mode. Note: do NOT
   set max-width: none on .message-block; the 80% below is the cap. */
.bubble-mode .message-block {
    position: relative;
    background: var(--background-secondary);
    border-radius: 12px;
    padding: 8px 12px 6px;
    max-width: 80%;
    width: fit-content;
    margin-left: 0;
    margin-right: 0;
    margin-bottom: 0;
}
.bubble-mode .message-block.bubble-tailed {
    border-radius: 12px 12px 12px 0;
}

/* Tail SVG. Filled via currentColor → one color rule themes everything. */
.bubble-mode .bubble-tail {
    position: absolute;
    bottom: 0;
    left: -8px;
    width: 8px;
    height: 13px;
    transform: scaleY(-1);
    color: var(--background-secondary);
}

/* Timestamp inside the bubble, float so prose wraps around it. */
.bubble-mode .bubble-time {
    font-size: 10px;
    color: rgba(255, 255, 255, 0.35);
    float: right;
    margin: 8px 0 0 10px;
    user-select: none;
}

/* Clearfix: floats don't expand their parent. Without this, a one-word
   message + timestamp lets the float poke out of the bubble's bottom
   padding. The ::after gives the content div a footer the float clears
   against. */
.bubble-mode .message-content::after {
    content: '';
    display: block;
    clear: both;
}

/* Accessibility — hide the original .speaker-name visually while keeping
   it in the a11y tree (the visible name is the new .bubble-name).
   Standard clip-rect utility; codebase had no .sr-only. */
.bubble-mode .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* System messages — un-bubbled, centered, italic. Stage direction. */
.bubble-mode .bubble-row--system {
    justify-content: center;
}
.bubble-mode .message-block.system {
    background: transparent;
    border-radius: 0;
    padding: 4px 0;
    font-style: italic;
    color: rgba(255, 255, 255, 0.45);
    font-size: 13px;
    max-width: 100%;
}
/* The block centers via justify-content on the row, but its CHILD
   .message-content carries a direct text-align: left rule in styles.css.
   Inheritance loses to any direct rule — explicit override below. */
.bubble-mode .message-block.system .message-content {
    text-align: center;
}
.bubble-mode .message-block.system .speaker-name {
    display: none;
}
/* System row spacing: add breathing room before the next bubble row. */
.bubble-mode .bubble-row--system {
    margin-top: 16px;
    margin-bottom: 16px;
}

/* Loading / thinking bubble — same background as a real bubble, no tail. */
.bubble-mode .message-block.loading-message {
    background: var(--background-secondary);
    border-radius: 12px;
    padding: 8px 12px;
    max-width: fit-content;
}
.bubble-mode .message-block.loading-message .bubble-tail {
    display: none;
}


/* ─── 2. DESKTOP GUTTER-HANG (≥940px) ─────────────────────────────── */

/* Hang the 42px rail outside the 48rem column. Avatar's negative margin
   places its leading edge at columnLeft − 42; gap-8 advances the bubble
   to columnLeft + 0. Text edge matches transcript mode exactly.
   Breakpoint = 940 = 768 column + 64 chat-container padding + 84 (rail × 2)
   + 15 (Windows scrollbar reserve) + safety. */
@media (min-width: 940px) {
    .bubble-mode .bubble-row > .bubble-avatar,
    .bubble-mode .bubble-row > .bubble-spacer {
        /* Hang the avatar fully in the gutter: leading edge at columnLeft −
           (size + gap), so the bubble lands exactly at columnLeft. Derived from
           the size variable so it tracks 1× and 3× automatically. */
        margin-left: calc(-1 * (var(--bubble-avatar-size) + 8px));
    }
    /* .bubble-name lives inside .message-content now; no column-edge
       indent to override. */
}

/* 3× ON WIDE DESKTOP. Only when the viewport is wide enough to hang a ~100px
   circle fully in the left gutter without overlapping the 48rem column
   (≈ 768 column + 2×108 rail + padding). Below this it stays 1× (34px). */
@media (min-width: 1200px) {
    .bubble-mode { --bubble-avatar-size: 100px; }
}


/* ─── 3. PANEL-OPEN OVERRIDE ──────────────────────────────────────── */

/* When any side panel is open, the chat column halves. The viewport-width
   media query above still fires, but there's no gutter to hang into.
   Tuck the rail back in-flow regardless of viewport.
   Selector starts at body because .bubble-mode lives on <body>; panel
   state classes live on .app-interface inside body. */
body.bubble-mode .app-interface:is(.doc-open, .projects-open, .cypher-open) .bubble-row > .bubble-avatar,
body.bubble-mode .app-interface:is(.doc-open, .projects-open, .cypher-open) .bubble-row > .bubble-spacer {
    margin-left: 0;
}
/* Panel open → 1× baseline (34px) for narrow/mobile; 2× (68px) on desktop, where
   the half-width chat still has room to the right of the bubbles. The avatar sits
   in-flow here (margin-left: 0 above), so the larger circle just shifts the bubbles
   right into that existing gap — no bubble resize. "edge case: open doc → 2× on desktop." */
body.bubble-mode .app-interface:is(.doc-open, .projects-open, .cypher-open) {
    --bubble-avatar-size: 34px;
}
@media (min-width: 768px) {
    body.bubble-mode .app-interface:is(.doc-open, .projects-open, .cypher-open) {
        --bubble-avatar-size: 68px;
    }
}


/* ─── 4. MOBILE (≤767px) ──────────────────────────────────────────── */

@media (max-width: 767px) {
    /* Mobile stays at the current size — unchanged. */
    .bubble-mode { --bubble-avatar-size: 28px; }
    .bubble-mode .bubble-row {
        gap: 6px;
    }
    .bubble-mode .message-block {
        max-width: 96%;
    }
    /* Drop the spacer on follow-up bubbles — they use the full width.
       A row with a spacer never has an avatar (mutually exclusive). */
    .bubble-mode .bubble-spacer {
        display: none;
    }
}


/* ─── 5. TAP-TO-REPLY AFFORDANCE ──────────────────────────────────
   Icon-only hint in the bottom-right of every sage bubble (only
   applied to assistant bubbles with a sage in applyBubbleLayer).
   Desktop: opacity 0 default, ~0.4 on .bubble-row:hover.
   Mobile:  opacity ~0.5 always (no hover state on touch).
   Highlight class .bubble-row.replying-target lifts the bubble while
   the user has its reply context active in the composer. */

.bubble-mode .message-block .bubble-reply-hint {
    position: absolute;
    bottom: 2px;
    right: 6px;
    font-size: 11px;
    color: var(--text-tertiary, rgba(255, 255, 255, 0.5));
    opacity: 0;
    transition: opacity 0.15s ease;
    user-select: none;
    pointer-events: none;
    line-height: 1;
}

.bubble-mode .bubble-row {
    cursor: pointer;
}

.bubble-mode .bubble-row:hover .bubble-reply-hint {
    opacity: 0.4;
}

.bubble-mode .bubble-row.replying-target .message-block {
    box-shadow: 0 0 0 1px var(--text-secondary, rgba(255, 255, 255, 0.4));
    transition: box-shadow 0.15s ease;
}

/* Mobile: always visible (no hover state on touch devices). */
@media (max-width: 767px) {
    .bubble-mode .message-block .bubble-reply-hint {
        opacity: 0.5;
    }
}


/* ─── 6. REPLY CONTEXT PREVIEW (composer slot) ─────────────────────
   WhatsApp-style quote above the message input. Hidden by default.
   user-input.js toggles .hidden + writes person/snippet content. */

.reply-context-preview {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 6px 10px;
    margin: 4px 8px 6px;
    border-left: 3px solid var(--text-secondary, rgba(255, 255, 255, 0.4));
    background: rgba(255, 255, 255, 0.04);
    border-radius: 4px;
    font-size: 12px;
    line-height: 1.35;
    color: var(--text-secondary, rgba(255, 255, 255, 0.7));
}

.reply-context-preview.hidden {
    display: none;
}

.reply-context-body {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
}

.reply-context-label {
    display: block;
    font-weight: 600;
    color: var(--text-secondary, rgba(255, 255, 255, 0.7));
    margin-bottom: 2px;
}

.reply-context-person {
    /* Inherit color from .reply-context-label so any sage-color override
       on the parent would cascade here. v1 keeps it neutral. */
}

.reply-context-snippet {
    display: block;
    color: var(--text-tertiary, rgba(255, 255, 255, 0.55));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.reply-context-cancel {
    flex: 0 0 auto;
    background: transparent;
    border: none;
    color: var(--text-tertiary, rgba(255, 255, 255, 0.5));
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    padding: 0 4px;
    opacity: 0.7;
    transition: opacity 0.15s ease, color 0.15s ease;
}

.reply-context-cancel:hover {
    opacity: 1;
    color: var(--text-primary, #fff);
}


/* ─── 7. EMBEDDED QUOTE-BLOCK (rendered above user reply text) ───── */
/* WhatsApp-style quote-block that appears inside a user-message bubble
   when the user replied to a sage bubble. Speaker label on top (colored
   to match the sage), single-line ellipsis-truncated quoted text below.
   See BUBBLE_TAP_TO_REPLY_PLAN.md Layer 2. */

.message-content .message-reply-quote {
    margin: 0 0 6px;
    padding: 4px 8px;
    border-left: 3px solid var(--text-secondary, rgba(255, 255, 255, 0.4));
    background: rgba(255, 255, 255, 0.06);
    border-radius: 3px;
    font-size: 12px;
    line-height: 1.35;
    overflow: hidden;
}

.message-reply-quote-label {
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--text-secondary, rgba(255, 255, 255, 0.7));
    margin-bottom: 2px;
}

.message-reply-quote-text {
    color: var(--text-tertiary, rgba(255, 255, 255, 0.6));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Sage-color override on the speaker label inside the quote. Mirrors
   the .companion / .analyst / .creative classes used elsewhere. */
.message-reply-quote.quote-gpt .message-reply-quote-label,
.message-reply-quote.quote-companion .message-reply-quote-label {
    color: #10b981; /* emerald — Companion */
}
.message-reply-quote.quote-gemini .message-reply-quote-label,
.message-reply-quote.quote-analyst .message-reply-quote-label {
    color: #3b82f6; /* blue — Analyst */
}
.message-reply-quote.quote-claude .message-reply-quote-label,
.message-reply-quote.quote-creative .message-reply-quote-label {
    color: #f97316; /* orange — Creative */
}

/* Border-left tinted to match the speaker color (subtle). */
.message-reply-quote.quote-gpt,
.message-reply-quote.quote-companion {
    border-left-color: rgba(16, 185, 129, 0.6);
}
.message-reply-quote.quote-gemini,
.message-reply-quote.quote-analyst {
    border-left-color: rgba(59, 130, 246, 0.6);
}
.message-reply-quote.quote-claude,
.message-reply-quote.quote-creative {
    border-left-color: rgba(249, 115, 22, 0.6);
}

/* Tap a reply-quote → jump to the original message (see chat.js jumpToMessage). */
.message-content .message-reply-quote {
    cursor: pointer;
}

/* Flash the jumped-to message briefly (WhatsApp-style). Applied to the
   .bubble-row (bubble mode) or .message-block (flat mode); self-clearing. */
@keyframes message-flash {
    0%   { background-color: rgba(255, 255, 255, 0.14); }
    100% { background-color: transparent; }
}
.message-flash {
    animation: message-flash 1s ease-out;
}
