:root{
  --bg:#0c0c10;--card:#14141a;--card2:#1c1c24;--bdr:#2a2a38;
  --text:#dddde8;--muted:#6b6b82;--hint:#3e3e52;
  --accent:#7c5af0;--accent2:#a07cf8;--green:#3dd68c;--red:#f05a7c;--yellow:#f0c05a;
  --r:12px;--r2:8px;
}
*{box-sizing:border-box}
html,body{margin:0;padding:0;min-height:100%;background:var(--bg);color:var(--text);
  font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;font-size:15px;line-height:1.6;
  overflow-x:hidden}
a{color:var(--accent2);text-decoration:none}
a:hover{text-decoration:underline}

.wrap{margin:0 auto;padding:32px 20px 60px}
/* Desktop is capped at a normal 1920x1080-monitor width, not truly 100% — on an ultrawide, letting
   it stretch edge-to-edge turned paragraph text and form fields into uncomfortably long lines.
   Mobile (.wrap.mobile, below) has no cap — phone screens are never wide enough for this to matter. */
.wrap:not(.mobile){max-width:1920px}
.brand{display:flex;align-items:center;gap:10px;margin-bottom:28px}
.brand .logo{font-size:26px}
.brand .name{font-size:18px;font-weight:700}
.brand .tag{font-size:11.5px;color:var(--muted);margin-left:4px}

h1{font-size:22px;margin:0 0 6px}
h2{font-size:16px;margin:0 0 12px}
p.lead{color:var(--muted);margin:0 0 24px;font-size:14px}

.card{background:var(--card);border:1px solid var(--bdr);border-radius:var(--r);padding:18px 20px;margin-bottom:16px}
.companion-card{display:flex;flex-direction:column;gap:6px}
.companion-card .name{font-size:16px;font-weight:700}
.companion-card .meta{font-size:12.5px;color:var(--muted)}
.companion-card .desc{font-size:13.5px;color:var(--text);opacity:.85}
.companion-card .row{display:flex;justify-content:space-between;align-items:center;gap:10px}

.field{margin-bottom:14px}
.field label{display:block;font-size:11.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}
/* font-size 16px, not 14px — iOS Safari/WKWebView auto-zooms the whole viewport on focus for
   any text input rendering below 16px, then leaves it zoomed in after blur until the user
   manually pinches back out. 16px is below the visual size difference threshold that would
   otherwise trigger this. Applies to every .field input, not just chat — same bug would hit
   login/register/claim-code fields on mobile too. */
.field input,.field textarea{width:100%;background:var(--card2);border:1px solid var(--bdr);border-radius:var(--r2);
  padding:10px 12px;color:var(--text);font-size:16px;outline:none;resize:vertical;font-family:inherit;transition:border-color .15s}
.field input:focus,.field textarea:focus{border-color:var(--accent)}

.btn{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--bdr);border-radius:var(--r2);
  padding:10px 18px;font-size:13.5px;cursor:pointer;background:var(--card2);color:var(--text);transition:all .15s}
