/*
 * The app's stylesheet, in load order.
 *
 * Ksena's design is COPIED, not transcribed. kxd-base.css and kxd-messenger.css
 * are her two \3c style> blocks byte for byte; kxd-theme-dark.css is the palette her
 * app applies from script instead of CSS. v3-additions.css holds the few rules
 * that exist only here, for things v3 has and v1 does not.
 *
 * Nothing else may restate a rule that already exists in the copied files. If a
 * value looks wrong, fix it in the copy — do not shadow it from somewhere else,
 * because then there are two answers to the same question and only one of them
 * is hers.
 */
/*
 * KxD base layer — COPIED VERBATIM from ../ksenaxdieter/index.html
 *
 * Do NOT reformat, retype, tidy or split this file. It is your design, byte for
 * byte, and every transformation of it is a chance to introduce a difference that
 * nobody notices until Ksena does.
 * 
 * Earlier the rules here were hand-ported selectively and "cleaned up" on the way.
 * That produced hundreds of small wrongnesses — thick icon strokes, inflated line
 * heights, off-by-one font sizes — which is a far worse outcome than carrying a
 * few retired rules. A stylesheet is an asset, not architecture; the no-dead-code
 * rule in PLAN.md §2 is about the engine.
 * 
 * To change the design, change the token or the rule here. To re-sync after Ksena
 * edits the old app, re-run tools/copy-legacy-css.js.
 */
*{box-sizing:border-box;margin:0}
/* FOUT mitigation for Great Vibes — the script font on the welcome + login titles.
   Before Great Vibes streams in from Google Fonts (or if it fails entirely), instead of
   falling through to the `cursive` generic family (= Comic Sans MS on Windows, Apple
   Chancery on macOS — both eye-poison), the browser uses this @font-face declaration to
   render the locally-installed Georgia at italic, with proportions tuned to reduce layout
   shift when the real font swaps in. Result: graceful italic-serif placeholder → clean swap
   to actual Great Vibes once the WOFF2 arrives. */
@font-face{
  font-family:'GreatVibesFallback';
  src:local('Georgia');
  font-style:italic;
  size-adjust:88%;
  ascent-override:96%;
  descent-override:24%;
}
/* color-scheme:light is the CSS-side counterpart to the <meta> tag — together they tell every
   browser (and any auto-dark-mode extension/OS feature) that this page renders its own light
   palette. Removes Chromium's auto-darken pass that was rewriting the welcome page in dark
   even on truly fresh sessions. The Golden theme override applies its own colors via the
   early-theme bootstrap; it doesn't need a different color-scheme value. */
:root{color-scheme:light;--bg:#eae7e0;--bg2:#eae7e0;--bg3:#fff8d8;--text:#3a2f1a;--muted:#7a6a3a;--border:#e8d8a8;--accent:#c9a44a;--ctrl:#fff8d8;--ctrl-b:#e8d8a8;--ctrl-h:#fbecb8;--text-88:rgba(58,47,26,.88);--text-40:rgba(58,47,26,.4);--input-bg:#eae7e0;--scroll:#d4af37;--chrome:#eae7e0;--panel-shadow-l:2px 0 12px rgba(122,106,42,.2);--panel-shadow-r:-2px 0 12px rgba(122,106,42,.2);--ov:0,0,0}
/* Mobile/PWA stability:
   - 100dvh tracks the *dynamic* viewport — iOS Safari's URL bar showing/hiding and the soft
     keyboard opening shrink the visible area, which 100vh ignores (causing the wobble Ksena
     described where window borders shift mid-typing). Falls back to 100vh on browsers that
     don't yet support dvh.
   - env(safe-area-inset-*) reserves space for the iPhone notch / home-bar so chrome elements
     don't sit under them. Pairs with viewport-fit=cover in the meta tag.
   - overscroll-behavior:none kills the rubber-band bounce when scrolling past the top/bottom
     of the chat — that bounce is what made the whole page "wobble" in standalone PWA mode. */
html{height:100vh}
body{font-family:'Quicksand','DM Sans',sans-serif;background:var(--bg2);color:var(--text);height:100vh;overflow:hidden;display:flex;visibility:hidden;padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right);overscroll-behavior:none}
/* round 24 — THE CHIN-GAP CURE (her r/PWA find): under black-translucent iOS OFFSETS the view up
   by the status-bar height without growing it; %-based and inset-based heights measure the offset
   container and come up short by exactly that offset (the 59px chin). 100vh measures the ACTUAL
   screen — html/body at 100vh reach the true bottom. NEVER size these with 100%, 100dvh, or
   fixed+inset — all three re-open the chin. */
/* black-translucent, round-15 layout (her screenshots: black gaps top AND bottom): the body pads
   NOTHING vertically — every surface runs edge-to-edge and the pieces that need clearance add the
   safe-area insets themselves. Top: side panels + the chat header carry the top inset (their own
   backgrounds fill the strip, seamless); the welcome/home scenes just run under the clock. Bottom:
   the composer and the panel footers already lift by the bottom inset. env()=0 in a browser tab /
   on desktop, so all of this collapses to nothing there. */
.panel-left,.panel-right{padding-top:env(safe-area-inset-top,0px)}
.cp-header{padding-top:env(safe-area-inset-top,0px)}
/* (round-17 pink diagnostic removed — verdict: the band is the iOS container, not our canvas) */
body.kxd-ready{visibility:visible}
/* Boot splash. Lives under #kxdBootSplash; visibility:visible !important overrides the body's
   initial visibility:hidden so the splash renders before the rest of the app does.
   Hidden via the .bs-hide class added by JS in the IIFE-3 ready handler — NOT via body.kxd-ready,
   because that fires too fast on returning visits (cached fonts → ~100ms) for the pulse animation
   to even complete one cycle. JS enforces a minimum visible duration. */
/* Boot splash: dark base with a soft golden glow centered behind the icon. The radial layer
   creates the "halo" feel; the linear underneath provides the deep-brown ambient gradient. */
#kxdBootSplash{position:fixed;inset:0;z-index:99997;display:flex;align-items:center;justify-content:center;background:radial-gradient(circle at 50% 42%,rgba(242,192,91,.18) 0%,transparent 55%),linear-gradient(135deg,#241e12 0%,#0a0703 100%);visibility:visible!important;transition:opacity .55s ease,visibility 0s linear .55s;opacity:1}
#kxdBootSplash .bs-icon{width:240px;height:240px;border-radius:18.75%;filter:drop-shadow(0 8px 28px rgba(242,192,91,.18));animation:bsPulse 2.2s ease-in-out infinite}
@keyframes bsPulse{0%,100%{transform:scale(1);filter:drop-shadow(0 8px 28px rgba(242,192,91,.18))}50%{transform:scale(1.075);filter:drop-shadow(0 12px 36px rgba(242,192,91,.32))}}
#kxdBootSplash.bs-hide{opacity:0;visibility:hidden;pointer-events:none}
input[type="color"]{appearance:none;-webkit-appearance:none;background:transparent;padding:0}
input[type="color"]::-webkit-color-swatch-wrapper{padding:0;border:none}
input[type="color"]::-webkit-color-swatch{border:1px solid var(--border);border-radius:4px}
.panel-left{width:260px;min-width:220px;background:var(--bg2);display:flex;flex-direction:column;flex-shrink:0;box-shadow:var(--panel-shadow-l);border-right:1px solid var(--border);z-index:1}
.panel-center{flex:1;display:flex;flex-direction:column;min-width:300px;background:var(--bg)}
.panel-right{width:260px;min-width:0;background:var(--bg2);display:flex;flex-direction:column;flex-shrink:0;box-shadow:var(--panel-shadow-r);border-left:1px solid var(--border);overflow:hidden;transition:width .3s cubic-bezier(.4,0,.2,1)}
.lp-header{padding:18px 16px 14px;font-size:17px;font-weight:600;display:flex;align-items:center;justify-content:center;color:var(--accent);font-family:'Cormorant Upright',Georgia,serif;letter-spacing:.02em}
/* ── STORY ROW (2026-07-07): Ksena + Dieter as tappable circles, Instagram-stories style.
   Tap opens that persona's profile in the right panel. Ksena glows GOLDEN (the app accent),
   Dieter glows VIRIDIAN (#27aa89 — his call-screen green) with a viridian tint behind him on
   hover; .sel keeps the ring lit while that profile is open (any panel rebuild clears it). */
.lp-stories{display:flex;gap:16px;padding:8px 14px 12px;align-items:flex-start;justify-content:center}
.lp-stories .story{display:flex;flex-direction:column;align-items:center;gap:5px;cursor:pointer;-webkit-tap-highlight-color:transparent;min-width:56px;user-select:none}
.lp-stories .story-ring{width:50px;height:50px;border-radius:50%;padding:2px;border:2px solid rgba(var(--ov),.12);transition:border-color .2s,box-shadow .2s,background .2s;box-sizing:content-box} /* 50px avatar, ring outside it (content-box) per her spec */
.lp-stories .story-av{width:50px;height:50px;border-radius:50%;overflow:hidden;display:grid;place-items:center;font-weight:700;font-size:17px;color:var(--text-88);background:var(--ctrl)}
.lp-stories .story-av img{width:100%;height:100%;object-fit:cover}
.lp-stories .story-name{font-size:11px;font-weight:600;color:var(--text-88);max-width:66px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:color .2s}
.story-ksena:hover .story-ring,.story-ksena.sel .story-ring{border-color:var(--accent);box-shadow:0 0 14px 2px rgba(242,192,91,.45);background:rgba(242,192,91,.10)}
.story-ksena:hover .story-name,.story-ksena.sel .story-name{color:var(--accent)}
.story-dieter:hover .story-ring,.story-dieter.sel .story-ring{border-color:#27aa89;box-shadow:0 0 14px 2px rgba(39,170,137,.45);background:rgba(39,170,137,.14)}
.story-dieter:hover .story-name,.story-dieter.sel .story-name{color:#27aa89}
.lp-list{flex:1;overflow-y:auto;padding:6px 10px}
.lp-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:12px;cursor:pointer;transition:all .15s;margin-bottom:2px;border:1px solid transparent}
.lp-item:hover{background:color-mix(in srgb,var(--accent) 6%,transparent)}
.lp-item.active{background:color-mix(in srgb,var(--accent) 8%,transparent);border-color:color-mix(in srgb,var(--accent) 10%,transparent)}
.lp-item.sending{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent)}
.lp-av{width:38px;height:38px;border-radius:50%;flex-shrink:0;display:grid;place-items:center;font-weight:700;font-size:14px;overflow:hidden;color:var(--text-88)}
.lp-av img{width:100%;height:100%;object-fit:cover}
.lp-info{flex:1;overflow:hidden}
.lp-nm{font-weight:600;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.lp-footer{padding:10px 10px 12px;display:flex;gap:6px}
.lp-btn{flex:1;padding:8px;border-radius:10px;background:rgba(var(--ov),.04);border:1px solid rgba(var(--ov),.06);color:var(--text);font-size:11px;font-family:inherit;cursor:pointer;text-align:center;font-weight:600;transition:all .15s}
.lp-btn:hover{background:rgba(var(--ov),.08)}
.lp-btn.add{color:var(--accent)}
.lp-themes{display:flex;align-items:center;justify-content:center;gap:6px;padding:8px 10px 12px}
.th-dot{width:20px;height:20px;border-radius:50%;cursor:pointer;border:none;transition:all .15s;opacity:.6;flex-shrink:0;display:block;box-sizing:border-box}
.th-dot:hover{opacity:1;transform:scale(1.15)}
.th-dot.active{opacity:1;box-shadow:0 0 0 2px var(--bg2),0 0 0 4px rgba(var(--ov),.4);transform:scale(1.15)}
.th-dot.light.active{box-shadow:0 0 0 2px var(--bg2),0 0 0 4px rgba(0,0,0,.3)}
.th-sep{width:1px;height:16px;background:rgba(var(--ov),.1);margin:0 2px}
/* Header casts a soft DROP SHADOW onto the chat (her 2026-07-04 clarification): the bar is solid,
   and a dark gradient just below its edge overlays the top of the scrolling messages so text
   glides UNDER it and dims away into the bar instead of a hard cut. The ::after belongs to the
   header (doesn't scroll) and sits above the messages via the header's stacking context.
   (v1 black box-shadow = invisible on near-black; v2 --ov haze = smudge; v3 chrome→bg fade =
   ugly ramp on light. This is the actual scroll-shadow she meant.) */
/* Header scroll-shadow: the overlay IS the bar colour (--chrome == chat bg on both themes) so
   scrolling text dissolves INTO the bar. TWO knobs live on .cp-header so Ksena can tune it in
   DevTools and just hand me the numbers: --hsh-strength = top opacity (how strong), --hsh-height
   = how tall the fade. Softened from the earlier opaque version she found too strong. */
/* Glass tint (2026-08-06, her cognac ask): light theme keeps the bg wash (whitish glass she
   likes); dark theme swaps in a warm cognac so the blur reads as amber glass, not grey smoke.
   Feeds the dock scrim only — the header's glass band was tried + retired the same day. */
.dock-scrim{--glass-tint-bot:var(--bg)}
body[data-mode="dark"] .dock-scrim{--glass-tint-bot:#261606}
.cp-header{display:flex;flex-direction:column;background:var(--chrome);position:relative;z-index:4;--hsh-strength:55%;--hsh-height:22px}
/* (Top glass fade tried + retired same day 2026-08-06 — her verdict: "stands out too much".
   Back to the original paint-only dissolve; the glass lives at the composer only.) */
.cp-header::after{content:'';position:absolute;left:0;right:0;top:100%;height:var(--hsh-height);background:linear-gradient(to bottom,color-mix(in srgb,var(--chrome) var(--hsh-strength),transparent),transparent);pointer-events:none}
.cp-header[data-hidden="1"]{display:none}
.cp-statusbar{display:flex;align-items:center;justify-content:space-between;padding:6px 16px 2px}
.cp-clock{font-weight:600;font-size:12px;font-family:'Quicksand','DM Sans',sans-serif;color:var(--muted)}
.cp-chatbar{display:flex;align-items:center;justify-content:space-between;padding:8px 16px 10px}
@media(min-width:769px){.cp-chatbar{padding-right:48px}} /* Reserve space on desktop for the absolute-positioned rp-toggle (panel hide/show chevron at right:8px) so it doesn't sit on top of the right-side header buttons. */
.mob-burger{display:none;width:32px;height:32px;border-radius:9px;background:none;border:1px solid rgba(var(--ov),.08);color:var(--text-88);place-items:center;cursor:pointer;flex-shrink:0;padding:0;font-family:inherit}
.mob-burger svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2}
/* Floating mobile menu — only on the welcome page (cp-header is hidden there, so the in-header burger/gear
   are gone). Sits over the empty-state content; mobile users can still open the chat list and settings. */
.mob-floating{display:none}
.mob-floating-btn{width:36px;height:36px;border-radius:10px;background:rgba(var(--ov),.06);border:1px solid rgba(var(--ov),.12);color:var(--text-88);display:grid;place-items:center;cursor:pointer;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
.mob-floating-btn svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2}
.mob-floating-btn:active{background:rgba(var(--ov),.12)}
/* Slide-out panel veil (2026-08-12): was a flat rgba(0,0,0,.5) sheet — it DARKENED the chat instead of
   frosting it. Now it's the house glass: the blur does the work, the tint is only there to keep the panel
   readable against it. Tints follow the call veil's lesson (index.html:471) — heavy alpha over the golden
   theme reads as pitch black, so dark stays light-handed. The @supports fallback keeps the old opaque
   scrim on any engine without backdrop-filter, so the chat never shows through unblurred.
   Blur 20→10→6px (her verdicts, same day): 20 erased plain text completely, 10 still softened it
   past reading — 6 keeps the contours legible, so the veil reads as glass rather than as fog. */
.mob-ov{display:none;position:fixed;inset:0;background:rgba(10,7,3,.30);backdrop-filter:blur(6px) saturate(1.25);-webkit-backdrop-filter:blur(6px) saturate(1.25);z-index:250}
body[data-mode="light"] .mob-ov{background:rgba(122,106,42,.16)} /* lemon: a warm haze, not a bruise — the cream panel still has to pop off it */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .mob-ov,body[data-mode="light"] .mob-ov{background:rgba(0,0,0,.5)}
}
.mob-ov.show{display:block;animation:mobOvIn .3s ease} /* blooms in with the panel's .3s slide instead of snapping on */
@keyframes mobOvIn{from{opacity:0}to{opacity:1}}
.rp-close{display:none;width:32px;height:32px;border-radius:8px;background:rgba(var(--ov),.06);border:1px solid rgba(var(--ov),.1);color:var(--muted);place-items:center;cursor:pointer;font-size:16px;padding:0;position:absolute;left:14px;top:50%;transform:translateY(-50%)}
.mob-back{display:none;position:absolute;left:12px;top:16px;width:28px;height:28px;border-radius:7px;background:none;border:1px solid rgba(var(--ov),.08);color:var(--muted);place-items:center;cursor:pointer;z-index:2;padding:0}
.mob-back svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2}
.cp-hdr-left{display:flex;align-items:center;gap:10px;min-width:0;flex:1} /* min-width:0 + flex:1 lets the cp-name shrink + truncate / marquee instead of pushing the right-side buttons off-screen */
.cp-hdr-text{min-width:0;flex:1;overflow:hidden;margin-right:14px} /* margin-right creates a gap between the title (which can be marquee-scrolling) and the right-side header buttons, so the text doesn't appear to slide under them */
.cp-av{width:36px;height:36px;border-radius:50%;background:var(--ctrl);display:grid;place-items:center;font-weight:700;font-size:14px;color:var(--text-88);overflow:hidden}
.cp-av img{width:100%;height:100%;object-fit:cover}
.cp-name{font-size:14px;font-weight:400}
.cp-presence{font-size:12px;color:var(--muted);display:flex;align-items:center;gap:5px}
.cp-presence .dot{width:7px;height:7px;border-radius:50%;background:#f2c05b}
.cp-hdr-right{display:flex;gap:6px;align-items:center}
.hdr-btn{width:32px;height:32px;border-radius:9px;background:var(--ctrl);border:1px solid var(--ctrl-b);color:var(--text-88);display:grid;place-items:center;cursor:pointer;padding:0;font-family:inherit}
.hdr-btn:hover{background:var(--ctrl-h)}
.hdr-btn svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2}
.cp-av-stack{display:flex;align-items:center;flex-shrink:0}
.cp-av-stack .cp-av{width:36px;height:36px;border-radius:50%;background:var(--ctrl);display:grid;place-items:center;font-weight:700;font-size:14px;color:var(--text-88);overflow:hidden;border:2px solid var(--chrome)}
.cp-av-stack .cp-av+.cp-av{margin-left:-12px}
.cp-av-stack .cp-av img{width:100%;height:100%;object-fit:cover}
.cp-name{font-size:14px;font-weight:400;outline:none;min-width:40px;white-space:nowrap;overflow:hidden;max-width:100%}
.cp-name:focus{border-bottom:1px solid var(--accent)}
/* Marquee scroll for long titles — JS adds .marquee-on when scrollWidth > clientWidth.
   The track contains two identical copies; translateX(-50%) slides exactly one copy off,
   bringing the second copy into the same starting position for a seamless loop. */
.cp-name.marquee-on .cp-name-track{display:inline-block;white-space:nowrap;animation:cp-name-marquee 14s linear infinite;will-change:transform}
.cp-name.marquee-on .cp-name-track>span{display:inline-block;padding-right:32px}
@keyframes cp-name-marquee{0%{transform:translateX(0)}100%{transform:translateX(-50%)}}
.cp-hdr-title-wrap{display:flex;align-items:center;gap:4px}







/* Fills the whole panel behind the floating dock; the scrim (.panel-center::after) handles the
   text near/under the bar. (No margin-bottom — it left a thin seam line at the content edge,
   visible on the welcome/dino page. Her 2026-07-04.) */
.messages{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:8px;scroll-behavior:smooth;padding-left:max(20px,calc((100% - 770px)/2));padding-right:max(20px,calc((100% - 770px)/2));position:relative;padding-bottom:0}
/* Composer clearance = a REAL spacer child, not padding (2026-07-08, her "can't scroll the text out
   from behind the composer" bug): when content exactly fills the container, browsers don't treat the
   scroll-container's own bottom padding as scrollable overflow — a flex child always counts. */
.messages::after{content:'';display:block;flex-shrink:0;height:var(--dock-h,84px)}
body.kxd-welcome .messages::after{height:0} /* home/journal/welcome have no composer — the dock spacer just made the page scroll into nothing (her round 11) */
.row{display:flex;position:relative;flex-direction:column}
.row-inner{display:flex;align-items:flex-start;gap:8px;width:100%}
.name-label{font-size:12px;font-weight:600;margin-bottom:3px;display:flex;align-items:baseline;gap:5px}
.name-label svg,.name-label img{align-self:center}
.msg-avatar{width:44px;height:44px;border-radius:50%;flex-shrink:0;background:var(--ctrl);display:grid;place-items:center;font-weight:700;font-size:14px;color:var(--text-88);overflow:hidden}
.msg-avatar img{width:100%;height:100%;object-fit:cover}
.msg-avatar[data-hidden="1"]{display:none}
.bubble{position:relative;max-width:72%;font-size:17px;line-height:1.35;word-wrap:break-word;font-family:'Crimson Text',Georgia,serif;overflow:visible;min-height:20px;margin-bottom:24px;min-width:0}
.bubble .msg{font-weight:400}.bubble .msg-para+.msg-para{margin-top:10px}
.bubble strong,.bubble b{font-weight:700}.bubble em,.bubble i{font-style:italic}
.bubble u{text-decoration:underline;text-underline-offset:2px}
.bubble s{text-decoration:line-through;opacity:.7}
.bubble .msg-h{font-weight:700;font-size:1.15em;margin-bottom:2px}
.bubble .sp-highlight{border-radius:3px;padding:0 3px;-webkit-box-decoration-break:clone;box-decoration-break:clone}
.bubble .sp-spoiler{border-radius:4px;padding:0 4px;cursor:pointer;-webkit-box-decoration-break:clone;box-decoration-break:clone;transition:background .2s,color .2s}
.bubble ul,.bubble ol{margin:6px 0 0;padding-left:1.2em}.bubble li{margin:2px 0}
.bubble blockquote{margin:6px 0 0;padding-left:10px;border-left:2px solid rgba(var(--ov),.2);opacity:.88}
.bubble .meta{font-size:11px;opacity:.6;position:absolute;bottom:-22px;display:flex;gap:6px;align-items:center;white-space:nowrap;line-height:1}
/* Older-than-today messages carry their date before the time ("Apr 2, 5:15 PM") — the date lives in
   a data-d attribute stamped by refreshDaySepLabels, so the stored time text is never rewritten. */
.bubble .meta span[data-d]::before{content:attr(data-d)}
.edited-mark{font-style:italic;font-size:10px;opacity:.7}
.bubble .meta[data-hidden="1"]{display:none}
/* Web-search sources footer — host-name chips under a searched reply. */
.src-row{display:flex;flex-wrap:wrap;gap:4px;align-items:center;margin-top:8px;font-size:11px}
.src-lbl{opacity:.55;font-weight:600;margin-right:2px}
.src-chip{display:inline-flex;align-items:center;gap:5px;padding:2px 9px 2px 4px;border-radius:999px;background:rgba(var(--ov),.07);border:1px solid rgba(var(--ov),.1);color:inherit;opacity:.85;text-decoration:none;transition:opacity .12s,border-color .12s}
.src-chip:hover{opacity:1;border-color:var(--accent);color:var(--accent)}
.src-ico{width:14px;height:14px;border-radius:50%;display:block;flex-shrink:0}
/* Ticks were originally cyclable (single → double → blue), but the cycling feature is gone
   (cycleTick / makeTicksCyclable are stubs now). Cursor stays as default so it doesn't tease
   the user with a clickable affordance for nothing. */
.bubble .ticks{display:inline-flex;gap:2px;cursor:default;align-items:center;line-height:1}
.bubble .ticks svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;display:block}
.bubble .ticks[data-hidden="1"]{display:none}.bubble .ticks[data-state="none"]{opacity:0}
/* Action row (2026-05-17 redesign): hangs BELOW the bubble on the timestamp line — naked icons,
   no glass chip. top:100% + padding-top (not a margin gap) keeps the hover bridge unbroken, so
   the row doesn't flicker off while the cursor crosses from bubble to buttons. */
/* Hover LINGER (her 2026-07-08 note: options vanished too fast to catch): after the pointer leaves,
   the bar stays visible AND clickable for ~1s (opacity fade delayed .9s; visibility — which is what
   actually blocks clicks — flips only at 1.1s). Re-entering the bar (a bubble child) re-hovers the
   bubble and snaps it back instantly. */
.actions-br{position:absolute;top:100%;bottom:auto;display:flex;gap:2px;align-items:center;opacity:0;visibility:hidden;transition:opacity .2s ease .9s,visibility 0s linear 1.1s;z-index:5;padding-top:4px}
.bubble:hover .actions-br,.bubble.show-actions .actions-br{opacity:1;visibility:visible;transition-delay:0s,0s}
/* Timestamp + ticks ride INSIDE the row (rightmost) — kill their standalone absolute placement. */
.actions-br .meta{position:static!important;bottom:auto!important;left:auto!important;right:auto!important;margin-left:6px}
/* Theme-aware icon color (was hardcoded white — invisible-ish on the light theme). --ov is the
   theme's overlay RGB: white-ish in dark themes, black in light. Same size on ALL devices. */
.mini{width:24px;height:24px;border-radius:6px;background:transparent;border:none;display:grid;place-items:center;cursor:pointer;color:rgba(var(--ov),.6);line-height:0;padding:0;transition:color .1s,background .1s}
/* Fork switcher ‹ k/N › — lives at the END of the action bar on messages with sibling timelines
   (2026-07-08). Arrows are .mini buttons; the counter sits between them. */
.br-switch{display:inline-flex;align-items:center;gap:1px;margin-left:2px}
.br-switch .mini{width:18px;min-width:18px}
.br-switch .mini svg{width:12px;height:12px}
.br-switch .mini:disabled{opacity:.25;cursor:default;pointer-events:none}
.br-count{font-family:'Quicksand','DM Sans',sans-serif;font-size:10px;font-weight:600;color:rgba(var(--ov),.55);min-width:24px;text-align:center;letter-spacing:.03em;user-select:none} /* explicit — it was inheriting the persona bubble font (her note: NOT quicksand, now it is) */
/* ✕ delete-branch (2026-07-09, her pick): rides at the END of the switcher cluster. Desktop
   reveals it on cluster hover so the bar stays calm; touch keeps it visible (the bar there is
   already tap-gated, hover doesn't exist). Deletes the variant currently ON SCREEN. */
.br-switch .br-x{width:16px;min-width:16px;opacity:0;transition:opacity .15s ease;margin-left:1px}
.br-switch .br-x svg{width:10px;height:10px}
.br-switch:hover .br-x{opacity:.8}
.br-switch .br-x:hover{opacity:1}
@media (hover:none){.br-switch .br-x{opacity:.7}}
.mini:hover{color:rgba(var(--ov),.95);background:rgba(var(--ov),.1)}
/* Info-only variant — used by the (i) "Used: <model>" trigger. It's hover-only (the
   themed .tip tooltip below is the whole UX), so it loses the pointer cursor, the hover-
   promote bump, and uses the accent gold to read as informational rather than actionable.
   The actions-br backdrop is always dark (rgba(0,0,0,.3) regardless of theme), so
   var(--accent) reads correctly in both golden and lemon. */
