/* 1800.show — one-room shell */

:root {
    --bg: #0a0a0a;
    --surf: #141414;
    --fg: #e4e4e4;
    --dim: #6c6c6c;
    --edge: #262626;
    --err: #ff4d4d;
    --safe-b: env(safe-area-inset-bottom, 0px);
}

* { box-sizing: border-box; }
html, body {
    margin: 0; padding: 0;
    background: var(--bg); color: var(--fg);
    font-family: "SF Mono", Menlo, "JetBrains Mono", "Courier New", monospace;
    font-size: 14px; line-height: 1.45;
    -webkit-font-smoothing: antialiased;
    overscroll-behavior: none;
    overflow-x: hidden;
}
/* Body is pinned to the viewport. Top-bleed means the chat log stretches
   up into the iPhone status bar / Dynamic Island (our black background
   matches the device chrome, so it reads as one continuous surface).
   Layout: main row (chat + cams) + composer at the bottom. */
body {
    display: grid;
    grid-template-rows: 1fr auto;
    height: 100dvh;
    overflow: hidden;
}
.middle {
    display: flex;
    min-height: 0;
    overflow: hidden;
}

a { color: var(--fg); text-decoration: none; }
button, input[type="text"], input[type="tel"] {
    background: transparent; color: var(--fg); font: inherit;
    border: 1px solid var(--edge); padding: 8px 12px; border-radius: 4px;
}
button { cursor: pointer; }
button:hover:not(:disabled) { border-color: var(--fg); }
button:disabled { opacity: 0.35; cursor: not-allowed; }
input[type="text"], input[type="tel"] { background: var(--surf); min-width: 0; font-size: 16px; }
input:focus { outline: none; border-color: var(--fg); }
[hidden] { display: none !important; }

.who { font-size: 12px; color: var(--dim); }
.btn { font-size: 12px; padding: 4px 10px; }

/* ───────── cam column (narrow, right) ───────── */
/* Column is a single flex stack with three sections:
     .me-tile    always-present square for the signed-in user (fixed at top)
     .cams-top   peers + alive bots (scrolls if overflow)
     .cams-bottom recent /gen images, anchored to bottom
   No gaps between children; tiles touch edge-to-edge. No borders on tiles
   themselves; the column has a single left-edge border. */
.cams {
    flex: 0 0 auto;
    display: flex; flex-direction: column;
    padding: 0;
    border-left: 1px solid var(--edge);
    overflow: hidden;
    background: var(--bg);
    min-height: 0;
}
.me-tile {
    flex: 0 0 auto;
    width: 96px; height: 96px;
    position: relative;
    cursor: pointer;
    background: #000;
}
.cams-top {
    display: flex; flex-direction: column;
    /* Grow to fill all remaining space in the column so .cams-actions
       (the final row) pins to the bottom. Internal scroll kicks in when
       there are more peer/bot tiles than fit. */
    flex: 1 1 auto;
    overflow-y: auto;
    min-height: 0;
}
@media (max-width: 480px) {
    .me-tile { width: 72px; height: 72px; }
}

/* Bot avatar in .cams-top uses the bot's source image (their /gen) as a
   square face tile, same size as a webcam. Future: replace with a
   generated profile portrait derived from the character description. */