.btn:hover{border-color:var(--accent);color:var(--accent2)}
.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
.btn.primary:hover{background:var(--accent2);color:#fff}
.btn:disabled{opacity:.5;cursor:default}

.msg{font-size:12.5px;min-height:16px;margin:8px 0}
.msg.err{color:var(--red)}
.msg.ok{color:var(--green)}

/* Companions page nav (2026-07-18) — Companions/Roleplay Chars/Creative Writing Companions each
   fold "Unused X" into a small submenu instead of a flat row of 6+ buttons. */
.tabs{display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap;align-items:flex-start}
.tab-group{display:flex;flex-direction:column;gap:4px}
.tab-group-row{display:flex;gap:2px}
.tab-group-toggle{background:none;border:1px solid var(--bdr);border-radius:var(--r2);color:var(--muted);
  cursor:pointer;font-size:11px;padding:0 8px;transition:all .15s}
.tab-group-toggle:hover{border-color:var(--accent);color:var(--accent2)}
.tab-group-toggle.open{transform:rotate(180deg)}
.tab-group-menu{display:none;flex-direction:column;gap:4px;padding-left:4px}
.tab-group-menu.open{display:flex}

.nav-links{display:flex;gap:16px;font-size:12.5px;margin-top:24px;color:var(--muted)}

/* Chat */
.chat-box{background:var(--card);border:1px solid var(--bdr);border-radius:var(--r);
  height:60vh;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;margin-bottom:12px}
/* min-width:0 overrides the flex-item default (min-width:auto), which otherwise refuses to
   shrink a bubble below its content's natural size — a generated photo (768x768) would then
   ignore max-width entirely and force the whole page into horizontal scroll on a narrow mobile
   viewport. Without this, the page visibly shifts left/right as soon as a photo lands. */
/* No white-space:pre-wrap here (deliberately) — text content always lives in the child
   .bubble-text div now (which sets its own white-space), never directly inside .bubble. Real
   bug when this WAS pre-wrap: PHP's server-rendered history template has literal indentation/
   newlines between </div> (bubble-text) and the next sibling (image/bubble-time) — with pre-wrap
   inherited here, that raw template whitespace rendered as visible blank space, but addBubble()'s
   JS-built DOM never has stray whitespace text nodes, so reloaded history looked "loose" while
   freshly-sent messages stayed tight. */
.bubble{max-width:80%;min-width:0;padding:9px 13px;border-radius:14px;font-size:14px;line-height:1.5}
.bubble img{max-width:100%;height:auto}
.bubble.user{align-self:flex-end;background:var(--accent);color:#fff;border-bottom-right-radius:4px}
.bubble.assistant{align-self:flex-start;background:var(--card2);border:1px solid var(--bdr);border-bottom-left-radius:4px}

/* Rendered markdown (both roles — see chat.php's renderBubbleText()/addBubble()/page-load pass;
   the user gets it too now via the B/I/U toolbar, not just assistant replies). The bubble's own
   white-space:pre-wrap is for the plain-text fallback; real HTML elements handle their own
   spacing, so this scopes back to normal inside a rendered block. */
.bubble-text{white-space:pre-wrap}
.bubble .bubble-text{white-space:normal}
.bubble .bubble-text p{margin:0 0 8px}
.bubble .bubble-text p:last-child{margin-bottom:0}
.bubble .bubble-text h1,.bubble .bubble-text h2,.bubble .bubble-text h3,.bubble .bubble-text h4{margin:10px 0 6px;font-size:1.05em;font-weight:700}
.bubble .bubble-text h1:first-child,.bubble .bubble-text h2:first-child,.bubble .bubble-text h3:first-child,.bubble .bubble-text h4:first-child{margin-top:0}
.bubble .bubble-text ul,.bubble .bubble-text ol{margin:0 0 8px 1.3em;padding:0}
.bubble .bubble-text li{margin:2px 0}
.bubble .bubble-text code{background:rgba(0,0,0,.25);padding:1px 5px;border-radius:4px;font-size:.92em}
.bubble .bubble-text pre{background:rgba(0,0,0,.25);padding:8px 10px;border-radius:6px;overflow-x:auto;margin:0 0 8px}
.bubble .bubble-text pre code{background:none;padding:0}
.bubble .bubble-text table{border-collapse:collapse;margin:0 0 8px;font-size:.92em;max-width:100%;display:block;overflow-x:auto}
.bubble .bubble-text th,.bubble .bubble-text td{border:1px solid rgba(255,255,255,.18);padding:4px 8px;text-align:left}
.bubble .bubble-text a{color:inherit;text-decoration:underline}
.bubble .bubble-text blockquote{border-left:3px solid rgba(255,255,255,.25);margin:0 0 8px;padding-left:10px;opacity:.85}

/* Formatting toolbar above the message textarea — B/I/U buttons insert markdown/HTML syntax
   (see chat.php's wrapSelection()) so users can format their own messages the same way
   assistant replies already render. */
.chat-format-toolbar{display:flex;gap:6px;margin-bottom:6px}
.chat-format-toolbar .fmt-btn{width:30px;height:30px;display:flex;align-items:center;justify-content:center;
  border:1px solid var(--bdr);border-radius:var(--r2);background:var(--card2);color:var(--text);
  font-size:13px;cursor:pointer;line-height:1}
.chat-format-toolbar .fmt-btn:hover{border-color:var(--accent);color:var(--accent2)}

/* "Thinking" indicator bubble — see chat.php's showTypingBubble(). Three dots bouncing in
   sequence, standard messaging-app "typing…" look. */
.bubble.typing{padding:11px 15px}
.typing-dots{display:inline-flex;gap:4px}
.typing-dots span{width:6px;height:6px;border-radius:50%;background:var(--muted);
  animation:typing-bounce 1.2s infinite ease-in-out}
.typing-dots span:nth-child(2){animation-delay:.2s}
.typing-dots span:nth-child(3){animation-delay:.4s}
@keyframes typing-bounce{0%,60%,100%{transform:translateY(0);opacity:.5}30%{transform:translateY(-4px);opacity:1}}
.chat-input-row{display:flex;gap:8px}
/* font-size 16px — not wrapped in .field, so it doesn't inherit that fix; without this the
   message box specifically triggers iOS's auto-zoom-on-focus (see the .field comment above). */
.chat-input-row textarea{flex:1;min-height:44px;max-height:120px;font-size:16px}
/* Desktop gets a taller, roomier plain-text box — Enter inserts a newline there instead of
   sending (see chat.php's keydown handler), so multi-paragraph messages need actual room to be
   written/pasted into, not the same cramped single-line-ish box mobile uses. Deliberately still a
   plain <textarea>, not a rich-text editor — no formatting toolbar, just more space. */
.chat-input-row.desktop textarea{min-height:90px;max-height:400px}
.chat-input-row.desktop{align-items:flex-end}
/* Creative Writing row (2026-07-18 fix) — the 4-button Revision/Reject/Continue Chapter/Accept
   stack is naturally taller (~170px) than the textarea's own 90px min-height, so under the
   default align-items:flex-end above, the SHORTER item (the textarea) was the one left bottom-
   anchored with dead space above it instead of the buttons — same "not uniform" symptom, just
   the other way round from what align-self:stretch alone (tried first, didn't resolve it) fixed.
   Overriding to align-items:stretch on this specific row makes BOTH children always fill
   whichever is taller's full height — no per-child override needed, no dead space either way. */
.chat-input-row.cw{align-items:stretch}
.chat-input-row.cw textarea{min-height:172px}
.placeholder{color:var(--hint);text-align:center;padding:40px 0;font-size:13px}

/* Creative Writing's Revision/Reject/Accept row (2026-07-18) — folds in from the plain Send
   button the moment narrating starts (see chat.php's switchCwMode()). Fixed width covers both
   states (single Send button vs 3 stacked buttons) so the transition reads as the bar smoothly
   growing/shrinking rather than snapping between two different sizes. */
/* 180px (not 110px) — wide enough for "➕ Continue Chapter", the longest of the 4 stacked
   Revision/Reject/Continue Chapter/Accept buttons, without wrapping.
   align-self:stretch (2026-07-18 fix) — .chat-input-row.desktop sets align-items:flex-end,
   which left this button column bottom-anchored at its own natural (shorter) height while the
   textarea grew independently above it — visually mismatched, "sticking out" instead of reading
   as one unified control. Stretching to the row's full height, then having #chat-cw-actions and
   its buttons fill that height evenly (flex:1 each), makes the button column always exactly as
   tall as the textarea next to it, buttons evenly spaced across the full height. */
.chat-send-area{width:180px;flex-shrink:0;align-self:stretch;display:flex;transition:width .2s ease}
.chat-send-area #chat-send{width:100%}
.chat-send-area .btn{width:100%;justify-content:center}
#chat-cw-actions{flex:1;min-height:0}
#chat-cw-actions .btn{flex:1}

/* Mobile layout (server-detected — see compai_is_mobile_device() in api/auth-helpers.php): both
   mobile and desktop are full-width now (.wrap itself has no max-width) — this override is just
   tighter padding for phone-size screens. The real mobile/desktop difference is the corner account
   menu vs. inline links (compai_render_account_menu()), not container width. */
.wrap.mobile{padding:18px 14px 48px}

/* Corner account menu — mobile only, collapses Passkeys/Claim code/Request memorial
   companion/Log out into one button+panel instead of showing them as separate inline links
   (which is what desktop still does, unchanged). Fixed position so it's reachable regardless of
   scroll position, same top-right corner those links already occupied on desktop. */
#account-menu-btn{position:fixed;top:14px;right:14px;z-index:50;width:40px;height:40px;
  border-radius:var(--r2);border:1px solid var(--bdr);background:var(--card2);color:var(--text);
  font-size:18px;line-height:1;cursor:pointer}
#account-menu-btn:hover{border-color:var(--accent)}
#account-menu-modal{display:none;position:fixed;inset:0;z-index:60;align-items:flex-start;justify-content:flex-end}
#account-menu-modal-bd{position:absolute;inset:0;background:rgba(0,0,0,.5)}
#account-menu-box{position:relative;background:var(--card);border:1px solid var(--bdr);
  border-radius:var(--r);margin:60px 14px 0 0;padding:10px;min-width:220px;
  display:flex;flex-direction:column;gap:2px}