/* Info indicator: NEUTRAL base (inherits .mini's theme color) — the old var(--accent) base
   flashed gold until applyRowStyle's metaColor pass caught up (Ksena 2026-05-13). The
   per-persona tint is applied inline at build time in addActionBar + refreshed by applyRowStyle. */
.actions-br .mini.info{cursor:default;opacity:.75}
.actions-br .mini.info:hover{background:transparent;opacity:.95}
/* AI-generated image bubbles carry no action row — their meta would otherwise revert to the
   old always-visible absolute hang, so it hides with the rest. */
.bubble[data-no-actions="1"] .meta{display:none!important}
/* Desktop hover-expand — SCOPED to CONSECUTIVE same-party messages (Ksena 2026-05-13: universal expand
   made the whole chat breathe on every mouse pass). A grouped bubble (data-group first/mid) gets
   margin-bottom:0 from applyRowStyle so it sits tight against the next message — its absolute .actions-br
   would then overlay that message. Push those on hover so the buttons have room, and the following
   messages slide down. Solo/last bubbles keep their 24px gap (buttons already fit — no push, no breathing).
   Image bubbles (data-no-actions) carry no buttons, so they're excluded from getting a hover margin.
   This REPLACES the older "push whenever the NEXT row is an image" rule, which also shoved a plain
   24px-gap AI message down when Ksena's OWN attached-image message (also no-actions) sat below it —
   her "his message pushes my image message on hover" report. Now that only happens between grouped
   messages that genuinely need the room. */
@media (hover:hover){
  .row[data-group="first"] .bubble:not([data-no-actions="1"]),
  .row[data-group="mid"] .bubble:not([data-no-actions="1"]){transition:margin-bottom .18s ease}
  .row[data-group="first"] .bubble:not([data-no-actions="1"]):hover,
  .row[data-group="mid"] .bubble:not([data-no-actions="1"]):hover{margin-bottom:34px!important}
}
/* Themed tooltip — same frosted-translucent treatment as the dropdown panel + hold-menu.
   Positioned by JS (position:fixed; coords from _attachTip), opacity-transitioned in/out.
   pointer-events:none so it never blocks hover/click on whatever's underneath. */
.tip{position:fixed;background:color-mix(in srgb,var(--ctrl) 88%,transparent);backdrop-filter:blur(14px) saturate(160%);-webkit-backdrop-filter:blur(14px) saturate(160%);border:1px solid var(--ctrl-b);border-radius:8px;padding:6px 10px;font-size:11px;font-family:"Quicksand","DM Sans",sans-serif;color:var(--text);pointer-events:none;z-index:10000;opacity:0;transform:translateY(3px);transition:opacity .14s ease,transform .14s ease;box-shadow:0 6px 20px rgba(0,0,0,.35);max-width:240px;line-height:1.4}
.tip.show{opacity:1;transform:translateY(0)}
.mini svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;display:block}
/* MOBILE / touch-only-device rules. We use both (hover: none) and (pointer: coarse) because some
   Android stylus tablets advertise hover support — coarse-pointer catches everything finger-only.
   On mobile we:
     1) hide the inline .actions-br entirely — the strip is still in the DOM (so the hold-menu can
        click() its buttons under the hood), it's just visually gone.
     2) hide the per-row ⋯ button on the chat list — long-press the row itself opens the menu instead.
     3) suppress text selection AND the iOS long-press callout on bubbles + chat-rows so long-press
        goes to OUR menu, not the system's "Copy / Look Up / Share". user-select:none on the bubble
        is what fixes the "hold just selects text" report. */
@media (hover: none),(pointer: coarse){
  /* Grok-style tap-to-expand (2026-05-17): a short tap on a bubble toggles .show-actions —
     the action row fades/slides in below the bubble while the bubble's bottom margin grows,
     so the zone visibly EXPANDS and pushes the next message down (not a ghost fading over it).
     Visibility is gated purely on .show-actions (never :hover, which is sticky on touch). */
  .actions-br{opacity:0!important;pointer-events:none!important;visibility:hidden!important;transform:translateY(-6px);transition:opacity .2s ease,transform .2s ease}
  .bubble.show-actions .actions-br{opacity:1!important;pointer-events:auto!important;visibility:visible!important;transform:translateY(0)}
  .bubble{transition:margin-bottom .2s ease}
  /* Action buttons keep the desktop size (Ksena 2026-05-18: 40px was "too big on the screen").
     Margin fits the 24px row + padding when it expands open. */
  .bubble.show-actions{margin-bottom:36px!important}
  .sess-entry .sess-menu-btn,.proj-entry .sess-menu-btn{display:none!important}
  .bubble{-webkit-user-select:none!important;user-select:none!important;-webkit-touch-callout:none!important}
  .sess-entry,.proj-entry,.lp-item,.row{-webkit-user-select:none!important;user-select:none!important;-webkit-touch-callout:none!important}
}
/* Suppress mobile-browser button tap chrome (the I-beam-flicker / blue tap-highlight rectangle).
   Applied broadly to every button-shaped element so we don't have to chase per-component fixes.
   Note: textareas and <input type="text"/password/etc> still allow selection because we never hit
   them with these rules. The list intentionally includes a bunch of <div>s that LOOK like buttons
   but aren't <button> tags (the in-header burger / gear, toggle switches, color pickers, etc.) —
   those weren't matched by the plain `button` selector and were the source of the "I-beam still
   flickers on these specific buttons" report. */
button,a.s-btn,.mini,.sess-menu-btn,.sess-x,.s-btn,.cf-btns button,.arch-row button,.arch-foot button,.hold-menu button,.sess-menu-dd button,.lp-item,.sess-entry,.lp-grp-hdr button,.lp-header button,
.mob-burger,.mob-floating-btn,
.s-tog,.s-tog .knob,
.s-sec-h,
input[type="color"],input[type="range"],input[type="checkbox"],input[type="radio"],
.tab-btn,.t-tab,.sn-item,.sett-tab,
[role="button"]{
  -webkit-user-select:none!important;user-select:none!important;
  -webkit-tap-highlight-color:transparent!important;
  -webkit-touch-callout:none!important;
}
/* Press-and-grow animation while finger is held down on a bubble (mobile only). The .holding class
   is added on touchstart and removed on touchend / touchcancel / touchmove-aborted / menu-open.
   The transform builds up over the same 500 ms window as the long-press timer, so the bubble feels
   like it's "winding up" — by the time the menu pops, the bubble is at full scale, then snaps back. */
.bubble{transition:transform .18s cubic-bezier(.2,.8,.3,1),box-shadow .18s cubic-bezier(.2,.8,.3,1)}
@media (hover: none),(pointer: coarse){
  .bubble.holding{transform:scale(1.045);box-shadow:0 12px 28px rgba(0,0,0,.18);z-index:10;transition:transform .42s cubic-bezier(.2,.8,.3,1),box-shadow .42s cubic-bezier(.2,.8,.3,1)}
}
/* Long-press context menu — used for both bubbles and session rows on mobile. Theme-aware (var(--bg2)
   et al.) so it matches the golden / dark theme Ksena uses by default. Built dynamically each open
   with items relevant to the long-pressed thing. Anchored near the touch point and clamped into the
   viewport. Outside-tap closes it. */
/* Frosted-glass hold-menu — the bg2 gets mixed with transparent so the backdrop-filter blur
   shows through. saturate(180%) keeps the chat colors underneath from washing out into a
   flat gray sheet. The border + shadow stay subtle so the menu reads as a floating panel
   instead of a hard rectangle. */
.hold-menu{position:fixed;background:color-mix(in srgb,var(--bg2) 72%,transparent);backdrop-filter:blur(24px) saturate(180%);-webkit-backdrop-filter:blur(24px) saturate(180%);border:1px solid rgba(var(--ov),.14);border-radius:14px;padding:6px;z-index:9100;display:none;min-width:220px;max-width:280px;box-shadow:0 14px 44px rgba(0,0,0,.45);font-family:"Quicksand","DM Sans",sans-serif;animation:holdMenuIn .14s ease-out}
/* "Used: <model>" header line at the top of the hold-menu for AI bubbles. Non-interactive
   label, sits above the first action button with a soft divider. */
.hold-menu .hold-menu-info{padding:9px 14px 9px;font-size:11px;color:var(--muted);font-weight:600;letter-spacing:.04em;border-bottom:1px solid rgba(var(--ov),.10);margin-bottom:4px}
.hold-menu.show{display:block}
@keyframes holdMenuIn{0%{opacity:0;transform:scale(.92)}100%{opacity:1;transform:scale(1)}}
.hold-menu button{display:flex;align-items:center;gap:12px;width:100%;background:none;border:none;color:var(--text);font-size:14px;padding:11px 14px;border-radius:9px;cursor:pointer;text-align:left;font-family:inherit;font-weight:500}
.hold-menu button:active{background:rgba(var(--ov),.08)}
.hold-menu button.danger{color:#d44040}
.hold-menu button.danger:active{background:rgba(180,40,40,.12)}
.hold-menu button svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2;flex-shrink:0;display:block}
.hold-menu hr{border:none;border-top:1px solid rgba(var(--ov),.08);margin:4px 6px}
.day-sep{align-self:center;font-size:13px;padding:5px 12px;border-radius:14px;cursor:pointer;position:relative}
.typing-ind{display:none;align-items:center;gap:8px;padding:4px 16px;flex-shrink:0;box-shadow:0 -2px 8px rgba(var(--ov),.05)}
.typing-ind.show{display:flex}
.typing-ind .typing-dots{padding:0}
.typing-ind .typing-dot{background:var(--accent)}
.typing-ind .status-text{font-size:11px;color:var(--muted);font-style:italic;letter-spacing:.02em}
.typing-row{display:flex;align-items:center;padding:4px 0;opacity:0;animation:typingFadeIn .3s ease forwards}
/* Web-search typing state: the detective — a magnifier that pulses and drifts like it's
   examining clues (Ksena 2026-05-14; deliberately NOT a spinner), plus a small status label. */
.search-scan{display:flex;align-items:center;gap:7px;padding:0 4px}
.search-mag{width:17px;height:17px;color:var(--accent);animation:magScan 2.2s ease-in-out infinite;transform-origin:center}
.search-label{font-size:11px;color:var(--muted);font-style:italic;letter-spacing:.02em}
@keyframes magScan{0%{transform:translate(0,0) scale(1)}18%{transform:translate(3px,-2px) scale(1.14)}38%{transform:translate(-2px,2px) scale(.94)}58%{transform:translate(2px,2px) scale(1.1)}78%{transform:translate(-3px,-1px) scale(1)}100%{transform:translate(0,0) scale(1)}}
@media (prefers-reduced-motion:reduce){.search-mag{animation:none}}
/* ── Thinking pill (reasoning models) — collapsed chip above the reply; expands to a quote box
   in smaller muted text. Ksena 2026-05-14, Claude-app style. */
.think-wrap{margin:0 0 8px}
.think-pill{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:999px;background:rgba(var(--ov),.06);border:1px solid rgba(var(--ov),.1);color:inherit;opacity:.75;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;transition:opacity .12s,border-color .12s}
.tp-bulb{width:13px;height:13px;flex-shrink:0}
/* Reasoning-ON indicator in the top bar (her 2026-07-04 ask — a shining bulb, not a dot). */
.think-on-pill{display:inline-flex;align-items:center;gap:3px;padding:2px 8px 2px 6px;border-radius:999px;font-size:10px;font-weight:700;letter-spacing:.06em;background:color-mix(in srgb,var(--accent) 16%,transparent);color:var(--accent);border:1px solid color-mix(in srgb,var(--accent) 35%,transparent)}
.think-on-pill[hidden]{display:none}
.think-on-pill .tp-bulb{width:12px;height:12px;filter:drop-shadow(0 0 3px color-mix(in srgb,var(--accent) 60%,transparent))}
.think-pill:hover{opacity:1;border-color:var(--accent)}
.think-chev{width:12px;height:12px;transition:transform .18s}
.think-wrap.collapsed .think-chev{transform:rotate(-90deg)}
.think-body{margin-top:6px;padding:8px 10px;border-left:2px solid rgba(var(--ov),.18);background:rgba(var(--ov),.04);border-radius:0 8px 8px 0;font-size:.85em;line-height:1.5;color:inherit;opacity:.75;white-space:pre-wrap;word-break:break-word;max-height:280px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--scroll) transparent}
.think-body::-webkit-scrollbar{width:6px}
.think-body::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:6px}
.think-wrap.collapsed .think-body{display:none}
/* ── Live Voice Mode (her design handoff 2026-07-03). Fidelity: high — tokens verbatim from the
   README; fonts aligned to the app's existing serif/sans per its instruction. Light theme is the
   default styling; dark overrides ride body[data-mode="dark"]. */