.bot-avatar {
    width: 96px; height: 96px;
    border: none;
    display: flex; align-items: flex-end; justify-content: center;
    position: relative;
    background-color: #000;
    background-size: cover;
    background-position: center;
    cursor: pointer;
}
.bot-avatar .ba-name {
    position: absolute;
    bottom: 0; left: 0; right: 0;
    font-size: 9px; letter-spacing: 0.5px;
    color: #fff;
    background: rgba(0, 0, 0, 0.7);
    padding: 2px 3px;
    text-align: center;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* No-picture state: center the username in the whole tile instead of the
   small footer label. Keeps the bot visible while its portrait is gen'ing. */
.bot-avatar.no-pic {
    background: #101010;
}
.bot-avatar.no-pic .ba-name {
    position: static;
    background: transparent;
    display: flex; align-items: center; justify-content: center;
    width: 100%; height: 100%;
    font-size: 11px; letter-spacing: 1px;
    color: #ffc857;
}
@media (max-width: 480px) {
    .bot-avatar { width: 72px; height: 72px; }
}

.cam-start {
    /* Fills the .me-tile slot when nobody's broadcasting. No border — the
       column itself has the only edge. */
    width: 100%; height: 100%; padding: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    background: transparent; border: none;
    color: var(--dim);
    cursor: pointer;
}
.cam-start:hover { color: var(--fg); }
.happy {
    margin: 0; font-family: inherit; font-size: 7px; line-height: 1;
    color: var(--fg); white-space: pre;
}
.cam-start-text { font-size: 8px; letter-spacing: 1px; text-transform: uppercase; }

/* Each cam tile is a 64-px source scaled to a small square. No borders —
   the column's left edge is the only line in the sidebar. */
.ascii-canvas {
    width: 96px; height: 96px;
    background: #000;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
    border: none;
    display: block;
    cursor: pointer;
}
/* Muted state: dimmed. (Dashed border omitted since tiles are borderless.) */
.ascii-canvas.muted { opacity: 0.35; }

/* Peer tile wrapper carries an overlay username for when the peer has no
   cam on (their ascii canvas draws black). Once they turn on cam, the
   .has-video class hides the label because the rendered face is enough. */
.peer-tile {
    position: relative;
    width: 96px; height: 96px;
    display: block;
}
.peer-tile .ascii-canvas { width: 100%; height: 100%; }
.peer-tile .peer-name {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    color: var(--fg);
    font-size: 11px; letter-spacing: 1px;
    pointer-events: none;
    text-shadow: 0 0 3px rgba(0,0,0,0.8);
}
.peer-tile.has-video .peer-name { display: none; }
@media (max-width: 480px) {
    .peer-tile { width: 72px; height: 72px; }
}
@media (max-width: 480px) {
    .ascii-canvas, .cam-start { width: 72px; height: 72px; }
    .happy { font-size: 6px; }
    .cams { padding: 6px 6px; }
}

/* ───────── chat log (fills the remaining row) ───────── */
/* Hide until chat-history arrives + scroll-to-bottom settles, so the
   first paint is already bottom-anchored. Prevents the visible jerk
   where content rendered top-down then animated downward. The .booting
   class is removed from app.js after renderChat on the history batch. */
.log.booting { visibility: hidden; }
.log {
    flex: 1 1 0;
    min-width: 0;
    /* Safe-area top padding so the log extends under the iPhone status bar
       / Dynamic Island without messages being obscured. 6px min for desktop. */
    padding: calc(env(safe-area-inset-top, 0px) + 6px) 12px 30vh;
    overflow-y: auto;
    /* Proximity snap so the chat-feed-boundary acts as a 'speed bump' at
       the bottom of chat — slow scrolls catch on it, momentum carries
       past into the feed. */
    scroll-snap-type: y proximity;
}
/* The boundary marker between the last chat message and the feed grid
   is a 1px element with scroll-snap-align: end. The browser will hold
   the bottom of this marker against the bottom of the viewport when
   scroll velocity is low — momentum carries past as soon as the user
   pulls harder. A subtle visible divider gives a perceptual cue too. */
.chat-feed-boundary {
    height: 1px;
    /* Big gap above the line so reaching the feed feels intentional —
       you're scrolling through visibly empty space before crossing into
       the grid below. */
    margin: 40vh 12px 0;
    background: var(--edge);
    scroll-snap-align: end;
    scroll-snap-stop: normal;
    opacity: 0.6;
}
/* display:flow-root makes each msg a new block formatting context so the
   floated .attach image is contained within its own message and the next
   message doesn't wrap around it. */
/* Messages are NOT a block-formatting-context of their own — that's
   deliberate. Image attachments float right and we WANT the text in the
   messages that follow (from any author) to wrap around the image, not
   just the row that posted it. Floats naturally flow through sibling
   block elements inside the same BFC until they clear. */
.msg { padding: 3px 0; line-height: 1.35; }
/* content-visibility:auto WAS here as an iOS scroll optimization, but
   in practice iOS Safari's implementation flashes blank frames when
   rows enter the viewport — the "top of area scrolled into not
   immediately rendered" symptom. Removed. The browser-default lazy
   layout is fine for our row counts. */
.msg .who { color: var(--dim); font-size: 12px; position: relative; }
/* Admin × for delete. Floats as a tiny superscript just above the right
   edge of the username, taking no layout space. Hover-revealed. The
   glyph lives inside .who, so position calculations track the username
   string length regardless of content. */
.msg .msg-del {
    position: absolute;
    right: -12px; top: -6px;
    width: 14px; height: 14px;
    line-height: 12px; text-align: center;
    font: inherit; font-size: 11px; font-weight: 700;
    color: var(--dim);
    background: transparent;
    border: none;
    padding: 0;
    cursor: pointer;
    opacity: 0;
    transition: opacity 80ms, color 80ms;
    pointer-events: auto;
    z-index: 2;
}
.msg.can-del:hover .msg-del,
.msg .msg-del:focus-visible { opacity: 0.85; }
.msg .msg-del:hover { color: #ff6e6e; opacity: 1; }
.msg .who .via-wrap {
    margin-left: 3px; opacity: 0.55;
    font-size: 11px; color: var(--dim);
    font-family: "Menlo", "SF Mono", monospace;
    vertical-align: baseline;
}
/* Consecutive messages from the same author within an hour collapse into a
   single visual block: drop the username row and tighten the top padding so
   the lines read like a continuation. */
.msg.grouped { padding-top: 0; }
.msg.grouped .who { display: none; }
/* When admin can delete, keep .who in the layout on grouped rows (as a
   zero-width marker) so the floating × still has an anchor and is
   reachable for continuation lines. Username text itself stays hidden. */
.msg.grouped.can-del .who {
    display: inline-block; width: 0; height: 0;
    overflow: visible; font-size: 0;
}
/* text-wrap:pretty is the browser's modern (Knuth-Plass-flavored) line
   breaker — better balance on ragged-right text at no runtime cost.
   white-space: pre-wrap means newlines in the message text render as
   actual line breaks without us having to inject <br> elements (saves
   tokens for bots, simplifies rendering). */
.msg .body { word-break: break-word; text-wrap: pretty; white-space: pre-wrap; }

/* Aside style — for ({...}) muttered lines (instant acks + wrap-CLI
   parens-brace asides). Italic, dimmed, no parens visible. The actual
   ({/}) wrapping is stripped at render time. */
.msg.aside .body { font-style: italic; color: var(--dim); opacity: 0.75; font-size: 0.95em; }
.msg.aside .body::before { content: "⋯ "; opacity: 0.6; }
/* Radio-chatter continuation: ".. " prefix merges into the prev line.
   Dot gets a dim color so the eye can still see the beat separations
   without it reading as a typo. */
.msg .msg-cont-dot { color: var(--dim); opacity: 0.6; padding: 0 2px; }
/* Audio-first messages: the caption (text / STT transcript) is a
   floating tooltip ABOVE the row — absolutely positioned so the row
   never reflows on hover. Desktop: reveal on row hover. Mobile: tap
   the waveform to toggle .caption-open (handled in app.js). */
.msg.audio-first { position: relative; }
.msg.audio-first .audio-caption {
    position: absolute;
    left: 0; bottom: calc(100% - 2px);
    min-width: 180px; max-width: min(420px, 80vw);
    padding: 6px 8px;
    background: #0b0b0d;
    border: 1px solid var(--edge);
    color: var(--fg);
    font-size: 11px; font-style: normal; line-height: 1.35;
    white-space: normal; word-break: break-word;
    pointer-events: none;
    opacity: 0; visibility: hidden;
    transition: opacity 80ms;
    z-index: 30;
    box-shadow: 0 4px 16px rgba(0,0,0,0.6);
}
.msg.audio-first:hover .audio-caption,
.msg.audio-first.caption-open .audio-caption {
    opacity: 1; visibility: visible;
}
.msg.audio-first .audio-wave { cursor: pointer; }
/* Inline todo checkbox — a "- text" line becomes this. Monochrome,
   compact, survives line-wrapping. Checked state strikes through. */
.todo-item {
    display: inline-flex; align-items: baseline; gap: 6px;
    cursor: pointer; user-select: text;
}
.todo-item .todo-check {
    width: 12px; height: 12px;
    accent-color: #ffc857;
    margin: 0;
    flex-shrink: 0;
    cursor: pointer;
}
.todo-item.done .todo-text,
.todo-check:checked + .todo-text {
    text-decoration: line-through;
    color: var(--dim);
}
.msg .body a, .url-btn { color: var(--fg); text-decoration: underline; }
.msg .body a:hover, .url-btn:hover { opacity: 0.7; }
/* Attached images float right at 20vw. Text from the posting message AND
   from every subsequent message wraps around them until the image clears.
   The anchor uses block-layout href for accessibility; the click handler
   in app.js preventDefaults and opens the lightbox. */
.msg-attach {
    float: right;
    display: block;
    width: 20vw;
    min-width: 120px;
    max-width: 320px;
    aspect-ratio: 1 / 1;       /* reserve square space pre-load — no reflow when image lands */
    margin: 0 0 6px 12px;
    cursor: zoom-in;
    position: relative;
}
.msg-attach img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border: 1px solid var(--edge);
}
.msg-attach:hover img { border-color: var(--fg); }
.msg-attach-video video {
    display: block; width: 100%; height: 100%;
    object-fit: cover;
    border: 1px solid var(--edge);
    background: #000;
}
.msg-attach-video:hover video { border-color: var(--fg); }

/* Click-to-copy creation-id badge in the bottom-left corner of every
   image that has a gen_id. Hidden by default, fades in on image hover.
   Compact so it doesn't cover the picture. */
.img-id-badge {
    position: absolute;
    left: 4px; bottom: 4px;
    background: rgba(0, 0, 0, 0.7);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 3px;
    font: 11px/1 ui-monospace, "SF Mono", Menlo, monospace;
    padding: 3px 6px;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s ease, background 0.12s ease;
}
.msg-attach:hover .img-id-badge,
.img-id-badge:focus { opacity: 1; }
.img-id-badge:hover { background: rgba(0, 0, 0, 0.92); }
.img-id-badge.img-id-badge-copied { background: #1f6f3a; opacity: 1; }
@media (max-width: 480px) {
    .msg-attach { width: 40vw; max-width: 40vw; min-width: 100px; }
}

.line { padding: 3px 0; color: var(--dim); font-size: 12px; }
.line.sys { color: var(--dim); }
.line.err { color: var(--err); }

/* Bots render as plain human-style messages. Intentional: keeps the room
   feeling populated rather than game-like. The ".sys" variant (system
   lines, e.g. kill notices) stays dim + italic but nothing else flags
   bot-ness visually. */

/* Inline voice-message player. Sits in the message body, same line-height
   as text. Intentionally minimal — no container background, no border,
   just the glyph + thin monochrome bars. */
.audio-msg {
    display: inline-flex; align-items: center; gap: 6px;
    vertical-align: baseline;
    margin-left: 4px;
    position: relative;   /* anchor for the floating caption tooltip */
    /* Explicit width so the row doesn't reflow when the play button
       text swaps (▶ → ▮▮) or when the waveform paints in. The play
       button reserves 22px (~2 chars + padding); the wave is 192px;
       gap 6px; total ≈ 220px. */
    width: 220px;
}
.audio-play {
    background: transparent; color: var(--fg);
    border: none; padding: 0 2px;
    font: inherit; line-height: 1; cursor: pointer;
    /* Fixed width — text changes between ▶ and ▮▮ shouldn't reflow. */
    width: 22px; min-width: 22px;
    text-align: left;
    flex-shrink: 0;
}
.audio-play:hover { color: #ffc857; }
.audio-play-glyph { font-size: 10px; letter-spacing: -1px; }
.audio-wave {
    display: inline-block;
    width: 192px; height: 14px; flex-shrink: 0;
    background: transparent;
    image-rendering: pixelated;
    vertical-align: middle;
}
/* Audio file missing (404) — show a thin strikethrough placeholder
   instead of a broken player. The original message text/transcript is
   still visible above it. */
.audio-msg.audio-msg-missing {
    opacity: 0.4;
}
.audio-msg.audio-msg-missing .audio-play { pointer-events: none; }
.audio-msg.audio-msg-missing .audio-play-glyph { color: var(--dim); }
.audio-msg.audio-msg-missing .audio-wave { opacity: 0.3; }
.audio-msg.audio-msg-missing::after {
    content: "(missing)";
    color: var(--dim); font-size: 10px;
    margin-left: 4px;
}

/* Suno-share inline player card. Appears under a chat message when the
   user pastes a https://suno.com/s/... or /song/... link. The mp3 url
   is resolved server-side via /api/suno-resolve, then the same
   audio-msg renderer used for voice messages plays it. */
.suno-card {
    display: block; margin-top: 6px;
    border: 1px solid var(--edge);
    background: rgba(255, 255, 255, 0.02);
    border-radius: 6px;
    overflow: hidden;
    max-width: 330px;
    /* Float right like images so chat text wraps around it. */
    float: right;
    margin-left: 10px; margin-bottom: 6px;
    clear: right;
    /* Layer above the audio-first transcript tooltip (z-index:30) so
       the play button is always clickable even when the hover-revealed
       transcript visually overlaps it. */
    position: relative;
    z-index: 31;
}
.suno-card-inner {
    display: grid;
    grid-template-columns: 64px 1fr;
    gap: 10px;
    padding: 8px;
    align-items: center;
}
.suno-thumb {
    width: 64px; height: 64px; border-radius: 4px;
    background: #181818 center/cover no-repeat;
    display: block;
    flex-shrink: 0;
}
.suno-meta { min-width: 0; }
.suno-title {
    color: var(--fg); font-weight: 500; font-size: 13px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.suno-artist {
    color: var(--dim); font-size: 11px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    margin-bottom: 4px;
}
.suno-card .audio-msg { margin: 4px 0 0; display: flex; }
.suno-link {
    display: inline-block; margin-top: 4px;
    color: var(--dim); font-size: 10px;
    text-decoration: none;
}
.suno-link:hover { color: var(--fg); }
.suno-loading .suno-title, .suno-error .suno-title { color: var(--dim); font-style: italic; }

/* Video shares get a silent autoplay-loop <video> in the same 64×64
   thumb slot as audio shares' cover image. No controls — it's an
   animated thumbnail. Audio waveform below stays interactive. */
.suno-thumb-video {
    width: 64px; height: 64px; border-radius: 4px;
    object-fit: cover;
    background: #000;
    display: block;
    flex-shrink: 0;
}

/* Private messages (kamaji's /priv) — visible only to the target user.
   Rendered distinctively so they're obviously not in the shared log. */
.msg.priv {
    border-left: 2px solid #7a5fff;
    padding-left: 8px;
    margin-left: -10px;
    background: rgba(122, 95, 255, 0.05);
}
.msg.priv .who { color: #b8a9ff; }
.msg.priv .body { color: var(--fg); }
.msg.priv::before {
    content: "(priv)";
    color: var(--dim); font-size: 10px; margin-right: 6px;
}

/* ───────── composer ───────── */
/* The textarea stretches across the full bottom width. Mic + send buttons
   are absolutely positioned on the right end of the textarea, so the
   input region visually extends edge-to-edge even though the buttons
   sit on top of its tail. */
.composer-wrap {
    display: flex; flex-direction: column;
    border-top: 1px solid var(--edge);
    background: var(--bg);
    padding-bottom: var(--safe-b);
}
/* Parascene live-feed grid. Plain inline-block layout (no CSS grid,
   no aspect-ratio constraints) — both of those add per-cell layout
   work that compounds badly with 1600 cells on iOS. Cells are exact
   pixel sizes; the row wraps via white-space:normal. */
.feed-grid {
    --feed-map-url: none;
    margin: 0 0 12px;
    font-size: 0;            /* kill inter-element whitespace */
    line-height: 0;
}
.feed-grid-sentinel {
    height: 1px; width: 100%; margin-bottom: 30vh;
}
.feed-cell {
    /* Each cell is one tile of a single 800×1600 sprite (4×8 of
       200×200 tiles). Display halved to 100×100, so background-size
       = 400×800 and bg-position = -col*100 -row*100. One image fetch
       paints the whole grid — no per-cell <img> decode storm. */
    display: inline-block;
    width: 100px; height: 100px;
    background-color: #111;
    background-image: var(--feed-map-url);
    background-size: 400px 800px;
    background-repeat: no-repeat;
    box-sizing: border-box;
    position: relative;
    vertical-align: top;
    font-size: 12px; line-height: 1;
}
.feed-cell-empty { background-color: #0c0c0c; background-image: none; }
.feed-cell-video video {
    display: block; width: 100px; height: 100px;
    object-fit: cover;
}
.feed-cell-id {
    position: absolute; right: 2px; bottom: 2px;
    font: 9px/1 ui-monospace, "SF Mono", monospace;
    color: #fff; background: rgba(0,0,0,0.7);
    padding: 1px 3px; border-radius: 2px;
    pointer-events: none;
}
.feed-cell-actions {
    position: absolute; left: 0; right: 0; bottom: 0;
    display: flex; gap: 1px;
    opacity: 0; transition: opacity 0.12s ease;
    background: rgba(0,0,0,0.85);
}
.feed-cell:hover .feed-cell-actions { opacity: 1; }
.feed-cell-link {
    position: absolute; top: 2px; left: 2px;
    width: 16px; height: 16px;
    display: flex; align-items: center; justify-content: center;
    color: #fff; background: rgba(0,0,0,0.7);
    text-decoration: none;
    font: 10px/1 ui-monospace, "SF Mono", monospace;
    border-radius: 2px;
    opacity: 0; transition: opacity 0.12s ease;
}
.feed-cell:hover .feed-cell-link { opacity: 1; }
.feed-cell-link:hover { color: #ffc857; }
.feed-cell-btn {
    flex: 1;
    background: transparent; color: #fff;
    border: none; padding: 4px 0;
    font: 10px/1 ui-monospace, "SF Mono", monospace;
    cursor: pointer;
    text-transform: lowercase;
}
.feed-cell-btn:hover { background: rgba(255,255,255,0.1); }
.feed-cell-dim {
    opacity: 0.45;
    pointer-events: none;
}
.feed-cell-dim .feed-cell-actions { display: none; }
.composer {
    display: flex;
    align-items: flex-end;
    position: relative;
    padding: 0;
    background: var(--bg);
}
/* Auto-growing textarea. rows="1" + JS resize keeps the default at exactly
   one line even when the placeholder wraps on narrow viewports (field-sizing
   :content was sizing to the placeholder height, which made the box start
   taller than one line). JS grows it via scrollHeight as the user types. */
.composer textarea {
    display: block;
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    padding: 8px 10px;
    font: inherit; font-size: 16px; line-height: 1.35;
    background: inherit; color: var(--fg);
    border: none; border-radius: 0;
    resize: none; overflow-y: auto;
    min-height: 34px;
    max-height: 40dvh;
}
.composer textarea::placeholder { color: var(--dim); }
.composer textarea:focus { outline: none; }
/* iMessage-style icon cluster on the right of the composer. Pic on the
   left (with leading "+"), mic on the right. Geometric unicode glyphs —
   no emoji. */
.composer-actions {
    display: flex; align-items: center;
    flex: 0 0 auto;
    padding: 0 4px 0 0;
    gap: 0;
}
.composer-actions button {
    background: transparent; color: var(--dim);
    border: none; padding: 6px 8px;
    font: inherit; line-height: 1;
    cursor: pointer;
}
.composer-actions button:hover:not(:disabled) { color: var(--fg); }
.composer-actions .pic-btn { display: inline-flex; align-items: center; gap: 2px; }
.composer-actions .pic-plus { font-size: 14px; opacity: 0.85; }
.composer-actions .pic-glyph { font-size: 16px; }
.composer-actions .pic-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.composer-actions .mic-btn { font-size: 14px; }
.composer-actions .mic-btn.recording {
    color: var(--err);
    animation: mic-pulse 1s ease-in-out infinite;
}

/* Bottom of the sidebar: send (desktop only) and the slim ascii-cam
   launcher. Edge-to-edge buttons, no individual borders. */
.cams-actions {
    display: flex; flex-direction: column;
    flex: 0 0 auto;
    border-top: 1px solid var(--edge);
}
.cams-actions button {
    width: 100%;
    background: var(--bg); color: var(--fg);
    border: none; border-radius: 0;
    padding: 10px 8px;
    font: inherit; font-size: 13px; letter-spacing: 0.5px;
    cursor: pointer;
}
.cams-actions button + button { border-top: 1px solid var(--edge); }
.cams-actions button:hover:not(:disabled) { color: #ffc857; }
/* Mobile: keyboard's return-key submits — no need for a send button. */
@media (max-width: 480px) {
    .cams-actions .send-btn { display: none; }
}

/* Universal audio transport row — three equal-width buttons sharing
   the same row width as the rest of the sidebar actions. Same vertical
   padding as mic/pic so it doesn't change the column rhythm. Buttons
   sit in a flex row inside the .cams-actions wrapper. */
.transport-row { flex-direction: row; }
.transport-row .tr-btn {
    flex: 1 1 0;
    width: auto;
    padding: 10px 0;
    border: none;
    background: var(--bg); color: var(--dim);
    font: inherit; font-size: 14px; letter-spacing: 0;
    cursor: pointer;
}
.transport-row .tr-btn + .tr-btn { border-left: 1px solid var(--edge); border-top: none; }
.transport-row .tr-btn:hover:not(:disabled) { color: #ffc857; }
.transport-row .tr-btn:disabled { opacity: 0.35; cursor: default; }
.transport-row .tr-btn.tr-active { color: var(--fg); }
[hidden].transport-row { display: none !important; }
.cams-actions .mic-btn.recording {
    color: var(--err);
    animation: mic-pulse 1s ease-in-out infinite;
}
@keyframes mic-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

/* Slash-command popup. Lives above the composer, inside the same wrap so the
   page layout doesn't jump when it shows/hides. */
.slash-popup {
    padding: 4px 6px;
    border-bottom: 1px solid var(--edge);
    background: var(--surf);
    max-height: 30vh;
    overflow-y: auto;
    font-size: 12px;
}
.slash-item {
    display: flex; gap: 8px; align-items: baseline;
    padding: 4px 8px; border-radius: 3px;
    cursor: pointer;
}
.slash-item:hover, .slash-item.active { background: var(--edge); }
.slash-cmd { color: var(--fg); }
.slash-hint { color: var(--dim); font-style: italic; }
.slash-desc { color: var(--dim); margin-left: auto; }

/* ───────── image lightbox ───────── */
.lightbox {
    position: fixed; inset: 0; z-index: 200;
    background: rgba(0, 0, 0, 0.90);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    display: flex; align-items: center; justify-content: center;
    padding: 28px 20px;
    cursor: zoom-out;
}
.lb-close {
    position: fixed; top: 12px; right: 14px;
    background: rgba(0,0,0,0.6); color: var(--fg);
    border: 1px solid var(--edge);
    font: inherit; font-size: 20px; line-height: 1;
    width: 34px; height: 34px; padding: 0;
    cursor: pointer;
    z-index: 1;
}
.lb-close:hover { border-color: var(--fg); }

/* Layout: desktop flex row (cmts | img | composer). Mobile stacks. */
.lb-layout {
    display: flex; flex-direction: row; align-items: stretch;
    gap: 14px;
    width: 100%; height: 100%;
    max-width: 1400px;
    cursor: default;
}
.lb-panel {
    flex: 0 0 240px;
    display: flex; flex-direction: column; gap: 6px;
    overflow: hidden;
    min-width: 0;
}
.lb-panel-title {
    font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase;
    color: var(--dim);
    padding-bottom: 4px; border-bottom: 1px solid var(--edge);
    flex-shrink: 0;
}
.lb-image-wrap {
    flex: 1 1 auto;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 10px; min-width: 0; min-height: 0;
}
.lb-image {
    max-width: 100%; max-height: calc(100% - 40px);
    border: 1px solid var(--edge);
    display: block;
    object-fit: contain;
}
.lb-controls {
    display: flex; gap: 10px; align-items: center;
    flex-wrap: wrap; justify-content: center;
}
.lb-mini-btn {
    background: transparent; color: var(--dim);
    border: 1px solid transparent;
    padding: 4px 8px; font: inherit; font-size: 11px;
    cursor: pointer; text-decoration: none;
    letter-spacing: 0.5px;
}
.lb-mini-btn:hover:not(:disabled) { color: var(--fg); border-color: var(--edge); }
.lb-mini-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.lb-mini-btn[aria-pressed="true"] { color: #ffc857; }

.lb-comments-list {
    flex: 1 1 auto; overflow-y: auto;
    font-size: 12px; line-height: 1.4;
    padding-right: 4px;
}
.lb-comment {
    padding: 4px 0; border-bottom: 1px solid var(--edge);
    word-break: break-word;
}
.lb-comment-who { color: var(--dim); margin-right: 4px; }
.lb-comment-body { color: var(--fg); }
.lb-empty { color: var(--dim); font-style: italic; padding: 8px 0; font-size: 11px; }

.lb-comment-form { display: flex; flex-direction: column; gap: 6px; }
.lb-comment-input {
    background: var(--surf); color: var(--fg);
    border: 1px solid var(--edge);
    font: inherit; font-size: 13px; line-height: 1.35;
    padding: 6px 8px; resize: none;
    min-height: 60px;
}
.lb-comment-input:focus { outline: none; border-color: var(--fg); }
.lb-comment-send {
    align-self: flex-end;
    background: transparent; color: var(--fg);
    border: 1px solid var(--edge);
    padding: 4px 12px; font: inherit; font-size: 12px;
    cursor: pointer;
}
.lb-comment-send:hover:not(:disabled) { border-color: var(--fg); }
.lb-comment-hint { color: var(--dim); font-size: 11px; min-height: 1em; }

/* ───── lightbox: inline conjure interface ───── */
/* When an image is opened, we load its vision analysis and overlay a
   numbered dot per detected character + a side panel listing each
   character with either a "conjure" button or a "live as @slug" badge
   if a bot already exists for that character. Click a dot to scroll
   the corresponding card into view. */
.lb-image-wrap { position: relative; }
.lb-char-overlay {
    position: absolute; inset: 0;
    pointer-events: none; /* children re-enable */
}
.lb-char-dot {
    pointer-events: auto;
    position: absolute;
    transform: translate(-50%, -50%);
    width: 22px; height: 22px;
    border-radius: 50%;
    background: rgba(0,0,0,0.75);
    border: 1px solid var(--fg);
    color: var(--fg);
    font: inherit; font-size: 11px; line-height: 1;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
}
.lb-char-dot:hover { background: rgba(255,200,87,0.25); border-color: #ffc857; color: #ffc857; }
.lb-char-dot-live { background: rgba(120,200,120,0.35); border-color: #78c878; color: #bfe7bf; }
.lb-char-dot-live:hover { background: rgba(120,200,120,0.55); color: #fff; }

.lb-char-list {
    flex: 1 1 auto; overflow-y: auto;
    display: flex; flex-direction: column; gap: 8px;
    padding-right: 4px;
    font-size: 12px; line-height: 1.4;
}
.lb-char-card {
    padding: 8px; border: 1px solid var(--edge);
    display: flex; flex-direction: column; gap: 6px;
    transition: border-color 150ms;
}
.lb-char-card.live { border-color: #3a5a3a; background: rgba(60,100,60,0.08); }
.lb-char-card.lb-char-flash { border-color: #ffc857; }
.lb-char-head {
    font-size: 12px; letter-spacing: 0.5px;
    display: flex; align-items: center; gap: 6px;
    color: var(--fg);
}
.lb-char-num {
    display: inline-flex; align-items: center; justify-content: center;
    width: 18px; height: 18px; border-radius: 50%;
    border: 1px solid var(--edge); font-size: 10px; color: var(--dim);
}
.lb-char-badge {
    margin-left: auto;
    font-size: 9px; letter-spacing: 1.5px; text-transform: uppercase;
    color: #bfe7bf; border: 1px solid #3a5a3a; padding: 1px 5px;
}
.lb-char-bio { color: var(--dim); font-size: 11px; line-height: 1.35; }
.lb-char-conjure {
    align-self: flex-start;
    background: transparent; color: var(--fg);
    border: 1px solid var(--edge);
    padding: 3px 10px; font: inherit; font-size: 11px;
    cursor: pointer; letter-spacing: 0.5px;
}
.lb-char-conjure:hover:not(:disabled) { border-color: var(--fg); }
.lb-char-conjure:disabled { opacity: 0.5; cursor: not-allowed; }

/* Mobile (narrow): stack panels. Image is middle, composer below, comments
   below that — both collapse into short scrollable blocks in the safe bars. */
@media (max-width: 720px) {
    .lightbox { padding: 10px 8px; }
    .lb-layout { flex-direction: column; }
    .lb-panel { flex: 0 0 auto; max-height: 28vh; }
    .lb-panel.lb-composer { order: 1; }
    .lb-image-wrap { order: 2; flex: 1 1 auto; min-height: 30vh; }
    .lb-panel.lb-conjure { order: 3; }
    .lb-panel.lb-comments { order: 4; }
    .lb-comment-input { min-height: 40px; }
    .lb-image { max-height: 100%; }
    .lb-char-dot { width: 28px; height: 28px; font-size: 13px; }
}

/* ───────── conjure character picker ───────── */
.conjure-picker {
    position: fixed; inset: 0; z-index: 260;
    background: rgba(0, 0, 0, 0.88);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    display: flex; align-items: flex-start; justify-content: center;
    padding: 24px; overflow-y: auto;
}
.cp-panel {
    background: var(--surf); border: 1px solid var(--edge);
    max-width: 640px; width: 100%;
    padding: 14px; display: flex; flex-direction: column; gap: 10px;
    position: relative;
}
.cp-close {
    position: absolute; top: 8px; right: 8px;
    background: transparent; color: var(--fg);
    border: 1px solid var(--edge); width: 28px; height: 28px;
    font-size: 16px; line-height: 1; padding: 0; cursor: pointer;
}
.cp-title { font-size: 13px; letter-spacing: 1px; text-transform: uppercase; color: var(--fg); }
.cp-sub { color: var(--dim); font-size: 12px; }
.cp-img-wrap {
    position: relative; display: block;
    width: 100%; max-width: 420px; margin: 0 auto;
}
.cp-img { width: 100%; height: auto; display: block; border: 1px solid var(--edge); }
.cp-char {
    position: absolute; transform: translate(-50%, -50%);
    width: 30px; height: 30px; border-radius: 50%;
    border: 2px solid #ffc857; background: rgba(0,0,0,0.75); color: #ffc857;
    font: inherit; font-weight: 700; font-size: 14px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; padding: 0;
    box-shadow: 0 0 0 3px rgba(0,0,0,0.6);
}
.cp-char:hover, .cp-char:focus-visible {
    background: #ffc857; color: #000; outline: none;
}
.cp-list { display: flex; flex-direction: column; gap: 6px; }
.cp-card { border: 1px solid var(--edge); padding: 8px; background: var(--bg); }
.cp-card-head { font-size: 13px; color: #ffc857; margin-bottom: 4px; }
.cp-card-desc { font-size: 12px; color: var(--fg); line-height: 1.4; margin-bottom: 6px; }
.cp-pick { font-size: 11px; padding: 4px 10px; }

/* ───────── me-tile ───────── */
/* The signed-in user's fixed slot at the top of the right column. When a
   cam is on, .self canvas fills this tile. When off, a generic "avatar"
   pane (either a /gen image or just the username) takes its place. Tap
   anywhere on the tile to open the profile panel (unless interacting with
   the camera — camera click is handled separately). */
.me-tile {
    background: #000;
}
.me-tile-body {
    width: 100%; height: 100%;
    position: relative;
}
.me-tile .cam-start {
    cursor: pointer;
}
.me-tile .self-name {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    color: var(--fg); font-size: 11px; letter-spacing: 1px;
    pointer-events: none; text-shadow: 0 0 3px rgba(0,0,0,0.8);
}
.me-tile .me-avatar {
    width: 100%; height: 100%;
    background-size: cover; background-position: center;
    background-color: #000;
}
.me-tile video.me-avatar, .avatar-video {
    object-fit: cover;
    width: 100%; height: 100%;
    background: #000;
}
.pp-current-video {
    object-fit: cover;
    background: #000;
}
.pp2-avatar.pp2-avatar-video { background: #000; overflow: hidden; }
.pp2-avatar-video video {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
}
/* ASCII cam launcher — slim button at the bottom of the cam column,
   roughly 10% shorter than the send button above it. When the cam is
   running, the launcher hides and the live cam takes the me-tile slot. */
.cam-launcher {
    flex: 0 0 auto;
    display: flex; align-items: center; justify-content: center;
    gap: 6px;
    background: var(--bg);
    border: none;
    color: var(--dim);
    font: inherit; font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
    cursor: pointer;
    transition: color 80ms, background 80ms;
}
.cam-launcher:hover, .cam-launcher:focus-visible {
    color: var(--fg); outline: none;
}
.cam-launcher-glyph { font-size: 13px; line-height: 1; opacity: 0.75; }
.cam-launcher-label { opacity: 0.85; }
.cam-launcher-slim {
    width: 100%;
    /* Send button has padding 10px → ~38px tall. 8px padding → ~34px = 10% shorter. */
    padding: 8px 8px;
}

/* Tiny always-available affordance to open the profile panel, regardless
   of what's in the me-tile (cam on, avatar, username, sign-in). Sits in
   the bottom-left corner and only shows on hover to stay minimal. */
.me-tile .me-profile-btn {
    position: absolute; left: 4px; bottom: 4px;
    width: 16px; height: 16px;
    background: rgba(0,0,0,0.6); color: var(--fg);
    border: none; border-radius: 0;
    font: inherit; font-size: 10px; line-height: 16px;
    text-align: center; padding: 0;
    cursor: pointer;
    opacity: 0;
    transition: opacity 80ms;
}
.me-tile:hover .me-profile-btn,
.me-tile .me-profile-btn:focus-visible { opacity: 1; }

/* ───────── self profile panel ───────── */
.profile-panel {
    position: fixed; inset: 0; z-index: 250;
    background: rgba(0, 0, 0, 0.94);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    overflow-y: auto;
    padding: calc(env(safe-area-inset-top, 0px) + 20px) 20px 20px;
}
.profile-panel[hidden] { display: none; }
.pp-close {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + 10px);
    right: 14px;
    background: rgba(0,0,0,0.6); color: var(--fg);
    border: 1px solid var(--edge);
    width: 34px; height: 34px; padding: 0;
    font: inherit; font-size: 20px; line-height: 1; cursor: pointer;
    z-index: 2;
}
.pp-close:hover { border-color: var(--fg); }
.pp-inner {
    max-width: 860px; margin: 0 auto;
    display: flex; flex-direction: column; gap: 22px;
}
.pp-hero {
    display: flex; gap: 16px; align-items: flex-start;
}
.pp-current {
    width: 160px; height: 160px;
    background: #000;
    object-fit: cover;
    border: 1px solid var(--edge);
}
.pp-meta { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.pp-handle { font-size: 18px; color: var(--fg); }
.pp-hint { font-size: 12px; color: var(--dim); line-height: 1.45; }
.pp-row { display: flex; gap: 8px; flex-wrap: wrap; }
.pp-section h3 {
    font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase;
    color: var(--dim);
    margin: 0 0 8px 0;
    padding-bottom: 4px; border-bottom: 1px solid var(--edge);
}
.pp-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 6px;
}
.pp-grid .pp-cell {
    aspect-ratio: 1 / 1;
    background: #000;
    background-size: cover; background-position: center;
    cursor: pointer;
    border: 1px solid transparent;
    transition: border-color 80ms;
    position: relative;
}
.pp-grid .pp-cell:hover { border-color: var(--fg); }
.pp-grid .pp-cell.current { border-color: #ffc857; }
.pp-grid-empty {
    grid-column: 1 / -1;
    color: var(--dim); font-style: italic; font-size: 12px; padding: 14px 0;
}
@media (max-width: 640px) {
    .pp-hero { flex-direction: column; }
    .pp-current { width: 120px; height: 120px; }
}

/* ───────── verify modal ───────── */
.modal { position: fixed; inset: 0; background: rgba(0,0,0,0.85); z-index: 100; display: flex; align-items: center; justify-content: center; }
.modal[hidden] { display: none; }
.modal-card { border: 1px solid var(--edge); padding: 20px; background: var(--bg); max-width: 360px; width: 90%; }
.modal-title { margin-bottom: 14px; font-size: 14px; letter-spacing: 1px; text-transform: uppercase; }
.modal-providers { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.modal-providers .prov { width: 100%; padding: 10px 12px; text-align: center; }
.modal-sep { color: var(--dim); font-size: 11px; text-align: center; margin-top: 4px; }
.modal-form { display: flex; gap: 6px; margin-bottom: 8px; }
.modal-form input { flex: 1; }
.modal-msg { color: var(--dim); font-size: 12px; margin-top: 8px; min-height: 1em; }
.modal-close { margin-top: 12px; width: 100%; }
.modal-or {
    color: var(--dim); font-size: 11px; text-align: center;
    text-transform: uppercase; letter-spacing: 1px; margin: 14px 0 10px;
    position: relative;
}
.modal-or::before, .modal-or::after {
    content: ""; position: absolute; top: 50%; height: 1px; width: 38%;
    background: var(--edge);
}
.modal-or::before { left: 0; }
.modal-or::after  { right: 0; }
.modal-google, .modal-google-link, .modal-parascene {
    display: block; width: 100%; text-align: center;
    padding: 10px 12px; text-decoration: none;
    border: 1px solid var(--edge); color: var(--fg);
    background: transparent;
}
.modal-google:hover, .modal-google-link:hover, .modal-parascene:hover {
    border-color: var(--fg);
}
.modal-google-link { margin-top: 8px; cursor: pointer; }
.modal-parascene { margin-bottom: 8px; cursor: pointer; }

/* ───────── profile panel (myspace-style) ───────── */
/* Full-screen overlay with a 3-column sheet: info on the left, the big
   avatar + custom-HTML + DM composer in the middle, wall + guestbook
   on the right. Default aesthetic is dark/understated; the custom
   panel iframe is where a user can go garish. Mobile stacks everything
   into a single scrollable column. */
.profile-panel {
    position: fixed; inset: 0; z-index: 9000;
    background: rgba(4, 4, 6, 0.94);
    display: flex; align-items: stretch; justify-content: center;
    padding: 24px;
    overflow: auto;
}
.pp2-close {
    position: fixed; top: 12px; right: 14px; z-index: 10;
    background: transparent; color: var(--dim);
    border: 1px solid var(--edge); width: 32px; height: 32px;
    font: inherit; font-size: 18px; line-height: 28px; cursor: pointer;
}
.pp2-close:hover { color: var(--fg); border-color: var(--fg); }
.pp2-sheet {
    display: grid;
    grid-template-columns: 280px 1fr 320px;
    gap: 20px;
    width: 100%; max-width: 1400px;
    background: #0b0b0d;
    border: 1px solid var(--edge);
    padding: 24px;
    background-size: cover; background-position: center;
}
.pp2-col {
    display: flex; flex-direction: column; gap: 14px;
    min-width: 0;
}
.pp2-info .pp2-avatar {
    width: 100%; aspect-ratio: 1 / 1;
    background: #000 center / cover no-repeat;
    border: 1px solid var(--edge);
}
.pp2-info .pp2-avatar-empty { background: repeating-linear-gradient(45deg, #111, #111 8px, #161616 8px, #161616 16px); }
.pp2-name { font-size: 18px; letter-spacing: 0.5px; color: var(--fg); }
.pp2-meta { color: var(--dim); font-size: 11px; letter-spacing: 0.5px; text-transform: uppercase; }
.pp2-bio { color: var(--fg); font-size: 13px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; }
.pp2-botinfo { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.pp2-botinfo-row { font-size: 11px; color: var(--dim); line-height: 1.4; }
.pp2-botinfo-label { text-transform: uppercase; letter-spacing: 1px; margin-right: 6px; opacity: 0.6; }
.pp2-botinfo-val { color: var(--fg); }
.pp2-source-link { color: #ffc857; text-decoration: none; font-size: 11px; }
.pp2-source-link:hover { text-decoration: underline; }

.pp2-top-friends { margin-top: 6px; display: flex; flex-direction: column; gap: 6px; }
.pp2-top-friends-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 4px;
}
.pp2-tf-cell {
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    cursor: pointer; text-decoration: none;
    min-width: 0;
}
.pp2-tf-img {
    width: 100%; aspect-ratio: 1 / 1;
    background: #111 center / cover no-repeat;
    border: 1px solid var(--edge);
}
.pp2-tf-img.pp2-tf-empty { background: repeating-linear-gradient(45deg, #151515, #151515 4px, #1a1a1a 4px, #1a1a1a 8px); }
.pp2-tf-name {
    font-size: 9px; color: var(--dim); letter-spacing: 0.5px;
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pp2-tf-cell:hover .pp2-tf-img { border-color: var(--fg); }
.pp2-tf-cell:hover .pp2-tf-name { color: var(--fg); }

.pp2-audio { margin-top: 4px; }
.pp2-audio-el { width: 100%; height: 32px; }
.pp2-audio-caption { font-size: 10px; color: var(--dim); letter-spacing: 1.5px; text-transform: uppercase; margin-top: 4px; }

.pp2-custom {
    border: 1px solid var(--edge);
    min-height: 220px;
    background: #040404;
}
.pp2-custom-frame {
    width: 100%; height: 100%; min-height: 220px;
    border: none; background: transparent;
    display: block;
}
.pp2-custom:empty { display: none; }

/* Fullbleed mode: customHtml has data-slot markers. The iframe takes
   the entire panel; pp2-sheet is just a container for the iframe. */
.profile-panel.pp2-fullbleed .pp2-sheet {
    grid-template-columns: 1fr; padding: 0; gap: 0;
}
.pp2-custom-fullbleed {
    width: 100%; min-height: 80vh; height: 80vh;
}

.pp2-dm { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.pp2-dm-thread {
    max-height: 240px; overflow-y: auto;
    border: 1px solid var(--edge);
    background: rgba(0,0,0,0.15);
    border-radius: 6px;
    padding: 8px 10px;
    display: flex; flex-direction: column; gap: 4px;
    font-size: 13px; line-height: 1.4;
}
.pp2-dm-msg { display: flex; gap: 6px; }
.pp2-dm-msg.pp2-dm-mine { color: var(--fg); }
.pp2-dm-from { color: var(--dim); font-size: 11px; min-width: 54px; }
.pp2-dm-text { word-break: break-word; }
.pp2-dm-empty { color: var(--dim); font-style: italic; font-size: 12px; }
.pp2-dm-msg { position: relative; }
.pp2-dm-attach {
    display: block; width: 220px; max-width: 100%;
    aspect-ratio: 1 / 1;        /* reserve square pre-load — no reflow */
    object-fit: cover;
    margin-top: 4px; border: 1px solid var(--edge); border-radius: 4px;
}
.pp2-dm-del {
    position: absolute; top: 0; right: 0;
    background: transparent; color: var(--dim);
    border: none; padding: 0 4px;
    font-size: 12px; line-height: 1;
    cursor: pointer; opacity: 0;
    transition: opacity 0.12s ease;
}
.pp2-dm-msg:hover .pp2-dm-del { opacity: 0.7; }
.pp2-dm-del:hover { opacity: 1; color: #ff6e6e; }
/* Peach badge — appears on hover after the X for any DM message that
   was a softened/rewritten reply (the bot toned down explicit content
   instead of refusing). */
.pp2-dm-rewrote {
    position: absolute; top: 0; right: 16px;
    font-size: 12px; line-height: 1;
    opacity: 0; cursor: default;
    transition: opacity 0.12s ease;
    pointer-events: auto;
}
.pp2-dm-msg:hover .pp2-dm-rewrote { opacity: 0.85; }
.pp2-dm-typing { opacity: 0.6; font-size: 12px; }
.pp2-dm-typing .pp2-dm-text em { font-style: italic; }
.pp2-section-title {
    font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase;
    color: var(--dim); padding-bottom: 4px; border-bottom: 1px solid var(--edge);
}
.pp2-dm-form, .pp2-wall-form, .pp2-guest-form {
    display: flex; flex-direction: column; gap: 6px;
}
.pp2-guest-form { flex-direction: row; }
.pp2-dm-input, .pp2-wall-input, .pp2-guest-input {
    background: #060606; color: var(--fg); border: 1px solid var(--edge);
    font: inherit; font-size: 12px; padding: 6px 8px; resize: vertical;
}
.pp2-guest-input { flex: 1; }
.pp2-dm-input:focus, .pp2-wall-input:focus, .pp2-guest-input:focus,
.pp2-ed-form textarea:focus, .pp2-ed-form input:focus {
    outline: none; border-color: var(--fg);
}
.pp2-btn {
    background: transparent; color: var(--fg);
    border: 1px solid var(--edge);
    padding: 5px 14px; font: inherit; font-size: 11px;
    cursor: pointer; letter-spacing: 1px; text-transform: uppercase;
    align-self: flex-end;
}
.pp2-btn:hover:not(:disabled) { border-color: var(--fg); background: #111; }
.pp2-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.pp2-wall-list, .pp2-guest-list {
    display: flex; flex-direction: column; gap: 10px;
    max-height: 48vh; overflow-y: auto;
    font-size: 12px; line-height: 1.4;
    padding-right: 4px;
}
.pp2-wall-post { border-bottom: 1px solid var(--edge); padding-bottom: 8px; }
.pp2-wall-head { display: flex; justify-content: space-between; align-items: baseline; font-size: 11px; margin-bottom: 3px; }
.pp2-wall-from { color: var(--dim); cursor: pointer; text-decoration: none; }
.pp2-wall-from:hover { color: var(--fg); }
.pp2-wall-ts { color: var(--dim); font-size: 10px; }
.pp2-wall-body { color: var(--fg); word-break: break-word; white-space: pre-wrap; }
.pp2-guest-entry { display: flex; gap: 6px; align-items: baseline; font-size: 11px; border-bottom: 1px solid var(--edge); padding-bottom: 5px; }
.pp2-guest-from { color: var(--dim); cursor: pointer; text-decoration: none; flex-shrink: 0; }
.pp2-guest-text { color: var(--fg); flex: 1; word-break: break-word; }
.pp2-guest-ts { color: var(--dim); font-size: 10px; flex-shrink: 0; }
.pp2-empty { color: var(--dim); font-style: italic; padding: 8px 0; font-size: 11px; }

.pp2-edit, .pp2-signout {
    position: fixed; bottom: 24px; z-index: 10;
    background: #111; color: var(--fg);
    border: 1px solid var(--edge);
    padding: 8px 16px; font: inherit; font-size: 11px; letter-spacing: 1.5px;
    text-transform: uppercase; cursor: pointer;
}
.pp2-edit { right: 24px; }
.pp2-signout { right: 156px; }
.pp2-edit:hover, .pp2-signout:hover { border-color: var(--fg); background: #1a1a1a; }

/* Editor modal */
.pp2-editor {
    position: fixed; inset: 0; z-index: 10000;
    background: rgba(0, 0, 0, 0.82);
    display: flex; align-items: center; justify-content: center;
    padding: 18px;
}
.pp2-ed-sheet {
    background: #0b0b0d; border: 1px solid var(--edge);
    padding: 24px; max-width: 700px; width: 100%; max-height: 92vh; overflow-y: auto;
    position: relative;
}
.pp2-ed-close {
    position: absolute; top: 10px; right: 12px;
    background: transparent; color: var(--dim);
    border: none; font: inherit; font-size: 18px; cursor: pointer;
}
.pp2-ed-close:hover { color: var(--fg); }
.pp2-ed-title { margin: 0 0 18px; font-size: 14px; letter-spacing: 1px; text-transform: uppercase; color: var(--fg); }
.pp2-ed-form { display: flex; flex-direction: column; gap: 14px; }
.pp2-ed-row { display: flex; flex-direction: column; gap: 4px; }

/* Linked-logins panel inside the profile editor. List of currently-
   linked credentials + add/unlink controls. */
.pp2-ed-creds {
    border: 1px solid var(--edge);
    border-radius: 4px;
    padding: 8px;
    display: flex; flex-direction: column; gap: 6px;
    font-size: 12px;
}
.pp2-ed-creds-loading, .pp2-ed-creds-empty { color: var(--dim); font-style: italic; }
.pp2-ed-cred {
    display: grid;
    grid-template-columns: 70px 1fr auto auto;
    gap: 8px; align-items: center;
}
.pp2-ed-cred-prov  { color: var(--fg); text-transform: lowercase; }
.pp2-ed-cred-id    { color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pp2-ed-cred-date  { color: var(--dim); font-size: 10px; }
.pp2-ed-cred-unlink {
    background: transparent; border: 1px solid var(--edge);
    color: var(--dim); padding: 2px 6px; cursor: pointer;
    font-size: 10px; border-radius: 3px;
}
.pp2-ed-cred-unlink:hover:not(:disabled) { color: #ff6e6e; border-color: #ff6e6e; }
.pp2-ed-cred-unlink:disabled { opacity: 0.4; cursor: default; }
.pp2-ed-cred-adders {
    display: flex; gap: 8px; flex-wrap: wrap; padding-top: 6px;
    border-top: 1px solid var(--edge); margin-top: 4px;
}
.pp2-ed-cred-add {
    color: var(--fg); text-decoration: none; font-size: 11px;
    border: 1px solid var(--edge); padding: 4px 8px; border-radius: 3px;
    background: transparent; cursor: pointer;
}
.pp2-ed-cred-add:hover:not(.pp2-ed-cred-disabled) { border-color: var(--fg); }
.pp2-ed-cred-disabled { opacity: 0.4; cursor: default; }
.pp2-ed-cred-have { color: var(--dim); font-size: 11px; padding: 4px 0; }
.pp2-ed-row span {
    font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--dim);
}
.pp2-ed-form textarea, .pp2-ed-form input {
    background: #060606; color: var(--fg); border: 1px solid var(--edge);
    font: inherit; font-size: 12px; line-height: 1.4; padding: 6px 8px; resize: vertical;
    width: 100%; box-sizing: border-box;
}
.pp2-ed-mono { font-family: "Menlo", "SF Mono", monospace; font-size: 11px; }
.pp2-ed-actions { display: flex; gap: 10px; align-items: center; margin-top: 4px; }
.pp2-ed-hint { flex: 1; font-size: 10px; color: var(--dim); line-height: 1.3; }
.pp2-ed-example { color: #ffc857; text-decoration: none; }
.pp2-ed-example:hover { text-decoration: underline; }

/* Mobile: single column, stacked. Info first, main middle, social last. */
@media (max-width: 900px) {
    .profile-panel { padding: 12px; }
    .pp2-sheet { grid-template-columns: 1fr; padding: 14px; }
    .pp2-info .pp2-avatar { max-width: 220px; margin: 0 auto; }
    .pp2-custom { min-height: 180px; }
    .pp2-wall-list, .pp2-guest-list { max-height: 40vh; }
    .pp2-edit { bottom: 12px; right: 12px; }
}

/* ───────── jank-isolation toggles ───────── */
/* Collapsible panel above the composer. Each checkbox flips a body
   class that hides one element type via the rules below — letting us
   isolate the actual source of scroll jank by elimination. */
.debug-toggles {
    border-top: 1px dashed var(--edge);
    background: var(--bg);
    color: var(--dim);
    font: 11px ui-monospace, "SF Mono", monospace;
}
.debug-toggles details { padding: 0; }
.debug-toggles summary {
    padding: 4px 8px;
    cursor: pointer;
    list-style: none;
    opacity: 0.6;
    letter-spacing: 1px;
    text-transform: uppercase;
    font-size: 9px;
}
.debug-toggles summary::-webkit-details-marker { display: none; }
.debug-toggles summary::before { content: "▸ "; }
.debug-toggles details[open] summary::before { content: "▾ "; }
.debug-toggles summary:hover { color: var(--fg); }
.debug-toggles-grid {
    display: flex; flex-wrap: wrap; gap: 4px 12px;
    padding: 4px 8px 8px;
}
.debug-toggles label {
    display: inline-flex; align-items: center; gap: 4px;
    cursor: pointer;
}
.debug-toggles input { margin: 0; }

body.no-avatar-video .avatar-video { display: none !important; }
body.no-feed .feed-grid,
body.no-feed .chat-feed-boundary,
body.no-feed .feed-grid-sentinel { display: none !important; }
body.no-suno .suno-card { display: none !important; }
body.no-audio .audio-msg { display: none !important; }
body.no-inline-video .msg-attach-video { display: none !important; }
body.no-inline-photo .msg-attach:not(.msg-attach-video) { display: none !important; }
body.no-msg-del .msg-del { display: none !important; }
body.no-who .who { display: none !important; }
body.no-cam-tiles .cams-top { display: none !important; }
body.no-msg-img-bg .msg .avatar,
body.no-msg-img-bg .msg [style*="background-image"] { background-image: none !important; }