#account-menu-box a{padding:12px 14px;border-radius:var(--r2);color:var(--text);font-size:14px}
#account-menu-box a:hover{background:var(--card2);text-decoration:none;color:var(--accent2)}

/* Multichat (2026-07-19) — two renderings of the same session data (chat.php). Desktop gets a
   real fixed left sidebar (.cw-sidebar) styled after the Network Panel's Inference AI; the
   dropdown+button row (.cw-session-switcher) is mobile-only. Default (mobile-first): sidebar
   hidden, switcher visible full-width. */
.cw-sidebar { display: none; }

@media (min-width: 601px) {
  .wrap:not(.mobile) .cw-session-switcher { display: none !important; }
  .wrap:not(.mobile) .cw-sidebar {
    display: block;
    position: fixed;
    top: 0; left: 0; bottom: 0;
    width: 220px;
    background: var(--card);
    border-right: 1px solid var(--bdr);
    padding: 14px 10px;
    overflow-y: auto;
    z-index: 5;
  }
  .wrap:not(.mobile) .cw-sidebar-back {
    display: block;
    font-size: 13px;
    color: var(--muted);
    text-decoration: none;
    margin-bottom: 12px;
  }
  .wrap:not(.mobile) .cw-sidebar-back:hover { color: var(--text); }
  /* The page's own bottom "Back to companions" link is redundant once the sidebar has its own —
     hidden only here (desktop + Multichat on), never touched for anyone without the sidebar. */
  body:has(.cw-sidebar) .wrap:not(.mobile) > .nav-links { display: none; }
  .wrap:not(.mobile) .cw-sidebar-list { margin-top: 14px; display: flex; flex-direction: column; gap: 2px; }
  .wrap:not(.mobile) .cw-sidebar-row {
    display: flex;
    align-items: center;
    border-radius: var(--r2);
  }
  .wrap:not(.mobile) .cw-sidebar-row:hover { background: var(--card2); }
  .wrap:not(.mobile) .cw-sidebar-row.active { background: var(--accent2); }
  .wrap:not(.mobile) .cw-sidebar-row.active .cw-sidebar-item,
  .wrap:not(.mobile) .cw-sidebar-row.active .cw-sidebar-rename,
  .wrap:not(.mobile) .cw-sidebar-row.active .cw-sidebar-del { color: #fff; }
  .wrap:not(.mobile) .cw-sidebar-item {
    flex: 1;
    min-width: 0;
    display: block;
    padding: 8px 10px;
    color: var(--text);
    font-size: 13px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-decoration: none;
  }
  .wrap:not(.mobile) .cw-sidebar-rename,
  .wrap:not(.mobile) .cw-sidebar-del {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--muted);
    cursor: pointer;
    padding: 4px 8px;
    font-size: 12px;
    opacity: 0;
  }
  .wrap:not(.mobile) .cw-sidebar-row:hover .cw-sidebar-rename,
  .wrap:not(.mobile) .cw-sidebar-row:hover .cw-sidebar-del { opacity: 1; }
  .wrap:not(.mobile) .cw-sidebar-rename:hover,
  .wrap:not(.mobile) .cw-sidebar-del:hover { color: #fff; }
  /* Shift the whole page content right so it doesn't sit under the fixed sidebar. Scoped to only
     apply when the sidebar is actually rendered (chat.php only emits .cw-sidebar when Multichat
     is on) — a companion/page without it is completely unaffected. */
  body:has(.cw-sidebar) .wrap:not(.mobile) { margin-left: 220px; }
}

/* Chapter image gallery (2026-07-20) — companion-bound, one grid of markup shared between two
   presentations: a persistent fixed RIGHT sidebar on desktop (mirrors .cw-sidebar's left-side
   pattern above, WhatsApp-media-viewer-style thumbnail grid), and a floating-button-triggered
   full-screen modal on mobile (no room for a permanent second sidebar there). Default
   (mobile-first): both hidden — desktop reveals the sidebar unconditionally below; mobile reveals
   the FAB unconditionally and the gallery itself only once JS adds .open (button click). */
.cw-gallery { display: none; }
.cw-gallery-fab { display: none; }
.cw-gallery-close-mobile { display: none; }

@media (min-width: 601px) {
  .wrap:not(.mobile) .cw-gallery {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0; right: 0; bottom: 0;
    width: 220px;
    background: var(--card);
    border-left: 1px solid var(--bdr);
    padding: 14px 10px;
    overflow-y: auto;
    z-index: 5;
  }
  body:has(.cw-gallery) .wrap:not(.mobile) { margin-right: 220px; }
}

.wrap:not(.mobile) .cw-gallery-header,
.wrap.mobile .cw-gallery.open .cw-gallery-header {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 12px; font-weight: 700; color: var(--muted); text-transform: uppercase;
  letter-spacing: .5px; margin-bottom: 10px;
}
.cw-gallery-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px;
}
.cw-gallery-thumb {
  position: relative; aspect-ratio: 1; border-radius: var(--r2); overflow: hidden;
  cursor: pointer; background: var(--card2);
}
.cw-gallery-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cw-gallery-thumb-label {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 3px 5px;
  font-size: 9.5px; color: #fff; background: linear-gradient(transparent, rgba(0,0,0,.75));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cw-gallery-empty { font-size: 11.5px; color: var(--muted); grid-column: 1 / -1; }

/* Mobile: floating button (bottom-right, fixed) opens the SAME .cw-gallery markup as a full-screen
   modal — toggled purely by JS adding/removing .open, no separate markup needed. */
.wrap.mobile .cw-gallery-fab {
  display: flex; align-items: center; justify-content: center;
  position: fixed; bottom: 18px; right: 18px; z-index: 6;
  width: 48px; height: 48px; border-radius: 50%;
  background: var(--accent2); color: #fff; border: none; font-size: 20px;
  box-shadow: 0 2px 10px rgba(0,0,0,.3); cursor: pointer;
}
.wrap.mobile .cw-gallery.open {
  display: flex; flex-direction: column;
  position: fixed; inset: 0; z-index: 83;
  background: var(--card); padding: 16px; overflow-y: auto;
}
.wrap.mobile .cw-gallery.open .cw-gallery-close-mobile {
  display: block; background: none; border: none; color: var(--muted); font-size: 16px; cursor: pointer;
}
.wrap.mobile .cw-gallery.open .cw-gallery-grid { grid-template-columns: repeat(3, 1fr); }

/* Retrain review modal (2026-08-03). Deliberately NOT reusing any .field/.card pattern: a
   staged retrain cannot advance past undecided conflicts, so this must not be dismissable —
   no backdrop close, no Escape. Everything else on this site closes; this one does not. */
#rtr-modal{display:none;position:fixed;inset:0;z-index:12000;align-items:center;justify-content:center}
#rtr-modal.on{display:flex}
#rtr-bd{position:absolute;inset:0;background:rgba(0,0,0,.84)}
#rtr-box{position:relative;background:var(--card,#14141a);border:1px solid var(--bdr,#2a2a38);
         border-radius:14px;width:92vw;max-width:540px;max-height:84vh;overflow:auto;
         padding:20px 22px;box-shadow:0 20px 64px rgba(0,0,0,.6)}