.vc-screen{position:fixed;inset:0;z-index:9500;background:#cfc2b0;overflow:hidden}
body[data-mode="dark"] .vc-screen{background:#000}
.vc-screen[hidden]{display:none}
/* vc-card: invisible full-bleed wrapper on mobile; the popup card on desktop (below). */
.vc-card{position:absolute;inset:0}
/* DESKTOP POPUP (2026-08-06, her ask — linn4jace LIVE CALL style): the screen becomes a glass
   scrim blurring the app; the call itself lives in a centered rounded card. Mobile keeps the
   full-screen takeover (a popup on a phone is just a smaller phone). */
@media (min-width:900px){
  .vc-screen{background:rgba(30,24,14,.30);backdrop-filter:blur(22px) saturate(1.15);-webkit-backdrop-filter:blur(22px) saturate(1.15)}
  body[data-mode="dark"] .vc-screen{background:rgba(14,9,3,.22)} /* light veil only — the blur does the work; .42 read as pitch black over the dark app */
  .vc-card{position:absolute;inset:auto;left:50%;top:50%;transform:translate(-50%,-50%);width:min(460px,92vw);height:min(680px,90vh);border-radius:26px;overflow:hidden;background:#cfc2b0;border:1px solid rgba(255,255,255,.22);box-shadow:0 30px 90px rgba(0,0,0,.5)}
  body[data-mode="dark"] .vc-card{background:#050505;border-color:rgba(255,255,255,.09)}
}
.vc-sheen{position:absolute;inset:0;pointer-events:none;background:radial-gradient(130% 55% at 50% -12%,rgba(255,255,255,.35),transparent 60%)}
body[data-mode="dark"] .vc-sheen{background:radial-gradient(130% 55% at 50% -12%,rgba(255,255,255,.09),transparent 60%)}
/* content overlay lets clicks fall through to the visualizer (tap canvas = cycle variant);
   only the interactive clusters re-enable pointer events */
.vc-content{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;padding:calc(14px + env(safe-area-inset-top)) 26px calc(24px + env(safe-area-inset-bottom));pointer-events:none} /* top 26→14 / bottom 40→24 (her 2026-07-07 layout pass: LIVE VOICE + panel higher, controls lower) */
.vc-top,.vc-panel,.vc-controls{pointer-events:auto}
/* Mid-call generated image card: expands in center-stage (scale+fade pop), rounded + shadowed;
   sits between the panel and the controls, over the lifeline (which dips via its center attr). */
.vc-img{margin-top:6px;pointer-events:auto;opacity:0;transform:scale(.6) translateY(12px);transition:opacity .3s ease,transform .38s cubic-bezier(.2,.9,.3,1.18);z-index:8;max-width:min(78vw,400px)} /* margin 18→6 (her tweak: image higher, clear of the lifeline) */
.vc-img.in{opacity:1;transform:scale(1) translateY(0)}
.vc-img[hidden]{display:none}
.vc-img img{display:block;width:100%;max-height:36vh;object-fit:contain;border-radius:22px;box-shadow:0 18px 50px rgba(0,0,0,.45)}
/* The rp-toggle chevron pierced the call screen on desktop — hide ALL of it while a call is up. */
body.kxd-on-call .rp-toggle{display:none!important}
.vc-top{display:flex;align-items:center;justify-content:space-between;width:100%}
.vc-back{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;cursor:pointer;background:rgba(255,251,238,.4);border:1px solid rgba(255,255,255,.6);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);color:#453a26;padding:0}
body[data-mode="dark"] .vc-back{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.12);color:#f3e8d0}
.vc-live{font-family:'Quicksand','DM Sans',sans-serif;font-size:10px;font-weight:600;letter-spacing:.24em;text-transform:uppercase;color:rgba(69,58,38,.45)}
body[data-mode="dark"] .vc-live{color:rgba(243,232,208,.4)}
.vc-topspacer{width:40px}
/* Panel chrome FULLY retired (2026-08-06 round 2, her clarification): no outline, no glass
   slab, no shadow — the name/timer/state float entirely clear over the visualizer. */
.vc-panel{margin-top:26px;padding:18px 34px;display:flex;flex-direction:column;align-items:center;gap:9px;background:none;border:none;box-shadow:none}
.vc-name{font-family:'Cormorant Upright',Georgia,serif;font-weight:600;font-size:34px;line-height:1;color:#1b2220} /* 40→34 per her 2026-07-07 layout pass */
body[data-mode="dark"] .vc-name{color:#344b43;text-shadow:0 2px 24px rgba(39,170,137,.35)}
.vc-timer{font-family:'Quicksand','DM Sans',sans-serif;font-size:12px;letter-spacing:.3em;color:rgba(69,58,38,.6)}
body[data-mode="dark"] .vc-timer{color:rgba(243,232,208,.55)}
/* state word: her gold in dark; light mode needs a dark warm brown — bright yellow drowned
   on the light background (her note 2026-07-03) */
.vc-state{font-family:'Quicksand','DM Sans',sans-serif;font-size:10px;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:#4a3f30}
body[data-mode="dark"] .vc-state{color:#ffae00}
.vc-controls{margin-top:auto;display:flex;align-items:center;justify-content:center;gap:64px} /* 42→64 (her 2026-07-08: "don't be afraid" — more air between mute and hang-up) */
.vc-mute{width:58px;height:58px;border-radius:50%;display:grid;place-items:center;cursor:pointer;padding:0;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);background:rgba(69,58,38,.07);border:1px solid rgba(255,255,255,.6);color:rgba(69,58,38,.8);transition:background .15s,color .15s}
body[data-mode="dark"] .vc-mute{background:rgba(255,255,255,.09);border-color:rgba(255,255,255,.12);color:rgba(243,232,208,.85)}
.vc-mute.on{background:#ffae00;color:#fff8e6;border-color:transparent}
body[data-mode="dark"] .vc-mute.on{background:#ffae00;color:#221604}
.vc-mute .vc-slash{opacity:0;transition:opacity .15s}
.vc-mute.on .vc-slash{opacity:1}
.vc-end{width:66px;height:66px;border-radius:50%;display:grid;place-items:center;cursor:pointer;padding:0;border:none;background:#ffae00;color:#fff8e6;box-shadow:0 12px 32px rgba(255,174,0,.35)}
body[data-mode="dark"] .vc-end{color:#221604}
.vc-end svg{transform:rotate(135deg)}
/* open/close v2 (her note: the old scale-down showed the screen's edges). The screen never
   scales now — a SOFT radial shadow swallows it from the edges inward (::before, oversized so
   its rim always lies beyond the viewport), then flat black (::after) finishes the last beat.
   Opening runs the bloom in reverse: black lifts first, then the shadow retreats outward. */
.vc-screen::before{content:'';position:absolute;inset:-70%;pointer-events:none;z-index:6;background:radial-gradient(circle at 50% 52%,rgba(0,0,0,0) 24%,rgba(0,0,0,.85) 46%,#000 62%);opacity:0;transform:scale(2.6);transition:transform .6s cubic-bezier(.45,.05,.35,1),opacity .35s ease .25s}
.vc-screen.vc-veiled::before{opacity:1;transform:scale(.62);transition:transform .6s cubic-bezier(.45,.05,.35,1),opacity .2s ease}
.vc-screen::after{content:'';position:absolute;inset:0;background:#000;opacity:0;pointer-events:none;z-index:7;transition:opacity .28s ease}
.vc-screen.vc-veiled::after{opacity:1;transition:opacity .3s ease .32s}
@media (prefers-reduced-motion:reduce){.vc-screen::before,.vc-screen::after,.vc-card::before,.vc-card::after{transition:none}}
/* DESKTOP (2026-08-06, her "it's pitch black" report): the veil above covers the whole SCREEN —
   on the popup layout that blacked out the glass scrim too. Move the bloom INSIDE the card
   (same radial + flat-black two-beat, same timings); the blurred app behind stays visible. */
@media (min-width:900px){
  .vc-screen::before,.vc-screen::after{display:none}
  .vc-card::before{content:'';position:absolute;inset:-70%;pointer-events:none;z-index:6;background:radial-gradient(circle at 50% 52%,rgba(0,0,0,0) 24%,rgba(0,0,0,.85) 46%,#000 62%);opacity:0;transform:scale(2.6);transition:transform .6s cubic-bezier(.45,.05,.35,1),opacity .35s ease .25s}
  .vc-screen.vc-veiled .vc-card::before{opacity:1;transform:scale(.62);transition:transform .6s cubic-bezier(.45,.05,.35,1),opacity .2s ease}
  .vc-card::after{content:'';position:absolute;inset:0;background:#000;opacity:0;pointer-events:none;z-index:7;transition:opacity .28s ease}
  .vc-screen.vc-veiled .vc-card::after{opacity:1;transition:opacity .3s ease .32s}
}
@keyframes typingFadeIn{to{opacity:1}}
.typing-av{width:24px;height:24px;border-radius:50%;object-fit:cover;flex-shrink:0;margin-right:-8px;position:relative}
.typing-av:last-of-type{margin-right:6px}
.typing-dots{display:flex;align-items:center;gap:4px;padding:4px 0}
.typing-dot{width:5px;height:5px;border-radius:50%;background:var(--muted);animation:dotSway 1.4s ease-in-out infinite}
.typing-dot:nth-child(2){animation-delay:.2s}
.typing-dot:nth-child(3){animation-delay:.4s}
@keyframes dotSway{0%,100%{transform:translateY(0);opacity:.4}50%{transform:translateY(-4px);opacity:1}}
.drawing-stars{display:flex;align-items:center;gap:5px;padding:4px 0}
.drawing-star{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;color:var(--accent);animation:starSway 1.4s ease-in-out infinite}
.drawing-star:nth-child(2){animation-delay:.2s}
.drawing-star:nth-child(3){animation-delay:.4s}
.drawing-star svg{width:100%;height:100%;display:block}
@keyframes starSway{0%,100%{transform:translateY(0);opacity:.4}50%{transform:translateY(-4px);opacity:1}}
.img-gen-error{display:flex;align-items:center;gap:10px;padding:8px 12px;background:rgba(220,80,80,.08);border:1px solid rgba(220,80,80,.25);border-radius:10px;margin-bottom:6px;font-size:13px}
.img-gen-error .err-icon{font-size:14px;opacity:.75}
.img-gen-error .err-text{flex:1;opacity:.8}
.img-retry-btn{padding:4px 10px;font-size:12px;background:var(--accent);color:#111;border:none;border-radius:8px;cursor:pointer;font-family:inherit;font-weight:600}
.img-retry-btn:hover{opacity:.9}
.img-retry-btn:disabled{opacity:.5;cursor:wait}
.fmt-wrap{display:flex;align-items:center;border-top:1px solid var(--border);background:var(--chrome);flex-shrink:0;min-width:0;max-width:100%}
.fmt-wrap[data-hidden="1"]{display:none}
.fmt-bar{display:flex;gap:2px;padding:6px 60px 0;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;flex:1;min-width:0;scrollbar-width:none}
.fmt-bar::-webkit-scrollbar{display:none}
.fmt-arrow{width:18px;height:26px;border-radius:5px;background:none;border:none;color:var(--muted);display:none;place-items:center;cursor:pointer;flex-shrink:0;padding:0;opacity:.3;transition:opacity .15s;margin:0 1px}
.fmt-arrow:hover{opacity:1}
.fmt-arrow svg{width:10px;height:10px;stroke:currentColor;fill:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round}
.fmt-wrap.needs-scroll .fmt-arrow{display:grid}
.fmt-b{width:28px;height:26px;border-radius:5px;background:none;border:1px solid transparent;color:var(--muted);display:grid;place-items:center;cursor:pointer;font-family:'Crimson Text',serif;font-size:14px;font-weight:700;padding:0;transition:all .12s;flex-shrink:0}
.fmt-b:hover{color:var(--text);background:var(--ctrl);border-color:var(--ctrl-b)}
.fmt-b svg{width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2.5}
.fmt-sep{width:1px;height:18px;background:var(--border);margin:4px 3px;flex-shrink:0}

.composer{background:var(--chrome);padding:10px 14px;display:flex;gap:8px;align-items:center;position:relative}
/* ── + menu (composer actions dropdown) ─────────────────────────────────────────────
   Opens UPWARD from the + button (composer sits at the bottom of the viewport).
   Themed like the other dropdown panels: var(--ctrl) surface, soft shadow, 6px gold scrollbar
   not needed (short list). Extensible: new actions = new .plus-menu-item rows. */
.plus-menu{position:absolute;bottom:calc(100% + 8px);left:14px;min-width:210px;background:var(--ctrl);border:1px solid var(--ctrl-b);border-radius:12px;box-shadow:0 8px 26px rgba(0,0,0,.22);z-index:1500;display:none;overflow:hidden;padding:4px}
.plus-menu.open{display:block;animation:plusMenuIn .16s cubic-bezier(.2,.8,.3,1)}
@keyframes plusMenuIn{0%{opacity:0;transform:translateY(6px) scale(.97)}100%{opacity:1;transform:translateY(0) scale(1)}}
.plus-menu-item{display:flex;align-items:center;gap:10px;padding:9px 11px;border-radius:8px;cursor:pointer;font-size:13px;color:var(--text);transition:background .12s;user-select:none;-webkit-tap-highlight-color:transparent}
.plus-menu-item:hover{background:rgba(var(--ov),.08)}
.plus-menu-item svg{width:16px;height:16px;flex-shrink:0;color:var(--muted)}
.plus-menu-item span:first-of-type{flex:1}
/* Mini toggle switch for on/off rows (web search) */
.plus-menu-switch{flex-shrink:0;width:32px;height:18px;border-radius:999px;background:rgba(var(--ov),.15);position:relative;transition:background .18s}
.plus-menu-switch::after{content:'';position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:var(--bg2);box-shadow:0 1px 3px rgba(0,0,0,.25);transition:left .18s}
.plus-menu-switch.on{background:var(--accent)}
.plus-menu-switch.on::after{left:16px}
/* Thinking power dropdown (2026-08-06 v2, her ask): a sub-row under the Thinking toggle,
   expanding an app-styled option list — never a native select. Options adapt to the active
   model's REAL supported levels (bridge catalog per-model / NanoGPT low-med-high). */
.plus-menu-effort{padding:0}
.plus-menu-effort[hidden]{display:none}
.pm-eff-head{display:flex;align-items:center;gap:8px;padding:8px 11px 8px 37px;border-radius:8px;cursor:pointer;font-size:12.5px;color:var(--muted);user-select:none;-webkit-tap-highlight-color:transparent;transition:background .12s}
.pm-eff-head:hover{background:rgba(var(--ov),.08)}
.pm-eff-cur{margin-left:auto;color:var(--text);font-size:12px}
.pm-eff-chev{width:12px;height:12px;flex-shrink:0;transition:transform .15s}
.plus-menu-effort.open .pm-eff-chev{transform:rotate(180deg)}
.pm-eff-list{display:none;padding:0 4px 4px 33px}
.plus-menu-effort.open .pm-eff-list{display:block}
.pm-eff-opt{padding:6px 11px;border-radius:8px;font-size:12.5px;color:var(--text);cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;transition:background .12s}
.pm-eff-opt:hover{background:rgba(var(--ov),.08)}
.pm-eff-opt.on{color:var(--accent);font-weight:600}
/* + button states: rotate to × while the menu is open; gold + badge dot while web search is on */
#attachBtn svg{transition:transform .18s}
#attachBtn.menu-open svg{transform:rotate(45deg)}
#attachBtn{position:relative}
#attachBtn .ws-badge{display:none;position:absolute;top:-3px;right:-3px;width:9px;height:9px;border-radius:50%;background:var(--accent);border:2px solid var(--chrome)}
#attachBtn.ws-on{border-color:var(--accent);color:var(--accent)}
#attachBtn.ws-on .ws-badge{display:block}
/* Selection formatting mini-menu — floats centered above the pill while composer text is
   selected. Same surface treatment as the + menu. */
.sel-menu{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translateX(-50%);display:none;gap:2px;background:var(--ctrl);border:1px solid var(--ctrl-b);border-radius:10px;padding:3px;box-shadow:0 8px 26px rgba(0,0,0,.22);z-index:1500}
/* Dedicated entrance keyframes: they MUST carry the translateX(-50%) centering — reusing
   plusMenuIn overrode the base transform for 160ms, so the menu appeared right-of-center and
   then SNAPPED to the middle when the animation released the transform (Ksena's "it jumps"). */
.sel-menu.show{display:flex;animation:selMenuIn .16s cubic-bezier(.2,.8,.3,1)}
@keyframes selMenuIn{0%{opacity:0;transform:translateX(-50%) translateY(6px) scale(.97)}100%{opacity:1;transform:translateX(-50%) translateY(0) scale(1)}}
/* Mobile: skipped entirely (Ksena's call, same as Discord) — the web platform has NO API to
   join or outrank the native copy/paste selection toolbar; that's a native-app privilege. */
@media (hover:none),(pointer:coarse){.sel-menu{display:none!important}}
.sel-menu button{width:30px;height:30px;border:none;background:transparent;border-radius:7px;color:var(--text);cursor:pointer;font-size:13px;font-family:inherit;display:grid;place-items:center;padding:0}
.sel-menu button:hover{background:rgba(var(--ov),.08);color:var(--accent)}
/* ── ChatGPT-style composer button choreography ─────────────────────────────
   Visibility is fully CSS-driven off .composer's data-cstate (+ .dictating override), so the
   JS only ever flips one attribute/class and never touches style props. */
#micBtn,#voiceBtn,#sendBtn,#stopBtn,#dictCancelBtn{display:none}
.composer[data-cstate="empty"] #micBtn,.composer[data-cstate="empty"] #voiceBtn{display:grid}
.composer[data-cstate="typed"] #micBtn,.composer[data-cstate="typed"] #sendBtn{display:grid}
.composer[data-cstate="gen"] #micBtn,.composer[data-cstate="gen"] #stopBtn{display:grid}
.composer[data-cstate="gen-typed"] #sendBtn,.composer[data-cstate="gen-typed"] #stopBtn{display:grid}
.composer.dictating #micBtn,.composer.dictating #dictCancelBtn{display:grid!important}
.composer.dictating #voiceBtn,.composer.dictating #sendBtn,.composer.dictating #stopBtn{display:none!important}
/* Circular right-slot buttons (voice / send / stop) — round, icon-only */
/* padding:0 needs !important: .send-btn's legacy text-button padding (0 14px) is declared later
   in the sheet and was shoving the arrow off-center inside the fixed-width circle. */
.comp-circle{border-radius:50%!important;width:38px!important;height:38px!important;min-width:38px;flex-shrink:0;padding:0!important}
.comp-circle svg{width:17px;height:17px}
#sendBtn.comp-circle{background:var(--accent);border-color:var(--accent);color:#1a1410;font-size:0}
#sendBtn.comp-circle:hover{filter:brightness(1.06)}
#stopBtn.comp-circle{background:var(--accent);border-color:var(--accent);color:#1a1410}
#stopBtn.comp-circle svg{width:14px;height:14px}
/* (Three orphan rules + a TRUNCATED, NEVER-CLOSED comment used to live right here — the broken
   comment swallowed every rule down to the pills block, which is why the composer textarea spent
   days wearing browser-default monospace + resize gripper + focus ring. Exorcised 2026-05-13.) */
/* Gold hover for the pill's ghost buttons — one of the exorcised fossils was accidentally
   doing this for the mic and Ksena loved it ("a very pretty yellow"). Now it's intentional,
   and the whole ghost family matches. ID selectors outrank .comp-btn:hover's var(--text). */
#attachBtn:hover,#emojiBtn:hover,#micBtn:hover,#dictCancelBtn:hover{color:var(--accent)}
.attach-preview{display:flex;flex-wrap:wrap;gap:6px;padding:6px 14px 0;background:var(--chrome);align-items:flex-start}
.attach-preview:empty{display:none}
.attach-thumb{position:relative;width:60px;height:60px;border-radius:8px;overflow:hidden;background:rgba(0,0,0,.1);flex-shrink:0;border:1px solid rgba(var(--ov),.12)}
.attach-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.attach-thumb .x{position:absolute;top:2px;right:2px;width:18px;height:18px;border-radius:50%;background:rgba(0,0,0,.65);color:#fff;font-size:13px;line-height:1;text-align:center;cursor:pointer;border:none;padding:0;display:flex;align-items:center;justify-content:center}
.attach-thumb .x:hover{background:rgba(0,0,0,.85)}
.composer[data-hidden="1"]{display:none}
/* padding-y 13px = (44px height − 18px line box) / 2, so a single line sits EXACTLY on the
   vertical center of the pill (Ksena's perfectionism itch, 2026-05-13). */
.comp-input{font-family:'Quicksand','DM Sans',sans-serif;font-size:15px;flex:1;background:var(--input-bg);border:1px solid var(--ctrl-b);color:var(--text);padding:13px 14px;border-radius:14px;outline:none;min-width:0;min-height:44px;max-height:120px;overflow-y:auto;line-height:1.2;box-sizing:border-box}
.comp-input::placeholder{color:var(--text-40)}
/* ── RICH COMPOSER (2026-07-08): the contenteditable div that replaced the textarea. It sizes
   itself (min/max-height above — the old JS height juggling is retired for the composer).
   white-space:pre-wrap renders \n and preserves spaces; :empty::before is the placeholder. */
.comp-rich{white-space:pre-wrap;word-break:break-word;caret-color:var(--text);cursor:text}
.comp-rich:empty::before{content:attr(data-placeholder);color:var(--text-40);pointer-events:none}
/* Pills inside the rich composer get their COLORS BACK (the thing she missed since the overlay
   inversion): real colored text on a soft chip, atomic (contenteditable=false → deletes as one). */
/* iOS HARDENING (round 40): WebKit's user-select propagates into contenteditable children and can
   leave them flaky to select/edit — the documented cure is to assert `text` on the editable itself.
   This app has user-select:none scattered over every button-shaped thing (see the big rule near the
   tap-chrome block) plus user-select:all on the pills below, so the composer sits in a neighbourhood
   full of it. Stated explicitly here so the editable can never inherit or half-inherit the wrong
   value — a candidate for her "tapping a misspelled word works maybe half the time". */
[contenteditable]{-webkit-user-select:text;user-select:text}
/* user-select:all → the pill is one selection unit: mouse/touch sweeps flow OVER it instead of
   stopping dead at the contenteditable=false boundary (her report: double-click selection died at
   the @mention), and tapping a pill selects it whole. */
/* Typography matches the .sc-tag example chips above the System Prompt (her 2026-07-08 spec: same
   font size, weight, and in-chip padding as those; colors stay per-type). */
.comp-rich .pill{border-radius:6px;padding:2px 8px;margin:0 2px;font-size:10px;font-weight:600;line-height:1.2;box-shadow:none;white-space:nowrap;vertical-align:1px;-webkit-user-select:all;user-select:all}
.comp-rich .pill-mention{background:rgba(39,170,137,.16);color:#27aa89!important;-webkit-text-fill-color:#27aa89!important} /* Dieter's viridian — his story-ring color (her 2026-07-08 spec) */
.comp-rich .pill-slash{background:rgba(120,150,180,.22);color:#7396b8!important;-webkit-text-fill-color:#7396b8!important}
.comp-rich .pill-tag{background:rgba(180,140,90,.25);color:#b08a54!important;-webkit-text-fill-color:#b08a54!important}
/* Pills-in-textarea (Discord-style mention/slash/tag highlighting). The textarea is the input layer
   AND renders the visible text; a sibling overlay div sized to inset:0 on the wrap mirrors the same
   text invisibly, with @mentions / /commands / {tags} wrapped in .pill spans whose BACKGROUND chip is
   the only painted part (see the inverted-layers note below). We deliberately do NOT pad/border the
   pills — that would change their rendered width and de-align the chips from the textarea text. */
/* The pill renderer relies on the textarea and the overlay div rendering the SAME text at
   pixel-identical positions. Two text-shaping pipelines threaten that:
   1. Browsers apply kerning across the whole textarea text run, but the overlay breaks at
      every <span class="pill"> boundary — so text touching a pill edge loses its kerning,
      drifting the overlay's character positions a few pixels right of the textarea's by
      the time you reach the bottom of a paragraph.
   2. Ligatures (fi, fl, etc.) can also fire differently across the same run boundary.
   Both end up showing as a "cursor offset" — backspace deletes characters before where the
   caret visually sits — and as a faint ghost on selection. Disabling both shaping passes on
   both elements forces glyph-by-glyph identical layout. Slight visual cost (kerning is gone)
   but unavoidable for the textarea-over-overlay technique. */
.pill-wrap>textarea,.pill-overlay{font-kerning:none;font-variant-ligatures:none;font-feature-settings:"liga" 0,"clig" 0,"kern" 0}
.pill-wrap{position:relative;display:block;width:100%}
/* In the composer (a flex row with attach + textarea + send/stop buttons) the wrap inherits the
   textarea's old flex:1 slot. Other parents (sc-field divs in Settings) use the default block. */
.composer>.pill-wrap{flex:1;min-width:0;width:auto}
/* ── INVERTED LAYERS (2026-07-07, the "stuck cursor" fix) ─────────────────────────────────────
   Previously the TEXTAREA's text was transparent and the overlay rendered the visible text. Any
   wrap-point disagreement between the two rendering pipelines (which iOS still produced during
   deletions, despite the kerning/ligature clamps) made the visible words sit on a different line
   than the caret — Ksena's "the cursor is stuck inside another word / there's a ghost of the word"
   while editing. Now the TEXTAREA renders its own visible text: caret and glyphs come from the SAME
   engine, so they can never disagree, no matter how the layout reflows. The overlay's job shrinks to
   painting the pill BACKGROUND chips (its text is fully transparent, pills included, so glyphs are
   never double-drawn). Worst remaining failure = a chip tint a couple px off on a reflow edge case —
   cosmetic, instead of the text itself lying about where it is. */
.pill-wrap>textarea{display:block;width:100%;position:relative;z-index:2;background:transparent!important;color:var(--text);caret-color:var(--text);-webkit-text-fill-color:var(--text)}
.pill-wrap>textarea::placeholder{color:var(--text-40);-webkit-text-fill-color:var(--text-40);opacity:1}
.pill-wrap>textarea::selection{background:rgba(242,192,91,.32)}
.pill-overlay{position:absolute;inset:0;pointer-events:none;color:transparent;z-index:1;overflow:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;box-sizing:border-box;font-family:inherit;font-size:inherit}
/* The pill chips: background + radius only. NO horizontal padding (width must stay = the text width)
   and NO text color — the glyphs above them are the textarea's; the overlay only supplies the tint. */
.pill{border-radius:4px}
/* Transparent text applies ONLY to overlay chips (the textarea-mirror system, still used by the
   System Core Prompt). The rich composer's pills are real visible text — see .comp-rich .pill. */
.pill-overlay .pill{color:transparent!important;-webkit-text-fill-color:transparent!important}
/* The inset ring (box-shadow paints INSIDE the box and never affects text layout — no ghost risk)
   makes the chip read as a deliberate tag rather than a smudge, since the text itself can no longer
   carry the colour (see inverted-layers note). */
.pill-mention{background:rgba(242,192,91,.30);box-shadow:inset 0 0 0 1px rgba(242,192,91,.5)}
/* Dust-blue chip for slash commands — soft slate that reads against the input bg without competing
   with the gold mentions. */
.pill-slash{background:rgba(120,150,180,.30);box-shadow:inset 0 0 0 1px rgba(120,150,180,.5)}
/* Warm bronze chip for {template tags} — keeps the warm-but-soft vibe of the golden theme. */
.pill-tag{background:rgba(180,140,90,.32);box-shadow:inset 0 0 0 1px rgba(180,140,90,.55)}
.comp-btn{height:38px;min-width:38px;display:grid;place-items:center;background:var(--ctrl);border:1px solid var(--ctrl-b);color:var(--text);border-radius:10px;cursor:pointer}
.send-btn{padding:0 14px;font-weight:700;font-family:'Quicksand','DM Sans',sans-serif;font-size:14px}
/* ── Composer pill (Ksena's redesign 2026-05-17) ─────────────────────────────────────────────
   The whole composer is ONE rounded pill: + inside the left edge, emoji/mic/voice-send cluster
   inside the right edge, textarea transparent in between. All secondary buttons are borderless
   ghosts; only voice/send/stop keep the accent circle. (The choreography/data-cstate rules and
   the accent-circle styling live in the earlier "composer button choreography" block — this
   block is the pill look + the states that used to sit in a duplicated copy of that block,
   deduped 2026-05-17.) */
/* FROSTED FLOATING COMPOSER (her 2026-07-04 goal, the real Claude look): the composer floats
   over the bottom of the messages via the absolutely-positioned .composer-dock; chat text scrolls
   BEHIND it and shows through the frosted glass, muted, then disappears under it. Translucent bg +
   backdrop-blur = the frost. --frost knob on .composer controls how see-through it is. */
/* Frosted floating pill. NO box-shadow glow (it leaked onto the side panels) — the "shadow" she
   wants is the full-chat-width scrim behind it (see .panel-center::after below). */
/* CARET FIX round 32 (2026-08-12, her screenshot #1 — round 31's selection nudge did NOT cure it):
   the frost used to sit on .composer itself, which made it a backdrop-filtered ANCESTOR of the
   contenteditable #input. WebKit paints the caret of an editable into the wrong compositing layer
   under exactly that condition — caret stranded above the pill, frozen mid-word while typing. The
   glass now lives on a ::before instead, so no ancestor of #input carries a filter and the look is
   unchanged. .composer takes z-index:1 so its ::before (z-1) still paints ABOVE .dock-scrim — its
   own stacking context — rather than letting the scrim wash over the pill. Plain z-index makes a
   stacking context but NOT a backdrop root, so ::before still samples the chat behind it. */
.composer{background:transparent;border:1px solid var(--ctrl-b);border-radius:26px;margin:0 10px 10px;padding:5px 9px;align-items:center;position:relative;z-index:1;--frost:40%}
.composer::before{content:'';position:absolute;inset:0;border-radius:inherit;background:color-mix(in srgb,var(--input-bg) var(--frost,40%),transparent);backdrop-filter:blur(22px) saturate(1.5);-webkit-backdrop-filter:blur(22px) saturate(1.5);z-index:-1;pointer-events:none}
/* Refused-send SHAKE (her 2026-07-08 call — replaces the "Stop the current reply first" toast):
   the classic not-yet headshake. Applied by sendText when a send is blocked mid-generation. */
@keyframes kxShake{0%,100%{transform:translateX(0)}20%{transform:translateX(-6px)}40%{transform:translateX(6px)}60%{transform:translateX(-4px)}80%{transform:translateX(4px)}}
.composer.shake{animation:kxShake .38s ease}
/* Desktop: the composer locks to the BUBBLE COLUMN width (770px, same as .messages' centered
   content) instead of stretching to fill the panel — her 2026-07-08 note: hiding the right panel
   or zooming out let the bar balloon across the whole space. Mobile keeps its own margins. */
@media(min-width:769px){.composer{width:calc(100% - 20px);max-width:770px;margin-left:auto;margin-right:auto}}
/* The dock holds the composer (+ reply/attach chrome) and floats it over the chat. */
.composer-dock{position:absolute;left:0;right:0;bottom:0;z-index:6;pointer-events:none}
.composer-dock>*{pointer-events:auto}
/* THE SCRIM (her 2026-07-04 drawing): a full-chat-width gradient across the BOTTOM of the chat,
   in the theme colour (--bg), dense at the very bottom and dissolving upward into transparent.
   Sits BEHIND the composer (z4 < dock z6) and ABOVE the messages text (z auto) — so chat text
   melts into the theme colour as it nears the bar, and the composer floats on top. Contained to
   .panel-center, so it never leaks onto the side panels (unlike the box-shadow did). Tunable
   --scrim-h (height). */
/* The scrim lives INSIDE the dock (z6) as its own element, so it's guaranteed above ALL message
   content (action bars are z5, bubbles can create stacking contexts) and below the composer, which
   paints on top of it. Full chat width, theme-colour gradient dissolving up. (Earlier it was a
   .panel-center::after at z4 which the message layer could out-stack — that's why it "didn't work"
   in the real app even though it looked right in the lab.) */
/* GLASS SCRIM (2026-08-06, her iOS-style ask): frosted blur rising from the composer, masked
   to dissolve upward — messages melt into glass instead of into a black gradient. The colour
   wash rides at half strength for text contrast; --scrim-a / --scrim-h stay her tuning knobs. */
.dock-scrim{position:absolute;left:0;right:0;bottom:0;height:var(--scrim-h,60px);background:linear-gradient(to top,color-mix(in srgb,var(--glass-tint-bot) calc(var(--scrim-a,100%) * 0.55),transparent),transparent);backdrop-filter:blur(12px) saturate(1.15);-webkit-backdrop-filter:blur(12px) saturate(1.15);-webkit-mask-image:linear-gradient(to top,#000 25%,transparent);mask-image:linear-gradient(to top,#000 25%,transparent);pointer-events:none!important}
body.kxd-welcome .dock-scrim{display:none}
/* THE KEYBOARD SEAM (2026-08-12, her screenshot #2 — NOT the keyboard's own colour). While typing,
   the body pins to the visual viewport: the app stops at the keyboard's top edge and the canvas
   (html) fills the rest. Her lemon-theme test PROVED that canvas is ours — it follows the theme —
   so the seam is a colour delta we can close, not native chrome.
   v1 hid the scrim while the keyboard was up. That killed the seam but also killed the blur behind
   the composer, which she uses and wants (her verdict) — so the scrim stays, and the CANVAS moves
   to meet it instead: --kbd-canvas is --bg lifted by the same 55% wash the scrim's bottom carries,
   so the two sides land on the same colour with the glass intact. In lemon the scrim's tint IS --bg
   (see .dock-scrim above), so no lift is needed there. */
:root{--kbd-canvas:#190f05} /* golden canvas: #0a0703 under the scrim's 55% #261606 wash */
/* ── OUR OWN CARET (round 36) ─────────────────────────────────────────────────────────────────
   Six rounds, six dead theories, then the probe answered it: caret rect t=425, input rect t=425,
   DELTA 0. WebKit's geometry is EXACT — it just paints the blinking bar one status-bar height
   (sat=59px) too high. Nothing reachable from CSS or JS controls that painter, and the fix isn't
   worth her edge-to-edge PWA (round 24's translucent status bar is restored and staying).
   So we hide the system caret and draw our own at the coordinates the probe PROVED correct. This
   is the one approach that cannot miss: the same rect that measured DELTA 0 is the rect we paint
   at, and a plain DOM element goes through the normal render path — the one that positions her
   text correctly — instead of the broken caret painter.
   Touch devices only; desktop's native caret is fine and keeps its drag/blink behaviour. */
body.kxd-owncaret #input{caret-color:transparent} /* the liar goes invisible; ours takes over */
/* SELECTION VISIBILITY IN THE RICH FIELDS (round 44). Her test settled that iOS ignores ::selection
   outright — the blue she sees in plain textareas is iOS's OWN colour, not ours — and that it draws
   contenteditable selections in its INACTIVE grey. On golden that grey all but merges with the
   background, so she can select text and not see it. The colour is unreachable; the CONTRAST is not.
   Rather than lightening the app (her words: "and I hate that"), the field lifts ONLY while a
   selection is actually live, so nothing changes at rest. Dark theme only — she says lemon reads
   fine already. Covers both rich fields, since #input and #scCorePrompt share .comp-rich. */
html:not(.th-lemon) .comp-rich.kxd-selbg{background:rgba(255,255,255,.15)!important;border-radius:12px}
.comp-rich{transition:background .12s ease}
/* SELECTION HIGHLIGHT (round 37, her ask; round 38 makes it actually take). The UA default grey
   vanishes against golden's near-black — she could not see what she had selected.
   WHY v1 FAILED: it set only `background`, on purpose, to leave each bubble's text colour intact.
   But the CSSWG resolution Safari implements is "if there IS a colour in the author origin the UA
   must respect it; if there ISN'T, the UA may apply magic" — background alone counts as no colour,
   so Safari kept washing it back to its own grey. Both properties must be declared for Safari to
   honour either. Solid accent + near-black text: legible in golden AND lemon, since both accents
   (#f2c05b / #c9a44a) are light enough to carry dark text.
   WHY v2 ALSO FAILED — no var() in here. Custom properties inside HIGHLIGHT pseudos are a very
   recent capability (default-on only from Chrome 134; WebKit does not honour them), so Safari threw
   `background:var(--accent)` away and fell straight back to grey. Her memory of "it used to be
   golden" was real and is the tell: .pill-wrap>textarea::selection has always worked, and it uses a
   LITERAL rgba. So these are literals too, with the theme split done by selector instead.
   v3 STILL SHOWED DARK ON HER PHONE. Standing theory: WebKit-on-iOS ignores ::selection inside
   EDITABLE fields and draws the system highlight, which reads dark rather than the familiar blue
   because this app declares color-scheme:dark (the same declaration that keeps her keyboard dark).
   Her Chrome-on-iOS test cannot separate the two — every iOS browser is WebKit underneath.
   ROUND 42 puts that theory to the test instead of asserting it, since the scope she actually meant
   is the COMPOSER (the typing field), not message bubbles. Explicit #input targeting does double
   duty: if a page-wide rule was losing to something, this outranks it; if iOS is ignoring author
   styling outright, this changes nothing and the theory is confirmed. Gold either way — a test does
   not have to be ugly to be decisive (her point, and she is right). */
::selection{background:#f2c05b;color:#14100a}
::-moz-selection{background:#f2c05b;color:#14100a}
html.th-lemon ::selection{background:#c9a44a;color:#14100a}
html.th-lemon ::-moz-selection{background:#c9a44a;color:#14100a}
/* The composer, named outright — highest specificity we can give a highlight pseudo. `#input *`
   covers text sitting inside child nodes (pills, formatting spans), which a bare #input::selection
   would miss. */
#input::selection,#input *::selection{background:#f2c05b;color:#14100a}
#input::-moz-selection,#input *::-moz-selection{background:#f2c05b;color:#14100a}
html.th-lemon #input::selection,html.th-lemon #input *::selection{background:#c9a44a;color:#14100a}
/* THE ACTUAL GREY (round 41 — HER catch: "the classic iOS selection is blue, not grey", and desktop
   was fine all along). On mobile .bubble is user-select:none (see the touch block near the top) so
   long-pressing a message NEVER selects text — the custom hold-menu owns that gesture. What she was
   seeing is therefore not a selection at all: it is WebKit's default tap highlight, a translucent
   BLACK, applied because -webkit-tap-highlight-color is only zeroed on button-shaped elements and
   nowhere else. Hence grey on phone, gold on desktop, and never iOS blue. Cure: paint the tap
   highlight in the app's own gold. It inherits, so html covers everything; the button rules that
   set `transparent!important` still win where a flash is unwanted. Literals, not var() — same
   lesson as ::selection above. */
html{-webkit-tap-highlight-color:rgba(242,192,91,.28)}
html.th-lemon{-webkit-tap-highlight-color:rgba(201,164,74,.32)}
.kxd-caret{position:absolute;width:2px;border-radius:1px;background:var(--text);pointer-events:none;z-index:2;display:none}
body.kxd-owncaret .kxd-caret.on{display:block;animation:kxdCaretBlink 1.06s steps(1,end) infinite}
@keyframes kxdCaretBlink{0%,50%{opacity:1}50.01%,100%{opacity:0}}
html.th-lemon{--kbd-canvas:#eae7e0} /* lemon: tint == --bg already, canvas stays put */
/* Chat images are click-to-expand (lightbox) — show the zoom cursor as the affordance. */
.msg-imgs img{cursor:zoom-in}
.msg-imgs img.img-editing{cursor:default}
/* In-edit remove-image ✕ button overlaid on an attached image (shown while editing the linked text). */
.msg-imgs{position:relative}
.img-rm-x{position:absolute;top:4px;right:4px;width:24px;height:24px;border:none;border-radius:50%;background:rgba(0,0,0,.65);color:#fff;font-size:15px;line-height:24px;text-align:center;cursor:pointer;z-index:3;padding:0;display:flex;align-items:center;justify-content:center}
.img-rm-x:hover{background:rgba(200,40,40,.9)}
.comp-btn{background:transparent;border:none;color:var(--muted);border-radius:50%;transition:color .15s,background .15s}
.comp-btn:hover{color:var(--text);background:rgba(var(--ov),.08)}
.comp-input{background:transparent!important;border:none!important;padding:10px 6px}
.comp-input:focus{border:none}
#micBtn svg,#dictCancelBtn svg,#emojiBtn svg{width:18px;height:18px}
#sendBtn:disabled{opacity:.45;cursor:not-allowed}
#micBtn.recording{background:#e05050;color:#fff;border-color:#e05050;animation:micPulse 1.2s ease-in-out infinite}
@keyframes micPulse{0%,100%{box-shadow:0 0 0 0 rgba(224,80,80,.55)}50%{box-shadow:0 0 0 7px rgba(224,80,80,0)}}
/* Emoji lives in the pill's right cluster, always visible except while dictating. */
.composer.dictating #emojiBtn{display:none!important}
/* Voice-mode circle: Ksena's hand-picked golds per theme (2026-05-13).
   Light (default root) #FFD280 with #F1C960 hover; dark #FFBC30 with the brightness hover. */
#voiceBtn.comp-circle{background:#FFD280;border:none;color:#1a1410}
#voiceBtn.comp-circle:hover{background:#F1C960;color:#1a1410}
body[data-mode="dark"] #voiceBtn.comp-circle{background:#FFBC30}
body[data-mode="dark"] #voiceBtn.comp-circle:hover{background:#FFBC30;filter:brightness(1.06)}
#voiceBtn.comp-circle svg{fill:currentColor;stroke:none}
/* Formatting toolbar retired (Ksena 2026-05-17: "I simply know how to format the text").
   DOM + JS stay for the sister app cherry-pick parity; only the UI is gone. Emoji moved into the pill. */
.fmt-wrap{display:none!important}
/* Read-aloud speaker button states (action bar .mini). Loading = an iOS-style 12-spoke
   "rice grain" spinner (SMIL-rotated SVG injected by _spin12On), not the icon twirling.
   The icon-hide selectors use `>` so they can't hide the spinner's own nested svg. */
.actions-br .mini.playing{color:var(--accent)}
.actions-br .mini.playing svg{animation:ttsThrob 1s ease-in-out infinite}
.actions-br .mini{position:relative}
.actions-br .mini.loading>svg{display:none}
@keyframes ttsThrob{0%,100%{opacity:1}50%{opacity:.45}}
.spin12{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none}
.spin12 svg{width:15px;height:15px;display:block}
#micBtn .spin12 svg{width:17px;height:17px}
/* Dictation: while TRANSCRIBING the mic goes neutral (recording red is only for recording)
   with the same spoke spinner. */
#micBtn{position:relative}
#micBtn.transcribing{background:rgba(var(--ov),.08);color:var(--muted);animation:none}
#micBtn.transcribing>svg{display:none}
/* Stop-generating button. Lives next to Send. Disabled until typingIn[activeChat] has entries. */
.stop-btn{height:34px;min-width:34px;padding:0 10px;display:grid;place-items:center;border-radius:10px;cursor:pointer;background:rgba(180,40,40,.12);border:1px solid rgba(180,40,40,.28);color:#d44040;transition:opacity .15s,background .15s}
.stop-btn:disabled{opacity:.32;cursor:default;background:rgba(var(--ov),.04);border-color:rgba(var(--ov),.08);color:var(--muted)}
.stop-btn:not(:disabled):hover{background:rgba(180,40,40,.2)}
.stop-btn svg{width:14px;height:14px}
.rp-header{padding:18px 16px 14px;font-weight:600;position:sticky;top:0;z-index:1;background:rgba(var(--ov),.02);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);text-align:center;font-family:'Cormorant Upright',Georgia,serif;font-size:17px;letter-spacing:.02em}
.rp-body{flex:1;overflow-y:auto;min-width:260px}
.rp-avatar-area{padding:24px 20px;display:flex;flex-direction:column;align-items:center;gap:10px}
.rp-av{width:72px;height:72px;border-radius:50%;background:rgba(var(--ov),.06);display:grid;place-items:center;font-weight:700;font-size:26px;color:var(--text-88);overflow:hidden;cursor:pointer;border:2px solid rgba(var(--ov),.1);transition:border-color .2s}
.rp-av:hover{border-color:rgba(var(--ov),.2)}
.rp-av img{width:100%;height:100%;object-fit:cover}
.rp-av-name{font-size:18px;font-weight:600;font-family:'Cormorant Upright',Georgia,serif}
.rp-av-btns{display:flex;gap:6px}
.s-sec{border-bottom:1px solid rgba(var(--ov),.05)}
.s-sec-h{padding:10px 16px;cursor:pointer;display:flex;align-items:center;justify-content:space-between;user-select:none;font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);transition:color .15s}
.s-sec-h:hover{color:var(--text)}
.s-sec.collapsed .s-sec-b{display:none}.s-sec.collapsed .arr{transform:rotate(-90deg)}
.s-sec-b{padding:6px 16px 12px}
.s-row{display:flex;align-items:center;justify-content:space-between;padding:4px 0;gap:8px;flex-wrap:wrap}
.s-lbl{flex:1;font-size:12px;min-width:60px}
.s-tog{position:relative;width:36px;height:20px;background:rgba(var(--ov),.06);border-radius:10px;cursor:pointer;border:1px solid rgba(var(--ov),.08);transition:background .2s;flex-shrink:0}
.s-tog.on{background:rgba(122,106,58,.6)}.s-tog .knob{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:var(--text);transition:left .2s}
.s-tog.on .knob{left:18px}
.s-clr{width:28px;height:20px;border:1px solid rgba(var(--ov),.08);border-radius:5px;cursor:pointer;padding:0;overflow:hidden;flex-shrink:0}
.s-clr input{width:100%;height:100%;border:none;cursor:pointer;padding:0}
.s-rng{width:85px;accent-color:var(--accent);flex-shrink:0}
.s-val{width:30px;text-align:right;font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;flex-shrink:0}
.s-sel{background:rgba(var(--ov),.04);color:var(--text);border:1px solid rgba(var(--ov),.08);border-radius:7px;padding:3px 6px;font-size:11px;font-family:inherit;cursor:pointer;max-width:165px}
/* (.s-font-sel rules removed — no more native <select> in Appearance; .fancy-sel handles
   font dropdowns via opts.optStyleFn = function(v){return 'font-family:'+v} so each option
   previews itself, and the closed-trigger mirrors the selected option's style.) */
.s-sel option{padding:4px 6px;font-size:14px}
.s-inp{background:rgba(var(--ov),.04);color:var(--text);border:1px solid rgba(var(--ov),.08);border-radius:7px;padding:4px 8px;font-size:12px;font-family:inherit;width:100px;outline:none}
.s-btn{background:rgba(var(--ov),.04);color:var(--text);border:1px solid rgba(var(--ov),.08);border-radius:7px;padding:5px 10px;cursor:pointer;font-size:11px;font-family:inherit;font-weight:600;transition:all .15s}
.s-btn:hover{background:rgba(var(--ov),.08)}
.s-btn.danger{background:rgba(180,40,40,.15);border-color:rgba(180,40,40,.25);color:#d44040;font-weight:700}
.icon-pick{width:30px;height:30px;border-radius:7px;background:rgba(var(--ov),.04);border:1px solid rgba(var(--ov),.06);color:var(--muted);display:grid;place-items:center;cursor:pointer;padding:0;transition:all .15s}
.icon-pick:hover{color:var(--text);background:rgba(var(--ov),.08)}
.icon-pick.sel{border-color:var(--accent);color:var(--accent);background:rgba(242,192,91,.10)}
.icon-grid{display:flex;flex-wrap:wrap;gap:4px;padding:4px 0}
.icon-grid .icon-pick{position:relative}
.icon-grid .icon-pick svg{width:14px;height:14px}
.icon-grid .icon-pick img{width:14px;height:14px;object-fit:cover;border-radius:50%}
.icon-grid .icon-pick .ico-tip{display:none;position:absolute;bottom:100%;left:50%;transform:translateX(-50%);background:var(--bg2);border:1px solid var(--border);color:var(--text);font-size:9px;padding:2px 6px;border-radius:4px;white-space:nowrap;pointer-events:none;z-index:10;margin-bottom:4px}
.icon-grid .icon-pick:hover .ico-tip{display:block}
.s-sub{font-size:10px;color:var(--muted);padding:6px 0 2px;font-weight:600;text-transform:uppercase;letter-spacing:.04em}
/* Reply preview (redesigned 2026-07-09, her call): the old full-width square strip predated the
   floating composer pill and clashed with it. Now it's a matching frosted chip hovering just above
   the pill — same glass (frost + blur + ctrl border), same width/centering rules, rounded 18. */
.reply-bar{display:none;align-items:center;gap:10px;padding:7px 12px;margin:0 10px 6px;border:1px solid var(--ctrl-b);border-radius:18px;background:color-mix(in srgb,var(--input-bg) var(--frost,40%),transparent);backdrop-filter:blur(22px) saturate(1.5);-webkit-backdrop-filter:blur(22px) saturate(1.5)}
@media(min-width:769px){.reply-bar{width:calc(100% - 20px);max-width:770px;margin-left:auto;margin-right:auto}}
.reply-bar.active{display:flex}
.reply-bar-color{width:3px;height:30px;border-radius:2px;flex-shrink:0}
.reply-bar-content{flex:1;overflow:hidden}
.reply-bar-name{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.reply-bar-text{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.reply-bar-close{width:24px;height:24px;border-radius:50%;background:none;border:1px solid var(--ctrl-b);color:var(--muted);font-size:16px;cursor:pointer;display:grid;place-items:center;flex-shrink:0;padding:0;line-height:1}
.reply-bar-close:hover{color:var(--text);background:var(--ctrl)}
.reply-quote{margin-bottom:8px;padding:6px 10px;border-left:3px solid #888;border-radius:0 8px 8px 0;background:rgba(var(--ov),.06);cursor:pointer;overflow:hidden;max-width:100%;box-sizing:border-box;word-break:break-word}
.reply-quote-name{font-size:11px;font-weight:600;margin-bottom:2px}
.reply-quote-text{font-size:13px;opacity:.7;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.crop-ov{position:fixed;inset:0;background:rgba(0,0,0,.6);display:none;align-items:center;justify-content:center;z-index:9960;padding:16px}
.crop-ov.show{display:flex}
.crop-card{background:var(--bg2);border:1px solid var(--border);border-radius:16px;padding:16px;width:320px;max-width:96vw}
.crop-wrap{position:relative;width:260px;height:260px;margin:8px auto;border-radius:12px;border:1px solid var(--border);background:#050505;overflow:hidden;touch-action:none}
#cropCanvas{width:100%;height:100%;display:block}
.crop-guide{pointer-events:none;position:absolute;inset:0;border-radius:50%;box-shadow:0 0 0 100vmax rgba(0,0,0,.35);border:2px dashed rgba(var(--ov),.35)}
.crop-ctrls{display:flex;align-items:center;gap:6px;margin-top:10px;flex-wrap:wrap;justify-content:center}
/* Toast redesigned 2026-07-08 (her spec): subtle and delicate — a small single-line pill floating
   just above the composer bar instead of the old chunky banner at the top. Slides up on show. */
.toast{position:fixed;bottom:calc(var(--dock-h,84px) + 12px);left:50%;transform:translateX(-50%) translateY(8px);background:color-mix(in srgb,var(--bg2) 88%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:var(--text-88);border:1px solid var(--border);padding:5px 14px;border-radius:999px;font-size:11px;letter-spacing:.02em;opacity:0;transition:opacity .25s,transform .25s;z-index:9999;pointer-events:none;box-shadow:0 4px 14px rgba(0,0,0,.35);white-space:nowrap;max-width:min(86vw,520px);overflow:hidden;text-overflow:ellipsis}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
.confirm-ov{position:fixed;inset:0;background:rgba(0,0,0,.55);display:none;align-items:center;justify-content:center;z-index:9998}
.confirm-ov.show{display:flex}
.confirm-box{background:var(--bg2);border:1px solid var(--border);border-radius:16px;padding:20px 24px;width:300px;max-width:90vw;text-align:center}
.confirm-box p{margin:0 0 16px;font-size:16px}
.cf-btns{display:flex;gap:10px;justify-content:center}
.cf-btns button{padding:8px 18px;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;border:1px solid var(--ctrl-b);font-family:inherit}
.cf-cancel{background:var(--ctrl);color:var(--text)}.cf-ok{background:#6b2a2a;color:#f3eee8;border-color:#8a3a3a}
.messages::-webkit-scrollbar,.rp-body::-webkit-scrollbar,.lp-list::-webkit-scrollbar,.comp-input::-webkit-scrollbar{width:6px}
.messages::-webkit-scrollbar-thumb,.rp-body::-webkit-scrollbar-thumb,.lp-list::-webkit-scrollbar-thumb,.comp-input::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:6px}
@media(max-width:768px){
  /* Session panel: a little wider than the old flat 280px, but capped so a gap stays on the
     right (her 2026-07-04 ask — don't cover the whole screen). */
  .panel-left{position:fixed;left:0;top:0;bottom:0;z-index:301;width:88vw;max-width:330px;min-width:0;transform:translateX(-100%);transition:transform .3s;box-shadow:4px 0 20px rgba(0,0,0,.5);will-change:transform}
  .panel-left.show{transform:translateX(0)}
  .lp-header span,.lp-nm,.lp-info{display:block!important}
  .lp-item{padding:10px 12px;justify-content:flex-start}
  /* Mirrors the left panel (88vw / max 330px, chat visible behind + overlay) instead of the old
     full-screen 100vw takeover — her 2026-07-07 ask: "slide out the same way the left panel does". */
  .panel-right{position:fixed;right:0;top:0;bottom:0;z-index:300;width:88vw;max-width:330px;min-width:0;transform:translateX(100%);transition:transform .3s;box-shadow:-4px 0 20px rgba(0,0,0,.5);background:var(--bg2)!important;will-change:transform}
  .panel-right.show{transform:translateX(0)}
  .panel-center{min-width:0;width:100%;height:100vh;height:100dvh;display:flex;flex-direction:column}
  .messages{flex:1;overflow-y:auto;padding:14px 10px;min-height:0;padding-bottom:0}
  .messages::after{height:var(--dock-h,78px)} /* same spacer-not-padding fix, mobile fallback height */
  /* Mobile: the scrim reads too short vs desktop, so it's taller here (her 2026-07-06 tuning: 100px). */
  /* 100→132px (2026-08-06): her verdict — 132 is perfect. (A 180 experiment lived for one
     deploy; she never asked for it. Don't "improve" this number again without her saying so.) */
  .dock-scrim{height:132px}
  .mob-burger{display:grid!important}
  /* Floating buttons appear only when the body is in welcome-state. body.kxd-welcome is toggled by showEmptyState/showChatState. */
  body.kxd-welcome .mob-floating{display:flex;position:fixed;top:calc(10px + env(safe-area-inset-top,0px));left:0;right:0;padding:0 12px;justify-content:space-between;z-index:50;pointer-events:none} /* fixed ignores body/panel padding — it adds the status-bar inset itself (her round 14: the burger drowned under the clock) */
  body.kxd-welcome .mob-floating .mob-floating-btn{pointer-events:auto}
  body.kxd-welcome:has(.memory-page) .mob-floating{display:none} /* Dream Journal only (her call): the burger sat ON the ← Home button — Home wins there */
  .rp-header{display:flex;align-items:center;justify-content:center;position:relative}
  .rp-close{display:grid}
  .cp-statusbar{padding:6px 12px 2px}
  .cp-chatbar{padding:8px 10px 10px}
  /* Bigger gap between the menu button and Dieter's avatar/title (her 2026-07-04 note). */
  .cp-hdr-left{margin-left:12px;gap:9px}
  .cp-hdr-right .hdr-btn{width:28px;height:28px}
  .fmt-bar{overflow-x:auto;flex-wrap:nowrap;-webkit-overflow-scrolling:touch;padding:6px 6px 0;gap:1px}
  .fmt-wrap{flex-shrink:0}
  .fmt-b{width:32px;height:30px;flex-shrink:0}
  .composer{padding:8px 10px;flex-shrink:0}
  /* 16px = the comfortable phone-input standard; padding-y 10px centers the 19.2px line box
     in the 40px field (same centering math as desktop). */
  .comp-input{font-size:16px;padding:10px 12px;min-height:40px}
  .send-btn{font-size:14px;padding:0 10px}
  .comp-btn{height:34px;min-width:34px}
  .lp-themes{padding:8px 10px 14px}
  .cp-av-stack .cp-av{width:30px;height:30px;font-size:12px}
  .cp-name{font-size:13px}
  .cp-presence{font-size:11px}
  .reply-bar{flex-shrink:0}
  .typing-ind{flex-shrink:0;padding:0 14px}
  .cp-header{flex-shrink:0}
  /* AI image bubbles: the inline style sets max-width:320px, which overflows on a ~360px-wide phone
     where the bubble itself is capped at 72%. Constrain to 100% of bubble width on mobile. */
  .bubble .msg-imgs.natural img{max-width:100%!important;height:auto!important}
  /* Mobile keyboards have built-in emoji input — the picker button is redundant. */
  .fmt-emoji-only{display:none!important}
  /* Mobile UI font bumps (~+10%) — bubble + name fonts are scaled in applyRowStyle (JS) since they're inline-styled.
     The rules below catch CSS-level UI text that's still too small on a phone. !important is needed because
     several desktop defaults (.sc-label, .sc-input, .lp-preview …) are declared later in the source than this
     media block — without !important they'd overwrite our mobile-targeted bumps. */
  /* (Composer font/padding consolidated into the earlier mobile block — single source.) */
  .lp-nm{font-size:14px!important}
  .lp-preview{font-size:12px!important}
  /* Session list text was too small on a phone (her 2026-07-04 note). */
  .sess-entry .sess-name{font-size:14px!important}
  .sess-entry .sess-preview{font-size:12.5px!important}
  .cp-name{font-size:14px!important}
  .cp-presence{font-size:12px!important}
  .bubble .meta{font-size:12px!important}
  .sn-item{font-size:12px!important}
  .sc-input{font-size:14px!important}
  .sc-label{font-size:12px!important}
  /* Composer pill (her 2026-07-07 tuning):
     - side margins 5px → 9px (the bar was touching the screen edges too much);
     - gap 8px → 4px + input side padding 6px → 2px (the empty seams between the +/mic buttons
       and the text were too wide);
     - bottom lift stays generous while the chat is just open (readability/clickability above the
       home-indicator), but COLLAPSES when the soft keyboard is up — see body.kxd-kbd below. */
  .composer{margin:0 13px calc(7px + env(safe-area-inset-bottom,0px));padding:4px 9px;gap:4px} /* 9px read as "same as before" to her — 13px makes the inset visible */
  .reply-bar{margin:0 13px 6px} /* frosted reply chip tracks the composer's mobile side margins */
  .comp-input{padding:10px 2px}
  /* Soft keyboard open (body.kxd-kbd, set from a visualViewport listener): the keyboard covers the
     home-indicator zone, so the safe-area lift is dead space — drop the body's safe-area padding and
     pull the composer down to a slim 6px so it sits snug on the keyboard (her yellow-wave note). */
  body.kxd-kbd{padding-bottom:0}
  body.kxd-kbd .composer{margin-bottom:6px}
  /* Round 30b (the pill's second verdict, cb 846 vs body h476): the body pins to the visual
     viewport while typing, but .panel-center was still 100dvh (852 — iOS dvh ignores the
     keyboard) and carried the composer dock right past the body's bottom. While the keyboard
     is up, the column obeys its shrunken parent. */
  body.kxd-kbd .panel-center{height:100%}
  /* The theme orbs (.lp-themes) are the BOTTOMMOST left-panel element, so THEY carry the
     safe-area lift above the iPhone home-indicator zone — padding the footer only lifted
     General and left the orbs stranded low (Ksena). Footer returns to its normal spacing. */
  .lp-themes{padding-bottom:calc(14px + env(safe-area-inset-bottom,0px))}
  /* Keep the circles actually CIRCULAR on phones (the 34px comp-btn height was squishing the
     38px-wide voice button into an oval). */
  .comp-circle{width:34px!important;height:34px!important;min-width:34px}
}
/* Emoji picker button is desktop-only — phones have a native emoji keyboard (same reasoning
   as the retired toolbar's fmt-emoji-only rule). */
@media (pointer:coarse){#emojiBtn{display:none!important}}
/* Backend login overlay (Phase 6) — shown only when /api/me reports unauthenticated. */
/* Dark-golden login screen (matches Ksena's default golden theme so the moment she opens
   the app she sees something cohesive). The overlay uses a deep brown gradient; the card
   sits on top of it in dark brown with a warm gold accent. */
#kxdLoginOverlay{display:none;position:fixed;inset:0;background:linear-gradient(180deg,#191610 0%,#0a0703 100%);align-items:center;justify-content:center;z-index:99999;font-family:'Quicksand','DM Sans',sans-serif}
#kxdLoginOverlay.show{display:flex!important;visibility:visible!important}
#kxdLoginOverlay *{visibility:visible!important;pointer-events:auto!important}
body.kxd-needs-login>*{visibility:hidden!important;pointer-events:none!important}
body.kxd-needs-login>#kxdLoginOverlay{visibility:visible!important;pointer-events:auto!important}
body.kxd-needs-login>script,body.kxd-needs-login>style{visibility:hidden} /* harmless — neither has rendered output */
.kxd-login-card{background:#191610;padding:36px 40px;border-radius:16px;max-width:380px;width:90%;text-align:center;box-shadow:0 12px 40px rgba(0,0,0,.55),0 0 24px rgba(242,192,91,.08);border:1px solid #3a2f1a}
.kxd-login-card h2{font-size:34px;color:#f2c05b;margin:0 0 4px;font-family:'Great Vibes','GreatVibesFallback',Georgia,serif;font-weight:400;letter-spacing:.5px}
.kxd-login-sub{color:#ccbea2;font-size:13px;margin:0 0 22px}
.kxd-login-card input{width:100%;padding:12px 14px;background:#0d0a05;border:1px solid #3a2f1a;color:#e5e2db;border-radius:10px;font-size:14px;outline:none;box-sizing:border-box;font-family:inherit;transition:border-color .15s,box-shadow .15s;margin-top:10px}
.kxd-login-card input:first-of-type{margin-top:0}
.kxd-login-card input:focus{border-color:#f2c05b;box-shadow:0 0 0 3px rgba(242,192,91,.18)}
.kxd-login-card input::placeholder{color:#80703a}
/* The :not(.pw-toggle) is the bug fix Ksena spotted in the screenshot — without it, the eye toggle
   inherits Enter's full-width gold styling and becomes a giant bar covering the password input.
   Now Enter alone gets the big-button styling; the eye button keeps its compact sit-on-the-right look
   from the global .pw-toggle rules. */
.kxd-login-card button:not(.pw-toggle){margin-top:14px;width:100%;padding:12px;background:#f2c05b;color:#1a1410;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s,box-shadow .15s;letter-spacing:.3px}
.kxd-login-card button:not(.pw-toggle):hover{background:#d4a045;box-shadow:0 4px 14px rgba(242,192,91,.32)}
.kxd-login-card button:not(.pw-toggle):disabled{opacity:.55;cursor:wait}
#kxdLoginErr{color:#e07070;font-size:12px;margin-top:10px;min-height:14px}
/*
 * KxD messenger layer — COPIED VERBATIM from ../ksenaxdieter/index.html
 *
 * Second \3c style> block. Same rule as kxd-base.css: do not retype or tidy.
 */
/* Hide What's New system */
/* Chat contact styles */
.lp-sep{height:1px;background:linear-gradient(90deg,transparent,rgba(var(--ov),.16) 50%,transparent);margin:6px 16px} /* fade-both-ends hairline (her round 13: "it looks classy") */
/* Sticky-pin Ksena's self indicator at the top of the chats list so she stays visible while
   the rest of the list scrolls underneath. position:sticky with top:0 anchors it to the
   scroll container's edge.
   IMPORTANT: opacity must NOT be on the element itself — that makes the entire box (including
   its background) translucent and chat items behind would bleed through. Apply the dim only
   to text/icons via .lp-self:not(.active) > * {opacity:.7}, keep the box bg fully opaque. */
.lp-self{position:sticky;top:0;z-index:5;background:var(--bg2);padding:10px 12px;border-radius:12px;display:flex;align-items:center;gap:10px;margin:0 10px;border:1px solid transparent;transition:background .15s}
.lp-self:not(.active) > *{opacity:.7;transition:opacity .15s}
.lp-self.active{background:color-mix(in srgb,var(--accent) 8%,var(--bg2));border-color:color-mix(in srgb,var(--accent) 10%,var(--border))}
.lp-self .lp-av{width:38px;height:38px}
.lp-preview{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px}
.lp-time{font-size:10px;color:var(--muted);margin-left:auto;flex-shrink:0}
.lp-grp-hdr{padding:8px 16px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);display:flex;align-items:center;justify-content:space-between}
.lp-grp-hdr button{background:none;border:none;color:var(--accent);font-size:16px;cursor:pointer;padding:0 4px;font-weight:700}
/* Group creation overlay */
.grp-ov{position:fixed;inset:0;background:rgba(0,0,0,.6);display:none;align-items:center;justify-content:center;z-index:9990}
.grp-ov.show{display:flex}
.grp-card{background:var(--bg2);border:1px solid var(--border);border-radius:16px;width:340px;max-width:95vw;padding:24px}
.grp-card h3{font-family:'Cormorant Upright',Georgia,serif;color:var(--accent);font-size:18px;margin-bottom:16px;text-align:center}
.grp-card label{display:flex;align-items:center;gap:10px;padding:6px 0;cursor:pointer;font-size:13px}
.grp-card input[type=checkbox]{accent-color:var(--accent);width:16px;height:16px}
.grp-card input[type=text]{width:100%;background:var(--input-bg);border:1px solid var(--ctrl-b);color:var(--text);border-radius:10px;padding:9px 13px;font-family:inherit;font-size:13px;outline:none;margin-bottom:12px}
.grp-btns{display:flex;gap:8px;margin-top:16px}
.grp-btns button{flex:1;padding:10px;border-radius:10px;font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;border:1px solid var(--ctrl-b)}
.grp-btn-cancel{background:var(--ctrl);color:var(--text)}
.grp-btn-create{background:var(--accent);color:#111;border-color:var(--accent)}
/* Autocomplete */
.ac-popup{position:absolute;bottom:100%;left:14px;right:80px;background:var(--bg2);border:1px solid var(--border);border-radius:10px;overflow:hidden;display:none;box-shadow:0 -4px 16px rgba(0,0,0,.4);z-index:10;margin-bottom:4px}
.ac-popup.show{display:block}
.ac-item{padding:8px 14px;cursor:pointer;display:flex;align-items:center;gap:8px;font-size:13px;transition:background .1s}
.ac-item:hover,.ac-item.selected{background:rgba(var(--ov),.06)}
.ac-item .ac-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.ac-item .ac-cmd{font-family:'Menlo','Consolas',monospace;font-size:13px;font-weight:600;color:#7396b8} /* matches the slash pill's blue (her 2026-07-08 spec — list and pill agree) */
/* AI settings sections in right panel */
.ai-s-section{padding:8px 16px 12px;border-bottom:1px solid rgba(var(--ov),.05)}
.ai-s-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin-bottom:8px}
.ai-s-field{margin-bottom:10px}
.ai-s-label{font-size:11px;color:var(--muted);margin-bottom:4px;display:block}
.ai-s-input{width:100%;background:rgba(var(--ov),.04);color:var(--text);border:1px solid rgba(var(--ov),.08);border-radius:7px;padding:6px 10px;font-size:12px;font-family:inherit;outline:none}
.ai-s-input:focus{border-color:var(--accent)}
textarea.ai-s-input{min-height:70px;resize:vertical;line-height:1.4}
/* Emoji picker — horizontal category tab strip + search + scrollable body with category sections.
   Layout (top to bottom): cat-tabs | search | body. Body scrolls; tabs and search are pinned. */
.emoji-pop{position:fixed;background:var(--bg2);border:1px solid var(--border);border-radius:12px;z-index:9980;box-shadow:0 8px 24px rgba(0,0,0,.5);width:360px;max-width:calc(100vw - 24px);height:400px;display:none;flex-direction:column;overflow:hidden}
.emoji-pop.show{display:flex}
.emoji-cats{display:flex;border-bottom:1px solid var(--border);padding:0 4px;flex-shrink:0;background:var(--bg2)}
.emoji-cat{flex:1;height:38px;background:none;border:none;color:var(--muted);cursor:pointer;display:grid;place-items:center;padding:0;border-radius:0;border-bottom:2px solid transparent;transition:color .15s,border-color .15s;font-family:inherit}
.emoji-cat svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.emoji-cat:hover{color:var(--text)}
.emoji-cat.active{color:var(--accent);border-bottom-color:var(--accent)}
.emoji-search{padding:8px;border-bottom:1px solid var(--border);flex-shrink:0;position:relative}
.emoji-search input{width:100%;background:var(--input-bg);border:1px solid var(--ctrl-b);color:var(--text);border-radius:8px;padding:6px 10px 6px 30px;font-family:inherit;font-size:13px;outline:none;box-sizing:border-box}
.emoji-search input:focus{border-color:var(--accent)}
.emoji-search::before{content:'';position:absolute;left:18px;top:50%;width:14px;height:14px;margin-top:-7px;background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23808080' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='8'/><line x1='21' y1='21' x2='16.65' y2='16.65'/></svg>") center/contain no-repeat;pointer-events:none}
.emoji-body{flex:1;overflow-y:auto;padding:6px}
.emoji-body::-webkit-scrollbar{width:5px}
.emoji-body::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:5px}
.emoji-section{margin-bottom:6px}
.emoji-section-h{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);padding:6px 4px 4px;position:sticky;top:0;background:var(--bg2);z-index:1}
.emoji-grid{display:flex;flex-wrap:wrap;gap:2px}
.emoji-grid button{width:32px;height:32px;border:none;background:none;font-size:20px;cursor:pointer;border-radius:6px;display:grid;place-items:center;transition:background .1s;padding:0;line-height:1;font-family:"Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif}
.emoji-grid button:hover{background:rgba(var(--ov),.08)}
.emoji-empty{padding:24px;text-align:center;color:var(--muted);font-size:12px}
/* Token counter */
.token-bar{position:fixed;bottom:0;left:0;right:0;background:rgba(10,10,8,.92);border-top:1px solid var(--border);padding:4px 16px;display:none;align-items:center;justify-content:space-between;font-family:'Quicksand','DM Sans',sans-serif;font-size:11px;color:var(--muted);z-index:9970;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
.token-bar.show{display:flex}
.token-bar .tk-group{display:flex;gap:14px}
.token-bar .tk-label{color:var(--muted);opacity:.6}
.token-bar .tk-val{color:#f3eee8;font-weight:600;font-family:'JetBrains Mono',monospace;font-size:11px}
.token-bar .tk-model{color:var(--accent);font-weight:500}
@media(min-width:769px){.panel-right{z-index:2}} /* desktop only — on mobile we need z-index:300 (set in the mobile media query) so the slide-in panel sits above mob-ov */
/* Toggle colors — bound to the golden accent. */
.s-tog.on{background:rgba(242,192,91,.45)!important}
/* Force the accent on native form controls. */
input[type="range"],input[type="checkbox"],input[type="radio"]{accent-color:var(--accent)}
input[type="range"]{cursor:pointer}
.lp-item .sess-del{display:none;width:20px;height:20px;border-radius:50%;background:rgba(180,40,40,.2);border:none;color:#d44040;font-size:13px;cursor:pointer;place-items:center;flex-shrink:0;padding:0;line-height:1;transition:background .15s}
.lp-item:hover .sess-del{display:grid}
.lp-item .sess-del:hover{background:rgba(180,40,40,.4)}
.sess-entry{display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:10px;cursor:pointer;transition:background .12s;position:relative}
/* ── PROJECT FOLDERS (2026-07-16) ─────────────────────────────────────────────────────────── */
.proj-entry{display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:10px;cursor:pointer;transition:background .12s;position:relative}
.proj-entry:hover,.proj-entry.menu-open{background:rgba(var(--ov),.06)}
.proj-entry .proj-ico{width:16px;height:16px;flex:none}
.proj-entry .proj-name{flex:1;min-width:0;font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.proj-entry .proj-count{font-size:10px;color:var(--muted);flex:none}
.proj-entry.open .proj-name{color:var(--accent)}
/* the folder ⋯ wears the chat-row muted treatment — but stays IN THE LAYOUT always
   (visibility, not display: the appearing 22px button was growing the row on hover — her note) */
.proj-entry .sess-menu-btn{display:flex;visibility:hidden;width:22px;height:22px;border-radius:6px;background:transparent;border:none;color:var(--muted);font-size:14px;cursor:pointer;align-items:center;justify-content:center;flex-shrink:0;padding:0;line-height:1;letter-spacing:1px}
.proj-entry:hover .sess-menu-btn,.proj-entry.menu-open .sess-menu-btn{visibility:visible}
.proj-entry .sess-menu-btn:hover{background:rgba(var(--ov),.08);color:var(--text)}
/* header "new" buttons: paper-and-pen glyph instead of the harsh + (her call) */
.lp-grp-hdr button svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;display:block}
.proj-chats{margin-left:14px;padding-left:6px;border-left:2px solid rgba(var(--ov),.08)}
.proj-chats .sess-entry.in-proj{padding:5px 8px}
.proj-empty{font-size:11px;color:var(--muted);padding:4px 12px;font-style:italic}
.lp-showmore{background:none;border:none;color:var(--muted);font-size:11px;cursor:pointer;padding:4px 12px;text-align:left;display:block}
.lp-showmore:hover{color:var(--accent)}
/* ── SEARCH BAR (2026-07-19) ──────────────────────────────────────────────────────────────── */
.lp-search{display:flex;align-items:center;gap:7px;margin:2px 8px 6px;padding:6px 10px;background:var(--input-bg);border:1px solid var(--ctrl-b);border-radius:10px}
.lp-search svg{width:13px;height:13px;flex:none;fill:none;stroke:var(--muted);stroke-width:2;stroke-linecap:round}
.lp-search input{flex:1;min-width:0;background:none;border:none;outline:none;color:var(--text);font-size:12px;font-family:inherit}
.lp-search input::placeholder{color:var(--muted);opacity:.7}
.lp-search button{background:none;border:none;padding:2px;cursor:pointer;display:flex;align-items:center}
.lp-search button svg{stroke:var(--muted)}
.lp-search button:hover svg{stroke:var(--text)}
.lp-sr{padding:7px 12px;border-radius:10px;cursor:pointer;transition:background .12s}
.lp-sr:hover{background:rgba(var(--ov),.06)}
.lp-sr-name{font-size:12px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lp-sr-snip{font-size:11px;color:var(--muted);margin-top:1px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.lp-sr-meta{font-size:10px;color:var(--accent);opacity:.8;margin-top:2px}
.lp-sr-note{font-size:11px;color:var(--muted);font-style:italic;padding:8px 12px}
/* ── USAGE TAB (2026-07-19) ───────────────────────────────────────────────────────────────── */
.us-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.us-card{background:rgba(var(--ov),.04);border:1px solid rgba(var(--ov),.1);border-radius:12px;padding:12px 10px;text-align:center}
.us-big{font-size:16px;font-weight:700;color:var(--accent);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.us-lbl{font-size:10px;color:var(--muted);margin-top:3px;text-transform:uppercase;letter-spacing:.05em}
.us-sub{font-weight:400;opacity:.5;text-transform:none;letter-spacing:0;font-size:11px}
.us-table{display:flex;flex-direction:column;gap:2px}
.us-row{display:grid;grid-template-columns:1fr 48px 56px 56px 70px;gap:6px;font-size:12px;color:var(--text);padding:5px 8px;border-radius:8px;align-items:center}
.us-row span:first-child{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.us-row span:not(:first-child){text-align:right;font-variant-numeric:tabular-nums}
.us-row:nth-child(even){background:rgba(var(--ov),.03)}
.us-row.us-head{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);background:none}
.us-empty{font-size:11px;color:var(--muted);font-style:italic;padding:8px}
.sess-menu-dd button .proj-ico{width:14px;height:14px;vertical-align:-2px;margin-right:6px}
/* Project Home (middle-panel page) */
.proj-home{max-width:560px;margin:0 auto;padding:34px 18px 24px;display:flex;flex-direction:column;gap:12px}
.proj-home .ph-head{display:flex;align-items:center;gap:12px}
.proj-home .ph-head .proj-ico{width:26px;height:26px;flex:none}
.proj-home .ph-name{flex:1;min-width:0;font-size:20px;font-weight:700;color:var(--text);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ph-actions{display:flex;gap:8px;flex:none}
.ph-btn{display:flex;align-items:center;gap:6px;background:rgba(var(--ov),.05);border:1px solid rgba(var(--ov),.14);border-radius:10px;color:var(--muted);font-size:12px;padding:6px 10px;cursor:pointer;font-family:inherit}
/* buttons added this week never joined the global font-inherit list — her Arial catch */
.pj-mini,.lp-showmore,.pj-ic{font-family:inherit}
.ph-btn:hover{color:var(--text);background:rgba(var(--ov),.09)}
.ph-btn svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.ph-desc{font-size:12px;color:var(--muted);line-height:1.5;background:rgba(var(--ov),.04);border:1px solid rgba(var(--ov),.08);border-radius:10px;padding:10px 12px;white-space:pre-wrap}
.ph-count{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
.ph-list{display:flex;flex-direction:column;gap:4px}
.ph-chat{display:flex;align-items:center;gap:8px;padding:9px 12px;border-radius:12px;cursor:pointer;border:1px solid transparent;transition:background .12s}
.ph-chat:hover,.ph-chat.menu-open{background:rgba(var(--ov),.06);border-color:rgba(var(--ov),.08)}
.ph-chat-info{flex:1;min-width:0}
.ph-chat-name{font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ph-chat-prev{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px}
.ph-chat .sess-menu-btn.ph-menu{display:none;width:22px;height:22px;border-radius:6px;background:transparent;border:none;color:var(--muted);font-size:14px;cursor:pointer;align-items:center;justify-content:center;flex-shrink:0;padding:0;line-height:1;letter-spacing:1px}
.ph-chat:hover .sess-menu-btn.ph-menu,.ph-chat.menu-open .sess-menu-btn.ph-menu{display:flex}
.ph-empty{font-size:12px;color:var(--muted);font-style:italic;padding:16px 4px}
/* Edit Project modal */
/* Scroll belt (2026-08-06, her out-of-reach Save report): the icon grid grew, and on short
   viewports the card ran past the screen with Save stranded below. The card itself scrolls
   (thin themed scrollbar, never the page) so Save is always reachable. */
.pj-card{max-width:400px;max-height:calc(100dvh - 32px);overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--scroll) transparent}
.pj-card::-webkit-scrollbar{width:6px}
.pj-card::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:6px}
.pj-card::-webkit-scrollbar-track{background:transparent}
.pj-lbl{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:10px 0 6px}
.pj-icons{display:grid;grid-template-columns:repeat(8,1fr);gap:6px}
.pj-ic{background:rgba(var(--ov),.04);border:1px solid rgba(var(--ov),.1);border-radius:8px;padding:6px;cursor:pointer;display:flex;align-items:center;justify-content:center}
/* Sizing only (2026-08-06): colour now rides IN the rendered svg markup (_projIconRender) —
   a stylesheet fill/stroke here would override her fill-based k- icons into invisibility. */
.pj-ic svg{width:16px;height:16px}
.pj-ic:hover{background:rgba(var(--ov),.09)}
.pj-ic.sel{border-color:var(--accent);background:rgba(var(--ov),.1)}
.pj-colorrow{display:flex;gap:8px;align-items:center;margin-top:8px}
.pj-colorrow input[type="color"]{width:40px;height:30px;border:none;border-radius:6px;cursor:pointer;background:transparent;padding:0}
.pj-hex{flex:1;background:var(--input-bg);border:1px solid var(--ctrl-b);color:var(--text);border-radius:8px;padding:6px 10px;font-size:12px;outline:none;font-family:inherit}
.pj-mini{background:rgba(var(--ov),.05);border:1px solid rgba(var(--ov),.14);border-radius:8px;color:var(--muted);font-size:11px;padding:6px 10px;cursor:pointer}
.pj-mini:hover{color:var(--text)}
.pj-card textarea{width:100%;min-height:74px;resize:vertical;background:var(--input-bg);border:1px solid var(--ctrl-b);color:var(--text);border-radius:10px;padding:8px 12px;font-family:inherit;font-size:13px;outline:none;line-height:1.4;margin:0 0 4px}
.pj-tgl{display:flex;align-items:flex-start;gap:8px;font-size:12px;color:var(--text);margin:8px 0;cursor:pointer}
.pj-tgl-t{display:flex;flex-direction:column;gap:1px;line-height:1.3}
.pj-tgl-t em{font-style:normal;font-size:10px;color:var(--muted)}
.sess-entry:hover{background:rgba(var(--ov),.04)}
.sess-entry.active{background:rgba(var(--ov),.07)}
/* Unread pulse rides the THEME accent (2026-07-13, her order: NO purple in this app — the old
   rgba fallback 130,80,200 was linn4jace's violet leaking through the never-defined --accent-rgb).
   color-mix keeps it golden on dark and bronze on lemon automatically. */
@keyframes unreadPulse{0%,100%{background:transparent}50%{background:color-mix(in srgb,var(--accent) 12%,transparent)}}
.lp-item.has-unread,.sess-entry.has-unread{animation:unreadPulse 2.5s ease-in-out infinite}
.unread-marker{display:flex;align-items:center;gap:8px;padding:4px 0;margin:4px 0}
.unread-line{flex:1;height:0.5px;background:rgba(224,64,80,.5)}
.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;text-align:center;color:var(--muted);font-family:"Cormorant Upright","Merriweather",Georgia,serif;gap:12px;padding:40px;position:absolute;inset:0;overflow:hidden;contain:layout paint}
.empty-state::before{content:'';position:absolute;inset:-40%;width:180%;height:180%;background:radial-gradient(ellipse at 24% 21%,rgba(255,210,120,0.80) 0%,transparent 33%),radial-gradient(ellipse at 33% 94%,rgba(120,70,15,0.80) 0%,transparent 23%),radial-gradient(ellipse at 78% 26%,rgba(220,150,80,0.55) 0%,transparent 34%),radial-gradient(ellipse at 61% 70%,rgba(220,140,40,0.80) 0%,transparent 20%),radial-gradient(ellipse at 53% 45%,rgba(255,245,210,0.20) 0%,transparent 50%),radial-gradient(ellipse at 50% 50%,rgba(255,235,170,0.12) 0%,transparent 50%),radial-gradient(ellipse at 96% 85%,rgba(190,170,110,0.18) 0%,transparent 50%);animation:auroraFlow 9s ease-in-out infinite alternate;animation-play-state:var(--aurora-play,running);z-index:0;filter:blur(var(--aurora-blur,37px));will-change:transform;transform:translateZ(0);contain:strict;transition:opacity .6s ease}
/* VIRIDIAN aurora twin (2026-07-07): identical geometry/flow to the golden ::before, recolored to
   Dieter's greens. Sits at opacity 0 and crossfades in while his figure is hovered (.dieter-hover,
   toggled by showEmptyState's mouseenter/leave on his fig) — hover away and the gold returns. */
.empty-state::after{content:'';position:absolute;inset:-40%;width:180%;height:180%;background:radial-gradient(ellipse at 24% 21%,rgba(110,235,190,0.75) 0%,transparent 33%),radial-gradient(ellipse at 33% 94%,rgba(8,68,52,0.85) 0%,transparent 23%),radial-gradient(ellipse at 78% 26%,rgba(60,190,150,0.55) 0%,transparent 34%),radial-gradient(ellipse at 61% 70%,rgba(25,150,110,0.80) 0%,transparent 20%),radial-gradient(ellipse at 53% 45%,rgba(215,255,240,0.20) 0%,transparent 50%),radial-gradient(ellipse at 50% 50%,rgba(170,255,225,0.12) 0%,transparent 50%),radial-gradient(ellipse at 96% 85%,rgba(90,170,145,0.20) 0%,transparent 50%);animation:auroraFlow 9s ease-in-out infinite alternate;animation-play-state:var(--aurora-play,running);z-index:0;filter:blur(var(--aurora-blur,37px));will-change:transform;transform:translateZ(0);contain:strict;opacity:0;transition:opacity .6s ease;pointer-events:none}
.empty-state.dieter-hover::before{opacity:0}
.empty-state.dieter-hover::after{opacity:1}
@keyframes auroraFlow{0%{transform:translate(-8%,-8%) rotate(0deg) scale(1)}33%{transform:translate(8%,3%) rotate(10deg) scale(1.05)}66%{transform:translate(-3%,8%) rotate(-5deg) scale(.97)}100%{transform:translate(8%,-3%) rotate(10deg) scale(1.03)}}
.empty-state>*{position:relative;z-index:1}
/* Title stays GOLD normally; it sheds the gold ONLY while Dieter's figure is hovered (her correction
   2026-07-07 — the recolor is part of his takeover, not the resting look): grayish black on light,
   grayish white on dark, fading with the aurora crossfade. */
.empty-state h2{font-size:36px;color:var(--accent);font-weight:400;margin:0;text-shadow:0 1px 8px rgba(0,0,0,.1);font-family:'Great Vibes','GreatVibesFallback',Georgia,serif;transition:color .6s ease}
.empty-state.dieter-hover h2{color:#45413a}
body[data-mode="dark"] .empty-state.dieter-hover h2{color:#e9e6df}
.empty-state p{font-size:14px;margin:0;font-family:"Quicksand","DM Sans",sans-serif;color:var(--text);opacity:.7}
@keyframes emptyFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
/* Mouse-parallax target. JS sets --px-x/--px-y for translate drift AND --px-rot for an additional
   rotation drift. Figures don't have data-rot-mult so they stay still rotation-wise, only translating
   on cursor / gyro input. The framework supports per-element --rot resting tilt + composed --px-rot
   drift for any future tilted decoration we add. */
.empty-state .px-item{transition:transform .55s cubic-bezier(.2,.8,.3,1);will-change:transform;pointer-events:none;transform:rotate(calc(var(--rot,0deg) + var(--px-rot,0deg))) translate3d(var(--px-x,0px),var(--px-y,0px),0)}
@media (prefers-reduced-motion:reduce){.empty-state .px-item{transition:none;--px-x:0px;--px-y:0px}}

/* (Shards system removed — was a the sister app decorative-glass-shards background flourish.) */

/* Character figures — KxD now ships SIX: Blue (dinosaur, far-left) wrapping Ksena, then Dieter
   in the center-right, then Echo (dinosaur, far-right) wrapping Dieter. Each dinosaur is split
   into body + head so the body sits behind the humans and the head wraps in front of them —
   the visual is the dinosaurs hugging the humans.
   All source files are ~1506-1507 px tall, so height-driven sizing keeps them at the same
   rendered height (Ksena's spec: same height for paired members). Anchors mirror left/right:
   Ksena & Blue (+ Blue's head) → right-anchored; Dieter & Echo (+ Echo's head) → left-anchored.
   That keeps the layout balanced as the viewport changes width.
   Z-order back→front: blue body (10) → echo body (20) → dieter (30) → ksena (40) →
   echo head (50) → blue head (60).
   The fig-front/fig-mid/fig-back z-index layering is kept for cherry-pick parity with the
   the sister app multi-figure framework but is no longer used in markup; fig-pos-N classes carry
   their own z-index now. Positions exported from /tools/positioner.html. */
.empty-state .fig-layer{position:absolute;inset:0;pointer-events:none;z-index:15}
.empty-state .fig{position:absolute;width:auto;pointer-events:none;will-change:transform;filter:drop-shadow(0 12px 30px rgba(58,47,26,.22));opacity:0;animation:figFadeIn 1.4s cubic-bezier(.2,.8,.3,1) forwards}
/* The two CHARACTER figures are live buttons (her 2026-07-07 ask) — hover glows their identity
   color (Ksena golden, Dieter viridian; drop-shadow hugs the png silhouette), click opens their
   profile panel, same as their story circles. The dinosaurs stay decorative — for now. */
.empty-state .fig.fig-click{pointer-events:auto;cursor:pointer;transition:filter .25s ease}
/* Glow tuned 2026-07-07 (her note: too strong) — denser and hugging the edge: small radius at high
   alpha + one modest halo, instead of the old wide double-bloom. */
.empty-state .fig.fig-ksena:hover{filter:drop-shadow(0 12px 30px rgba(58,47,26,.22)) drop-shadow(0 0 7px rgba(242,192,91,.85)) drop-shadow(0 0 16px rgba(242,192,91,.30))}
.empty-state .fig.fig-dieter:hover{filter:drop-shadow(0 12px 30px rgba(58,47,26,.22)) drop-shadow(0 0 7px rgba(39,170,137,.85)) drop-shadow(0 0 16px rgba(39,170,137,.30))}
/* ── DIETER HOVER TAKEOVER (her 2026-07-07 design, recipes 1:1 from her /tools/glitch-lab picks:
   fx-skew "small, slow, sinister", fx-flicker, fx-rgb chromatic split in the characters' colors,
   fx-title-glitch, scanlines). A JS director in showEmptyState fires these as ONE-SHOT bursts at
   random while his figure is hovered — the lab's own architecture. Golden ghost left, viridian
   ghost right (--fx-color-l/-r, same variable contract as the lab). Ksena + dinosaurs never glitch. */
/* Ghost colors: golden left, viridian right — viridian DARKENED per her note ("teal too light,
   slightly darker for the atmosphere"). */
.empty-state{--fx-color-l:rgba(242,192,91,.85);--fx-color-r:rgba(18,116,92,.9)}
/* CRITICAL: every fx class keeps figFadeIn FIRST in its animation list. The base .fig rule holds
   opacity:0 filled to .96 by figFadeIn — a bare `animation:fxWhatever` would REPLACE that list,
   dropping the figure to invisible during the burst and re-running the 1.4s fade-in after it
   (her "flicker makes Dieter disappear for a long time" + "I don't see the skew" — he was skewing
   while invisible). Same-name-same-params at the same list position = figFadeIn neither restarts
   nor loses its finished fill. */
/* fx-skew = the lab's EXACT values (her note: my 2x amplification read as a "wiggle" / too jumpy;
   the lab's subtlety is the whole point — "you almost think you're seeing things"). Small, slow. */
.empty-state .fig.fx-skew{animation:figFadeIn 1.4s cubic-bezier(.2,.8,.3,1) forwards,fxSkew 380ms cubic-bezier(.45,.05,.55,.95) 1}
@keyframes fxSkew{
  0%{transform:translate(0,0) skew(0,0) rotate(0)}
  30%{transform:translate(2px,-1px) skew(-.8deg,.4deg) rotate(-.4deg)}
  60%{transform:translate(-1.5px,1.5px) skew(.6deg,-.5deg) rotate(.3deg)}
  100%{transform:translate(0,0) skew(0,0) rotate(0)}
}
.empty-state .fig.fx-flicker{animation:figFadeIn 1.4s cubic-bezier(.2,.8,.3,1) forwards,fxFlicker 520ms steps(7) 1}
@keyframes fxFlicker{0%,100%{opacity:.96}30%{opacity:.12}50%{opacity:.5}70%{opacity:.2}}
.empty-state .fig.fx-rgb{filter:drop-shadow(0 12px 30px rgba(58,47,26,.22)) drop-shadow(-3px 0 var(--fx-color-l)) drop-shadow(3px 0 var(--fx-color-r)) saturate(1.15)!important;animation:figFadeIn 1.4s cubic-bezier(.2,.8,.3,1) forwards,fxRgbPulse 600ms ease 1}
@keyframes fxRgbPulse{0%,100%{opacity:.96}40%{opacity:.88}60%{opacity:.7}}
.welcome-text.fx-title-glitch h2{text-shadow:-2px 0 var(--fx-color-l),2px 0 var(--fx-color-r);animation:fxTitleFlicker 520ms steps(7) 1}
.welcome-text.fx-title-glitch .pre{text-shadow:-1px 0 var(--fx-color-l),1px 0 var(--fx-color-r);animation:fxTitleFlicker 520ms steps(7) 1}
@keyframes fxTitleFlicker{0%,100%{opacity:1}30%{opacity:.12}50%{opacity:.55}70%{opacity:.25}}
/* Scanlines overlay — bright lines, one 820ms roll per burst (the lab's .scan verbatim; z200 = over the scene). */
.empty-state .scan{position:absolute;inset:0;pointer-events:none;opacity:0;z-index:200;background:repeating-linear-gradient(to bottom, rgba(255,255,255,0) 0 2px, rgba(255,255,255,.16) 3px, rgba(255,255,255,0) 4px);transition:opacity 120ms ease}
.empty-state .scan.on{opacity:.85;animation:scanroll 820ms linear}
@keyframes scanroll{from{background-position:0 -24px}to{background-position:0 24px}}
.empty-state .fig-back{z-index:20}
.empty-state .fig-mid{z-index:40}
.empty-state .fig-front{z-index:60}
/* Desktop layout — exported positions from the positioner tool.
   Each fig-pos-N pins position + z-index in one place so adjustments stay co-located. */
.empty-state .fig-pos-1{right:47%;top:28%;height:102%;z-index:40}  /* Ksena */
.empty-state .fig-pos-2{left:47%;top:25%;height:100%;z-index:30}   /* Dieter */
.empty-state .fig-pos-3{right:42%;top:32%;height:100%;z-index:10}  /* Blue body */
.empty-state .fig-pos-4{left:45%;top:32%;height:100%;z-index:20}   /* Echo body */
.empty-state .fig-pos-5{left:45%;top:32%;height:100%;z-index:50}   /* Echo head */
.empty-state .fig-pos-6{right:42%;top:32%;height:100%;z-index:60}  /* Blue head */
@keyframes figFadeIn{0%{opacity:0}100%{opacity:.96}}
@media (prefers-reduced-motion:reduce){.empty-state .fig{animation:none;opacity:.96}}

/* Welcome text overlay: pinned above the figure row since the avatar row is gone, with a z-index
   above every figure layer so the title reads cleanly over the scene. */
.empty-state .welcome-text{position:absolute;top:7%;left:0;right:0;text-align:center;z-index:100;pointer-events:auto;display:flex;flex-direction:column;align-items:center;gap:10px;padding:0 20px;font-family:"Cormorant Upright","Merriweather",Georgia,serif}

/* (Anglerfish buddy CSS removed for KxD — sister-app-only flourish.) */

/* ─── WELCOME BUBBLE + MAIL ICON ──────────────────────────────────────
   On Ksena's first ever open of the app, a chat-bubble-styled message from "Ksena" floats in
   from the top-right corner of the welcome page. After dismissal (or anytime later), an envelope
   icon at the top-left of the welcome page lets her re-read it. The bubble's appearance mirrors
   Dieter's bubble settings (gradient creme/pink, top-left sharp corner, DM Sans 13 px, etc.)
   exported from the customization tab — so it looks like a real first message.

   Mail icon: lives in the left panel's .lp-footer between the Add and General buttons.
   Only visible on the welcome page (body.kxd-welcome). Subtly styled to match the existing
   lp-btn aesthetic — narrow flex slot, muted icon, same hover behavior as siblings. */
.lp-btn-icon{flex:0 0 32px;padding:6px;display:none;align-items:center;justify-content:center;color:var(--muted)}
body.kxd-welcome .lp-btn-icon{display:flex}
.lp-btn-icon svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2}


/* (Cursor-glow CSS removed for KxD — paired with the anglerfish, sister-app-only flourish.) */
@media (hover:none),(pointer:coarse){.buddy-cursor-glow{display:none!important}}

/* Mobile / narrow-screen tuning. Mobile positions also exported from the positioner — six
   figures arranged tighter horizontally and dropped lower (top:48%) so the welcome title at
   top:11.5% has room above. Height reduced to 80% so the row fits a phone canvas. */
@media (max-width:768px){
  .empty-state .fig-pos-1{right:42%;top:36%;height:92%}  /* Ksena */
  .empty-state .fig-pos-2{left:27%;top:34%;height:90%}   /* Dieter */
  .empty-state .fig-pos-3{right:19%;top:41%;height:85%}  /* Blue body */
  .empty-state .fig-pos-4{left:17%;top:41%;height:85%}   /* Echo body */
  .empty-state .fig-pos-5{left:17%;top:41%;height:85%}   /* Echo head */
  .empty-state .fig-pos-6{right:19%;top:41%;height:85%}  /* Blue head */
  .empty-state .welcome-text{top:11.5%;gap:6px}
  .empty-state .welcome-text h2{font-size:37px!important}
  .empty-state .welcome-text p{font-size:12px!important}
}
.unread-label{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:rgba(224,64,80,.7);white-space:nowrap;padding:2px 8px;border-radius:4px;background:rgba(224,64,80,.06)}
/* (.sess-av rules removed 2026-07-07 — session entries no longer show an avatar; with a single AI
   every row wore the same face. Name + preview carry the row now.) */
.sess-entry .sess-info{flex:1;min-width:0;overflow:hidden}
.sess-entry .sess-name{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sess-entry .sess-preview{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px}
.sess-entry .sess-x{display:none;width:18px;height:18px;border-radius:50%;background:rgba(180,40,40,.15);border:none;color:#d44040;font-size:12px;cursor:pointer;place-items:center;flex-shrink:0;padding:0;line-height:1}
.sess-entry:hover .sess-x{display:grid}
.sess-entry .sess-x:hover{background:rgba(180,40,40,.35)}
/* Three-dot menu button on each chat row — replaces the old × delete button. The dropdown that appears
   has Rename / Archive / Delete actions. Hidden until row hover (or .menu-open while the dropdown is showing). */
.sess-entry .sess-menu-btn{display:none;width:22px;height:22px;border-radius:6px;background:transparent;border:none;color:var(--muted);font-size:14px;cursor:pointer;align-items:center;justify-content:center;flex-shrink:0;padding:0;line-height:1;letter-spacing:1px}
.sess-entry:hover .sess-menu-btn,.sess-entry.menu-open .sess-menu-btn{display:flex}
.sess-entry .sess-menu-btn:hover{background:rgba(var(--ov),.08);color:var(--text)}
.sess-menu-dd{position:fixed;background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:4px;box-shadow:0 8px 24px rgba(0,0,0,.25);z-index:9000;min-width:140px;display:none;font-family:"Quicksand","DM Sans",sans-serif}
.sess-menu-dd.show{display:block}
.sess-menu-dd button{display:flex;align-items:center;gap:8px;width:100%;background:none;border:none;color:var(--text);font-size:13px;padding:8px 12px;border-radius:6px;cursor:pointer;text-align:left;font-family:inherit}
.sess-menu-dd button:hover{background:rgba(var(--ov),.08)}
.sess-menu-dd button.danger{color:#d44040}
.sess-menu-dd button.danger:hover{background:rgba(180,40,40,.12)}
.sess-menu-dd hr{border:none;border-top:1px solid rgba(var(--ov),.08);margin:4px 0}
/* Styled prompt overlay (text input). Mirrors .confirm-ov but adds an input. */
.prompt-ov{position:fixed;inset:0;background:rgba(0,0,0,.55);display:none;align-items:center;justify-content:center;z-index:9998}
.prompt-ov.show{display:flex}
.prompt-box{background:var(--bg2);border:1px solid var(--border);border-radius:16px;padding:20px 24px;width:340px;max-width:90vw}
.prompt-box p{margin:0 0 12px;font-size:15px;color:var(--text)}
.prompt-box input{width:100%;background:var(--ctrl);border:1px solid var(--ctrl-b);border-radius:8px;padding:8px 12px;font-size:14px;color:var(--text);font-family:inherit;margin-bottom:14px;box-sizing:border-box}
.prompt-box input:focus{outline:none;border-color:var(--accent)}
.prompt-box .cf-btns{justify-content:flex-end}
.prompt-box .cf-ok{background:var(--accent);color:#111;border-color:var(--accent)}
/* Manage-archived-chats modal: lists archived chats with View / Restore / Delete each. */
.arch-ov{position:fixed;inset:0;background:rgba(0,0,0,.55);display:none;align-items:center;justify-content:center;z-index:9997}
.arch-ov.show{display:flex}
.arch-box{background:var(--bg2);border:1px solid var(--border);border-radius:16px;padding:18px 22px;width:480px;max-width:92vw;max-height:78vh;display:flex;flex-direction:column}
.arch-box h3{margin:0 0 12px;font-size:16px;color:var(--text);font-family:"Quicksand","DM Sans",sans-serif}
.arch-list{flex:1;overflow:auto;display:flex;flex-direction:column;gap:6px;margin-bottom:14px;min-height:120px}
.arch-empty{color:var(--muted);text-align:center;padding:30px 10px;font-size:13px}
.arch-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;background:rgba(var(--ov),.04)}
.arch-row .arch-name{flex:1;font-size:13px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.arch-row button{background:rgba(var(--ov),.06);border:1px solid var(--ctrl-b);color:var(--text);font-size:11px;padding:4px 10px;border-radius:6px;cursor:pointer;font-family:inherit;flex-shrink:0}
.arch-row button:hover{background:rgba(var(--ov),.12)}
.arch-row button.danger{color:#d44040;border-color:rgba(180,40,40,.3)}
.arch-row button.danger:hover{background:rgba(180,40,40,.15)}
.arch-foot{display:flex;justify-content:flex-end;gap:8px}
.arch-foot button{padding:7px 16px;border-radius:8px;font-size:13px;cursor:pointer;border:1px solid var(--ctrl-b);background:var(--ctrl);color:var(--text);font-family:inherit}
/* Archive-preview mode: a read-only window into the saved chat HTML, embedded in the same modal.
   The body uses the existing .messages styling (bubbles render naturally) but is non-interactive —
   we don't run rebindAll() on the injected nodes so action buttons / clicks won't fire. */
.arch-mode-list,.arch-mode-preview{display:flex;flex-direction:column;flex:1;min-height:0}
.arch-prev-hdr{display:flex;align-items:center;gap:10px;margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid rgba(var(--ov),.06)}
.arch-prev-hdr button{background:rgba(var(--ov),.06);border:1px solid var(--ctrl-b);color:var(--text);font-size:14px;width:32px;height:32px;border-radius:8px;cursor:pointer;font-family:inherit;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:0;line-height:1}
.arch-prev-hdr button:hover{background:rgba(var(--ov),.12)}
.arch-prev-name{font-size:14px;font-weight:600;color:var(--text);font-family:"Quicksand","DM Sans",sans-serif;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Preview body uses .messages class so bubble styles apply, but we have to override a few of its
   layout-y properties: .messages is sized to fit a chat panel (770px-targeted side padding,
   position:relative for the aurora) which isn't right inside a 480px modal. */
.arch-prev-body{flex:1!important;overflow:auto!important;background:var(--bg)!important;border:1px solid var(--border);border-radius:8px;padding:14px!important;margin-bottom:14px;min-height:0!important;height:auto!important;display:flex;flex-direction:column;gap:8px;user-select:text}
.arch-prev-body .typing-row,.arch-prev-body .unread-marker,.arch-prev-body .actions-br,.arch-prev-body .scroll-down-btn{display:none!important}
.sess-badge{min-width:16px;height:16px;border-radius:8px;background:var(--accent);color:#111;font-size:9px;font-weight:700;display:grid;place-items:center;padding:0 4px;flex-shrink:0}
.mention{font-weight:600;border-radius:3px;padding:0 3px}
.mention-everyone{color:var(--muted);background:rgba(var(--ov),.14)}
.cmd-pill{display:inline-block;padding:0 6px;border-radius:4px;color:var(--accent);background:rgba(var(--ov),.08);font-family:'Menlo','Consolas',monospace;font-size:.92em;font-weight:600;line-height:1.4}
/* Quick-pick model dropdowns in Settings — text input + chevron + panel of options */
.model-pick{position:relative;display:inline-block}
.model-pick-panel{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--ctrl);border:1px solid var(--ctrl-b);border-radius:8px;box-shadow:0 6px 20px rgba(0,0,0,.18);z-index:1500;max-height:280px;overflow-y:auto;display:none}
.model-pick.open .model-pick-panel{display:block}
.model-pick-opt{padding:8px 12px;cursor:pointer;display:flex;flex-direction:row;align-items:center;gap:8px;border-bottom:1px solid rgba(var(--ov),.05);transition:background .12s}
.model-pick-opt-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}
.model-pick-opt:last-child{border-bottom:none}
.model-pick-opt:hover{background:rgba(var(--ov),.08)}
/* Favorite star toggle on each model row */
.model-pick-star{flex-shrink:0;width:26px;height:26px;padding:0;border:none;background:none;cursor:pointer;display:grid;place-items:center;border-radius:6px;color:var(--muted);opacity:.5;transition:opacity .12s,color .12s,background .12s}
.model-pick-star:hover{opacity:1;background:rgba(var(--ov),.12)}
.model-pick-star svg{width:15px;height:15px;fill:none}
.model-pick-star.faved{opacity:1;color:var(--accent)}
.model-pick-star.faved svg{fill:var(--accent)}
.model-pick-fav-empty{padding:8px 12px;font-size:11px;color:var(--muted);font-style:italic}
.model-pick-title{font-size:12px;font-weight:600;display:flex;align-items:center;gap:6px;color:var(--text)}
.model-pick-code{font-family:'Menlo','Consolas',monospace;font-size:10px;color:var(--muted);opacity:.85;font-weight:400}
/* Live model picker (NanoGPT /v1/models): sticky search bar + collapsible provider groups. */
.model-pick-search{position:sticky;top:0;z-index:2;padding:7px;background:var(--ctrl);border-bottom:1px solid rgba(var(--ov),.08);display:flex;gap:6px;align-items:center}
.model-pick-search input{flex:1;min-width:0;box-sizing:border-box;padding:6px 9px;font-size:12px;font-family:inherit;color:var(--text);background:rgba(var(--ov),.05);border:1px solid rgba(var(--ov),.12);border-radius:6px;outline:none}
.model-pick-search input:focus{border-color:var(--accent)}
.model-pick-refresh{flex-shrink:0;width:28px;height:28px;padding:0;border:none;background:rgba(var(--ov),.05);color:var(--muted);cursor:pointer;display:grid;place-items:center;border-radius:6px;transition:color .15s,background .15s}
.model-pick-refresh:hover{color:var(--accent);background:rgba(var(--ov),.12)}
.model-pick-refresh svg{width:14px;height:14px}
.model-pick-refresh.spinning svg{animation:mpSpin .8s linear infinite}
@keyframes mpSpin{to{transform:rotate(360deg)}}
/* Selector trigger (replaces the old free-text input) */
.model-pick-trigger{display:flex;align-items:center;justify-content:space-between;gap:6px;width:100%;box-sizing:border-box;background:rgba(var(--ov),.04);color:var(--text);border:1px solid rgba(var(--ov),.12);border-radius:8px;padding:7px 10px;font-size:12px;font-family:inherit;cursor:pointer;text-align:left;outline:none;transition:border-color .15s,background .15s}
.model-pick-trigger:hover,.model-pick.open .model-pick-trigger{border-color:var(--accent)}
.model-pick-triglabel{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.model-pick-triglabel.is-ph{color:var(--muted)}
.model-pick-chevron{flex-shrink:0;width:14px;height:14px;color:var(--muted);transition:transform .2s}
.model-pick.open .model-pick-chevron{transform:rotate(180deg)}
.model-pick-default .model-pick-title{font-style:italic;color:var(--muted)}
.model-pick-opt.cur{background:rgba(var(--ov),.10)}
.model-pick-opt.cur .model-pick-title{color:var(--accent);font-weight:700}
.model-pick-grp{border-bottom:1px solid rgba(var(--ov),.05)}
.model-pick-grp:last-child{border-bottom:none}
.model-pick-group{display:flex;align-items:center;gap:6px;padding:6px 10px;cursor:pointer;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);background:rgba(var(--ov),.04);user-select:none}
.model-pick-group:hover{color:var(--accent)}
.model-pick-group-chevron{width:10px;height:10px;flex-shrink:0;transition:transform .2s}
.model-pick-grp.collapsed .model-pick-group-chevron{transform:rotate(-90deg)}
.model-pick-group-count{margin-left:auto;font-weight:600;opacity:.65}
.model-pick-grp.collapsed .model-pick-group-items{display:none}
.model-pick-loading,.model-pick-empty{padding:12px;font-size:11px;color:var(--muted);text-align:center;line-height:1.5}
/* (.rec-pill / .try-pill model-picker badges removed per Ksena.) */

/* Themed dropdown component — used wherever a native <select> would have rendered an
   un-stylable OS popup (native <option> popups on Chromium Windows ignore most CSS, which
   is why the Settings → Appearance dropdowns looked light-on-white in the dark theme).
   Mirrors .model-pick's visual style but for value/label option lists. Wraps a hidden
   <input> so existing JS that reads $(id).value or listens on `change` keeps working
   unchanged — selecting an option updates the hidden input's value AND dispatches a
   `change` event on it. */
.fancy-sel{position:relative;display:inline-block;width:100%;vertical-align:middle}
.fancy-sel-trigger{display:flex;align-items:center;justify-content:space-between;gap:6px;width:100%;background:rgba(var(--ov),.04);color:var(--text);border:1px solid rgba(var(--ov),.08);border-radius:7px;padding:3px 6px;font-size:11px;font-family:inherit;cursor:pointer;text-align:left;outline:none;transition:border-color .15s,background .15s;box-sizing:border-box;line-height:1.4}
.fancy-sel-trigger:hover,.fancy-sel.open .fancy-sel-trigger{border-color:var(--accent)}
.fancy-sel-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fancy-sel-chevron{flex-shrink:0;width:12px;height:12px;color:var(--muted);transition:transform .2s}
.fancy-sel.open .fancy-sel-chevron{transform:rotate(180deg)}
.fancy-sel-panel{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--ctrl);border:1px solid var(--ctrl-b);border-radius:8px;box-shadow:0 6px 20px rgba(0,0,0,.18);z-index:1500;max-height:280px;overflow-y:auto;display:none;min-width:140px}
.fancy-sel.open .fancy-sel-panel{display:block}
/* Collision-aware flip: when there isn't enough room below the trigger for the panel,
   _positionDropdownPanel adds .flip-up to the wrapper and the panel anchors above instead. */
.fancy-sel.flip-up .fancy-sel-panel,.model-pick.flip-up .model-pick-panel{top:auto;bottom:calc(100% + 4px)}
.fancy-sel-group{padding:6px 10px 4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);background:rgba(var(--ov),.04);border-top:1px solid rgba(var(--ov),.06)}
.fancy-sel-group:first-child{border-top:none}
.fancy-sel-opt{padding:7px 10px;cursor:pointer;color:var(--text);font-size:12px;transition:background .12s;border-bottom:1px solid rgba(var(--ov),.04)}
.fancy-sel-opt:last-child{border-bottom:none}
.fancy-sel-opt:hover{background:rgba(var(--ov),.08)}
.fancy-sel-opt.fancy-sel-opt-selected{background:rgba(var(--ov),.12);color:var(--accent);font-weight:600}
.scroll-down-btn{position:absolute;bottom:110px;left:50%;transform:translateX(-50%);width:28px;height:28px;border-radius:50%;background:var(--bg2);border:1px solid var(--border);color:var(--muted);cursor:pointer;display:grid;place-items:center;z-index:8;opacity:0;pointer-events:none;transition:opacity .25s,color .15s,border-color .15s,transform .15s}
.scroll-down-btn.show{opacity:.95;pointer-events:auto}
.scroll-down-btn:hover{opacity:1;color:var(--accent);border-color:var(--accent);transform:translateX(-50%) translateY(-1px)}
.scroll-down-btn svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.scroll-down-badge{position:absolute;top:-5px;right:-7px;min-width:16px;height:16px;border-radius:9px;background:var(--accent);color:#111;font-size:9px;font-weight:700;display:none;place-items:center;padding:0 4px;line-height:1;box-shadow:0 1px 4px rgba(0,0,0,.2)}
.scroll-down-badge:not(:empty){display:grid}
/* Memory UI */
.mem-list{display:flex;flex-direction:column;gap:6px;margin-bottom:10px;max-height:300px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--scroll) transparent}
.mem-list::-webkit-scrollbar{width:4px}
.mem-list::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:4px}
.mem-item{display:flex;gap:6px;align-items:flex-start;padding:6px 8px;background:rgba(var(--ov),.03);border:1px solid rgba(var(--ov),.06);border-radius:8px}
.mem-item textarea{flex:1;background:transparent;border:none;color:var(--text);font-size:11px;font-family:inherit;resize:none;outline:none;line-height:1.4;min-height:0;padding:0;overflow:hidden}
.mem-item .mem-del{width:20px;height:20px;border-radius:50%;background:none;border:none;color:rgba(180,40,40,.6);font-size:14px;cursor:pointer;flex-shrink:0;display:grid;place-items:center;padding:0;opacity:0;transition:opacity .15s}
.mem-item:hover .mem-del{opacity:1}
.mem-item .mem-del:hover{color:#d44040;background:rgba(180,40,40,.1)}
/* Tag-insertion buttons — visually echo the bronze .pill-tag they insert, so the user can recognize
   "what I click here looks like what shows up in the textarea". */
.sc-tag{font-size:10px;padding:3px 10px;border-radius:6px;border:none;background:rgba(180,140,90,.25);color:#8B6B3A;cursor:pointer;font-family:inherit;font-weight:600;transition:background .15s}.sc-tag:hover{background:rgba(180,140,90,.40)}
.mem-add-btn{width:100%;padding:8px;border-radius:8px;background:rgba(var(--ov),.04);border:1px dashed rgba(var(--ov),.12);color:var(--muted);font-size:11px;font-family:inherit;cursor:pointer;transition:all .15s}
.mem-add-btn:hover{background:rgba(var(--ov),.07);border-color:var(--accent);color:var(--accent)}
/* Dream-digest Clear — delete-adjacent, so it reads as "this erases something" (her wine ask) */
.dream-clear{color:#a34655!important;border-color:rgba(163,70,85,.4)!important}
.dream-clear:hover{background:rgba(163,70,85,.1)!important;border-color:#a34655!important;color:#b05262!important}
/* ── APPEARANCE PRESETS + THE ATELIER (2026-07-09, her design) ─────────────────────────────── */
.preset-row{display:flex;gap:16px;flex-wrap:wrap;justify-content:center;padding:4px 0}
.preset-orb{position:relative;text-align:center;cursor:pointer;min-width:56px;user-select:none}
.preset-orb .po-card{width:64px;height:46px;border-radius:10px;margin:0 auto 5px;border:2px solid var(--border);box-sizing:border-box;display:flex;flex-direction:column;justify-content:center;gap:3px;padding:5px 6px;overflow:hidden;transition:border-color .2s,box-shadow .2s}
.preset-orb .po-name{font-size:10px;color:var(--muted);max-width:70px;display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.preset-orb .po-x{position:absolute;top:-6px;right:2px;width:18px;height:18px;border-radius:50%;background:var(--bg2);border:1px solid rgba(163,70,85,.5);color:#a34655;font-size:12px;line-height:1;cursor:pointer;display:grid;place-items:center;padding:0;opacity:0;transition:opacity .15s;z-index:2}
/* Hover reveals ONLY where hover exists — on iOS a :hover that changes descendant visibility
   makes the first tap a "hover tap" and the second the real click (her round: presets needed
   two taps). Touch gets the ✕ faintly-always instead. */
@media (hover:hover){
  .preset-orb:hover .po-card{border-color:var(--accent);box-shadow:0 0 12px 2px rgba(242,192,91,.35)}
  .preset-orb:hover .po-name{color:var(--accent)}
  .preset-orb:hover .po-x{opacity:1}
}
@media (hover:none){.preset-orb .po-x{opacity:.75}}
.preset-orb .po-x:hover{background:rgba(163,70,85,.15)}
.atelier-ov{position:fixed;inset:0;background:rgba(0,0,0,.55);display:none;align-items:center;justify-content:center;z-index:9996}
.atelier-ov.show{display:flex}
.atelier{background:var(--bg2);border:1px solid var(--border);border-radius:16px;width:min(1080px,94vw);height:min(740px,90vh);display:flex;flex-direction:column;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.4)}
.atelier-hdr{display:flex;justify-content:space-between;align-items:center;padding:12px 20px;border-bottom:1px solid rgba(var(--ov),.08);font-family:'Cormorant Upright','Merriweather',Georgia,serif;font-size:20px;font-weight:600;color:var(--accent);flex-shrink:0}
.atelier-hdr #atelierClose:hover{color:var(--text);background:rgba(var(--ov),.06)}
.atelier-body{flex:1;display:flex;min-height:0}
.atelier-left{flex:1.05;padding:18px;overflow-y:auto;border-right:1px solid rgba(var(--ov),.08)}
.atelier-right{flex:1;overflow-y:auto;padding:2px 18px 20px}
.atelier-right .sc-section{margin-top:14px}
/* ── GALLERY (2026-07-09, her design: two shelves — his works / her uploads) ────────────────── */
.lp-header{position:relative}
#lpGalleryBtn{position:absolute;right:10px;top:50%;transform:translateY(-50%);width:28px;height:28px;border-radius:8px;background:none;border:none;color:var(--muted);cursor:pointer;display:grid;place-items:center;padding:0}
#lpGalleryBtn svg{width:16px;height:16px}
#lpGalleryBtn:hover{color:var(--accent);background:rgba(var(--ov),.06)}
.gal-tabs{display:flex;gap:6px}
.gal-tab{background:none;border:1px solid var(--ctrl-b);color:var(--muted);font-size:11px;padding:5px 12px;border-radius:999px;cursor:pointer;font-family:inherit;transition:color .15s,border-color .15s}
.gal-tab.active{color:var(--accent);border-color:var(--accent)}
.gal-body{flex:1;overflow-y:auto;padding:16px}
.gal-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px}
.gal-tile{position:relative;aspect-ratio:1;border-radius:10px;overflow:hidden;background:rgba(var(--ov),.05);cursor:zoom-in;border:1px solid rgba(var(--ov),.08)}
.gal-tile img{width:100%;height:100%;object-fit:cover;display:block}
.gal-tile .gal-cap{position:absolute;left:0;right:0;bottom:0;font-size:9px;padding:14px 6px 22px;background:linear-gradient(transparent,rgba(0,0,0,.72));color:#eee;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:0;transition:opacity .15s}
.gal-tile:hover .gal-cap{opacity:1}
/* Per-image actions (her round 12): jump to the birth session / delete everywhere. Hover-reveal
   on desktop; always faintly present on touch (no hover there). */
.gal-act{position:absolute;right:5px;bottom:4px;display:flex;gap:6px;opacity:0;transition:opacity .15s;z-index:2}
.gal-tile:hover .gal-act{opacity:1}
@media (hover:none){.gal-act{opacity:.85}}
.gal-act button{background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.18);color:#eee;font-size:10px;padding:3px 8px;border-radius:8px;cursor:pointer;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}
.gal-act button:hover{background:rgba(0,0,0,.75)}
.gal-act .gal-del:hover{color:#ff8a94;border-color:rgba(255,138,148,.5)}
.gal-empty{text-align:center;color:var(--muted);font-size:12px;padding:40px 10px}
/* ── HOME CARD + THE LIFELINE (2026-07-09, home-page trilogy ②) ─────────────────────────────── */
.home-card{position:absolute;left:50%;transform:translateX(-50%);bottom:26px;width:min(520px,86%);z-index:130;background:color-mix(in srgb,var(--input-bg) var(--frost,40%),transparent);backdrop-filter:blur(22px) saturate(1.5);-webkit-backdrop-filter:blur(22px) saturate(1.5);border:1px solid var(--ctrl-b);border-radius:20px;padding:12px 18px 10px;animation:figFadeIn 1.4s ease .6s both}
.hc-top{display:flex;justify-content:space-between;align-items:baseline;font-family:'Quicksand','DM Sans',sans-serif;font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--muted);text-transform:uppercase;margin-bottom:2px}
.home-card canvas{display:block;width:100%;height:56px}
.hc-mood{display:flex;align-items:center;justify-content:center;gap:7px;cursor:pointer;margin-top:2px;user-select:none}
.hc-dot{width:7px;height:7px;border-radius:50%;background:#27aa89;box-shadow:0 0 8px 1px rgba(39,170,137,.6);animation:hcPulse 1.1s ease-in-out infinite}
@keyframes hcPulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(.75);opacity:.6}}
#hcMoodWord{font-family:'Cormorant Upright','Merriweather',Georgia,serif;font-style:italic;font-size:17px;color:#27aa89;letter-spacing:.03em}
.hc-note{display:none;text-align:center;font-size:11px;color:var(--muted);margin-top:5px;line-height:1.45}
.hc-note.show{display:block}
@media (max-width:768px){.home-card{bottom:calc(18px + env(safe-area-inset-bottom,0px));width:min(440px,90%);padding:10px 14px 8px}}
/* Scheduled tasks + home feed on the card (trilogy ③) */
.hc-sec{margin-top:8px;border-top:1px solid rgba(var(--ov),.08);padding-top:6px}
.hc-sec-t{font-family:'Quicksand','DM Sans',sans-serif;font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:4px}
.hc-task{display:flex;align-items:center;gap:8px;font-size:11px;padding:2px 0}
.hct-l{font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;text-align:left}
.hct-w{color:var(--muted);flex-shrink:0;font-size:10px}
.hct-x{background:none;border:none;color:rgba(163,70,85,.65);cursor:pointer;font-size:11px;padding:2px 4px;border-radius:4px;flex-shrink:0}
.hct-x:hover{color:#a34655;background:rgba(163,70,85,.1)}
.hc-feed-msg{display:flex;flex-direction:column;gap:1px;text-align:left;padding:4px 0;border-bottom:1px dashed rgba(var(--ov),.06)}
.hc-feed-msg:last-child{border-bottom:none}
.hcf-t{font-size:9px;color:var(--muted);letter-spacing:.04em}
.hcf-x{font-size:11px;line-height:1.4;color:var(--text)}
#hcFeedList{max-height:150px;overflow-y:auto}
/* Task confirmation card in chat (created when he schedules something) */
.task-card{display:flex;align-items:center;gap:10px;margin:10px auto;width:min(420px,92%);padding:9px 12px;border-radius:14px;background:rgba(39,170,137,.08);border:1px solid rgba(39,170,137,.3)}
.task-card svg{width:18px;height:18px;color:#27aa89;flex-shrink:0}
.task-card .tc-main{flex:1;min-width:0;text-align:left}
.task-card .tc-label{font-size:12px;font-weight:600;color:var(--text)}
.task-card .tc-sub{font-size:10px;color:var(--muted);margin-top:1px}
.task-card .tc-x{background:none;border:1px solid rgba(163,70,85,.4);color:#a34655;width:22px;height:22px;border-radius:50%;cursor:pointer;font-size:11px;display:grid;place-items:center;padding:0;flex-shrink:0}
.task-card .tc-x:hover{background:rgba(163,70,85,.12)}
.task-card.off{opacity:.45;border-color:rgba(var(--ov),.15);background:rgba(var(--ov),.04)}
.task-card.off svg{color:var(--muted)}
/* ── THE HOME PAGE (2026-07-11, from her designer's mock, re-tokened) ───────────────────────── */
/* THE HOME SCENE (2026-07-11, her reference image, in BLACK & GOLD): near-black night, big
   blurred golden clouds breathing slowly behind the glass, and LITERAL burning embers — a
   particle canvas of golden sparks rising, swaying, flickering, dying into the dark. The home
   is always dark, both themes (the concept IS black and gold). */
.home-page{position:absolute;inset:0;overflow:hidden;background:linear-gradient(165deg,#0e0a06 0%,#070402 55%,#040201 100%);contain:layout paint;color:#e5e2db}
.home-page::before{content:'';position:absolute;inset:-25%;background:radial-gradient(ellipse at 25% 18%,rgba(242,192,91,.15),transparent 40%),radial-gradient(ellipse at 76% 58%,rgba(214,140,40,.13),transparent 42%),radial-gradient(ellipse at 44% 92%,rgba(163,90,30,.11),transparent 45%),radial-gradient(ellipse at 60% 35%,rgba(255,235,170,.05),transparent 50%);filter:blur(46px);animation:auroraFlow 18s ease-in-out infinite alternate;will-change:transform;transform:translateZ(0);contain:strict;pointer-events:none}
.home-page>*{position:relative;z-index:1}
.hp-embers{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
/* dark-glass overrides — the theme vars would paint light glass/dark text on the lemon theme */
.home-page .hp-card{background:rgba(13,10,5,.45);border-color:rgba(120,104,70,.35)}
.home-page .hp-clock{color:#f2c05b}
.home-page .hpf-x,.home-page .hpt-l{color:#e5e2db}
.home-page .hp-sec-t,.home-page .hp-date,.home-page .hp-life-top,.home-page .hp-room-l,.home-page .hpf-t,.home-page .hpt-s,.home-page .hp-note,.home-page .hp-door-sub,.home-page .hp-j-line,.home-page .hp-back{color:#ccbea2}
.home-page .hp-back{border-color:rgba(120,104,70,.45)}
/* ── LEMON DAYLIGHT home (her call: the light theme deserves its own vibe) ──────────────────────
   Warm parchment morning: cream base, honey clouds, and the ember canvas swaps to GOLD DUST
   drifting in sunlight (the engine reads html.th-lemon per frame). The lifeline KEEPS its
   near-black stage — a dark hearth in a bright room, deliberate contrast. */
html.th-lemon .home-page{background:linear-gradient(165deg,#f0eadd 0%,#e8e0d0 55%,#ddd2bf 100%);color:#3a2f1a}
html.th-lemon .home-page::before{background:radial-gradient(ellipse at 25% 18%,rgba(255,232,150,.6),transparent 40%),radial-gradient(ellipse at 76% 58%,rgba(240,200,120,.5),transparent 42%),radial-gradient(ellipse at 44% 92%,rgba(214,170,90,.4),transparent 45%),radial-gradient(ellipse at 60% 35%,rgba(255,250,230,.55),transparent 50%)}
html.th-lemon .home-page .hp-card{background:rgba(255,248,216,.55);border-color:#e8d8a8}
html.th-lemon .home-page .hp-clock{color:#a8842a}
html.th-lemon .home-page .hpf-x,html.th-lemon .home-page .hpt-l{color:#3a2f1a}
html.th-lemon .home-page .hp-sec-t,html.th-lemon .home-page .hp-date,html.th-lemon .home-page .hp-room-l,html.th-lemon .home-page .hpf-t,html.th-lemon .home-page .hpt-s,html.th-lemon .home-page .hp-j-line,html.th-lemon .home-page .hp-back,html.th-lemon .home-page .hp-life-top,html.th-lemon .home-page .hp-note{color:#7a6a3a}
html.th-lemon .home-page .hp-back{border-color:#d9c68e}
html.th-lemon .hpf-thumb{border-color:#e8d8a8}
html.th-lemon .home-page .hp-door-j{background:rgba(255,248,216,.55)}
.hp-col{position:relative;height:100%;overflow-y:auto;padding:calc(10px + env(safe-area-inset-top,0px)) 16px calc(20px + env(safe-area-inset-bottom,0px));max-width:430px;margin:0 auto;display:flex;flex-direction:column;gap:12px} /* her hand-tuned paddings (round 26, from her devtools session): 10 top / 20 bottom + insets */
.hp-clockwrap{text-align:center;animation:figFadeIn 1.2s ease both;position:relative}
/* Notification bell (2026-07-12): rides the clock's top-right corner. Dim = off, accent = on.
   Hidden entirely when the platform can't push (Safari tab on iOS — needs the installed PWA). */
.hp-bell{position:absolute;top:2px;right:0;background:none;border:none;cursor:pointer;color:var(--text);opacity:.35;padding:8px;transition:opacity .18s,color .18s;-webkit-tap-highlight-color:transparent}
.hp-bell svg{width:19px;height:19px;display:block}
.hp-bell.on{opacity:.95;color:var(--accent)}
/* Task-form pickers v2 (2026-07-13, her reference images): Google-style compact calendar +
   wheel time picker (three snap-scroll columns, exact minutes). Scrollbars hidden entirely —
   the wheel IS the scroll affordance (her no-native-widgets rule). */
.tf-cal-nav{background:none;border:none;color:var(--muted);font-size:16px;width:28px;height:28px;cursor:pointer;border-radius:50%;font-family:inherit;padding:0}
.tf-cal-nav:hover{background:rgba(var(--ov),.08)}
.tf-cal-dow{display:grid;grid-template-columns:repeat(7,1fr);font-size:10px;color:var(--muted);text-align:center;margin-bottom:2px}
.tf-cal-grid{display:grid;grid-template-columns:repeat(7,1fr);row-gap:2px}
.tf-cal-day{background:none;border:none;width:32px;height:32px;padding:0;margin:0 auto;border-radius:50%;font-family:inherit;font-size:12px;color:var(--text);cursor:pointer;display:flex;align-items:center;justify-content:center}
.tf-cal-day:hover{background:rgba(var(--ov),.08)}
.tf-cal-day.dim{color:rgba(var(--ov),.25);pointer-events:none}
.tf-cal-day.today{border:1px solid var(--accent)}
.tf-cal-day.sel{background:var(--accent);color:#1a1410}
.tf-cal-day.sel:hover{background:var(--accent)}
.tw-wrap{position:relative;display:flex;align-items:stretch;justify-content:center;gap:4px;height:150px;margin:4px 0}
.tw-col{overflow-y:auto;scroll-snap-type:y mandatory;scrollbar-width:none;-ms-overflow-style:none;width:48px;text-align:center}
.tw-col::-webkit-scrollbar{display:none}
.tw-pad{height:60px}
.tw-item{height:30px;line-height:30px;scroll-snap-align:center;font-size:14px;color:var(--muted);opacity:.4;transition:opacity .12s;cursor:pointer}
.tw-item.on{opacity:1;color:var(--text);font-weight:600}
.tw-band{position:absolute;left:12%;right:12%;top:60px;height:30px;border:1px solid var(--ctrl-b);border-radius:10px;pointer-events:none;background:rgba(var(--ov),.05)}
.tw-sep{align-self:center;color:var(--muted);font-size:14px}
/* The pickers float in a small anchored popover (her reference images), not an inline band. */
.tf-pop{position:fixed;z-index:6;width:252px;background:var(--bg2);border:1px solid var(--ctrl-b);border-radius:14px;box-shadow:0 10px 34px rgba(0,0,0,.38);padding:12px}
/* ── ATTENTION LAYER (2026-07-13, her ask) ─────────────────────────────────────────────────────
   .kxd-banner: the messenger-style heads-up that slides from the top when a delivery or a
   non-open session gets a message while she's elsewhere in the app. Tap = go there, ✕ = dismiss.
   .hp-feed-msg.unseen: feed entries newer than her last home visit wear an accent glow. */
.kxd-banner{position:fixed;top:calc(10px + env(safe-area-inset-top,0px));left:50%;transform:translate(-50%,-130%);width:min(440px,calc(100% - 24px));z-index:10000;display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:16px;background:color-mix(in srgb,var(--bg2) 88%,transparent);backdrop-filter:blur(18px) saturate(1.4);-webkit-backdrop-filter:blur(18px) saturate(1.4);border:1px solid var(--ctrl-b);box-shadow:0 10px 30px rgba(0,0,0,.35);cursor:pointer;transition:transform .32s ease}
.kxd-banner.show{transform:translate(-50%,0)}
.kxd-banner-av{width:34px;height:34px;border-radius:50%;overflow:hidden;background:var(--ctrl);flex-shrink:0;display:grid;place-items:center;font-weight:700;font-size:14px;color:var(--text-88)}
.kxd-banner-av img{width:100%;height:100%;object-fit:cover}
.kxd-banner-m{flex:1;min-width:0}
.kxd-banner-n{font-size:12px;font-weight:700;color:var(--accent)}
.kxd-banner-t{font-size:12px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.kxd-banner-x{background:none;border:none;color:var(--muted);cursor:pointer;font-size:13px;padding:4px;flex-shrink:0;font-family:inherit}
/* App-styled range slider (2026-07-15, His Initiative — her no-native-widgets law applies to
   sliders too): thin track in the overlay tint, accent thumb, both engines covered. */
.kxd-range{-webkit-appearance:none;appearance:none;height:4px;border-radius:999px;background:rgba(var(--ov),.16);outline:none;cursor:pointer;padding:0;margin:0}
.kxd-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:18px;height:18px;border-radius:50%;background:var(--accent);border:none;cursor:pointer;box-shadow:0 1px 5px rgba(0,0,0,.35)}
.kxd-range::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:var(--accent);border:none;cursor:pointer;box-shadow:0 1px 5px rgba(0,0,0,.35)}
.kxd-range::-moz-range-track{height:4px;border-radius:999px;background:rgba(var(--ov),.16)}
/* ── INCOMING CALL RING (2026-07-15) ───────────────────────────────────────────────────────────
   Full-screen glass veil, avatar with expanding pulse rings, round accept/decline. All-SVG. */
.ic-ov{position:fixed;inset:0;z-index:30000;display:flex;align-items:center;justify-content:center;background:rgba(4,2,1,.82);backdrop-filter:blur(14px) saturate(1.2);-webkit-backdrop-filter:blur(14px) saturate(1.2);opacity:0;transition:opacity .3s ease}
.ic-ov.show{opacity:1}
.ic-card{text-align:center;padding:24px}
.ic-avatar{position:relative;width:116px;height:116px;margin:0 auto 18px;border-radius:50%;background:var(--ctrl);display:grid;place-items:center;font-size:44px;font-weight:700;color:var(--text-88)}
.ic-avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;position:relative;z-index:2}
.ic-avatar span{position:relative;z-index:2}
.ic-pulse{position:absolute;inset:0;border-radius:50%;border:2px solid rgba(39,170,137,.55);animation:icPulse 2.4s ease-out infinite;z-index:1}
.ic-p2{animation-delay:1.2s}
@keyframes icPulse{0%{transform:scale(1);opacity:.8}100%{transform:scale(1.9);opacity:0}}
.ic-name{font-family:'Cormorant Upright',Georgia,serif;font-size:34px;color:#e5e2db;margin-bottom:4px}
.ic-sub{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:#ccbea2;margin-bottom:40px;animation:icBreathe 2.4s ease-in-out infinite}
@keyframes icBreathe{0%,100%{opacity:.55}50%{opacity:1}}
.ic-btns{display:flex;gap:72px;justify-content:center}
.ic-btn{width:66px;height:66px;border-radius:50%;border:none;cursor:pointer;display:grid;place-items:center;color:#fff;transition:transform .15s ease,filter .15s ease;-webkit-tap-highlight-color:transparent}
.ic-btn svg{width:28px;height:28px}
.ic-btn:active{transform:scale(.92)}
.ic-decline{background:#a3352c;box-shadow:0 6px 22px rgba(163,53,44,.45)}
.ic-accept{background:#27aa89;box-shadow:0 6px 22px rgba(39,170,137,.5);animation:icNudge 2.4s ease-in-out infinite}
@keyframes icNudge{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}
.hp-feed-msg{transition:border-color .9s ease,box-shadow .9s ease}
.hp-feed-msg.unseen{border:1px solid var(--accent);border-radius:12px;box-shadow:0 0 14px rgba(242,192,91,.2);padding-left:8px;padding-right:8px}
.hp-clock{font-family:'Cormorant Upright',Georgia,serif;font-size:54px;font-weight:600;line-height:1;color:var(--accent)}
.hp-date{font-family:'Quicksand','DM Sans',sans-serif;font-size:10px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);margin-top:5px}
.hp-card{background:color-mix(in srgb,var(--input-bg) 40%,transparent);backdrop-filter:blur(22px) saturate(1.5);-webkit-backdrop-filter:blur(22px) saturate(1.5);border:1px solid var(--ctrl-b);border-radius:20px;padding:12px 16px;animation:figFadeIn 1.2s ease .15s both}
.hp-life{border-radius:22px;transition:box-shadow .6s;background:rgba(6,5,3,.30)!important;backdrop-filter:none;-webkit-backdrop-filter:none} /* 30% dark stage (her round 8 — embers show through) */
html.th-lemon .hp-life{background:rgba(255,248,216,.55)!important;border-color:#e8d8a8} /* daylight: matching cream stage (her call — black card looked wrong in lemon) */
.hp-life-top{display:flex;align-items:baseline;justify-content:space-between;font-family:'Quicksand','DM Sans',sans-serif;font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.hp-life canvas{display:block;width:100%;height:56px}
.hp-mood{display:flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;user-select:none}
.hp-dot{width:7px;height:7px;border-radius:50%;background:#27aa89;animation:hcPulse 1.1s ease-in-out infinite}
#hpWord{font-family:'Cormorant Upright',Georgia,serif;font-style:italic;font-size:21px;color:#27aa89}
.hp-note{display:none;text-align:center;font-family:'Raleway','DM Sans',sans-serif;font-size:11px;color:var(--muted);margin-top:4px;line-height:1.45}
.hp-note.show{display:block}
.hp-rooms{flex:none;display:flex;align-items:flex-start;gap:10px;overflow-x:auto;overflow-y:hidden;height:119px;padding:14px 2px 2px;margin-top:-14px;scrollbar-width:none;animation:figFadeIn 1.2s ease .25s both} /* 119 = 14 padT + 86 tile + 5 gap + 10 label + 2 padB + 2 spare (round 32 trim — the gap below the titles now matches the other block gaps) */ /* flex:none = THE GREMLIN'S TRUE NAME (round 31): hp-col is a flex column whose content always overflows the screen — flex squeezes children before scrolling, every content-sized card refuses, and the ONE scroll container (this row, automatic min-size 0) absorbs ALL the shrinkage. Every sliver/clip since round 4 was this. min-height patches worked only because min-height blocks flex shrinking; desktop healed because grid doesn't shrink. */ /* padding-top+negative margin: the hover glow needs room INSIDE the scrollport (round 9). EXPLICIT height (round 26): auto height COLLAPSES (round-4 gremlin). overflow-y HIDDEN (round 28): 'visible' silently computed to auto, and iOS fonts render the labels a few px taller than a knife-edge budget — the row became a vertical rubber-band toy (tiles draggable everywhere). Hidden kills the pan; generous height keeps labels un-clipped. */
@media (max-width:480px){
  /* Round 25/27 (her blue mark + pitiful meowing): exactly four tiles fit the phone width.
     Budget: 100vw − 32 hp-col side padding − 4 row side padding − 30 gaps = tiles ÷ 4.
     Row height = 16 row padding + tile + 5 gap + 15 label allowance (matches the desktop
     wrapper's 106 = 86+5+15) — the round-26 formula shorted the labels their headroom. */
  .hp-room-tile{width:min(86px,calc((100vw - 66px)/4))!important;height:min(86px,calc((100vw - 66px)/4))!important;min-height:0!important}
  .hp-room{min-height:0!important}
  .hp-rooms{height:calc((100vw - 66px)/4 + 32px);max-height:118px} /* round 32, from HER device's pill: tile + 14 padT + 5 gap + 10 label + 2 padB + 1 spare. The old +44 headroom was gremlin paranoia (fonts were never the problem — the flex squeeze was); trimming it makes the gap below the titles match the other block gaps (her ask). */
}
.hp-rooms::-webkit-scrollbar{display:none}
.hp-room{display:flex;flex-direction:column;align-items:center;gap:5px;flex-shrink:0}
.hp-room-tile{width:86px;height:86px;border-radius:20px;border:1px solid var(--ctrl-b);cursor:pointer;transition:box-shadow .2s;background-size:cover;background-position:center}
.hp-room-tile:hover{box-shadow:0 0 12px 1px rgba(var(--ov),.18)}
.hp-room-l{font-family:'Quicksand','DM Sans',sans-serif;font-size:8px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
/* ── Dream Journal bookshelf (her round 9: the digest list was boring) ─────────────────────────
   One book per session, standing on a board; horizontal scroll like the rooms row. */
.dj-shelf{display:flex;align-items:flex-end;gap:7px;overflow-x:auto;padding:16px 8px 0;scrollbar-width:none}
.dj-shelf::-webkit-scrollbar{display:none}
.dj-book{flex-shrink:0;border-radius:3px 5px 5px 3px;cursor:pointer;position:relative;display:flex;align-items:center;justify-content:center;transition:transform .18s ease,box-shadow .18s ease;box-shadow:inset 0 6px 0 -3px rgba(212,175,55,.45),inset 0 -6px 0 -3px rgba(212,175,55,.45),inset 2px 0 3px rgba(255,255,255,.14),inset -4px 0 6px rgba(0,0,0,.45),1px 2px 4px rgba(0,0,0,.35)}
.dj-book:hover{transform:translateY(-7px)!important;box-shadow:inset 0 6px 0 -3px rgba(212,175,55,.6),inset 0 -6px 0 -3px rgba(212,175,55,.6),inset 2px 0 3px rgba(255,255,255,.14),inset -4px 0 6px rgba(0,0,0,.45),0 8px 14px rgba(0,0,0,.45)}
.dj-book-t{writing-mode:vertical-rl;font-family:'Cormorant Upright',Georgia,serif;font-size:11px;font-weight:600;color:rgba(255,244,214,.92);max-height:calc(100% - 20px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:.03em;text-shadow:0 1px 2px rgba(0,0,0,.55);pointer-events:none}
.dj-board{height:9px;border-radius:2px;margin:0 2px 4px;background:linear-gradient(#7a5c30,#463315 70%,#2b1e0c);box-shadow:0 3px 7px rgba(0,0,0,.4)}
.dj-reader{position:fixed;inset:0;z-index:2100;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;padding:20px;animation:figFadeIn .18s ease both}
.dj-reader-card{position:relative;max-width:560px;width:100%;max-height:78vh;overflow-y:auto;background:var(--bg);color:var(--text);border:1px solid rgba(212,175,55,.4);border-radius:18px;padding:22px 26px;font-size:13px;line-height:1.6;box-shadow:0 18px 50px rgba(0,0,0,.5);scrollbar-width:thin;scrollbar-color:var(--scroll) transparent}
.dj-reader-card::-webkit-scrollbar{width:6px}
.dj-reader-card::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:6px}
.dj-reader-card h4{font-family:'Cormorant Upright',Georgia,serif;font-size:21px;font-weight:600;color:#f2c05b;margin:0 26px 10px 0}
html.th-lemon .dj-reader-card h4{color:#a8842a}
.dj-reader-x{position:absolute;top:12px;right:12px;background:none;border:none;color:var(--muted);font-size:15px;cursor:pointer;padding:4px 7px;border-radius:8px}
.dj-reader-x:hover{color:var(--text)}
/* Dream prose formatting OUTSIDE chat bubbles (journal + book reader) — review #17/#18: the
   paragraph gap and spoiler/highlight ink were bubble-scoped, so dreams rendered as one dense
   block with spoilers in plain sight. currentColor-on-currentColor hides spoiler text without
   needing the per-bubble repaint machinery. */
#scDreamJournal .msg-para+.msg-para,.dj-reader-body .msg-para+.msg-para{margin-top:10px}
#scDreamJournal .sp-spoiler,.dj-reader-body .sp-spoiler{background:currentColor;border-radius:4px;padding:0 4px;cursor:pointer;transition:background .2s}
#scDreamJournal .sp-spoiler.revealed,.dj-reader-body .sp-spoiler.revealed{background:rgba(212,175,55,.16)}
#scDreamJournal .sp-highlight,.dj-reader-body .sp-highlight{background:rgba(242,192,91,.3);border-radius:3px;padding:0 3px}
.hp-sec-t{font-family:'Quicksand','DM Sans',sans-serif;font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
.hp-feed-msg{display:flex;gap:10px;align-items:flex-start;padding:4px 0}
/* Classy hairline between feed entries — 1px, fading out at both ends (her round 13). */
.hp-feed-msg+.hp-feed-msg{margin-top:8px;padding-top:10px;background:linear-gradient(90deg,transparent,rgba(204,190,162,.28) 50%,transparent) top/100% 1px no-repeat}
html.th-lemon .hp-feed-msg+.hp-feed-msg{background:linear-gradient(90deg,transparent,rgba(122,106,58,.35) 50%,transparent) top/100% 1px no-repeat}
.hpf-t{font-family:'Merriweather',Georgia,serif;font-size:10px;color:var(--muted);flex-shrink:0;padding-top:2px}
.hpf-x{font-family:'Raleway','DM Sans',sans-serif;font-size:12px;line-height:1.45;color:var(--text);flex:1;min-width:0}
.hpf-thumb{width:44px;height:44px;border-radius:10px;object-fit:cover;float:left;margin:2px 9px 4px 0;cursor:zoom-in;border:1px solid rgba(120,104,70,.35);transition:box-shadow .15s} /* floats so the text WRAPS around it (her round 12) */
.hpf-thumb:hover{box-shadow:0 0 10px 1px rgba(242,192,91,.35)}
.hpf-act{display:flex;gap:14px;margin-top:5px;clear:left;justify-content:center;opacity:0;transition:opacity .18s} /* centered under the text (her round 14 — right-aligned "not really nice"), hover-reveal */
.hp-feed-msg:hover .hpf-act{opacity:1}
@media (hover:none){.hpf-act{opacity:.65}} /* touch has no hover — keep them faintly present */
.hpf-btn{background:none;border:none;padding:0;font-family:'Quicksand','DM Sans',sans-serif;font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);cursor:pointer;opacity:.75;transition:opacity .15s}
.hpf-btn:hover{opacity:1}
.home-page .hpf-btn{color:#ccbea2}
html.th-lemon .home-page .hpf-btn{color:#7a6a3a}
.home-page .hpf-btn[data-act="dismiss"],html.th-lemon .home-page .hpf-btn[data-act="dismiss"]{color:#a34655} /* the wine (her call) — after the theme rules so it wins in both */
.hp-task{display:flex;align-items:center;gap:9px;padding:3px 0}
.hp-task svg{width:15px;height:15px;flex-shrink:0}
.hpt-m{flex:1;min-width:0}
.hpt-l{font-size:12px;font-weight:600;color:var(--text)}
.hpt-s{font-size:9px;color:var(--muted)}
.hpt-x{background:none;border:1px solid rgba(163,70,85,.4);color:#a34655;width:20px;height:20px;border-radius:50%;cursor:pointer;font-size:10px;flex-shrink:0;padding:0;display:flex;align-items:center;justify-content:center;line-height:1} /* flex-centered ✕ (her fix: glyph sat low on desktop) */
.hpt-x:hover{background:rgba(163,70,85,.12)}
.hp-doors{display:grid;grid-template-columns:1fr 1fr;gap:11px;animation:figFadeIn 1.2s ease .35s both}
.hp-door{position:relative;border-radius:20px;overflow:hidden;border:1px solid var(--ctrl-b);background:linear-gradient(135deg,#3a2030,#6b3a2a 55%,#1c2e28);min-height:104px;cursor:pointer;background-size:cover;background-position:center}
.hp-door-cap{position:absolute;inset:auto 0 0 0;padding:20px 14px 10px;background:linear-gradient(transparent,rgba(8,6,4,.85))}
.hp-door-cap .hp-sec-t{color:#e5e2db;margin-bottom:1px}
.hp-door-sub{font-size:9px;color:rgba(229,226,219,.65)}
.hp-door-j{min-height:104px;cursor:pointer}
.hp-j-line{font-family:'Cormorant Upright',Georgia,serif;font-style:italic;font-size:13px;color:var(--muted);line-height:1.4}
/* The Memory page (Home → Dream journal door) */
.hp-back{align-self:flex-start;background:none;border:1px solid var(--ctrl-b);color:var(--muted);font-family:'Quicksand','DM Sans',sans-serif;font-size:11px;font-weight:600;letter-spacing:.06em;padding:6px 14px;border-radius:999px;cursor:pointer;grid-column:1/-1;justify-self:start}
.hp-back:hover{color:var(--accent);border-color:var(--accent)}
.memory-page .hp-card{padding:16px 20px}
.memory-page .hp-card h2{font-family:'Cormorant Upright','Merriweather',Georgia,serif;color:var(--accent);font-size:26px;margin-bottom:6px}
/* Home tile in the left panel (under the story avatars) */
.lp-home-tile{display:flex;align-items:center;gap:8px;margin:2px 14px 6px;padding:8px 14px;border-radius:14px;background:color-mix(in srgb,var(--input-bg) 40%,transparent);border:1px solid var(--ctrl-b);cursor:pointer;transition:border-color .2s,box-shadow .2s}
.lp-home-tile:hover{border-color:var(--accent);box-shadow:0 0 10px 1px rgba(242,192,91,.25)}
.lht-dot{width:7px;height:7px;border-radius:50%;background:#27aa89;box-shadow:0 0 8px 1px rgba(39,170,137,.55);animation:hcPulse 1.1s ease-in-out infinite;flex-shrink:0}
.lht-label{font-family:'Quicksand','DM Sans',sans-serif;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text)}
.lht-mood{font-family:'Cormorant Upright',Georgia,serif;font-style:italic;font-size:13px;color:#27aa89;margin-left:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Desktop home (her round 2): a real two-column layout, not a stranded phone strip. Clock,
   lifeline and rooms span the width; From him and Scheduled sit side by side; doors span. */
@media (min-width:900px){
  .hp-col{max-width:880px;display:grid;grid-template-columns:1fr 1fr;gap:14px;align-content:start;padding:calc(10px + env(safe-area-inset-top,0px)) 24px calc(20px + env(safe-area-inset-bottom,0px))} /* her exact devtools numbers (round 26) */
  .hp-clockwrap,.hp-life,.hp-rooms,.hp-doors,#hpFeedCard,#hpTasksCard{grid-column:1/-1} /* From him spans the page (her round 3 — the half-width card looked strange) */
  .hp-clock{font-size:64px}
  .hp-rooms{justify-content:center}
}
.mem-count{font-size:10px;color:#a58034;font-weight:400;margin-left:6px}
/* Memory confirm overlay */
.mem-confirm-ov{position:fixed;inset:0;background:rgba(0,0,0,.6);display:none;align-items:center;justify-content:center;z-index:9990}
.mem-confirm-ov.show{display:flex}
.mem-confirm{background:var(--bg2);border:1px solid var(--border);border-radius:16px;width:400px;max-width:95vw;padding:24px}
.mem-confirm h3{font-family:'Cormorant Upright','Merriweather',Georgia,serif;color:var(--accent);font-size:16px;margin-bottom:12px;text-align:center}
.mem-confirm textarea{width:100%;background:var(--input-bg);border:1px solid var(--ctrl-b);color:var(--text);border-radius:10px;padding:10px 13px;font-family:inherit;font-size:13px;outline:none;resize:vertical;min-height:124px;line-height:1.4}
.mem-confirm-btns{display:flex;gap:8px;margin-top:14px}
.mem-confirm-btns button{flex:1;padding:10px;border-radius:10px;font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;border:1px solid var(--ctrl-b)}
.mc-cancel{background:var(--ctrl);color:var(--text)}
.mc-save{background:var(--accent);color:#111;border-color:var(--accent)}
.mem-count{font-size:10px;color:#a58034;font-weight:400;margin-left:6px}
/* Memory confirm overlay */
.mem-confirm-ov{position:fixed;inset:0;background:rgba(0,0,0,.6);display:none;align-items:center;justify-content:center;z-index:9990}
.mem-confirm-ov.show{display:flex}
.mem-confirm{background:var(--bg2);border:1px solid var(--border);border-radius:16px;width:400px;max-width:95vw;padding:24px}
.mem-confirm h3{font-family:'Cormorant Upright','Merriweather',Georgia,serif;color:var(--accent);font-size:16px;margin-bottom:12px;text-align:center}
.mem-confirm textarea{width:100%;background:var(--input-bg);border:1px solid var(--ctrl-b);color:var(--text);border-radius:10px;padding:10px 13px;font-family:inherit;font-size:13px;outline:none;resize:vertical;min-height:124px;line-height:1.4}
.mem-confirm-btns{display:flex;gap:8px;margin-top:14px}
.mem-confirm-btns button{flex:1;padding:10px;border-radius:10px;font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;border:1px solid var(--ctrl-b)}
.mc-cancel{background:var(--ctrl);color:var(--text)}
.mc-save{background:var(--accent);color:#111;border-color:var(--accent)}
/* Settings overlay — compact modal like ChatGPT */
.settings-ov{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9950;display:none;align-items:center;justify-content:center;backdrop-filter:blur(4px)}
.settings-ov.show{display:flex}
.settings-modal{background:var(--bg2);border:1px solid var(--border);border-radius:16px;width:780px;max-width:95vw;height:560px;max-height:85vh;display:flex;overflow:hidden;box-shadow:0 16px 48px rgba(0,0,0,.5);position:relative}
/* (Removed .settings-modal::after / .show-fade rules — outer fade was covering the Appearance tab/menu. Same place as the removed inner ::before fade.) */
.settings-nav{width:200px;min-width:160px;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;padding:16px 0;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--scroll) transparent}
.settings-nav::-webkit-scrollbar{width:4px}
.settings-nav::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:4px}
/* Compacted 2026-07-09 (her call: the new nav row overflowed the panel by a hair and summoned a
   scrollbar — trimmed header/item/separator spacing to win the height back with margin to spare). */
.settings-nav-hdr{padding:10px 20px 10px;font-family:'Cormorant Upright','Merriweather',Georgia,serif;font-size:20px;font-weight:600;color:var(--accent);display:flex;align-items:center;justify-content:space-between}
.settings-nav-hdr button{background:none;border:none;color:var(--muted);cursor:pointer;font-size:18px;padding:4px 8px;border-radius:6px}
.settings-nav-hdr button:hover{color:var(--text);background:rgba(var(--ov),.06)}
.sn-sep{height:1px;background:rgba(var(--ov),.06);margin:5px 16px}
.sn-item{display:flex;align-items:center;gap:10px;padding:8px 20px;cursor:pointer;transition:background .12s;color:var(--text);font-size:13px;font-weight:500;border-left:3px solid transparent}
.sn-item:hover{background:rgba(var(--ov),.04)}
.sn-item.active{background:rgba(var(--ov),.06);border-left-color:var(--accent);color:var(--accent)}
.sn-item .sn-av{width:28px;height:28px;border-radius:50%;flex-shrink:0;display:grid;place-items:center;font-size:11px;font-weight:700;overflow:hidden}
.sn-item .sn-av img{width:100%;height:100%;object-fit:cover}
/* Settings-nav people share ONE story row (her 2026-07-09 refinement: circles side by side like
   the left panel), each circle its own tab button. Identity rings — Ksena golden (accent),
   Dieter viridian — glow on hover/active, exactly the story-row language. */
.sn-people{display:flex;gap:20px;justify-content:center;align-items:flex-start;padding:6px 10px 6px}
.sn-item.sn-person{flex-direction:column;gap:5px;padding:0;border-left:none;align-items:center;min-width:56px;font-size:11px;font-weight:600}
.sn-item.sn-person:hover,.sn-item.sn-person.active{background:none;border-left:none}
.sn-person .sn-av{width:44px;height:44px;font-size:15px;box-sizing:content-box;border:2px solid var(--border);transition:border-color .2s,box-shadow .2s} /* SOLID ring (her fix: translucent ring let the square image corners ghost through) */
.sn-person .sn-av img{border-radius:50%} /* clip the image itself too — corner-leak antialiasing guard */
/* Identity colors AT REST too (her call: Dieter stays viridian, so Ksena stays golden) — hover/active adds the glow. */
.sn-ksena .sn-av{border-color:var(--accent)}
.sn-dieter .sn-av{border-color:#27aa89}
.sn-person .sn-pname{max-width:72px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:color .2s}
.sn-ksena:hover .sn-av,.sn-ksena.active .sn-av{border-color:var(--accent);box-shadow:0 0 12px 2px rgba(242,192,91,.45);background:rgba(242,192,91,.10)}
.sn-dieter:hover .sn-av,.sn-dieter.active .sn-av{border-color:#27aa89;box-shadow:0 0 12px 2px rgba(39,170,137,.45);background:rgba(39,170,137,.14)}
.sn-ksena:hover .sn-pname,.sn-ksena.active .sn-pname{color:var(--accent)}
.sn-dieter:hover .sn-pname,.sn-dieter.active .sn-pname{color:#27aa89}
.sn-item .sn-icon{width:28px;height:28px;border-radius:50%;flex-shrink:0;display:grid;place-items:center;background:rgba(var(--ov),.06);color:var(--muted)}
.sn-item .sn-icon svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.settings-content{flex:1;overflow-y:auto;padding:24px 32px 40px;scrollbar-width:thin;scrollbar-color:var(--scroll) transparent}
/* Sticky wrap around the Appearance preview (per Ksena's inspect-element tuning).
   - top:-26 pulls the wrap's pin point up so its padding-top covers settings-content's padding-top gap.
   - padding:16 all sides creates a bg2 buffer around the inner preview so its box-shadow renders inside
     the wrap (no clipping when the wrap pins).
   - margin:0 -32px 14px extends sideways past settings-content's horizontal padding edges; bottom 14
     pairs with the ::after fade for a soft hand-off to the menu items below. */
.sc-preview-wrap{position:sticky;top:-26px;z-index:8;background:var(--bg2);margin:0 -32px 14px;padding:16px}
.sc-preview-wrap::after{content:'';position:absolute;left:0;right:0;top:100%;height:14px;background:linear-gradient(var(--bg2),transparent);pointer-events:none}
.settings-content::-webkit-scrollbar{width:6px}
.settings-content::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:6px}
.settings-content>*{max-width:600px}
.settings-content h2{font-family:'Cormorant Upright','Merriweather',Georgia,serif;font-size:22px;color:var(--accent);margin-bottom:20px;font-weight:600}
.sc-section{padding-bottom:20px;border-bottom:1px solid rgba(var(--ov),.06)} /* margin-bottom:24 removed per Ksena's inspect-element tuning — sections now butt up against each other (only the 1px border separates them), which made the Appearance preview's transition cleaner */
.sc-section:last-child{border-bottom:none}
.sc-section h3{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-bottom:12px}
.sc-field{margin-bottom:14px}
.sc-label{font-size:11px;color:var(--muted);margin-bottom:5px;display:block}
.sc-input{width:100%;background:rgba(var(--ov),.04);color:var(--text);border:1px solid rgba(var(--ov),.08);border-radius:8px;padding:8px 12px;font-size:13px;font-family:inherit;outline:none;transition:border-color .15s}
/* Password "show/hide" toggle. _attachPwToggle (defined in head) wraps a password <input> in
   .pw-wrap and inserts a .pw-toggle button at the right edge — clicking flips input.type
   between "password" and "text". Padding-right on the input reserves room for the button. */
.pw-wrap{position:relative;display:inline-block;vertical-align:middle}
.pw-wrap>input{padding-right:36px!important}
.pw-toggle{position:absolute;right:4px;top:50%;transform:translateY(-50%);width:28px;height:28px;padding:0;border:none;background:none;cursor:pointer;color:var(--muted);display:grid;place-items:center;border-radius:6px}
.pw-toggle:hover{color:var(--text);background:rgba(var(--ov),.06)}
.pw-toggle svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
/* Eye toggle on the dark golden login card. Muted by default, brighter on hover — matches the rest of the form. */
.kxd-login-card .pw-toggle{color:#80703a}
.kxd-login-card .pw-toggle:hover{color:#f2c05b;background:rgba(242,192,91,.10)}
/* CSS-mask used by the API key field. type=text + this class = visually masked but Chrome
   doesn't see it as a password field, so it stops offering to autofill saved login passwords.
   Placeholder stays readable so "Paste key..." is visible when empty. */
.api-key-mask{-webkit-text-security:disc;text-security:disc;-moz-text-security:disc}
.api-key-mask::placeholder{-webkit-text-security:none;text-security:none}
.api-key-mask.revealed{-webkit-text-security:none;text-security:none}

/* helper kept inline in head so any inline script later in the page can call it */
.sc-input:focus{border-color:var(--accent)}
.vision-ind{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;margin-left:6px;color:var(--muted);opacity:.4;transition:color .2s,opacity .2s;vertical-align:middle;cursor:help}
.vision-ind.on{color:var(--accent);opacity:1}
.vision-ind svg{width:16px;height:16px}
textarea.sc-input{min-height:0;resize:none;line-height:1.4;overflow:hidden}
/* Rich-editor variant of a settings field (System Core Prompt): grows with content, capped, scrolls. */
.sc-rich{min-height:140px;max-height:46vh;overflow-y:auto;line-height:1.5}
.sc-avatar-row{display:flex;align-items:center;gap:14px;margin-bottom:14px}
.sc-avatar{width:56px;height:56px;border-radius:50%;overflow:hidden;display:grid;place-items:center;font-weight:700;font-size:18px;flex-shrink:0}
.sc-avatar img{width:100%;height:100%;object-fit:cover}
.sc-avatar-btns{display:flex;gap:6px}
.sc-avatar-btns button{padding:6px 12px;border-radius:8px;font-size:11px;font-weight:600;font-family:inherit;cursor:pointer;border:1px solid rgba(var(--ov),.1);background:rgba(var(--ov),.04);color:var(--text);transition:all .15s}
.sc-avatar-btns button:hover{background:rgba(var(--ov),.08)}
/* Right panel hide/show */
.rp-toggle{position:absolute;right:8px;top:11px;width:28px;height:28px;border-radius:50%;background:var(--bg2);border:1px solid var(--border);color:var(--muted);cursor:pointer;display:grid;place-items:center;z-index:7;transition:all .15s} /* top:11px centers it in the ~50px-tall chatbar. z7: the chat header (.cp-header) is z4 with an OPAQUE chrome background — at the old z3 the button sat BEHIND it and was only visible on the welcome page (whose header area differs); it was never removed from the DOM, just covered. */
@media(max-width:768px){.rp-toggle{display:none!important}} /* desktop-only feature — on mobile the right panel is a modal slide-in, no expand/collapse needed */
.rp-toggle:hover{color:var(--accent);border-color:var(--accent)}
.rp-toggle svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
/* DESKTOP-ONLY (2026-08-12): the width-collapse itself must be scoped too. Unscoped, `.panel-right.collapsed`
   (two classes) outranks the mobile media query's `.panel-right{width:88vw}` (one class) — so once the desktop
   toggle stored kxd.rpHidden=1, the MOBILE right panel slid in at zero width forever: scrim visible, panel
   invisible, both the session menu and the character card "dead". The toggle is display:none on mobile, so
   collapse has no meaning there at all. */
@media(min-width:769px){.panel-right.collapsed{width:0;border-left:none;box-shadow:none}}
/* DESKTOP-ONLY (2026-07-07): this width-collapse transition used to be unscoped, and being later in
   the stylesheet it overrode the mobile rule's `transition:transform .3s` (same specificity, later
   wins) — which is why the mobile right panel SNAPPED open instead of sliding. Scoped to desktop,
   the mobile slide-in keeps its transform transition. */
@media(min-width:769px){.panel-right{transition:width .2s,min-width .2s,padding .2s}}
.panel-center{position:relative}
/* Read-only profile styles */
.rp-profile{padding:4px 16px 24px;display:flex;flex-direction:column;align-items:center;gap:12px}
.rp-profile .rp-av-big{width:72px;height:72px;border-radius:50%;overflow:hidden;display:grid;place-items:center;font-weight:700;font-size:24px}
.rp-profile .rp-av-big img{width:100%;height:100%;object-fit:cover}
.rp-profile .rp-name-big{font-family:'Cormorant Upright','Merriweather',Georgia,serif;font-size:18px;font-weight:600;color:var(--accent)}
.rp-profile .rp-platform{font-size:11px;color:var(--muted);text-align:center}
.rp-prompt-preview{margin:0 16px;padding:12px;background:rgba(var(--ov),.03);border:1px solid rgba(var(--ov),.06);border-radius:10px;font-size:11px;color:var(--muted);line-height:1.5;overflow-y:auto;white-space:pre-wrap;scrollbar-width:thin;scrollbar-color:var(--scroll) transparent}
.rp-prompt-preview::-webkit-scrollbar{width:4px}
.rp-prompt-preview::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:4px}
/* ── Unified themed scrollbars ────────────────────────────────────────────────────────────────
   One place that standardizes every scrollable surface: gold thumb, rounded, transparent track,
   thin on Firefox. Placed after all earlier per-element scrollbar rules so it overrides their
   drifting widths (4/5/6px) to a single 6px, AND adds the two dropdown panels that had no themed
   scrollbar at all (the session model list Ksena flagged). Later + equal specificity = this wins. */
.messages,.rp-body,.lp-list,.comp-input,.emoji-body,.mem-list,.settings-nav,.settings-content,.rp-prompt-preview,.model-pick-panel,.fancy-sel-panel{scrollbar-width:thin;scrollbar-color:var(--scroll) transparent}
.messages::-webkit-scrollbar,.rp-body::-webkit-scrollbar,.lp-list::-webkit-scrollbar,.comp-input::-webkit-scrollbar,.emoji-body::-webkit-scrollbar,.mem-list::-webkit-scrollbar,.settings-nav::-webkit-scrollbar,.settings-content::-webkit-scrollbar,.rp-prompt-preview::-webkit-scrollbar,.model-pick-panel::-webkit-scrollbar,.fancy-sel-panel::-webkit-scrollbar{width:6px;height:6px}
.messages::-webkit-scrollbar-thumb,.rp-body::-webkit-scrollbar-thumb,.lp-list::-webkit-scrollbar-thumb,.comp-input::-webkit-scrollbar-thumb,.emoji-body::-webkit-scrollbar-thumb,.mem-list::-webkit-scrollbar-thumb,.settings-nav::-webkit-scrollbar-thumb,.settings-content::-webkit-scrollbar-thumb,.rp-prompt-preview::-webkit-scrollbar-thumb,.model-pick-panel::-webkit-scrollbar-thumb,.fancy-sel-panel::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:6px}
.messages::-webkit-scrollbar-track,.rp-body::-webkit-scrollbar-track,.lp-list::-webkit-scrollbar-track,.comp-input::-webkit-scrollbar-track,.emoji-body::-webkit-scrollbar-track,.mem-list::-webkit-scrollbar-track,.settings-nav::-webkit-scrollbar-track,.settings-content::-webkit-scrollbar-track,.rp-prompt-preview::-webkit-scrollbar-track,.model-pick-panel::-webkit-scrollbar-track,.fancy-sel-panel::-webkit-scrollbar-track{background:transparent}
/* margin-top:24px gives breathing room between the prompt-preview block and the row of
   edit links below ("Edit in Settings <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" style="width:9px;height:9px;vertical-align:-1px;margin-left:3px"><path d="M7 17 17 7"/><path d="M8 7h9v9"/></svg>", "Appearance <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" style="width:9px;height:9px;vertical-align:-1px;margin-left:3px"><path d="M7 17 17 7"/><path d="M8 7h9v9"/></svg>", etc.). Stacked links override
   to a tighter gap inline so they don't all float 24px apart. */
.rp-edit-link{font-size:11px;color:var(--accent);cursor:pointer;margin-top:24px;text-align:center;opacity:.6;transition:opacity .15s}
/* Breathing room below the last link in the profile/session card so it doesn't kiss the
   next element (or the panel's bottom edge) directly. Tight 4px stacking between adjacent
   links is preserved by the `.rp-edit-link+.rp-edit-link` rule below. */
.rp-edit-link:last-child{margin-bottom:24px}
/* Subsequent stacked links: tight 4px gap between siblings. */
.rp-edit-link+.rp-edit-link{margin-top:4px}
.rp-edit-link:hover{opacity:1}
.settings-mobile-close,.settings-mobile-header{display:none}
@media(max-width:768px){
  .settings-modal{flex-direction:column;width:95vw;height:85vh;position:relative}
  /* Mobile gets a real header row: title on the left, round ✕ on the right.
     This replaces the previous absolute-positioned X that overlapped the Appearance tab. */
  .settings-mobile-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px 10px;border-bottom:1px solid var(--border);flex-shrink:0}
  .settings-mobile-header span{font-family:'Cormorant Upright','Merriweather',Georgia,serif;font-size:20px;font-weight:600;color:var(--accent);line-height:1}
  .settings-mobile-close{display:grid;width:32px;height:32px;border-radius:50%;background:rgba(var(--ov),.06);border:1px solid var(--border);color:var(--muted);font-size:14px;font-weight:600;cursor:pointer;place-items:center;font-family:inherit;flex-shrink:0;padding:0}
  .settings-mobile-close:active{color:var(--text);border-color:var(--accent)}
  .settings-nav{width:100%;min-width:0;flex-direction:row;overflow-x:auto;overflow-y:hidden;padding:8px 0;border-right:none;border-bottom:1px solid var(--border);flex-shrink:0}
  .settings-nav-hdr{display:none}
  .sn-item{flex-direction:column;gap:4px;padding:8px 14px;font-size:12px;border-left:none;border-bottom:2px solid transparent;text-align:center;line-height:1.2} /* font-size bumped from 11→12 for mobile readability; white-space:nowrap removed so multi-word labels like "Custom instructions" can wrap to a second line */
  .sn-item.active{border-bottom-color:var(--accent);border-left-color:transparent}
  .sn-item.sn-person.active{border-bottom-color:transparent} /* people indicate via their identity ring, not the tab underline */
  .sn-sep{display:none}
  .settings-content{padding:16px}
  /* Settings-content padding shrinks to 16px on mobile, so the preview wrap's negative side-margin must shrink too */
  /* Mobile settings-content padding is 16px (vs 24/32 on desktop), so wrap's negative side-margins shrink to match. */
  .sc-preview-wrap{margin:0 -16px 14px;padding:16px}
}
.token-bar .tk-toggle{background:none;border:1px solid rgba(var(--ov),.1);color:var(--muted);font-size:10px;padding:2px 8px;border-radius:6px;cursor:pointer;font-family:inherit}
.token-bar .tk-toggle:hover{color:var(--text);border-color:rgba(var(--ov),.2)}
.tk-btn-float{position:fixed;bottom:8px;right:8px;width:28px;height:28px;border-radius:50%;background:var(--ctrl);border:1px solid var(--ctrl-b);color:var(--muted);display:grid;place-items:center;cursor:pointer;z-index:9971;font-size:12px;transition:all .15s;opacity:.4}
.tk-btn-float:hover{opacity:1;color:var(--accent);border-color:var(--accent)}
/*
 * Golden (dark) palette.
 *
 * The old app applies these from an inline script before first paint rather than
 * from CSS, so they are not inside the copied \3c style> blocks — this is the one
 * piece of the design that has to be transcribed rather than copied. Values are
 * verbatim from that bootstrap.
 *
 * Here it is plain CSS keyed off body[data-mode], so there is no boot script and
 * no flash of the light theme.
 */
body[data-mode='dark'] {
  color-scheme: dark;
  --bg: #0a0703;
  --bg2: #0d0a05;
  --bg3: #191610;
  --text: #e5e2db;
  --muted: #ccbea2;
  --border: #3a2f1a;
  --accent: #f2c05b;
  --ctrl: #191610;
  --ctrl-b: #3a2f1a;
  --ctrl-h: #241e12;
  --input-bg: #0d0a05;
  --scroll: #3a2f1a;
  --chrome: #0a0703;
  --text-88: rgba(229, 226, 219, 0.88);
  --text-40: rgba(229, 226, 219, 0.4);
  --ov: 255, 255, 255;
  --panel-shadow-l: 0 0 20px rgba(242, 192, 91, 0.12), 2px 0 8px rgba(0, 0, 0, 0.5);
  --panel-shadow-r: 0 0 20px rgba(242, 192, 91, 0.12), -2px 0 8px rgba(0, 0, 0, 0.5);
}
/*
 * Rules that exist only in v3. Keep this file small and justify every entry —
 * anything here is a place where v3 and Ksena's app can drift apart.
 */


/* Temporary, Phase 1 only: says out loud that sending is not connected yet.
   Delete this rule and the element it styles when generation lands. */
.comp-notice {
  max-width: 770px;
  margin: 0 auto 10px;
  padding: 0 14px;
  color: var(--muted);
  font-size: 12px;
  text-align: center;
}

.comp-notice[hidden] {
  display: none;
}

/*
 * The ONE correction to her copied body rule.
 *
 * v1 ships body{visibility:hidden} and its boot script reveals the page once
 * initialised, so a half-built DOM is never shown. Copied verbatim that hid v3
 * completely - all 88 browser tests failed on it, which is what they are for.
 * v3 renders into an empty #app, so there is nothing half-built to hide.
 *
 * Everything else on her body rule - height, display:flex, the safe-area
 * padding, overscroll-behavior - is now left exactly as she wrote it.
 */
body {
  visibility: visible;
}

/*
 * The right-panel chevron's direction.
 *
 * Her app swaps the icon markup between a right- and left-pointing chevron. That
 * icon is built by script, so it is not in the static markup the extractor reads.
 * Rather than hand-draw two new chevrons — the mistake that produced a wrong
 * gear and a wrong mic — this rotates HER chevron, so the shape stays hers.
 *
 * Points right while the panel is open (push it away), left once it is collapsed
 * (pull it back).
 */
.rp-toggle svg {
  transition: transform 0.15s;
}

.rp-toggle[aria-expanded='true'] svg {
  transform: rotate(-90deg);
}

.rp-toggle[aria-expanded='false'] svg {
  transform: rotate(90deg);
}

/*
 * THE UPWARD DRAG WHEN THE KEYBOARD IS OPEN — investigated, not fixable, closed.
 *
 * Symptom: with the keyboard up, the whole page can be dragged upward from any
 * grab point. Only upward; no horizontal, no downward.
 *
 * Cause: the visible area shrinks to the keyboard-free space, but html keeps the
 * LARGE-viewport height that 100vh reports on iOS. The difference is draggable.
 *
 * NOT a v3 regression. The same diagnostic pill installed in the old app showed
 * identical numbers and identical behaviour, and Ksena reproduced it from a
 * Chrome home-screen shortcut too. It is iOS PWA behaviour, independent of the
 * app and of the browser the shortcut came from.
 *
 * Tried and did NOT work, so nobody repeats them:
 *   - pinning html to visualViewport.height with overflow:hidden
 *   - the same, re-read across the keyboard animation to avoid a stale height
 *   - the documented app-shell lock, html:has(body.kxd-kbd){position:fixed;inset:0}
 *
 * Her Round 30 body pin is already the strongest part of the remedy and is
 * ported. Ksena has judged the remainder acceptable: it went unnoticed in the
 * old app for months and was only found while testing v3.
 *
 * Do not reopen without new evidence — specifically, a fix that is known to work
 * on iOS, not another variation on pinning heights.
 */

/*
 * Settings additions.
 *
 * The shell, nav rail and content pane are all HER classes from kxd-*.css. Only
 * the API-key row is new, because v1 kept that key in the browser and v3 does
 * not, so the field behaves differently: paste, save, and it is gone from the
 * page.
 */
.kxd-key-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-top: 10px;
}

/* Never shows the key itself — only whether one is saved and its last four. */
.kxd-key-status {
  font-size: 11px;
  color: var(--muted);
  line-height: 1.35;
}

/*
 * The row lifts out of the list as its hold-menu opens, then settles back and
 * stays lit for as long as the menu is up.
 *
 * Ksena's ask, 2026-08-15 — not in her original, wanted for a long time: on a
 * long-press the session should feel picked up, "pulled out of the list", so
 * the menu reads as belonging to it. Her words on the first cut: "it needs to
 * pause for a moment when the menu opens and then come back to its natural
 * state with the highlight on." So the lift is a short animation, not a held
 * state; the highlight IS the held state.
 *
 * The highlight is written the way her stylesheet writes every overlay tint —
 * `rgba(var(--ov),.08)` — because `--ov` is a comma triple ("255, 255, 255")
 * and the modern slash form is not valid with it. The first cut used the
 * slash form, the browser dropped the whole declaration, and the row went
 * dark the moment her finger lifted.
 */
@media (pointer: coarse) {
  .sess-entry.menu-open {
    background: rgba(var(--ov), 0.08);
    animation: sess-lift 0.45s ease-out;
  }

  @keyframes sess-lift {
    0% {
      transform: none;
      box-shadow: none;
    }

    35% {
      transform: translateX(6px) scale(1.02);
      box-shadow: 0 8px 24px rgb(0 0 0 / 35%);
    }

    100% {
      transform: none;
      box-shadow: none;
    }
  }
}


/*
 * The look engine (src/features/appearance) now owns spoiler and highlight
 * inks, alignment, gaps and bubbles — generated from her stored looks into
 * #kxdLookStyle. Nothing interim remains here for the chat surface.
 */