#rtr-kicker{font-size:10.5px;font-weight:700;color:var(--muted,#8a8aa3);text-transform:uppercase;letter-spacing:.6px}
#rtr-title{font-size:16px;font-weight:800;margin:4px 0 10px}
.rtr-fact{font-size:13px;background:rgba(255,255,255,.06);border:1px solid var(--bdr,#2a2a38);
          border-radius:9px;padding:10px 12px;margin:8px 0;word-break:break-word;line-height:1.45}
.rtr-arrow{text-align:center;color:var(--muted,#8a8aa3);font-size:16px;margin:2px 0}
.rtr-why{font-size:12.5px;color:var(--muted,#8a8aa3);margin-top:10px;line-height:1.5}
.rtr-actions{display:flex;gap:9px;justify-content:flex-end;margin-top:16px;flex-wrap:wrap}
.rtr-prog{font-size:11px;color:var(--muted,#8a8aa3);opacity:.75;margin-top:12px}

/* Admin message menu + editor (2026-08-03). Mirrors the panel's #users-msg-menu so the two
   surfaces behave identically — right-click a bubble, Edit or Delete. A menu rather than
   per-bubble buttons: a row of edit affordances down a conversation invites tidying rather
   than fixing, and should stay out of the way while reading. */
#msg-menu{display:none;position:fixed;z-index:12100;background:var(--card,#14141a);
          border:1px solid var(--bdr,#2a2a38);border-radius:7px;padding:4px;min-width:158px;
          box-shadow:0 8px 26px rgba(0,0,0,.5)}
#msg-menu button{display:block;width:100%;text-align:left;background:none;border:0;
                 color:var(--text,#dddde8);font:inherit;font-size:12.5px;padding:8px 11px;
                 border-radius:5px;cursor:pointer}
#msg-menu button:hover{background:rgba(255,255,255,.08)}
#msg-menu button.danger{color:var(--red,#f05a7c)}
#msg-edit-modal{display:none;position:fixed;inset:0;z-index:12050;align-items:center;justify-content:center}
#msg-edit-modal.on{display:flex}
#msg-edit-bd{position:absolute;inset:0;background:rgba(0,0,0,.7)}
#msg-edit-box{position:relative;background:var(--card,#14141a);border:1px solid var(--bdr,#2a2a38);
              border-radius:12px;width:92vw;max-width:640px;max-height:82vh;overflow:auto;padding:18px 20px;
              box-shadow:0 18px 56px rgba(0,0,0,.55)}
/* Only a hint that a bubble can be repaired - it must not look like a control. */
.bubble[data-id]{cursor:context-menu}
